diff --git a/src/models/Call.ts b/src/models/Call.ts index b5b222a622..ecf1cbab66 100644 --- a/src/models/Call.ts +++ b/src/models/Call.ts @@ -50,6 +50,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../stores/ActiveWidge import { getCurrentLanguage } from "../languageHandler"; import { FontWatcher } from "../settings/watchers/FontWatcher"; import { PosthogAnalytics } from "../PosthogAnalytics"; +import { UPDATE_EVENT } from "../stores/AsyncStore"; const TIMEOUT_MS = 16000; @@ -225,7 +226,7 @@ export abstract class Call extends TypedEventEmitter WidgetType.CALL.matches(app.type)); if (ecWidget) { - logger.log("There is already a widget in this room, so we recreate it"); - ActiveWidgetStore.instance.destroyPersistentWidget(ecWidget.id, ecWidget.roomId); - WidgetStore.instance.removeVirtualWidget(ecWidget.id, ecWidget.roomId); + return ecWidget; } const accountAnalyticsData = client.getAccountData(PosthogAnalytics.ANALYTICS_EVENT_TYPE); // The analyticsID is passed directly to element call (EC) since this codepath is only for EC and no other widget. @@ -707,7 +706,7 @@ export class ElementCall extends Call { } public static get(room: Room): ElementCall | null { - // Only supported in the new group call experience or in video rooms + // Only supported in the new group call experience or in video rooms. if ( SettingsStore.getValue("feature_group_calls") || (SettingsStore.getValue("feature_video_rooms") && @@ -715,15 +714,16 @@ export class ElementCall extends Call { room.isCallRoom()) ) { const apps = WidgetStore.instance.getApps(room.roomId); - const ecWidget = apps.find((app) => WidgetType.CALL.matches(app.type)); + const hasEcWidget = apps.some((app) => WidgetType.CALL.matches(app.type)); const session = room.client.matrixRTC.getRoomSession(room); // A call is present if we - // - have a widget: This means the create function was called + // - have a widget: This means the create function was called. // - or there is a running session where we have not yet created a widget for. - if (ecWidget || session.memberships.length !== 0) { + // - or this this is a call room. Then we also always want to show a call. + if (hasEcWidget || session.memberships.length !== 0 || room.isCallRoom()) { // create a widget for the case we are joining a running call and don't have on yet. - const availableOrCreatedWidget = ecWidget ?? ElementCall.createCallWidget(room.roomId, room.client); + const availableOrCreatedWidget = ElementCall.createOrGetCallWidget(room.roomId, room.client); return new ElementCall(session, availableOrCreatedWidget, room.client); } } @@ -738,7 +738,8 @@ export class ElementCall extends Call { room.isCallRoom(); console.log("Intend is ", isVideoRoom ? "VideoRoom" : "Prompt", " TODO, handle intent appropriately"); - ElementCall.createCallWidget(room.roomId, room.client); + ElementCall.createOrGetCallWidget(room.roomId, room.client); + WidgetStore.instance.emit(UPDATE_EVENT, null); } protected async performConnection( @@ -790,7 +791,8 @@ export class ElementCall extends Call { } private onRTCSessionEnded = (roomId: string, session: MatrixRTCSession): void => { - if (roomId == this.roomId) { + // Don't destroy widget on hangup for video call rooms. + if (roomId == this.roomId && !this.room.isCallRoom()) { this.destroy(); } }; diff --git a/src/stores/WidgetStore.ts b/src/stores/WidgetStore.ts index aac8037ea6..fd092e7fbb 100644 --- a/src/stores/WidgetStore.ts +++ b/src/stores/WidgetStore.ts @@ -202,7 +202,6 @@ export default class WidgetStore extends AsyncStoreWithClient { const app = WidgetUtils.makeAppConfig(widget.id, widget, widget.creatorUserId, roomId, undefined); this.widgetMap.set(WidgetUtils.getWidgetUid(app), app); this.roomMap.get(roomId)!.widgets.push(app); - this.emit(UPDATE_EVENT, roomId); return app; } diff --git a/test/models/Call-test.ts b/test/models/Call-test.ts index 94a102c95c..bd394e628b 100644 --- a/test/models/Call-test.ts +++ b/test/models/Call-test.ts @@ -596,16 +596,19 @@ describe("ElementCall", () => { it("finds calls", async () => { await ElementCall.create(room); expect(Call.get(room)).toBeInstanceOf(ElementCall); + Call.get(room)?.destroy(); }); it("finds ongoing calls that are created by the session manager", async () => { // There is an existing session created by another user in this room. client.matrixRTC.getRoomSession.mockReturnValue({ on: (ev: any, fn: any) => {}, + off: (ev: any, fn: any) => {}, memberships: [{ fakeVal: "fake membership" }], } as unknown as MatrixRTCSession); const call = Call.get(room); if (!(call instanceof ElementCall)) throw new Error("Failed to create call"); + call.destroy(); }); it("passes font settings through widget URL", async () => { @@ -642,6 +645,7 @@ describe("ElementCall", () => { const urlParams1 = new URLSearchParams(new URL(call1.widget.url).hash.slice(1)); expect(urlParams1.has("allowIceFallback")).toBe(false); + call1.destroy(); // Now test with the preference set to true const originalGetValue = SettingsStore.getValue; @@ -654,13 +658,14 @@ describe("ElementCall", () => { } }; - await ElementCall.create(room); + ElementCall.create(room); const call2 = Call.get(room); if (!(call2 instanceof ElementCall)) throw new Error("Failed to create call"); const urlParams2 = new URLSearchParams(new URL(call2.widget.url).hash.slice(1)); expect(urlParams2.has("allowIceFallback")).toBe(true); + call2.destroy(); SettingsStore.getValue = originalGetValue; }); @@ -677,6 +682,7 @@ describe("ElementCall", () => { const urlParams = new URLSearchParams(new URL(call.widget.url).hash.slice(1)); expect(urlParams.get("analyticsID")).toBe("123456789987654321"); + call.destroy(); }); it("does not pass analyticsID if `pseudonymousAnalyticsOptIn` set to false", async () => {