Skip to content

Commit 87a9e31

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

File tree

2 files changed

+115
-102
lines changed

2 files changed

+115
-102
lines changed

src/matrixrtc/CallMembership.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,17 @@ export class CallMembership {
218218
private membershipData: MembershipData;
219219

220220
private parentEventData: { eventId: string; sender: string };
221+
/**
222+
*
223+
* @param parentEvent
224+
* @param data
225+
* @param relatedEvent
226+
* @throws if the data does not match any known membership format.
227+
*/
221228
public constructor(
222229
private parentEvent: MatrixEvent,
223230
data: any,
231+
private relatedEvent?: MatrixEvent,
224232
) {
225233
const sessionErrors: string[] = [];
226234
const rtcErrors: string[] = [];
@@ -341,8 +349,7 @@ export class CallMembership {
341349
const { kind, data } = this.membershipData;
342350
switch (kind) {
343351
case "rtc":
344-
// TODO we need to read the referenced (relation) event if available to get the real created_ts
345-
return this.parentEvent.getTs();
352+
return this.relatedEvent?.getTs() ?? this.parentEvent.getTs();
346353
default: // "session":
347354
return data.created_ts ?? this.parentEvent.getTs();
348355
}
@@ -356,7 +363,8 @@ export class CallMembership {
356363
const { kind, data } = this.membershipData;
357364
switch (kind) {
358365
case "rtc":
359-
return undefined;
366+
// TODO use the sticky property of the rtc membership to determine expiry?
367+
return this.createdTs() + DEFAULT_EXPIRE_DURATION;
360368
default: // "session":
361369
// TODO: calculate this from the MatrixRTCSession join configuration directly
362370
return this.createdTs() + (data.expires ?? DEFAULT_EXPIRE_DURATION);
@@ -367,16 +375,10 @@ export class CallMembership {
367375
* @returns The number of milliseconds until the membership expires or undefined if applicable
368376
*/
369377
public getMsUntilExpiry(): number | undefined {
370-
const { kind } = this.membershipData;
371-
switch (kind) {
372-
case "rtc":
373-
return undefined;
374-
default: // "session":
375-
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
376-
// We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.
377-
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
378-
return this.getAbsoluteExpiry()! - Date.now();
379-
}
378+
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
379+
// We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.
380+
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
381+
return this.getAbsoluteExpiry()! - Date.now();
380382
}
381383

382384
/**

src/matrixrtc/MatrixRTCSession.ts

Lines changed: 100 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
} from "./RoomAndToDeviceKeyTransport.ts";
5151
import { TypedReEmitter } from "../ReEmitter.ts";
5252
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
53+
import { MatrixEvent } from "src/matrix.ts";
5354

5455
/**
5556
* Events emitted by MatrixRTCSession
@@ -308,10 +309,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
308309
*
309310
* @deprecated Use `MatrixRTCSession.sessionMembershipsForSlot` instead.
310311
*/
311-
public static callMembershipsForRoom(
312-
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
313-
): CallMembership[] {
314-
return MatrixRTCSession.sessionMembershipsForSlot(room, {
312+
public static async callMembershipsForRoom(
313+
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
314+
): Promise<CallMembership[]> {
315+
return await MatrixRTCSession.sessionMembershipsForSlot(room, {
315316
id: "",
316317
application: "m.call",
317318
});
@@ -320,72 +321,67 @@ export class MatrixRTCSession extends TypedEventEmitter<
320321
/**
321322
* @deprecated use `MatrixRTCSession.slotMembershipsForRoom` instead.
322323
*/
323-
public static sessionMembershipsForRoom(
324-
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
324+
public static async sessionMembershipsForRoom(
325+
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
325326
sessionDescription: SlotDescription,
326-
): CallMembership[] {
327-
return this.sessionMembershipsForSlot(room, sessionDescription);
327+
): Promise<CallMembership[]> {
328+
return await this.sessionMembershipsForSlot(room, sessionDescription);
328329
}
329330

330331
/**
331332
* Returns all the call memberships for a room that match the provided `sessionDescription`,
332333
* oldest first.
333334
*/
334-
public static sessionMembershipsForSlot(
335-
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState">,
335+
public static async sessionMembershipsForSlot(
336+
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
336337
slotDescription: SlotDescription,
337-
): CallMembership[] {
338+
existingMemberships?: CallMembership[],
339+
): Promise<CallMembership[]> {
338340
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId}]`);
339341
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
340342
if (!roomState) {
341343
logger.warn("Couldn't get state for room " + room.roomId);
342344
throw new Error("Could't get state for room " + room.roomId);
343345
}
344346
const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
345-
347+
// TODO optimise this to reuse existing memberships instead of always creating new ones.
346348
const callMemberships: CallMembership[] = [];
349+
347350
for (const memberEvent of callMemberEvents) {
348-
const content = memberEvent.getContent();
349-
const eventKeysCount = Object.keys(content).length;
350-
// Dont even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
351-
if (eventKeysCount === 0) continue;
352-
353-
const membershipContents: any[] = [];
354-
355-
// We first decide if its a MSC4143 event (per device state key)
356-
if (eventKeysCount > 1 && "focus_active" in content) {
357-
// We have a MSC4143 event membership event
358-
membershipContents.push(content);
359-
} else if (eventKeysCount === 1 && "memberships" in content) {
360-
logger.warn(`Legacy event found. Those are ignored, they do not contribute to the MatrixRTC session`);
361-
}
351+
let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId());
352+
if (!membership) {
353+
const content = memberEvent.getContent();
362354

363-
if (membershipContents.length === 0) continue;
355+
const relatedEventId = memberEvent.relationEventId;
356+
const relatedEvent = relatedEventId
357+
? room.findEventById(relatedEventId)
358+
: new MatrixEvent(await room.client.fetchRoomEvent(room.roomId, relatedEventId!));
364359

365-
for (const membershipData of membershipContents) {
366360
try {
367-
const membership = new CallMembership(memberEvent, membershipData);
368-
369-
if (!deepCompare(membership.slotDescription, slotDescription)) {
370-
logger.info(
371-
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
372-
);
373-
continue;
374-
}
375-
376-
if (membership.isExpired()) {
377-
logger.info(`Ignoring expired device membership ${membership.sender}/${membership.deviceId}`);
378-
continue;
379-
}
380-
if (!room.hasMembershipState(membership.sender ?? "", KnownMembership.Join)) {
381-
logger.info(`Ignoring membership of user ${membership.sender} who is not in the room.`);
382-
continue;
383-
}
384-
callMemberships.push(membership);
361+
membership = new CallMembership(memberEvent, content, relatedEvent);
385362
} catch (e) {
386363
logger.warn("Couldn't construct call membership: ", e);
364+
continue;
365+
}
366+
// static check for newly created memberships
367+
if (!deepCompare(membership.slotDescription, slotDescription)) {
368+
logger.info(
369+
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
370+
);
371+
continue;
387372
}
388373
}
374+
375+
// Dynamic checks for all (including existing) memberships
376+
if (membership.isExpired()) {
377+
logger.info(`Ignoring expired device membership ${membership.sender}/${membership.deviceId}`);
378+
continue;
379+
}
380+
if (!room.hasMembershipState(membership.sender ?? "", KnownMembership.Join)) {
381+
logger.info(`Ignoring membership of user ${membership.sender} who is not in the room.`);
382+
continue;
383+
}
384+
callMemberships.push(membership);
389385
}
390386

391387
callMemberships.sort((a, b) => a.createdTs() - b.createdTs());
@@ -409,15 +405,22 @@ export class MatrixRTCSession extends TypedEventEmitter<
409405
*
410406
* @deprecated Use `MatrixRTCSession.sessionForSlot` with sessionDescription `{ id: "", application: "m.call" }` instead.
411407
*/
412-
public static roomSessionForRoom(client: MatrixClient, room: Room): MatrixRTCSession {
413-
const callMemberships = MatrixRTCSession.sessionMembershipsForSlot(room, { id: "", application: "m.call" });
408+
public static async roomSessionForRoom(client: MatrixClient, room: Room): Promise<MatrixRTCSession> {
409+
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, {
410+
id: "",
411+
application: "m.call",
412+
});
414413
return new MatrixRTCSession(client, room, callMemberships, { id: "", application: "m.call" });
415414
}
416415

417416
/**
418417
* @deprecated Use `MatrixRTCSession.sessionForSlot` instead.
419418
*/
420-
public static sessionForRoom(client: MatrixClient, room: Room, slotDescription: SlotDescription): MatrixRTCSession {
419+
public static async sessionForRoom(
420+
client: MatrixClient,
421+
room: Room,
422+
slotDescription: SlotDescription,
423+
): Promise<MatrixRTCSession> {
421424
return this.sessionForSlot(client, room, slotDescription);
422425
}
423426

@@ -426,8 +429,12 @@ export class MatrixRTCSession extends TypedEventEmitter<
426429
* This returned session can be used to find out if there are active sessions
427430
* for the requested room and `slotDescription`.
428431
*/
429-
public static sessionForSlot(client: MatrixClient, room: Room, slotDescription: SlotDescription): MatrixRTCSession {
430-
const callMemberships = MatrixRTCSession.sessionMembershipsForSlot(room, slotDescription);
432+
public static async sessionForSlot(
433+
client: MatrixClient,
434+
room: Room,
435+
slotDescription: SlotDescription,
436+
): Promise<MatrixRTCSession> {
437+
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, slotDescription);
431438

432439
return new MatrixRTCSession(client, room, callMemberships, slotDescription);
433440
}
@@ -799,46 +806,50 @@ export class MatrixRTCSession extends TypedEventEmitter<
799806
*/
800807
private recalculateSessionMembers = (): void => {
801808
const oldMemberships = this.memberships;
802-
this.memberships = MatrixRTCSession.sessionMembershipsForSlot(this.room, this.slotDescription);
803-
804-
this._slotId = this._slotId ?? this.memberships[0]?.slotId;
805-
806-
const changed =
807-
oldMemberships.length != this.memberships.length ||
808-
oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
809-
810-
if (changed) {
811-
this.logger.info(
812-
`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`,
813-
);
814-
logDurationSync(this.logger, "emit MatrixRTCSessionEvent.MembershipsChanged", () => {
815-
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
816-
});
817-
818-
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
819-
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
820-
const ownMembership = this.membershipManager?.ownMembership;
821-
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
822-
// If we're the first member in the call, we're responsible for
823-
// sending the notification event
824-
if (ownMembership.eventId && this.joinConfig?.notificationType) {
825-
this.sendCallNotify(
826-
ownMembership.eventId,
827-
this.joinConfig.notificationType,
828-
ownMembership.callIntent,
809+
void MatrixRTCSession.sessionMembershipsForSlot(this.room, this.slotDescription, oldMemberships).then(
810+
(newMemberships) => {
811+
this.memberships = newMemberships;
812+
this._slotId = this._slotId ?? this.memberships[0]?.slotId;
813+
814+
const changed =
815+
oldMemberships.length != this.memberships.length ||
816+
// If they have the same length, this is enough to check "changed"
817+
oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
818+
819+
if (changed) {
820+
this.logger.info(
821+
`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`,
829822
);
830-
} else {
831-
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
823+
logDurationSync(this.logger, "emit MatrixRTCSessionEvent.MembershipsChanged", () => {
824+
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
825+
});
826+
827+
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
828+
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
829+
const ownMembership = this.membershipManager?.ownMembership;
830+
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
831+
// If we're the first member in the call, we're responsible for
832+
// sending the notification event
833+
if (ownMembership.eventId && this.joinConfig?.notificationType) {
834+
this.sendCallNotify(
835+
ownMembership.eventId,
836+
this.joinConfig.notificationType,
837+
ownMembership.callIntent,
838+
);
839+
} else {
840+
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
841+
}
842+
}
843+
// If anyone else joins the session it is no longer our responsibility to send the notification.
844+
// (If we were the joiner we already did sent the notification in the block above.)
845+
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
832846
}
833-
}
834-
// If anyone else joins the session it is no longer our responsibility to send the notification.
835-
// (If we were the joiner we already did sent the notification in the block above.)
836-
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
837-
}
838-
// This also needs to be done if `changed` = false
839-
// A member might have updated their fingerprint (created_ts)
840-
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
847+
// This also needs to be done if `changed` = false
848+
// A member might have updated their fingerprint (created_ts)
849+
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
841850

842-
this.setExpiryTimer();
851+
this.setExpiryTimer();
852+
},
853+
);
843854
};
844855
}

0 commit comments

Comments
 (0)