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

fix lints

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-10-06 15:06:03 +02:00
parent 8a21ff69bb
commit 4bbb240142
3 changed files with 143 additions and 121 deletions

View File

@@ -38,6 +38,8 @@ describe("MatrixRTCSession", () => {
client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA"); client.getDeviceId = jest.fn().mockReturnValue("AAAAAAA");
client.sendEvent = jest.fn().mockResolvedValue({ event_id: "success" }); client.sendEvent = jest.fn().mockResolvedValue({ event_id: "success" });
client.decryptEventIfNeeded = jest.fn(); client.decryptEventIfNeeded = jest.fn();
client.fetchRoomEvent = jest.fn().mockResolvedValue(undefined);
client._unstable_sendDelayedStateEvent = jest.fn().mockResolvedValue({ event_id: "success" });
}); });
afterEach(async () => { afterEach(async () => {
@@ -48,10 +50,10 @@ describe("MatrixRTCSession", () => {
}); });
describe("roomSessionForRoom", () => { describe("roomSessionForRoom", () => {
it("creates a room-scoped session from room state", () => { it("creates a room-scoped session from room state", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(1); expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].slotDescription.id).toEqual(""); expect(sess?.memberships[0].slotDescription.id).toEqual("");
expect(sess?.memberships[0].scope).toEqual("m.room"); expect(sess?.memberships[0].scope).toEqual("m.room");
@@ -61,26 +63,26 @@ describe("MatrixRTCSession", () => {
expect(sess?.slotDescription.id).toEqual(""); expect(sess?.slotDescription.id).toEqual("");
}); });
it("ignores memberships where application is not m.call", () => { it("ignores memberships where application is not m.call", async () => {
const testMembership = Object.assign({}, membershipTemplate, { const testMembership = Object.assign({}, membershipTemplate, {
application: "not-m.call", application: "not-m.call",
}); });
const mockRoom = makeMockRoom([testMembership]); const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); const sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0); expect(sess?.memberships).toHaveLength(0);
}); });
it("ignores memberships where callId is not empty", () => { it("ignores memberships where callId is not empty", async () => {
const testMembership = Object.assign({}, membershipTemplate, { const testMembership = Object.assign({}, membershipTemplate, {
call_id: "not-empty", call_id: "not-empty",
scope: "m.room", scope: "m.room",
}); });
const mockRoom = makeMockRoom([testMembership]); const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); const sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0); expect(sess?.memberships).toHaveLength(0);
}); });
it("ignores expired memberships events", () => { it("ignores expired memberships events", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const expiredMembership = Object.assign({}, membershipTemplate); const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.expires = 1000; expiredMembership.expires = 1000;
@@ -88,38 +90,38 @@ describe("MatrixRTCSession", () => {
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]); const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
jest.advanceTimersByTime(2000); jest.advanceTimersByTime(2000);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(1); expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA"); expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
jest.useRealTimers(); jest.useRealTimers();
}); });
it("ignores memberships events of members not in the room", () => { it("ignores memberships events of members not in the room", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join; mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(0); expect(sess?.memberships.length).toEqual(0);
}); });
it("honours created_ts", () => { it("honours created_ts", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.setSystemTime(500); jest.setSystemTime(500);
const expiredMembership = Object.assign({}, membershipTemplate); const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.created_ts = 500; expiredMembership.created_ts = 500;
expiredMembership.expires = 1000; expiredMembership.expires = 1000;
const mockRoom = makeMockRoom([expiredMembership]); const mockRoom = makeMockRoom([expiredMembership]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships[0].getAbsoluteExpiry()).toEqual(1500); expect(sess?.memberships[0].getAbsoluteExpiry()).toEqual(1500);
jest.useRealTimers(); jest.useRealTimers();
}); });
it("returns empty session if no membership events are present", () => { it("returns empty session if no membership events are present", async () => {
const mockRoom = makeMockRoom([]); const mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0); expect(sess?.memberships).toHaveLength(0);
}); });
it("safely ignores events with no memberships section", () => { it("safely ignores events with no memberships section", async () => {
const roomId = secureRandomString(8); const roomId = secureRandomString(8);
const event = { const event = {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix), getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
@@ -150,11 +152,11 @@ describe("MatrixRTCSession", () => {
}), }),
}), }),
}; };
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
expect(sess.memberships).toHaveLength(0); expect(sess.memberships).toHaveLength(0);
}); });
it("safely ignores events with junk memberships section", () => { it("safely ignores events with junk memberships section", async () => {
const roomId = secureRandomString(8); const roomId = secureRandomString(8);
const event = { const event = {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix), getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
@@ -185,29 +187,29 @@ describe("MatrixRTCSession", () => {
}), }),
}), }),
}; };
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
expect(sess.memberships).toHaveLength(0); expect(sess.memberships).toHaveLength(0);
}); });
it("ignores memberships with no device_id", () => { it("ignores memberships with no device_id", async () => {
const testMembership = Object.assign({}, membershipTemplate); const testMembership = Object.assign({}, membershipTemplate);
(testMembership.device_id as string | undefined) = undefined; (testMembership.device_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]); const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); const sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.memberships).toHaveLength(0); expect(sess.memberships).toHaveLength(0);
}); });
it("ignores memberships with no call_id", () => { it("ignores memberships with no call_id", async () => {
const testMembership = Object.assign({}, membershipTemplate); const testMembership = Object.assign({}, membershipTemplate);
(testMembership.call_id as string | undefined) = undefined; (testMembership.call_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]); const mockRoom = makeMockRoom([testMembership]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.memberships).toHaveLength(0); expect(sess.memberships).toHaveLength(0);
}); });
}); });
describe("getOldestMembership", () => { describe("getOldestMembership", () => {
it("returns the oldest membership event", () => { it("returns the oldest membership event", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.setSystemTime(4000); jest.setSystemTime(4000);
const mockRoom = makeMockRoom([ const mockRoom = makeMockRoom([
@@ -216,7 +218,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }), Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]); ]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.getOldestMembership()!.deviceId).toEqual("old"); expect(sess.getOldestMembership()!.deviceId).toEqual("old");
jest.useRealTimers(); jest.useRealTimers();
}); });
@@ -229,7 +231,7 @@ describe("MatrixRTCSession", () => {
[undefined, "audio", "audio"], [undefined, "audio", "audio"],
["audio", "audio", "audio"], ["audio", "audio", "audio"],
["audio", "video", undefined], ["audio", "video", undefined],
])("gets correct consensus for %s + %s = %s", (intentA, intentB, result) => { ])("gets correct consensus for %s + %s = %s", async (intentA, intentB, result) => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.setSystemTime(4000); jest.setSystemTime(4000);
const mockRoom = makeMockRoom([ const mockRoom = makeMockRoom([
@@ -237,7 +239,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { "m.call.intent": intentB }), Object.assign({}, membershipTemplate, { "m.call.intent": intentB }),
]); ]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.getConsensusCallIntent()).toEqual(result); expect(sess.getConsensusCallIntent()).toEqual(result);
jest.useRealTimers(); jest.useRealTimers();
}); });
@@ -249,7 +251,7 @@ describe("MatrixRTCSession", () => {
livekit_service_url: "https://active.url", livekit_service_url: "https://active.url",
livekit_alias: "!active:active.url", livekit_alias: "!active:active.url",
}; };
it("gets the correct active focus with oldest_membership", () => { it("gets the correct active focus with oldest_membership", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.setSystemTime(3000); jest.setSystemTime(3000);
const mockRoom = makeMockRoom([ const mockRoom = makeMockRoom([
@@ -262,7 +264,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }), Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]); ]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], { sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
type: "livekit", type: "livekit",
@@ -273,7 +275,7 @@ describe("MatrixRTCSession", () => {
); );
jest.useRealTimers(); jest.useRealTimers();
}); });
it("does not provide focus if the selection method is unknown", () => { it("does not provide focus if the selection method is unknown", async () => {
const mockRoom = makeMockRoom([ const mockRoom = makeMockRoom([
Object.assign({}, membershipTemplate, { Object.assign({}, membershipTemplate, {
device_id: "foo", device_id: "foo",
@@ -284,7 +286,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }), Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]); ]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], { sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
type: "livekit", type: "livekit",
@@ -300,7 +302,7 @@ describe("MatrixRTCSession", () => {
let sendStateEventMock: jest.Mock; let sendStateEventMock: jest.Mock;
let sentStateEvent: Promise<void>; let sentStateEvent: Promise<void>;
beforeEach(() => { beforeEach(async () => {
sentStateEvent = new Promise((resolve) => { sentStateEvent = new Promise((resolve) => {
sendStateEventMock = jest.fn(resolve); sendStateEventMock = jest.fn(resolve);
}); });
@@ -311,7 +313,7 @@ describe("MatrixRTCSession", () => {
client._unstable_updateDelayedEvent = jest.fn(); client._unstable_updateDelayedEvent = jest.fn();
mockRoom = makeMockRoom([]); mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
}); });
afterEach(async () => { afterEach(async () => {
@@ -347,8 +349,14 @@ describe("MatrixRTCSession", () => {
sess!.joinRoomSession([mockFocus], mockFocus, { notificationType: "ring" }); sess!.joinRoomSession([mockFocus], mockFocus, { notificationType: "ring" });
await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 5000))]); await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 5000))]);
const { resolve: r, promise: p } = Promise.withResolvers();
sess?.once(MatrixRTCSessionEvent.JoinStateChanged, r);
await p;
mockRoomState(mockRoom, [{ ...membershipTemplate, user_id: client.getUserId()! }]); mockRoomState(mockRoom, [{ ...membershipTemplate, user_id: client.getUserId()! }]);
sess!.onRTCSessionMemberUpdate(); sess!.onRTCSessionMemberUpdate();
const { resolve, promise } = Promise.withResolvers();
sess?.once(MatrixRTCSessionEvent.MembershipsChanged, resolve);
await promise;
const ownMembershipId = sess?.memberships[0].eventId; const ownMembershipId = sess?.memberships[0].eventId;
expect(client.sendEvent).toHaveBeenCalledWith(mockRoom!.roomId, EventType.RTCNotification, { expect(client.sendEvent).toHaveBeenCalledWith(mockRoom!.roomId, EventType.RTCNotification, {
@@ -361,7 +369,6 @@ describe("MatrixRTCSession", () => {
"lifetime": 30000, "lifetime": 30000,
"sender_ts": expect.any(Number), "sender_ts": expect.any(Number),
}); });
// Check if deprecated notify event is also sent. // Check if deprecated notify event is also sent.
expect(client.sendEvent).toHaveBeenCalledWith(mockRoom!.roomId, EventType.CallNotify, { expect(client.sendEvent).toHaveBeenCalledWith(mockRoom!.roomId, EventType.CallNotify, {
"application": "m.call", "application": "m.call",
@@ -498,9 +505,9 @@ describe("MatrixRTCSession", () => {
}); });
describe("onMembershipsChanged", () => { describe("onMembershipsChanged", () => {
it("does not emit if no membership changes", () => { it("does not emit if no membership changes", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMembershipsChanged = jest.fn(); const onMembershipsChanged = jest.fn();
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged); sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
@@ -509,9 +516,9 @@ describe("MatrixRTCSession", () => {
expect(onMembershipsChanged).not.toHaveBeenCalled(); expect(onMembershipsChanged).not.toHaveBeenCalled();
}); });
it("emits on membership changes", () => { it("emits on membership changes", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMembershipsChanged = jest.fn(); const onMembershipsChanged = jest.fn();
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged); sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
@@ -555,7 +562,7 @@ describe("MatrixRTCSession", () => {
let sendEventMock: jest.Mock; let sendEventMock: jest.Mock;
let sendToDeviceMock: jest.Mock; let sendToDeviceMock: jest.Mock;
beforeEach(() => { beforeEach(async () => {
sendStateEventMock = jest.fn().mockResolvedValue({ event_id: "id" }); sendStateEventMock = jest.fn().mockResolvedValue({ event_id: "id" });
sendDelayedStateMock = jest.fn().mockResolvedValue({ event_id: "id" }); sendDelayedStateMock = jest.fn().mockResolvedValue({ event_id: "id" });
sendEventMock = jest.fn().mockResolvedValue({ event_id: "id" }); sendEventMock = jest.fn().mockResolvedValue({ event_id: "id" });
@@ -566,7 +573,7 @@ describe("MatrixRTCSession", () => {
client.encryptAndSendToDevice = sendToDeviceMock; client.encryptAndSendToDevice = sendToDeviceMock;
mockRoom = makeMockRoom([]); mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
}); });
afterEach(async () => { afterEach(async () => {
@@ -685,7 +692,7 @@ describe("MatrixRTCSession", () => {
device_id: "BBBBBBB", device_id: "BBBBBBB",
}); });
const mockRoom = makeMockRoom([membershipTemplate, member2]); const mockRoom = makeMockRoom([membershipTemplate, member2]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
// joining will trigger an initial key send // joining will trigger an initial key send
const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => { const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => {
@@ -734,7 +741,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers(); jest.useFakeTimers();
try { try {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => { const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => {
sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload)); sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload));
@@ -785,7 +792,7 @@ describe("MatrixRTCSession", () => {
const mockRoom = makeMockRoom([member1, member2]); const mockRoom = makeMockRoom([member1, member2]);
mockRoomState(mockRoom, [member1, member2]); mockRoomState(mockRoom, [member1, member2]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1; await keysSentPromise1;
@@ -830,7 +837,7 @@ describe("MatrixRTCSession", () => {
}; };
const mockRoom = makeMockRoom([member1, member2]); const mockRoom = makeMockRoom([member1, member2]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1; await keysSentPromise1;
@@ -894,7 +901,7 @@ describe("MatrixRTCSession", () => {
device_id: "BBBBBBB", device_id: "BBBBBBB",
}); });
const mockRoom = makeMockRoom([membershipTemplate, member2]); const mockRoom = makeMockRoom([membershipTemplate, member2]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMyEncryptionKeyChanged = jest.fn(); const onMyEncryptionKeyChanged = jest.fn();
sess.on( sess.on(
@@ -984,7 +991,7 @@ describe("MatrixRTCSession", () => {
if (i === 0) { if (i === 0) {
// if first time around then set up the session // if first time around then set up the session
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
} else { } else {
// otherwise update the state reducing the membership each time in order to trigger key rotation // otherwise update the state reducing the membership each time in order to trigger key rotation
@@ -1010,7 +1017,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers(); jest.useFakeTimers();
try { try {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const keysSentPromise1 = new Promise((resolve) => { const keysSentPromise1 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve); sendEventMock.mockImplementation(resolve);
@@ -1051,7 +1058,7 @@ describe("MatrixRTCSession", () => {
}); });
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { sess!.joinRoomSession([mockFocus], mockFocus, {
manageMediaKeys: true, manageMediaKeys: true,
@@ -1074,7 +1081,7 @@ describe("MatrixRTCSession", () => {
describe("receiving", () => { describe("receiving", () => {
it("collects keys from encryption events", async () => { it("collects keys from encryption events", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", { makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -1099,7 +1106,7 @@ describe("MatrixRTCSession", () => {
it("collects keys at non-zero indices", async () => { it("collects keys at non-zero indices", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", { makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -1125,7 +1132,7 @@ describe("MatrixRTCSession", () => {
it("collects keys by merging", async () => { it("collects keys by merging", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", { makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -1176,7 +1183,7 @@ describe("MatrixRTCSession", () => {
it("ignores older keys at same index", async () => { it("ignores older keys at same index", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
makeMockEvent( makeMockEvent(
@@ -1235,7 +1242,7 @@ describe("MatrixRTCSession", () => {
it("key timestamps are treated as monotonic", async () => { it("key timestamps are treated as monotonic", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
makeMockEvent( makeMockEvent(
@@ -1277,9 +1284,9 @@ describe("MatrixRTCSession", () => {
); );
}); });
it("ignores keys event for the local participant", () => { it("ignores keys event for the local participant", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent( mockRoom.emitTimelineEvent(
@@ -1302,7 +1309,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers(); jest.useFakeTimers();
try { try {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
// defaults to getTs() // defaults to getTs()
jest.setSystemTime(1000); jest.setSystemTime(1000);
@@ -1356,9 +1363,9 @@ describe("MatrixRTCSession", () => {
}); });
}); });
describe("read status", () => { describe("read status", () => {
it("returns the correct probablyLeft status", () => { it("returns the correct probablyLeft status", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess!.probablyLeft).toBe(undefined); expect(sess!.probablyLeft).toBe(undefined);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
@@ -1372,9 +1379,9 @@ describe("MatrixRTCSession", () => {
expect(sess!.probablyLeft).toBe(true); expect(sess!.probablyLeft).toBe(true);
}); });
it("returns membershipStatus once joinRoomSession got called", () => { it("returns membershipStatus once joinRoomSession got called", async () => {
const mockRoom = makeMockRoom([membershipTemplate]); const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession); sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess!.membershipStatus).toBe(undefined); expect(sess!.membershipStatus).toBe(undefined);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true }); sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });

View File

@@ -17,6 +17,7 @@ limitations under the License.
import { type Logger, logger as rootLogger } from "../logger.ts"; import { type Logger, logger as rootLogger } from "../logger.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts"; import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { EventTimeline } from "../models/event-timeline.ts"; import { EventTimeline } from "../models/event-timeline.ts";
import { MatrixEvent } from "../models/event.ts";
import { type Room } from "../models/room.ts"; import { type Room } from "../models/room.ts";
import { type MatrixClient } from "../client.ts"; import { type MatrixClient } from "../client.ts";
import { EventType, RelationType } from "../@types/event.ts"; import { EventType, RelationType } from "../@types/event.ts";
@@ -50,7 +51,6 @@ import {
} from "./RoomAndToDeviceKeyTransport.ts"; } from "./RoomAndToDeviceKeyTransport.ts";
import { TypedReEmitter } from "../ReEmitter.ts"; import { TypedReEmitter } from "../ReEmitter.ts";
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts"; import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
import { MatrixEvent } from "../models/event.ts";
/** /**
* Events emitted by MatrixRTCSession * Events emitted by MatrixRTCSession
@@ -311,8 +311,9 @@ export class MatrixRTCSession extends TypedEventEmitter<
*/ */
public static async callMembershipsForRoom( public static async callMembershipsForRoom(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">, room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
client: Pick<MatrixClient, "fetchRoomEvent">,
): Promise<CallMembership[]> { ): Promise<CallMembership[]> {
return await MatrixRTCSession.sessionMembershipsForSlot(room, { return await MatrixRTCSession.sessionMembershipsForSlot(room, client, {
id: "", id: "",
application: "m.call", application: "m.call",
}); });
@@ -323,9 +324,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
*/ */
public static async sessionMembershipsForRoom( public static async sessionMembershipsForRoom(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">, room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
client: Pick<MatrixClient, "fetchRoomEvent">,
sessionDescription: SlotDescription, sessionDescription: SlotDescription,
): Promise<CallMembership[]> { ): Promise<CallMembership[]> {
return await this.sessionMembershipsForSlot(room, sessionDescription); return await this.sessionMembershipsForSlot(room, client, sessionDescription);
} }
/** /**
@@ -334,6 +336,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
*/ */
public static async sessionMembershipsForSlot( public static async sessionMembershipsForSlot(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">, room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
client: Pick<MatrixClient, "fetchRoomEvent">,
slotDescription: SlotDescription, slotDescription: SlotDescription,
existingMemberships?: CallMembership[], existingMemberships?: CallMembership[],
): Promise<CallMembership[]> { ): Promise<CallMembership[]> {
@@ -350,9 +353,18 @@ export class MatrixRTCSession extends TypedEventEmitter<
let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId()); let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId());
if (!membership) { if (!membership) {
const relatedEventId = memberEvent.relationEventId; const relatedEventId = memberEvent.relationEventId;
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 const relatedEvent = relatedEventId
? room.findEventById(relatedEventId) ? room.findEventById(relatedEventId)
: new MatrixEvent(await room.client.fetchRoomEvent(room.roomId, relatedEventId!)); : await getRelatedMatrixEvent();
try { try {
membership = new CallMembership(memberEvent, relatedEvent); membership = new CallMembership(memberEvent, relatedEvent);
@@ -403,7 +415,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
* @deprecated Use `MatrixRTCSession.sessionForSlot` with sessionDescription `{ id: "", application: "m.call" }` instead. * @deprecated Use `MatrixRTCSession.sessionForSlot` with sessionDescription `{ id: "", application: "m.call" }` instead.
*/ */
public static async roomSessionForRoom(client: MatrixClient, room: Room): Promise<MatrixRTCSession> { public static async roomSessionForRoom(client: MatrixClient, room: Room): Promise<MatrixRTCSession> {
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, { const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, client, {
id: "", id: "",
application: "m.call", application: "m.call",
}); });
@@ -413,7 +425,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
/** /**
* @deprecated Use `MatrixRTCSession.sessionForSlot` instead. * @deprecated Use `MatrixRTCSession.sessionForSlot` instead.
*/ */
public static async sessionForRoom( public static sessionForRoom(
client: MatrixClient, client: MatrixClient,
room: Room, room: Room,
slotDescription: SlotDescription, slotDescription: SlotDescription,
@@ -431,7 +443,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
room: Room, room: Room,
slotDescription: SlotDescription, slotDescription: SlotDescription,
): Promise<MatrixRTCSession> { ): Promise<MatrixRTCSession> {
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, slotDescription); const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, client, slotDescription);
return new MatrixRTCSession(client, room, callMemberships, slotDescription); return new MatrixRTCSession(client, room, callMemberships, slotDescription);
} }
@@ -472,6 +484,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
| "off" | "off"
| "on" | "on"
| "decryptEventIfNeeded" | "decryptEventIfNeeded"
| "fetchRoomEvent"
>, >,
private roomSubset: Pick< private roomSubset: Pick<
Room, Room,
@@ -784,14 +797,14 @@ export class MatrixRTCSession extends TypedEventEmitter<
* Call this when the Matrix room members have changed. * Call this when the Matrix room members have changed.
*/ */
public onRoomMemberUpdate = (): void => { public onRoomMemberUpdate = (): void => {
this.recalculateSessionMembers(); void this.recalculateSessionMembers();
}; };
/** /**
* Call this when something changed that may impacts the current MatrixRTC members in this session. * Call this when something changed that may impacts the current MatrixRTC members in this session.
*/ */
public onRTCSessionMemberUpdate = (): void => { public onRTCSessionMemberUpdate = (): void => {
this.recalculateSessionMembers(); void this.recalculateSessionMembers();
}; };
/** /**
@@ -801,52 +814,54 @@ 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 = (): void => { private recalculateSessionMembers = async (): Promise<void> => {
const oldMemberships = this.memberships; const oldMemberships = this.memberships;
void MatrixRTCSession.sessionMembershipsForSlot(this.room, this.slotDescription, oldMemberships).then( const newMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
(newMemberships) => { this.room,
this.memberships = newMemberships; this.client,
this._slotId = this._slotId ?? this.memberships[0]?.slotId; this.slotDescription,
oldMemberships,
const changed =
oldMemberships.length != this.memberships.length ||
// If they have the same length, this is enough to check "changed"
oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
if (changed) {
this.logger.info(
`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`,
);
logDurationSync(this.logger, "emit MatrixRTCSessionEvent.MembershipsChanged", () => {
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
});
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
const ownMembership = this.membershipManager?.ownMembership;
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
// If we're the first member in the call, we're responsible for
// sending the notification event
if (ownMembership.eventId && this.joinConfig?.notificationType) {
this.sendCallNotify(
ownMembership.eventId,
this.joinConfig.notificationType,
ownMembership.callIntent,
);
} else {
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
}
}
// If anyone else joins the session it is no longer our responsibility to send the notification.
// (If we were the joiner we already did sent the notification in the block above.)
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
}
// This also needs to be done if `changed` = false
// A member might have updated their fingerprint (created_ts)
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
this.setExpiryTimer();
},
); );
this.memberships = newMemberships;
this._slotId = this._slotId ?? this.memberships[0]?.slotId;
const changed =
oldMemberships.length != this.memberships.length ||
// If they have the same length, this is enough to check "changed"
oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
if (changed) {
this.logger.info(
`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`,
);
logDurationSync(this.logger, "emit MatrixRTCSessionEvent.MembershipsChanged", () => {
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
});
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
const ownMembership = this.membershipManager?.ownMembership;
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
// If we're the first member in the call, we're responsible for
// sending the notification event
if (ownMembership.eventId && this.joinConfig?.notificationType) {
this.sendCallNotify(
ownMembership.eventId,
this.joinConfig.notificationType,
ownMembership.callIntent,
);
} else {
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
}
}
// If anyone else joins the session it is no longer our responsibility to send the notification.
// (If we were the joiner we already did sent the notification in the block above.)
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
}
// This also needs to be done if `changed` = false
// A member might have updated their fingerprint (created_ts)
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
this.setExpiryTimer();
}; };
} }

View File

@@ -62,11 +62,11 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
this.logger = rootLogger.getChild("[MatrixRTCSessionManager]"); this.logger = rootLogger.getChild("[MatrixRTCSessionManager]");
} }
public start(): void { public async start(): Promise<void> {
// We shouldn't need to null-check here, but matrix-client.spec.ts mocks getRooms // We shouldn't need to null-check here, but matrix-client.spec.ts mocks getRooms
// returning nothing, and breaks tests if you change it to return an empty array :'( // returning nothing, and breaks tests if you change it to return an empty array :'(
for (const room of this.client.getRooms() ?? []) { for (const room of this.client.getRooms() ?? []) {
const session = MatrixRTCSession.sessionForRoom(this.client, room, this.sessionDescription); const session = await MatrixRTCSession.sessionForRoom(this.client, room, this.sessionDescription);
if (session.memberships.length > 0) { if (session.memberships.length > 0) {
this.roomSessions.set(room.roomId, session); this.roomSessions.set(room.roomId, session);
} }
@@ -98,11 +98,11 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
* Gets the main MatrixRTC session for a room, returning an empty session * Gets the main MatrixRTC session for a room, returning an empty session
* if no members are currently participating * if no members are currently participating
*/ */
public getRoomSession(room: Room): MatrixRTCSession { public async getRoomSession(room: Room): Promise<MatrixRTCSession> {
if (!this.roomSessions.has(room.roomId)) { if (!this.roomSessions.has(room.roomId)) {
this.roomSessions.set( this.roomSessions.set(
room.roomId, room.roomId,
MatrixRTCSession.sessionForRoom(this.client, room, this.sessionDescription), await MatrixRTCSession.sessionForRoom(this.client, room, this.sessionDescription),
); );
} }
@@ -110,7 +110,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
} }
private onRoom = (room: Room): void => { private onRoom = (room: Room): void => {
this.refreshRoom(room); void this.refreshRoom(room);
}; };
private onRoomState = (event: MatrixEvent, _state: RoomState): void => { private onRoomState = (event: MatrixEvent, _state: RoomState): void => {
@@ -121,13 +121,13 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
} }
if (event.getType() == EventType.GroupCallMemberPrefix) { if (event.getType() == EventType.GroupCallMemberPrefix) {
this.refreshRoom(room); void this.refreshRoom(room);
} }
}; };
private refreshRoom(room: Room): void { private async refreshRoom(room: Room): Promise<void> {
const isNewSession = !this.roomSessions.has(room.roomId); const isNewSession = !this.roomSessions.has(room.roomId);
const session = this.getRoomSession(room); const session = await this.getRoomSession(room);
const wasActiveAndKnown = session.memberships.length > 0 && !isNewSession; const wasActiveAndKnown = session.memberships.length > 0 && !isNewSession;
// This needs to be here and the event listener cannot be setup in the MatrixRTCSession, // This needs to be here and the event listener cannot be setup in the MatrixRTCSession,