You've already forked matrix-js-sdk
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:
@@ -26,6 +26,8 @@ const mockFocus = { type: "mock" };
|
|||||||
|
|
||||||
const textEncoder = new TextEncoder();
|
const textEncoder = new TextEncoder();
|
||||||
|
|
||||||
|
const callSession = { id: "", application: "m.call" };
|
||||||
|
|
||||||
describe("MatrixRTCSession", () => {
|
describe("MatrixRTCSession", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
let sess: MatrixRTCSession | undefined;
|
let sess: MatrixRTCSession | undefined;
|
||||||
@@ -49,14 +51,14 @@ describe("MatrixRTCSession", () => {
|
|||||||
it("creates a room-scoped session from room state", () => {
|
it("creates a room-scoped session from room state", () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
|
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess?.memberships.length).toEqual(1);
|
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].scope).toEqual("m.room");
|
||||||
expect(sess?.memberships[0].application).toEqual("m.call");
|
expect(sess?.memberships[0].application).toEqual("m.call");
|
||||||
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
|
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
|
||||||
expect(sess?.memberships[0].isExpired()).toEqual(false);
|
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", () => {
|
it("ignores memberships where application is not m.call", () => {
|
||||||
@@ -64,7 +66,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
application: "not-m.call",
|
application: "not-m.call",
|
||||||
});
|
});
|
||||||
const mockRoom = makeMockRoom([testMembership]);
|
const mockRoom = makeMockRoom([testMembership]);
|
||||||
const sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess?.memberships).toHaveLength(0);
|
expect(sess?.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
scope: "m.room",
|
scope: "m.room",
|
||||||
});
|
});
|
||||||
const mockRoom = makeMockRoom([testMembership]);
|
const mockRoom = makeMockRoom([testMembership]);
|
||||||
const sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess?.memberships).toHaveLength(0);
|
expect(sess?.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
|
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
|
||||||
|
|
||||||
jest.advanceTimersByTime(2000);
|
jest.advanceTimersByTime(2000);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = 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();
|
||||||
@@ -95,7 +97,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
it("ignores memberships events of members not in the room", () => {
|
it("ignores memberships events of members not in the room", () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
|
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess?.memberships.length).toEqual(0);
|
expect(sess?.memberships.length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,14 +108,14 @@ describe("MatrixRTCSession", () => {
|
|||||||
expiredMembership.created_ts = 500;
|
expiredMembership.created_ts = 500;
|
||||||
expiredMembership.expires = 1000;
|
expiredMembership.expires = 1000;
|
||||||
const mockRoom = makeMockRoom([expiredMembership]);
|
const mockRoom = makeMockRoom([expiredMembership]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = 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", () => {
|
||||||
const mockRoom = makeMockRoom([]);
|
const mockRoom = makeMockRoom([]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess?.memberships).toHaveLength(0);
|
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);
|
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);
|
expect(sess.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -191,7 +193,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
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.roomSessionForRoom(client, mockRoom);
|
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess.memberships).toHaveLength(0);
|
expect(sess.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -199,23 +201,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
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.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
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);
|
|
||||||
expect(sess.memberships).toHaveLength(0);
|
expect(sess.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -230,7 +216,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
|
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");
|
expect(sess.getOldestMembership()!.deviceId).toEqual("old");
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
@@ -255,7 +241,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
|
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" }], {
|
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
@@ -275,7 +261,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
Object.assign({}, membershipTemplate, { device_id: "bar", created_ts: 2000 }),
|
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" }], {
|
sess.joinRoomSession([{ type: "livekit", livekit_service_url: "htts://test.org" }], {
|
||||||
type: "livekit",
|
type: "livekit",
|
||||||
@@ -302,7 +288,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
client._unstable_updateDelayedEvent = jest.fn();
|
client._unstable_updateDelayedEvent = jest.fn();
|
||||||
|
|
||||||
mockRoom = makeMockRoom([]);
|
mockRoom = makeMockRoom([]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -385,7 +371,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
describe("onMembershipsChanged", () => {
|
describe("onMembershipsChanged", () => {
|
||||||
it("does not emit if no membership changes", () => {
|
it("does not emit if no membership changes", () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
const onMembershipsChanged = jest.fn();
|
const onMembershipsChanged = jest.fn();
|
||||||
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
||||||
@@ -396,7 +382,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
it("emits on membership changes", () => {
|
it("emits on membership changes", () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
const onMembershipsChanged = jest.fn();
|
const onMembershipsChanged = jest.fn();
|
||||||
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
sess.on(MatrixRTCSessionEvent.MembershipsChanged, onMembershipsChanged);
|
||||||
@@ -451,7 +437,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
client.encryptAndSendToDevice = sendToDeviceMock;
|
client.encryptAndSendToDevice = sendToDeviceMock;
|
||||||
|
|
||||||
mockRoom = makeMockRoom([]);
|
mockRoom = makeMockRoom([]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -570,7 +556,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
device_id: "BBBBBBB",
|
device_id: "BBBBBBB",
|
||||||
});
|
});
|
||||||
const mockRoom = makeMockRoom([membershipTemplate, member2]);
|
const mockRoom = makeMockRoom([membershipTemplate, member2]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = 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) => {
|
||||||
@@ -619,7 +605,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
try {
|
try {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = 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));
|
||||||
@@ -670,7 +656,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
const mockRoom = makeMockRoom([member1, member2]);
|
const mockRoom = makeMockRoom([member1, member2]);
|
||||||
mockRoomState(mockRoom, [member1, member2]);
|
mockRoomState(mockRoom, [member1, member2]);
|
||||||
|
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
|
|
||||||
await keysSentPromise1;
|
await keysSentPromise1;
|
||||||
@@ -715,7 +701,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mockRoom = makeMockRoom([member1, member2]);
|
const mockRoom = makeMockRoom([member1, member2]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
|
|
||||||
await keysSentPromise1;
|
await keysSentPromise1;
|
||||||
@@ -779,7 +765,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
device_id: "BBBBBBB",
|
device_id: "BBBBBBB",
|
||||||
});
|
});
|
||||||
const mockRoom = makeMockRoom([membershipTemplate, member2]);
|
const mockRoom = makeMockRoom([membershipTemplate, member2]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
const onMyEncryptionKeyChanged = jest.fn();
|
const onMyEncryptionKeyChanged = jest.fn();
|
||||||
sess.on(
|
sess.on(
|
||||||
@@ -869,7 +855,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.roomSessionForRoom(client, mockRoom);
|
sess = 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
|
||||||
@@ -895,7 +881,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
try {
|
try {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
const keysSentPromise1 = new Promise((resolve) => {
|
const keysSentPromise1 = new Promise((resolve) => {
|
||||||
sendEventMock.mockImplementation(resolve);
|
sendEventMock.mockImplementation(resolve);
|
||||||
@@ -936,7 +922,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, {
|
sess!.joinRoomSession([mockFocus], mockFocus, {
|
||||||
manageMediaKeys: true,
|
manageMediaKeys: true,
|
||||||
@@ -959,7 +945,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.roomSessionForRoom(client, mockRoom);
|
sess = 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", {
|
||||||
@@ -984,7 +970,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.roomSessionForRoom(client, mockRoom);
|
sess = 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", {
|
||||||
@@ -1010,7 +996,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.roomSessionForRoom(client, mockRoom);
|
sess = 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", {
|
||||||
@@ -1061,7 +1047,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.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
mockRoom.emitTimelineEvent(
|
mockRoom.emitTimelineEvent(
|
||||||
makeMockEvent(
|
makeMockEvent(
|
||||||
@@ -1120,7 +1106,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.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
mockRoom.emitTimelineEvent(
|
mockRoom.emitTimelineEvent(
|
||||||
makeMockEvent(
|
makeMockEvent(
|
||||||
@@ -1164,7 +1150,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
it("ignores keys event for the local participant", () => {
|
it("ignores keys event for the local participant", () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
mockRoom.emitTimelineEvent(
|
mockRoom.emitTimelineEvent(
|
||||||
@@ -1187,7 +1173,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
try {
|
try {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, mockRoom);
|
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
|
|
||||||
// defaults to getTs()
|
// defaults to getTs()
|
||||||
jest.setSystemTime(1000);
|
jest.setSystemTime(1000);
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ 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 { MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
|
import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
|
||||||
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks";
|
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks";
|
||||||
|
import { logger } from "../../../src/logger";
|
||||||
|
|
||||||
describe("MatrixRTCSessionManager", () => {
|
describe("MatrixRTCSessionManager", () => {
|
||||||
let client: MatrixClient;
|
let client: MatrixClient;
|
||||||
@@ -47,6 +48,21 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Doesn't fire event if unrelated sessions starts", () => {
|
||||||
|
const onStarted = jest.fn();
|
||||||
|
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
expect(onStarted).not.toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("Fires event when session ends", () => {
|
it("Fires event when session ends", () => {
|
||||||
const onEnded = jest.fn();
|
const onEnded = jest.fn();
|
||||||
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
||||||
@@ -59,9 +75,75 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
||||||
|
|
||||||
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
const membEvent = roomState.getStateEvents("")[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);
|
||||||
|
|
||||||
expect(onEnded).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
|
expect(onEnded).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Fires correctly with for with custom sessionDescription", () => {
|
||||||
|
const onStarted = jest.fn();
|
||||||
|
const onEnded = jest.fn();
|
||||||
|
// create a session manager with a custom session description
|
||||||
|
const sessionManager = new MatrixRTCSessionManager(logger, client, { id: "test", application: "m.notCall" });
|
||||||
|
|
||||||
|
// manually start the session manager (its not the default one started by the client)
|
||||||
|
sessionManager.start();
|
||||||
|
sessionManager.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
||||||
|
sessionManager.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
expect(onStarted).not.toHaveBeenCalled();
|
||||||
|
onStarted.mockClear();
|
||||||
|
|
||||||
|
const room2 = makeMockRoom([{ ...membershipTemplate, application: "m.notCall", call_id: "test" }]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room2);
|
||||||
|
expect(onStarted).toHaveBeenCalled();
|
||||||
|
onStarted.mockClear();
|
||||||
|
|
||||||
|
mockRoomState(room2, [{ user_id: membershipTemplate.user_id }]);
|
||||||
|
jest.spyOn(client, "getRoom").mockReturnValue(room2);
|
||||||
|
|
||||||
|
const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
|
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
||||||
|
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
|
||||||
|
expect(onEnded).toHaveBeenCalled();
|
||||||
|
onEnded.mockClear();
|
||||||
|
|
||||||
|
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
||||||
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
|
const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
|
const membEventOther = roomStateOther.getStateEvents("org.matrix.msc3401.call.member")[0];
|
||||||
|
client.emit(RoomStateEvent.Events, membEventOther, roomStateOther, null);
|
||||||
|
expect(onEnded).not.toHaveBeenCalled();
|
||||||
|
} finally {
|
||||||
|
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
||||||
|
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Doesn't fire event if unrelated sessions ends", () => {
|
||||||
|
const onEnded = jest.fn();
|
||||||
|
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
||||||
|
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other_app" }]);
|
||||||
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
|
||||||
|
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
||||||
|
|
||||||
|
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
|
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
||||||
|
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
|
||||||
|
|
||||||
|
expect(onEnded).not.toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ function createAsyncHandle<T>(method: MockedFunction<any>) {
|
|||||||
return { reject, resolve };
|
return { reject, resolve };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const callSession = { id: "", application: "m.call" };
|
||||||
|
|
||||||
describe("MembershipManager", () => {
|
describe("MembershipManager", () => {
|
||||||
let client: MockClient;
|
let client: MockClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
@@ -95,12 +97,12 @@ describe("MembershipManager", () => {
|
|||||||
|
|
||||||
describe("isActivated()", () => {
|
describe("isActivated()", () => {
|
||||||
it("defaults to false", () => {
|
it("defaults to false", () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
expect(manager.isActivated()).toEqual(false);
|
expect(manager.isActivated()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns true after join()", () => {
|
it("returns true after join()", () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([]);
|
manager.join([]);
|
||||||
expect(manager.isActivated()).toEqual(true);
|
expect(manager.isActivated()).toEqual(true);
|
||||||
});
|
});
|
||||||
@@ -114,7 +116,7 @@ describe("MembershipManager", () => {
|
|||||||
const updateDelayedEventHandle = createAsyncHandle<void>(client._unstable_updateDelayedEvent as Mock);
|
const updateDelayedEventHandle = createAsyncHandle<void>(client._unstable_updateDelayedEvent as Mock);
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
const memberManager = new MembershipManager(undefined, room, client, () => undefined);
|
const memberManager = new MembershipManager(undefined, room, client, () => undefined, callSession);
|
||||||
memberManager.join([focus], focusActive);
|
memberManager.join([focus], focusActive);
|
||||||
// expects
|
// expects
|
||||||
await waitForMockCall(client.sendStateEvent, Promise.resolve({ event_id: "id" }));
|
await waitForMockCall(client.sendStateEvent, Promise.resolve({ event_id: "id" }));
|
||||||
@@ -130,7 +132,7 @@ describe("MembershipManager", () => {
|
|||||||
focus_active: focusActive,
|
focus_active: focusActive,
|
||||||
scope: "m.room",
|
scope: "m.room",
|
||||||
},
|
},
|
||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA_m.call",
|
||||||
);
|
);
|
||||||
updateDelayedEventHandle.resolve?.();
|
updateDelayedEventHandle.resolve?.();
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
||||||
@@ -138,13 +140,13 @@ describe("MembershipManager", () => {
|
|||||||
{ delay: 8000 },
|
{ delay: 8000 },
|
||||||
"org.matrix.msc3401.call.member",
|
"org.matrix.msc3401.call.member",
|
||||||
{},
|
{},
|
||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA_m.call",
|
||||||
);
|
);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("reschedules delayed leave event if sending state cancels it", async () => {
|
it("reschedules delayed leave event if sending state cancels it", async () => {
|
||||||
const memberManager = new MembershipManager(undefined, room, client, () => undefined);
|
const memberManager = new MembershipManager(undefined, room, client, () => undefined, callSession);
|
||||||
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
||||||
const waitForUpdateDelaye = waitForMockCallOnce(
|
const waitForUpdateDelaye = waitForMockCallOnce(
|
||||||
client._unstable_updateDelayedEvent,
|
client._unstable_updateDelayedEvent,
|
||||||
@@ -189,7 +191,7 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const userStateKey = `${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA`;
|
const userStateKey = `${!useOwnedStateEvents ? "_" : ""}@alice:example.org_AAAAAAA_m.call`;
|
||||||
// preparing the delayed disconnect should handle ratelimiting
|
// preparing the delayed disconnect should handle ratelimiting
|
||||||
const sendDelayedStateAttempt = new Promise<void>((resolve) => {
|
const sendDelayedStateAttempt = new Promise<void>((resolve) => {
|
||||||
const error = new MatrixError({ errcode: "M_LIMIT_EXCEEDED" });
|
const error = new MatrixError({ errcode: "M_LIMIT_EXCEEDED" });
|
||||||
@@ -220,6 +222,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
callSession,
|
||||||
);
|
);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
|
|
||||||
@@ -276,7 +279,7 @@ describe("MembershipManager", () => {
|
|||||||
describe("delayed leave event", () => {
|
describe("delayed leave event", () => {
|
||||||
it("does not try again to schedule a delayed leave event if not supported", () => {
|
it("does not try again to schedule a delayed leave event if not supported", () => {
|
||||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
delayedHandle.reject?.(
|
delayedHandle.reject?.(
|
||||||
new UnsupportedDelayedEventsEndpointError(
|
new UnsupportedDelayedEventsEndpointError(
|
||||||
@@ -288,7 +291,7 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
it("does try to schedule a delayed leave event again if rate limited", async () => {
|
it("does try to schedule a delayed leave event again if rate limited", async () => {
|
||||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
delayedHandle.reject?.(new HTTPError("rate limited", 429, undefined));
|
delayedHandle.reject?.(new HTTPError("rate limited", 429, undefined));
|
||||||
await jest.advanceTimersByTimeAsync(5000);
|
await jest.advanceTimersByTimeAsync(5000);
|
||||||
@@ -300,6 +303,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
callSession,
|
||||||
);
|
);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledWith(
|
||||||
@@ -307,7 +311,7 @@ describe("MembershipManager", () => {
|
|||||||
{ delay: 123456 },
|
{ delay: 123456 },
|
||||||
"org.matrix.msc3401.call.member",
|
"org.matrix.msc3401.call.member",
|
||||||
{},
|
{},
|
||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA_m.call",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -319,6 +323,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
callSession,
|
||||||
);
|
);
|
||||||
// Join with the membership manager
|
// Join with the membership manager
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -351,7 +356,13 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses membershipEventExpiryMs from config", async () => {
|
it("uses membershipEventExpiryMs from config", async () => {
|
||||||
const manager = new MembershipManager({ membershipEventExpiryMs: 1234567 }, room, client, () => undefined);
|
const manager = new MembershipManager(
|
||||||
|
{ membershipEventExpiryMs: 1234567 },
|
||||||
|
room,
|
||||||
|
client,
|
||||||
|
() => undefined,
|
||||||
|
callSession,
|
||||||
|
);
|
||||||
|
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
@@ -370,12 +381,12 @@ describe("MembershipManager", () => {
|
|||||||
type: "livekit",
|
type: "livekit",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA_m.call",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does nothing if join called when already joined", async () => {
|
it("does nothing if join called when already joined", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
@@ -387,7 +398,7 @@ describe("MembershipManager", () => {
|
|||||||
describe("leave()", () => {
|
describe("leave()", () => {
|
||||||
// TODO add rate limit cases.
|
// TODO add rate limit cases.
|
||||||
it("resolves delayed leave event when leave is called", async () => {
|
it("resolves delayed leave event when leave is called", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
await manager.leave();
|
await manager.leave();
|
||||||
@@ -395,7 +406,7 @@ describe("MembershipManager", () => {
|
|||||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("send leave event when leave is called and resolving delayed leave fails", async () => {
|
it("send leave event when leave is called and resolving delayed leave fails", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue("unknown");
|
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue("unknown");
|
||||||
@@ -406,11 +417,11 @@ describe("MembershipManager", () => {
|
|||||||
room.roomId,
|
room.roomId,
|
||||||
"org.matrix.msc3401.call.member",
|
"org.matrix.msc3401.call.member",
|
||||||
{},
|
{},
|
||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA_m.call",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it("does nothing if not joined", () => {
|
it("does nothing if not joined", () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
expect(async () => await manager.leave()).not.toThrow();
|
expect(async () => await manager.leave()).not.toThrow();
|
||||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
@@ -420,7 +431,7 @@ describe("MembershipManager", () => {
|
|||||||
describe("getsActiveFocus", () => {
|
describe("getsActiveFocus", () => {
|
||||||
it("gets the correct active focus with oldest_membership", () => {
|
it("gets the correct active focus with oldest_membership", () => {
|
||||||
const getOldestMembership = jest.fn();
|
const getOldestMembership = jest.fn();
|
||||||
const manager = new MembershipManager({}, room, client, getOldestMembership);
|
const manager = new MembershipManager({}, room, client, getOldestMembership, callSession);
|
||||||
// Before joining the active focus should be undefined (see FocusInUse on MatrixRTCSession)
|
// Before joining the active focus should be undefined (see FocusInUse on MatrixRTCSession)
|
||||||
expect(manager.getActiveFocus()).toBe(undefined);
|
expect(manager.getActiveFocus()).toBe(undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -455,7 +466,7 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not provide focus if the selection method is unknown", () => {
|
it("does not provide focus if the selection method is unknown", () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], Object.assign(focusActive, { type: "unknown_type" }));
|
manager.join([focus], Object.assign(focusActive, { type: "unknown_type" }));
|
||||||
expect(manager.getActiveFocus()).toBe(undefined);
|
expect(manager.getActiveFocus()).toBe(undefined);
|
||||||
});
|
});
|
||||||
@@ -463,7 +474,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, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
||||||
await jest.advanceTimersToNextTimerAsync();
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
@@ -471,7 +482,7 @@ describe("MembershipManager", () => {
|
|||||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("does nothing if own membership still present", async () => {
|
it("does nothing if own membership still present", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
const myMembership = (client.sendStateEvent as Mock).mock.calls[0][2];
|
const myMembership = (client.sendStateEvent as Mock).mock.calls[0][2];
|
||||||
@@ -495,7 +506,7 @@ describe("MembershipManager", () => {
|
|||||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("recreates membership if it is missing", async () => {
|
it("recreates membership if it is missing", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
||||||
@@ -513,7 +524,7 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("updates the UpdateExpiry entry in the action scheduler", async () => {
|
it("updates the UpdateExpiry entry in the action scheduler", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
||||||
@@ -547,6 +558,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
{ id: "", application: "m.call" },
|
||||||
);
|
);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
@@ -578,6 +590,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
{ id: "", application: "m.call" },
|
||||||
);
|
);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
@@ -600,14 +613,14 @@ describe("MembershipManager", () => {
|
|||||||
});
|
});
|
||||||
describe("status updates", () => {
|
describe("status updates", () => {
|
||||||
it("starts 'Disconnected'", () => {
|
it("starts 'Disconnected'", () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
expect(manager.status).toBe(Status.Disconnected);
|
expect(manager.status).toBe(Status.Disconnected);
|
||||||
});
|
});
|
||||||
it("emits 'Connection' and 'Connected' after join", async () => {
|
it("emits 'Connection' and 'Connected' after join", async () => {
|
||||||
const handleDelayedEvent = createAsyncHandle<void>(client._unstable_sendDelayedStateEvent);
|
const handleDelayedEvent = createAsyncHandle<void>(client._unstable_sendDelayedStateEvent);
|
||||||
const handleStateEvent = createAsyncHandle<void>(client.sendStateEvent);
|
const handleStateEvent = createAsyncHandle<void>(client.sendStateEvent);
|
||||||
|
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
expect(manager.status).toBe(Status.Disconnected);
|
expect(manager.status).toBe(Status.Disconnected);
|
||||||
const connectEmit = jest.fn();
|
const connectEmit = jest.fn();
|
||||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||||
@@ -621,7 +634,7 @@ describe("MembershipManager", () => {
|
|||||||
expect(connectEmit).toHaveBeenCalledWith(Status.Connecting, Status.Connected);
|
expect(connectEmit).toHaveBeenCalledWith(Status.Connecting, Status.Connected);
|
||||||
});
|
});
|
||||||
it("emits 'Disconnecting' and 'Disconnected' after leave", async () => {
|
it("emits 'Disconnecting' and 'Disconnected' after leave", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
const connectEmit = jest.fn();
|
const connectEmit = jest.fn();
|
||||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -637,7 +650,7 @@ describe("MembershipManager", () => {
|
|||||||
it("sends retry if call membership event is still valid at time of retry", async () => {
|
it("sends retry if call membership event is still valid at time of retry", async () => {
|
||||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
|
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
@@ -664,7 +677,7 @@ describe("MembershipManager", () => {
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
// Should call _unstable_sendDelayedStateEvent but not sendStateEvent because of the
|
// Should call _unstable_sendDelayedStateEvent but not sendStateEvent because of the
|
||||||
// RateLimit error.
|
// RateLimit error.
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -684,7 +697,7 @@ describe("MembershipManager", () => {
|
|||||||
it("abandons retry loop if leave() was called before sending state event", async () => {
|
it("abandons retry loop if leave() was called before sending state event", async () => {
|
||||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
|
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
handle.reject?.(
|
handle.reject?.(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
@@ -719,7 +732,7 @@ describe("MembershipManager", () => {
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
|
|
||||||
// Hit rate limit
|
// Hit rate limit
|
||||||
@@ -752,7 +765,7 @@ describe("MembershipManager", () => {
|
|||||||
new Headers({ "Retry-After": "2" }),
|
new Headers({ "Retry-After": "2" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive, delayEventSendError);
|
manager.join([focus], focusActive, delayEventSendError);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
@@ -772,7 +785,7 @@ describe("MembershipManager", () => {
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive, delayEventRestartError);
|
manager.join([focus], focusActive, delayEventRestartError);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
@@ -783,7 +796,7 @@ describe("MembershipManager", () => {
|
|||||||
it("falls back to using pure state events when some error occurs while sending delayed events", async () => {
|
it("falls back to using pure state events when some error occurs while sending delayed events", async () => {
|
||||||
const unrecoverableError = jest.fn();
|
const unrecoverableError = jest.fn();
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 601));
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 601));
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive, unrecoverableError);
|
manager.join([focus], focusActive, unrecoverableError);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
expect(unrecoverableError).not.toHaveBeenCalledWith();
|
expect(unrecoverableError).not.toHaveBeenCalledWith();
|
||||||
@@ -797,6 +810,7 @@ describe("MembershipManager", () => {
|
|||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
() => undefined,
|
() => undefined,
|
||||||
|
callSession,
|
||||||
);
|
);
|
||||||
manager.join([focus], focusActive, unrecoverableError);
|
manager.join([focus], focusActive, unrecoverableError);
|
||||||
for (let retries = 0; retries < 7; retries++) {
|
for (let retries = 0; retries < 7; retries++) {
|
||||||
@@ -814,7 +828,7 @@ describe("MembershipManager", () => {
|
|||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
||||||
new UnsupportedDelayedEventsEndpointError("not supported", "sendDelayedStateEvent"),
|
new UnsupportedDelayedEventsEndpointError("not supported", "sendDelayedStateEvent"),
|
||||||
);
|
);
|
||||||
const manager = new MembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined, callSession);
|
||||||
manager.join([focus], focusActive, unrecoverableError);
|
manager.join([focus], focusActive, unrecoverableError);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
|
|
||||||
@@ -828,7 +842,7 @@ it("Should prefix log with MembershipManager used", () => {
|
|||||||
const client = makeMockClient("@alice:example.org", "AAAAAAA");
|
const client = makeMockClient("@alice:example.org", "AAAAAAA");
|
||||||
const room = makeMockRoom([membershipTemplate]);
|
const room = makeMockRoom([membershipTemplate]);
|
||||||
|
|
||||||
const membershipManager = new MembershipManager(undefined, room, client, () => undefined, logger);
|
const membershipManager = new MembershipManager(undefined, room, client, () => undefined, callSession, logger);
|
||||||
|
|
||||||
const spy = jest.spyOn(console, "error");
|
const spy = jest.spyOn(console, "error");
|
||||||
// Double join
|
// Double join
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { type MatrixEvent } from "../matrix.ts";
|
|||||||
import { deepCompare } from "../utils.ts";
|
import { deepCompare } from "../utils.ts";
|
||||||
import { type Focus } from "./focus.ts";
|
import { type Focus } from "./focus.ts";
|
||||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
||||||
|
import { type SessionDescription } from "./MatrixRTCSession.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default duration in milliseconds that a membership is considered valid for.
|
* The default duration in milliseconds that a membership is considered valid for.
|
||||||
@@ -130,6 +131,9 @@ export class CallMembership {
|
|||||||
return this.parentEvent.getId();
|
return this.parentEvent.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use sessionDescription.id instead.
|
||||||
|
*/
|
||||||
public get callId(): string {
|
public get callId(): string {
|
||||||
return this.membershipData.call_id;
|
return this.membershipData.call_id;
|
||||||
}
|
}
|
||||||
@@ -138,6 +142,13 @@ export class CallMembership {
|
|||||||
return this.membershipData.device_id;
|
return this.membershipData.device_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get sessionDescription(): SessionDescription {
|
||||||
|
return {
|
||||||
|
application: this.membershipData.application,
|
||||||
|
id: this.membershipData.call_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public get application(): string | undefined {
|
public get application(): string | undefined {
|
||||||
return this.membershipData.application;
|
return this.membershipData.application;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { type Focus } from "./focus.ts";
|
|||||||
import { KnownMembership } from "../@types/membership.ts";
|
import { KnownMembership } from "../@types/membership.ts";
|
||||||
import { MembershipManager } from "./MembershipManager.ts";
|
import { MembershipManager } from "./MembershipManager.ts";
|
||||||
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
|
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
|
||||||
import { logDurationSync } from "../utils.ts";
|
import { deepCompare, logDurationSync } from "../utils.ts";
|
||||||
import { type Statistics, type RTCNotificationType } from "./types.ts";
|
import { type Statistics, type RTCNotificationType } from "./types.ts";
|
||||||
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
||||||
import type { IMembershipManager } from "./IMembershipManager.ts";
|
import type { IMembershipManager } from "./IMembershipManager.ts";
|
||||||
@@ -74,6 +74,14 @@ export interface SessionConfig {
|
|||||||
notificationType?: RTCNotificationType;
|
notificationType?: RTCNotificationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The session description is used to identify a session. Used in the state event.
|
||||||
|
*/
|
||||||
|
export interface SessionDescription {
|
||||||
|
id: string;
|
||||||
|
application: string;
|
||||||
|
}
|
||||||
|
|
||||||
// The names follow these principles:
|
// The names follow these principles:
|
||||||
// - we use the technical term delay if the option is related to delayed events.
|
// - we use the technical term delay if the option is related to delayed events.
|
||||||
// - we use delayedLeaveEvent if the option is related to the delayed leave event.
|
// - we use delayedLeaveEvent if the option is related to the delayed leave event.
|
||||||
@@ -86,7 +94,7 @@ export interface MembershipConfig {
|
|||||||
* Use the new Manager.
|
* Use the new Manager.
|
||||||
*
|
*
|
||||||
* Default: `false`.
|
* Default: `false`.
|
||||||
* @deprecated does nothing anymore we always default to the new memberhip manager.
|
* @deprecated does nothing anymore we always default to the new membership manager.
|
||||||
*/
|
*/
|
||||||
useNewMembershipManager?: boolean;
|
useNewMembershipManager?: boolean;
|
||||||
|
|
||||||
@@ -254,10 +262,27 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the call memberships for a room, oldest first
|
* Returns all the call memberships for a room that match the provided `sessionDescription`,
|
||||||
|
* oldest first.
|
||||||
|
*
|
||||||
|
* @deprecated Use `MatrixRTCSession.sessionMembershipsForRoom` instead.
|
||||||
*/
|
*/
|
||||||
public static callMembershipsForRoom(
|
public static callMembershipsForRoom(
|
||||||
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
|
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
|
||||||
|
): CallMembership[] {
|
||||||
|
return MatrixRTCSession.sessionMembershipsForRoom(room, {
|
||||||
|
id: "",
|
||||||
|
application: "m.call",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the call memberships for a room that match the provided `sessionDescription`,
|
||||||
|
* oldest first.
|
||||||
|
*/
|
||||||
|
public static sessionMembershipsForRoom(
|
||||||
|
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
|
||||||
|
sessionDescription: SessionDescription,
|
||||||
): CallMembership[] {
|
): CallMembership[] {
|
||||||
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId}]`);
|
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId}]`);
|
||||||
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||||
@@ -290,15 +315,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
try {
|
try {
|
||||||
const membership = new CallMembership(memberEvent, membershipData);
|
const membership = new CallMembership(memberEvent, membershipData);
|
||||||
|
|
||||||
if (membership.application !== "m.call") {
|
if (!deepCompare(membership.sessionDescription, sessionDescription)) {
|
||||||
// Only process MatrixRTC sessions associated with calls
|
logger.info(
|
||||||
logger.info("Skipping non-call MatrixRTC session");
|
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.sessionDescription)}`,
|
||||||
continue;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (membership.callId !== "" || membership.scope !== "m.room") {
|
|
||||||
// for now, just ignore anything that isn't a room scope call
|
|
||||||
logger.info(`Ignoring user-scoped call`);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,12 +349,33 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the MatrixRTC session for the room, whether there are currently active members or not
|
* Return the MatrixRTC session for the room.
|
||||||
|
* This returned session can be used to find out if there are active room call sessions
|
||||||
|
* for the requested room.
|
||||||
|
*
|
||||||
|
* This method is an alias for `MatrixRTCSession.sessionForRoom` with
|
||||||
|
* sessionDescription `{ id: "", application: "m.call" }`.
|
||||||
|
*
|
||||||
|
* @deprecated Use `MatrixRTCSession.sessionForRoom` with sessionDescription `{ id: "", application: "m.call" }` instead.
|
||||||
*/
|
*/
|
||||||
public static roomSessionForRoom(client: MatrixClient, room: Room): MatrixRTCSession {
|
public static roomSessionForRoom(client: MatrixClient, room: Room): MatrixRTCSession {
|
||||||
const callMemberships = MatrixRTCSession.callMembershipsForRoom(room);
|
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(room, { id: "", application: "m.call" });
|
||||||
|
return new MatrixRTCSession(client, room, callMemberships, { id: "", application: "m.call" });
|
||||||
|
}
|
||||||
|
|
||||||
return new MatrixRTCSession(client, room, callMemberships);
|
/**
|
||||||
|
* Return the MatrixRTC session for the room.
|
||||||
|
* This returned session can be used to find out if there are active sessions
|
||||||
|
* for the requested room and `sessionDescription`.
|
||||||
|
*/
|
||||||
|
public static sessionForRoom(
|
||||||
|
client: MatrixClient,
|
||||||
|
room: Room,
|
||||||
|
sessionDescription: SessionDescription,
|
||||||
|
): MatrixRTCSession {
|
||||||
|
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(room, sessionDescription);
|
||||||
|
|
||||||
|
return new MatrixRTCSession(client, room, callMemberships, sessionDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -379,10 +420,15 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
"getLiveTimeline" | "roomId" | "getVersion" | "hasMembershipState" | "on" | "off"
|
"getLiveTimeline" | "roomId" | "getVersion" | "hasMembershipState" | "on" | "off"
|
||||||
>,
|
>,
|
||||||
public memberships: CallMembership[],
|
public memberships: CallMembership[],
|
||||||
|
/**
|
||||||
|
* The session description is used to define the exact session this object is tracking.
|
||||||
|
* A session is distinct from another session if one of those properties differ: `roomSubset.roomId`, `sessionDescription.application`, `sessionDescription.id`.
|
||||||
|
*/
|
||||||
|
public readonly sessionDescription: SessionDescription,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = rootLogger.getChild(`[MatrixRTCSession ${roomSubset.roomId}]`);
|
this.logger = rootLogger.getChild(`[MatrixRTCSession ${roomSubset.roomId}]`);
|
||||||
this._callId = memberships[0]?.callId;
|
this._callId = memberships[0]?.sessionDescription.id;
|
||||||
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||||
// TODO: double check if this is actually needed. Should be covered by refreshRoom in MatrixRTCSessionManager
|
// TODO: double check if this is actually needed. Should be covered by refreshRoom in MatrixRTCSessionManager
|
||||||
roomState?.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
roomState?.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
||||||
@@ -440,6 +486,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
this.roomSubset,
|
this.roomSubset,
|
||||||
this.client,
|
this.client,
|
||||||
() => this.getOldestMembership(),
|
() => this.getOldestMembership(),
|
||||||
|
this.sessionDescription,
|
||||||
this.logger,
|
this.logger,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -648,9 +695,9 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
*/
|
*/
|
||||||
private recalculateSessionMembers = (): void => {
|
private recalculateSessionMembers = (): void => {
|
||||||
const oldMemberships = this.memberships;
|
const oldMemberships = this.memberships;
|
||||||
this.memberships = MatrixRTCSession.callMembershipsForRoom(this.room);
|
this.memberships = MatrixRTCSession.sessionMembershipsForRoom(this.room, this.sessionDescription);
|
||||||
|
|
||||||
this._callId = this._callId ?? this.memberships[0]?.callId;
|
this._callId = this._callId ?? this.memberships[0]?.sessionDescription.id;
|
||||||
|
|
||||||
const changed =
|
const changed =
|
||||||
oldMemberships.length != this.memberships.length ||
|
oldMemberships.length != this.memberships.length ||
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
|||||||
import { type Room } from "../models/room.ts";
|
import { type Room } from "../models/room.ts";
|
||||||
import { type RoomState, RoomStateEvent } from "../models/room-state.ts";
|
import { type RoomState, RoomStateEvent } from "../models/room-state.ts";
|
||||||
import { type MatrixEvent } from "../models/event.ts";
|
import { type MatrixEvent } from "../models/event.ts";
|
||||||
import { MatrixRTCSession } from "./MatrixRTCSession.ts";
|
import { MatrixRTCSession, type SessionDescription } from "./MatrixRTCSession.ts";
|
||||||
import { EventType } from "../@types/event.ts";
|
import { EventType } from "../@types/event.ts";
|
||||||
|
|
||||||
export enum MatrixRTCSessionManagerEvents {
|
export enum MatrixRTCSessionManagerEvents {
|
||||||
@@ -37,6 +37,9 @@ type EventHandlerMap = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds all active MatrixRTC session objects and creates new ones as events arrive.
|
* Holds all active MatrixRTC session objects and creates new ones as events arrive.
|
||||||
|
* One `MatrixRTCSessionManager` is required for each MatrixRTC sessionDescription (application, session id) that the client wants to support.
|
||||||
|
* If no application type is specified in the constructor, the default is "m.call".
|
||||||
|
*
|
||||||
* This interface is UNSTABLE and may change without warning.
|
* This interface is UNSTABLE and may change without warning.
|
||||||
*/
|
*/
|
||||||
export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionManagerEvents, EventHandlerMap> {
|
export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionManagerEvents, EventHandlerMap> {
|
||||||
@@ -53,6 +56,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
|||||||
public constructor(
|
public constructor(
|
||||||
rootLogger: Logger,
|
rootLogger: Logger,
|
||||||
private client: MatrixClient,
|
private client: MatrixClient,
|
||||||
|
private readonly sessionDescription: SessionDescription = { id: "", application: "m.call" }, // Default to the Matrix Call application
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = rootLogger.getChild("[MatrixRTCSessionManager]");
|
this.logger = rootLogger.getChild("[MatrixRTCSessionManager]");
|
||||||
@@ -62,7 +66,7 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
|||||||
// 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.roomSessionForRoom(this.client, room);
|
const session = 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);
|
||||||
}
|
}
|
||||||
@@ -96,7 +100,10 @@ export class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionM
|
|||||||
*/
|
*/
|
||||||
public getRoomSession(room: Room): MatrixRTCSession {
|
public getRoomSession(room: Room): MatrixRTCSession {
|
||||||
if (!this.roomSessions.has(room.roomId)) {
|
if (!this.roomSessions.has(room.roomId)) {
|
||||||
this.roomSessions.set(room.roomId, MatrixRTCSession.roomSessionForRoom(this.client, room));
|
this.roomSessions.set(
|
||||||
|
room.roomId,
|
||||||
|
MatrixRTCSession.sessionForRoom(this.client, room, this.sessionDescription),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.roomSessions.get(room.roomId)!;
|
return this.roomSessions.get(room.roomId)!;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipDat
|
|||||||
import { type Focus } from "./focus.ts";
|
import { type Focus } from "./focus.ts";
|
||||||
import { isMyMembership, Status } from "./types.ts";
|
import { isMyMembership, Status } from "./types.ts";
|
||||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
||||||
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
import { type SessionDescription, type MembershipConfig } from "./MatrixRTCSession.ts";
|
||||||
import { ActionScheduler, type ActionUpdate } from "./MembershipManagerActionScheduler.ts";
|
import { ActionScheduler, type ActionUpdate } from "./MembershipManagerActionScheduler.ts";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||||
import {
|
import {
|
||||||
@@ -101,7 +101,7 @@ export enum MembershipActionType {
|
|||||||
// -> MembershipActionType.Update if the timeout has passed so the next update is required.
|
// -> MembershipActionType.Update if the timeout has passed so the next update is required.
|
||||||
|
|
||||||
SendScheduledDelayedLeaveEvent = "SendScheduledDelayedLeaveEvent",
|
SendScheduledDelayedLeaveEvent = "SendScheduledDelayedLeaveEvent",
|
||||||
// -> MembershipActionType.SendLeaveEvent on failiour (not found) we need to send the leave manually and cannot use the scheduled delayed event
|
// -> MembershipActionType.SendLeaveEvent on failure (not found) we need to send the leave manually and cannot use the scheduled delayed event
|
||||||
// -> DelayedLeaveActionType.SendScheduledDelayedLeaveEvent on error we try again.
|
// -> DelayedLeaveActionType.SendScheduledDelayedLeaveEvent on error we try again.
|
||||||
|
|
||||||
SendLeaveEvent = "SendLeaveEvent",
|
SendLeaveEvent = "SendLeaveEvent",
|
||||||
@@ -294,6 +294,7 @@ export class MembershipManager
|
|||||||
| "_unstable_updateDelayedEvent"
|
| "_unstable_updateDelayedEvent"
|
||||||
>,
|
>,
|
||||||
private getOldestMembership: () => CallMembership | undefined,
|
private getOldestMembership: () => CallMembership | undefined,
|
||||||
|
public readonly sessionDescription: SessionDescription,
|
||||||
parentLogger?: Logger,
|
parentLogger?: Logger,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -700,7 +701,7 @@ export class MembershipManager
|
|||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
private makeMembershipStateKey(localUserId: string, localDeviceId: string): string {
|
private makeMembershipStateKey(localUserId: string, localDeviceId: string): string {
|
||||||
const stateKey = `${localUserId}_${localDeviceId}`;
|
const stateKey = `${localUserId}_${localDeviceId}_${this.sessionDescription.application}${this.sessionDescription.id}`;
|
||||||
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
||||||
return stateKey;
|
return stateKey;
|
||||||
} else {
|
} else {
|
||||||
@@ -713,9 +714,10 @@ export class MembershipManager
|
|||||||
*/
|
*/
|
||||||
private makeMyMembership(expires: number): SessionMembershipData {
|
private makeMyMembership(expires: number): SessionMembershipData {
|
||||||
return {
|
return {
|
||||||
call_id: "",
|
// TODO: use the new format for m.rtc.member events where call_id becomes session.id
|
||||||
|
application: this.sessionDescription.application,
|
||||||
|
call_id: this.sessionDescription.id,
|
||||||
scope: "m.room",
|
scope: "m.room",
|
||||||
application: "m.call",
|
|
||||||
device_id: this.deviceId,
|
device_id: this.deviceId,
|
||||||
expires,
|
expires,
|
||||||
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
|
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
|
||||||
|
|||||||
Reference in New Issue
Block a user