Files
bitvid/tests/lnurl-fetch-error.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 f7f246835a zap/wallet: flag non-zappable Lightning addresses; per-item settings restore
A static client can't fetch LNURL hosts that omit CORS headers, so zaps to
those creators fail. Add a browser-side reachability probe
(checkLightningAddressZappable) and surface it on both sides: the creator
is warned at wallet-connect time if their own address isn't browser-
zappable, and the zapper sees a heads-up when opening a non-reachable
creator's zap dialog.

Also fix encrypted settings restore: the "already offered" flag was
per-pubkey, so once storage was offered the wallet note was never offered
later. Track it per-item so the wallet is offered independently.
Zappability check extracted to walletZappabilityCheck.js for the size cap.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-23 14:53:51 -04:00

102 lines
3.5 KiB
JavaScript

// A recipient's Lightning endpoint being unreachable (down, or — common for
// LNURL — missing CORS headers) makes the browser fetch reject with
// "Failed to fetch". The zap flow should surface a clear, actionable message,
// not the raw error.
import assert from "node:assert/strict";
import test from "node:test";
import { fetchPayServiceData } from "../js/payments/lnurl.js";
test("network/CORS failure throws a friendly, coded error", async () => {
const fetcher = async () => {
throw new TypeError("Failed to fetch");
};
await assert.rejects(
() => fetchPayServiceData("https://example.com/.well-known/lnurlp/x", { fetcher }),
(err) => {
assert.match(err.message, /reach this recipient's Lightning address/i);
assert.equal(err.code, "lnurl-unreachable");
assert.ok(err.cause, "preserves the original error as cause");
return true;
},
);
});
test("a non-OK HTTP response still reports a status error (unchanged)", async () => {
const fetcher = async () => ({ ok: false, status: 502 });
await assert.rejects(
() => fetchPayServiceData("https://example.com/.well-known/lnurlp/x", { fetcher }),
/Failed to load LNURL metadata \(502\)/,
);
});
test("a valid LNURL pay response parses (no false friendly error)", async () => {
const fetcher = async () => ({
ok: true,
json: async () => ({
callback: "https://example.com/cb",
minSendable: 1000,
maxSendable: 100000,
tag: "payRequest",
}),
});
const meta = await fetchPayServiceData("https://example.com/.well-known/lnurlp/x", {
fetcher,
});
assert.equal(meta.callback, "https://example.com/cb");
});
import { checkLightningAddressZappable } from "../js/payments/lnurl.js";
// Helper: a fetcher that returns a valid LNURL pay-service-data JSON response.
function okFetcher({ allowsNostr = true } = {}) {
return async () => ({
ok: true,
status: 200,
json: async () => ({
tag: "payRequest",
callback: "https://host/callback",
minSendable: 1000,
maxSendable: 1000000,
metadata: "[]",
allowsNostr,
nostrPubkey: allowsNostr ? "a".repeat(64) : "",
}),
});
}
test("checkLightningAddressZappable: reachable + Nostr-enabled host -> ok", async () => {
const result = await checkLightningAddressZappable("[email protected]", {
fetcher: okFetcher({ allowsNostr: true }),
});
assert.equal(result.ok, true);
assert.equal(result.reason, "ok");
assert.equal(result.address, "[email protected]");
});
test("checkLightningAddressZappable: CORS/offline host -> not ok, coded", async () => {
const result = await checkLightningAddressZappable("[email protected]", {
fetcher: async () => {
throw new TypeError("Failed to fetch");
},
});
assert.equal(result.ok, false);
assert.equal(result.reason, "lnurl-unreachable");
assert.equal(result.address, "[email protected]");
});
test("checkLightningAddressZappable: reachable but not Nostr-aware -> ok with no-nostr", async () => {
const result = await checkLightningAddressZappable("[email protected]", {
fetcher: okFetcher({ allowsNostr: false }),
});
assert.equal(result.ok, true);
assert.equal(result.reason, "no-nostr");
assert.equal(result.allowsNostr, false);
});
test("checkLightningAddressZappable: invalid address -> not ok", async () => {
const result = await checkLightningAddressZappable("", { fetcher: okFetcher() });
assert.equal(result.ok, false);
assert.equal(result.reason, "invalid-address");
});