mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 00:14:18 +00:00
fix: stop a stuck push setup from holding up every tab and login
This commit is contained in:
@@ -903,6 +903,60 @@ describe('a subscription id the server gave to another device', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('a setup that gets stuck', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
delete (navigator as unknown as { locks?: unknown }).locks;
|
||||
});
|
||||
|
||||
it('fails in the end and lets the next one through', async () => {
|
||||
vi.useFakeTimers();
|
||||
let tail: Promise<unknown> = Promise.resolve();
|
||||
Object.defineProperty(navigator, 'locks', {
|
||||
configurable: true,
|
||||
value: { request: (_n: string, run: () => Promise<unknown>) => { const next = tail.then(run, run); tail = next.catch(() => undefined); return next; } },
|
||||
});
|
||||
const { registration } = installPushBrowser();
|
||||
(registration.pushManager.getSubscription as ReturnType<typeof vi.fn>).mockResolvedValueOnce(null);
|
||||
(registration.pushManager.subscribe as ReturnType<typeof vi.fn>).mockReturnValueOnce(new Promise(() => {}));
|
||||
const client = makeClient([]);
|
||||
installFetch({});
|
||||
|
||||
const stuck = enableWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false }).then(() => 'done', () => 'failed');
|
||||
await vi.advanceTimersByTimeAsync(1_000);
|
||||
const second = enableWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false }).then(() => 'done', () => 'failed');
|
||||
await vi.advanceTimersByTimeAsync(200_000);
|
||||
|
||||
expect(await stuck).toBe('failed');
|
||||
expect(await second).toBe('done');
|
||||
});
|
||||
|
||||
it('turns off for the account it was asked about, even if the client disconnected while waiting', async () => {
|
||||
let tail: Promise<unknown> = Promise.resolve();
|
||||
let release = () => {};
|
||||
tail = new Promise<void>((resolve) => { release = resolve; });
|
||||
Object.defineProperty(navigator, 'locks', {
|
||||
configurable: true,
|
||||
value: { request: (_n: string, run: () => Promise<unknown>) => { const next = tail.then(run, run); tail = next.catch(() => undefined); return next; } },
|
||||
});
|
||||
localStorage.setItem(DEVICE_KEY, THIS_DEVICE);
|
||||
localStorage.setItem(SUB_KEY, 'push-mine');
|
||||
localStorage.setItem(RELAY_BASE_KEY, RELAY);
|
||||
const client = makeClient([sub('push-mine', THIS_DEVICE)]);
|
||||
let accountId = ACCOUNT_ID;
|
||||
(client as unknown as { getAccountId: () => string }).getAccountId = () => accountId;
|
||||
const calls = installFetch({});
|
||||
|
||||
const off = disableWebPush({ client, relayBaseUrl: RELAY }).catch(() => undefined);
|
||||
accountId = '';
|
||||
release();
|
||||
await off;
|
||||
|
||||
expect(localStorage.getItem(SUB_KEY)).toBeNull();
|
||||
expect(calls).toContainEqual({ url: `${RELAY}/api/push/register/${THIS_DEVICE}`, method: 'DELETE' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('resyncWebPush when the relay changed (#903)', () => {
|
||||
afterEach(() => setNoThirdPartyTraffic(false));
|
||||
|
||||
|
||||
+18
-4
@@ -775,8 +775,21 @@ async function refreshSubscriptionExpires(
|
||||
// verifying. Tabs take turns, in every browser that has Web Locks. Whoever comes
|
||||
// second finds the first one's work and only refreshes it.
|
||||
function oneTabAtATime<T>(run: () => Promise<T>): Promise<T> {
|
||||
const limited = () => withinPushSetupLimit(run);
|
||||
const locks = typeof navigator !== 'undefined' ? navigator.locks : undefined;
|
||||
return locks ? locks.request('bulwark-web-push', run) as Promise<T> : run();
|
||||
return locks ? locks.request('bulwark-web-push', limited) as Promise<T> : limited();
|
||||
}
|
||||
|
||||
// A worker that never gets ready or a subscribe() that never answers would hold up
|
||||
// every tab and every login, for good. Verification alone can take 80 s.
|
||||
const PUSH_SETUP_LIMIT_MS = 120_000;
|
||||
|
||||
function withinPushSetupLimit<T>(run: () => Promise<T>): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
const limit = new Promise<never>((_resolve, reject) => {
|
||||
timer = setTimeout(() => reject(new WebPushStepError('verification-timeout', 'Push setup took too long')), PUSH_SETUP_LIMIT_MS);
|
||||
});
|
||||
return Promise.race([run(), limit]).finally(() => clearTimeout(timer));
|
||||
}
|
||||
|
||||
export async function enableWebPush(
|
||||
@@ -1018,12 +1031,13 @@ async function dropRelayRecord(relayBaseUrl: string, deviceClientId: string): Pr
|
||||
// a later page load won't bring push back. But a server or relay that couldn't be
|
||||
// told keeps pinging this browser for days, so the caller hears about it at the end.
|
||||
export function disableWebPush(params: DisableWebPushParams): Promise<void> {
|
||||
return oneTabAtATime(() => disableWebPushForThisTab(params));
|
||||
// Read now: a client signing out loses it while this waits its turn.
|
||||
const accountId = params.client.getAccountId();
|
||||
return oneTabAtATime(() => disableWebPushForThisTab(params, accountId));
|
||||
}
|
||||
|
||||
async function disableWebPushForThisTab(params: DisableWebPushParams): Promise<void> {
|
||||
async function disableWebPushForThisTab(params: DisableWebPushParams, accountId: string): Promise<void> {
|
||||
const relayBaseUrl = (params.relayBaseUrl ?? DEFAULT_RELAY_BASE_URL).replace(/\/+$/, '');
|
||||
const accountId = params.client.getAccountId();
|
||||
|
||||
const subIdKey = subscriptionIdKey(accountId);
|
||||
const devIdKey = deviceClientIdKey(accountId);
|
||||
|
||||
Reference in New Issue
Block a user