mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-05 00:14:18 +00:00
fix: replace a browser subscription the push service gave up on
This commit is contained in:
@@ -741,6 +741,80 @@ describe('what enabling and disabling say when they fail', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('an endpoint the push service gave up on', () => {
|
||||
const ENDPOINT_KEY = `bulwark.push.endpoint.v1.${ACCOUNT_ID}`;
|
||||
|
||||
function registered() {
|
||||
localStorage.setItem(DEVICE_KEY, THIS_DEVICE);
|
||||
localStorage.setItem(SUB_KEY, 'push-mine');
|
||||
localStorage.setItem(RELAY_BASE_KEY, RELAY);
|
||||
localStorage.setItem(ENDPOINT_KEY, 'https://fcm.example/endpoint');
|
||||
return makeClient([sub('push-mine', THIS_DEVICE)]);
|
||||
}
|
||||
|
||||
it('gets a new subscription when the relay dropped the record for the very endpoint this browser still holds', async () => {
|
||||
const { browserSub, registration } = installPushBrowser();
|
||||
const client = registered();
|
||||
installFetch({}); // The relay answers 404 for this device.
|
||||
|
||||
await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false });
|
||||
|
||||
expect(browserSub.unsubscribe).toHaveBeenCalledTimes(1);
|
||||
expect(registration.pushManager.subscribe).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps the subscription while the relay knows the record', async () => {
|
||||
const { browserSub } = installPushBrowser();
|
||||
const client = registered();
|
||||
installFetch({ [THIS_DEVICE]: true });
|
||||
|
||||
await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false });
|
||||
|
||||
expect(browserSub.unsubscribe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps it when the relay cannot be asked', async () => {
|
||||
const { browserSub } = installPushBrowser();
|
||||
const client = registered();
|
||||
const calls: string[] = [];
|
||||
vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
calls.push(url);
|
||||
if (url.includes('/api/push/active/')) throw new TypeError('blip');
|
||||
const json = (body: unknown) => ({ ok: true, status: 200, json: async () => body }) as Response;
|
||||
return url.endsWith('/vapid-public-key') ? json({ publicKey: 'QUJD' }) : json({});
|
||||
}));
|
||||
|
||||
await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false });
|
||||
|
||||
expect(calls.some((url) => url.includes('/api/push/active/'))).toBe(true);
|
||||
expect(browserSub.unsubscribe).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('only registers when another account already moved the browser to a new endpoint', async () => {
|
||||
const { browserSub } = installPushBrowser();
|
||||
const client = registered();
|
||||
localStorage.setItem(ENDPOINT_KEY, 'https://fcm.example/older');
|
||||
installFetch({});
|
||||
|
||||
await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false });
|
||||
|
||||
expect(browserSub.unsubscribe).not.toHaveBeenCalled();
|
||||
expect(localStorage.getItem(ENDPOINT_KEY)).toBe('https://fcm.example/endpoint');
|
||||
});
|
||||
|
||||
it('does not mistake a move to another relay for a dead endpoint', async () => {
|
||||
const { browserSub } = installPushBrowser();
|
||||
const client = registered();
|
||||
localStorage.setItem(RELAY_BASE_KEY, 'https://old-relay.example');
|
||||
installFetch({});
|
||||
|
||||
await resyncWebPush({ client, relayBaseUrl: RELAY, inboxOnly: false });
|
||||
|
||||
expect(browserSub.unsubscribe).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resyncWebPush when the relay changed (#903)', () => {
|
||||
afterEach(() => setNoThirdPartyTraffic(false));
|
||||
|
||||
|
||||
+38
-3
@@ -53,6 +53,12 @@ function relayBaseKey(accountId: string): string {
|
||||
return RELAY_BASE_PREFIX + accountId;
|
||||
}
|
||||
|
||||
/** The browser endpoint this account last gave the relay: see relayDroppedOurEndpoint. */
|
||||
const ENDPOINT_PREFIX = 'bulwark.push.endpoint.v1.';
|
||||
function endpointKey(accountId: string): string {
|
||||
return ENDPOINT_PREFIX + accountId;
|
||||
}
|
||||
|
||||
function emailPushRefusedKey(accountId: string): string {
|
||||
return EMAIL_PUSH_REFUSED_PREFIX + accountId;
|
||||
}
|
||||
@@ -642,6 +648,28 @@ async function relayReportsDead(
|
||||
const VERIFICATION_RESEND_AFTER_MS = 62_000;
|
||||
const VERIFICATION_TIMEOUT_MS = 80_000;
|
||||
|
||||
/**
|
||||
* The relay deletes a record when the push service answers 404 or 410. The browser
|
||||
* doesn't get told: it keeps the subscription, and registering that endpoint again
|
||||
* only lasts until the next push. Only for the endpoint this account registered:
|
||||
* after another account moved the browser on, a missing record means nothing.
|
||||
*/
|
||||
async function relayDroppedOurEndpoint(
|
||||
accountId: string,
|
||||
relayBaseUrl: string,
|
||||
subscriptionId: string,
|
||||
endpoint: string,
|
||||
): Promise<boolean> {
|
||||
if (localStorage.getItem(relayBaseKey(accountId)) !== relayBaseUrl) return false;
|
||||
if (localStorage.getItem(endpointKey(accountId)) !== endpoint) return false;
|
||||
try {
|
||||
const res = await fetch(buildRelayUrl(relayBaseUrl, `/api/push/active/${encodeURIComponent(subscriptionId)}`));
|
||||
return res.status === 404;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function pollVerificationCode(
|
||||
relayBaseUrl: string,
|
||||
subscriptionId: string,
|
||||
@@ -785,6 +813,14 @@ async function enableWebPushForThisTab(
|
||||
await pushSubscription.unsubscribe().catch(() => undefined);
|
||||
pushSubscription = null;
|
||||
}
|
||||
const accountId = params.client.getAccountId();
|
||||
const deviceClientId = getOrCreateDeviceClientId(accountId);
|
||||
if (pushSubscription && await relayDroppedOurEndpoint(accountId, relayBaseUrl, deviceClientId, pushSubscription.endpoint)) {
|
||||
await pushSubscription.unsubscribe().catch(() => undefined);
|
||||
pushSubscription = null;
|
||||
// The other accounts' records point at the old endpoint: their turn comes too.
|
||||
lastResync.clear();
|
||||
}
|
||||
if (!pushSubscription) {
|
||||
pushSubscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
@@ -792,9 +828,6 @@ async function enableWebPushForThisTab(
|
||||
});
|
||||
}
|
||||
|
||||
const accountId = params.client.getAccountId();
|
||||
const deviceClientId = getOrCreateDeviceClientId(accountId);
|
||||
|
||||
await registerWithRelay({
|
||||
relayBaseUrl,
|
||||
subscriptionId: deviceClientId,
|
||||
@@ -807,6 +840,7 @@ async function enableWebPushForThisTab(
|
||||
},
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
localStorage.setItem(endpointKey(accountId), pushSubscription.endpoint);
|
||||
await keepRelayRegistration(accountId, {
|
||||
relay: relayBaseUrl,
|
||||
subscriptionId: deviceClientId,
|
||||
@@ -1001,6 +1035,7 @@ async function disableWebPushForThisTab(params: DisableWebPushParams): Promise<v
|
||||
}
|
||||
localStorage.removeItem(subIdKey);
|
||||
localStorage.removeItem(relayBaseKey(accountId));
|
||||
localStorage.removeItem(endpointKey(accountId));
|
||||
await forgetRelayRegistration(accountId);
|
||||
// Enabling again starts from the full emailPush map.
|
||||
localStorage.removeItem(emailPushRefusedKey(accountId));
|
||||
|
||||
Reference in New Issue
Block a user