Self-heal push subscriptions when the VAPID key changes
A redeploy regenerates VAPID (DB wipe), so a browser's existing subscription was tied to a stale key and the push service rejected sends. subscribePush now recreates the subscription if its applicationServerKey != the current key, and registerSW re-validates on load. (Real fix for the churn is the persistent-disk deploy; this makes re-enabling work correctly meanwhile.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 file changed,
+28
−5
web/static/push.js+28 −5
diff --git a/web/static/push.js b/web/static/push.js| index 1bdd698..5b76bba 100644 |
| --- a/web/static/push.js |
| +++ b/web/static/push.js |
| @@ -68,10 +68,9 @@ export async function registerSW(session) { |
| return; |
| } |
| if (pushEnabled()) { |
| - try { |
| - const sub = await swReg.pushManager.getSubscription(); |
| - if (sub) await sendSubscription(session.token, sub); |
| - } catch {} |
| + // Re-validate the subscription against the current VAPID key (it changes if |
| + // the server DB was reset) and recreate it if stale — self-heals on load. |
| + await subscribePush(session); |
| } |
| } |
| @@ -91,11 +90,20 @@ export async function subscribePush(session) { |
| try { |
| const reg = swReg || (await navigator.serviceWorker.register("/sw.js")); |
| const { publicKey } = await (await fetch("/api/push/vapid")).json(); |
| + const appKey = urlB64ToUint8Array(publicKey); |
| let sub = await reg.pushManager.getSubscription(); |
| + // If an existing subscription was made with a different (old) VAPID key, the |
| + // push service will reject sends — recreate it against the current key. |
| + if (sub && !sameAppServerKey(sub, appKey)) { |
| + try { |
| + await sub.unsubscribe(); |
| + } catch {} |
| + sub = null; |
| + } |
| if (!sub) { |
| sub = await reg.pushManager.subscribe({ |
| userVisibleOnly: true, |
| - applicationServerKey: urlB64ToUint8Array(publicKey), |
| + applicationServerKey: appKey, |
| }); |
| } |
| await sendSubscription(session.token, sub); |
| @@ -106,6 +114,21 @@ export async function subscribePush(session) { |
| } |
| } |
| +function sameAppServerKey(sub, appKey) { |
| + try { |
| + const cur = sub.options && sub.options.applicationServerKey; |
| + if (!cur) return false; |
| + const a = new Uint8Array(cur); |
| + if (a.length !== appKey.length) return false; |
| + for (let i = 0; i < a.length; i++) { |
| + if (a[i] !== appKey[i]) return false; |
| + } |
| + return true; |
| + } catch { |
| + return false; |
| + } |
| +} |
| + |
| export async function isSubscribed() { |
| if (!("serviceWorker" in navigator)) return false; |
| try { |