Skip to content

Commit b59603d

Browse files
toger5Half-Shotrobintown
authored
[MatrixRTC] Sticky Events support (MSC4354) (#5017)
* Implement Sticky Events MSC * Renames * lint * some review work * Update for support for 4-ples * fix lint * pull through method * Fix the mistake * More tests to appease SC * Cleaner code * Review cleanup * Refactors based on review. * lint * Add sticky event support to the js-sdk Signed-off-by: Timo K <[email protected]> * use sticky events for matrixRTC Signed-off-by: Timo K <[email protected]> * make sticky events a non breaking change (default to state events. use joinConfig to use sticky events) Signed-off-by: Timo K <[email protected]> * review - fix types (`msc4354_sticky:number` -> `msc4354_sticky?: { duration_ms: number };`) - add `MultiKeyMap` Signed-off-by: Timo K <[email protected]> * Refactor all of this away to it's own accumulator and class. * Add tests * tidyup * more test cleaning * lint * Updates and tests * fix filter * fix filter with lint * Add timer tests * Add tests for MatrixRTCSessionManager * Listen for sticky events on MatrixRTCSessionManager * fix logic on filtering out state events * lint * more lint * tweaks * Add logging in areas * more debugging * much more logging * remove more logging * Finish supporting new MSC * a line * reconnect the bits to RTC * fixup more bits * fixup testrs * Ensure consistent order * lint * fix log line * remove extra bit of code * revert changes to room-sticky-events.ts * fixup mocks again * lint * fix * cleanup * fix paths * tweak test * fixup * Add more tests for coverage * Small improvements Signed-off-by: Timo K <[email protected]> * review Signed-off-by: Timo K <[email protected]> * Document better * fix sticky event type Signed-off-by: Timo K <[email protected]> * fix demo Signed-off-by: Timo K <[email protected]> * fix tests Signed-off-by: Timo K <[email protected]> * Update src/matrixrtc/CallMembership.ts Co-authored-by: Robin <[email protected]> * cleanup * lint * fix ci Signed-off-by: Timo K <[email protected]> --------- Signed-off-by: Timo K <[email protected]> Co-authored-by: Half-Shot <[email protected]> Co-authored-by: Robin <[email protected]>
1 parent b0cbe22 commit b59603d

File tree

10 files changed

+857
-419
lines changed

10 files changed

+857
-419
lines changed

spec/unit/matrixrtc/MatrixRTCSession.spec.ts

Lines changed: 334 additions & 111 deletions
Large diffs are not rendered by default.

spec/unit/matrixrtc/MatrixRTCSessionManager.spec.ts

Lines changed: 128 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -14,136 +14,145 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { ClientEvent, EventTimeline, MatrixClient } from "../../../src";
17+
import { ClientEvent, EventTimeline, MatrixClient, type Room } from "../../../src";
1818
import { RoomStateEvent } from "../../../src/models/room-state";
1919
import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
20-
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks";
20+
import { makeMockRoom, type MembershipData, membershipTemplate, mockRoomState, mockRTCEvent } from "./mocks";
2121
import { logger } from "../../../src/logger";
2222

23-
describe("MatrixRTCSessionManager", () => {
24-
let client: MatrixClient;
25-
26-
beforeEach(() => {
27-
client = new MatrixClient({ baseUrl: "base_url" });
28-
client.matrixRTC.start();
29-
});
30-
31-
afterEach(() => {
32-
client.stopClient();
33-
client.matrixRTC.stop();
34-
});
35-
36-
it("Fires event when session starts", () => {
37-
const onStarted = jest.fn();
38-
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
39-
40-
try {
41-
const room1 = makeMockRoom([membershipTemplate]);
42-
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
43-
44-
client.emit(ClientEvent.Room, room1);
45-
expect(onStarted).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
46-
} finally {
47-
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
23+
describe.each([{ eventKind: "sticky" }, { eventKind: "memberState" }])(
24+
"MatrixRTCSessionManager ($eventKind)",
25+
({ eventKind }) => {
26+
let client: MatrixClient;
27+
28+
function sendLeaveMembership(room: Room, membershipData: MembershipData[]): void {
29+
if (eventKind === "memberState") {
30+
mockRoomState(room, [{ user_id: membershipTemplate.user_id }]);
31+
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
32+
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
33+
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
34+
} else {
35+
membershipData.splice(0, 1, { user_id: membershipTemplate.user_id });
36+
client.emit(ClientEvent.Event, mockRTCEvent(membershipData[0], room.roomId, 10000));
37+
}
4838
}
49-
});
50-
51-
it("Doesn't fire event if unrelated sessions starts", () => {
52-
const onStarted = jest.fn();
53-
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
5439

55-
try {
56-
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
40+
beforeEach(() => {
41+
client = new MatrixClient({ baseUrl: "base_url" });
42+
client.matrixRTC.start();
43+
});
44+
45+
afterEach(() => {
46+
client.stopClient();
47+
client.matrixRTC.stop();
48+
});
49+
50+
it("Fires event when session starts", () => {
51+
const onStarted = jest.fn();
52+
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
53+
54+
try {
55+
const room1 = makeMockRoom([membershipTemplate], eventKind === "sticky");
56+
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
57+
58+
client.emit(ClientEvent.Room, room1);
59+
expect(onStarted).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
60+
} finally {
61+
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
62+
}
63+
});
64+
65+
it("Doesn't fire event if unrelated sessions starts", () => {
66+
const onStarted = jest.fn();
67+
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
68+
69+
try {
70+
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }], eventKind === "sticky");
71+
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
72+
73+
client.emit(ClientEvent.Room, room1);
74+
expect(onStarted).not.toHaveBeenCalled();
75+
} finally {
76+
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
77+
}
78+
});
79+
80+
it("Fires event when session ends", () => {
81+
const onEnded = jest.fn();
82+
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
83+
const membershipData: MembershipData[] = [membershipTemplate];
84+
const room1 = makeMockRoom(membershipData, eventKind === "sticky");
5785
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
58-
86+
jest.spyOn(client, "getRoom").mockReturnValue(room1);
5987
client.emit(ClientEvent.Room, room1);
60-
expect(onStarted).not.toHaveBeenCalled();
61-
} finally {
62-
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
63-
}
64-
});
65-
66-
it("Fires event when session ends", () => {
67-
const onEnded = jest.fn();
68-
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
69-
const room1 = makeMockRoom([membershipTemplate]);
70-
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
71-
jest.spyOn(client, "getRoom").mockReturnValue(room1);
72-
73-
client.emit(ClientEvent.Room, room1);
74-
75-
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
76-
77-
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
78-
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
79-
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
8088

81-
expect(onEnded).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
82-
});
83-
84-
it("Fires correctly with for with custom sessionDescription", () => {
85-
const onStarted = jest.fn();
86-
const onEnded = jest.fn();
87-
// create a session manager with a custom session description
88-
const sessionManager = new MatrixRTCSessionManager(logger, client, { id: "test", application: "m.notCall" });
89-
90-
// manually start the session manager (its not the default one started by the client)
91-
sessionManager.start();
92-
sessionManager.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
93-
sessionManager.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
94-
95-
try {
96-
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
89+
sendLeaveMembership(room1, membershipData);
90+
91+
expect(onEnded).toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
92+
});
93+
94+
it("Fires correctly with custom sessionDescription", () => {
95+
const onStarted = jest.fn();
96+
const onEnded = jest.fn();
97+
// create a session manager with a custom session description
98+
const sessionManager = new MatrixRTCSessionManager(logger, client, {
99+
id: "test",
100+
application: "m.notCall",
101+
});
102+
103+
// manually start the session manager (its not the default one started by the client)
104+
sessionManager.start();
105+
sessionManager.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
106+
sessionManager.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
107+
108+
try {
109+
// Create a session for applicaation m.other, we ignore this session ecause it lacks a call_id
110+
const room1MembershipData: MembershipData[] = [{ ...membershipTemplate, application: "m.other" }];
111+
const room1 = makeMockRoom(room1MembershipData, eventKind === "sticky");
112+
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
113+
client.emit(ClientEvent.Room, room1);
114+
expect(onStarted).not.toHaveBeenCalled();
115+
onStarted.mockClear();
116+
117+
// Create a session for applicaation m.notCall. We expect this call to be tracked because it has a call_id
118+
const room2MembershipData: MembershipData[] = [
119+
{ ...membershipTemplate, application: "m.notCall", call_id: "test" },
120+
];
121+
const room2 = makeMockRoom(room2MembershipData, eventKind === "sticky");
122+
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
123+
client.emit(ClientEvent.Room, room2);
124+
expect(onStarted).toHaveBeenCalled();
125+
onStarted.mockClear();
126+
127+
// Stop room1's RTC session. Tracked.
128+
jest.spyOn(client, "getRoom").mockReturnValue(room2);
129+
sendLeaveMembership(room2, room2MembershipData);
130+
expect(onEnded).toHaveBeenCalled();
131+
onEnded.mockClear();
132+
133+
// Stop room1's RTC session. Not tracked.
134+
jest.spyOn(client, "getRoom").mockReturnValue(room1);
135+
sendLeaveMembership(room1, room1MembershipData);
136+
expect(onEnded).not.toHaveBeenCalled();
137+
} finally {
138+
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
139+
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
140+
}
141+
});
142+
143+
it("Doesn't fire event if unrelated sessions ends", () => {
144+
const onEnded = jest.fn();
145+
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
146+
const membership: MembershipData[] = [{ ...membershipTemplate, application: "m.other_app" }];
147+
const room1 = makeMockRoom(membership, eventKind === "sticky");
97148
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
98-
99-
client.emit(ClientEvent.Room, room1);
100-
expect(onStarted).not.toHaveBeenCalled();
101-
onStarted.mockClear();
102-
103-
const room2 = makeMockRoom([{ ...membershipTemplate, application: "m.notCall", call_id: "test" }]);
104-
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
105-
106-
client.emit(ClientEvent.Room, room2);
107-
expect(onStarted).toHaveBeenCalled();
108-
onStarted.mockClear();
109-
110-
mockRoomState(room2, [{ user_id: membershipTemplate.user_id }]);
111-
jest.spyOn(client, "getRoom").mockReturnValue(room2);
112-
113-
const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
114-
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
115-
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
116-
expect(onEnded).toHaveBeenCalled();
117-
onEnded.mockClear();
118-
119-
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
120149
jest.spyOn(client, "getRoom").mockReturnValue(room1);
121150

122-
const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
123-
const membEventOther = roomStateOther.getStateEvents("org.matrix.msc3401.call.member")[0];
124-
client.emit(RoomStateEvent.Events, membEventOther, roomStateOther, null);
125-
expect(onEnded).not.toHaveBeenCalled();
126-
} finally {
127-
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
128-
client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
129-
}
130-
});
131-
132-
it("Doesn't fire event if unrelated sessions ends", () => {
133-
const onEnded = jest.fn();
134-
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
135-
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other_app" }]);
136-
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
137-
jest.spyOn(client, "getRoom").mockReturnValue(room1);
138-
139-
client.emit(ClientEvent.Room, room1);
140-
141-
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
151+
client.emit(ClientEvent.Room, room1);
142152

143-
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
144-
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
145-
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
153+
sendLeaveMembership(room1, membership);
146154

147-
expect(onEnded).not.toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
148-
});
149-
});
155+
expect(onEnded).not.toHaveBeenCalledWith(room1.roomId, client.matrixRTC.getActiveRoomSession(room1));
156+
});
157+
},
158+
);

0 commit comments

Comments
 (0)