fix: handle synthetic ID errors when updating calendar events with fallback to destroy and recreate

This commit is contained in:
Linus Rath
2026-03-21 20:45:18 +01:00
parent 8b1b3ad57b
commit 7c3c3b5f7b
+58 -7
View File
@@ -169,7 +169,30 @@ export const useCalendarStore = create<CalendarStore>()(
}
cleanUpdates.calendarIds = remapped;
}
await client.updateCalendarEvent(realId, cleanUpdates, sendSchedulingMessages, targetAccountId);
try {
await client.updateCalendarEvent(realId, cleanUpdates, sendSchedulingMessages, targetAccountId);
} catch (updateError) {
// Stalwart doesn't support updating events created via CalDAV (e.g. by Thunderbird).
// These events have "synthetic" JMAP IDs. Work around by destroying and recreating.
const message = updateError instanceof Error ? updateError.message : '';
if (message.toLowerCase().includes('synthetic') && storeEvent) {
debug.log('Event has synthetic ID, falling back to destroy+recreate');
// Merge existing event with updates, strip server-computed fields
const { id: _id, originalId: _oi, originalCalendarIds: _oc,
accountId: _ai, accountName: _an, isShared: _is,
utcStart: _us, utcEnd: _ue, isDraft: _dr, isOrigin: _io,
created: _cr, updated: _up, ...baseEvent } = storeEvent;
const mergedEvent: Partial<CalendarEvent> = { ...baseEvent, ...cleanUpdates };
// Delete the old event without sending cancellations (we're recreating it)
await client.deleteCalendarEvent(realId, false, targetAccountId);
const created = await client.createCalendarEvent(mergedEvent, sendSchedulingMessages, targetAccountId);
set((state) => ({
events: state.events.map(e => e.id === id ? created : e),
}));
return;
}
throw updateError;
}
set((state) => ({
events: state.events.map(e => e.id === id ? { ...e, ...updates } : e),
}));
@@ -202,12 +225,40 @@ export const useCalendarStore = create<CalendarStore>()(
if (replyTo) {
patch.replyTo = replyTo;
}
await client.updateCalendarEvent(
realId,
patch as unknown as Partial<CalendarEvent>,
true,
targetAccountId
);
try {
await client.updateCalendarEvent(
realId,
patch as unknown as Partial<CalendarEvent>,
true,
targetAccountId
);
} catch (updateError) {
// Fallback for CalDAV-created events with synthetic IDs
const message = updateError instanceof Error ? updateError.message : '';
if (message.toLowerCase().includes('synthetic') && storeEvent) {
debug.log('RSVP: Event has synthetic ID, falling back to destroy+recreate');
const { id: _id, originalId: _oi, originalCalendarIds: _oc,
accountId: _ai, accountName: _an, isShared: _is,
utcStart: _us, utcEnd: _ue, isDraft: _dr, isOrigin: _io,
created: _cr, updated: _up, ...baseEvent } = storeEvent;
const updatedParticipants = storeEvent.participants ? {
...storeEvent.participants,
[participantId]: { ...storeEvent.participants[participantId], participationStatus: status },
} : storeEvent.participants;
const mergedEvent: Partial<CalendarEvent> = {
...baseEvent,
participants: updatedParticipants as Record<string, CalendarParticipant> | null,
...(replyTo ? { replyTo } : {}),
};
await client.deleteCalendarEvent(realId, false, targetAccountId);
const created = await client.createCalendarEvent(mergedEvent, true, targetAccountId);
set((state) => ({
events: state.events.map(e => e.id === eventId ? created : e),
}));
return;
}
throw updateError;
}
set((state) => ({
events: state.events.map(e => {
if (e.id !== eventId || !e.participants?.[participantId]) return e;