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

Allow multiple rtc sessions per room (with different sessionDescriptions) (#4945)

* Introduce sessionDescription

Signed-off-by: Timo K <toger5@hotmail.de>

* Make sessionDescription part of a MatrixRTCSession

Signed-off-by: Timo K <toger5@hotmail.de>

* Make session manager only menage session for one sessionDescription

Signed-off-by: Timo K <toger5@hotmail.de>

* make membership manager aware about session (application + id)
Before this was just hardcoded to a call session

Signed-off-by: Timo K <toger5@hotmail.de>

* update tests

Signed-off-by: Timo K <toger5@hotmail.de>

* fix doc comments

Signed-off-by: Timo K <toger5@hotmail.de>

* Make fields private,  improve comments, improve whitespace, don't use deprecated fields

Signed-off-by: Timo K <toger5@hotmail.de>

* add test for other application end event

Signed-off-by: Timo K <toger5@hotmail.de>

* rename call -> session

Signed-off-by: Timo K <toger5@hotmail.de>

* fix tests

Signed-off-by: Timo K <toger5@hotmail.de>

* remove id check since its already part of `deepCompare(membership.sessionDescription, sessionDescription)`

Signed-off-by: Timo K <toger5@hotmail.de>

* remove scope related tests. The id should be the only thing that scopes sessions. everything else is application (session type) specific

Signed-off-by: Timo K <toger5@hotmail.de>

* review

Signed-off-by: Timo K <toger5@hotmail.de>

* add test for custom sessionDescription

Signed-off-by: Timo K <toger5@hotmail.de>

* callMembershipsForRoom to default to call

Signed-off-by: Timo K <toger5@hotmail.de>

* roomSessionForRoom backwards compatible (And deprecate the call specific method)

Signed-off-by: Timo K <toger5@hotmail.de>

---------

Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo
2025-08-19 13:58:57 +02:00
committed by GitHub
parent 2b46579bd8
commit 2abf7ca795
7 changed files with 263 additions and 114 deletions

View File

@@ -26,6 +26,8 @@ const mockFocus = { type: "mock" };
const textEncoder = new TextEncoder();
const callSession = { id: "", application: "m.call" };
describe("MatrixRTCSession", () => {
let client: MatrixClient;
let sess: MatrixRTCSession | undefined;
@@ -49,14 +51,14 @@ describe("MatrixRTCSession", () => {
it("creates a room-scoped session from room state", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].callId).toEqual("");
expect(sess?.memberships[0].sessionDescription.id).toEqual("");
expect(sess?.memberships[0].scope).toEqual("m.room");
expect(sess?.memberships[0].application).toEqual("m.call");
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
expect(sess?.memberships[0].isExpired()).toEqual(false);
expect(sess?.callId).toEqual("");
expect(sess?.sessionDescription.id).toEqual("");
});
it("ignores memberships where application is not m.call", () => {
@@ -64,7 +66,7 @@ describe("MatrixRTCSession", () => {
application: "not-m.call",
});
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0);
});
@@ -74,7 +76,7 @@ describe("MatrixRTCSession", () => {
scope: "m.room",
});
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0);
});
@@ -86,7 +88,7 @@ describe("MatrixRTCSession", () => {
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
jest.advanceTimersByTime(2000);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
jest.useRealTimers();
@@ -95,7 +97,7 @@ describe("MatrixRTCSession", () => {
it("ignores memberships events of members not in the room", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships.length).toEqual(0);
});
@@ -106,14 +108,14 @@ describe("MatrixRTCSession", () => {
expiredMembership.created_ts = 500;
expiredMembership.expires = 1000;
const mockRoom = makeMockRoom([expiredMembership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships[0].getAbsoluteExpiry()).toEqual(1500);
jest.useRealTimers();
});
it("returns empty session if no membership events are present", () => {
const mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess?.memberships).toHaveLength(0);
});
@@ -148,7 +150,7 @@ describe("MatrixRTCSession", () => {
}),
}),
};
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom as unknown as Room);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
expect(sess.memberships).toHaveLength(0);
});
@@ -183,7 +185,7 @@ describe("MatrixRTCSession", () => {
}),
}),
};
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom as unknown as Room);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
expect(sess.memberships).toHaveLength(0);
});
@@ -191,7 +193,7 @@ describe("MatrixRTCSession", () => {
const testMembership = Object.assign({}, membershipTemplate);
(testMembership.device_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.memberships).toHaveLength(0);
});
@@ -199,23 +201,7 @@ describe("MatrixRTCSession", () => {
const testMembership = Object.assign({}, membershipTemplate);
(testMembership.call_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
expect(sess.memberships).toHaveLength(0);
});
it("ignores memberships with no scope", () => {
const testMembership = Object.assign({}, membershipTemplate);
(testMembership.scope as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
expect(sess.memberships).toHaveLength(0);
});
it("ignores anything that's not a room-scoped call (for now)", () => {
const testMembership = Object.assign({}, membershipTemplate);
testMembership.scope = "m.user";
const mockRoom = makeMockRoom([testMembership]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.memberships).toHaveLength(0);
});
});
@@ -230,7 +216,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
expect(sess.getOldestMembership()!.deviceId).toEqual("old");
jest.useRealTimers();
});
@@ -255,7 +241,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
type: "livekit",
@@ -275,7 +261,7 @@ describe("MatrixRTCSession", () => {
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
type: "livekit",
@@ -302,7 +288,7 @@ describe("MatrixRTCSession", () => {
client._unstable_updateDelayedEvent = jest.fn();
mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
});
afterEach(async () => {
@@ -385,7 +371,7 @@ describe("MatrixRTCSession", () => {
describe("onMembershipsChanged", () => {
it("does not emit if no membership changes", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMembershipsChanged = jest.fn();
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
@@ -396,7 +382,7 @@ describe("MatrixRTCSession", () => {
it("emits on membership changes", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMembershipsChanged = jest.fn();
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
@@ -451,7 +437,7 @@ describe("MatrixRTCSession", () => {
client.encryptAndSendToDevice = sendToDeviceMock;
mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
});
afterEach(async () => {
@@ -570,7 +556,7 @@ describe("MatrixRTCSession", () => {
device_id: "BBBBBBB",
});
const mockRoom = makeMockRoom([membershipTemplate, member2]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
// joining will trigger an initial key send
const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => {
@@ -619,7 +605,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers();
try {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const keysSentPromise1 = new Promise<EncryptionKeysEventContent>((resolve) => {
sendEventMock.mockImplementation((_roomId, _evType, payload) => resolve(payload));
@@ -670,7 +656,7 @@ describe("MatrixRTCSession", () => {
const mockRoom = makeMockRoom([member1, member2]);
mockRoomState(mockRoom, [member1, member2]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1;
@@ -715,7 +701,7 @@ describe("MatrixRTCSession", () => {
};
const mockRoom = makeMockRoom([member1, member2]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
await keysSentPromise1;
@@ -779,7 +765,7 @@ describe("MatrixRTCSession", () => {
device_id: "BBBBBBB",
});
const mockRoom = makeMockRoom([membershipTemplate, member2]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const onMyEncryptionKeyChanged = jest.fn();
sess.on(
@@ -869,7 +855,7 @@ describe("MatrixRTCSession", () => {
if (i === 0) {
// if first time around then set up the session
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
} else {
// otherwise update the state reducing the membership each time in order to trigger key rotation
@@ -895,7 +881,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers();
try {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const keysSentPromise1 = new Promise((resolve) => {
sendEventMock.mockImplementation(resolve);
@@ -936,7 +922,7 @@ describe("MatrixRTCSession", () => {
});
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, {
manageMediaKeys: true,
@@ -959,7 +945,7 @@ describe("MatrixRTCSession", () => {
describe("receiving", () => {
it("collects keys from encryption events", async () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -984,7 +970,7 @@ describe("MatrixRTCSession", () => {
it("collects keys at non-zero indices", async () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -1010,7 +996,7 @@ describe("MatrixRTCSession", () => {
it("collects keys by merging", async () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
makeMockEvent("io.element.call.encryption_keys", "@bob:example.org", "1234roomId", {
@@ -1061,7 +1047,7 @@ describe("MatrixRTCSession", () => {
it("ignores older keys at same index", async () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
makeMockEvent(
@@ -1120,7 +1106,7 @@ describe("MatrixRTCSession", () => {
it("key timestamps are treated as monotonic", async () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
makeMockEvent(
@@ -1164,7 +1150,7 @@ describe("MatrixRTCSession", () => {
it("ignores keys event for the local participant", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
mockRoom.emitTimelineEvent(
@@ -1187,7 +1173,7 @@ describe("MatrixRTCSession", () => {
jest.useFakeTimers();
try {
const mockRoom = makeMockRoom([membershipTemplate]);
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
// defaults to getTs()
jest.setSystemTime(1000);