From 1044a95687a318c704ed47eb9e814b5d94435fa1 Mon Sep 17 00:00:00 2001 From: adis veletanlic <96535657+defaultdino@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:58:15 +0200 Subject: [PATCH] fix(call): leave call along with room (#33162) * make sure to disconnect from possibly active calls for a room when leaving the room * log error on log call * Update apps/web/src/utils/leave-behaviour.ts Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * fix wrong logger import * hang up calls properly on empty rooms for both legacy and element calls (listen for room event and leave call if only one member left). add tests for both legacy and element calls. * format Call-test.ts * revert async on function def * revert Call.ts and Call-test.ts. Wrap legacy call hangup in try --------- Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- apps/web/src/utils/leave-behaviour.ts | 20 +++++++++++++++ .../unit-tests/utils/leave-behaviour-test.ts | 25 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/apps/web/src/utils/leave-behaviour.ts b/apps/web/src/utils/leave-behaviour.ts index 8b7ee02f36..9756fa50cf 100644 --- a/apps/web/src/utils/leave-behaviour.ts +++ b/apps/web/src/utils/leave-behaviour.ts @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. import { sleep } from "matrix-js-sdk/src/utils"; import React, { type ReactNode } from "react"; import { EventStatus, MatrixEventEvent, type Room, type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; +import { logger } from "matrix-js-sdk/src/logger"; import Modal, { type IHandle } from "../Modal"; import Spinner from "../components/views/elements/Spinner"; @@ -25,6 +26,8 @@ import { type AfterLeaveRoomPayload } from "../dispatcher/payloads/AfterLeaveRoo import { bulkSpaceBehaviour } from "./space"; import { SdkContextClass } from "../contexts/SDKContext"; import SettingsStore from "../settings/SettingsStore"; +import { CallStore } from "../stores/CallStore"; +import LegacyCallHandler from "../LegacyCallHandler"; export async function leaveRoomBehaviour( matrixClient: MatrixClient, @@ -59,6 +62,23 @@ export async function leaveRoomBehaviour( throw new Error(`Expected to find room for id ${roomId}`); } + // attempt to hang up legacy based calls + try { + LegacyCallHandler.instance.hangupOrReject(roomId); + } catch (e) { + logger.warn("Failed to hangup call before leaving room: ", e); + } + + // hang up widget based calls + const activeCall = CallStore.instance.getActiveCall(roomId); + if (activeCall) { + try { + await activeCall.disconnect(); + } catch (e) { + logger.warn("Failed to disconnect call before leaving room: ", e); + } + } + // await any queued messages being sent so that they do not fail await Promise.all( room diff --git a/apps/web/test/unit-tests/utils/leave-behaviour-test.ts b/apps/web/test/unit-tests/utils/leave-behaviour-test.ts index 9da796a3a5..a6da3a4083 100644 --- a/apps/web/test/unit-tests/utils/leave-behaviour-test.ts +++ b/apps/web/test/unit-tests/utils/leave-behaviour-test.ts @@ -22,6 +22,9 @@ import SpaceStore from "../../../src/stores/spaces/SpaceStore"; import { MetaSpace } from "../../../src/stores/spaces"; import { type ActionPayload } from "../../../src/dispatcher/payloads"; import SettingsStore from "../../../src/settings/SettingsStore"; +import { CallStore } from "../../../src/stores/CallStore"; +import { type Call } from "../../../src/models/Call"; +import LegacyCallHandler from "../../../src/LegacyCallHandler"; describe("leaveRoomBehaviour", () => { SdkContextClass.instance.constructEagerStores(); // Initialize RoomViewStore @@ -76,6 +79,28 @@ describe("leaveRoomBehaviour", () => { defaultDispatcher.unregister(dispatcherRef); }; + it("hangs up legacy calls when leaving a room", async () => { + const hangupSpy = jest.spyOn(LegacyCallHandler.instance, "hangupOrReject").mockImplementation(() => {}); + + viewRoom(room); + await leaveRoomBehaviour(client, room.roomId); + + expect(hangupSpy).toHaveBeenCalledWith(room.roomId); + }); + + it("disconnects widget-based calls when leaving a room", async () => { + const mockCall = { + disconnect: jest.fn().mockResolvedValue(undefined), + } as unknown as Call; + + jest.spyOn(CallStore.instance, "getActiveCall").mockReturnValue(mockCall); + + viewRoom(room); + await leaveRoomBehaviour(client, room.roomId); + + expect(mockCall.disconnect).toHaveBeenCalled(); + }); + it("returns to the home page after leaving a room outside of a space that was being viewed", async () => { viewRoom(room);