mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-04 00:06:53 +00:00
fix: register with the previous push relay again when the mail server refuses a new one
This commit is contained in:
@@ -399,8 +399,18 @@ describe('setupPushNotifications subscription shape', () => {
|
||||
expect(await AsyncStorage.getItem('push:relayBaseUrl:v1')).toBe(RELAY);
|
||||
expect(await AsyncStorage.getItem(SUB_KEY)).toBe('existing');
|
||||
expect(destroyMock).not.toHaveBeenCalledWith('existing');
|
||||
const deleted = fetchMock.mock.calls.filter((c) => (c[1] as RequestInit | undefined)?.method === 'DELETE').map((c) => String(c[0]));
|
||||
expect(deleted).toEqual([`https://relay.home.lan/api/push/register/${OUR_DCID}`]);
|
||||
const relayCalls = fetchMock.mock.calls
|
||||
.filter((c) => String(c[0]).includes('/api/push/register'))
|
||||
.map((c) => `${(c[1] as RequestInit | undefined)?.method ?? 'GET'} ${String(c[0])}`);
|
||||
// The refused URL may be another name of the relay in use: after dropping
|
||||
// the record there, this device registers with the previous relay again.
|
||||
expect(relayCalls).toEqual([
|
||||
'POST https://relay.home.lan/api/push/register',
|
||||
`DELETE https://relay.home.lan/api/push/register/${OUR_DCID}`,
|
||||
`POST ${RELAY}/api/push/register`,
|
||||
]);
|
||||
const restored = fetchMock.mock.calls.filter((c) => String(c[0]) === `${RELAY}/api/push/register`);
|
||||
expect(JSON.parse(String((restored[0][1] as RequestInit).body))).toMatchObject({ subscriptionId: OUR_DCID, fcmToken: 'fcm-token-xyz' });
|
||||
|
||||
// The same relay refused (a server that tightened its rules) stays as it is.
|
||||
fetchMock.mockClear();
|
||||
@@ -1299,6 +1309,37 @@ describe('setupPushNotifications over UnifiedPush', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("puts the distributor back on the previous relay's VAPID key when the server refuses the new relay", async () => {
|
||||
await AsyncStorage.setItem('push:relayBaseUrl:v1', RELAY);
|
||||
await AsyncStorage.setItem(SUB_KEY, 'existing');
|
||||
listMock.mockResolvedValue([{ id: 'existing', deviceClientId: OUR_DCID, expires: new Date(Date.now() + 5 * 86400000).toISOString(), types: ['EmailDelivery'] }]);
|
||||
createMock.mockRejectedValueOnce(new JMAPSetError('invalidProperties', 'Host is not a public domain name', ['url']));
|
||||
const registered: string[] = [];
|
||||
global.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const url = typeof input === 'string' ? input : input.toString();
|
||||
if (url.endsWith('/api/push/vapid-public-key')) {
|
||||
const publicKey = url.startsWith(RELAY) ? 'VAPID-PUB' : 'VAPID-LAN';
|
||||
return { ok: true, status: 200, json: async () => ({ publicKey }) } as Response;
|
||||
}
|
||||
if (url.includes('/api/push/register')) {
|
||||
registered.push(`${init?.method ?? 'GET'} ${url}`);
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true }) } as Response;
|
||||
}
|
||||
throw new Error(`unexpected fetch: ${url}`);
|
||||
}) as typeof fetch;
|
||||
|
||||
const err = await setupPushNotifications({ relayBaseUrl: 'https://relay.home.lan', forceRecreate: true }).catch((e) => e);
|
||||
|
||||
expect(err).toBeInstanceOf(PushSetupError);
|
||||
expect(upNative().register.mock.calls.map((c) => c[0])).toEqual(['VAPID-LAN', 'VAPID-PUB']);
|
||||
expect(registered).toEqual([
|
||||
'POST https://relay.home.lan/api/push/register/unifiedpush',
|
||||
`DELETE https://relay.home.lan/api/push/register/${OUR_DCID}`,
|
||||
`POST ${RELAY}/api/push/register/unifiedpush`,
|
||||
]);
|
||||
expect(await AsyncStorage.getItem('push:relayBaseUrl:v1')).toBe(RELAY);
|
||||
});
|
||||
|
||||
it('sends keys: null for a legacy distributor without Web Push keys', async () => {
|
||||
upNative().getEndpoint.mockResolvedValue({
|
||||
url: 'https://legacy.example/up123456',
|
||||
|
||||
@@ -1235,28 +1235,42 @@ async function setupPushNotificationsInner(
|
||||
// individually and tags it with the JMAP account id so the headless task
|
||||
// can route it.
|
||||
logPhase('relay', relayBaseUrl);
|
||||
if (apnsToken) {
|
||||
await registerWithRelayApns({
|
||||
relayBaseUrl,
|
||||
subscriptionId: deviceClientId,
|
||||
deviceToken: apnsToken,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
} else if (upEndpoint) {
|
||||
await registerWithRelayUnifiedPush({
|
||||
relayBaseUrl,
|
||||
subscriptionId: deviceClientId,
|
||||
endpoint: upEndpoint,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
} else {
|
||||
await registerWithRelay({
|
||||
relayBaseUrl,
|
||||
subscriptionId: deviceClientId,
|
||||
fcmToken: fcmToken!,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
}
|
||||
const registerWith = async (relay: string, endpoint: UnifiedPushEndpoint | null) => {
|
||||
if (apnsToken) {
|
||||
await registerWithRelayApns({
|
||||
relayBaseUrl: relay,
|
||||
subscriptionId: deviceClientId,
|
||||
deviceToken: apnsToken,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
} else if (endpoint) {
|
||||
await registerWithRelayUnifiedPush({
|
||||
relayBaseUrl: relay,
|
||||
subscriptionId: deviceClientId,
|
||||
endpoint,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
} else {
|
||||
await registerWithRelay({
|
||||
relayBaseUrl: relay,
|
||||
subscriptionId: deviceClientId,
|
||||
fcmToken: fcmToken!,
|
||||
accountLabel: params.accountLabel,
|
||||
});
|
||||
}
|
||||
};
|
||||
await registerWith(relayBaseUrl, upEndpoint);
|
||||
// Undo this run's relay steps once the mail server refuses the new relay's
|
||||
// address. The refused URL may be another name of the relay in use, so the
|
||||
// record dropped from it can be the working one; and a UnifiedPush
|
||||
// distributor now holds the refused relay's VAPID key, which may make it
|
||||
// turn away the previous relay's pushes. Registering with the previous relay
|
||||
// again puts both back; the record itself only for an account that had a
|
||||
// working subscription through it.
|
||||
const restoreRelay = async (relay: string, hadSubscription: boolean) => {
|
||||
const endpoint = upEndpoint ? await getUnifiedPushEndpointOrThrow(relay) : null;
|
||||
if (hadSubscription) await registerWith(relay, endpoint);
|
||||
};
|
||||
|
||||
// Reuse the previous JMAP subscription when the server still has it, but
|
||||
// push the expiry forward so it doesn't time out before the next app start.
|
||||
@@ -1410,6 +1424,7 @@ async function setupPushNotificationsInner(
|
||||
if (isRefusedPushUrl(err) && previousRelayBaseUrl !== relayBaseUrl) {
|
||||
await setStoredRelayBaseUrl(previousRelayBaseUrl);
|
||||
await deregisterFromRelay(relayBaseUrl, deviceClientId);
|
||||
if (previousRelayBaseUrl) await restoreRelay(previousRelayBaseUrl, !!match).catch(() => undefined);
|
||||
}
|
||||
throw subscriptionError(err, relayBaseUrl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user