From 520aa7a1e39dbe9d8e0fd44b5cc3ce06e79f5abf Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 10 Nov 2026 07:35:30 -0700 Subject: A zap answered over NWC waits on far fewer round trips, and a phone with Coinos behind another app answers from its worker. "I'm on V3 … I find nwc zaps a lot more slower than usual" (398ja, 2026-11-10). Measured from the notifier's wakes and the ASP's log over four days: a closed app took about six seconds from the push to its cosign (the Lightning part after that is one and a half), an Android phone fifteen at the median, up to thirty. Where it went: every call to the ASP and the notifier was two round trips (no Access-Control-Max-Age, so Chrome re-asked the CORS preflight after five seconds; a Cloudflare response-header rule now sets 7200 on OPTIONS for ark.coinos.io, ark-staging and nwcpush), and the worker made them one after another: answered?, GetArkInfo, Handshake, claim, a save, the chain tip, then the payment, then status ("pending"), the tip again, the held status call, and status once more for the preimage it had just been handed. Now: what the mirror alone decides (invoice, network, budgets, coins, keys) comes first with no network; the claim (which now also says "already answered", coinosv3-server 3c5e9c3) goes out beside the manager's start, whose info call, handshake and tip run together (init, warmTip/_tipFresh); a send just initiated returns to its caller instead of asking at once, and awaitLnPay settles from the held call's own answer. A worker-style send on regtest: eight calls, six in sequence, where there were thirteen. The app's own answer no longer waits a second on a phone that is on screen (only one behind another app holds back for a computer). Android freezes an app behind another one, and the worker's nudge to it sat unread until it was opened: on a phone the worker now hands a request only to a window on screen (as it already did on iOS), nudges a hidden one in case it is awake, and answers itself; the request's lock decides between them. For that the mirror is no longer written over the worker's hand: the worker stamps its writes (workerAt), the app writes under the worker's own lock (nwc-bg-work) and folds the worker's state in first (never mid-payment), carries the stamp, absorbs it when it comes back on screen (arkBgAbsorb), and refreshes the mirror as it goes behind another app. Tests: ln-pay-double-wake (the one held call settles; a failed or mismatched answer doesn't), sw-push-quota (an Android phone's hidden window), nwc-sw-test's notifier stand-in answers /claim as the real one; tools/ark/ln-e2e-test's pay half against regtest captaind 0.8 + CLN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.2 --- src/sw-nwc.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/sw-nwc.js') diff --git a/src/sw-nwc.js b/src/sw-nwc.js index a4e4e56..b0ef49f 100644 --- a/src/sw-nwc.js +++ b/src/sw-nwc.js @@ -187,14 +187,23 @@ self.addEventListener('push', (e) => { // An open window handles requests itself with full wallet state — nudge // it and stay out of the way. On iOS only one on screen counts: a // backgrounded app is suspended, so a nudge to it went unanswered. + // Android does the same to an app behind another one, minutes in: the + // page is frozen, its relay sockets dead, and the nudge sat unread until + // the app was opened — the zap spun for as long (wake to payment: 15 s + // at the median on Android phones against 6 for a closed app, 2026-11-10). + // So on any phone only a window on screen is handed the request; one + // behind another app is nudged too, in case it is still awake, and the + // request's lock decides between it and this worker. + const phone = apple || /Mobi|Android/i.test((self.navigator && self.navigator.userAgent) || ''); const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); - const clients = all.filter((c) => !apple || c.visibilityState === 'visible'); + const clients = all.filter((c) => !phone || c.visibilityState === 'visible'); if (clients.length) { for (const c of clients) c.postMessage({ type: 'nwc-wake', servicePubkey: data.servicePubkey }); if (apple) await dropStandIn(); // the window on screen shows it live diary('window on screen — handed over'); return; } + if (!apple) for (const c of all) { try { c.postMessage({ type: 'nwc-wake', servicePubkey: data.servicePubkey }); } catch (_) {} } const notice = (n) => show(n.title, { body: n.body, icon: 'icon-192.png', badge: 'badge-96.png', tag: n.tag || 'nwc-incoming', renotify: !apple, data: { url: './' }, -- cgit v1.2.3