diff --git a/lib/__tests__/web-push.test.ts b/lib/__tests__/web-push.test.ts index a8b24b077..a6884a579 100644 --- a/lib/__tests__/web-push.test.ts +++ b/lib/__tests__/web-push.test.ts @@ -6,6 +6,7 @@ import { disableWebPush, enableWebPush, listPushDevices, + isWebPushEnabled, resetWebPushResyncState, resyncWebPush, revokePushDevice, @@ -274,6 +275,40 @@ describe('enableWebPush', () => { // repaired in the background on app start - nobody should have to find the // settings toggle to stop the spam pushes. describe('resyncWebPush', () => { + it('repairs a lost browser subscription for an account that previously opted in', async () => { + localStorage.setItem(DEVICE_KEY, THIS_DEVICE); + localStorage.setItem(SUB_KEY, 'push-old'); + const { registration } = installPushBrowser(); + registration.pushManager.getSubscription.mockResolvedValue(null as never); + const client = makeClient([sub('push-old', THIS_DEVICE)]); + const calls = installFetch({}); + + expect(await isWebPushEnabled(ACCOUNT_ID)).toBe(false); + expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(true); + expect(registration.pushManager.subscribe).toHaveBeenCalledTimes(1); + expect(calls).toContainEqual({ url: `${RELAY}/api/push/register/web`, method: 'POST' }); + }); + + it.each(['default', 'denied'])('does not resubscribe or prompt with %s permission', async (permission) => { + localStorage.setItem(SUB_KEY, 'push-old'); + const { registration } = installPushBrowser(); + vi.stubGlobal('Notification', { permission, requestPermission: vi.fn() }); + const client = makeClient([sub('push-old', THIS_DEVICE)]); + expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(false); + expect(registration.pushManager.subscribe).not.toHaveBeenCalled(); + expect(Notification.requestPermission).not.toHaveBeenCalled(); + }); + + it('allows retry after a transient relay failure', async () => { + localStorage.setItem(SUB_KEY, 'push-old'); + localStorage.setItem(DEVICE_KEY, THIS_DEVICE); + const client = makeClient([sub('push-old', THIS_DEVICE)]); + vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline'))); + expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(false); + installFetch({}); + expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(true); + }); + it('re-syncs an enabled registration and installs the missing filter', async () => { localStorage.setItem(DEVICE_KEY, THIS_DEVICE); localStorage.setItem(SUB_KEY, 'push-old'); diff --git a/lib/web-push.ts b/lib/web-push.ts index c3f742025..90280a5cd 100644 --- a/lib/web-push.ts +++ b/lib/web-push.ts @@ -732,7 +732,7 @@ export interface ResyncWebPushParams { /** * Bring an already-enabled push registration up to date without any user * action: refresh its expiry and install/repair the delivery filter. Nothing - * here can prompt - it only runs when push is already on for the account - + * here can prompt - it requires a saved opt-in and granted permission - * and every failure is swallowed because the app must not care whether the * background touch-up worked. Returns true when a re-sync actually ran. */ @@ -747,7 +747,12 @@ export async function resyncWebPush(params: ResyncWebPushParams): Promise