aboutsummaryrefslogtreecommitdiffstats
path: root/src/sw-nwc.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-25 07:46:55 -0700
committerAdam Soltys <[email protected]>2026-10-25 07:46:55 -0700
commit43a35d0fe6abc56b6594015ae1028842c04a2c19 (patch)
treebe56c2bb8f71826df0360b0842ee1bd7fa4b3a3b /src/sw-nwc.js
parent9270bc52a33ef007d57d15b5e6aee95248c368a2 (diff)
downloadcoinosv3-43a35d0fe6abc56b6594015ae1028842c04a2c19.tar.gz
coinosv3-43a35d0fe6abc56b6594015ae1028842c04a2c19.zip
A closed wallet's NWC payments fit iOS's ten-second wake: a paid send is never marked for revocation by a second wake, lookups report outgoing sends and finish them, and pushes no longer run side by side
PullCap's night of 2026-10-24: every wake's diary stopped 8-12 s after its push (iOS stops the worker there, not at 30 s). Two wakes two seconds apart both drove one send; the second's InitiateLightningPayment was refused ("state: ln-spent") and the paid send went to 'revoking', where every later wake spent its whole budget on it, so no request was answered after midnight. - manager: an initiate refused as ln-spent follows the payment ('initiated'); a send in 'revoking' asks the ASP first (paid: settle; in flight before expiry: back to waiting; not found: revoke) - worker: 9 s budget on Apple endpoints (27 s elsewhere) for answers, earlier sends and renewals; every ASP step held to it - lookup_invoice asks every copy of the connection and drives an outgoing send in flight, answering settled with the preimage - a lease in the worker's IndexedDB store serialises pushes (the Web Lock didn't); diary notes a missing Web Locks API - the reply and the mirror save run together; stuck sends are finished before a new payment only when they hold its coins Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4
Diffstat (limited to 'src/sw-nwc.js')
-rw-r--r--src/sw-nwc.js19
1 files changed, 13 insertions, 6 deletions
diff --git a/src/sw-nwc.js b/src/sw-nwc.js
index 78bb2bd..3a48cff 100644
--- a/src/sw-nwc.js
+++ b/src/sw-nwc.js
@@ -6,7 +6,7 @@
import { respondFromBg, tendRenewals, tendLnPays } from './nwc-respond.js';
import { withRequestLock, withWorkLock } from './nwc-lock.js';
-import { appendBgLog } from './nwc-bg.js';
+import { appendBgLog, takeLease } from './nwc-bg.js';
import { bgAutoWithdraw } from './bg-autowithdraw.js';
import { allInboxes, classifyDm, shouldNotifyDm, classifyCall } from './dm-inbox.js';
@@ -15,7 +15,7 @@ const NOTIFIER = 'https://nwcpush.coinos.io';
// written, so a worker the phone stops mid-push leaves its last step behind.
const stamp = () => new Date().toISOString().replace('T', ' ').slice(0, 19);
const diary = (m) => { console.log('[sw-nwc]', m); appendBgLog(`${stamp()} ${m}`); };
-diary('worker started');
+diary(`worker started${typeof navigator !== 'undefined' && navigator.locks ? '' : ' (no Web Locks)'}`);
const CALL_CACHE = 'coinos-call'; // a call offer a push brought, for the app the tap opens
// where a call's offer wrap is fetched from: the notifier, or (the app served
// on this machine, as the tests do) this origin
@@ -179,6 +179,11 @@ self.addEventListener('push', (e) => {
// four payments, no line for the fourth, then no more wakes). On an
// Apple endpoint the stand-in goes up FIRST; the outcome replaces it.
const apple = await applePush();
+ // What a push may take. iOS stops the worker about ten seconds in (PullCap's
+ // diary of 2026-10-24: every wake's last line 8–12 s after its push, in the
+ // middle of a payment), whatever the waitUntil — so there everything is
+ // planned inside nine; elsewhere the usual thirty.
+ const budgetEnd = startedAt + (apple ? 9_000 : 27_000);
const dropStandIn = async () => {
const open = await self.registration.getNotifications({ tag: 'coinos-bg' }).catch(() => []);
for (const n of open) n.close();
@@ -204,10 +209,10 @@ self.addEventListener('push', (e) => {
// has run, cover coins without one. After the answer, inside what is
// left of the push budget.
const tend = async () => {
- const late = await tendLnPays({ deadline: startedAt + 27_000, log: diary }).catch((err) => { diary('earlier sends failed: ' + (err && err.message)); return []; });
+ const late = await tendLnPays({ deadline: budgetEnd, log: diary }).catch((err) => { diary('earlier sends failed: ' + (err && err.message)); return []; });
for (const n of late) await notice(n);
if (late.length && apple) await dropStandIn();
- await tendRenewals({ deadline: startedAt + 27_000, log: (m) => diary('renew: ' + m) })
+ await tendRenewals({ deadline: budgetEnd, log: (m) => diary('renew: ' + m) })
.catch((err) => diary('renewals failed: ' + (err && err.message)));
};
// an NWC wake-up may also have moved money in; check the rule after
@@ -217,10 +222,11 @@ self.addEventListener('push', (e) => {
await withWorkLock(async () => {
const waited = Date.now() - startedAt;
if (waited > 500) diary(`waited ${waited} ms for an earlier push to finish`);
+ if (Date.now() > budgetEnd - 2_000) { diary('no time left after waiting — leaving it to the next try'); return; }
let handled = false;
try {
handled = await withRequestLock(data.event && data.event.id,
- () => respondFromBg(data, { notifier: NOTIFIER, log: diary }),
+ () => respondFromBg(data, { notifier: NOTIFIER, log: diary, deadline: budgetEnd }),
() => { diary('another context holds this request'); return true; });
} catch (err) {
diary('auto-answer failed: ' + (err && err.message));
@@ -243,6 +249,7 @@ self.addEventListener('push', (e) => {
await tend();
return;
}
+ if (Date.now() > budgetEnd) { diary('out of time — leaving it to the next try'); return; }
// A kind-21001 offer request is money coming IN (someone asking us to
// mint an invoice) — "an app wants to pay" would read as the opposite.
const incoming = data.event && data.event.kind === 21001;
@@ -257,7 +264,7 @@ self.addEventListener('push', (e) => {
});
if (apple) await dropStandIn();
await tend();
- });
+ }, { lease: () => takeLease('work', { wait: Math.max(0, budgetEnd - Date.now() - 3_000) }) });
diary(`push done in ${Date.now() - startedAt} ms`);
});
});