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();
|
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", () => {
|
describe("non-legacy calls", () => {
|
||||||
const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" };
|
const activeFocusConfig = { type: "livekit", livekit_service_url: "https://active.url" };
|
||||||
const activeFocus = { type: "livekit", focus_selection: "oldest_membership" };
|
const activeFocus = { type: "livekit", focus_selection: "oldest_membership" };
|
||||||
|
@@ -42,20 +42,6 @@ import { sleep } from "../utils.ts";
|
|||||||
|
|
||||||
const logger = rootLogger.getChild("MatrixRTCSession");
|
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 getParticipantId = (userId: string, deviceId: string): string => `${userId}:${deviceId}`;
|
||||||
const getParticipantIdFromMembership = (m: CallMembership): string => getParticipantId(m.sender!, m.deviceId);
|
const getParticipantIdFromMembership = (m: CallMembership): string => getParticipantId(m.sender!, m.deviceId);
|
||||||
|
|
||||||
@@ -87,12 +73,15 @@ export type MatrixRTCSessionEventHandlerMap = {
|
|||||||
participantId: string,
|
participantId: string,
|
||||||
) => void;
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface JoinSessionConfig {
|
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
|
* and emit MatrixRTCSessionEvent.EncryptionKeyChanged when
|
||||||
* media keys for other participants become available.
|
* media keys for other participants become available.
|
||||||
*/
|
*/
|
||||||
manageMediaKeys?: boolean;
|
manageMediaKeys?: boolean;
|
||||||
|
|
||||||
/** Lets you configure how the events for the session are formatted.
|
/** Lets you configure how the events for the session are formatted.
|
||||||
* - legacy: use one event with a membership array.
|
* - legacy: use one event with a membership array.
|
||||||
* - MSC4143: use one event per membership (with only one membership per event)
|
* - MSC4143: use one event per membership (with only one membership per event)
|
||||||
@@ -100,7 +89,64 @@ export interface JoinSessionConfig {
|
|||||||
* `CallMembershipDataLegacy` and `SessionMembershipData`
|
* `CallMembershipDataLegacy` and `SessionMembershipData`
|
||||||
*/
|
*/
|
||||||
useLegacyMemberEvents?: boolean;
|
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.
|
* 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.
|
* 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.
|
// The session Id of the call, this is the call_id of the call Member event.
|
||||||
private _callId: string | undefined;
|
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;
|
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
|
// 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.
|
// 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
|
// 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.ownFocusActive = fociActive;
|
||||||
this.ownFociPreferred = fociPreferred;
|
this.ownFociPreferred = fociPreferred;
|
||||||
this.relativeExpiry = MEMBERSHIP_EXPIRY_TIME;
|
this.joinConfig = joinConfig;
|
||||||
|
this.relativeExpiry = this.membershipExpiryTimeout;
|
||||||
this.manageMediaKeys = joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
|
this.manageMediaKeys = joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
|
||||||
this.useLegacyMemberEvents = joinConfig?.useLegacyMemberEvents ?? this.useLegacyMemberEvents;
|
this.useLegacyMemberEvents = joinConfig?.useLegacyMemberEvents ?? this.useLegacyMemberEvents;
|
||||||
this.membershipId = randomString(5);
|
this.membershipId = randomString(5);
|
||||||
@@ -373,6 +457,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
this.setNewKeyTimeouts.clear();
|
this.setNewKeyTimeouts.clear();
|
||||||
|
|
||||||
logger.info(`Leaving call session in room ${this.room.roomId}`);
|
logger.info(`Leaving call session in room ${this.room.roomId}`);
|
||||||
|
this.joinConfig = undefined;
|
||||||
this.relativeExpiry = undefined;
|
this.relativeExpiry = undefined;
|
||||||
this.ownFocusActive = undefined;
|
this.ownFocusActive = undefined;
|
||||||
this.manageMediaKeys = false;
|
this.manageMediaKeys = false;
|
||||||
@@ -515,7 +600,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
this.currentEncryptionKeyIndex = encryptionKeyIndex;
|
this.currentEncryptionKeyIndex = encryptionKeyIndex;
|
||||||
}
|
}
|
||||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, participantId);
|
||||||
}, USE_KEY_DELAY);
|
}, this.useKeyDelay);
|
||||||
this.setNewKeyTimeouts.add(useKeyTimeout);
|
this.setNewKeyTimeouts.add(useKeyTimeout);
|
||||||
} else {
|
} else {
|
||||||
if (userId === this.client.getUserId() && deviceId === this.client.getDeviceId()) {
|
if (userId === this.client.getUserId() && deviceId === this.client.getDeviceId()) {
|
||||||
@@ -554,11 +639,14 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
this.lastEncryptionKeyUpdateRequest &&
|
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");
|
logger.info("Last encryption key event sent too recently: postponing");
|
||||||
if (this.keysEventUpdateTimeout === undefined) {
|
if (this.keysEventUpdateTimeout === undefined) {
|
||||||
this.keysEventUpdateTimeout = setTimeout(this.sendEncryptionKeysEvent, UPDATE_ENCRYPTION_KEY_THROTTLE);
|
this.keysEventUpdateTimeout = setTimeout(
|
||||||
|
this.sendEncryptionKeysEvent,
|
||||||
|
this.updateEncryptionKeyThrottle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -799,7 +887,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
|
|
||||||
if (anyLeft) {
|
if (anyLeft) {
|
||||||
logger.debug(`Member(s) have left: queueing sender key rotation`);
|
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) {
|
} else if (anyJoined) {
|
||||||
logger.debug(`New member(s) have joined: re-sending keys`);
|
logger.debug(`New member(s) have joined: re-sending keys`);
|
||||||
this.requestSendCurrentKey();
|
this.requestSendCurrentKey();
|
||||||
@@ -887,9 +975,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
if (!myPrevMembership) return true;
|
if (!myPrevMembership) return true;
|
||||||
|
|
||||||
const expiryTime = myPrevMembership.getMsUntilExpiry();
|
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
|
// ...or if the expiry time needs bumping
|
||||||
this.relativeExpiry! += MEMBERSHIP_EXPIRY_TIME;
|
this.relativeExpiry! += this.membershipExpiryTimeout;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,7 +992,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
return {};
|
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)
|
* (if any) and this device's previous membership (if any)
|
||||||
*/
|
*/
|
||||||
private makeNewLegacyMemberships(
|
private makeNewLegacyMemberships(
|
||||||
@@ -1010,7 +1098,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
}
|
}
|
||||||
if (!this.membershipEventNeedsUpdate(myPrevMembershipData, myPrevMembership)) {
|
if (!this.membershipEventNeedsUpdate(myPrevMembershipData, myPrevMembership)) {
|
||||||
// nothing to do - reschedule the check again
|
// nothing to do - reschedule the check again
|
||||||
this.memberEventTimeout = setTimeout(this.triggerCallMembershipEventUpdate, MEMBER_EVENT_CHECK_PERIOD);
|
this.memberEventTimeout = setTimeout(
|
||||||
|
this.triggerCallMembershipEventUpdate,
|
||||||
|
this.memberEventCheckPeriod,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
newContent = this.makeNewLegacyMemberships(memberships, localDeviceId, myCallMemberEvent, myPrevMembership);
|
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
|
// check periodically to see if we need to refresh our member event
|
||||||
this.memberEventTimeout = setTimeout(
|
this.memberEventTimeout = setTimeout(
|
||||||
this.triggerCallMembershipEventUpdate,
|
this.triggerCallMembershipEventUpdate,
|
||||||
MEMBER_EVENT_CHECK_PERIOD,
|
this.memberEventCheckPeriod,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (this.isJoined()) {
|
} else if (this.isJoined()) {
|
||||||
@@ -1041,7 +1132,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
this.client._unstable_sendDelayedStateEvent(
|
this.client._unstable_sendDelayedStateEvent(
|
||||||
this.room.roomId,
|
this.room.roomId,
|
||||||
{
|
{
|
||||||
delay: 8000,
|
delay: this.membershipServerSideExpiryTimeout,
|
||||||
},
|
},
|
||||||
EventType.GroupCallMemberPrefix,
|
EventType.GroupCallMemberPrefix,
|
||||||
{}, // leave event
|
{}, // leave event
|
||||||
@@ -1108,7 +1199,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
}
|
}
|
||||||
logger.info("Sent updated call member event.");
|
logger.info("Sent updated call member event.");
|
||||||
} catch (e) {
|
} 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}`);
|
logger.warn(`Failed to send call member event (retrying in ${resendDelay}): ${e}`);
|
||||||
await sleep(resendDelay);
|
await sleep(resendDelay);
|
||||||
await this.triggerCallMembershipEventUpdate();
|
await this.triggerCallMembershipEventUpdate();
|
||||||
@@ -1116,7 +1207,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
|
|||||||
}
|
}
|
||||||
|
|
||||||
private scheduleDelayDisconnection(): void {
|
private scheduleDelayDisconnection(): void {
|
||||||
this.memberEventTimeout = setTimeout(this.delayDisconnection, 5000);
|
this.memberEventTimeout = setTimeout(this.delayDisconnection, this.membershipKeepAlivePeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly delayDisconnection = async (): Promise<void> => {
|
private readonly delayDisconnection = async (): Promise<void> => {
|
||||||
|
Reference in New Issue
Block a user