From 1ea1784c4ad7d6715c0efdcd0078019c692eefe5 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 22 Oct 2026 10:32:22 -0700 Subject: iPhone: a push subscription iOS dropped is made again when the app opens or comes back WebKit removes a subscription after three pushes that showed no notification (743011c stops new strikes, but a wallet already struck out stayed deaf): background stayed on, getSubscription() came back empty and refreshRegistration gave up, so NWC requests stopped waking the closed app until the switch was toggled (reported by PullCap). It now subscribes again (also when the VAPID key changed) and re-registers with the notifier, on start and whenever the app returns to the screen. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01K5xtiFrkFVsrkWx6JDQjiT --- src/features/nwc.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/features/nwc.js b/src/features/nwc.js index ec79081..9c71a7f 100644 --- a/src/features/nwc.js +++ b/src/features/nwc.js @@ -822,6 +822,25 @@ export function nwcFeature(ctx) { // wrong keys — background wake-ups silently die the first time a // connection is recreated. Debounced; the server replaces by endpoint. let regTimer = null; + // The browser's subscription, made again when it's gone: iOS drops it after + // three pushes that showed no notification (and a key rotation strands an + // old one), and nothing else re-arms a wallet whose `background` is still on. + async function currentSub() { + const reg = await navigator.serviceWorker.ready; + const { publicKey } = await (await fetch(`${NOTIFIER}/vapid`)).json(); + const want = b64ToBytes(publicKey); + let sub = await reg.pushManager.getSubscription(); + if (sub) { + let have = []; + try { have = new Uint8Array(sub.options.applicationServerKey || []); } catch {} + if (have.length === want.length && have.every((b, i) => b === want[i])) return sub.toJSON(); + try { await sub.unsubscribe(); } catch {} + } + if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return null; + sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: want }); + dlog('nwc: push subscription was gone, made a new one'); + return sub.toJSON(); + } function refreshRegistration() { if (!load().background) return; if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return; @@ -831,8 +850,7 @@ export function nwcFeature(ctx) { const pks = watchPks(); if (!pks.length) return; const up2 = upEndpoint(); - const sub = up2 ? { endpoint: up2, unifiedpush: true } - : await (await navigator.serviceWorker.ready).pushManager.getSubscription().then((x) => x && x.toJSON()); + const sub = up2 ? { endpoint: up2, unifiedpush: true } : await currentSub(); if (!sub) return; await fetch(`${NOTIFIER}/register`, { method: 'POST', headers: { 'content-type': 'application/json' }, @@ -949,7 +967,7 @@ export function nwcFeature(ctx) { if (typeof window !== 'undefined') { window.addEventListener('online', () => listen()); document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible' && conns().length) listen(); + if (document.visibilityState === 'visible' && conns().length) { listen(); refreshRegistration(); } }); } } -- cgit v1.2.3