diff --git a/src/app/components/LogoutDialog.tsx b/src/app/components/LogoutDialog.tsx index bfa604f4d..bcf8bce8e 100644 --- a/src/app/components/LogoutDialog.tsx +++ b/src/app/components/LogoutDialog.tsx @@ -1,7 +1,11 @@ import React, { forwardRef, useCallback } from 'react'; import { Dialog, Header, config, Box, Text, Button, Spinner, color } from 'folds'; +import { useAtom } from 'jotai'; +import { MatrixClient } from 'matrix-js-sdk'; import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; import { logoutClient } from '../../client/initMatrix'; +import { callEmbedAtom } from '../state/callEmbed'; +import { CallEmbed } from '../plugins/call'; import { useMatrixClient } from '../hooks/useMatrixClient'; import { useModalStyle } from '../hooks/useModalStyle'; import { useCrossSigningActive } from '../hooks/useCrossSigning'; @@ -11,6 +15,32 @@ import { VerificationStatus, } from '../hooks/useDeviceVerificationStatus'; +/** + * Ask Element Call to hang up and wait (bounded) until our own MatrixRTC + * membership has actually been removed from the room, so the leave reaches + * the homeserver before the client is stopped and the token is revoked. + */ +const hangupAndWait = async (mx: MatrixClient, embed: CallEmbed): Promise => { + const myUserId = mx.getUserId(); + const myDeviceId = mx.getDeviceId(); + const stillIn = (): boolean => + mx.matrixRTC + .getRoomSession(embed.room) + .memberships.some((m) => m.sender === myUserId && m.deviceId === myDeviceId); + try { + await embed.hangup(); + } catch { + // widget already gone — fall through to the wait/timeout + } + const deadline = Date.now() + 4000; + while (stillIn() && Date.now() < deadline) { + // eslint-disable-next-line no-await-in-loop + await new Promise((r) => { + setTimeout(r, 150); + }); + } +}; + type LogoutDialogProps = { handleClose: () => void; }; @@ -26,10 +56,19 @@ export const LogoutDialog = forwardRef( mx.getDeviceId() ?? undefined, ); + const [callEmbed, setCallEmbed] = useAtom(callEmbedAtom); + const [logoutState, logout] = useAsyncCallback( useCallback(async () => { + // [Gitea #29] Logging out mid-call must hang up first, or the MatrixRTC + // membership (expires: 4 h) stays behind as a ghost participant and + // everyone else sees you "in call" until it times out. + if (callEmbed && callEmbed.joined && !callEmbed.disposed) { + await hangupAndWait(mx, callEmbed); + setCallEmbed(undefined); + } await logoutClient(mx); - }, [mx]), + }, [mx, callEmbed, setCallEmbed]), ); const ongoingLogout = logoutState.status === AsyncStatus.Loading;