@getalby/lightning-tools 8.1.1 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/402.js CHANGED
@@ -1257,6 +1257,28 @@ class Invoice {
1257
1257
  }
1258
1258
  }
1259
1259
 
1260
+ /** Apply a previously-obtained credential to the outgoing request headers. */
1261
+ const applyCredentials = (headers, credentials) => {
1262
+ headers.set(credentials.header, credentials.value);
1263
+ };
1264
+ /** Attach payment metadata to a response and return it (typed). */
1265
+ const attachPayment = (response, payment) => {
1266
+ if (payment) {
1267
+ response.payment = payment;
1268
+ }
1269
+ return response;
1270
+ };
1271
+ /** Payment metadata describing a request authorized with a reused credential. */
1272
+ const reusedCredentialPayment = (credentials) => credentials ? { paid: false, amount: 0, credentials } : undefined;
1273
+ /** Satoshi amount of a BOLT11 invoice (0 when it cannot be decoded). */
1274
+ const getInvoiceAmount = (invoice) => {
1275
+ try {
1276
+ return new Invoice({ pr: invoice }).satoshi;
1277
+ }
1278
+ catch (_) {
1279
+ return 0;
1280
+ }
1281
+ };
1260
1282
  function createGuardedWallet(wallet, maxAmountSats) {
1261
1283
  return {
1262
1284
  payInvoice: async (args) => {
@@ -1308,6 +1330,9 @@ const handleL402Payment = async (l402Header, url, fetchArgs, headers, wallet) =>
1308
1330
  const details = parseL402(l402Header);
1309
1331
  const token = details.token || details.macaroon;
1310
1332
  const invoice = details.invoice;
1333
+ // Preserve the scheme the server challenged with (L402 or LSAT) so the
1334
+ // retry's Authorization header matches what the server expects.
1335
+ const scheme = /^\s*LSAT\b/i.test(l402Header) ? "LSAT" : "L402";
1311
1336
  if (!token) {
1312
1337
  throw new Error("L402: missing token/macaroon in WWW-Authenticate header");
1313
1338
  }
@@ -1315,8 +1340,16 @@ const handleL402Payment = async (l402Header, url, fetchArgs, headers, wallet) =>
1315
1340
  throw new Error("L402: missing invoice in WWW-Authenticate header");
1316
1341
  }
1317
1342
  const invResp = await wallet.payInvoice({ invoice });
1318
- headers.set("Authorization", `L402 ${token}:${invResp.preimage}`);
1319
- return fetch(url, fetchArgs);
1343
+ const value = `${scheme} ${token}:${invResp.preimage}`;
1344
+ headers.set("Authorization", value);
1345
+ const response = await fetch(url, fetchArgs);
1346
+ return attachPayment(response, {
1347
+ paid: true,
1348
+ amount: getInvoiceAmount(invoice),
1349
+ feesPaid: invResp.fees_paid,
1350
+ preimage: invResp.preimage,
1351
+ credentials: { header: "Authorization", value },
1352
+ });
1320
1353
  };
1321
1354
  const fetchWithL402 = async (url, fetchArgs, options) => {
1322
1355
  const wallet = options.wallet;
@@ -1330,6 +1363,15 @@ const fetchWithL402 = async (url, fetchArgs, options) => {
1330
1363
  fetchArgs.mode = "cors";
1331
1364
  const headers = new Headers(fetchArgs.headers ?? undefined);
1332
1365
  fetchArgs.headers = headers;
1366
+ // If the caller supplied a credential, we MUST use it and never pay again —
1367
+ // even if the server still responds with a 402. Re-paying here is the exact
1368
+ // double-charge this API exists to prevent; the caller decides what to do
1369
+ // with a rejected credential (retry after settlement, top up, etc.).
1370
+ if (options.credentials) {
1371
+ applyCredentials(headers, options.credentials);
1372
+ const reusedResp = await fetch(url, fetchArgs);
1373
+ return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
1374
+ }
1333
1375
  const initResp = await fetch(url, fetchArgs);
1334
1376
  const header = initResp.headers.get("www-authenticate");
1335
1377
  if (!header) {
@@ -1396,9 +1438,17 @@ const handleX402Payment = async (x402Header, url, fetchArgs, headers, wallet) =>
1396
1438
  if (invoice.amountRaw != requirements.amount) {
1397
1439
  throw new Error(`Invalid invoice amount: ${invoice.amountRaw}. expected ${requirements.amount}`);
1398
1440
  }
1399
- await wallet.payInvoice({ invoice: invoice.paymentRequest });
1400
- headers.set("payment-signature", buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements));
1401
- return fetch(url, fetchArgs);
1441
+ const invResp = await wallet.payInvoice({ invoice: invoice.paymentRequest });
1442
+ const value = buildX402PaymentSignature(requirements.scheme, requirements.network, invoice.paymentRequest, requirements);
1443
+ headers.set("payment-signature", value);
1444
+ const response = await fetch(url, fetchArgs);
1445
+ return attachPayment(response, {
1446
+ paid: true,
1447
+ amount: invoice.satoshi,
1448
+ feesPaid: invResp.fees_paid,
1449
+ preimage: invResp.preimage,
1450
+ credentials: { header: "payment-signature", value },
1451
+ });
1402
1452
  };
1403
1453
  const fetchWithX402 = async (url, fetchArgs, options) => {
1404
1454
  const wallet = options.wallet;
@@ -1409,6 +1459,15 @@ const fetchWithX402 = async (url, fetchArgs, options) => {
1409
1459
  fetchArgs.mode = "cors";
1410
1460
  const headers = new Headers(fetchArgs.headers ?? undefined);
1411
1461
  fetchArgs.headers = headers;
1462
+ // If the caller supplied a credential, we MUST use it and never pay again —
1463
+ // even if the server still responds with a 402. Re-paying here is the exact
1464
+ // double-charge this API exists to prevent; the caller decides what to do
1465
+ // with a rejected credential (retry after settlement, top up, etc.).
1466
+ if (options.credentials) {
1467
+ applyCredentials(headers, options.credentials);
1468
+ const reusedResp = await fetch(url, fetchArgs);
1469
+ return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
1470
+ }
1412
1471
  const initResp = await fetch(url, fetchArgs);
1413
1472
  const header = initResp.headers.get("PAYMENT-REQUIRED");
1414
1473
  if (!header) {
@@ -1555,8 +1614,16 @@ const handleMppChargePayment = async (wwwAuthHeader, url, fetchArgs, headers, wa
1555
1614
  const invResp = await wallet.payInvoice({ invoice });
1556
1615
  // Per spec: Authorization: Payment <base64url-token> (single token, no wrapper)
1557
1616
  const credential = buildMppCredential(challenge, invResp.preimage);
1558
- headers.set("Authorization", `Payment ${credential}`);
1559
- return fetch(url, fetchArgs);
1617
+ const value = `Payment ${credential}`;
1618
+ headers.set("Authorization", value);
1619
+ const response = await fetch(url, fetchArgs);
1620
+ return attachPayment(response, {
1621
+ paid: true,
1622
+ amount: getInvoiceAmount(invoice),
1623
+ feesPaid: invResp.fees_paid,
1624
+ preimage: invResp.preimage,
1625
+ credentials: { header: "Authorization", value },
1626
+ });
1560
1627
  };
1561
1628
  /**
1562
1629
  * Fetch a resource protected by the draft-lightning-charge-00 payment
@@ -1567,9 +1634,11 @@ const handleMppChargePayment = async (wwwAuthHeader, url, fetchArgs, headers, wa
1567
1634
  * the function pays the embedded BOLT11 invoice and retries with the
1568
1635
  * resulting preimage as the credential.
1569
1636
  *
1570
- * Note: lightning-charge uses consume-once challenge semantics each
1571
- * challenge embeds a fresh invoice, so paid credentials cannot be reused.
1572
- * The `store` option is accepted for API consistency but is not used.
1637
+ * Pass a previous credential via `options.credentials` to reuse it (e.g. when
1638
+ * polling); the credential is applied and the function NEVER pays again, even
1639
+ * if the server still responds with a 402 (that response is returned as-is).
1640
+ * Note: lightning-charge typically uses consume-once challenge semantics, so a
1641
+ * reused credential is only accepted by servers that explicitly support it.
1573
1642
  */
1574
1643
  const fetchWithMpp = async (url, fetchArgs, options) => {
1575
1644
  const wallet = options.wallet;
@@ -1583,6 +1652,15 @@ const fetchWithMpp = async (url, fetchArgs, options) => {
1583
1652
  fetchArgs.mode = "cors";
1584
1653
  const headers = new Headers(fetchArgs.headers ?? undefined);
1585
1654
  fetchArgs.headers = headers;
1655
+ // If the caller supplied a credential, we MUST use it and never pay again —
1656
+ // even if the server still responds with a 402. Re-paying here is the exact
1657
+ // double-charge this API exists to prevent; the caller decides what to do
1658
+ // with a rejected credential (retry after settlement, top up, etc.).
1659
+ if (options.credentials) {
1660
+ applyCredentials(headers, options.credentials);
1661
+ const reusedResp = await fetch(url, fetchArgs);
1662
+ return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
1663
+ }
1586
1664
  const initResp = await fetch(url, fetchArgs);
1587
1665
  const wwwAuthHeader = initResp.headers.get("www-authenticate");
1588
1666
  if (!wwwAuthHeader ||
@@ -1603,6 +1681,15 @@ const fetch402 = async (url, fetchArgs, options) => {
1603
1681
  fetchArgs.mode = "cors";
1604
1682
  const headers = new Headers(fetchArgs.headers ?? undefined);
1605
1683
  fetchArgs.headers = headers;
1684
+ // If the caller supplied a credential, we MUST use it and never pay again —
1685
+ // even if the server still responds with a 402. Re-paying here is the exact
1686
+ // double-charge this API exists to prevent; the caller decides what to do
1687
+ // with a rejected credential (retry after settlement, top up, etc.).
1688
+ if (options.credentials) {
1689
+ applyCredentials(headers, options.credentials);
1690
+ const reusedResp = await fetch(url, fetchArgs);
1691
+ return attachPayment(reusedResp, reusedCredentialPayment(options.credentials));
1692
+ }
1606
1693
  const initResp = await fetch(url, fetchArgs);
1607
1694
  // L402 / LSAT: dedicated scheme, dispatch directly.
1608
1695
  const wwwAuthHeader = initResp.headers.get("www-authenticate");
@@ -1710,5 +1797,5 @@ function parseL402Authorization(input) {
1710
1797
  };
1711
1798
  }
1712
1799
 
1713
- export { createGuardedWallet, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, issueL402Macaroon, makeL402AuthenticateHeader, parseL402, parseL402Authorization, verifyL402Macaroon };
1800
+ export { applyCredentials, attachPayment, createGuardedWallet, fetch402, fetchWithL402, fetchWithMpp, fetchWithX402, findX402LightningRequirements, getInvoiceAmount, issueL402Macaroon, makeL402AuthenticateHeader, parseL402, parseL402Authorization, reusedCredentialPayment, verifyL402Macaroon };
1714
1801
  //# sourceMappingURL=402.js.map