You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-07 23:02:56 +03:00
Allow configuration of MatrixRTC timers when calling joinRoomSession() (#4510)
This commit is contained in:
@@ -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" };
|
||||
|
@@ -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<MatrixRTCSessionEvent, M
|
||||
// The session Id of the call, this is the call_id of the call Member event.
|
||||
private _callId: string | undefined;
|
||||
|
||||
// How many ms after we joined the call, that our membership should expire, or undefined
|
||||
// if we're not yet joined
|
||||
private relativeExpiry: number | undefined;
|
||||
|
||||
// undefined means not yet joined
|
||||
private joinConfig?: JoinSessionConfig;
|
||||
|
||||
private get membershipExpiryTimeout(): number {
|
||||
return this.joinConfig?.membershipExpiryTimeout ?? 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
private get memberEventCheckPeriod(): number {
|
||||
return this.joinConfig?.memberEventCheckPeriod ?? 2 * 60 * 1000;
|
||||
}
|
||||
|
||||
private get callMemberEventRetryDelayMinimum(): number {
|
||||
return this.joinConfig?.callMemberEventRetryDelayMinimum ?? 3_000;
|
||||
}
|
||||
|
||||
private get updateEncryptionKeyThrottle(): number {
|
||||
return this.joinConfig?.updateEncryptionKeyThrottle ?? 3_000;
|
||||
}
|
||||
|
||||
private get makeKeyDelay(): number {
|
||||
return this.joinConfig?.makeKeyDelay ?? 3_000;
|
||||
}
|
||||
|
||||
private get useKeyDelay(): number {
|
||||
return this.joinConfig?.useKeyDelay ?? 5_000;
|
||||
}
|
||||
|
||||
private get membershipServerSideExpiryTimeout(): number {
|
||||
return this.joinConfig?.membershipServerSideExpiryTimeout ?? 8_000;
|
||||
}
|
||||
|
||||
private get membershipKeepAlivePeriod(): number {
|
||||
return this.joinConfig?.membershipKeepAlivePeriod ?? 5_000;
|
||||
}
|
||||
|
||||
private get callMemberEventRetryJitter(): number {
|
||||
return this.joinConfig?.callMemberEventRetryJitter ?? 2_000;
|
||||
}
|
||||
|
||||
// An identifier for our membership of the call. This will allow us to easily recognise
|
||||
// whether a membership was sent by this session or is stale from some other time.
|
||||
// It also forces our membership events to be unique, because otherwise we could try
|
||||
@@ -320,7 +403,8 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
|
||||
this.ownFocusActive = fociActive;
|
||||
this.ownFociPreferred = fociPreferred;
|
||||
this.relativeExpiry = MEMBERSHIP_EXPIRY_TIME;
|
||||
this.joinConfig = joinConfig;
|
||||
this.relativeExpiry = this.membershipExpiryTimeout;
|
||||
this.manageMediaKeys = joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
|
||||
this.useLegacyMemberEvents = joinConfig?.useLegacyMemberEvents ?? this.useLegacyMemberEvents;
|
||||
this.membershipId = randomString(5);
|
||||
@@ -373,6 +457,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
this.setNewKeyTimeouts.clear();
|
||||
|
||||
logger.info(`Leaving call session in room ${this.room.roomId}`);
|
||||
this.joinConfig = undefined;
|
||||
this.relativeExpiry = undefined;
|
||||
this.ownFocusActive = undefined;
|
||||
this.manageMediaKeys = false;
|
||||
@@ -515,7 +600,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
this.currentEncryptionKeyIndex = encryptionKeyIndex;
|
||||
}
|
||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
||||
}, USE_KEY_DELAY);
|
||||
}, this.useKeyDelay);
|
||||
this.setNewKeyTimeouts.add(useKeyTimeout);
|
||||
} else {
|
||||
if (userId === this.client.getUserId() && deviceId === this.client.getDeviceId()) {
|
||||
@@ -554,11 +639,14 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
|
||||
if (
|
||||
this.lastEncryptionKeyUpdateRequest &&
|
||||
this.lastEncryptionKeyUpdateRequest + UPDATE_ENCRYPTION_KEY_THROTTLE > 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<MatrixRTCSessionEvent, M
|
||||
|
||||
if (anyLeft) {
|
||||
logger.debug(`Member(s) have left: queueing sender key rotation`);
|
||||
this.makeNewKeyTimeout = setTimeout(this.onRotateKeyTimeout, MAKE_KEY_DELAY);
|
||||
this.makeNewKeyTimeout = setTimeout(this.onRotateKeyTimeout, this.makeKeyDelay);
|
||||
} else if (anyJoined) {
|
||||
logger.debug(`New member(s) have joined: re-sending keys`);
|
||||
this.requestSendCurrentKey();
|
||||
@@ -887,9 +975,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
if (!myPrevMembership) return true;
|
||||
|
||||
const expiryTime = myPrevMembership.getMsUntilExpiry();
|
||||
if (expiryTime !== undefined && expiryTime < MEMBERSHIP_EXPIRY_TIME / 2) {
|
||||
if (expiryTime !== undefined && expiryTime < this.membershipExpiryTimeout / 2) {
|
||||
// ...or if the expiry time needs bumping
|
||||
this.relativeExpiry! += MEMBERSHIP_EXPIRY_TIME;
|
||||
this.relativeExpiry! += this.membershipExpiryTimeout;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -904,7 +992,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* Makes a new membership list given the old list alonng with this user's previous membership event
|
||||
* Makes a new membership list given the old list along with this user's previous membership event
|
||||
* (if any) and this device's previous membership (if any)
|
||||
*/
|
||||
private makeNewLegacyMemberships(
|
||||
@@ -1010,7 +1098,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
}
|
||||
if (!this.membershipEventNeedsUpdate(myPrevMembershipData, myPrevMembership)) {
|
||||
// nothing to do - reschedule the check again
|
||||
this.memberEventTimeout = setTimeout(this.triggerCallMembershipEventUpdate, MEMBER_EVENT_CHECK_PERIOD);
|
||||
this.memberEventTimeout = setTimeout(
|
||||
this.triggerCallMembershipEventUpdate,
|
||||
this.memberEventCheckPeriod,
|
||||
);
|
||||
return;
|
||||
}
|
||||
newContent = this.makeNewLegacyMemberships(memberships, localDeviceId, myCallMemberEvent, myPrevMembership);
|
||||
@@ -1030,7 +1121,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
// check periodically to see if we need to refresh our member event
|
||||
this.memberEventTimeout = setTimeout(
|
||||
this.triggerCallMembershipEventUpdate,
|
||||
MEMBER_EVENT_CHECK_PERIOD,
|
||||
this.memberEventCheckPeriod,
|
||||
);
|
||||
}
|
||||
} else if (this.isJoined()) {
|
||||
@@ -1041,7 +1132,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
this.client._unstable_sendDelayedStateEvent(
|
||||
this.room.roomId,
|
||||
{
|
||||
delay: 8000,
|
||||
delay: this.membershipServerSideExpiryTimeout,
|
||||
},
|
||||
EventType.GroupCallMemberPrefix,
|
||||
{}, // leave event
|
||||
@@ -1108,7 +1199,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
}
|
||||
logger.info("Sent updated call member event.");
|
||||
} catch (e) {
|
||||
const resendDelay = CALL_MEMBER_EVENT_RETRY_DELAY_MIN + Math.random() * 2000;
|
||||
const resendDelay = this.callMemberEventRetryDelayMinimum + Math.random() * this.callMemberEventRetryJitter;
|
||||
logger.warn(`Failed to send call member event (retrying in ${resendDelay}): ${e}`);
|
||||
await sleep(resendDelay);
|
||||
await this.triggerCallMembershipEventUpdate();
|
||||
@@ -1116,7 +1207,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
||||
}
|
||||
|
||||
private scheduleDelayDisconnection(): void {
|
||||
this.memberEventTimeout = setTimeout(this.delayDisconnection, 5000);
|
||||
this.memberEventTimeout = setTimeout(this.delayDisconnection, this.membershipKeepAlivePeriod);
|
||||
}
|
||||
|
||||
private readonly delayDisconnection = async (): Promise<void> => {
|
||||
|
Reference in New Issue
Block a user