1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-23 17:02:25 +03:00

sonar cubes coaching ;)

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-10-07 18:31:38 +02:00
parent 383b219f94
commit 3c2f9b48b0
9 changed files with 151 additions and 87 deletions

View File

@@ -15,8 +15,8 @@ limitations under the License.
*/ */
import { type MatrixEvent } from "../../../src"; import { type MatrixEvent } from "../../../src";
import { CallMembership, DEFAULT_EXPIRE_DURATION, type RtcMembershipData } from "../../../src/matrixrtc/CallMembership"; import { CallMembership, DEFAULT_EXPIRE_DURATION } from "../../../src/matrixrtc/CallMembership";
import { membershipTemplate } from "./mocks"; import { rtcMembershipTemplate, sessionMembershipTemplate } from "./mocks";
function makeMockEvent(originTs = 0, content = {}): MatrixEvent { function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
return { return {
@@ -29,6 +29,7 @@ function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
describe("CallMembership", () => { describe("CallMembership", () => {
describe("SessionMembershipData", () => { describe("SessionMembershipData", () => {
const membershipTemplate = sessionMembershipTemplate;
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
}); });
@@ -185,15 +186,7 @@ describe("CallMembership", () => {
}); });
describe("RtcMembershipData", () => { describe("RtcMembershipData", () => {
const membershipTemplate: RtcMembershipData = { const membershipTemplate = rtcMembershipTemplate;
"slot_id": "m.call#",
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
"member": { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
"rtc_transports": [{ type: "livekit" }],
"m.call.intent": "voice",
"versions": [],
};
it("rejects membership with no slot_id", () => { it("rejects membership with no slot_id", () => {
expect(() => { expect(() => {
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined })); new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));

View File

@@ -14,12 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src"; import {
encodeBase64,
EventType,
MatrixClient,
MatrixEvent,
RelationType,
type MatrixError,
type Room,
} from "../../../src";
import { KnownMembership } from "../../../src/@types/membership"; import { KnownMembership } from "../../../src/@types/membership";
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession"; import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
import { Status, type EncryptionKeysEventContent } from "../../../src/matrixrtc/types"; import { Status, type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
import { secureRandomString } from "../../../src/randomstring"; import { secureRandomString } from "../../../src/randomstring";
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey, type MembershipData, mockRoomState } from "./mocks"; import {
makeMockEvent,
makeMockRoom,
sessionMembershipTemplate as membershipTemplate,
makeKey,
type MembershipData,
mockRoomState,
rtcMembershipTemplate,
sessionMembershipTemplate,
} from "./mocks";
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts"; import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts";
const mockFocus = { type: "mock" }; const mockFocus = { type: "mock" };
@@ -50,6 +67,7 @@ describe("MatrixRTCSession", () => {
}); });
describe("roomSessionForRoom", () => { describe("roomSessionForRoom", () => {
const membershipTemplate = sessionMembershipTemplate;
it("creates a room-scoped session from room state", async () => { it("creates a room-scoped session from room state", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
@@ -206,6 +224,41 @@ describe("MatrixRTCSession", () => {
sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.memberships).toHaveLength(0); expect(sess.memberships).toHaveLength(0);
}); });
it("fetches related events if needed from room", async () => {
const testMembership = {
...rtcMembershipTemplate,
"m.relates_to": { event_id: "id", rel_type: RelationType.Reference as const },
// hack for simple makeMockRoom construction
"user_id": rtcMembershipTemplate.member.user_id,
};
const mockRoom = makeMockRoom([testMembership]);
mockRoom.findEventById = jest
.fn()
.mockImplementation((id) =>
id === "id"
? new MatrixEvent({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 })
: undefined,
);
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
expect(sess.memberships[0].createdTs()).toBe(100);
});
it("fetches related events if needed from cs api", async () => {
const testMembership = {
...rtcMembershipTemplate,
"m.relates_to": { event_id: "id", rel_type: RelationType.Reference as const },
// hack for simple makeMockRoom construction
"user_id": rtcMembershipTemplate.member.user_id,
};
const mockRoom = makeMockRoom([testMembership]);
mockRoom.findEventById = jest.fn().mockReturnValue(undefined);
client.fetchRoomEvent = jest
.fn()
.mockResolvedValue({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 });
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
expect(sess.memberships[0].createdTs()).toBe(100);
});
}); });
describe("getOldestMembership", () => { describe("getOldestMembership", () => {

View File

@@ -17,7 +17,7 @@ limitations under the License.
import { ClientEvent, EventTimeline, MatrixClient } from "../../../src"; import { ClientEvent, EventTimeline, MatrixClient } from "../../../src";
import { RoomStateEvent } from "../../../src/models/room-state"; import { RoomStateEvent } from "../../../src/models/room-state";
import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager"; import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks"; import { makeMockRoom, sessionMembershipTemplate, mockRoomState } from "./mocks";
import { logger } from "../../../src/logger"; import { logger } from "../../../src/logger";
describe("MatrixRTCSessionManager", () => { describe("MatrixRTCSessionManager", () => {
@@ -42,7 +42,7 @@ describe("MatrixRTCSessionManager", () => {
}); });
try { try {
const room1 = makeMockRoom([membershipTemplate]); const room1 = makeMockRoom([sessionMembershipTemplate]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]); jest.spyOn(client, "getRooms").mockReturnValue([room1]);
client.emit(ClientEvent.Room, room1); client.emit(ClientEvent.Room, room1);
@@ -58,7 +58,7 @@ describe("MatrixRTCSessionManager", () => {
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted); client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
try { try {
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]); const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other" }]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]); jest.spyOn(client, "getRooms").mockReturnValue([room1]);
client.emit(ClientEvent.Room, room1); client.emit(ClientEvent.Room, room1);
@@ -76,14 +76,14 @@ describe("MatrixRTCSessionManager", () => {
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionEnded, rEnd); client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionEnded, rEnd);
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionStarted, rStart); client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionStarted, rStart);
const room1 = makeMockRoom([membershipTemplate]); const room1 = makeMockRoom([sessionMembershipTemplate]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]); jest.spyOn(client, "getRooms").mockReturnValue([room1]);
jest.spyOn(client, "getRoom").mockReturnValue(room1); jest.spyOn(client, "getRoom").mockReturnValue(room1);
client.emit(ClientEvent.Room, room1); client.emit(ClientEvent.Room, room1);
await startPromise; await startPromise;
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]); mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!; const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0]; const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
client.emit(RoomStateEvent.Events, membEvent, roomState, null); client.emit(RoomStateEvent.Events, membEvent, roomState, null);
@@ -112,14 +112,14 @@ describe("MatrixRTCSessionManager", () => {
}); });
try { try {
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]); const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other" }]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]); jest.spyOn(client, "getRooms").mockReturnValue([room1]);
client.emit(ClientEvent.Room, room1); client.emit(ClientEvent.Room, room1);
expect(onStarted).not.toHaveBeenCalled(); expect(onStarted).not.toHaveBeenCalled();
onStarted.mockClear(); onStarted.mockClear();
const room2 = makeMockRoom([{ ...membershipTemplate, application: "m.notCall", call_id: "test" }]); const room2 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.notCall", call_id: "test" }]);
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]); jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
client.emit(ClientEvent.Room, room2); client.emit(ClientEvent.Room, room2);
@@ -127,7 +127,7 @@ describe("MatrixRTCSessionManager", () => {
expect(onStarted).toHaveBeenCalled(); expect(onStarted).toHaveBeenCalled();
onStarted.mockClear(); onStarted.mockClear();
mockRoomState(room2, [{ user_id: membershipTemplate.user_id }]); mockRoomState(room2, [{ user_id: sessionMembershipTemplate.user_id }]);
jest.spyOn(client, "getRoom").mockReturnValue(room2); jest.spyOn(client, "getRoom").mockReturnValue(room2);
const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!; const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
@@ -137,7 +137,7 @@ describe("MatrixRTCSessionManager", () => {
expect(onEnded).toHaveBeenCalled(); expect(onEnded).toHaveBeenCalled();
onEnded.mockClear(); onEnded.mockClear();
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]); mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
jest.spyOn(client, "getRoom").mockReturnValue(room1); jest.spyOn(client, "getRoom").mockReturnValue(room1);
const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!; const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
@@ -153,13 +153,13 @@ describe("MatrixRTCSessionManager", () => {
it("Doesn't fire event if unrelated sessions ends", () => { it("Doesn't fire event if unrelated sessions ends", () => {
const onEnded = jest.fn(); const onEnded = jest.fn();
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded); client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other_app" }]); const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other_app" }]);
jest.spyOn(client, "getRooms").mockReturnValue([room1]); jest.spyOn(client, "getRooms").mockReturnValue([room1]);
jest.spyOn(client, "getRoom").mockReturnValue(room1); jest.spyOn(client, "getRoom").mockReturnValue(room1);
client.emit(ClientEvent.Room, room1); client.emit(ClientEvent.Room, room1);
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]); mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!; const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0]; const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];

View File

@@ -31,7 +31,7 @@ import {
type SessionMembershipData, type SessionMembershipData,
type LivekitFocusSelection, type LivekitFocusSelection,
} from "../../../src/matrixrtc"; } from "../../../src/matrixrtc";
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks"; import { makeMockClient, makeMockRoom, sessionMembershipTemplate, mockCallMembership, type MockClient } from "./mocks";
import { MembershipManager } from "../../../src/matrixrtc/MembershipManager.ts"; import { MembershipManager } from "../../../src/matrixrtc/MembershipManager.ts";
import { waitFor } from "../../test-utils/test-utils.ts"; import { waitFor } from "../../test-utils/test-utils.ts";
@@ -90,7 +90,7 @@ describe("MembershipManager", () => {
// Default to fake timers. // Default to fake timers.
jest.useFakeTimers(); jest.useFakeTimers();
client = makeMockClient("@alice:example.org", "AAAAAAA"); client = makeMockClient("@alice:example.org", "AAAAAAA");
room = makeMockRoom([membershipTemplate]); room = makeMockRoom([sessionMembershipTemplate]);
// Provide a default mock that is like the default "non error" server behaviour. // Provide a default mock that is like the default "non error" server behaviour.
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" }); (client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined); (client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
@@ -385,7 +385,7 @@ describe("MembershipManager", () => {
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent); const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
await jest.advanceTimersByTimeAsync(RESTART_DELAY); await jest.advanceTimersByTimeAsync(RESTART_DELAY);
// first simulate the sync, then resolve sending the delayed event. // first simulate the sync, then resolve sending the delayed event.
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]); await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
resolve({ delay_id: "id" }); resolve({ delay_id: "id" });
// Let the scheduler run one iteration so that the new join gets sent // Let the scheduler run one iteration so that the new join gets sent
await jest.runOnlyPendingTimersAsync(); await jest.runOnlyPendingTimersAsync();
@@ -468,7 +468,7 @@ describe("MembershipManager", () => {
describe("onRTCSessionMemberUpdate()", () => { describe("onRTCSessionMemberUpdate()", () => {
it("does nothing if not joined", async () => { it("does nothing if not joined", async () => {
const manager = new MembershipManager({}, room, client, callSession); const manager = new MembershipManager({}, room, client, callSession);
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]); await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
await jest.advanceTimersToNextTimerAsync(); await jest.advanceTimersToNextTimerAsync();
expect(client.sendStateEvent).not.toHaveBeenCalled(); expect(client.sendStateEvent).not.toHaveBeenCalled();
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled(); expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
@@ -485,7 +485,7 @@ describe("MembershipManager", () => {
(client._unstable_sendDelayedStateEvent as Mock).mockClear(); (client._unstable_sendDelayedStateEvent as Mock).mockClear();
await manager.onRTCSessionMemberUpdate([ await manager.onRTCSessionMemberUpdate([
mockCallMembership(membershipTemplate, room.roomId), mockCallMembership(sessionMembershipTemplate, room.roomId),
mockCallMembership( mockCallMembership(
{ ...(myMembership as SessionMembershipData), user_id: client.getUserId()! }, { ...(myMembership as SessionMembershipData), user_id: client.getUserId()! },
room.roomId, room.roomId,
@@ -508,7 +508,7 @@ describe("MembershipManager", () => {
(client._unstable_sendDelayedStateEvent as Mock).mockClear(); (client._unstable_sendDelayedStateEvent as Mock).mockClear();
// Our own membership is removed: // Our own membership is removed:
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]); await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
await jest.advanceTimersByTimeAsync(1); await jest.advanceTimersByTimeAsync(1);
expect(client.sendStateEvent).toHaveBeenCalled(); expect(client.sendStateEvent).toHaveBeenCalled();
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled(); expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled();
@@ -531,7 +531,7 @@ describe("MembershipManager", () => {
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent); const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
await jest.advanceTimersByTimeAsync(10_000); await jest.advanceTimersByTimeAsync(10_000);
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]); await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
resolve({ delay_id: "id" }); resolve({ delay_id: "id" });
await jest.advanceTimersByTimeAsync(10_000); await jest.advanceTimersByTimeAsync(10_000);
@@ -900,7 +900,10 @@ describe("MembershipManager", () => {
const manager = new MembershipManager({}, room, client, callSession); const manager = new MembershipManager({}, room, client, callSession);
manager.join([]); manager.join([]);
expect(manager.isActivated()).toEqual(true); expect(manager.isActivated()).toEqual(true);
const membership = mockCallMembership({ ...membershipTemplate, user_id: client.getUserId()! }, room.roomId); const membership = mockCallMembership(
{ ...sessionMembershipTemplate, user_id: client.getUserId()! },
room.roomId,
);
await manager.onRTCSessionMemberUpdate([membership]); await manager.onRTCSessionMemberUpdate([membership]);
await manager.updateCallIntent("video"); await manager.updateCallIntent("video");
expect(client.sendStateEvent).toHaveBeenCalledTimes(2); expect(client.sendStateEvent).toHaveBeenCalledTimes(2);
@@ -914,7 +917,7 @@ describe("MembershipManager", () => {
manager.join([]); manager.join([]);
expect(manager.isActivated()).toEqual(true); expect(manager.isActivated()).toEqual(true);
const membership = mockCallMembership( const membership = mockCallMembership(
{ ...membershipTemplate, "user_id": client.getUserId()!, "m.call.intent": "video" }, { ...sessionMembershipTemplate, "user_id": client.getUserId()!, "m.call.intent": "video" },
room.roomId, room.roomId,
); );
await manager.onRTCSessionMemberUpdate([membership]); await manager.onRTCSessionMemberUpdate([membership]);
@@ -927,7 +930,7 @@ describe("MembershipManager", () => {
it("Should prefix log with MembershipManager used", async () => { it("Should prefix log with MembershipManager used", async () => {
const spy = jest.spyOn(console, "error"); const spy = jest.spyOn(console, "error");
const client = makeMockClient("@alice:example.org", "AAAAAAA"); const client = makeMockClient("@alice:example.org", "AAAAAAA");
const room = makeMockRoom([membershipTemplate]); const room = makeMockRoom([sessionMembershipTemplate]);
const membershipManager = new MembershipManager(undefined, room, client, callSession); const membershipManager = new MembershipManager(undefined, room, client, callSession);

View File

@@ -20,7 +20,7 @@ import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManage
import { type CallMembership, type Statistics } from "../../../src/matrixrtc"; import { type CallMembership, type Statistics } from "../../../src/matrixrtc";
import { type ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts"; import { type ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts";
import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts"; import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts";
import { membershipTemplate, mockCallMembership } from "./mocks.ts"; import { sessionMembershipTemplate, mockCallMembership } from "./mocks.ts";
import { decodeBase64, TypedEventEmitter } from "../../../src"; import { decodeBase64, TypedEventEmitter } from "../../../src";
import { RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts"; import { RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts";
import { type RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts"; import { type RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts";
@@ -864,7 +864,7 @@ describe("RTCEncryptionManager", () => {
function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership { function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership {
return mockCallMembership( return mockCallMembership(
{ ...membershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts }, { ...sessionMembershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts },
"!room:id", "!room:id",
); );
} }

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey } from "./mocks"; import { makeMockEvent, makeMockRoom, sessionMembershipTemplate, makeKey } from "./mocks";
import { RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport"; import { RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport";
import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport"; import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport";
import { EventType, MatrixClient, RoomEvent } from "../../../src"; import { EventType, MatrixClient, RoomEvent } from "../../../src";
@@ -48,7 +48,7 @@ describe("RoomKeyTransport", () => {
roomEventEncryptionKeysReceivedTotalAge: 0, roomEventEncryptionKeysReceivedTotalAge: 0,
}, },
}; };
room = makeMockRoom([membershipTemplate]); room = makeMockRoom([sessionMembershipTemplate]);
client = new MatrixClient({ baseUrl: "base_url" }); client = new MatrixClient({ baseUrl: "base_url" });
await client.matrixRTC.start(); await client.matrixRTC.start();
transport = new RoomKeyTransport(room, client, statistics, { transport = new RoomKeyTransport(room, client, statistics, {

View File

@@ -16,13 +16,17 @@ limitations under the License.
import { EventEmitter } from "stream"; import { EventEmitter } from "stream";
import { EventType, type Room, RoomEvent, type MatrixClient, type MatrixEvent } from "../../../src"; import { EventType, MatrixEvent, type Room, RoomEvent, type MatrixClient } from "../../../src";
import { CallMembership, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership"; import {
CallMembership,
type RtcMembershipData,
type SessionMembershipData,
} from "../../../src/matrixrtc/CallMembership";
import { secureRandomString } from "../../../src/randomstring"; import { secureRandomString } from "../../../src/randomstring";
export type MembershipData = (SessionMembershipData | {}) & { user_id: string }; export type MembershipData = (SessionMembershipData | RtcMembershipData | {}) & { user_id: string };
export const membershipTemplate: SessionMembershipData & { user_id: string } = { export const sessionMembershipTemplate: SessionMembershipData & { user_id: string } = {
"application": "m.call", "application": "m.call",
"call_id": "", "call_id": "",
"user_id": "@mock:user.example", "user_id": "@mock:user.example",
@@ -44,6 +48,15 @@ export const membershipTemplate: SessionMembershipData & { user_id: string } = {
"m.call.intent": "voice", "m.call.intent": "voice",
}; };
export const rtcMembershipTemplate: RtcMembershipData = {
"slot_id": "m.call#",
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
"member": { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
"rtc_transports": [{ type: "livekit" }],
"m.call.intent": "voice",
"versions": [],
};
export type MockClient = Pick< export type MockClient = Pick<
MatrixClient, MatrixClient,
| "getUserId" | "getUserId"
@@ -131,15 +144,14 @@ export function makeMockEvent(
content: any, content: any,
timestamp?: number, timestamp?: number,
): MatrixEvent { ): MatrixEvent {
return { return new MatrixEvent({
getType: jest.fn().mockReturnValue(type), event_id: "mock_event_id",
getContent: jest.fn().mockReturnValue(content), sender,
getSender: jest.fn().mockReturnValue(sender), type,
getTs: jest.fn().mockReturnValue(timestamp ?? Date.now()), content,
getRoomId: jest.fn().mockReturnValue(roomId), room_id: roomId,
getId: jest.fn().mockReturnValue(secureRandomString(8)), origin_server_ts: timestamp ?? 0,
isDecryptionFailure: jest.fn().mockReturnValue(false), });
} as unknown as MatrixEvent;
} }
export function mockRTCEvent({ user_id: sender, ...membershipData }: MembershipData, roomId: string): MatrixEvent { export function mockRTCEvent({ user_id: sender, ...membershipData }: MembershipData, roomId: string): MatrixEvent {

View File

@@ -230,8 +230,8 @@ export class CallMembership {
* @throws if the data does not match any known membership format. * @throws if the data does not match any known membership format.
*/ */
public constructor( public constructor(
private matrixEvent: MatrixEvent, private readonly matrixEvent: MatrixEvent,
private relatedEvent?: MatrixEvent, private readonly relatedEvent?: MatrixEvent,
) { ) {
const data = matrixEvent.getContent() as any; const data = matrixEvent.getContent() as any;
const sessionErrors: string[] = []; const sessionErrors: string[] = [];
@@ -252,8 +252,8 @@ export class CallMembership {
const eventId = matrixEvent.getId(); const eventId = matrixEvent.getId();
const sender = matrixEvent.getSender(); const sender = matrixEvent.getSender();
if (eventId === undefined) throw new Error("parentEvent is missing eventId field"); if (eventId === undefined) throw new Error("CallMembership matrixEvent is missing eventId field");
if (sender === undefined) throw new Error("parentEvent is missing sender field"); if (sender === undefined) throw new Error("CallMembership matrixEvent is missing sender field");
this.matrixEventData = { eventId, sender }; this.matrixEventData = { eventId, sender };
} }

View File

@@ -349,37 +349,40 @@ export class MatrixRTCSession extends TypedEventEmitter<
const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix); const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
const callMemberships: CallMembership[] = []; const callMemberships: CallMembership[] = [];
const createMembership = async (memberEvent: MatrixEvent): Promise<CallMembership | undefined> => {
const relatedEventId = memberEvent.relationEventId;
const fetchRelatedEvent = async (): Promise<MatrixEvent | undefined> => {
const eventData = await client
.fetchRoomEvent(room.roomId, relatedEventId!)
.catch((e) => logger.error(`Could not get related event ${relatedEventId} for call membership`, e));
return eventData ? new MatrixEvent(eventData) : undefined;
};
const relatedEvent = relatedEventId
? (room.findEventById(relatedEventId) ?? (await fetchRelatedEvent()))
: undefined;
let membership = undefined;
try {
membership = new CallMembership(memberEvent, relatedEvent);
} catch (e) {
logger.warn("Couldn't construct call membership: ", e);
return undefined;
}
// static check for newly created memberships
if (!deepCompare(membership.slotDescription, slotDescription)) {
logger.info(
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
);
return undefined;
}
return membership;
};
for (const memberEvent of callMemberEvents) { for (const memberEvent of callMemberEvents) {
let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId()); let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId());
if (!membership) { if (!membership) membership = await createMembership(memberEvent);
const relatedEventId = memberEvent.relationEventId; if (!membership) continue;
const getRelatedMatrixEvent = async (): Promise<MatrixEvent | undefined> => {
const eventData = await client
.fetchRoomEvent(room.roomId, relatedEventId!)
.catch((e) =>
logger.error(`Could not get related event ${relatedEventId} for call membership`, e),
);
return eventData ? new MatrixEvent(eventData) : undefined;
};
const relatedEvent = relatedEventId
? (room.findEventById(relatedEventId) ?? (await getRelatedMatrixEvent()))
: undefined;
try {
membership = new CallMembership(memberEvent, relatedEvent);
} catch (e) {
logger.warn("Couldn't construct call membership: ", e);
continue;
}
// static check for newly created memberships
if (!deepCompare(membership.slotDescription, slotDescription)) {
logger.info(
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
);
continue;
}
}
// Dynamic checks for all (including existing) memberships // Dynamic checks for all (including existing) memberships
if (membership.isExpired()) { if (membership.isExpired()) {
@@ -394,7 +397,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
} }
callMemberships.sort((a, b) => a.createdTs() - b.createdTs()); callMemberships.sort((a, b) => a.createdTs() - b.createdTs());
if (callMemberships.length > 1) { if (callMemberships.length >= 1) {
logger.debug( logger.debug(
`Call memberships in room ${room.roomId}, in order: `, `Call memberships in room ${room.roomId}, in order: `,
callMemberships.map((m) => [m.createdTs(), m.sender]), callMemberships.map((m) => [m.createdTs(), m.sender]),
@@ -814,7 +817,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
* *
* This function should be called when the room members or call memberships might have changed. * This function should be called when the room members or call memberships might have changed.
*/ */
private recalculateSessionMembers = async (): Promise<void> => { private async recalculateSessionMembers(): Promise<void> {
const oldMemberships = this.memberships; const oldMemberships = this.memberships;
const newMemberships = await MatrixRTCSession.sessionMembershipsForSlot( const newMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
this.room, this.room,
@@ -863,5 +866,5 @@ export class MatrixRTCSession extends TypedEventEmitter<
void this.encryptionManager?.onMembershipsUpdate(oldMemberships); void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
this.setExpiryTimer(); this.setExpiryTimer();
}; }
} }