fix: keep iCal subscriptions safe when the mail server was replaced

When the server behind an account is replaced (a fresh Stalwart 1.0
install at the address 0.16 ran on), its ids start over. The app
already dropped its caches then, but kept what the user set up with
server ids inside:

- iCal subscriptions kept the ids of their mirror calendars. The next
  sync deleted the "stale" events of whatever calendar now had the id
  and filled it with the feed, and "Remove subscription" destroyed it.
  Now the account's subscriptions keep their feed, name, colour and
  interval but lose their mirror, and the next sync makes a new one as
  adding a subscription does. Renaming or removing an unlinked one
  touches no calendar. Events are only pruned from, and remove only
  destroys, a calendar that still carries the subscription's name.
  Subscriptions now record the signed-in account, so accounts on two
  servers that got the same JMAP id no longer share them.
- the preferred sender identity kept its id, which a new server can
  give to another address (live: identity "b" is usera@ on 0.16.23 and
  team@ on 1.0). It is forgotten for the account's old and new JMAP
  id, which the app now records with the server fingerprint.
- hidden calendars kept their ids. The list is shared by all accounts,
  so it is cleared.

Offline changes the drop discards are now announced once ("3 offline
changes were discarded because the mail server changed"), in all
languages.

Revoking this device from another one turns push off here again: a
subscription gone before its recorded expiry counts as revoked, as it
did before the server check came in. A replaced server forgets the
recorded expiry, so there the subscription is re-created instead.
This commit is contained in:
Linus Rath
2026-09-25 23:49:36 +02:00
parent a2ca98e0e8
commit f490886a8b
40 changed files with 632 additions and 71 deletions
+14
View File
@@ -1156,6 +1156,20 @@ export async function setDefaultCalendar(
methodResult(res);
}
/**
* The name of the user's calendar `id`, or null when the account has no
* calendar by that id.
*/
export async function getCalendarName(id: string): Promise<string | null> {
const accountId = jmapClient.accountId;
const res = await jmapClient.request(
[['Calendar/get', { accountId, ids: [id], properties: ['id', 'name'] }, '0']],
USING,
);
const list = methodResult<{ list?: Calendar[] }>(res).list ?? [];
return list.find((cal) => cal.id === id)?.name ?? null;
}
// Destroy a calendar. `onDestroyRemoveEvents` removes its events too —
// Stalwart otherwise refuses to delete a non-empty calendar.
export async function deleteCalendar(id: string, targetAccountId?: string): Promise<void> {