1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-06 12:02:40 +03:00

Remove support for "legacy" MSC3898 group calling in MatrixRTCSession and CallMembership (#4583)

* remove all legacy call related code and adjust tests.
We actually had a bit of tests just for legacy and not for session events. All those tests got ported over so we do not remove any tests.

* dont adjust tests but remove legacy tests

* Remove deprecated CallMembership.getLocalExpiry()

* Remove references to legacy in test case names

* Clean up SessionMembershipData tsdoc

* Remove CallMembership.expires

* Use correct expire duration.

* make expiration methods not return optional values and update docstring

* add docs to `SessionMembershipData`

* Use `MSC4143` (instaed of `non-legacy`) wording in comment

Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>

* Incorporate feedback from review

* Fix test name

---------

Co-authored-by: Hugh Nimmo-Smith <hughns@matrix.org>
Co-authored-by: Hugh Nimmo-Smith <hughns@users.noreply.github.com>
This commit is contained in:
Timo
2025-01-06 18:23:16 +01:00
committed by GitHub
parent 7678923e04
commit ffd3c9575e
8 changed files with 240 additions and 918 deletions

View File

@@ -35,11 +35,7 @@ import {
SpaceChildEventContent,
SpaceParentEventContent,
} from "./state_events.ts";
import {
ExperimentalGroupCallRoomMemberState,
IGroupCallRoomMemberState,
IGroupCallRoomState,
} from "../webrtc/groupCall.ts";
import { IGroupCallRoomMemberState, IGroupCallRoomState } from "../webrtc/groupCall.ts";
import { MSC3089EventContent } from "../models/MSC3089Branch.ts";
import { M_BEACON, M_BEACON_INFO, MBeaconEventContent, MBeaconInfoEventContent } from "./beacon.ts";
import { XOR } from "./common.ts";
@@ -361,10 +357,7 @@ export interface StateEvents {
// MSC3401
[EventType.GroupCallPrefix]: IGroupCallRoomState;
[EventType.GroupCallMemberPrefix]: XOR<
XOR<IGroupCallRoomMemberState, ExperimentalGroupCallRoomMemberState>,
XOR<SessionMembershipData, {}>
>;
[EventType.GroupCallMemberPrefix]: XOR<IGroupCallRoomMemberState, XOR<SessionMembershipData, {}>>;
// MSC3089
[UNSTABLE_MSC3089_BRANCH.name]: MSC3089EventContent;

View File

@@ -14,35 +14,71 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EitherAnd } from "matrix-events-sdk/lib/types";
import { MatrixEvent } from "../matrix.ts";
import { deepCompare } from "../utils.ts";
import { Focus } from "./focus.ts";
import { isLivekitFocusActive } from "./LivekitFocus.ts";
type CallScope = "m.room" | "m.user";
// Represents an entry in the memberships section of an m.call.member event as it is on the wire
// There are two different data interfaces. One for the Legacy types and one compliant with MSC4143
// MSC4143 (MatrixRTC) session membership data
/**
* MSC4143 (MatrixRTC) session membership data.
* Represents an entry in the memberships section of an m.call.member event as it is on the wire.
**/
export type SessionMembershipData = {
/**
* The RTC application defines the type of the RTC session.
*/
application: string;
/**
* The id of this session.
* A session can never span over multiple rooms so this id is to distinguish between
* multiple session in one room. A room wide session that is not associated with a user,
* and therefore immune to creation race conflicts, uses the `call_id: ""`.
*/
call_id: string;
/**
* The Matrix device ID of this session. A single user can have multiple sessions on different devices.
*/
device_id: string;
/**
* The focus selection system this user/membership is using.
*/
focus_active: Focus;
/**
* A list of possible foci this uses knows about. One of them might be used based on the focus_active
* selection system.
*/
foci_preferred: Focus[];
/**
* Optional field that contains the creation of the session. If it is undefined the creation
* is the `origin_server_ts` of the event itself. For updates to the event this property tracks
* the `origin_server_ts` of the initial join event.
* - If it is undefined it can be interpreted as a "Join".
* - If it is defined it can be interpreted as an "Update"
*/
created_ts?: number;
// Application specific data
scope?: CallScope;
};
export const isSessionMembershipData = (data: CallMembershipData): data is SessionMembershipData =>
"focus_active" in data;
/**
* If the `application` = `"m.call"` this defines if it is a room or user owned call.
* There can always be one room scroped call but multiple user owned calls (breakout sessions)
*/
scope?: CallScope;
/**
* Optionally we allow to define a delta to the `created_ts` that defines when the event is expired/invalid.
* This should be set to multiple hours. The only reason it exist is to deal with failed delayed events.
* (for example caused by a homeserver crashes)
**/
expires?: number;
};
const checkSessionsMembershipData = (data: any, errors: string[]): data is SessionMembershipData => {
const prefix = "Malformed session membership event: ";
@@ -59,65 +95,20 @@ const checkSessionsMembershipData = (data: any, errors: string[]): data is Sessi
return errors.length === 0;
};
// Legacy session membership data
export type CallMembershipDataLegacy = {
application: string;
call_id: string;
scope: CallScope;
device_id: string;
membershipID: string;
created_ts?: number;
foci_active?: Focus[];
} & EitherAnd<{ expires: number }, { expires_ts: number }>;
export const isLegacyCallMembershipData = (data: CallMembershipData): data is CallMembershipDataLegacy =>
"membershipID" in data;
const checkCallMembershipDataLegacy = (data: any, errors: string[]): data is CallMembershipDataLegacy => {
const prefix = "Malformed legacy rtc membership event: ";
if (!("expires" in data || "expires_ts" in data)) {
errors.push(prefix + "expires_ts or expires must be present");
}
if ("expires" in data) {
if (typeof data.expires !== "number") {
errors.push(prefix + "expires must be numeric");
}
}
if ("expires_ts" in data) {
if (typeof data.expires_ts !== "number") {
errors.push(prefix + "expires_ts must be numeric");
}
}
if (typeof data.device_id !== "string") errors.push(prefix + "device_id must be string");
if (typeof data.call_id !== "string") errors.push(prefix + "call_id must be string");
if (typeof data.application !== "string") errors.push(prefix + "application must be a string");
if (typeof data.membershipID !== "string") errors.push(prefix + "membershipID must be a string");
// optional elements
if (data.created_ts && typeof data.created_ts !== "number") errors.push(prefix + "created_ts must be number");
// application specific data (we first need to check if they exist)
if (data.scope && typeof data.scope !== "string") errors.push(prefix + "scope must be string");
return errors.length === 0;
};
export type CallMembershipData = CallMembershipDataLegacy | SessionMembershipData;
export class CallMembership {
public static equal(a: CallMembership, b: CallMembership): boolean {
return deepCompare(a.membershipData, b.membershipData);
}
private membershipData: CallMembershipData;
private membershipData: SessionMembershipData;
public constructor(
private parentEvent: MatrixEvent,
data: any,
) {
const sessionErrors: string[] = [];
const legacyErrors: string[] = [];
if (!checkSessionsMembershipData(data, sessionErrors) && !checkCallMembershipDataLegacy(data, legacyErrors)) {
if (!checkSessionsMembershipData(data, sessionErrors)) {
throw Error(
`unknown CallMembership data. Does not match legacy call.member (${legacyErrors.join(" & ")}) events nor MSC4143 (${sessionErrors.join(" & ")})`,
`unknown CallMembership data. Does not match MSC4143 call.member (${sessionErrors.join(" & ")}) events this could be a legacy membership event: (${data})`,
);
} else {
this.membershipData = data;
@@ -149,11 +140,10 @@ export class CallMembership {
}
public get membershipID(): string {
if (isLegacyCallMembershipData(this.membershipData)) return this.membershipData.membershipID;
// the createdTs behaves equivalent to the membershipID.
// we only need the field for the legacy member envents where we needed to update them
// synapse ignores sending state events if they have the same content.
else return this.createdTs().toString();
return this.createdTs().toString();
}
public createdTs(): number {
@@ -161,57 +151,24 @@ export class CallMembership {
}
/**
* Gets the absolute expiry time of the membership if applicable to this membership type.
* Gets the absolute expiry timestamp of the membership.
* @returns The absolute expiry time of the membership as a unix timestamp in milliseconds or undefined if not applicable
*/
public getAbsoluteExpiry(): number | undefined {
// if the membership is not a legacy membership, we assume it is MSC4143
if (!isLegacyCallMembershipData(this.membershipData)) return undefined;
// TODO: implement this in a future PR. Something like:
// TODO: calculate this from the MatrixRTCSession join configuration directly
// return this.createdTs() + (this.membershipData.expires ?? DEFAULT_EXPIRE_DURATION);
if ("expires" in this.membershipData) {
// we know createdTs exists since we already do the isLegacyCallMembershipData check
return this.createdTs() + this.membershipData.expires;
} else {
// We know it exists because we checked for this in the constructor.
return this.membershipData.expires_ts;
}
}
/**
* Gets the expiry time of the event, converted into the device's local time.
* @deprecated This function has been observed returning bad data and is no longer used by MatrixRTC.
* @returns The local expiry time of the membership as a unix timestamp in milliseconds or undefined if not applicable
*/
public getLocalExpiry(): number | undefined {
// if the membership is not a legacy membership, we assume it is MSC4143
if (!isLegacyCallMembershipData(this.membershipData)) return undefined;
if ("expires" in this.membershipData) {
// we know createdTs exists since we already do the isLegacyCallMembershipData check
const relativeCreationTime = this.parentEvent.getTs() - this.createdTs();
const localCreationTs = this.parentEvent.localTimestamp - relativeCreationTime;
return localCreationTs + this.membershipData.expires;
} else {
// With expires_ts we cannot convert to local time.
// TODO: Check the server timestamp and compute a diff to local time.
return this.membershipData.expires_ts;
}
return undefined;
}
/**
* @returns The number of milliseconds until the membership expires or undefined if applicable
*/
public getMsUntilExpiry(): number | undefined {
if (isLegacyCallMembershipData(this.membershipData)) {
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
// We used to try and adjust for the local clock being skewed, but there are cases where this is not accurate.
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
return this.getAbsoluteExpiry()! - Date.now();
}
// TODO: implement this in a future PR. Something like:
// return this.getAbsoluteExpiry() - Date.now();
// Assumed to be MSC4143
return undefined;
}
@@ -219,29 +176,20 @@ export class CallMembership {
* @returns true if the membership has expired, otherwise false
*/
public isExpired(): boolean {
if (isLegacyCallMembershipData(this.membershipData)) return this.getMsUntilExpiry()! <= 0;
// TODO: implement this in a future PR. Something like:
// return this.getMsUntilExpiry() <= 0;
// MSC4143 events expire by being updated. So if the event exists, its not expired.
return false;
}
public getPreferredFoci(): Focus[] {
// To support both, the new and the old MatrixRTC memberships have two cases based
// on the availablitiy of `foci_preferred`
if (isLegacyCallMembershipData(this.membershipData)) return this.membershipData.foci_active ?? [];
// MSC4143 style membership
return this.membershipData.foci_preferred;
}
public getFocusSelection(): string | undefined {
if (isLegacyCallMembershipData(this.membershipData)) {
return "oldest_membership";
} else {
const focusActive = this.membershipData.focus_active;
if (isLivekitFocusActive(focusActive)) {
return focusActive.focus_selection;
}
const focusActive = this.membershipData.focus_active;
if (isLivekitFocusActive(focusActive)) {
return focusActive.focus_selection;
}
}
}

View File

@@ -21,25 +21,20 @@ import { Room } from "../models/room.ts";
import { MatrixClient } from "../client.ts";
import { EventType } from "../@types/event.ts";
import { UpdateDelayedEventAction } from "../@types/requests.ts";
import {
CallMembership,
CallMembershipData,
CallMembershipDataLegacy,
SessionMembershipData,
isLegacyCallMembershipData,
} from "./CallMembership.ts";
import { CallMembership, SessionMembershipData } from "./CallMembership.ts";
import { RoomStateEvent } from "../models/room-state.ts";
import { Focus } from "./focus.ts";
import { randomString, secureRandomBase64Url } from "../randomstring.ts";
import { secureRandomBase64Url } from "../randomstring.ts";
import { EncryptionKeysEventContent } from "./types.ts";
import { decodeBase64, encodeUnpaddedBase64 } from "../base64.ts";
import { KnownMembership } from "../@types/membership.ts";
import { HTTPError, MatrixError, safeGetRetryAfterMs } from "../http-api/errors.ts";
import { MatrixEvent } from "../models/event.ts";
import { isLivekitFocusActive } from "./LivekitFocus.ts";
import { ExperimentalGroupCallRoomMemberState } from "../webrtc/groupCall.ts";
import { sleep } from "../utils.ts";
const DEFAULT_EXPIRE_DURATION = 1000 * 60 * 60 * 4; // 4 hours
const logger = rootLogger.getChild("MatrixRTCSession");
const getParticipantId = (userId: string, deviceId: string): string => `${userId}:${deviceId}`;
@@ -82,14 +77,6 @@ export interface JoinSessionConfig {
*/
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)
* More details can be found in MSC4143 and by checking the types:
* `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.
@@ -161,11 +148,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
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;
return this.joinConfig?.membershipExpiryTimeout ?? DEFAULT_EXPIRE_DURATION;
}
private get callMemberEventRetryDelayMinimum(): number {
@@ -206,14 +189,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
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
// to overwrite a membership from a previous session but it would do nothing because the
// event content would be identical. We need the origin_server_ts to update though, so
// forcing unique content fixes this.
private membershipId: string | undefined;
private memberEventTimeout?: ReturnType<typeof setTimeout>;
private expiryTimeout?: ReturnType<typeof setTimeout>;
private keysEventUpdateTimeout?: ReturnType<typeof setTimeout>;
@@ -229,7 +204,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
private needCallMembershipUpdate = false;
private manageMediaKeys = false;
private useLegacyMemberEvents = true;
// userId:deviceId => array of (key, timestamp)
private encryptionKeys = new Map<string, Array<{ key: Uint8Array; timestamp: number }>>();
private lastEncryptionKeyUpdateRequest?: number;
@@ -292,19 +266,14 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
// Dont even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
if (eventKeysCount === 0) continue;
let membershipContents: any[] = [];
const membershipContents: any[] = [];
// We first decide if its a MSC4143 event (per device state key)
if (eventKeysCount > 1 && "focus_active" in content) {
// We have a MSC4143 event membership event
membershipContents.push(content);
} else if (eventKeysCount === 1 && "memberships" in content) {
// we have a legacy (one event for all devices) event
if (!Array.isArray(content["memberships"])) {
logger.warn(`Malformed member event from ${memberEvent.getSender()}: memberships is not an array`);
continue;
}
membershipContents = content["memberships"];
logger.warn(`Legacy event found. Those are ignored, they do not contribute to the MatrixRTC session`);
}
if (membershipContents.length === 0) continue;
@@ -416,8 +385,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
this.joinConfig = joinConfig;
this.relativeExpiry = this.membershipExpiryTimeout;
this.manageMediaKeys = joinConfig?.manageMediaKeys ?? this.manageMediaKeys;
this.useLegacyMemberEvents = joinConfig?.useLegacyMemberEvents ?? this.useLegacyMemberEvents;
this.membershipId = randomString(5);
logger.info(`Joining call session in room ${this.room.roomId} with manageMediaKeys=${this.manageMediaKeys}`);
if (joinConfig?.manageMediaKeys) {
@@ -471,7 +438,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
this.relativeExpiry = undefined;
this.ownFocusActive = undefined;
this.manageMediaKeys = false;
this.membershipId = undefined;
this.emit(MatrixRTCSessionEvent.JoinStateChanged, false);
if (timeout) {
@@ -492,9 +458,9 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
const oldestMembership = this.getOldestMembership();
return oldestMembership?.getPreferredFoci()[0];
}
}
if (!this.ownFocusActive) {
// we use the legacy call.member events so default to oldest member
} else {
// We do not understand the membership format (could be legacy). We default to oldestMembership
// Once there are other methods this is a hard error!
const oldestMembership = this.getOldestMembership();
return oldestMembership?.getPreferredFoci()[0];
}
@@ -928,37 +894,10 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
this.lastMembershipFingerprints = new Set(
this.memberships
.filter((m) => !this.isMyMembership(m))
.map((m) => `${getParticipantIdFromMembership(m)}:${m.membershipID}:${m.createdTs()}`),
.map((m) => `${getParticipantIdFromMembership(m)}:${m.createdTs()}`),
);
}
/**
* Constructs our own membership
* @param prevMembership - The previous value of our call membership, if any
*/
private makeMyMembershipLegacy(deviceId: string, prevMembership?: CallMembership): CallMembershipDataLegacy {
if (this.relativeExpiry === undefined) {
throw new Error("Tried to create our own membership event when we're not joined!");
}
if (this.membershipId === undefined) {
throw new Error("Tried to create our own membership event when we have no membership ID!");
}
const createdTs = prevMembership?.createdTs();
return {
call_id: "",
scope: "m.room",
application: "m.call",
device_id: deviceId,
expires: this.relativeExpiry,
// TODO: Date.now() should be the origin_server_ts (now).
expires_ts: this.relativeExpiry + (createdTs ?? Date.now()),
// we use the fociPreferred since this is the list of foci.
// it is named wrong in the Legacy events.
foci_active: this.ownFociPreferred,
membershipID: this.membershipId,
...(createdTs ? { created_ts: createdTs } : {}),
};
}
/**
* Constructs our own membership
*/
@@ -968,36 +907,12 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
scope: "m.room",
application: "m.call",
device_id: deviceId,
expires: this.relativeExpiry,
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
foci_preferred: this.ownFociPreferred ?? [],
};
}
/**
* Returns true if our membership event needs to be updated
*/
private membershipEventNeedsUpdate(
myPrevMembershipData?: CallMembershipData,
myPrevMembership?: CallMembership,
): boolean {
if (myPrevMembership && myPrevMembership.getMsUntilExpiry() === undefined) return false;
// Need to update if there's a membership for us but we're not joined (valid or otherwise)
if (!this.isJoined()) return !!myPrevMembershipData;
// ...or if we are joined, but there's no valid membership event
if (!myPrevMembership) return true;
const expiryTime = myPrevMembership.getMsUntilExpiry();
if (expiryTime !== undefined && expiryTime < this.membershipExpiryTimeout / 2) {
// ...or if the expiry time needs bumping
this.relativeExpiry! += this.membershipExpiryTimeout;
return true;
}
return false;
}
private makeNewMembership(deviceId: string): SessionMembershipData | {} {
// If we're joined, add our own
if (this.isJoined()) {
@@ -1005,49 +920,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
}
return {};
}
/**
* 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(
oldMemberships: CallMembershipData[],
localDeviceId: string,
myCallMemberEvent?: MatrixEvent,
myPrevMembership?: CallMembership,
): ExperimentalGroupCallRoomMemberState {
const filterExpired = (m: CallMembershipData): boolean => {
let membershipObj;
try {
membershipObj = new CallMembership(myCallMemberEvent!, m);
} catch {
return false;
}
return !membershipObj.isExpired();
};
const transformMemberships = (m: CallMembershipData): CallMembershipData => {
if (m.created_ts === undefined) {
// we need to fill this in with the origin_server_ts from its original event
m.created_ts = myCallMemberEvent!.getTs();
}
return m;
};
// Filter our any invalid or expired memberships, and also our own - we'll add that back in next
let newMemberships = oldMemberships.filter(filterExpired).filter((m) => m.device_id !== localDeviceId);
// Fix up any memberships that need their created_ts adding
newMemberships = newMemberships.map(transformMemberships);
// If we're joined, add our own
if (this.isJoined()) {
newMemberships.push(this.makeMyMembershipLegacy(localDeviceId, myPrevMembership));
}
return { memberships: newMemberships };
}
private triggerCallMembershipEventUpdate = async (): Promise<void> => {
// TODO: Should this await on a shared promise?
@@ -1081,64 +953,14 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
const localDeviceId = this.client.getDeviceId();
if (!localUserId || !localDeviceId) throw new Error("User ID or device ID was null!");
const callMemberEvents = roomState.events.get(EventType.GroupCallMemberPrefix);
const legacy = this.stateEventsContainOngoingLegacySession(callMemberEvents);
let newContent: {} | ExperimentalGroupCallRoomMemberState | SessionMembershipData = {};
if (legacy) {
const myCallMemberEvent = callMemberEvents?.get(localUserId);
const content = myCallMemberEvent?.getContent() ?? {};
let myPrevMembership: CallMembership | undefined;
// We know its CallMembershipDataLegacy
const memberships: CallMembershipDataLegacy[] = Array.isArray(content["memberships"])
? content["memberships"]
: [];
const myPrevMembershipData = memberships.find((m) => m.device_id === localDeviceId);
try {
if (
myCallMemberEvent &&
myPrevMembershipData &&
isLegacyCallMembershipData(myPrevMembershipData) &&
myPrevMembershipData.membershipID === this.membershipId
) {
myPrevMembership = new CallMembership(myCallMemberEvent, myPrevMembershipData);
}
} catch (e) {
// This would indicate a bug or something weird if our own call membership
// wasn't valid
logger.warn("Our previous call membership was invalid - this shouldn't happen.", e);
}
if (myPrevMembership) {
logger.debug(`${myPrevMembership.getMsUntilExpiry()} until our membership expires`);
}
if (!this.membershipEventNeedsUpdate(myPrevMembershipData, myPrevMembership)) {
// nothing to do - reschedule the check again
this.memberEventTimeout = setTimeout(
this.triggerCallMembershipEventUpdate,
this.memberEventCheckPeriod,
);
return;
}
newContent = this.makeNewLegacyMemberships(memberships, localDeviceId, myCallMemberEvent, myPrevMembership);
} else {
newContent = this.makeNewMembership(localDeviceId);
}
let newContent: {} | SessionMembershipData = {};
// TODO: implement expiry logic to MSC4143 events
// previously we checked here if the event is timed out and scheduled a check if not.
// maybe there is a better way.
newContent = this.makeNewMembership(localDeviceId);
try {
if (legacy) {
await this.client.sendStateEvent(
this.room.roomId,
EventType.GroupCallMemberPrefix,
newContent,
localUserId,
);
if (this.isJoined()) {
// check periodically to see if we need to refresh our member event
this.memberEventTimeout = setTimeout(
this.triggerCallMembershipEventUpdate,
this.memberEventCheckPeriod,
);
}
} else if (this.isJoined()) {
if (this.isJoined()) {
const stateKey = this.makeMembershipStateKey(localUserId, localDeviceId);
const prepareDelayedDisconnection = async (): Promise<void> => {
try {
@@ -1203,6 +1025,7 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
this.scheduleDelayDisconnection();
}
} else {
// Not joined
let sentDelayedDisconnect = false;
if (this.disconnectDelayId !== undefined) {
try {
@@ -1255,29 +1078,6 @@ export class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent, M
}
};
private stateEventsContainOngoingLegacySession(callMemberEvents: Map<string, MatrixEvent> | undefined): boolean {
if (!callMemberEvents?.size) {
return this.useLegacyMemberEvents;
}
let containsAnyOngoingSession = false;
let containsUnknownOngoingSession = false;
for (const callMemberEvent of callMemberEvents.values()) {
const content = callMemberEvent.getContent();
if (Array.isArray(content["memberships"])) {
for (const membership of content.memberships) {
if (!new CallMembership(callMemberEvent, membership).isExpired()) {
return true;
}
}
} else if (Object.keys(content).length > 0) {
containsAnyOngoingSession ||= true;
containsUnknownOngoingSession ||= !("focus_active" in content);
}
}
return containsAnyOngoingSession && !containsUnknownOngoingSession ? false : this.useLegacyMemberEvents;
}
private makeMembershipStateKey(localUserId: string, localDeviceId: string): string {
const stateKey = `${localUserId}_${localDeviceId}`;
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {

View File

@@ -35,7 +35,6 @@ import {
import { SummaryStatsReportGatherer } from "./stats/summaryStatsReportGatherer.ts";
import { CallFeedStatsReporter } from "./stats/callFeedStatsReporter.ts";
import { KnownMembership } from "../@types/membership.ts";
import { CallMembershipData } from "../matrixrtc/CallMembership.ts";
export enum GroupCallIntent {
Ring = "m.ring",
@@ -198,11 +197,6 @@ export interface IGroupCallRoomMemberState {
"m.calls": IGroupCallRoomMemberCallState[];
}
// XXX: this hasn't made it into the MSC yet
export interface ExperimentalGroupCallRoomMemberState {
memberships: CallMembershipData[];
}
export enum GroupCallState {
LocalCallFeedUninitialized = "local_call_feed_uninitialized",
InitializingLocalCallFeed = "initializing_local_call_feed",