fix(calls): hang up explicitly when answering another call while in one (#195)

Answering from the in-call banner is the only call-to-call switch path; it
started the new embed and let the atom dispose the old one, which just removed
the iframe — our m.call.member in the old room lingered ~17 s (until the
delayed leave expired), so everyone there still saw us in the call.

hangupAndWait moves out of LogoutDialog into plugins/call/hangup.ts and
handleAnswer now hangs up, waits for our membership to clear (bounded 4 s),
disposes the old embed itself (its HangupCall echo would otherwise land after
startCall and clear the NEW embed from the atom — seen in testing), then joins
the new call. Measured headless: old membership gone in 1 s, new call live
with both participants.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-09-19 01:04:26 -04:00
co-authored by Claude Opus 5
parent bbcbdad55a
commit 070a1ea012
3 changed files with 52 additions and 27 deletions
+35
View File
@@ -0,0 +1,35 @@
import { MatrixClient } from 'matrix-js-sdk';
import { CallEmbed } from './CallEmbed';
/**
* Ask EC to hang up and wait (bounded) until our own RTC membership has left
* the room's session. Used wherever the embed is about to be torn down for a
* reason other than the user pressing End — logout (#29) and answering another
* call while in one (#195) — because disposing the iframe alone leaves a ghost
* `m.call.member` for everyone else until the delayed leave event expires
* (~17–30 s measured).
*/
export const hangupCallAndWait = async (
mx: MatrixClient,
embed: CallEmbed,
timeoutMs = 4000,
): Promise<void> => {
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() + timeoutMs;
while (stillIn() && Date.now() < deadline) {
// eslint-disable-next-line no-await-in-loop
await new Promise((r) => {
setTimeout(r, 150);
});
}
};