Files
bitvid/tests/lnurl-fetch-error.test.mjs
T
thePR0M3TH3ANandClaude Opus 4.8 dd1ce113c1 zap: graceful error when a recipient's Lightning endpoint is unreachable
Sending a zap to a creator whose LNURL endpoint is down or doesn't send
CORS headers (e.g. unitypay.cash/.well-known/lnurlp/<name>) made the browser
fetch reject with a raw "Failed to fetch", which bubbled up as the zap's
error toast — the "clunky errors" reported during the zap audit.

fetchPayServiceData now catches the network/CORS rejection and throws a
clear, coded error ("Couldn't reach this recipient's Lightning address. It
may be offline or blocking browser requests."), preserving the original as
`cause`. Non-OK HTTP and valid responses are unchanged.

Part of the zap audit (TODO #3). Also recorded: the video-modal popover
mis-positioning (3a) and that the deeper CORS issue (static client can't
fetch CORS-less LNURL hosts) may need a proxy — tracked separately.

Tests + mutation-verified. 1274 pass (known UploadModal flake).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 22:22:18 -04:00

48 lines
1.6 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");
});