fix(invites): decline invites robustly (no 500, no ghost, friendly error)
CI / Build & Quality Checks (push) Successful in 27m33s
CI / Trigger Desktop Build (push) Successful in 7s

Declining a remote invite could show a raw 'MatrixError: [500] Internal server
error' and appear to do nothing. Root causes were client-side: decline called
mx.leave unconditionally, so re-clicking after a slow federated leave hit an
already-left remote room that Synapse 500s on; the room was never forgotten so a
'leave' ghost lingered and re-invited a click; and the raw error string was shown.

Add a shared declineInvite(mx, roomId) helper that only leaves when still in the
room (invite/join/knock) and then forgets it (best-effort, first use of forget in
the app). Route the InviteCard decline and both 'Decline All' paths through it,
and replace the raw error with a friendly message (real error kept in console).

Tests: declineInvite covered (6 cases); typecheck + full suite + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 11:55:35 -04:00
co-authored by Claude Opus 4.8
parent 57da9a6ce8
commit 29d74eda8f
3 changed files with 95 additions and 5 deletions
+18
View File
@@ -417,6 +417,24 @@ export const downloadEncryptedMedia = async (
return decryptedContent;
};
/**
* Decline (reject) a room invite robustly. Only sends a leave when we're actually
* still in the room (invite/join/knock) — re-leaving an already-left *remote* room
* is exactly what Synapse 500s on — then forgets the room so a lingering "leave"
* ghost can't re-render as a clickable invite. Idempotent; forget is best-effort.
*/
export const declineInvite = async (mx: MatrixClient, roomId: string): Promise<void> => {
const membership = mx.getRoom(roomId)?.getMyMembership();
if (
membership === Membership.Invite ||
membership === Membership.Join ||
membership === Membership.Knock
) {
await mx.leave(roomId);
}
await mx.forget(roomId).catch(() => undefined);
};
export const rateLimitedActions = async <T, R = void>(
data: T[],
callback: (item: T, index: number) => Promise<R>,