fix(invites): decline invites robustly (no 500, no ghost, friendly error)
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:
@@ -47,6 +47,7 @@ import { nameInitials } from '../../../utils/common';
|
||||
import { RoomAvatar } from '../../../components/room-avatar';
|
||||
import {
|
||||
addRoomIdToMDirect,
|
||||
declineInvite,
|
||||
getMxIdLocalPart,
|
||||
guessDmRoomUserId,
|
||||
rateLimitedActions,
|
||||
@@ -179,8 +180,16 @@ function InviteCard({
|
||||
onNavigate(invite.roomId, invite.isSpace);
|
||||
}, [mx, invite, userId, onNavigate]),
|
||||
);
|
||||
const [leaveState, leave] = useAsyncCallback<Record<string, never>, MatrixError, []>(
|
||||
useCallback(() => mx.leave(invite.roomId), [mx, invite]),
|
||||
const [leaveState, leave] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
try {
|
||||
await declineInvite(mx, invite.roomId);
|
||||
} catch (e) {
|
||||
// Surface a friendly message; keep the real error in the console.
|
||||
console.warn('Failed to decline invite', invite.roomId, e);
|
||||
throw e;
|
||||
}
|
||||
}, [mx, invite.roomId]),
|
||||
);
|
||||
|
||||
const joining =
|
||||
@@ -276,7 +285,7 @@ function InviteCard({
|
||||
)}
|
||||
{leaveState.status === AsyncStatus.Error && (
|
||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||
{leaveState.error.message}
|
||||
Couldn’t decline this invite — please try again.
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
@@ -481,7 +490,7 @@ function UnknownInvites({
|
||||
useCallback(async () => {
|
||||
const roomIds = invites.map((invite) => invite.roomId);
|
||||
|
||||
await rateLimitedActions(roomIds, (roomId) => mx.leave(roomId));
|
||||
await rateLimitedActions(roomIds, (roomId) => declineInvite(mx, roomId));
|
||||
}, [mx, invites]),
|
||||
);
|
||||
|
||||
@@ -559,7 +568,7 @@ function SpamInvites({
|
||||
useCallback(async () => {
|
||||
const roomIds = invites.map((invite) => invite.roomId);
|
||||
|
||||
await rateLimitedActions(roomIds, (roomId) => mx.leave(roomId));
|
||||
await rateLimitedActions(roomIds, (roomId) => declineInvite(mx, roomId));
|
||||
}, [mx, invites]),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user