Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/web/src/stores/CallStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export enum CallStoreEvent {
Call = "call",
// Signals a change in the active calls
ConnectedCalls = "connected_calls",
// Signals a change in the participants of a call
Participants = "participants",
// Signals a change in the configured RTC transports.
TransportsUpdated = "transports_updated",
}
Expand Down Expand Up @@ -164,20 +166,25 @@ export class CallStore extends AsyncStoreWithClient<EmptyObject> {
this.connectedCalls = new Set([...this.connectedCalls].filter((c) => c !== call));
}
};
const onParticipants = (): void => {
this.emit(CallStoreEvent.Participants, room.roomId);
};
const onDestroy = (): void => {
this.calls.delete(room.roomId);
for (const [event, listener] of this.callListeners.get(call)!) call.off(event, listener);
this.updateRoom(room);
};

call.on(CallEvent.ConnectionState, onConnectionState);
call.on(CallEvent.Participants, onParticipants);
call.on(CallEvent.Destroy, onDestroy);

this.calls.set(room.roomId, call);
this.callListeners.set(
call,
new Map<CallEvent, (...args: any[]) => unknown>([
[CallEvent.ConnectionState, onConnectionState],
[CallEvent.Participants, onParticipants],
[CallEvent.Destroy, onDestroy],
]),
);
Expand Down
9 changes: 9 additions & 0 deletions apps/web/src/viewmodels/room-list/RoomListItemViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class RoomListItemViewModel

// Subscribe to call state changes
this.disposables.trackListener(CallStore.instance, CallStoreEvent.ConnectedCalls, this.onCallStateChanged);
this.disposables.trackListener(CallStore.instance, CallStoreEvent.Participants, this.onCallParticipantsChanged);

// Subscribe to room-specific events
this.disposables.trackListener(props.room, RoomEvent.Name, this.onRoomChanged);
Expand All @@ -100,6 +101,14 @@ export class RoomListItemViewModel
void this.loadAndSetMessagePreview();
};

private onCallParticipantsChanged = (...args: unknown[]): void => {
// Only react to participant changes for this room
const roomId = args[0] as string;
if (roomId === this.props.room.roomId) {
this.onCallStateChanged();
}
};

private onCallStateChanged = (): void => {
// Only update if call state for this room actually changed
const call = CallStore.instance.getCall(this.props.room.roomId);
Expand Down
26 changes: 24 additions & 2 deletions apps/web/test/unit-tests/stores/CallStore-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { type CallMembership, MatrixRTCSessionManagerEvents } from "matrix-js-sd
import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { type MockedObject } from "jest-mock";

import { ElementCall } from "../../../src/models/Call";
import { CallStore } from "../../../src/stores/CallStore";
import { CallEvent, ElementCall } from "../../../src/models/Call";
import { CallStore, CallStoreEvent } from "../../../src/stores/CallStore";
import {
setUpClientRoomAndStores,
cleanUpClientRoomAndStores,
Expand Down Expand Up @@ -66,4 +66,26 @@ describe("CallStore", () => {
{ type: "type-d", other_data: "baz" },
]);
});

it("emits CallStoreEvent.Participants when a call's participants change", () => {
setupAsyncStoreWithClient(CallStore.instance, client);

// Simulate another user starting a new MatrixRTC session
const session = client.matrixRTC.getRoomSession(room);
session.memberships.push({} as CallMembership);
client.matrixRTC.emit(MatrixRTCSessionManagerEvents.SessionStarted, room.roomId, session);

const call = CallStore.instance.getCall(room.roomId)!;
expect(call).not.toBeNull();

const participantsSpy = jest.fn();
CallStore.instance.on(CallStoreEvent.Participants, participantsSpy);

// Emit a participants change on the call
call.emit(CallEvent.Participants, new Map(), new Map());

expect(participantsSpy).toHaveBeenCalledWith(room.roomId);

CallStore.instance.off(CallStoreEvent.Participants, participantsSpy);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
* Please see LICENSE files in the repository root for full details.
*/

import { type MatrixClient, type MatrixEvent, Room, RoomEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
import {
type MatrixClient,
type MatrixEvent,
Room,
RoomEvent,
PendingEventOrdering,
type RoomMember,
} from "matrix-js-sdk/src/matrix";
import { CallType } from "matrix-js-sdk/src/webrtc/call";

import { createTestClient, flushPromises } from "../../test-utils";
Expand Down Expand Up @@ -40,6 +47,7 @@ jest.mock("../../../src/stores/CallStore", () => ({
},
CallStoreEvent: {
ConnectedCalls: "connected_calls",
Participants: "participants",
},
}));

Expand Down Expand Up @@ -310,6 +318,28 @@ describe("RoomListItemViewModel", () => {

expect(viewModel.getSnapshot().notification.callType).toBeUndefined();
});

it("should listen to call participant changes", () => {
const mockCall = {
callType: CallType.Voice,
participants: new Map(),
} as unknown as Call;
jest.spyOn(CallStore.instance, "getCall").mockReturnValue(mockCall);

viewModel = new RoomListItemViewModel({ room, client: matrixClient });
expect(viewModel.getSnapshot().notification.callType).toBeUndefined();

// Simulate participant joining
mockCall.participants.set(matrixClient.getUserId()! as unknown as RoomMember, new Set());

// Get the callback registered for participant changes
const mockCalls = (CallStore.instance.on as jest.Mock).mock.calls;
const participantsCallback = mockCalls[mockCalls.length - 1][1];
// Trigger participants event
participantsCallback(room.roomId);

expect(viewModel.getSnapshot().notification.callType).toBe("voice");
});
});

describe("Room name updates", () => {
Expand Down
Loading