Skip to content

Commit 8a21ff6

Browse files
committed
Add the parent event to the CallMembership.
Signed-off-by: Timo K <[email protected]>
1 parent 4643844 commit 8a21ff6

File tree

3 files changed

+195
-175
lines changed

3 files changed

+195
-175
lines changed

spec/unit/matrixrtc/CallMembership.spec.ts

Lines changed: 83 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import {
2323
} from "../../../src/matrixrtc/CallMembership";
2424
import { membershipTemplate } from "./mocks";
2525

26-
function makeMockEvent(originTs = 0): MatrixEvent {
26+
function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
2727
return {
2828
getTs: jest.fn().mockReturnValue(originTs),
2929
getSender: jest.fn().mockReturnValue("@alice:example.org"),
3030
getId: jest.fn().mockReturnValue("$eventid"),
31+
getContent: jest.fn().mockReturnValue(content),
3132
} as unknown as MatrixEvent;
3233
}
3334

@@ -53,63 +54,64 @@ describe("CallMembership", () => {
5354

5455
it("rejects membership with no device_id", () => {
5556
expect(() => {
56-
new CallMembership(makeMockEvent(), Object.assign({}, membershipTemplate, { device_id: undefined }));
57+
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { device_id: undefined })));
5758
}).toThrow();
5859
});
5960

6061
it("rejects membership with no call_id", () => {
6162
expect(() => {
62-
new CallMembership(makeMockEvent(), Object.assign({}, membershipTemplate, { call_id: undefined }));
63+
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { call_id: undefined })));
6364
}).toThrow();
6465
});
6566

6667
it("allow membership with no scope", () => {
6768
expect(() => {
68-
new CallMembership(makeMockEvent(), Object.assign({}, membershipTemplate, { scope: undefined }));
69+
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { scope: undefined })));
6970
}).not.toThrow();
7071
});
7172

7273
it("uses event timestamp if no created_ts", () => {
73-
const membership = new CallMembership(makeMockEvent(12345), membershipTemplate);
74+
const membership = new CallMembership(makeMockEvent(12345, membershipTemplate));
7475
expect(membership.createdTs()).toEqual(12345);
7576
});
7677

7778
it("uses created_ts if present", () => {
7879
const membership = new CallMembership(
79-
makeMockEvent(12345),
80-
Object.assign({}, membershipTemplate, { created_ts: 67890 }),
80+
makeMockEvent(12345, Object.assign({}, membershipTemplate, { created_ts: 67890 })),
8181
);
8282
expect(membership.createdTs()).toEqual(67890);
8383
});
8484

8585
it("considers memberships unexpired if local age low enough", () => {
86-
const fakeEvent = makeMockEvent(1000);
86+
const fakeEvent = makeMockEvent(1000, membershipTemplate);
8787
fakeEvent.getTs = jest.fn().mockReturnValue(Date.now() - (DEFAULT_EXPIRE_DURATION - 1));
88-
expect(new CallMembership(fakeEvent, membershipTemplate).isExpired()).toEqual(false);
88+
expect(new CallMembership(fakeEvent).isExpired()).toEqual(false);
8989
});
9090

9191
it("considers memberships expired if local age large enough", () => {
92-
const fakeEvent = makeMockEvent(1000);
92+
const fakeEvent = makeMockEvent(1000, membershipTemplate);
9393
fakeEvent.getTs = jest.fn().mockReturnValue(Date.now() - (DEFAULT_EXPIRE_DURATION + 1));
94-
expect(new CallMembership(fakeEvent, membershipTemplate).isExpired()).toEqual(true);
94+
expect(new CallMembership(fakeEvent).isExpired()).toEqual(true);
9595
});
9696

9797
it("returns preferred foci", () => {
98-
const fakeEvent = makeMockEvent();
9998
const mockFocus = { type: "this_is_a_mock_focus" };
100-
const membership = new CallMembership(fakeEvent, { ...membershipTemplate, foci_preferred: [mockFocus] });
99+
const fakeEvent = makeMockEvent(0, { ...membershipTemplate, foci_preferred: [mockFocus] });
100+
const membership = new CallMembership(fakeEvent);
101101
expect(membership.transports).toEqual([mockFocus]);
102102
});
103103

104104
describe("getTransport", () => {
105105
const mockFocus = { type: "this_is_a_mock_focus" };
106-
const oldestMembership = new CallMembership(makeMockEvent(), membershipTemplate);
106+
const oldestMembership = new CallMembership(makeMockEvent(0, membershipTemplate));
107107
it("gets the correct active transport with oldest_membership", () => {
108-
const membership = new CallMembership(makeMockEvent(), {
109-
...membershipTemplate,
110-
foci_preferred: [mockFocus],
111-
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
112-
});
108+
const membership = new CallMembership(
109+
makeMockEvent(0, {
110+
...membershipTemplate,
111+
foci_preferred: [mockFocus],
112+
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
113+
}),
114+
);
113115

114116
// if we are the oldest member we use our focus.
115117
expect(membership.getTransport(membership)).toStrictEqual(mockFocus);
@@ -119,11 +121,13 @@ describe("CallMembership", () => {
119121
});
120122

121123
it("gets the correct active transport with multi_sfu", () => {
122-
const membership = new CallMembership(makeMockEvent(), {
123-
...membershipTemplate,
124-
foci_preferred: [mockFocus],
125-
focus_active: { type: "livekit", focus_selection: "multi_sfu" },
126-
});
124+
const membership = new CallMembership(
125+
makeMockEvent(0, {
126+
...membershipTemplate,
127+
foci_preferred: [mockFocus],
128+
focus_active: { type: "livekit", focus_selection: "multi_sfu" },
129+
}),
130+
);
127131

128132
// if we are the oldest member we use our focus.
129133
expect(membership.getTransport(membership)).toStrictEqual(mockFocus);
@@ -132,18 +136,20 @@ describe("CallMembership", () => {
132136
expect(membership.getTransport(oldestMembership)).toBe(mockFocus);
133137
});
134138
it("does not provide focus if the selection method is unknown", () => {
135-
const membership = new CallMembership(makeMockEvent(), {
136-
...membershipTemplate,
137-
foci_preferred: [mockFocus],
138-
focus_active: { type: "livekit", focus_selection: "unknown" },
139-
});
139+
const membership = new CallMembership(
140+
makeMockEvent(0, {
141+
...membershipTemplate,
142+
foci_preferred: [mockFocus],
143+
focus_active: { type: "livekit", focus_selection: "unknown" },
144+
}),
145+
);
140146

141147
// if we are the oldest member we use our focus.
142148
expect(membership.getTransport(membership)).toBeUndefined();
143149
});
144150
});
145151
describe("correct values from computed fields", () => {
146-
const membership = new CallMembership(makeMockEvent(), membershipTemplate);
152+
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
147153
it("returns correct sender", () => {
148154
expect(membership.sender).toBe("@alice:example.org");
149155
});
@@ -192,58 +198,68 @@ describe("CallMembership", () => {
192198

193199
it("rejects membership with no slot_id", () => {
194200
expect(() => {
195-
new CallMembership(makeMockEvent(), { ...membershipTemplate, slot_id: undefined });
201+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));
196202
}).toThrow();
197203
});
198204

199205
it("rejects membership with no application", () => {
200206
expect(() => {
201-
new CallMembership(makeMockEvent(), { ...membershipTemplate, application: undefined });
207+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, application: undefined }));
202208
}).toThrow();
203209
});
204210

205211
it("rejects membership with incorrect application", () => {
206212
expect(() => {
207-
new CallMembership(makeMockEvent(), {
208-
...membershipTemplate,
209-
application: { wrong_type_key: "unknown" },
210-
});
213+
new CallMembership(
214+
makeMockEvent(0, {
215+
...membershipTemplate,
216+
application: { wrong_type_key: "unknown" },
217+
}),
218+
);
211219
}).toThrow();
212220
});
213221

214222
it("rejects membership with no member", () => {
215223
expect(() => {
216-
new CallMembership(makeMockEvent(), { ...membershipTemplate, member: undefined });
224+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, member: undefined }));
217225
}).toThrow();
218226
});
219227

220228
it("rejects membership with incorrect member", () => {
221229
expect(() => {
222-
new CallMembership(makeMockEvent(), { ...membershipTemplate, member: { i: "test" } });
230+
new CallMembership(makeMockEvent(0, { ...membershipTemplate, member: { i: "test" } }));
223231
}).toThrow();
224232
expect(() => {
225-
new CallMembership(makeMockEvent(), {
226-
...membershipTemplate,
227-
member: { id: "test", device_id: "test", user_id_wrong: "test" },
228-
});
233+
new CallMembership(
234+
makeMockEvent(0, {
235+
...membershipTemplate,
236+
member: { id: "test", device_id: "test", user_id_wrong: "test" },
237+
}),
238+
);
229239
}).toThrow();
230240
expect(() => {
231-
new CallMembership(makeMockEvent(), {
232-
...membershipTemplate,
233-
member: { id: "test", device_id_wrong: "test", user_id_wrong: "test" },
234-
});
241+
new CallMembership(
242+
makeMockEvent(0, {
243+
...membershipTemplate,
244+
member: { id: "test", device_id_wrong: "test", user_id_wrong: "test" },
245+
}),
246+
);
235247
}).toThrow();
236248
expect(() => {
237-
new CallMembership(makeMockEvent(), {
238-
...membershipTemplate,
239-
member: { id: "test", device_id: "test", user_id: "@@test" },
240-
});
249+
new CallMembership(
250+
makeMockEvent(0, {
251+
...membershipTemplate,
252+
member: { id: "test", device_id: "test", user_id: "@@test" },
253+
}),
254+
);
241255
}).toThrow();
242256
expect(() => {
243-
new CallMembership(makeMockEvent(), {
244-
...membershipTemplate,
245-
member: { id: "test", device_id: "test", user_id: "@test:user.id" },
246-
});
257+
new CallMembership(
258+
makeMockEvent(0, {
259+
...membershipTemplate,
260+
member: { id: "test", device_id: "test", user_id: "@test:user.id" },
261+
}),
262+
);
247263
}).not.toThrow();
248264
});
249265

@@ -257,11 +273,13 @@ describe("CallMembership", () => {
257273

258274
describe("getTransport", () => {
259275
it("gets the correct active transport with oldest_membership", () => {
260-
const oldestMembership = new CallMembership(makeMockEvent(), {
261-
...membershipTemplate,
262-
rtc_transports: [{ type: "oldest_transport" }],
263-
});
264-
const membership = new CallMembership(makeMockEvent(), membershipTemplate);
276+
const oldestMembership = new CallMembership(
277+
makeMockEvent(0, {
278+
...membershipTemplate,
279+
rtc_transports: [{ type: "oldest_transport" }],
280+
}),
281+
);
282+
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
265283

266284
// if we are the oldest member we use our focus.
267285
expect(membership.getTransport(membership)).toStrictEqual({ type: "livekit" });
@@ -271,7 +289,7 @@ describe("CallMembership", () => {
271289
});
272290
});
273291
describe("correct values from computed fields", () => {
274-
const membership = new CallMembership(makeMockEvent(), membershipTemplate);
292+
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
275293
it("returns correct sender", () => {
276294
expect(membership.sender).toBe("@alice:example.org");
277295
});
@@ -304,9 +322,9 @@ describe("CallMembership", () => {
304322
it("returns correct membershipID", () => {
305323
expect(membership.membershipID).toBe("xyzHASHxyz");
306324
});
307-
it("returns correct unused fields", () => {
308-
expect(membership.getAbsoluteExpiry()).toBe(undefined);
309-
expect(membership.getMsUntilExpiry()).toBe(undefined);
325+
it("returns correct expiration fields", () => {
326+
expect(membership.getAbsoluteExpiry()).toBe(DEFAULT_EXPIRE_DURATION);
327+
expect(membership.getMsUntilExpiry()).toBe(DEFAULT_EXPIRE_DURATION - Date.now());
310328
expect(membership.isExpired()).toBe(false);
311329
});
312330
});
@@ -318,8 +336,8 @@ describe("CallMembership", () => {
318336

319337
beforeEach(() => {
320338
// server origin timestamp for this event is 1000
321-
fakeEvent = makeMockEvent(1000);
322-
membership = new CallMembership(fakeEvent!, membershipTemplate);
339+
fakeEvent = makeMockEvent(1000, membershipTemplate);
340+
membership = new CallMembership(fakeEvent!);
323341

324342
jest.useFakeTimers();
325343
});

src/matrixrtc/CallMembership.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,17 @@ export class CallMembership {
223223
* To access checked eventId and sender from the matrixEvent.
224224
* Class construction will fail if these values cannot get obtained. */
225225
private matrixEventData: { eventId: string; sender: string };
226+
/**
227+
* Constructs a CallMembership from a Matrix event.
228+
* @param matrixEvent The Matrix event that this membership is based on
229+
* @param relatedEvent The fetched event linked via the `event_id` from the `m.relates_to` field if present.
230+
* @throws if the data does not match any known membership format.
231+
*/
226232
public constructor(
227-
/** The Matrix event that this membership is based on */
228233
private matrixEvent: MatrixEvent,
229-
data: any,
234+
private relatedEvent?: MatrixEvent,
230235
) {
236+
const data = matrixEvent.getContent() as any;
231237
const sessionErrors: string[] = [];
232238
const rtcErrors: string[] = [];
233239
if (checkSessionsMembershipData(data, sessionErrors)) {
@@ -354,8 +360,7 @@ export class CallMembership {
354360
const { kind, data } = this.membershipData;
355361
switch (kind) {
356362
case "rtc":
357-
// TODO we need to read the referenced (relation) event if available to get the real created_ts
358-
return this.matrixEvent.getTs();
363+
return this.relatedEvent?.getTs() ?? this.matrixEvent.getTs();
359364
case "session":
360365
default:
361366
return data.created_ts ?? this.matrixEvent.getTs();
@@ -370,7 +375,7 @@ export class CallMembership {
370375
const { kind, data } = this.membershipData;
371376
switch (kind) {
372377
case "rtc":
373-
return undefined;
378+
return this.createdTs() + DEFAULT_EXPIRE_DURATION;
374379
case "session":
375380
default:
376381
// TODO: calculate this from the MatrixRTCSession join configuration directly
@@ -382,17 +387,10 @@ export class CallMembership {
382387
* @returns The number of milliseconds until the membership expires or undefined if applicable
383388
*/
384389
public getMsUntilExpiry(): number | undefined {
385-
const { kind } = this.membershipData;
386-
switch (kind) {
387-
case "rtc":
388-
return undefined;
389-
case "session":
390-
default:
391-
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
392-
// We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.
393-
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
394-
return this.getAbsoluteExpiry()! - Date.now();
395-
}
390+
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
391+
// We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.
392+
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
393+
return this.getAbsoluteExpiry()! - Date.now();
396394
}
397395

398396
/**

0 commit comments

Comments
 (0)