aboutsummaryrefslogtreecommitdiffstats
path: root/src/sw-nwc.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/sw-nwc.js')
-rw-r--r--src/sw-nwc.js109
1 files changed, 70 insertions, 39 deletions
diff --git a/src/sw-nwc.js b/src/sw-nwc.js
index 211c4f9..78bb2bd 100644
--- a/src/sw-nwc.js
+++ b/src/sw-nwc.js
@@ -4,12 +4,18 @@
// nwc-respond.js), and only falls back to the wake-the-user notification
// when it can't.
-import { respondFromBg, tendRenewals } from './nwc-respond.js';
-import { withRequestLock } from './nwc-lock.js';
+import { respondFromBg, tendRenewals, tendLnPays } from './nwc-respond.js';
+import { withRequestLock, withWorkLock } from './nwc-lock.js';
+import { appendBgLog } from './nwc-bg.js';
import { bgAutoWithdraw } from './bg-autowithdraw.js';
import { allInboxes, classifyDm, shouldNotifyDm, classifyCall } from './dm-inbox.js';
const NOTIFIER = 'https://nwcpush.coinos.io';
+// The worker's diary (nwc-bg.js): every line also lands in IndexedDB as it is
+// 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');
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
@@ -52,11 +58,12 @@ self.addEventListener('push', (e) => {
// (PullCap's night of 2026-10-22: paid four times, no "Payment sent" seen.)
// Try the full options; refused, try again with the plainest ones.
const show = async (title, opts) => {
- try { await self.registration.showNotification(title, opts); showed = true; return; }
- catch (e) { console.warn('[sw] notification refused:', e && e.message); }
- try { await self.registration.showNotification(title, { body: opts && opts.body, tag: opts && opts.tag, data: opts && opts.data }); showed = true; }
- catch (e) { console.warn('[sw] plain notification refused too:', e && e.message); }
+ try { await self.registration.showNotification(title, opts); showed = true; diary(`notification shown: ${title}`); return; }
+ catch (e) { diary(`notification refused (${title}): ${e && e.message}`); }
+ try { await self.registration.showNotification(title, { body: opts && opts.body, tag: opts && opts.tag, data: opts && opts.data }); showed = true; diary(`plain notification shown: ${title}`); }
+ catch (e) { diary(`plain notification refused too (${title}): ${e && e.message}`); }
};
+ diary(`push ${data.type || '?'}${data.reason ? '/' + data.reason : ''}${data.event && data.event.id ? ' ' + data.event.id.slice(0, 8) : ''}`);
// the stand-in, as soon as a path knows it has nothing to say (a long tail
// of work after it mustn't keep the push waiting for its notification)
const quiet = async () => {
@@ -176,7 +183,7 @@ self.addEventListener('push', (e) => {
const open = await self.registration.getNotifications({ tag: 'coinos-bg' }).catch(() => []);
for (const n of open) n.close();
};
- if (apple && await standIn(false).catch(() => false)) showed = true;
+ if (apple && await standIn(false).catch((e) => { diary('stand-in refused: ' + (e && e.message)); return false; })) { showed = true; diary('stand-in shown'); }
// 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.
@@ -185,49 +192,73 @@ self.addEventListener('push', (e) => {
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;
}
+ 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: './' },
+ });
// A wake is the only moment a closed wallet gets to look after its coins:
- // claim renewals whose round has run, cover coins without one. After the
- // answer, inside what is left of the push budget.
- const tend = () => tendRenewals({ deadline: startedAt + 27_000, log: (m) => console.log('[sw-renew]', m) })
- .catch((err) => console.warn('[sw-renew] failed:', err && err.message));
+ // finish sends an earlier wake left in flight, claim renewals whose round
+ // 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 []; });
+ 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) })
+ .catch((err) => diary('renewals failed: ' + (err && err.message)));
+ };
// an NWC wake-up may also have moved money in; check the rule after
setTimeout(() => { bgAutoWithdraw({ log: (m) => console.log('[sw-aw]', m) }).catch(() => {}); }, 0);
- let handled = false;
- try {
- handled = await withRequestLock(data.event && data.event.id,
- () => respondFromBg(data, { notifier: NOTIFIER, log: (m) => console.log('[sw-nwc]', m) }),
- () => { console.log('[sw-nwc] another context holds this request'); return true; });
- } catch (err) {
- console.warn('[sw-nwc] auto-answer failed:', err && err.message);
- }
- // handled-with-a-heads-up: e.g. an invoice was minted while closed and
- // the user should open coinos to complete the receive
- if (handled && handled.notify) {
- await show(handled.notify.title, {
- body: handled.notify.body,
+ // one push at a time over the shared mirror (a client's retry lands
+ // seconds after its lookup, and each push builds its own manager)
+ await withWorkLock(async () => {
+ const waited = Date.now() - startedAt;
+ if (waited > 500) diary(`waited ${waited} ms for an earlier push to finish`);
+ let handled = false;
+ try {
+ handled = await withRequestLock(data.event && data.event.id,
+ () => respondFromBg(data, { notifier: NOTIFIER, log: diary }),
+ () => { diary('another context holds this request'); return true; });
+ } catch (err) {
+ diary('auto-answer failed: ' + (err && err.message));
+ }
+ // sends from an earlier wake that settled on the way to this answer
+ const late = (handled && handled.notices) || [];
+ for (const n of late) await notice(n);
+ // handled-with-a-heads-up: e.g. an invoice was minted while closed and
+ // the user should open coinos to complete the receive
+ if (handled && handled.notify) {
+ await notice(handled.notify);
+ if (apple) await dropStandIn();
+ await tend();
+ return;
+ }
+ if (handled) {
+ if (late.length && apple) await dropStandIn();
+ diary('answered quietly');
+ await quiet();
+ await tend();
+ 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;
+ diary(incoming ? 'waking the user: incoming payment' : 'waking the user: payment request');
+ await show(incoming ? 'Incoming payment' : 'Payment request', {
+ body: incoming
+ ? 'Someone is trying to send you money. Open coinos so your wallet can receive it.'
+ : 'An app is asking your wallet to pay. Open Coinos to approve.',
icon: 'icon-192.png', badge: 'badge-96.png',
- tag: handled.notify.tag || 'nwc-incoming', renotify: !apple,
+ tag: 'nwc-' + (data.servicePubkey || 'req'), renotify: false,
data: { url: './' },
});
if (apple) await dropStandIn();
await tend();
- return;
- }
- if (handled) { await quiet(); await tend(); 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;
- await show(incoming ? 'Incoming payment' : 'Payment request', {
- body: incoming
- ? 'Someone is trying to send you money. Open coinos so your wallet can receive it.'
- : 'An app is asking your wallet to pay. Open Coinos to approve.',
- icon: 'icon-192.png', badge: 'badge-96.png',
- tag: 'nwc-' + (data.servicePubkey || 'req'), renotify: false,
- data: { url: './' },
});
- if (apple) await dropStandIn();
+ diary(`push done in ${Date.now() - startedAt} ms`);
});
});