Skip to content

Commit

Permalink
ElementCall fix strict mode call creation loop
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
toger5 committed Feb 5, 2025
1 parent fc0797a commit 7533f59
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 11 deletions.
10 changes: 1 addition & 9 deletions src/components/views/voip/CallView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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]);

Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 3 additions & 2 deletions src/models/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,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()!,
Expand All @@ -762,6 +762,8 @@ export class ElementCall extends Call {
},
roomId,
);
WidgetStore.instance.emit(UPDATE_EVENT, null);
return createdWidget;
}

private static getWidgetData(
Expand Down Expand Up @@ -830,7 +832,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> {
Expand Down
14 changes: 14 additions & 0 deletions src/stores/RoomViewStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { CancelAskToJoinPayload } from "../dispatcher/payloads/CancelAskToJoinPa
import { 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;

Expand Down Expand Up @@ -350,6 +351,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.
Expand Down

0 comments on commit 7533f59

Please sign in to comment.