From 581b3209ab0f4cfdcc6fae1a0175b7b5868e2e45 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Mon, 11 Nov 2024 15:35:05 +0000 Subject: [PATCH] Allow configuration of MatrixRTC timers when calling joinRoomSession() (#4510) --- spec/unit/matrixrtc/MatrixRTCSession.spec.ts | 30 ++++ src/matrixrtc/MatrixRTCSession.ts | 151 +++++++++++++++---- 2 files changed, 151 insertions(+), 30 deletions(-) diff --git a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts index 96b1db9b03..6b45a93025 100644 --- a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts +++ b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts @@ -467,6 +467,36 @@ describe("MatrixRTCSession", () => { jest.useRealTimers(); }); + it("uses membershipExpiryTimeout from join config", async () => { + const realSetTimeout = setTimeout; + jest.useFakeTimers(); + sess!.joinRoomSession([mockFocus], mockFocus, { membershipExpiryTimeout: 60000 }); + await Promise.race([sentStateEvent, new Promise((resolve) => realSetTimeout(resolve, 500))]); + expect(client.sendStateEvent).toHaveBeenCalledWith( + mockRoom!.roomId, + EventType.GroupCallMemberPrefix, + { + memberships: [ + { + application: "m.call", + scope: "m.room", + call_id: "", + device_id: "AAAAAAA", + expires: 60000, + expires_ts: Date.now() + 60000, + foci_active: [mockFocus], + + membershipID: expect.stringMatching(".*"), + }, + ], + }, + "@alice:example.org", + ); + await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]); + expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(0); + jest.useRealTimers(); + }); + describe("non-legacy calls", () => { const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" }; const activeFocus = { type: "livekit", focus_selection: "oldest_membership" }; diff --git a/src/matrixrtc/MatrixRTCSession.ts b/src/matrixrtc/MatrixRTCSession.ts index b8274fdbaa..5d4d90ea58 100644 --- a/src/matrixrtc/MatrixRTCSession.ts +++ b/src/matrixrtc/MatrixRTCSession.ts @@ -42,20 +42,6 @@ import { sleep } from "../utils.ts"; const logger = rootLogger.getChild("MatrixRTCSession"); -const MEMBERSHIP_EXPIRY_TIME = 60 * 60 * 1000; -const MEMBER_EVENT_CHECK_PERIOD = 2 * 60 * 1000; // How often we check to see if we need to re-send our member event -const CALL_MEMBER_EVENT_RETRY_DELAY_MIN = 3000; -const UPDATE_ENCRYPTION_KEY_THROTTLE = 3000; - -// A delay after a member leaves before we create and publish a new key, because people -// tend to leave calls at the same time -const MAKE_KEY_DELAY = 3000; -// The delay between creating and sending a new key and starting to encrypt with it. This gives others -// a chance to receive the new key to minimise the chance they don't get media they can't decrypt. -// The total time between a member leaving and the call switching to new keys is therefore -// MAKE_KEY_DELAY + SEND_KEY_DELAY -const USE_KEY_DELAY = 5000; - const getParticipantId = (userId: string, deviceId: string): string => `${userId}:${deviceId}`; const getParticipantIdFromMembership = (m: CallMembership): string => getParticipantId(m.sender!, m.deviceId); @@ -87,12 +73,15 @@ export type MatrixRTCSessionEventHandlerMap = { participantId: string, ) => void; }; + export interface JoinSessionConfig { - /** If true, generate and share a media key for this participant, + /** + * If true, generate and share a media key for this participant, * and emit MatrixRTCSessionEvent.EncryptionKeyChanged when * media keys for other participants become available. */ manageMediaKeys?: boolean; + /** Lets you configure how the events for the session are formatted. * - legacy: use one event with a membership array. * - MSC4143: use one event per membership (with only one membership per event) @@ -100,7 +89,64 @@ export interface JoinSessionConfig { * `CallMembershipDataLegacy` and `SessionMembershipData` */ useLegacyMemberEvents?: boolean; + + /** + * The timeout (in milliseconds) after we joined the call, that our membership should expire + * unless we have explicitly updated it. + */ + membershipExpiryTimeout?: number; + + /** + * The period (in milliseconds) with which we check that our membership event still exists on the + * server. If it is not found we create it again. + */ + memberEventCheckPeriod?: number; + + /** + * The minimum delay (in milliseconds) after which we will retry sending the membership event if it + * failed to send. + */ + callMemberEventRetryDelayMinimum?: number; + + /** + * The jitter (in milliseconds) which is added to callMemberEventRetryDelayMinimum before retrying + * sending the membership event. e.g. if this is set to 1000, then a random delay of between 0 and 1000 + * milliseconds will be added. + */ + callMemberEventRetryJitter?: number; + + /** + * The minimum time (in milliseconds) between each attempt to send encryption key(s). + * e.g. if this is set to 1000, then we will send at most one key event every second. + */ + updateEncryptionKeyThrottle?: number; + + /** + * The delay (in milliseconds) after a member leaves before we create and publish a new key, because people + * tend to leave calls at the same time. + */ + makeKeyDelay?: number; + + /** + * The delay (in milliseconds) between creating and sending a new key and starting to encrypt with it. This + * gives other a chance to receive the new key to minimise the chance they don't get media they can't decrypt. + * The total time between a member leaving and the call switching to new keys is therefore: + * makeKeyDelay + useKeyDelay + */ + useKeyDelay?: number; + + /** + * The timeout (in milliseconds) after which the server will consider the membership to have expired if it + * has not received a keep-alive from the client. + */ + membershipServerSideExpiryTimeout?: number; + + /** + * The period (in milliseconds) that the client will send membership keep-alives to the server. + */ + membershipKeepAlivePeriod?: number; } + /** * A MatrixRTCSession manages the membership & properties of a MatrixRTC session. * This class doesn't deal with media at all, just membership & properties of a session. @@ -109,10 +155,47 @@ export class MatrixRTCSession extends TypedEventEmitter Date.now() + this.lastEncryptionKeyUpdateRequest + this.updateEncryptionKeyThrottle > Date.now() ) { logger.info("Last encryption key event sent too recently: postponing"); if (this.keysEventUpdateTimeout === undefined) { - this.keysEventUpdateTimeout = setTimeout(this.sendEncryptionKeysEvent, UPDATE_ENCRYPTION_KEY_THROTTLE); + this.keysEventUpdateTimeout = setTimeout( + this.sendEncryptionKeysEvent, + this.updateEncryptionKeyThrottle, + ); } return; } @@ -799,7 +887,7 @@ export class MatrixRTCSession extends TypedEventEmitter => {