You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-07-30 02:21:17 +03:00
Use new matrixRTC calling (#11792)
* initial Signed-off-by: Timo K <toger5@hotmail.de> * cleanup1 Signed-off-by: Timo K <toger5@hotmail.de> * bring back call timer Signed-off-by: Timo K <toger5@hotmail.de> * more cleanup and test removals Signed-off-by: Timo K <toger5@hotmail.de> * remove event Signed-off-by: Timo K <toger5@hotmail.de> * cleanups and minor fixes Signed-off-by: Timo K <toger5@hotmail.de> * add matrixRTC to stubClient Signed-off-by: Timo K <toger5@hotmail.de> * update tests (some got removed) The removal is a consequence of EW now doing less call logic. More logic is done by the js sdk (MatrixRTCSession) And therefore in EC itself. Signed-off-by: Timo K <toger5@hotmail.de> * cleanups Signed-off-by: Timo K <toger5@hotmail.de> * mock the session Signed-off-by: Timo K <toger5@hotmail.de> * lint Signed-off-by: Timo K <toger5@hotmail.de> * remove GroupCallDuration Signed-off-by: Timo K <toger5@hotmail.de> * review and fixing tests Signed-off-by: Timo K <toger5@hotmail.de> --------- Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
@ -34,6 +34,7 @@ import { Action } from "../../../src/dispatcher/actions";
|
||||
import { UserTab } from "../../../src/components/views/dialogs/UserTab";
|
||||
import {
|
||||
clearAllModals,
|
||||
createStubMatrixRTC,
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
@ -109,6 +110,7 @@ describe("<MatrixChat />", () => {
|
||||
secretStorage: {
|
||||
isStored: jest.fn().mockReturnValue(null),
|
||||
},
|
||||
matrixRTC: createStubMatrixRTC(),
|
||||
getDehydratedDevice: jest.fn(),
|
||||
whoami: jest.fn(),
|
||||
isRoomEncrypted: jest.fn(),
|
||||
|
@ -141,7 +141,6 @@ describe("CallEvent", () => {
|
||||
|
||||
screen.getByText("@alice:example.org started a video call");
|
||||
screen.getByLabelText("2 participants");
|
||||
screen.getByText("1m 30s");
|
||||
|
||||
// Test that the join button works
|
||||
const dispatcherSpy = jest.fn();
|
||||
|
@ -17,16 +17,14 @@ limitations under the License.
|
||||
import EventEmitter from "events";
|
||||
import { mocked } from "jest-mock";
|
||||
import { waitFor } from "@testing-library/react";
|
||||
import {
|
||||
RoomType,
|
||||
Room,
|
||||
RoomEvent,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
PendingEventOrdering,
|
||||
GroupCallIntent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { RoomType, Room, RoomEvent, MatrixEvent, RoomStateEvent, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSessionManagerEvents } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CallMembership } from "matrix-js-sdk/src/matrixrtc/CallMembership";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSession, MatrixRTCSessionEvent } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
import type { Mocked } from "jest-mock";
|
||||
import type { MatrixClient, IMyDevice, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
@ -96,9 +94,16 @@ const setUpClientRoomAndStores = (): {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
jest.spyOn(room, "getMyMembership").mockReturnValue("join");
|
||||
|
||||
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
|
||||
client.getRoom.mockImplementation((roomId) => (roomId === room.roomId ? room : null));
|
||||
client.matrixRTC.getRoomSession.mockImplementation((roomId) => {
|
||||
const session = new EventEmitter() as MatrixRTCSession;
|
||||
session.memberships = [];
|
||||
return session;
|
||||
});
|
||||
client.getRooms.mockReturnValue([room]);
|
||||
client.getUserId.mockReturnValue(alice.userId);
|
||||
client.getDeviceId.mockReturnValue("alices_device");
|
||||
@ -576,11 +581,9 @@ describe("ElementCall", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
let alice: RoomMember;
|
||||
let bob: RoomMember;
|
||||
let carol: RoomMember;
|
||||
|
||||
beforeEach(() => {
|
||||
({ client, room, alice, bob, carol } = setUpClientRoomAndStores());
|
||||
({ client, room, alice } = setUpClientRoomAndStores());
|
||||
});
|
||||
|
||||
afterEach(() => cleanUpClientRoomAndStores(client, room));
|
||||
@ -595,15 +598,14 @@ describe("ElementCall", () => {
|
||||
expect(Call.get(room)).toBeInstanceOf(ElementCall);
|
||||
});
|
||||
|
||||
it("ignores terminated calls", async () => {
|
||||
await ElementCall.create(room);
|
||||
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) => {},
|
||||
memberships: [{ fakeVal: "fake membership" }],
|
||||
} as unknown as MatrixRTCSession);
|
||||
const call = Call.get(room);
|
||||
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||
|
||||
// Terminate the call
|
||||
await call.groupCall.terminate();
|
||||
|
||||
expect(Call.get(room)).toBeNull();
|
||||
});
|
||||
|
||||
it("passes font settings through widget URL", async () => {
|
||||
@ -731,10 +733,6 @@ describe("ElementCall", () => {
|
||||
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget, audioMutedSpy, videoMutedSpy));
|
||||
|
||||
it("has prompt intent", () => {
|
||||
expect(call.groupCall.intent).toBe(GroupCallIntent.Prompt);
|
||||
});
|
||||
|
||||
it("connects muted", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
audioMutedSpy.mockReturnValue(true);
|
||||
@ -828,57 +826,6 @@ describe("ElementCall", () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("tracks participants in room state", async () => {
|
||||
expect(call.participants).toEqual(new Map());
|
||||
|
||||
// A participant with multiple devices (should only show up once)
|
||||
await client.sendStateEvent(
|
||||
room.roomId,
|
||||
ElementCall.MEMBER_EVENT_TYPE.name,
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": call.groupCall.groupCallId,
|
||||
"m.devices": [
|
||||
{ device_id: "bobweb", session_id: "1", feeds: [], expires_ts: 1000 * 60 * 10 },
|
||||
{ device_id: "bobdesktop", session_id: "1", feeds: [], expires_ts: 1000 * 60 * 10 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
bob.userId,
|
||||
);
|
||||
// A participant with an expired device (should not show up)
|
||||
await client.sendStateEvent(
|
||||
room.roomId,
|
||||
ElementCall.MEMBER_EVENT_TYPE.name,
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": call.groupCall.groupCallId,
|
||||
"m.devices": [
|
||||
{ device_id: "carolandroid", session_id: "1", feeds: [], expires_ts: -1000 * 60 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
carol.userId,
|
||||
);
|
||||
|
||||
// Now, stub out client.sendStateEvent so we can test our local echo
|
||||
client.sendStateEvent.mockReset();
|
||||
await call.connect();
|
||||
expect(call.participants).toEqual(
|
||||
new Map([
|
||||
[alice, new Set(["alices_device"])],
|
||||
[bob, new Set(["bobweb", "bobdesktop"])],
|
||||
]),
|
||||
);
|
||||
|
||||
await call.disconnect();
|
||||
expect(call.participants).toEqual(new Map([[bob, new Set(["bobweb", "bobdesktop"])]]));
|
||||
});
|
||||
|
||||
it("tracks layout", async () => {
|
||||
await call.connect();
|
||||
expect(call.layout).toBe(Layout.Tile);
|
||||
@ -924,14 +871,11 @@ describe("ElementCall", () => {
|
||||
|
||||
it("emits events when participants change", async () => {
|
||||
const onParticipants = jest.fn();
|
||||
call.session.memberships = [{ sender: alice.userId, deviceId: "alices_device" } as CallMembership];
|
||||
call.on(CallEvent.Participants, onParticipants);
|
||||
call.session.emit(MatrixRTCSessionEvent.MembershipsChanged, [], []);
|
||||
|
||||
await call.connect();
|
||||
await call.disconnect();
|
||||
expect(onParticipants.mock.calls).toEqual([
|
||||
[new Map([[alice, new Set(["alices_device"])]]), new Map()],
|
||||
[new Map(), new Map([[alice, new Set(["alices_device"])]])],
|
||||
]);
|
||||
expect(onParticipants.mock.calls).toEqual([[new Map([[alice, new Set(["alices_device"])]]), new Map()]]);
|
||||
|
||||
call.off(CallEvent.Participants, onParticipants);
|
||||
});
|
||||
@ -954,60 +898,19 @@ describe("ElementCall", () => {
|
||||
call.off(CallEvent.Layout, onLayout);
|
||||
});
|
||||
|
||||
it("ends the call immediately if we're the last participant to leave", async () => {
|
||||
it("ends the call immediately if the session ended", async () => {
|
||||
await call.connect();
|
||||
const onDestroy = jest.fn();
|
||||
call.on(CallEvent.Destroy, onDestroy);
|
||||
await call.disconnect();
|
||||
expect(onDestroy).toHaveBeenCalled();
|
||||
call.off(CallEvent.Destroy, onDestroy);
|
||||
});
|
||||
|
||||
it("ends the call after a random delay if the last participant leaves without ending it", async () => {
|
||||
// Bob connects
|
||||
await client.sendStateEvent(
|
||||
// this will be called automatically
|
||||
// disconnect -> widget sends state event -> session manager notices no-one left
|
||||
client.matrixRTC.emit(
|
||||
MatrixRTCSessionManagerEvents.SessionEnded,
|
||||
room.roomId,
|
||||
ElementCall.MEMBER_EVENT_TYPE.name,
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": call.groupCall.groupCallId,
|
||||
"m.devices": [
|
||||
{ device_id: "bobweb", session_id: "1", feeds: [], expires_ts: 1000 * 60 * 10 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
bob.userId,
|
||||
{} as unknown as MatrixRTCSession,
|
||||
);
|
||||
|
||||
const onDestroy = jest.fn();
|
||||
call.on(CallEvent.Destroy, onDestroy);
|
||||
|
||||
// Bob disconnects
|
||||
await client.sendStateEvent(
|
||||
room.roomId,
|
||||
ElementCall.MEMBER_EVENT_TYPE.name,
|
||||
{
|
||||
"m.calls": [
|
||||
{
|
||||
"m.call_id": call.groupCall.groupCallId,
|
||||
"m.devices": [],
|
||||
},
|
||||
],
|
||||
},
|
||||
bob.userId,
|
||||
);
|
||||
|
||||
// Nothing should happen for at least a second, to give Bob a chance
|
||||
// to end the call on his own
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(onDestroy).not.toHaveBeenCalled();
|
||||
|
||||
// Within 10 seconds, our client should end the call on behalf of Bob
|
||||
jest.advanceTimersByTime(9000);
|
||||
expect(onDestroy).toHaveBeenCalled();
|
||||
|
||||
call.off(CallEvent.Destroy, onDestroy);
|
||||
});
|
||||
|
||||
@ -1040,10 +943,6 @@ describe("ElementCall", () => {
|
||||
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget, audioMutedSpy, videoMutedSpy));
|
||||
|
||||
it("has room intent", () => {
|
||||
expect(call.groupCall.intent).toBe(GroupCallIntent.Room);
|
||||
});
|
||||
|
||||
it("doesn't end the call when the last participant leaves", async () => {
|
||||
await call.connect();
|
||||
const onDestroy = jest.fn();
|
||||
|
@ -35,6 +35,9 @@ export class MockedCall extends Call {
|
||||
url: "https://example.org",
|
||||
name: "Group call",
|
||||
creatorUserId: "@alice:example.org",
|
||||
// waitForIframeLoad = false, makes the widget API wait for the 'contentLoaded' event instead.
|
||||
// This is how the EC is designed, but for backwards compatibility (full mesh) we currently need to use waitForIframeLoad = true
|
||||
// waitForIframeLoad: false
|
||||
},
|
||||
room.client,
|
||||
);
|
||||
|
@ -45,6 +45,10 @@ import { MediaHandler } from "matrix-js-sdk/src/webrtc/mediaHandler";
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { CryptoBackend } from "matrix-js-sdk/src/common-crypto/CryptoBackend";
|
||||
import { MapperOpts } from "matrix-js-sdk/src/event-mapper";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSessionManager } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSessionManager";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
|
||||
import type { GroupCall } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg";
|
||||
@ -89,6 +93,7 @@ export function stubClient(): MatrixClient {
|
||||
*/
|
||||
export function createTestClient(): MatrixClient {
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
let txnId = 1;
|
||||
|
||||
const client = {
|
||||
@ -256,6 +261,7 @@ export function createTestClient(): MatrixClient {
|
||||
submitMsisdnToken: jest.fn(),
|
||||
getMediaConfig: jest.fn(),
|
||||
baseUrl: "https://matrix-client.matrix.org",
|
||||
matrixRTC: createStubMatrixRTC(),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
client.reEmitter = new ReEmitter(client);
|
||||
@ -272,6 +278,26 @@ export function createTestClient(): MatrixClient {
|
||||
return client;
|
||||
}
|
||||
|
||||
export function createStubMatrixRTC(): MatrixRTCSessionManager {
|
||||
const eventEmitterMatrixRTCSessionManager = new EventEmitter();
|
||||
const mockGetRoomSession = jest.fn();
|
||||
mockGetRoomSession.mockImplementation((roomId) => {
|
||||
const session = new EventEmitter() as MatrixRTCSession;
|
||||
session.memberships = [];
|
||||
session.getOldestMembership = () => undefined;
|
||||
return session;
|
||||
});
|
||||
return {
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
getActiveRoomSession: jest.fn(),
|
||||
getRoomSession: mockGetRoomSession,
|
||||
on: eventEmitterMatrixRTCSessionManager.on.bind(eventEmitterMatrixRTCSessionManager),
|
||||
off: eventEmitterMatrixRTCSessionManager.off.bind(eventEmitterMatrixRTCSessionManager),
|
||||
removeListener: eventEmitterMatrixRTCSessionManager.removeListener.bind(eventEmitterMatrixRTCSessionManager),
|
||||
emit: eventEmitterMatrixRTCSessionManager.emit.bind(eventEmitterMatrixRTCSessionManager),
|
||||
} as unknown as MatrixRTCSessionManager;
|
||||
}
|
||||
type MakeEventPassThruProps = {
|
||||
user: User["userId"];
|
||||
relatesTo?: IEventRelation;
|
||||
|
Reference in New Issue
Block a user