fix: keep the working push registration when a re-register at the subscription limit moves to another relay

This commit is contained in:
Linus Rath
2026-10-29 02:31:06 +01:00
parent 3047d5c495
commit 93ea948045
2 changed files with 26 additions and 2 deletions
@@ -1118,6 +1118,7 @@ describe('setupPushNotifications at the server\'s subscription limit', () => {
await AsyncStorage.clear();
await AsyncStorage.setItem(deviceClientIdKey(ACCOUNT_ID), OUR_DCID);
await AsyncStorage.setItem(SUB_KEY, 'existing');
await AsyncStorage.setItem('push:relayBaseUrl:v1', RELAY);
listMock.mockResolvedValue([recorded(80)]);
installFetch({});
});
@@ -1128,6 +1129,26 @@ describe('setupPushNotifications at the server\'s subscription limit', () => {
updateMock.mockImplementation(async () => undefined);
});
it('keeps the working record when a forced re-register moves to another relay', async () => {
// Stalwart answers overQuota before it looks at the URL; freeing a place
// would only lead to the new relay being refused with the old record gone.
let attempts = 0;
createMock.mockImplementation(async () => {
attempts += 1;
throw attempts === 1
? overQuota()
: new JMAPSetError('invalidProperties', 'Host is not a public domain name', ['url']);
});
const err = await setupPushNotifications({ relayBaseUrl: 'https://relay.home.lan', forceRecreate: true }).catch((e) => e);
expect(err).toBeInstanceOf(PushSetupError);
expect(err.message).toContain('too many push registrations on the mail server');
expect(createMock).toHaveBeenCalledTimes(1);
expect(destroyMock.mock.calls.map((c) => c[0])).not.toContain('existing');
expect(await AsyncStorage.getItem(SUB_KEY)).toBe('existing');
});
it('a forced re-register frees the old record\'s place and tries once more', async () => {
createMock.mockRejectedValueOnce(overQuota());
+5 -2
View File
@@ -1388,8 +1388,11 @@ async function setupPushNotificationsInner(
// the one being replaced, so at the cap a forced re-register could never
// create its replacement. The user asked for a new record: free the old
// one's place and try once more. Should that fail too, push is off and
// the error says why.
if (!params.forceRecreate || !replacedServerId || !isJMAPSetError(err) || err.type !== 'overQuota') throw err;
// the error says why. Not when this run moves to another relay: Stalwart
// counts the subscriptions before it looks at the URL, so a relay it
// refuses would only show up after the working record is gone.
const relayChanged = previousRelayBaseUrl !== relayBaseUrl;
if (!params.forceRecreate || !replacedServerId || relayChanged || !isJMAPSetError(err) || err.type !== 'overQuota') throw err;
logPhase('jmap', 'subscription limit reached, replacing the old record first');
await destroyPushSubscription(replacedServerId);
await AsyncStorage.removeItem(subKey);