mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep retrying a lost push subscription, at most every 15 minutes #1147
Recreating the subscription removed the account's stored id before the replacement was created and verified, and the resync now requires that id. One interrupted or timed-out attempt (up to 75 seconds of verification) therefore ended recovery for that account for good. The id now stays until a verified replacement overwrites it. A failed resync also forgot its timestamp, so every return to the tab reran the whole enable. It now waits 15 minutes before the next try.
This commit is contained in:
@@ -306,7 +306,27 @@ describe('resyncWebPush', () => {
|
||||
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);
|
||||
// Not on the very next tab focus...
|
||||
expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(false);
|
||||
// ...but a quarter of an hour later.
|
||||
const later = Date.now() + 16 * 60_000;
|
||||
vi.spyOn(Date, 'now').mockReturnValue(later);
|
||||
try {
|
||||
expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(true);
|
||||
} finally {
|
||||
vi.mocked(Date.now).mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the opt-in when recreating the subscription fails', async () => {
|
||||
localStorage.setItem(SUB_KEY, 'push-old');
|
||||
localStorage.setItem(DEVICE_KEY, THIS_DEVICE);
|
||||
// The server lost the old subscription, and creating a new one fails.
|
||||
const client = makeClient([]);
|
||||
vi.mocked(client.createPushSubscription).mockRejectedValue(new Error('server down'));
|
||||
installFetch({});
|
||||
expect(await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false })).toBe(false);
|
||||
expect(localStorage.getItem(SUB_KEY)).toBe('push-old');
|
||||
});
|
||||
|
||||
it('re-syncs an enabled registration and installs the missing filter', async () => {
|
||||
|
||||
+8
-3
@@ -526,7 +526,9 @@ export async function enableWebPush(
|
||||
}
|
||||
await params.client.destroyPushSubscription(storedServerId).catch(() => undefined);
|
||||
}
|
||||
localStorage.removeItem(subIdKey);
|
||||
// The stored id stays until the replacement is verified below: it is
|
||||
// what tells resyncWebPush this account opted in, so dropping it here
|
||||
// would end recovery for good after one interrupted attempt.
|
||||
}
|
||||
|
||||
// Reap leftover subscriptions that would otherwise starve the new one's
|
||||
@@ -719,6 +721,8 @@ export async function isWebPushEnabled(accountId: string): Promise<boolean> {
|
||||
// clamps `expires` to 7 days, so a tab or installed app left open for a
|
||||
// week would otherwise let the subscription lapse and push stop silently.
|
||||
const RESYNC_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
// After a failed attempt: soon again, but not on every return to the tab.
|
||||
const RESYNC_RETRY_MS = 15 * 60 * 1000;
|
||||
const lastResyncAt = new Map<string, number>();
|
||||
|
||||
export interface ResyncWebPushParams {
|
||||
@@ -762,8 +766,9 @@ export async function resyncWebPush(params: ResyncWebPushParams): Promise<boolea
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
// A failed attempt must not wait a day for the next one.
|
||||
lastResyncAt.delete(accountId);
|
||||
// A failed attempt must not wait a day for the next one, nor rerun the
|
||||
// whole enable on every tab focus.
|
||||
lastResyncAt.set(accountId, Date.now() - RESYNC_INTERVAL_MS + RESYNC_RETRY_MS);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user