ElementCall fix strict mode call creation loop

In strict mode there is a call create -> destroy -> create infinite loop wen pressing the call button.

This loop was a consequence of relying on component creation/destruction to handle creating and removing the call.
This logic:
 - destroying a call if it was in the lobby but leaving it if it is connected when the user stops viewing the room the call belongs to.
  - Creating an ElementCall if there is not yet once when the user starts viewing a call.

Belongs into the roomViewStore and not the components that are just a sideffect in the call livecycle. (view model separation)
This commit is contained in:
Timo
2025-02-05 12:36:34 +01:00
parent 4de9fe60ae
commit 1f3203f3a2
3 changed files with 18 additions and 11 deletions

View File

@@ -29,6 +29,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
useEffect(() => {
// We'll take this opportunity to tidy up our room state
// Only needed for jitsi.
call.clean();
}, [call]);
@@ -44,10 +45,6 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call, skipLobby,
// (this will start the lobby view in the widget and connect to all required widget events)
call.start();
}
return (): void => {
// If we are connected the widget is sticky and we do not want to destroy the call.
if (!call.connected) call.destroy();
};
}, [call]);
const disconnectAllOtherCalls: () => Promise<void> = useCallback(async () => {
// The stickyPromise has to resolve before the widget actually becomes sticky.
@@ -88,11 +85,6 @@ interface CallViewProps {
export const CallView: FC<CallViewProps> = ({ room, resizing, waitForCall, skipLobby, role }) => {
const call = useCall(room.roomId);
useEffect(() => {
if (call === null && !waitForCall) {
ElementCall.create(room, skipLobby);
}
}, [call, room, skipLobby, waitForCall]);
if (call === null) {
return null;
} else {

View File

@@ -740,7 +740,7 @@ export class ElementCall extends Call {
// To use Element Call without touching room state, we create a virtual
// widget (one that doesn't have a corresponding state event)
const url = ElementCall.generateWidgetUrl(client, roomId);
return WidgetStore.instance.addVirtualWidget(
const createdWidget = WidgetStore.instance.addVirtualWidget(
{
id: secureRandomString(24), // So that it's globally unique
creatorUserId: client.getUserId()!,
@@ -761,6 +761,8 @@ export class ElementCall extends Call {
},
roomId,
);
WidgetStore.instance.emit(UPDATE_EVENT, null);
return createdWidget;
}
private static getWidgetData(
@@ -829,7 +831,6 @@ export class ElementCall extends Call {
public static async create(room: Room, skipLobby = false): Promise<void> {
ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, false, isVideoRoom(room));
WidgetStore.instance.emit(UPDATE_EVENT, null);
}
protected async sendCallNotify(): Promise<void> {

View File

@@ -50,6 +50,7 @@ import { type CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJ
import { type SubmitAskToJoinPayload } from "../dispatcher/payloads/SubmitAskToJoinPayload";
import { ModuleRunner } from "../modules/ModuleRunner";
import { setMarkedUnreadState } from "../utils/notifications";
import { ElementCall } from "../models/Call";
const NUM_JOIN_RETRY = 5;
@@ -353,6 +354,19 @@ export class RoomViewStore extends EventEmitter {
});
}
// Start call when requested
const currentRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null;
if (payload.view_call && room) {
if (!currentRoomCall) {
ElementCall.create(room, false);
}
}
// Destroy call when requested leaving call view
const prevRoomCall = this.state.roomId ? CallStore.instance.getCall(this.state.roomId) : null;
if (prevRoomCall && !prevRoomCall.connected) {
currentRoomCall?.destroy();
}
if (SettingsStore.getValue("feature_sliding_sync") && this.state.roomId !== payload.room_id) {
if (this.state.subscribingRoomId && this.state.subscribingRoomId !== payload.room_id) {
// unsubscribe from this room, but don't await it as we don't care when this gets done.