init
This commit is contained in:
154
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.d.ts
generated
vendored
Normal file
154
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.d.ts
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
import { type RTCCallIntent, type Transport, type SlotDescription } from "./types.ts";
|
||||
import { type MatrixEvent } from "../models/event.ts";
|
||||
import { type RtcMembershipData, type SessionMembershipData } from "./membershipData/index.ts";
|
||||
/**
|
||||
* The default duration in milliseconds that a membership is considered valid for.
|
||||
* Ordinarily the client responsible for the session will update the membership before it expires.
|
||||
* We use this duration as the fallback case where stale sessions are present for some reason.
|
||||
*/
|
||||
export declare const DEFAULT_EXPIRE_DURATION: number;
|
||||
/**
|
||||
* Describes the source event type that provided the membership data.
|
||||
*/
|
||||
declare enum MembershipKind {
|
||||
/**
|
||||
* The modern MSC4143 format event.
|
||||
*/
|
||||
RTC = "rtc",
|
||||
/**
|
||||
* The legacy call event type.
|
||||
*/
|
||||
Session = "session"
|
||||
}
|
||||
type MembershipData = {
|
||||
kind: MembershipKind.RTC;
|
||||
data: RtcMembershipData;
|
||||
} | {
|
||||
kind: MembershipKind.Session;
|
||||
data: SessionMembershipData;
|
||||
};
|
||||
type LimitedEvent = Pick<MatrixEvent, "getId" | "getSender" | "getTs" | "getType" | "getContent">;
|
||||
export declare class CallMembership {
|
||||
/** The Matrix event that this membership is based on */
|
||||
private readonly matrixEvent;
|
||||
private readonly membershipData;
|
||||
readonly rtcBackendIdentity: string;
|
||||
/**
|
||||
* Parse the membershipdata from a call membership event.
|
||||
* @param matrixEvent The Matrix event to read.
|
||||
* @returns MembershipData in either MembershipKind.RTC or MembershipKind.Session format.
|
||||
* @throws If the content is neither format.
|
||||
*/
|
||||
static membershipDataFromMatrixEvent(matrixEvent: LimitedEvent): MembershipData;
|
||||
/**
|
||||
* Parse the contents of a MatrixEvent and create a CallMembership instance.
|
||||
* @param matrixEvent The Matrix event to read.
|
||||
*/
|
||||
static parseFromEvent(matrixEvent: LimitedEvent): Promise<CallMembership>;
|
||||
static equal(a?: CallMembership, b?: CallMembership): boolean;
|
||||
private logger;
|
||||
/** The parsed data from the Matrix event.
|
||||
* To access checked eventId and sender from the matrixEvent.
|
||||
* Class construction will fail if these values cannot get obtained. */
|
||||
private readonly matrixEventData;
|
||||
/**
|
||||
* Use `parseFromEvent`.
|
||||
* Constructor should only be used by tests.
|
||||
* @private
|
||||
* @param matrixEvent
|
||||
* @param membershipData
|
||||
* @param rtcBackendIdentity
|
||||
*/
|
||||
constructor(
|
||||
/** The Matrix event that this membership is based on */
|
||||
matrixEvent: LimitedEvent, membershipData: MembershipData, rtcBackendIdentity: string);
|
||||
/** @deprecated use userId instead */
|
||||
get sender(): string;
|
||||
get userId(): string;
|
||||
get eventId(): string;
|
||||
/**
|
||||
* The ID of the MatrixRTC slot that this membership belongs to (format `{application}#{id}`).
|
||||
* This is computed in case SessionMembershipData is used.
|
||||
*/
|
||||
get slotId(): string;
|
||||
get deviceId(): string;
|
||||
get callIntent(): RTCCallIntent | undefined;
|
||||
/**
|
||||
* Parsed `slot_id` (format `{application}#{id}`) into its components (application and id).
|
||||
*/
|
||||
get slotDescription(): SlotDescription;
|
||||
/**
|
||||
* The application `type`.
|
||||
* @deprecated Use @see applicationData
|
||||
*/
|
||||
get application(): string;
|
||||
/**
|
||||
* Information about the application being used for the RTC session.
|
||||
* May contain extra keys specific to the application.
|
||||
*/
|
||||
get applicationData(): {
|
||||
type: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/** @deprecated scope is not used and will be removed in future versions. replaced by application specific types.*/
|
||||
get scope(): SessionMembershipData["scope"] | undefined;
|
||||
/**
|
||||
* This computes the membership ID for the membership.
|
||||
* For the sticky event based rtcSessionData this is trivial it is `member.id`.
|
||||
* This is not supposed to be used to identity on an rtc backend. This is just a nouance for
|
||||
* a generated (sha256) anonymised identity. Only send `rtcBackendIdentity` to any rtc backend service.
|
||||
*
|
||||
* For the legacy sessionMemberEvents it is a bit more complex. Here we sometimes do not have this data
|
||||
* in the event content and we expected the SFU and the client to use `${this.matrixEventData.sender}:${data.device_id}`.
|
||||
*
|
||||
* So if there is no membershipID we use the hard coded jwt id default (`${this.matrixEventData.sender}:${data.device_id}`)
|
||||
* value (used until version 0.16.0)
|
||||
*
|
||||
* It is also possible for a session event to set a custom membershipID. in that case this will be used.
|
||||
*/
|
||||
get memberId(): string;
|
||||
/**
|
||||
* @deprecated renamed to `memberId`
|
||||
*/
|
||||
get membershipID(): string;
|
||||
createdTs(): number;
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
getAbsoluteExpiry(): number | undefined;
|
||||
/**
|
||||
* @returns The number of milliseconds until the membership expires or undefined if applicable
|
||||
* @deprecated Not used by RTC events.
|
||||
*/
|
||||
getMsUntilExpiry(): number | undefined;
|
||||
/**
|
||||
* @returns true if the membership has expired, otherwise false
|
||||
*/
|
||||
isExpired(): boolean;
|
||||
/**
|
||||
* ## RTC Membership
|
||||
* Gets the primary transport to use for this RTC membership (m.rtc.member).
|
||||
* This will return the primary transport that is used by this call membership to publish their media.
|
||||
* Directly relates to the `transports.published` field.
|
||||
*
|
||||
* ## Legacy session membership
|
||||
* In case of a legacy session membership (m.call.member) this will return the selected transport where
|
||||
* media is published. How this selection happens depends on the `focus_active` field of the session membership.
|
||||
* If the `focus_selection` is `oldest_membership` this will return the transport of the oldest membership
|
||||
* in the room (based on the `created_ts` field of the session membership).
|
||||
* If the `focus_selection` is `multi_sfu` it will return the first transport of the `foci_preferred` list.
|
||||
* (`multi_sfu` is equivalent to how `m.rtc.member` `transports.published` work).
|
||||
* @param oldestMembership For backwards compatibility with session membership (legacy). Unused in case of RTC membership.
|
||||
* Always required to make the consumer not care if it deals with RTC or session memberships.
|
||||
* @returns The transport this membership uses to publish media or undefined if no transport is available.
|
||||
*/
|
||||
getTransport(oldestMembership: CallMembership): Transport | undefined;
|
||||
/**
|
||||
* The value of the `transports.published` field for RTC memberships (m.rtc.member).
|
||||
* Or the value of the `foci_preferred` field for legacy session memberships (m.call.member).
|
||||
*/
|
||||
get transports(): Transport[];
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=CallMembership.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CallMembership.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/CallMembership.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAGH,KAAK,iBAAiB,EAEtB,KAAK,qBAAqB,EAE7B,MAAM,2BAA2B,CAAC;AAGnC;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D;;GAEG;AACH,aAAK,cAAc;IACf;;OAEG;IACH,GAAG,QAAQ;IACX;;OAEG;IACH,OAAO,YAAY;CACtB;AAED,KAAK,cAAc,GACb;IAAE,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC;IAAC,IAAI,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEpE,KAAK,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC;AAElG,qBAAa,cAAc;IAkEnB,wDAAwD;IACxD,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,cAAc;aACf,kBAAkB,EAAE,MAAM;IApE9C;;;;;OAKG;IACH,OAAc,6BAA6B,CAAC,WAAW,EAAE,YAAY,GAAG,cAAc,CAoBrF;IAED;;;OAGG;IACH,OAAoB,cAAc,CAAC,WAAW,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAWrF;IAED,OAAc,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,EAAE,cAAc,GAAG,OAAO,CAEnE;IAED,OAAO,CAAC,MAAM,CAAS;IAEvB;;2EAEuE;IACvE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsC;IAEtE;;;;;;;OAOG;IACH;IACI,wDAAwD;IACvC,WAAW,EAAE,YAAY,EACzB,cAAc,EAAE,cAAc,EAC/B,kBAAkB,EAAE,MAAM,EAU7C;IAED,qCAAqC;IACrC,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,IAAW,MAAM,IAAI,MAAM,CAS1B;IAED,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED;;;OAGG;IACH,IAAW,MAAM,IAAI,MAAM,CAgD1B;IAED,IAAW,QAAQ,IAAI,MAAM,CAS5B;IAED,IAAW,UAAU,IAAI,aAAa,GAAG,SAAS,CAOjD;IAED;;OAEG;IACH,IAAW,eAAe,IAAI,eAAe,CAO5C;IAED;;;OAGG;IACH,IAAW,WAAW,IAAI,MAAM,CAE/B;IAED;;;OAGG;IACH,IAAW,eAAe,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAWrE;IAED,mHAAmH;IACnH,IAAW,KAAK,IAAI,qBAAqB,CAAC,OAAO,CAAC,GAAG,SAAS,CAS7D;IAED;;;;;;;;;;;;;OAaG;IACH,IAAW,QAAQ,IAAI,MAAM,CAkB5B;IAED;;OAEG;IACH,IAAW,YAAY,IAAI,MAAM,CAEhC;IAEM,SAAS,IAAI,MAAM,CAUzB;IAED;;;OAGG;IACI,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAU7C;IAED;;;OAGG;IACI,gBAAgB,IAAI,MAAM,GAAG,SAAS,CAY5C;IAED;;OAEG;IACI,SAAS,IAAI,OAAO,CAS1B;IAED;;;;;;;;;;;;;;;;OAgBG;IACI,YAAY,CAAC,gBAAgB,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,CAqB3E;IAED;;;OAGG;IACH,IAAW,UAAU,IAAI,SAAS,EAAE,CASnC;CACJ"}
|
||||
462
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.js
generated
vendored
Normal file
462
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.js
generated
vendored
Normal file
@@ -0,0 +1,462 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2023-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { deepCompare } from "../utils.js";
|
||||
import { logger } from "../logger.js";
|
||||
import { computeSlotId, slotIdToDescription } from "./utils.js";
|
||||
import { checkRtcMembershipData, computeRtcIdentityRaw, checkSessionsMembershipData, MatrixRTCMembershipParseError } from "./membershipData/index.js";
|
||||
import { EventType } from "../@types/event.js";
|
||||
|
||||
/**
|
||||
* The default duration in milliseconds that a membership is considered valid for.
|
||||
* Ordinarily the client responsible for the session will update the membership before it expires.
|
||||
* We use this duration as the fallback case where stale sessions are present for some reason.
|
||||
*/
|
||||
export const DEFAULT_EXPIRE_DURATION = 1000 * 60 * 60 * 4;
|
||||
|
||||
/**
|
||||
* Describes the source event type that provided the membership data.
|
||||
*/
|
||||
var MembershipKind = /*#__PURE__*/function (MembershipKind) {
|
||||
/**
|
||||
* The modern MSC4143 format event.
|
||||
*/
|
||||
MembershipKind["RTC"] = "rtc";
|
||||
/**
|
||||
* The legacy call event type.
|
||||
*/
|
||||
MembershipKind["Session"] = "session";
|
||||
return MembershipKind;
|
||||
}(MembershipKind || {});
|
||||
// TODO: Rename to RtcMembership once we removed the legacy SessionMembership is removed, to avoid confusion.
|
||||
export class CallMembership {
|
||||
/**
|
||||
* Parse the membershipdata from a call membership event.
|
||||
* @param matrixEvent The Matrix event to read.
|
||||
* @returns MembershipData in either MembershipKind.RTC or MembershipKind.Session format.
|
||||
* @throws If the content is neither format.
|
||||
*/
|
||||
static membershipDataFromMatrixEvent(matrixEvent) {
|
||||
const sender = matrixEvent.getSender();
|
||||
const evType = matrixEvent.getType();
|
||||
const data = matrixEvent.getContent();
|
||||
if (sender === undefined) throw new Error("matrixEvent is missing sender field");
|
||||
try {
|
||||
// Event types are strictly checked here.
|
||||
if (evType === EventType.RTCMembership && checkRtcMembershipData(data, sender)) {
|
||||
return {
|
||||
kind: MembershipKind.RTC,
|
||||
data
|
||||
};
|
||||
} else if (evType === EventType.GroupCallMemberPrefix && checkSessionsMembershipData(data)) {
|
||||
return {
|
||||
kind: MembershipKind.Session,
|
||||
data
|
||||
};
|
||||
} else {
|
||||
throw Error(`'${evType} is not a known call membership type`);
|
||||
}
|
||||
} catch (ex) {
|
||||
if (ex instanceof MatrixRTCMembershipParseError) {
|
||||
logger.debug("CallMembership.MatrixRTCMembershipParseError provided invalid data", data);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of a MatrixEvent and create a CallMembership instance.
|
||||
* @param matrixEvent The Matrix event to read.
|
||||
*/
|
||||
static async parseFromEvent(matrixEvent) {
|
||||
const membershipData = this.membershipDataFromMatrixEvent(matrixEvent);
|
||||
const rtcBackendIdentity = membershipData.kind === MembershipKind.RTC ? await computeRtcIdentityRaw(membershipData.data.member.user_id, membershipData.data.member.device_id, membershipData.data.member.id) : `${matrixEvent.getSender()}:${membershipData.data.device_id}`;
|
||||
return new CallMembership(matrixEvent, membershipData, rtcBackendIdentity);
|
||||
}
|
||||
static equal(a, b) {
|
||||
return deepCompare(a?.membershipData, b?.membershipData);
|
||||
}
|
||||
/**
|
||||
* Use `parseFromEvent`.
|
||||
* Constructor should only be used by tests.
|
||||
* @private
|
||||
* @param matrixEvent
|
||||
* @param membershipData
|
||||
* @param rtcBackendIdentity
|
||||
*/
|
||||
constructor(/** The Matrix event that this membership is based on */
|
||||
matrixEvent, membershipData, rtcBackendIdentity) {
|
||||
_defineProperty(this, "logger", void 0);
|
||||
/** The parsed data from the Matrix event.
|
||||
* To access checked eventId and sender from the matrixEvent.
|
||||
* Class construction will fail if these values cannot get obtained. */
|
||||
_defineProperty(this, "matrixEventData", void 0);
|
||||
this.matrixEvent = matrixEvent;
|
||||
this.membershipData = membershipData;
|
||||
this.rtcBackendIdentity = rtcBackendIdentity;
|
||||
const eventId = matrixEvent.getId();
|
||||
const sender = matrixEvent.getSender();
|
||||
if (eventId === undefined) throw new Error("parentEvent is missing eventId field");
|
||||
if (sender === undefined) throw new Error("parentEvent is missing sender field");
|
||||
this.logger = logger.getChild(`[CallMembership ${sender}:${this.deviceId}]`);
|
||||
this.matrixEventData = {
|
||||
eventId,
|
||||
sender
|
||||
};
|
||||
}
|
||||
|
||||
/** @deprecated use userId instead */
|
||||
get sender() {
|
||||
return this.userId;
|
||||
}
|
||||
get userId() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.member.user_id;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return this.matrixEventData.sender;
|
||||
}
|
||||
}
|
||||
get eventId() {
|
||||
return this.matrixEventData.eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the MatrixRTC slot that this membership belongs to (format `{application}#{id}`).
|
||||
* This is computed in case SessionMembershipData is used.
|
||||
*/
|
||||
get slotId() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
if (data.application === "m.call") {
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.slot_id;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
{
|
||||
const [application, id] = [data.application, data.call_id];
|
||||
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// The spec got changed to use `"ROOM"` instead of `""` empyt string for the implicit default call.
|
||||
// State events still are sent with `""` however. To find other events that should end up in the same call,
|
||||
// we use the slotId.
|
||||
// Since the CallMembership is the public representation of a rtc.member event, we just pretend it is a
|
||||
// "ROOM" slotId/call_id.
|
||||
// This makes all the remote members work with just this simple trick.
|
||||
//
|
||||
// We of course now need to be careful when sending legacy events (state events)
|
||||
// They get a slotDescription containing "ROOM" since this is what we use starting at the time this comment
|
||||
// is commited.
|
||||
//
|
||||
// See the Other INFO_SLOT_ID_LEGACY_CASE comments to see where we revert back to "" just before sending the event.
|
||||
let compatibilityAdaptedId;
|
||||
if (id === "") {
|
||||
compatibilityAdaptedId = "ROOM";
|
||||
this.logger?.info("use slotId compat hack emptyString -> ROOM");
|
||||
} else {
|
||||
compatibilityAdaptedId = id;
|
||||
}
|
||||
return computeSlotId({
|
||||
application,
|
||||
id: compatibilityAdaptedId
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger?.info("NOT using slotId compat hack emptyString -> ROOM");
|
||||
// This is what the function should look like for any other application that did not
|
||||
// go through a `""`=> `"ROOM"` rename
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.slot_id;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return computeSlotId({
|
||||
application: data.application,
|
||||
id: data.call_id
|
||||
});
|
||||
}
|
||||
}
|
||||
get deviceId() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.member.device_id;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return data.device_id;
|
||||
}
|
||||
}
|
||||
get callIntent() {
|
||||
const intent = this.applicationData["m.call.intent"];
|
||||
if (typeof intent === "string") {
|
||||
return intent;
|
||||
}
|
||||
this.logger.warn("RTC membership has invalid m.call.intent");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed `slot_id` (format `{application}#{id}`) into its components (application and id).
|
||||
*/
|
||||
get slotDescription() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
if (kind === MembershipKind.RTC) {
|
||||
const id = data.slot_id.slice(`${data.application.type}#`.length);
|
||||
return {
|
||||
application: data.application.type,
|
||||
id
|
||||
};
|
||||
}
|
||||
return slotIdToDescription(this.slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The application `type`.
|
||||
* @deprecated Use @see applicationData
|
||||
*/
|
||||
get application() {
|
||||
return this.applicationData.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the application being used for the RTC session.
|
||||
* May contain extra keys specific to the application.
|
||||
*/
|
||||
get applicationData() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.application;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
// SessionData does not have application data as such. We return specific
|
||||
// properties in use by other getters in this class, for compatibility.
|
||||
return {
|
||||
"type": data.application,
|
||||
"m.call.intent": data["m.call.intent"]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** @deprecated scope is not used and will be removed in future versions. replaced by application specific types.*/
|
||||
get scope() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return undefined;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return data.scope;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This computes the membership ID for the membership.
|
||||
* For the sticky event based rtcSessionData this is trivial it is `member.id`.
|
||||
* This is not supposed to be used to identity on an rtc backend. This is just a nouance for
|
||||
* a generated (sha256) anonymised identity. Only send `rtcBackendIdentity` to any rtc backend service.
|
||||
*
|
||||
* For the legacy sessionMemberEvents it is a bit more complex. Here we sometimes do not have this data
|
||||
* in the event content and we expected the SFU and the client to use `${this.matrixEventData.sender}:${data.device_id}`.
|
||||
*
|
||||
* So if there is no membershipID we use the hard coded jwt id default (`${this.matrixEventData.sender}:${data.device_id}`)
|
||||
* value (used until version 0.16.0)
|
||||
*
|
||||
* It is also possible for a session event to set a custom membershipID. in that case this will be used.
|
||||
*/
|
||||
get memberId() {
|
||||
// the createdTs behaves equivalent to the membershipID.
|
||||
// we only need the field for the legacy member events where we needed to update them
|
||||
// synapse ignores sending state events if they have the same content.
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case "rtc":
|
||||
return data.member.id;
|
||||
case "session":
|
||||
return (
|
||||
// best case we have a client already publishing the right custom membershipId
|
||||
data.membershipID ??
|
||||
// alternativly we use the hard coded jwt id defuatl value (used until version 0.16.0)
|
||||
`${this.matrixEventData.sender}:${data.device_id}`
|
||||
);
|
||||
default:
|
||||
throw Error("Not possible to get memberID without knowing the membership event kind");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated renamed to `memberId`
|
||||
*/
|
||||
get membershipID() {
|
||||
return this.memberId;
|
||||
}
|
||||
createdTs() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
// TODO we need to read the referenced (relation) event if available to get the real created_ts
|
||||
return this.matrixEvent.getTs();
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return data.created_ts ?? this.matrixEvent.getTs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
getAbsoluteExpiry() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return undefined;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
// TODO: calculate this from the MatrixRTCSession join configuration directly
|
||||
return this.createdTs() + (data.expires ?? DEFAULT_EXPIRE_DURATION);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The number of milliseconds until the membership expires or undefined if applicable
|
||||
* @deprecated Not used by RTC events.
|
||||
*/
|
||||
getMsUntilExpiry() {
|
||||
const {
|
||||
kind
|
||||
} = this.membershipData;
|
||||
if (kind === MembershipKind.Session) {
|
||||
const absExpiry = this.getAbsoluteExpiry();
|
||||
if (absExpiry) {
|
||||
// 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 absExpiry - Date.now();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if the membership has expired, otherwise false
|
||||
*/
|
||||
isExpired() {
|
||||
const {
|
||||
kind
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return false;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return this.getMsUntilExpiry() <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ## RTC Membership
|
||||
* Gets the primary transport to use for this RTC membership (m.rtc.member).
|
||||
* This will return the primary transport that is used by this call membership to publish their media.
|
||||
* Directly relates to the `transports.published` field.
|
||||
*
|
||||
* ## Legacy session membership
|
||||
* In case of a legacy session membership (m.call.member) this will return the selected transport where
|
||||
* media is published. How this selection happens depends on the `focus_active` field of the session membership.
|
||||
* If the `focus_selection` is `oldest_membership` this will return the transport of the oldest membership
|
||||
* in the room (based on the `created_ts` field of the session membership).
|
||||
* If the `focus_selection` is `multi_sfu` it will return the first transport of the `foci_preferred` list.
|
||||
* (`multi_sfu` is equivalent to how `m.rtc.member` `transports.published` work).
|
||||
* @param oldestMembership For backwards compatibility with session membership (legacy). Unused in case of RTC membership.
|
||||
* Always required to make the consumer not care if it deals with RTC or session memberships.
|
||||
* @returns The transport this membership uses to publish media or undefined if no transport is available.
|
||||
*/
|
||||
getTransport(oldestMembership) {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.transports.published[0];
|
||||
case MembershipKind.Session:
|
||||
switch (data.focus_active.focus_selection) {
|
||||
case "oldest_membership":
|
||||
if (CallMembership.equal(this, oldestMembership)) return data.foci_preferred[0];
|
||||
if (oldestMembership !== undefined) return oldestMembership.getTransport(oldestMembership);
|
||||
break;
|
||||
case "multi_sfu":
|
||||
return data.foci_preferred[0];
|
||||
default:
|
||||
// `focus_selection` not understood.
|
||||
return undefined;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The value of the `transports.published` field for RTC memberships (m.rtc.member).
|
||||
* Or the value of the `foci_preferred` field for legacy session memberships (m.call.member).
|
||||
*/
|
||||
get transports() {
|
||||
const {
|
||||
kind,
|
||||
data
|
||||
} = this.membershipData;
|
||||
switch (kind) {
|
||||
case MembershipKind.RTC:
|
||||
return data.transports.published;
|
||||
case MembershipKind.Session:
|
||||
default:
|
||||
return data.foci_preferred;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=CallMembership.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/CallMembership.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
44
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.d.ts
generated
vendored
Normal file
44
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.d.ts
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
import { type EncryptionConfig } from "./MatrixRTCSession.ts";
|
||||
import { type CallMembership } from "./CallMembership.ts";
|
||||
import { type EncryptionKeyMapKey } from "./types.ts";
|
||||
/**
|
||||
* The string used for the keys in the the encryption key map.
|
||||
* `@bob:examle.org:DEVICEID(UUIDRANDOM_MEMBERID_RANDOMUUID)`
|
||||
*/
|
||||
export declare function getEncryptionKeyMapKey(membership: CallMembershipIdentityParts): EncryptionKeyMapKey;
|
||||
/**
|
||||
* This interface is for testing and for making it possible to interchange the encryption manager.
|
||||
* @internal
|
||||
*/
|
||||
export interface IEncryptionManager {
|
||||
/**
|
||||
* Joins the encryption manager with the provided configuration.
|
||||
*
|
||||
* @param joinConfig - The configuration for joining encryption, or undefined
|
||||
* if no specific configuration is provided.
|
||||
*/
|
||||
join(joinConfig: EncryptionConfig | undefined): void;
|
||||
/**
|
||||
* Leaves the encryption manager, cleaning up any associated resources.
|
||||
*/
|
||||
leave(): void;
|
||||
/**
|
||||
* Called from the MatrixRTCSession when the memberships in this session updated.
|
||||
*
|
||||
* @param oldMemberships - The previous state of call memberships before the update.
|
||||
*/
|
||||
onMembershipsUpdate(oldMemberships: CallMembership[]): void;
|
||||
/**
|
||||
* Retrieves the encryption keys currently managed by the encryption manager.
|
||||
*
|
||||
* @returns A map of participant IDs to their encryption keys.
|
||||
*/
|
||||
getEncryptionKeys(): ReadonlyMap<EncryptionKeyMapKey, ReadonlyArray<{
|
||||
key: Uint8Array<ArrayBuffer>;
|
||||
keyIndex: number;
|
||||
membership: CallMembershipIdentityParts;
|
||||
rtcBackendIdentity: string;
|
||||
}>>;
|
||||
}
|
||||
export type CallMembershipIdentityParts = Pick<CallMembership, "userId" | "deviceId" | "memberId">;
|
||||
//# sourceMappingURL=EncryptionManager.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"EncryptionManager.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/EncryptionManager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,2BAA2B,GAAG,mBAAmB,CAEnG;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;OAKG;IACH,IAAI,CAAC,UAAU,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAAC;IAErD;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IAEd;;;;OAIG;IACH,mBAAmB,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAE5D;;;;OAIG;IACH,iBAAiB,IAAI,WAAW,CAC5B,mBAAmB,EACnB,aAAa,CAAC;QACV,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,2BAA2B,CAAC;QACxC,kBAAkB,EAAE,MAAM,CAAC;KAC9B,CAAC,CACL,CAAC;CACL;AAED,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC"}
|
||||
13
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.js
generated
vendored
Normal file
13
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* The string used for the keys in the the encryption key map.
|
||||
* `@bob:examle.org:DEVICEID(UUIDRANDOM_MEMBERID_RANDOMUUID)`
|
||||
*/
|
||||
export function getEncryptionKeyMapKey(membership) {
|
||||
return `${membership.userId}:${membership.deviceId}(${membership.memberId})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interface is for testing and for making it possible to interchange the encryption manager.
|
||||
* @internal
|
||||
*/
|
||||
//# sourceMappingURL=EncryptionManager.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/EncryptionManager.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"EncryptionManager.js","names":[],"sources":["../../src/matrixrtc/EncryptionManager.ts"],"sourcesContent":["import { type EncryptionConfig } from \"./MatrixRTCSession.ts\";\nimport { type CallMembership } from \"./CallMembership.ts\";\nimport { type EncryptionKeyMapKey } from \"./types.ts\";\n\n/**\n * The string used for the keys in the the encryption key map.\n * `@bob:examle.org:DEVICEID(UUIDRANDOM_MEMBERID_RANDOMUUID)`\n */\nexport function getEncryptionKeyMapKey(membership: CallMembershipIdentityParts): EncryptionKeyMapKey {\n return `${membership.userId}:${membership.deviceId}(${membership.memberId})`;\n}\n\n/**\n * This interface is for testing and for making it possible to interchange the encryption manager.\n * @internal\n */\nexport interface IEncryptionManager {\n /**\n * Joins the encryption manager with the provided configuration.\n *\n * @param joinConfig - The configuration for joining encryption, or undefined\n * if no specific configuration is provided.\n */\n join(joinConfig: EncryptionConfig | undefined): void;\n\n /**\n * Leaves the encryption manager, cleaning up any associated resources.\n */\n leave(): void;\n\n /**\n * Called from the MatrixRTCSession when the memberships in this session updated.\n *\n * @param oldMemberships - The previous state of call memberships before the update.\n */\n onMembershipsUpdate(oldMemberships: CallMembership[]): void;\n\n /**\n * Retrieves the encryption keys currently managed by the encryption manager.\n *\n * @returns A map of participant IDs to their encryption keys.\n */\n getEncryptionKeys(): ReadonlyMap<\n EncryptionKeyMapKey,\n ReadonlyArray<{\n key: Uint8Array<ArrayBuffer>;\n keyIndex: number;\n membership: CallMembershipIdentityParts;\n rtcBackendIdentity: string;\n }>\n >;\n}\n\nexport type CallMembershipIdentityParts = Pick<CallMembership, \"userId\" | \"deviceId\" | \"memberId\">;\n"],"mappings":"AAIA;AACA;AACA;AACA;AACA,OAAO,SAAS,sBAAsB,CAAC,UAAuC,EAAuB;EACjG,OAAO,GAAG,UAAU,CAAC,MAAM,IAAI,UAAU,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ,GAAG;AAChF;;AAEA;AACA;AACA;AACA","ignoreList":[]}
|
||||
37
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.d.ts
generated
vendored
Normal file
37
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.d.ts
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
|
||||
import { type ParticipantDeviceInfo } from "./types.ts";
|
||||
export declare enum KeyTransportEvents {
|
||||
ReceivedKeys = "received_keys",
|
||||
NotSupportedError = "not_supported_error"
|
||||
}
|
||||
export type KeyTransportEventsHandlerMap = {
|
||||
[KeyTransportEvents.ReceivedKeys]: KeyTransportEventListener;
|
||||
[KeyTransportEvents.NotSupportedError]: () => void;
|
||||
};
|
||||
export type KeyTransportEventListener = (membership: CallMembershipIdentityParts, keyBase64Encoded: string, index: number, timestamp: number) => void;
|
||||
/**
|
||||
* Generic interface for the transport used to share room keys.
|
||||
* Keys can be shared using different transports, e.g. to-device messages or room messages.
|
||||
*/
|
||||
export interface IKeyTransport {
|
||||
/**
|
||||
* Sends the current user media key to the given members.
|
||||
* @param keyBase64Encoded
|
||||
* @param index
|
||||
* @param members - The participants that should get they key
|
||||
*/
|
||||
sendKey(keyBase64Encoded: string, index: number, members: ParticipantDeviceInfo[]): Promise<void>;
|
||||
/** Subscribe to keys from this transport. */
|
||||
on(event: KeyTransportEvents.ReceivedKeys, listener: KeyTransportEventListener): this;
|
||||
/** Unsubscribe from keys from this transport. */
|
||||
off(event: KeyTransportEvents.ReceivedKeys, listener: KeyTransportEventListener): this;
|
||||
/** Once start is called the underlying transport will subscribe to its transport system.
|
||||
* Before start is called this transport will not emit any events.
|
||||
*/
|
||||
start(): void;
|
||||
/** Once stop is called the underlying transport will unsubscribe from its transport system.
|
||||
* After stop is called this transport will not emit any events.
|
||||
*/
|
||||
stop(): void;
|
||||
}
|
||||
//# sourceMappingURL=IKeyTransport.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"IKeyTransport.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/IKeyTransport.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExD,oBAAY,kBAAkB;IAC1B,YAAY,kBAAkB;IAC9B,iBAAiB,wBAAwB;CAC5C;AAED,MAAM,MAAM,4BAA4B,GAAG;IACvC,CAAC,kBAAkB,CAAC,YAAY,CAAC,EAAE,yBAAyB,CAAC;IAC7D,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CACtD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CACpC,UAAU,EAAE,2BAA2B,EACvC,gBAAgB,EAAE,MAAM,EACxB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,KAChB,IAAI,CAAC;AAEV;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElG,6CAA6C;IAC7C,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,YAAY,EAAE,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACtF,iDAAiD;IACjD,GAAG,CAAC,KAAK,EAAE,kBAAkB,CAAC,YAAY,EAAE,QAAQ,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAEvF;;OAEG;IACH,KAAK,IAAI,IAAI,CAAC;IACd;;OAEG;IACH,IAAI,IAAI,IAAI,CAAC;CAChB"}
|
||||
27
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.js
generated
vendored
Normal file
27
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.js
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export let KeyTransportEvents = /*#__PURE__*/function (KeyTransportEvents) {
|
||||
KeyTransportEvents["ReceivedKeys"] = "received_keys";
|
||||
KeyTransportEvents["NotSupportedError"] = "not_supported_error";
|
||||
return KeyTransportEvents;
|
||||
}({});
|
||||
|
||||
/**
|
||||
* Generic interface for the transport used to share room keys.
|
||||
* Keys can be shared using different transports, e.g. to-device messages or room messages.
|
||||
*/
|
||||
//# sourceMappingURL=IKeyTransport.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/IKeyTransport.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"IKeyTransport.js","names":[],"sources":["../../src/matrixrtc/IKeyTransport.ts"],"sourcesContent":["/*\nCopyright 2025 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type CallMembershipIdentityParts } from \"./EncryptionManager.ts\";\nimport { type ParticipantDeviceInfo } from \"./types.ts\";\n\nexport enum KeyTransportEvents {\n ReceivedKeys = \"received_keys\",\n NotSupportedError = \"not_supported_error\",\n}\n\nexport type KeyTransportEventsHandlerMap = {\n [KeyTransportEvents.ReceivedKeys]: KeyTransportEventListener;\n [KeyTransportEvents.NotSupportedError]: () => void;\n};\n\nexport type KeyTransportEventListener = (\n membership: CallMembershipIdentityParts,\n keyBase64Encoded: string,\n index: number,\n timestamp: number,\n) => void;\n\n/**\n * Generic interface for the transport used to share room keys.\n * Keys can be shared using different transports, e.g. to-device messages or room messages.\n */\nexport interface IKeyTransport {\n /**\n * Sends the current user media key to the given members.\n * @param keyBase64Encoded\n * @param index\n * @param members - The participants that should get they key\n */\n sendKey(keyBase64Encoded: string, index: number, members: ParticipantDeviceInfo[]): Promise<void>;\n\n /** Subscribe to keys from this transport. */\n on(event: KeyTransportEvents.ReceivedKeys, listener: KeyTransportEventListener): this;\n /** Unsubscribe from keys from this transport. */\n off(event: KeyTransportEvents.ReceivedKeys, listener: KeyTransportEventListener): this;\n\n /** Once start is called the underlying transport will subscribe to its transport system.\n * Before start is called this transport will not emit any events.\n */\n start(): void;\n /** Once stop is called the underlying transport will unsubscribe from its transport system.\n * After stop is called this transport will not emit any events.\n */\n stop(): void;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAKA,WAAY,kBAAkB,0BAAlB,kBAAkB;EAAlB,kBAAkB;EAAlB,kBAAkB;EAAA,OAAlB,kBAAkB;AAAA;;AAiB9B;AACA;AACA;AACA","ignoreList":[]}
|
||||
94
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.d.ts
generated
vendored
Normal file
94
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.d.ts
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { CallMembership } from "./CallMembership.ts";
|
||||
import type { RTCCallIntent, Status, Transport } from "./types.ts";
|
||||
import { type TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
export declare enum MembershipManagerEvent {
|
||||
StatusChanged = "StatusChanged",
|
||||
/**
|
||||
* Emitted when the membership manager has not heard back from the server for the duration
|
||||
* of the delayed event and hence failed to restart the delayed event.
|
||||
* This means that the user is probably not joined anymore and the leave event was distributed to other session members.
|
||||
*/
|
||||
ProbablyLeft = "ProbablyLeft",
|
||||
/**
|
||||
* Once the membershipManger has aquired the a delay id (after sending the state event)
|
||||
* It will emit and share the delay id.
|
||||
*/
|
||||
DelayIdChanged = "DelayIdChanged"
|
||||
}
|
||||
export type MembershipManagerEventHandlerMap = {
|
||||
[MembershipManagerEvent.StatusChanged]: (prefStatus: Status, newStatus: Status) => void;
|
||||
[MembershipManagerEvent.ProbablyLeft]: (probablyLeft: boolean) => void;
|
||||
[MembershipManagerEvent.DelayIdChanged]: (delayId: string | undefined) => void;
|
||||
};
|
||||
/**
|
||||
* This interface defines what a MembershipManager uses and exposes.
|
||||
* This interface is what we use to write tests and allows changing the actual implementation
|
||||
* without breaking tests because of some internal method renaming.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export interface IMembershipManager extends TypedEventEmitter<MembershipManagerEvent, MembershipManagerEventHandlerMap> {
|
||||
/**
|
||||
* If we are trying to join, or have successfully joined the session.
|
||||
* It does not reflect if the room state is already configured to represent us being joined.
|
||||
* It only means that the Manager should be trying to connect or to disconnect running.
|
||||
* The Manager is still running right after isJoined becomes false to send the disconnect events.
|
||||
* @returns true if we intend to be participating in the MatrixRTC session
|
||||
* @deprecated This name is confusing and replaced by `isActivated()`. (Returns the same as `isActivated()`)
|
||||
*/
|
||||
isJoined(): boolean;
|
||||
/**
|
||||
* If the manager is activated. This means it tries to do its job to join the call, resend state events...
|
||||
* It does not imply that the room state is already configured to represent being joined.
|
||||
* It means that the Manager tries to connect or is connected. ("the manager is still active")
|
||||
* Once `leave()` is called the manager is not activated anymore but still running until `leave()` resolves.
|
||||
* @returns `true` if we intend to be participating in the MatrixRTC session
|
||||
*/
|
||||
isActivated(): boolean;
|
||||
/**
|
||||
* Get the actual connection status of the manager.
|
||||
*/
|
||||
get status(): Status;
|
||||
/**
|
||||
* The Current own state event if the manger is connected.
|
||||
* `undefined` if not connected.
|
||||
*/
|
||||
get ownMembership(): CallMembership | undefined;
|
||||
/**
|
||||
* If the membership manager has reason to believe that the hs sent a leave event
|
||||
* and as a consequence the current user is perceived as left for other session participants.
|
||||
*/
|
||||
get probablyLeft(): boolean;
|
||||
/**
|
||||
* If the membership manager has reason to believe that the hs sent a leave event
|
||||
* and as a consequence the current user is perceived as left for other session participants.
|
||||
*/
|
||||
get delayId(): string | undefined;
|
||||
/**
|
||||
* Start sending all necessary events to make this user participate in the RTC session.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.
|
||||
* @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the
|
||||
* membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`
|
||||
* transport selection will be used instead.
|
||||
* @throws can throw if it exceeds a configured maximum retry.
|
||||
*/
|
||||
join(fociPreferred: Transport[], multiSfuFocus?: Transport, onError?: (error: unknown) => void): void;
|
||||
/**
|
||||
* Send all necessary events to make this user leave the RTC session.
|
||||
* @param timeout the maximum duration in ms until the promise is forced to resolve.
|
||||
* @returns It resolves with true in case the leave was sent successfully.
|
||||
* It resolves with false in case we hit the timeout before sending successfully.
|
||||
*/
|
||||
leave(timeout?: number): Promise<boolean>;
|
||||
/**
|
||||
* Call this if the MatrixRTC session members have changed.
|
||||
*/
|
||||
onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
|
||||
/**
|
||||
* Update the intent of a membership on the call (e.g. user is now providing a video feed)
|
||||
* @param callIntent The new intent to set.
|
||||
*/
|
||||
updateCallIntent(callIntent: RTCCallIntent): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=IMembershipManager.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"IMembershipManager.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/IMembershipManager.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,oBAAY,sBAAsB;IAC9B,aAAa,kBAAkB;IAC/B;;;;OAIG;IACH,YAAY,iBAAiB;IAC7B;;;OAGG;IACH,cAAc,mBAAmB;CACpC;AAED,MAAM,MAAM,gCAAgC,GAAG;IAC3C,CAAC,sBAAsB,CAAC,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxF,CAAC,sBAAsB,CAAC,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IACvE,CAAC,sBAAsB,CAAC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAClF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB,CACzD,sBAAsB,EACtB,gCAAgC,CACnC;IACG;;;;;;;OAOG;IACH,QAAQ,IAAI,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,WAAW,IAAI,OAAO,CAAC;IACvB;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAAC;IAErB;;;OAGG;IACH,IAAI,aAAa,IAAI,cAAc,GAAG,SAAS,CAAC;IAEhD;;;OAGG;IACH,IAAI,YAAY,IAAI,OAAO,CAAC;IAE5B;;;OAGG;IACH,IAAI,OAAO,IAAI,MAAM,GAAG,SAAS,CAAC;IAElC;;;;;;;;OAQG;IACH,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,aAAa,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;IACtG;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C;;OAEG;IACH,wBAAwB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvE;;;OAGG;IACH,gBAAgB,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D"}
|
||||
40
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.js
generated
vendored
Normal file
40
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.js
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export let MembershipManagerEvent = /*#__PURE__*/function (MembershipManagerEvent) {
|
||||
MembershipManagerEvent["StatusChanged"] = "StatusChanged";
|
||||
/**
|
||||
* Emitted when the membership manager has not heard back from the server for the duration
|
||||
* of the delayed event and hence failed to restart the delayed event.
|
||||
* This means that the user is probably not joined anymore and the leave event was distributed to other session members.
|
||||
*/
|
||||
MembershipManagerEvent["ProbablyLeft"] = "ProbablyLeft";
|
||||
/**
|
||||
* Once the membershipManger has aquired the a delay id (after sending the state event)
|
||||
* It will emit and share the delay id.
|
||||
*/
|
||||
MembershipManagerEvent["DelayIdChanged"] = "DelayIdChanged";
|
||||
return MembershipManagerEvent;
|
||||
}({});
|
||||
|
||||
/**
|
||||
* This interface defines what a MembershipManager uses and exposes.
|
||||
* This interface is what we use to write tests and allows changing the actual implementation
|
||||
* without breaking tests because of some internal method renaming.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
//# sourceMappingURL=IMembershipManager.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/IMembershipManager.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
23
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.d.ts
generated
vendored
Normal file
23
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.d.ts
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type Transport } from "./types.ts";
|
||||
export interface LivekitTransportConfig extends Transport {
|
||||
type: "livekit";
|
||||
livekit_service_url: string;
|
||||
}
|
||||
export declare const isLivekitTransportConfig: (object: any) => object is LivekitTransportConfig;
|
||||
export interface LivekitTransport extends LivekitTransportConfig {
|
||||
livekit_alias: string;
|
||||
}
|
||||
export declare const isLivekitTransport: (object: any) => object is LivekitTransport;
|
||||
/**
|
||||
* @deprecated this is just needed for the old focus active / focus fields of a call membership.
|
||||
* Not needed for new implementations.
|
||||
*/
|
||||
export interface LivekitFocusSelection extends Transport {
|
||||
type: "livekit";
|
||||
focus_selection: "oldest_membership" | "multi_sfu";
|
||||
}
|
||||
/**
|
||||
* @deprecated see LivekitFocusSelection
|
||||
*/
|
||||
export declare const isLivekitFocusSelection: (object: any) => object is LivekitFocusSelection;
|
||||
//# sourceMappingURL=LivekitTransport.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"LivekitTransport.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/LivekitTransport.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,sBAAuB,SAAQ,SAAS;IACrD,IAAI,EAAE,SAAS,CAAC;IAChB,mBAAmB,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,wBAAwB,WAAY,GAAG,KAAG,MAAM,IAAI,sBACD,CAAC;AAEjE,MAAM,WAAW,gBAAiB,SAAQ,sBAAsB;IAC5D,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,WAAY,GAAG,KAAG,MAAM,IAAI,gBACM,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,qBAAsB,SAAQ,SAAS;IACpD,IAAI,EAAE,SAAS,CAAC;IAChB,eAAe,EAAE,mBAAmB,GAAG,WAAW,CAAC;CACtD;AACD;;GAEG;AACH,eAAO,MAAM,uBAAuB,WAAY,GAAG,KAAG,MAAM,IAAI,qBACJ,CAAC"}
|
||||
29
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.js
generated
vendored
Normal file
29
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export const isLivekitTransportConfig = object => object.type === "livekit" && "livekit_service_url" in object;
|
||||
export const isLivekitTransport = object => isLivekitTransportConfig(object) && "livekit_alias" in object;
|
||||
|
||||
/**
|
||||
* @deprecated this is just needed for the old focus active / focus fields of a call membership.
|
||||
* Not needed for new implementations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated see LivekitFocusSelection
|
||||
*/
|
||||
export const isLivekitFocusSelection = object => object.type === "livekit" && "focus_selection" in object;
|
||||
//# sourceMappingURL=LivekitTransport.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/LivekitTransport.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"LivekitTransport.js","names":[],"sources":["../../src/matrixrtc/LivekitTransport.ts"],"sourcesContent":["/*\nCopyright 2025 New Vector Ltd\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { type Transport } from \"./types.ts\";\n\nexport interface LivekitTransportConfig extends Transport {\n type: \"livekit\";\n livekit_service_url: string;\n}\n\nexport const isLivekitTransportConfig = (object: any): object is LivekitTransportConfig =>\n object.type === \"livekit\" && \"livekit_service_url\" in object;\n\nexport interface LivekitTransport extends LivekitTransportConfig {\n livekit_alias: string;\n}\n\nexport const isLivekitTransport = (object: any): object is LivekitTransport =>\n isLivekitTransportConfig(object) && \"livekit_alias\" in object;\n\n/**\n * @deprecated this is just needed for the old focus active / focus fields of a call membership.\n * Not needed for new implementations.\n */\nexport interface LivekitFocusSelection extends Transport {\n type: \"livekit\";\n focus_selection: \"oldest_membership\" | \"multi_sfu\";\n}\n/**\n * @deprecated see LivekitFocusSelection\n */\nexport const isLivekitFocusSelection = (object: any): object is LivekitFocusSelection =>\n object.type === \"livekit\" && \"focus_selection\" in object;\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AASA,OAAO,MAAM,wBAAwB,GAAI,MAAW,IAChD,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,qBAAqB,IAAI,MAAM;AAMhE,OAAO,MAAM,kBAAkB,GAAI,MAAW,IAC1C,wBAAwB,CAAC,MAAM,CAAC,IAAI,eAAe,IAAI,MAAM;;AAEjE;AACA;AACA;AACA;;AAKA;AACA;AACA;AACA,OAAO,MAAM,uBAAuB,GAAI,MAAW,IAC/C,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,iBAAiB,IAAI,MAAM","ignoreList":[]}
|
||||
365
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.d.ts
generated
vendored
Normal file
365
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.d.ts
generated
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { type Room } from "../models/room.ts";
|
||||
import { type MatrixClient } from "../client.ts";
|
||||
import { CallMembership } from "./CallMembership.ts";
|
||||
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
|
||||
import type { Statistics, RTCNotificationType, Status, IRTCNotificationContent, RTCCallIntent, Transport, SlotDescription, RtcSlotEventContent } from "./types.ts";
|
||||
import { MembershipManagerEvent, type MembershipManagerEventHandlerMap } from "./IMembershipManager.ts";
|
||||
/**
|
||||
* Events emitted by MatrixRTCSession
|
||||
*/
|
||||
export declare enum MatrixRTCSessionEvent {
|
||||
MembershipsChanged = "memberships_changed",
|
||||
JoinStateChanged = "join_state_changed",
|
||||
EncryptionKeyChanged = "encryption_key_changed",
|
||||
/** The membership manager had to shut down caused by an unrecoverable error */
|
||||
MembershipManagerError = "membership_manager_error",
|
||||
/** The RTCSession did send a call notification caused by joining the call as the first member */
|
||||
DidSendCallNotification = "did_send_call_notification"
|
||||
}
|
||||
export type MatrixRTCSessionEventHandlerMap = {
|
||||
[MatrixRTCSessionEvent.MembershipsChanged]: (oldMemberships: CallMembership[], newMemberships: CallMembership[]) => void;
|
||||
[MatrixRTCSessionEvent.JoinStateChanged]: (isJoined: boolean) => void;
|
||||
[MatrixRTCSessionEvent.EncryptionKeyChanged]: (key: Uint8Array<ArrayBuffer>, encryptionKeyIndex: number, membership: CallMembershipIdentityParts, rtcBackendIdentity: string) => void;
|
||||
[MatrixRTCSessionEvent.MembershipManagerError]: (error: unknown) => void;
|
||||
[MatrixRTCSessionEvent.DidSendCallNotification]: (notificationContentNew: {
|
||||
event_id: string;
|
||||
} & IRTCNotificationContent) => void;
|
||||
};
|
||||
export interface SessionConfig {
|
||||
/**
|
||||
* What kind of notification to send when starting the session.
|
||||
* @default `undefined` (no notification)
|
||||
*/
|
||||
notificationType?: RTCNotificationType;
|
||||
/**
|
||||
* Determines the kind of call this will be.
|
||||
*/
|
||||
callIntent?: RTCCallIntent;
|
||||
/**
|
||||
* How long (in milliseconds) the callee's client should keep ringing/waiting for an
|
||||
* answer before the sender gives up and the call notification is considered timed out.
|
||||
*/
|
||||
notificationLifetimeMs?: number;
|
||||
}
|
||||
export interface MembershipConfig {
|
||||
/**
|
||||
* The timeout (in milliseconds) after we joined the call, that our membership should expire
|
||||
* unless we have explicitly updated it.
|
||||
*
|
||||
* This is what goes into the m.rtc.member event expiry field and is typically set to a number of hours.
|
||||
*/
|
||||
membershipEventExpiryMs?: number;
|
||||
/**
|
||||
* The time in (in milliseconds) which the manager will prematurely send the updated state event before the membership `expires` time to make sure it
|
||||
* sends the updated state event early enough.
|
||||
*
|
||||
* A headroom of 1000ms and a `membershipExpiryTimeout` of 10000ms would result in the first membership event update after 9s and
|
||||
* a membership event that would be considered expired after 10s.
|
||||
*
|
||||
* This value does not have an effect on the value of `SessionMembershipData.expires`.
|
||||
*/
|
||||
membershipEventExpiryHeadroomMs?: number;
|
||||
/**
|
||||
* The timeout (in milliseconds) with which the deleayed leave event on the server is configured.
|
||||
* After this time the server will set the event to the disconnected stat if it has not received a keep-alive from the client.
|
||||
*/
|
||||
delayedLeaveEventDelayMs?: number;
|
||||
/**
|
||||
* The interval (in milliseconds) in which the client will send membership keep-alives to the server.
|
||||
*/
|
||||
delayedLeaveEventRestartMs?: number;
|
||||
/**
|
||||
* The maximum number of retries that the manager will do for delayed event sending/updating and state event sending when a server rate limit has been hit.
|
||||
*/
|
||||
maximumRateLimitRetryCount?: number;
|
||||
/**
|
||||
* The maximum number of retries that the manager will do for delayed event sending/updating and state event sending when a network error occurs.
|
||||
*/
|
||||
maximumNetworkErrorRetryCount?: number;
|
||||
/**
|
||||
* The time (in milliseconds) after which we will retry a http request if it
|
||||
* failed to send due to a network error. (send membership event, send delayed event, restart delayed event...)
|
||||
*/
|
||||
networkErrorRetryMs?: number;
|
||||
/**
|
||||
* The time (in milliseconds) after which a we consider a delayed event restart http request to have failed.
|
||||
* Setting this to a lower value will result in more frequent retries but also a higher chance of failiour.
|
||||
*
|
||||
* In the presence of network packet loss (hurting TCP connections), the custom delayedEventRestartLocalTimeoutMs
|
||||
* helps by keeping more delayed event reset candidates in flight,
|
||||
* improving the chances of a successful reset. (its is equivalent to the js-sdk `localTimeout` configuration,
|
||||
* but only applies to calls to the `_unstable_restartScheduledDelayedEvent` endpoint
|
||||
* or the `_unstable_updateDelayedEvent` endpoint with a body of `{action:"restart"}`.)
|
||||
*/
|
||||
delayedLeaveEventRestartLocalTimeoutMs?: number;
|
||||
/**
|
||||
* Send membership using sticky events rather than state events.
|
||||
* This also make the client use the new m.rtc.member MSC4354 event format. (instead of m.call.member)
|
||||
*
|
||||
* **WARNING**: This is an unstable feature and not all clients will support it.
|
||||
*/
|
||||
unstableSendStickyEvents?: boolean;
|
||||
}
|
||||
export interface EncryptionConfig {
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated - Not used by the new encryption manager.
|
||||
*/
|
||||
updateEncryptionKeyThrottle?: number;
|
||||
/**
|
||||
* Sometimes it is necessary to rotate the encryption key after a membership update.
|
||||
* For performance reasons we might not want to rotate the key immediately but allow future memberships to use the same key.
|
||||
* If 5 people join in a row in less than 5 seconds, we don't want to rotate the key for each of them.
|
||||
* If 5 people leave in a row in less than 5 seconds, we don't want to rotate the key for each of them.
|
||||
* So we do share the key which was already used live for <5s to new joiners.
|
||||
* This does result in a potential leak up to the configured time of call media.
|
||||
* This has to be considered when choosing a value for this property.
|
||||
*/
|
||||
keyRotationGracePeriodMs?: 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.
|
||||
* @deprecated - Not used by the new encryption manager.
|
||||
*/
|
||||
makeKeyDelay?: number;
|
||||
/**
|
||||
* The delay (in milliseconds) between sending a new key and starting to encrypt with it. This
|
||||
* gives others a chance to receive the new key to minimize the chance they get media they can't decrypt.
|
||||
*
|
||||
* The higher this value is, the better it is for existing members as they will have a smoother experience.
|
||||
* But it impacts new joiners: They will always have to wait `useKeyDelay` before being able to decrypt the media
|
||||
* (as it will be encrypted with the new key after the delay only), even if the key has already arrived before the delay.
|
||||
*/
|
||||
useKeyDelay?: number;
|
||||
}
|
||||
export type JoinSessionConfig = SessionConfig & MembershipConfig & EncryptionConfig;
|
||||
interface SessionMembershipsForSlotOpts {
|
||||
/**
|
||||
* Listen for incoming sticky member events. If disabled, this session will
|
||||
* ignore any incoming sticky events.
|
||||
*/
|
||||
listenForStickyEvents: boolean;
|
||||
/**
|
||||
* Listen for incoming member state events (legacy). If disabled, this session will
|
||||
* ignore any incoming state events.
|
||||
*/
|
||||
listenForMemberStateEvents: boolean;
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export declare class MatrixRTCSession extends TypedEventEmitter<MatrixRTCSessionEvent | MembershipManagerEvent, MatrixRTCSessionEventHandlerMap & MembershipManagerEventHandlerMap> {
|
||||
private readonly client;
|
||||
private roomSubset;
|
||||
readonly slotDescription: SlotDescription;
|
||||
private readonly calculateMembershipsOpts?;
|
||||
private membershipManager?;
|
||||
private encryptionManager?;
|
||||
private joinConfig?;
|
||||
private logger;
|
||||
private pendingNotificationToSend;
|
||||
/**
|
||||
* This timeout is responsible to track any expiration. We need to know when we have to start
|
||||
* to ignore other call members. There is no callback for this. This timeout will always be configured to
|
||||
* emit when the next membership expires.
|
||||
*/
|
||||
private expiryTimeout?;
|
||||
memberships: CallMembership[];
|
||||
/**
|
||||
* Resolves when the session has calculated the initial membership of the session.
|
||||
*/
|
||||
readonly initialMembershipCalculated: Promise<void>;
|
||||
/**
|
||||
* Does membership need to be recalculated? This is set to false upon
|
||||
* recalculation.
|
||||
*/
|
||||
private membershipNeedsRecalculation;
|
||||
/**
|
||||
* The statistics for this session.
|
||||
*/
|
||||
statistics: Statistics;
|
||||
get membershipStatus(): Status | undefined;
|
||||
get probablyLeft(): boolean | undefined;
|
||||
get delayId(): string | undefined;
|
||||
/**
|
||||
* The callId (sessionId) of the call.
|
||||
*
|
||||
* It can be undefined since the callId is only known once the first membership joins.
|
||||
* The callId is the property that, per definition, groups memberships into one call.
|
||||
* @deprecated use `slotId` instead.
|
||||
*/
|
||||
get callId(): string | undefined;
|
||||
/**
|
||||
* The slotId of the call.
|
||||
* `{application}#{appSpecificId}`
|
||||
* It can be undefined since the slotId is only known once the first membership joins.
|
||||
* The slotId is the property that, per definition, groups memberships into one call.
|
||||
*/
|
||||
get slotId(): string | undefined;
|
||||
/**
|
||||
* Reads the current slot state event's content for this session.
|
||||
*
|
||||
* @returns The slot event's content, or `undefined` if no slot event exists for this session.
|
||||
*/
|
||||
getRtcSlot(): RtcSlotEventContent | undefined;
|
||||
/**
|
||||
* Whether this session's slot is closed.
|
||||
*
|
||||
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
||||
* if no slot exists.
|
||||
*/
|
||||
isSlotClosed(): boolean | undefined;
|
||||
/**
|
||||
* Returns all the call memberships for a room that match the provided `sessionDescription`,
|
||||
* oldest first.
|
||||
*
|
||||
* By default, this will return *both* sticky and member state events.
|
||||
*/
|
||||
static sessionMembershipsForSlot(room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "_unstable_getStickyEvents">, slotDescription: SlotDescription, options?: SessionMembershipsForSlotOpts): Promise<CallMembership[]>;
|
||||
/**
|
||||
* Return the MatrixRTC session for the room.
|
||||
* This returned session can be used to find out if there are active sessions
|
||||
* for the requested room and `slotDescription`.
|
||||
*/
|
||||
static sessionForSlot(client: MatrixClient, room: Room, slotDescription: SlotDescription, opts?: SessionMembershipsForSlotOpts): MatrixRTCSession;
|
||||
/**
|
||||
* WARN: this can in theory only be a subset of the room with the properties required by
|
||||
* this class.
|
||||
* Outside of tests this most likely will be a full room, however.
|
||||
* @deprecated Relying on a full Room object being available here is an anti-pattern. You should be tracking
|
||||
* the room object in your own code and passing it in when needed.
|
||||
*/
|
||||
get room(): Room;
|
||||
/**
|
||||
* This constructs a room session. When using MatrixRTC inside the js-sdk this is expected
|
||||
* to be used with the MatrixRTCSessionManager exclusively.
|
||||
*
|
||||
* In cases where you don't use the js-sdk but build on top of another Matrix stack this class can be used standalone
|
||||
* to manage a joined MatrixRTC session.
|
||||
*
|
||||
* @param client A subset of the {@link MatrixClient} that lets the session interact with the Matrix room.
|
||||
* @param roomSubset The room this session is attached to. A subset of a js-sdk Room that the session needs.
|
||||
* @param slotDescription The slot description is a virtual address where participants are allowed to meet.
|
||||
* This session will only manage memberships that match this slot description.Sessions are distinct if any of
|
||||
* those properties are distinct: `roomSubset.roomId`, `slotDescription.application`, `slotDescription.id`.
|
||||
* @param calculateMembershipsOpts - Options to configure how memberships are calculated for this session.
|
||||
*/
|
||||
constructor(client: Pick<MatrixClient, "getUserId" | "getDeviceId" | "sendEvent" | "sendStateEvent" | "_unstable_sendDelayedStateEvent" | "_unstable_updateDelayedEvent" | "_unstable_cancelScheduledDelayedEvent" | "_unstable_restartScheduledDelayedEvent" | "_unstable_sendScheduledDelayedEvent" | "_unstable_sendStickyEvent" | "_unstable_sendStickyDelayedEvent" | "cancelPendingEvent" | "encryptAndSendToDevice" | "off" | "on" | "decryptEventIfNeeded">, roomSubset: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion" | "hasMembershipState" | "on" | "off" | "_unstable_getStickyEvents">, slotDescription: SlotDescription, calculateMembershipsOpts?: SessionMembershipsForSlotOpts | undefined);
|
||||
isJoined(): boolean;
|
||||
/**
|
||||
* Performs cleanup & removes timers for client shutdown
|
||||
*/
|
||||
stop(): Promise<void>;
|
||||
private reEmitter;
|
||||
/**
|
||||
* Announces this user and device as joined to the MatrixRTC session,
|
||||
* and continues to update the membership event to keep it valid until
|
||||
* leaveRoomSession() is called
|
||||
* This will not subscribe to updates: remember to call subscribe() separately if
|
||||
* desired.
|
||||
* This method will return immediately and the session will be joined in the background.
|
||||
* @param ownMembershipIdentity the identity of the user and device joining the session.
|
||||
* This will be put into the content.member.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.
|
||||
* @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the
|
||||
* membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`
|
||||
* transport selection will be used instead.
|
||||
* @param joinConfig - Additional configuration for the joined session.
|
||||
*/
|
||||
joinRTCSession(ownMembershipIdentity: CallMembershipIdentityParts, fociPreferred: Transport[], multiSfuFocus?: Transport, joinConfig?: JoinSessionConfig): void;
|
||||
/**
|
||||
*
|
||||
* @param fociPreferred
|
||||
* @param multiSfuFocus
|
||||
* @param joinConfig
|
||||
* @deprecated use the joinRTCSession method instead
|
||||
*/
|
||||
joinRoomSession(fociPreferred: Transport[], multiSfuFocus?: Transport, joinConfig?: JoinSessionConfig): void;
|
||||
/**
|
||||
* Announces this user and device as having left the MatrixRTC session
|
||||
* and stops scheduled updates.
|
||||
* This will not unsubscribe from updates: remember to call unsubscribe() separately if
|
||||
* desired.
|
||||
* The membership update required to leave the session will retry if it fails.
|
||||
* Without network connection the promise will never resolve.
|
||||
* A timeout can be provided so that there is a guarantee for the promise to resolve.
|
||||
* @returns Whether the membership update was attempted and did not time out.
|
||||
*/
|
||||
leaveRoomSession(timeout?: number | undefined): Promise<boolean>;
|
||||
/**
|
||||
* This returns the focus in use by the oldest membership.
|
||||
* Do not use since this might be just the focus for the oldest membership. others might use a different focus.
|
||||
* @deprecated use `member.getTransport(session.getOldestMembership())` instead for the specific member you want to get the focus for.
|
||||
*/
|
||||
getFocusInUse(): Transport | undefined;
|
||||
getOldestMembership(): CallMembership | undefined;
|
||||
/**
|
||||
* Get the call intent for the current call, based on what members are advertising. If one or more
|
||||
* members disagree on the current call intent, or nobody specifies one then `undefined` is returned.
|
||||
*
|
||||
* If all members that specify a call intent agree, that value is returned.
|
||||
* @returns A call intent, or `undefined` if no consensus or not given.
|
||||
*/
|
||||
getConsensusCallIntent(): RTCCallIntent | undefined;
|
||||
updateCallIntent(callIntent: RTCCallIntent): Promise<void>;
|
||||
/**
|
||||
* Re-emit an EncryptionKeyChanged event for each tracked encryption key. This can be used to export
|
||||
* the keys.
|
||||
*/
|
||||
reemitEncryptionKeys(): void;
|
||||
/**
|
||||
* Sets a timer for the soonest membership expiry
|
||||
*/
|
||||
private setExpiryTimer;
|
||||
/**
|
||||
* Sends notification events to indiciate the call has started.
|
||||
* Note: This does not return a promise, instead scheduling the notification events to be sent.
|
||||
* @param parentEventId Event id linking to your RTC call membership event.
|
||||
* @param notificationType The type of notification to send
|
||||
* @param callIntent The type of call this is (e.g. "audio").
|
||||
*/
|
||||
private sendCallNotify;
|
||||
/**
|
||||
* Call this when the Matrix room members have changed.
|
||||
*/
|
||||
private readonly onRoomMemberUpdate;
|
||||
/**
|
||||
* Call this when a room state event has been updated.
|
||||
*/
|
||||
private readonly onRoomStateEvent;
|
||||
/**
|
||||
* Call this when a sticky event update has occured.
|
||||
*/
|
||||
private readonly onStickyEventUpdate;
|
||||
/**
|
||||
* Call this when something changed that may impacts the current MatrixRTC members in this session.
|
||||
*/
|
||||
_onRTCSessionMemberUpdate: () => Promise<void>;
|
||||
private recalculateSessionMembersPromise;
|
||||
/**
|
||||
* Ensures that membership is recalculated when the state of the session may have changed.
|
||||
* Also ensures that only one recalculation is made at a time.
|
||||
* @returns A promise resolving when the state has been recalculated.
|
||||
*/
|
||||
private ensureRecalculateSessionMembers;
|
||||
/**
|
||||
* Call this when anything that could impact rtc memberships has changed: Room Members or RTC members.
|
||||
*
|
||||
* Examines the latest call memberships and handles any encryption key sending or rotation that is needed.
|
||||
*
|
||||
* This function should be called when the room members or call memberships might have changed.
|
||||
*/
|
||||
private readonly recalculateSessionMembers;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=MatrixRTCSession.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.d.ts.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
632
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.js
generated
vendored
Normal file
632
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.js
generated
vendored
Normal file
@@ -0,0 +1,632 @@
|
||||
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2023 - 2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { logger as rootLogger } from "../logger.js";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.js";
|
||||
import { EventTimeline } from "../models/event-timeline.js";
|
||||
import { EventType, RelationType } from "../@types/event.js";
|
||||
import { KnownMembership } from "../@types/membership.js";
|
||||
import { CallMembership } from "./CallMembership.js";
|
||||
import { RoomStateEvent } from "../models/room-state.js";
|
||||
import { MembershipManager, StickyEventMembershipManager } from "./MembershipManager.js";
|
||||
import { logDurationSync } from "../utils.js";
|
||||
import { MembershipManagerEvent } from "./IMembershipManager.js";
|
||||
import { RTCEncryptionManager } from "./RTCEncryptionManager.js";
|
||||
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.js";
|
||||
import { TypedReEmitter } from "../ReEmitter.js";
|
||||
import { RoomStickyEventsEvent } from "../models/room-sticky-events.js";
|
||||
import { computeSlotId } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Events emitted by MatrixRTCSession
|
||||
*/
|
||||
export let MatrixRTCSessionEvent = /*#__PURE__*/function (MatrixRTCSessionEvent) {
|
||||
// A member joined, left, or updated a property of their membership.
|
||||
MatrixRTCSessionEvent["MembershipsChanged"] = "memberships_changed";
|
||||
// We joined or left the session: our own local idea of whether we are joined,
|
||||
// separate from MembershipsChanged, ie. independent of whether our member event
|
||||
// has successfully gone through.
|
||||
MatrixRTCSessionEvent["JoinStateChanged"] = "join_state_changed";
|
||||
// The key used to encrypt media has changed
|
||||
MatrixRTCSessionEvent["EncryptionKeyChanged"] = "encryption_key_changed";
|
||||
/** The membership manager had to shut down caused by an unrecoverable error */
|
||||
MatrixRTCSessionEvent["MembershipManagerError"] = "membership_manager_error";
|
||||
/** The RTCSession did send a call notification caused by joining the call as the first member */
|
||||
MatrixRTCSessionEvent["DidSendCallNotification"] = "did_send_call_notification";
|
||||
return MatrixRTCSessionEvent;
|
||||
}({});
|
||||
|
||||
// The names follow these principles:
|
||||
// - we use the technical term delay if the option is related to delayed events.
|
||||
// - we use delayedLeaveEvent if the option is related to the delayed leave event.
|
||||
// - we use membershipEvent if the option is related to the rtc member state event.
|
||||
// - we use the technical term expiry if the option is related to the expiry field of the membership state event.
|
||||
// - we use a `Ms` postfix if the option is a duration to avoid using words like:
|
||||
// `time`, `duration`, `delay`, `timeout`... that might be mistaken/confused with technical terms.
|
||||
|
||||
const DEFAULT_SESSION_MEMBERSHIPS_FOR_SLOT_OPTS = {
|
||||
listenForStickyEvents: true,
|
||||
listenForMemberStateEvents: true
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export class MatrixRTCSession extends TypedEventEmitter {
|
||||
get membershipStatus() {
|
||||
return this.membershipManager?.status;
|
||||
}
|
||||
get probablyLeft() {
|
||||
return this.membershipManager?.probablyLeft;
|
||||
}
|
||||
get delayId() {
|
||||
return this.membershipManager?.delayId;
|
||||
}
|
||||
|
||||
/**
|
||||
* The callId (sessionId) of the call.
|
||||
*
|
||||
* It can be undefined since the callId is only known once the first membership joins.
|
||||
* The callId is the property that, per definition, groups memberships into one call.
|
||||
* @deprecated use `slotId` instead.
|
||||
*/
|
||||
get callId() {
|
||||
return this.slotDescription?.id;
|
||||
}
|
||||
/**
|
||||
* The slotId of the call.
|
||||
* `{application}#{appSpecificId}`
|
||||
* It can be undefined since the slotId is only known once the first membership joins.
|
||||
* The slotId is the property that, per definition, groups memberships into one call.
|
||||
*/
|
||||
get slotId() {
|
||||
return computeSlotId(this.slotDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current slot state event's content for this session.
|
||||
*
|
||||
* @returns The slot event's content, or `undefined` if no slot event exists for this session.
|
||||
*/
|
||||
getRtcSlot() {
|
||||
return getSlotEventContent(this.roomSubset, this.slotDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this session's slot is closed.
|
||||
*
|
||||
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
||||
* if no slot exists.
|
||||
*/
|
||||
isSlotClosed() {
|
||||
return isSlotClosed(this.roomSubset, this.slotDescription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the call memberships for a room that match the provided `sessionDescription`,
|
||||
* oldest first.
|
||||
*
|
||||
* By default, this will return *both* sticky and member state events.
|
||||
*/
|
||||
static async sessionMembershipsForSlot(room, slotDescription,
|
||||
// default both true this implied we combine sticky and state events for the final call state
|
||||
// (prefer sticky events in case of a duplicate)
|
||||
options = DEFAULT_SESSION_MEMBERSHIPS_FOR_SLOT_OPTS) {
|
||||
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId} ${slotDescription.application}#${slotDescription.id}]`);
|
||||
const callMemberEvents = collectMembersEvents(room, slotDescription, options, logger);
|
||||
const callMemberships = await computeBackendIdentityAndVerifyMemberEvents(room, callMemberEvents, slotDescription, logger);
|
||||
callMemberships.sort((a, b) => a.createdTs() - b.createdTs());
|
||||
if (callMemberships.length > 1) {
|
||||
logger.debug(`Call memberships in room ${room.roomId}, in order: `, callMemberships.map(m => [m.createdTs(), m.userId]));
|
||||
}
|
||||
return callMemberships;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MatrixRTC session for the room.
|
||||
* This returned session can be used to find out if there are active sessions
|
||||
* for the requested room and `slotDescription`.
|
||||
*/
|
||||
static sessionForSlot(client, room, slotDescription, opts) {
|
||||
return new MatrixRTCSession(client, room, slotDescription, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* WARN: this can in theory only be a subset of the room with the properties required by
|
||||
* this class.
|
||||
* Outside of tests this most likely will be a full room, however.
|
||||
* @deprecated Relying on a full Room object being available here is an anti-pattern. You should be tracking
|
||||
* the room object in your own code and passing it in when needed.
|
||||
*/
|
||||
get room() {
|
||||
return this.roomSubset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructs a room session. When using MatrixRTC inside the js-sdk this is expected
|
||||
* to be used with the MatrixRTCSessionManager exclusively.
|
||||
*
|
||||
* In cases where you don't use the js-sdk but build on top of another Matrix stack this class can be used standalone
|
||||
* to manage a joined MatrixRTC session.
|
||||
*
|
||||
* @param client A subset of the {@link MatrixClient} that lets the session interact with the Matrix room.
|
||||
* @param roomSubset The room this session is attached to. A subset of a js-sdk Room that the session needs.
|
||||
* @param slotDescription The slot description is a virtual address where participants are allowed to meet.
|
||||
* This session will only manage memberships that match this slot description.Sessions are distinct if any of
|
||||
* those properties are distinct: `roomSubset.roomId`, `slotDescription.application`, `slotDescription.id`.
|
||||
* @param calculateMembershipsOpts - Options to configure how memberships are calculated for this session.
|
||||
*/
|
||||
constructor(client, roomSubset, slotDescription, calculateMembershipsOpts) {
|
||||
super();
|
||||
_defineProperty(this, "membershipManager", void 0);
|
||||
_defineProperty(this, "encryptionManager", void 0);
|
||||
_defineProperty(this, "joinConfig", void 0);
|
||||
_defineProperty(this, "logger", void 0);
|
||||
_defineProperty(this, "pendingNotificationToSend", void 0);
|
||||
/**
|
||||
* This timeout is responsible to track any expiration. We need to know when we have to start
|
||||
* to ignore other call members. There is no callback for this. This timeout will always be configured to
|
||||
* emit when the next membership expires.
|
||||
*/
|
||||
_defineProperty(this, "expiryTimeout", void 0);
|
||||
_defineProperty(this, "memberships", []);
|
||||
/**
|
||||
* Resolves when the session has calculated the initial membership of the session.
|
||||
*/
|
||||
_defineProperty(this, "initialMembershipCalculated", void 0);
|
||||
/**
|
||||
* Does membership need to be recalculated? This is set to false upon
|
||||
* recalculation.
|
||||
*/
|
||||
_defineProperty(this, "membershipNeedsRecalculation", false);
|
||||
/**
|
||||
* The statistics for this session.
|
||||
*/
|
||||
_defineProperty(this, "statistics", {
|
||||
counters: {
|
||||
roomEventEncryptionKeysSent: 0,
|
||||
roomEventEncryptionKeysReceived: 0
|
||||
},
|
||||
totals: {
|
||||
roomEventEncryptionKeysReceivedTotalAge: 0
|
||||
}
|
||||
});
|
||||
_defineProperty(this, "reEmitter", new TypedReEmitter(this));
|
||||
/**
|
||||
* Call this when the Matrix room members have changed.
|
||||
*/
|
||||
_defineProperty(this, "onRoomMemberUpdate", () => {
|
||||
void this.ensureRecalculateSessionMembers();
|
||||
});
|
||||
/**
|
||||
* Call this when a room state event has been updated.
|
||||
*/
|
||||
_defineProperty(this, "onRoomStateEvent", event => {
|
||||
if (event.getType() !== EventType.RTCSlot) return;
|
||||
if (event.getStateKey() !== computeSlotId(this.slotDescription)) return;
|
||||
void this.ensureRecalculateSessionMembers();
|
||||
});
|
||||
/**
|
||||
* Call this when a sticky event update has occured.
|
||||
*/
|
||||
_defineProperty(this, "onStickyEventUpdate", (added, updated, removed) => {
|
||||
if ([...added, ...removed, ...updated.flatMap(v => [v.current, v.previous])].some(e => e.getType() === EventType.RTCMembership)) {
|
||||
void this.ensureRecalculateSessionMembers();
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Call this when something changed that may impacts the current MatrixRTC members in this session.
|
||||
*/
|
||||
// We allow this name schema since this function should only be used for testing purposes.
|
||||
_defineProperty(this, "_onRTCSessionMemberUpdate", async () => {
|
||||
await this.recalculateSessionMembers();
|
||||
});
|
||||
// helper variables to make sure we do not have parallel running recalculations.
|
||||
_defineProperty(this, "recalculateSessionMembersPromise", Promise.resolve());
|
||||
/**
|
||||
* Call this when anything that could impact rtc memberships has changed: Room Members or RTC members.
|
||||
*
|
||||
* Examines the latest call memberships and handles any encryption key sending or rotation that is needed.
|
||||
*
|
||||
* This function should be called when the room members or call memberships might have changed.
|
||||
*/
|
||||
_defineProperty(this, "recalculateSessionMembers", async () => {
|
||||
// Clear the flag.
|
||||
this.membershipNeedsRecalculation = false;
|
||||
const oldMemberships = this.memberships;
|
||||
this.memberships = await MatrixRTCSession.sessionMembershipsForSlot(this.room, this.slotDescription, this.calculateMembershipsOpts);
|
||||
const changed = oldMemberships.length != this.memberships.length || oldMemberships.some((m, i) => !CallMembership.equal(m, this.memberships[i]));
|
||||
if (changed) {
|
||||
this.logger.info(`Memberships for call in room ${this.roomSubset.roomId} have changed: emitting (${this.memberships.length} members)`);
|
||||
logDurationSync(this.logger, "emit MatrixRTCSessionEvent.MembershipsChanged", () => {
|
||||
this.emit(MatrixRTCSessionEvent.MembershipsChanged, oldMemberships, this.memberships);
|
||||
});
|
||||
void this.membershipManager?.onRTCSessionMemberUpdate(this.memberships);
|
||||
// The `ownMembership` will be set when calling `onRTCSessionMemberUpdate`.
|
||||
const ownMembership = this.membershipManager?.ownMembership;
|
||||
if (this.pendingNotificationToSend && ownMembership && oldMemberships.length === 0) {
|
||||
// If we're the first member in the call, we're responsible for
|
||||
// sending the notification event
|
||||
if (ownMembership.eventId && this.joinConfig?.notificationType) {
|
||||
this.sendCallNotify(ownMembership.eventId, this.joinConfig.notificationType, ownMembership.callIntent);
|
||||
} else {
|
||||
this.logger.warn("Own membership eventId is undefined, cannot send call notification");
|
||||
}
|
||||
}
|
||||
// If anyone else joins the session it is no longer our responsibility to send the notification.
|
||||
// (If we were the joiner we already did sent the notification in the block above.)
|
||||
if (this.memberships.length > 0) this.pendingNotificationToSend = undefined;
|
||||
} else {
|
||||
this.logger.debug(`No membership changes detected for room ${this.roomSubset.roomId}`);
|
||||
}
|
||||
// This also needs to be done if `changed` = false
|
||||
// A member might have updated their fingerprint (created_ts)
|
||||
this.encryptionManager?.onMembershipsUpdate(oldMemberships);
|
||||
this.setExpiryTimer();
|
||||
});
|
||||
this.client = client;
|
||||
this.roomSubset = roomSubset;
|
||||
this.slotDescription = slotDescription;
|
||||
this.calculateMembershipsOpts = calculateMembershipsOpts;
|
||||
this.logger = rootLogger.getChild(`[MatrixRTCSession ${roomSubset.roomId} ${slotDescription.application}#${slotDescription.id}]`);
|
||||
this.roomSubset.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
||||
this.roomSubset.on(RoomStateEvent.Events, this.onRoomStateEvent);
|
||||
this.roomSubset.on(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
|
||||
this.initialMembershipCalculated = this.ensureRecalculateSessionMembers();
|
||||
this.setExpiryTimer();
|
||||
}
|
||||
/*
|
||||
* Returns true if we intend to be participating in the MatrixRTC session.
|
||||
* This is determined by checking if the relativeExpiry has been set.
|
||||
*/
|
||||
isJoined() {
|
||||
return this.membershipManager?.isJoined() ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs cleanup & removes timers for client shutdown
|
||||
*/
|
||||
async stop() {
|
||||
await this.membershipManager?.leave(1000);
|
||||
if (this.expiryTimeout) {
|
||||
clearTimeout(this.expiryTimeout);
|
||||
this.expiryTimeout = undefined;
|
||||
}
|
||||
this.roomSubset.off(RoomStateEvent.Members, this.onRoomMemberUpdate);
|
||||
this.roomSubset.off(RoomStateEvent.Events, this.onRoomStateEvent);
|
||||
this.roomSubset.off(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
|
||||
}
|
||||
/**
|
||||
* Announces this user and device as joined to the MatrixRTC session,
|
||||
* and continues to update the membership event to keep it valid until
|
||||
* leaveRoomSession() is called
|
||||
* This will not subscribe to updates: remember to call subscribe() separately if
|
||||
* desired.
|
||||
* This method will return immediately and the session will be joined in the background.
|
||||
* @param ownMembershipIdentity the identity of the user and device joining the session.
|
||||
* This will be put into the content.member.
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.
|
||||
* @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the
|
||||
* membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`
|
||||
* transport selection will be used instead.
|
||||
* @param joinConfig - Additional configuration for the joined session.
|
||||
*/
|
||||
joinRTCSession(ownMembershipIdentity, fociPreferred, multiSfuFocus, joinConfig) {
|
||||
if (this.isJoined()) {
|
||||
this.logger.info(`Already joined to session in room ${this.roomSubset.roomId}: ignoring join call`);
|
||||
return;
|
||||
} else {
|
||||
// Create MembershipManager and pass the RTCSession logger (with room id info)
|
||||
this.membershipManager = joinConfig?.unstableSendStickyEvents ? new StickyEventMembershipManager(joinConfig, this.roomSubset, this.client, this.slotDescription, ownMembershipIdentity.memberId, this.logger) : new MembershipManager(joinConfig, this.roomSubset, this.client, this.slotDescription, this.logger);
|
||||
this.reEmitter.reEmit(this.membershipManager, [MembershipManagerEvent.ProbablyLeft, MembershipManagerEvent.StatusChanged, MembershipManagerEvent.DelayIdChanged]);
|
||||
// Create Encryption manager
|
||||
const [room, client, statistics] = [this.roomSubset, this.client, this.statistics];
|
||||
const transport = new ToDeviceKeyTransport(ownMembershipIdentity, room.roomId, client, statistics);
|
||||
this.encryptionManager = new RTCEncryptionManager(ownMembershipIdentity, () => this.memberships, transport, (keyBin, encryptionKeyIndex, membership, rtcBackendIdentity) => {
|
||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyBin, encryptionKeyIndex, membership, rtcBackendIdentity);
|
||||
}, this.logger);
|
||||
}
|
||||
this.joinConfig = joinConfig;
|
||||
this.pendingNotificationToSend = this.joinConfig?.notificationType;
|
||||
|
||||
// Join!
|
||||
this.membershipManager.join(fociPreferred, multiSfuFocus, e => {
|
||||
this.logger.error("MembershipManager encountered an unrecoverable error: ", e);
|
||||
this.emit(MatrixRTCSessionEvent.MembershipManagerError, e);
|
||||
this.emit(MatrixRTCSessionEvent.JoinStateChanged, this.isJoined());
|
||||
});
|
||||
this.encryptionManager.join(joinConfig);
|
||||
this.emit(MatrixRTCSessionEvent.JoinStateChanged, true);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fociPreferred
|
||||
* @param multiSfuFocus
|
||||
* @param joinConfig
|
||||
* @deprecated use the joinRTCSession method instead
|
||||
*/
|
||||
joinRoomSession(fociPreferred, multiSfuFocus, joinConfig) {
|
||||
const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
||||
// TODO this wants to become a UUID
|
||||
const memberId = `${userId}:${deviceId}`;
|
||||
this.joinRTCSession({
|
||||
userId,
|
||||
deviceId,
|
||||
memberId
|
||||
}, fociPreferred, multiSfuFocus, joinConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces this user and device as having left the MatrixRTC session
|
||||
* and stops scheduled updates.
|
||||
* This will not unsubscribe from updates: remember to call unsubscribe() separately if
|
||||
* desired.
|
||||
* The membership update required to leave the session will retry if it fails.
|
||||
* Without network connection the promise will never resolve.
|
||||
* A timeout can be provided so that there is a guarantee for the promise to resolve.
|
||||
* @returns Whether the membership update was attempted and did not time out.
|
||||
*/
|
||||
async leaveRoomSession(timeout = undefined) {
|
||||
if (!this.isJoined()) {
|
||||
this.logger.info(`Not joined to session in room ${this.roomSubset.roomId}: ignoring leave call`);
|
||||
return false;
|
||||
}
|
||||
this.logger.info(`Leaving call session in room ${this.roomSubset.roomId}`);
|
||||
this.encryptionManager.leave();
|
||||
const leavePromise = this.membershipManager.leave(timeout);
|
||||
this.emit(MatrixRTCSessionEvent.JoinStateChanged, false);
|
||||
return await leavePromise;
|
||||
}
|
||||
/**
|
||||
* This returns the focus in use by the oldest membership.
|
||||
* Do not use since this might be just the focus for the oldest membership. others might use a different focus.
|
||||
* @deprecated use `member.getTransport(session.getOldestMembership())` instead for the specific member you want to get the focus for.
|
||||
*/
|
||||
getFocusInUse() {
|
||||
const oldestMembership = this.getOldestMembership();
|
||||
return oldestMembership?.getTransport(oldestMembership);
|
||||
}
|
||||
getOldestMembership() {
|
||||
return this.memberships[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the call intent for the current call, based on what members are advertising. If one or more
|
||||
* members disagree on the current call intent, or nobody specifies one then `undefined` is returned.
|
||||
*
|
||||
* If all members that specify a call intent agree, that value is returned.
|
||||
* @returns A call intent, or `undefined` if no consensus or not given.
|
||||
*/
|
||||
getConsensusCallIntent() {
|
||||
const getFirstCallIntent = this.memberships.find(m => !!m.callIntent)?.callIntent;
|
||||
if (!getFirstCallIntent) {
|
||||
return undefined;
|
||||
}
|
||||
if (this.memberships.every(m => !m.callIntent || m.callIntent === getFirstCallIntent)) {
|
||||
return getFirstCallIntent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
async updateCallIntent(callIntent) {
|
||||
const myMembership = this.membershipManager?.ownMembership;
|
||||
if (!myMembership) {
|
||||
throw Error("Not connected yet");
|
||||
}
|
||||
await this.membershipManager?.updateCallIntent(callIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-emit an EncryptionKeyChanged event for each tracked encryption key. This can be used to export
|
||||
* the keys.
|
||||
*/
|
||||
reemitEncryptionKeys() {
|
||||
this.encryptionManager?.getEncryptionKeys().forEach((keyRing, key) => {
|
||||
keyRing.forEach(keyInfo => {
|
||||
this.emit(MatrixRTCSessionEvent.EncryptionKeyChanged, keyInfo.key, keyInfo.keyIndex, keyInfo.membership, keyInfo.rtcBackendIdentity);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timer for the soonest membership expiry
|
||||
*/
|
||||
setExpiryTimer() {
|
||||
if (this.expiryTimeout) {
|
||||
clearTimeout(this.expiryTimeout);
|
||||
this.expiryTimeout = undefined;
|
||||
}
|
||||
let soonestExpiry;
|
||||
for (const membership of this.memberships) {
|
||||
const thisExpiry = membership.getMsUntilExpiry();
|
||||
// If getMsUntilExpiry is undefined we have a MSC4143 (MatrixRTC) compliant event - it never expires
|
||||
// but will be reliably resent on disconnect.
|
||||
if (thisExpiry !== undefined && (soonestExpiry === undefined || thisExpiry < soonestExpiry)) {
|
||||
soonestExpiry = thisExpiry;
|
||||
}
|
||||
}
|
||||
if (soonestExpiry != undefined) {
|
||||
this.expiryTimeout = setTimeout(() => void this.ensureRecalculateSessionMembers(), soonestExpiry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends notification events to indiciate the call has started.
|
||||
* Note: This does not return a promise, instead scheduling the notification events to be sent.
|
||||
* @param parentEventId Event id linking to your RTC call membership event.
|
||||
* @param notificationType The type of notification to send
|
||||
* @param callIntent The type of call this is (e.g. "audio").
|
||||
*/
|
||||
sendCallNotify(parentEventId, notificationType, callIntent) {
|
||||
const lifetime = this.joinConfig?.notificationLifetimeMs ?? 90000;
|
||||
const sendNotificationEvent = async () => {
|
||||
const content = {
|
||||
"m.mentions": {
|
||||
user_ids: [],
|
||||
room: true
|
||||
},
|
||||
"notification_type": notificationType,
|
||||
"m.relates_to": {
|
||||
event_id: parentEventId,
|
||||
rel_type: RelationType.Reference
|
||||
},
|
||||
"sender_ts": Date.now(),
|
||||
"lifetime": lifetime
|
||||
};
|
||||
if (callIntent) {
|
||||
content["m.call.intent"] = callIntent;
|
||||
}
|
||||
const response = await this.client.sendEvent(this.roomSubset.roomId, EventType.RTCNotification, content);
|
||||
return {
|
||||
response,
|
||||
content
|
||||
};
|
||||
};
|
||||
void sendNotificationEvent().then(notification => {
|
||||
// Join event_id and origin event content
|
||||
const newResult = _objectSpread(_objectSpread({}, notification.response), notification.content);
|
||||
this.emit(MatrixRTCSessionEvent.DidSendCallNotification, newResult);
|
||||
}).catch(([errorLegacy, errorNew]) => this.logger.error("Failed to send call notification", errorLegacy, errorNew));
|
||||
}
|
||||
/**
|
||||
* Ensures that membership is recalculated when the state of the session may have changed.
|
||||
* Also ensures that only one recalculation is made at a time.
|
||||
* @returns A promise resolving when the state has been recalculated.
|
||||
*/
|
||||
ensureRecalculateSessionMembers() {
|
||||
if (this.membershipNeedsRecalculation) {
|
||||
// We have already requested recalcuation, don't attempt a new one.
|
||||
return this.recalculateSessionMembersPromise;
|
||||
}
|
||||
this.membershipNeedsRecalculation = true;
|
||||
// Chain the recalculation.
|
||||
this.recalculateSessionMembersPromise = this.recalculateSessionMembersPromise.then(() => this.recalculateSessionMembers(), () => this.recalculateSessionMembers());
|
||||
return this.recalculateSessionMembersPromise;
|
||||
}
|
||||
}
|
||||
|
||||
/// Private helpers
|
||||
async function computeBackendIdentityAndVerifyMemberEvents(room, callMemberEvents, slotDescription, logger) {
|
||||
const callMemberships = [];
|
||||
for (const memberEvent of callMemberEvents) {
|
||||
const content = memberEvent.getContent();
|
||||
|
||||
// Quick filter to avoid unneeded processing of invalid events or left events.
|
||||
if (!quickFilterNonRelevantContents(content, logger)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const membership = await CallMembership.parseFromEvent(memberEvent);
|
||||
if (isValidMembership(membership, room, slotDescription, logger)) {
|
||||
callMemberships.push(membership);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn("Couldn't construct call membership: ", e);
|
||||
}
|
||||
}
|
||||
return callMemberships;
|
||||
}
|
||||
function quickFilterNonRelevantContents(content, logger) {
|
||||
// Ignore sticky keys for the count
|
||||
const eventKeysCount = Object.keys(content).filter(k => k !== "msc4354_sticky_key").length;
|
||||
// Don't even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
|
||||
if (eventKeysCount === 0) return false;
|
||||
|
||||
// We first decide if it's a MSC4143 event (per device state key)
|
||||
if (eventKeysCount > 1 && "application" in content) {
|
||||
// We have a MSC4143 event membership event with a proper joined content
|
||||
return true;
|
||||
} else if (eventKeysCount === 1 && "memberships" in content) {
|
||||
// Events used to have this format in the past, but are now deprecated.
|
||||
// Given that state events ~cannot be deleted, there can be some remaining events in the room, just ignore them.
|
||||
return false;
|
||||
} else {
|
||||
// Invalid or left content
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function isValidMembership(membership, room, slotDescription, logger) {
|
||||
if (membership.slotDescription.id !== slotDescription.id) {
|
||||
logger.info(`Ignoring membership of user ${membership.userId} for a different slot. Theirs: ${JSON.stringify(membership.slotDescription)}, Expected: ${JSON.stringify(slotDescription)}`);
|
||||
return false;
|
||||
}
|
||||
if (membership.isExpired()) {
|
||||
logger.info(`Ignoring expired device membership ${membership.userId}/${membership.deviceId}`);
|
||||
return false;
|
||||
}
|
||||
if (!room.hasMembershipState(membership.userId ?? "", KnownMembership.Join)) {
|
||||
logger.info(`Ignoring membership of user ${membership.userId} who is not in the room.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the slot state event's content for the given slot description.
|
||||
*
|
||||
* @returns The slot event's content, or `undefined` if no slot event exists for the given description.
|
||||
*/
|
||||
function getSlotEventContent(room, slotDescription) {
|
||||
const slotId = computeSlotId(slotDescription);
|
||||
const slotEvent = room.getLiveTimeline().getState(EventTimeline.FORWARDS)?.getStateEvents(EventType.RTCSlot, slotId);
|
||||
if (!slotEvent) return undefined;
|
||||
return slotEvent.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given slot is closed.
|
||||
*
|
||||
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
||||
* if no slot exists.
|
||||
*/
|
||||
function isSlotClosed(room, slotDescription) {
|
||||
const content = getSlotEventContent(room, slotDescription);
|
||||
if (content === undefined) return undefined;
|
||||
return content.status !== "open" || typeof content.application !== "object" || content.application?.type !== slotDescription.application;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the raw member events from room state and sticky store.
|
||||
*/
|
||||
function collectMembersEvents(room, slotDescription, options, logger) {
|
||||
const {
|
||||
listenForStickyEvents,
|
||||
listenForMemberStateEvents
|
||||
} = options;
|
||||
let callMemberEvents = [];
|
||||
if (listenForStickyEvents && !isSlotClosed(room, slotDescription)) {
|
||||
// prefill with sticky events
|
||||
callMemberEvents = [...room._unstable_getStickyEvents()].filter(e => e.getType() === EventType.RTCMembership);
|
||||
}
|
||||
if (listenForMemberStateEvents) {
|
||||
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
||||
if (!roomState) {
|
||||
logger.warn("Couldn't get state for room " + room.roomId + "using empty membership array");
|
||||
return [];
|
||||
}
|
||||
const callMemberStateEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
|
||||
callMemberEvents = callMemberEvents.concat(callMemberStateEvents.filter(callMemberStateEvent => !callMemberEvents.some(
|
||||
// only care about state events which have keys which we have not yet seen in the sticky events.
|
||||
// TODO: I believe this can discard a joined state event if there is a matching left sticky event.
|
||||
stickyEvent => stickyEvent.getContent().msc4354_sticky_key === callMemberStateEvent.getStateKey())));
|
||||
}
|
||||
return callMemberEvents;
|
||||
}
|
||||
//# sourceMappingURL=MatrixRTCSession.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSession.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
59
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.d.ts
generated
vendored
Normal file
59
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.d.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { type MatrixClient } from "../client.ts";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { type Room } from "../models/room.ts";
|
||||
import { MatrixRTCSession } from "./MatrixRTCSession.ts";
|
||||
import { type RtcSlotEventContent, type SlotDescription } from "./types.ts";
|
||||
export declare enum MatrixRTCSessionManagerEvents {
|
||||
SessionStarted = "session_started",
|
||||
SessionEnded = "session_ended"
|
||||
}
|
||||
type EventHandlerMap = {
|
||||
[MatrixRTCSessionManagerEvents.SessionStarted]: (roomId: string, session: MatrixRTCSession) => void;
|
||||
[MatrixRTCSessionManagerEvents.SessionEnded]: (roomId: string, session: MatrixRTCSession) => void;
|
||||
};
|
||||
/**
|
||||
* Holds all active MatrixRTC session objects and creates new ones as events arrive.
|
||||
* One `MatrixRTCSessionManager` is required for each MatrixRTC sessionDescription (application, session id) that the client wants to support.
|
||||
* If no application type is specified in the constructor, the default is "m.call".
|
||||
*
|
||||
* This interface is UNSTABLE and may change without warning.
|
||||
*/
|
||||
export declare class MatrixRTCSessionManager extends TypedEventEmitter<MatrixRTCSessionManagerEvents, EventHandlerMap> {
|
||||
private client;
|
||||
private readonly slotDescription;
|
||||
private roomSessions;
|
||||
private readonly logger;
|
||||
constructor(rootLogger: Logger, client: MatrixClient, slotDescription?: SlotDescription);
|
||||
start(): void;
|
||||
stop(): void;
|
||||
/**
|
||||
* Gets the main MatrixRTC session for a room, or undefined if there is
|
||||
* no current session
|
||||
*/
|
||||
getActiveRoomSession(room: Room): MatrixRTCSession | undefined;
|
||||
/**
|
||||
* Reads the current slot state event's content for the given room's session.
|
||||
*
|
||||
* @returns The slot event's content, or `undefined` if no slot event exists for the session.
|
||||
*/
|
||||
getRtcSlot(room: Room): RtcSlotEventContent | undefined;
|
||||
/**
|
||||
* Whether the given room's slot is closed.
|
||||
*
|
||||
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
||||
* if no slot exists.
|
||||
*/
|
||||
isSlotClosed(room: Room): boolean | undefined;
|
||||
/**
|
||||
* Gets the main MatrixRTC session for a room, returning an empty session
|
||||
* if no members are currently participating
|
||||
*/
|
||||
getRoomSession(room: Room): MatrixRTCSession;
|
||||
private onRoom;
|
||||
private readonly onEvent;
|
||||
private readonly onRoomState;
|
||||
private refreshRoom;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=MatrixRTCSessionManager.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MatrixRTCSessionManager.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/MatrixRTCSessionManager.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,KAAK,YAAY,EAAe,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAG5E,oBAAY,6BAA6B;IAErC,cAAc,oBAAoB;IAElC,YAAY,kBAAkB;CACjC;AAED,KAAK,eAAe,GAAG;IACnB,CAAC,6BAA6B,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpG,CAAC,6BAA6B,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACrG,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB,CAAC,6BAA6B,EAAE,eAAe,CAAC;IAatG,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,QAAQ,CAAC,eAAe;IAPpC,OAAO,CAAC,YAAY,CAAuC;IAE3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YACI,UAAU,EAAE,MAAM,EACV,MAAM,EAAE,YAAY,EACX,eAAe,GAAE,eAAuD,EAI5F;IAEM,KAAK,IAAI,IAAI,CAanB;IAEM,IAAI,IAAI,IAAI,CASlB;IAED;;;OAGG;IACI,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,gBAAgB,GAAG,SAAS,CAEpE;IAED;;;;OAIG;IACI,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,mBAAmB,GAAG,SAAS,CAE7D;IAED;;;;;OAKG;IACI,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAEnD;IAED;;;OAGG;IACI,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,gBAAgB,CASlD;IAED,OAAO,CAAC,MAAM,CAEZ;IAEF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAStB;IAEF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAW1B;YAEY,WAAW;CA0B5B"}
|
||||
163
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.js
generated
vendored
Normal file
163
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.js
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2023-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ClientEvent } from "../client.js";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.js";
|
||||
import { RoomStateEvent } from "../models/room-state.js";
|
||||
import { MatrixRTCSession } from "./MatrixRTCSession.js";
|
||||
import { EventType } from "../@types/event.js";
|
||||
import { computeSlotId } from "./utils.js";
|
||||
export let MatrixRTCSessionManagerEvents = /*#__PURE__*/function (MatrixRTCSessionManagerEvents) {
|
||||
// A member has joined the MatrixRTC session, creating an active session in a room where there wasn't previously
|
||||
MatrixRTCSessionManagerEvents["SessionStarted"] = "session_started";
|
||||
// All participants have left a given MatrixRTC session.
|
||||
MatrixRTCSessionManagerEvents["SessionEnded"] = "session_ended";
|
||||
return MatrixRTCSessionManagerEvents;
|
||||
}({});
|
||||
/**
|
||||
* Holds all active MatrixRTC session objects and creates new ones as events arrive.
|
||||
* One `MatrixRTCSessionManager` is required for each MatrixRTC sessionDescription (application, session id) that the client wants to support.
|
||||
* If no application type is specified in the constructor, the default is "m.call".
|
||||
*
|
||||
* This interface is UNSTABLE and may change without warning.
|
||||
*/
|
||||
export class MatrixRTCSessionManager extends TypedEventEmitter {
|
||||
constructor(rootLogger, client, slotDescription = {
|
||||
application: "m.call",
|
||||
id: "ROOM"
|
||||
} // Default to the Matrix Call application
|
||||
) {
|
||||
super();
|
||||
// All the room-scoped sessions we know about. This will include any where the app
|
||||
// has queried for the MatrixRTC sessions in a room, whether it's ever had any members
|
||||
// or not). We keep a (lazily created) session object for every room to ensure that there
|
||||
// is only ever one single room session object for any given room for the lifetime of the
|
||||
// client: that way there can never be any code holding onto a stale object that is no
|
||||
// longer the correct session object for the room.
|
||||
_defineProperty(this, "roomSessions", new Map());
|
||||
_defineProperty(this, "logger", void 0);
|
||||
_defineProperty(this, "onRoom", room => {
|
||||
void this.refreshRoom(room);
|
||||
});
|
||||
_defineProperty(this, "onEvent", event => {
|
||||
if (!event.unstableStickyExpiresAt) return; // Not sticky, not interested.
|
||||
|
||||
if (event.getType() !== EventType.RTCMembership) return;
|
||||
const room = this.client.getRoom(event.getRoomId());
|
||||
if (!room) return;
|
||||
void this.refreshRoom(room);
|
||||
});
|
||||
_defineProperty(this, "onRoomState", event => {
|
||||
if (event.getType() !== EventType.GroupCallMemberPrefix && event.getType() !== EventType.RTCSlot) {
|
||||
return;
|
||||
}
|
||||
const room = this.client.getRoom(event.getRoomId());
|
||||
if (!room) {
|
||||
this.logger.error(`Got room state event for unknown room ${event.getRoomId()}!`);
|
||||
return;
|
||||
}
|
||||
void this.refreshRoom(room);
|
||||
});
|
||||
this.client = client;
|
||||
this.slotDescription = slotDescription;
|
||||
this.logger = rootLogger.getChild(`[MatrixRTCSessionManager ${computeSlotId(slotDescription)}]`);
|
||||
}
|
||||
start() {
|
||||
// We shouldn't need to null-check here, but matrix-client.spec.ts mocks getRooms
|
||||
// returning nothing, and breaks tests if you change it to return an empty array :'(
|
||||
for (const room of this.client.getRooms() ?? []) {
|
||||
const session = MatrixRTCSession.sessionForSlot(this.client, room, this.slotDescription);
|
||||
if (session.memberships.length > 0) {
|
||||
this.roomSessions.set(room.roomId, session);
|
||||
}
|
||||
}
|
||||
this.client.on(ClientEvent.Room, this.onRoom);
|
||||
this.client.on(ClientEvent.Event, this.onEvent);
|
||||
this.client.on(RoomStateEvent.Events, this.onRoomState);
|
||||
}
|
||||
stop() {
|
||||
for (const sess of this.roomSessions.values()) {
|
||||
void sess.stop();
|
||||
}
|
||||
this.roomSessions.clear();
|
||||
this.client.off(ClientEvent.Room, this.onRoom);
|
||||
this.client.off(ClientEvent.Event, this.onEvent);
|
||||
this.client.off(RoomStateEvent.Events, this.onRoomState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main MatrixRTC session for a room, or undefined if there is
|
||||
* no current session
|
||||
*/
|
||||
getActiveRoomSession(room) {
|
||||
return this.roomSessions.get(room.roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the current slot state event's content for the given room's session.
|
||||
*
|
||||
* @returns The slot event's content, or `undefined` if no slot event exists for the session.
|
||||
*/
|
||||
getRtcSlot(room) {
|
||||
return this.getRoomSession(room).getRtcSlot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given room's slot is closed.
|
||||
*
|
||||
* @returns `true` if the slot is closed, `false` if the slot is open or `undefined`
|
||||
* if no slot exists.
|
||||
*/
|
||||
isSlotClosed(room) {
|
||||
return this.getRoomSession(room).isSlotClosed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the main MatrixRTC session for a room, returning an empty session
|
||||
* if no members are currently participating
|
||||
*/
|
||||
getRoomSession(room) {
|
||||
if (!this.roomSessions.has(room.roomId)) {
|
||||
this.roomSessions.set(room.roomId, MatrixRTCSession.sessionForSlot(this.client, room, this.slotDescription));
|
||||
}
|
||||
return this.roomSessions.get(room.roomId);
|
||||
}
|
||||
async refreshRoom(room) {
|
||||
const isNewSession = !this.roomSessions.has(room.roomId);
|
||||
const session = this.getRoomSession(room);
|
||||
const wasActiveAndKnown = session.memberships.length > 0 && !isNewSession;
|
||||
// This needs to be here and the event listener cannot be setup in the MatrixRTCSession,
|
||||
// because we need the update to happen between:
|
||||
// wasActiveAndKnown = session.memberships.length > 0 and
|
||||
// nowActive = session.memberships.length
|
||||
// Alternatively we would need to setup some event emission when the RTC session ended.
|
||||
// TODO we want to add the emission en session end. This makes the responsibility of the session manager more clear.
|
||||
|
||||
await session._onRTCSessionMemberUpdate().catch(error => {
|
||||
this.logger.error(`Error updating RTC session members for ${room.roomId}: ${error}`);
|
||||
});
|
||||
const nowActive = session.memberships.length > 0;
|
||||
if (wasActiveAndKnown && !nowActive) {
|
||||
this.logger.trace(`Session ended for ${room.roomId} (${session.memberships.length} members)`);
|
||||
this.emit(MatrixRTCSessionManagerEvents.SessionEnded, room.roomId, this.roomSessions.get(room.roomId));
|
||||
} else if (!wasActiveAndKnown && nowActive) {
|
||||
this.logger.trace(`Session started for ${room.roomId} (${session.memberships.length} members)`);
|
||||
this.emit(MatrixRTCSessionManagerEvents.SessionStarted, room.roomId, this.roomSessions.get(room.roomId));
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=MatrixRTCSessionManager.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MatrixRTCSessionManager.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
212
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.d.ts
generated
vendored
Normal file
212
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.d.ts
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
import { type ISendEventResponse, type SendDelayedEventResponse } from "../@types/requests.ts";
|
||||
import { type EmptyObject } from "../@types/common.ts";
|
||||
import type { MatrixClient } from "../client.ts";
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { type Room } from "../models/room.ts";
|
||||
import { type CallMembership } from "./CallMembership.ts";
|
||||
import { type Transport, type RTCCallIntent, Status, type SlotDescription } from "./types.ts";
|
||||
import { type MembershipConfig, type SessionConfig } from "./MatrixRTCSession.ts";
|
||||
import { type ActionUpdate } from "./MembershipManagerActionScheduler.ts";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { MembershipManagerEvent, type IMembershipManager, type MembershipManagerEventHandlerMap } from "./IMembershipManager.ts";
|
||||
import { type RtcMembershipData, type SessionMembershipData } from "./membershipData/index.ts";
|
||||
/**
|
||||
* The different types of actions the MembershipManager can take.
|
||||
* @internal
|
||||
*/
|
||||
export declare enum MembershipActionType {
|
||||
SendDelayedEvent = "SendDelayedEvent",
|
||||
SendJoinEvent = "SendJoinEvent",
|
||||
RestartDelayedEvent = "RestartDelayedEvent",
|
||||
UpdateExpiry = "UpdateExpiry",
|
||||
SendScheduledDelayedLeaveEvent = "SendScheduledDelayedLeaveEvent",
|
||||
SendLeaveEvent = "SendLeaveEvent"
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface MembershipManagerState {
|
||||
/** The delayId we got when successfully sending the delayed leave event.
|
||||
* Gets set to undefined if the server claims it cannot find the delayed event anymore. */
|
||||
delayId?: string;
|
||||
/** Stores how often we have update the `expires` field.
|
||||
* `expireUpdateIterations` * `membershipEventExpiryTimeout` resolves to the value the expires field should contain next */
|
||||
expireUpdateIterations: number;
|
||||
/** The time at which we send the first state event. The time the call started from the DAG point of view.
|
||||
* This is used to compute the local sleep timestamps when to next update the member event with a new expires value. */
|
||||
startTime: number;
|
||||
/** The manager is in the state where its actually connected to the session. */
|
||||
hasMemberStateEvent: boolean;
|
||||
/** Retry counter for rate limits */
|
||||
rateLimitRetries: Map<MembershipActionType, number>;
|
||||
/** Retry counter for other errors */
|
||||
networkErrorRetries: Map<MembershipActionType, number>;
|
||||
/** The time at which we expect the server to send the delayed leave event. */
|
||||
expectedServerDelayLeaveTs?: number;
|
||||
/** This is used to track if the client expects the scheduled delayed leave event to have
|
||||
* been sent because restarting failed during the available time.
|
||||
* Once we resend the delayed event or successfully restarted it will get unset. */
|
||||
probablyLeft: boolean;
|
||||
}
|
||||
type MembershipManagerClient = Pick<MatrixClient, "getUserId" | "getDeviceId" | "sendStateEvent" | "_unstable_sendDelayedStateEvent" | "_unstable_updateDelayedEvent" | "_unstable_cancelScheduledDelayedEvent" | "_unstable_restartScheduledDelayedEvent" | "_unstable_sendScheduledDelayedEvent">;
|
||||
/**
|
||||
* This class is responsible for sending all events relating to the own membership of a matrixRTC call.
|
||||
* It has the following tasks:
|
||||
* - Send the users leave delayed event before sending the membership
|
||||
* - Send the users membership if the state machine is started
|
||||
* - Check if the delayed event was canceled due to sending the membership
|
||||
* - update the delayed event (`restart`)
|
||||
* - Update the state event every ~5h = `DEFAULT_EXPIRE_DURATION` (so it does not get treated as expired)
|
||||
* - When the state machine is stopped:
|
||||
* - Disconnect the member
|
||||
* - Stop the timer for the delay refresh
|
||||
* - Stop the timer for updating the state event
|
||||
*/
|
||||
export declare class MembershipManager extends TypedEventEmitter<MembershipManagerEvent, MembershipManagerEventHandlerMap> implements IMembershipManager {
|
||||
private readonly joinConfig;
|
||||
protected readonly room: Pick<Room, "roomId" | "getVersion">;
|
||||
protected readonly client: MembershipManagerClient;
|
||||
readonly slotDescription: SlotDescription;
|
||||
private activated;
|
||||
private readonly logger;
|
||||
protected callIntent: RTCCallIntent | undefined;
|
||||
isActivated(): boolean;
|
||||
isJoined(): boolean;
|
||||
/**
|
||||
* Puts the MembershipManager in a state where it tries to be joined.
|
||||
* It will send delayed events and membership events
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.
|
||||
* @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the
|
||||
* membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`
|
||||
* transport selection will be used instead.
|
||||
* @param onError This will be called once the membership manager encounters an unrecoverable error.
|
||||
* This should bubble up the the frontend to communicate that the call does not work in the current environment.
|
||||
* The original underlying error is preserved as `error.cause`, so consumers can `instanceof`-check the typed
|
||||
* cause.
|
||||
*/
|
||||
join(fociPreferred: Transport[], multiSfuFocus?: Transport, onError?: (error: unknown) => void): void;
|
||||
/**
|
||||
* Leave from the call (Send an rtc session event with content: `{}`)
|
||||
* @param timeout the maximum duration this promise will take to resolve
|
||||
* @returns true if it managed to leave and false if the timeout condition happened.
|
||||
*/
|
||||
leave(timeout?: number): Promise<boolean>;
|
||||
private leavePromiseResolvers?;
|
||||
onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void>;
|
||||
updateCallIntent(callIntent: RTCCallIntent): Promise<void>;
|
||||
/**
|
||||
* @throws if the client does not return user or device id.
|
||||
* @param joinConfig
|
||||
* @param room
|
||||
* @param client
|
||||
*/
|
||||
constructor(joinConfig: (SessionConfig & MembershipConfig) | undefined, room: Pick<Room, "roomId" | "getVersion">, client: MembershipManagerClient, slotDescription: SlotDescription, parentLogger?: Logger);
|
||||
private _ownMembership?;
|
||||
get ownMembership(): CallMembership | undefined;
|
||||
private oldStatus?;
|
||||
private scheduler;
|
||||
private state;
|
||||
private static get defaultState();
|
||||
protected deviceId: string;
|
||||
protected userId: string;
|
||||
protected stateKey: string;
|
||||
protected rtcTransport?: Transport;
|
||||
/** @deprecated This will be removed in favor or rtcTransport becoming a list of actively used transports */
|
||||
private fociPreferred?;
|
||||
private delayedLeaveEventDelayMsOverride?;
|
||||
private get networkErrorRetryMs();
|
||||
private get membershipEventExpiryMs();
|
||||
private get membershipEventExpiryHeadroomMs();
|
||||
private computeNextExpiryActionTs;
|
||||
protected get delayedLeaveEventDelayMs(): number;
|
||||
private get delayedLeaveEventRestartMs();
|
||||
private get maximumRateLimitRetryCount();
|
||||
private get maximumNetworkErrorRetryCount();
|
||||
private get delayedLeaveEventRestartLocalTimeoutMs();
|
||||
private membershipLoopHandler;
|
||||
protected clientSendDelayedDisconnectMembership: () => Promise<SendDelayedEventResponse>;
|
||||
private sendOrResendDelayedLeaveEvent;
|
||||
private cancelKnownDelayIdBeforeSendDelayedEvent;
|
||||
private setAndEmitProbablyLeft;
|
||||
private setAndEmitDelayId;
|
||||
private restartDelayedEvent;
|
||||
private sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent;
|
||||
protected clientSendMembership: (myMembership: RtcMembershipData | SessionMembershipData | EmptyObject) => Promise<ISendEventResponse>;
|
||||
private sendJoinEvent;
|
||||
private updateExpiryOnJoinedEvent;
|
||||
private sendFallbackLeaveEvent;
|
||||
/**
|
||||
* this creates `${localUserId}_${localDeviceId}_${this.slotDescription.application}${this.slotDescription.id}`
|
||||
* which is not compatible with membershipID of session type member events. They have to be `${localUserId}:${localDeviceId}`
|
||||
*/
|
||||
private makeMembershipStateKey;
|
||||
/**
|
||||
* Constructs our own membership
|
||||
* @returns Only returns `SessionMembershipData`
|
||||
*/
|
||||
protected makeMyMembership(expires: number): SessionMembershipData | RtcMembershipData;
|
||||
/**
|
||||
* Check if its a NOT_FOUND error
|
||||
* @param error the error causing this handler check/execution
|
||||
* @returns true if its a not found error
|
||||
*/
|
||||
private isNotFoundError;
|
||||
/**
|
||||
* Check if this is a DelayExceeded timeout and update the TimeoutOverride for the next try
|
||||
* @param error the error causing this handler check/execution
|
||||
* @returns true if its a delay exceeded error and we updated the local TimeoutOverride
|
||||
*/
|
||||
private manageMaxDelayExceededSituation;
|
||||
protected actionUpdateFromErrors(error: unknown, type: MembershipActionType, method: string): ActionUpdate | undefined;
|
||||
/**
|
||||
* Check if we have a rate limit error and schedule the same action again if we dont exceed the rate limit retry count yet.
|
||||
* @param error the error causing this handler check/execution
|
||||
* @param method the method used for the throw message
|
||||
* @param type which MembershipActionType we reschedule because of a rate limit.
|
||||
* @throws If it is a rate limit error and the retry count got exceeded
|
||||
* @returns Returns true if we handled the error by rescheduling the correct next action.
|
||||
* Returns false if it is not a network error.
|
||||
*/
|
||||
private actionUpdateFromRateLimitError;
|
||||
/**
|
||||
* FIXME Don't Check the error and retry the same MembershipAction again in the configured time and for the configured retry count.
|
||||
* @param error the error causing this handler check/execution
|
||||
* @param type the action type that we need to repeat because of the error
|
||||
* @throws If it is a network error and the retry count got exceeded
|
||||
* @returns
|
||||
* Returns true if we handled the error by rescheduling the correct next action.
|
||||
* Returns false if it is not a network error.
|
||||
*/
|
||||
private actionUpdateFromNetworkErrorRetry;
|
||||
/**
|
||||
* Check if its an UnsupportedDelayedEventsEndpointError and which implies that we cannot do any delayed event logic
|
||||
* @param error The error to check
|
||||
* @returns true it its an UnsupportedDelayedEventsEndpointError
|
||||
*/
|
||||
private isUnsupportedDelayedEndpoint;
|
||||
private resetRateLimitCounter;
|
||||
get status(): Status;
|
||||
get probablyLeft(): boolean;
|
||||
get delayId(): string | undefined;
|
||||
}
|
||||
/**
|
||||
* Implementation of the Membership manager that uses sticky events
|
||||
* rather than state events.
|
||||
*/
|
||||
export declare class StickyEventMembershipManager extends MembershipManager {
|
||||
private readonly clientWithSticky;
|
||||
private readonly memberId;
|
||||
constructor(joinConfig: (SessionConfig & MembershipConfig) | undefined, room: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion">, clientWithSticky: MembershipManagerClient & Pick<MatrixClient, "_unstable_sendStickyEvent" | "_unstable_sendStickyDelayedEvent">, sessionDescription: SlotDescription, memberId: string, parentLogger?: Logger);
|
||||
protected clientSendDelayedDisconnectMembership: () => Promise<SendDelayedEventResponse>;
|
||||
protected clientSendMembership: (myMembership: RtcMembershipData | SessionMembershipData | EmptyObject) => Promise<ISendEventResponse>;
|
||||
private static nameMap;
|
||||
protected actionUpdateFromErrors(e: unknown, t: MembershipActionType, m: string): ActionUpdate | undefined;
|
||||
/**
|
||||
*
|
||||
* @returns Only returns `RtcMembershipData`
|
||||
*/
|
||||
protected makeMyMembership(): RtcMembershipData;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=MembershipManager.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MembershipManager.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/MembershipManager.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,KAAK,MAAM,EAAwB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,KAAK,cAAc,EAA2B,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAE,KAAK,SAAS,EAAkB,KAAK,aAAa,EAAE,MAAM,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAC9G,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAErE,OAAO,EACH,sBAAsB,EACtB,KAAK,kBAAkB,EACvB,KAAK,gCAAgC,EACxC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAmD/F;;;GAGG;AACH,oBAAY,oBAAoB;IAC5B,gBAAgB,qBAAqB;IAKrC,aAAa,kBAAkB;IAK/B,mBAAmB,wBAAwB;IAK3C,YAAY,iBAAiB;IAG7B,8BAA8B,mCAAmC;IAIjE,cAAc,mBAAmB;CAEpC;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC;8FAC0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;+HAC2H;IAC3H,sBAAsB,EAAE,MAAM,CAAC;IAC/B;2HACuH;IACvH,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,mBAAmB,EAAE,OAAO,CAAC;IAG7B,oCAAoC;IACpC,gBAAgB,EAAE,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACpD,qCAAqC;IACrC,mBAAmB,EAAE,GAAG,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;IACvD,8EAA8E;IAC9E,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC;;uFAEmF;IACnF,YAAY,EAAE,OAAO,CAAC;CACzB;AAcD,KAAK,uBAAuB,GAAG,IAAI,CAC/B,YAAY,EACV,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,iCAAiC,GACjC,8BAA8B,GAC9B,uCAAuC,GACvC,wCAAwC,GACxC,qCAAqC,CAC1C,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,qBAAa,iBACT,SAAQ,iBAAiB,CAAC,sBAAsB,EAAE,gCAAgC,CAClF,YAAW,kBAAkB;IAqIzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC5D,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,uBAAuB;aAClC,eAAe,EAAE,eAAe;IAtIpD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,SAAS,CAAC,UAAU,EAAE,aAAa,GAAG,SAAS,CAAC;IAEzC,WAAW,IAAI,OAAO,CAE5B;IAEM,QAAQ,IAAI,OAAO,CAEzB;IAED;;;;;;;;;;;;OAYG;IACI,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,EAAE,aAAa,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI,CA8B3G;IAED;;;;OAIG;IACI,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgB/C;IAED,OAAO,CAAC,qBAAqB,CAAC,CAAgC;IAEvD,wBAAwB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B5E;IAEY,gBAAgB,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAUtE;IAED;;;;;OAKG;IACH,YACqB,UAAU,EAAE,CAAC,aAAa,GAAG,gBAAgB,CAAC,GAAG,SAAS,EACxD,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC,EACzC,MAAM,EAAE,uBAAuB,EAClC,eAAe,EAAE,eAAe,EAChD,YAAY,CAAC,EAAE,MAAM,EA8BxB;IAED,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,IAAW,aAAa,IAAI,cAAc,GAAG,SAAS,CAErD;IAGD,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAkB;IAGnC,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,MAAM,KAAK,YAAY,GAW9B;IAED,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC;IACnC,4GAA4G;IAC5G,OAAO,CAAC,aAAa,CAAC,CAAc;IAGpC,OAAO,CAAC,gCAAgC,CAAC,CAAS;IAElD,OAAO,KAAK,mBAAmB,GAE9B;IACD,OAAO,KAAK,uBAAuB,GAElC;IACD,OAAO,KAAK,+BAA+B,GAE1C;IACD,OAAO,CAAC,yBAAyB;IAOjC,SAAS,KAAK,wBAAwB,IAAI,MAAM,CAE/C;IACD,OAAO,KAAK,0BAA0B,GAErC;IACD,OAAO,KAAK,0BAA0B,GAErC;IACD,OAAO,KAAK,6BAA6B,GAExC;IACD,OAAO,KAAK,sCAAsC,GAEjD;YAGa,qBAAqB;IAsDnC,SAAS,CAAC,qCAAqC,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,CAOlF;YAGQ,6BAA6B;YAqD7B,wCAAwC;IAmCtD,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,iBAAiB;YAOX,mBAAmB;YA0DnB,wDAAwD;IA6BtE,SAAS,CAAC,oBAAoB,EAAE,CAC5B,YAAY,EAAE,iBAAiB,GAAG,qBAAqB,GAAG,WAAW,KACpE,OAAO,CAAC,kBAAkB,CAAC,CAO9B;YAEY,aAAa;YAuCb,yBAAyB;YAyBzB,sBAAsB;IAepC;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAc9B;;;OAGG;IACH,SAAS,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,GAAG,iBAAiB,CA+BrF;IAID;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAIvB;;;;OAIG;IACH,OAAO,CAAC,+BAA+B;IAgBvC,SAAS,CAAC,sBAAsB,CAC5B,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,oBAAoB,EAC1B,MAAM,EAAE,MAAM,GACf,YAAY,GAAG,SAAS,CAK1B;IACD;;;;;;;;OAQG;IACH,OAAO,CAAC,8BAA8B;IAgCtC;;;;;;;;OAQG;IACH,OAAO,CAAC,iCAAiC;IAmEzC;;;;OAIG;IACH,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,qBAAqB;IAK7B,IAAW,MAAM,IAAI,MAAM,CA4C1B;IAED,IAAW,YAAY,IAAI,OAAO,CAEjC;IACD,IAAW,OAAO,IAAI,MAAM,GAAG,SAAS,CAEvC;CACJ;AAED;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,iBAAiB;IAI3D,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAKjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAR7B,YACI,UAAU,EAAE,CAAC,aAAa,GAAG,gBAAgB,CAAC,GAAG,SAAS,EAC1D,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,iBAAiB,GAAG,QAAQ,GAAG,YAAY,CAAC,EAC5C,gBAAgB,EAAE,uBAAuB,GACtD,IAAI,CAAC,YAAY,EAAE,2BAA2B,GAAG,kCAAkC,CAAC,EACxF,kBAAkB,EAAE,eAAe,EAGlB,QAAQ,EAAE,MAAM,EACjC,YAAY,CAAC,EAAE,MAAM,EAGxB;IAED,SAAS,CAAC,qCAAqC,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,CAQlF;IAEN,SAAS,CAAC,oBAAoB,EAAE,CAC5B,YAAY,EAAE,iBAAiB,GAAG,qBAAqB,GAAG,WAAW,KACpE,OAAO,CAAC,kBAAkB,CAAC,CAQ9B;IAEF,OAAO,CAAC,MAAM,CAAC,OAAO,CAGnB;IACH,SAAS,CAAC,sBAAsB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,oBAAoB,EAAE,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEzG;IAED;;;OAGG;IACH,SAAS,CAAC,gBAAgB,IAAI,iBAAiB,CAyB9C;CACJ"}
|
||||
936
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.js
generated
vendored
Normal file
936
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.js
generated
vendored
Normal file
@@ -0,0 +1,936 @@
|
||||
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2025-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { AbortError } from "p-retry";
|
||||
import { EventType, RelationType } from "../@types/event.js";
|
||||
import { ConnectionError, HTTPError, MatrixError } from "../http-api/errors.js";
|
||||
import { logger as rootLogger } from "../logger.js";
|
||||
import { DEFAULT_EXPIRE_DURATION } from "./CallMembership.js";
|
||||
import { isMyMembership, Status } from "./types.js";
|
||||
import { ActionScheduler } from "./MembershipManagerActionScheduler.js";
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.js";
|
||||
import { UnsupportedDelayedEventsEndpointError } from "../errors.js";
|
||||
import { MembershipManagerEvent } from "./IMembershipManager.js";
|
||||
import { computeSlotId } from "./utils.js";
|
||||
import { isLivekitTransportConfig } from "./LivekitTransport.js";
|
||||
|
||||
/* MembershipActionTypes:
|
||||
On Join: ───────────────┐ ┌───────────────(1)───────────┐
|
||||
▼ ▼ │
|
||||
┌────────────────┐ │
|
||||
│SendDelayedEvent│ ──────(2)───┐ │
|
||||
└────────────────┘ │ │
|
||||
│(3) │ │
|
||||
▼ │ │
|
||||
┌─────────────┐ │ │
|
||||
┌──────(4)───│SendJoinEvent│────(4)─────┐ │ │
|
||||
│ └─────────────┘ │ │ │
|
||||
│ ┌─────┐ ┌──────┐ │ │ │
|
||||
▼ ▼ │ │ ▼ ▼ ▼ │
|
||||
┌────────────┐ │ │ ┌───────────────────┐ │
|
||||
│UpdateExpiry│ (s) (s)|RestartDelayedEvent│ │
|
||||
└────────────┘ │ │ └───────────────────┘ │
|
||||
│ │ │ │ │ │
|
||||
└─────┘ └──────┘ └───────┘
|
||||
|
||||
On Leave: ───────── STOP ALL ABOVE
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ SendScheduledDelayedLeaveEvent │
|
||||
└────────────────────────────────┘
|
||||
│(5)
|
||||
▼
|
||||
┌──────────────┐
|
||||
│SendLeaveEvent│
|
||||
└──────────────┘
|
||||
(1) [Not found error] results in resending the delayed event
|
||||
(2) [hasMemberEvent = true] Sending the delayed event if we
|
||||
already have a call member event results jumping to the
|
||||
RestartDelayedEvent loop directly
|
||||
(3) [hasMemberEvent = false] if there is not call member event
|
||||
sending it is the next step
|
||||
(4) Both (UpdateExpiry and RestartDelayedEvent) actions are
|
||||
scheduled when successfully sending the state event
|
||||
(5) Only if delayed event sending failed (fallback)
|
||||
(s) Successful restart/resend
|
||||
*/
|
||||
|
||||
/**
|
||||
* Call membership should always remain sticky for this amount
|
||||
* of time.
|
||||
*/
|
||||
const MEMBERSHIP_STICKY_DURATION_MS = 60 * 60 * 1000; // 60 minutes
|
||||
|
||||
/**
|
||||
* The different types of actions the MembershipManager can take.
|
||||
* @internal
|
||||
*/
|
||||
export let MembershipActionType = /*#__PURE__*/function (MembershipActionType) {
|
||||
MembershipActionType["SendDelayedEvent"] = "SendDelayedEvent";
|
||||
// -> MembershipActionType.SendJoinEvent if successful
|
||||
// -> DelayedLeaveActionType.SendDelayedEvent on error, retry sending the first delayed event.
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent on success start updating the delayed event
|
||||
MembershipActionType["SendJoinEvent"] = "SendJoinEvent";
|
||||
// -> MembershipActionType.SendJoinEvent if we run into a rate limit and need to retry
|
||||
// -> MembershipActionType.Update if we successfully send the join event then schedule the expire event update
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent to recheck the delayed event
|
||||
MembershipActionType["RestartDelayedEvent"] = "RestartDelayedEvent";
|
||||
// -> DelayedLeaveActionType.SendMainDelayedEvent on missing delay id but there is a rtc state event
|
||||
// -> DelayedLeaveActionType.SendDelayedEvent on missing delay id and there is no state event
|
||||
// -> DelayedLeaveActionType.RestartDelayedEvent on success we schedule the next restart
|
||||
MembershipActionType["UpdateExpiry"] = "UpdateExpiry";
|
||||
// -> MembershipActionType.Update if the timeout has passed so the next update is required.
|
||||
MembershipActionType["SendScheduledDelayedLeaveEvent"] = "SendScheduledDelayedLeaveEvent";
|
||||
// -> MembershipActionType.SendLeaveEvent on failure (not found) we need to send the leave manually and cannot use the scheduled delayed event
|
||||
// -> DelayedLeaveActionType.SendScheduledDelayedLeaveEvent on error we try again.
|
||||
MembershipActionType["SendLeaveEvent"] = "SendLeaveEvent"; // -> MembershipActionType.SendLeaveEvent
|
||||
return MembershipActionType;
|
||||
}({});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
function createInsertActionUpdate(type, offset) {
|
||||
return {
|
||||
insert: [{
|
||||
ts: Date.now() + (offset ?? 0),
|
||||
type
|
||||
}]
|
||||
};
|
||||
}
|
||||
function createReplaceActionUpdate(type, offset) {
|
||||
return {
|
||||
replace: [{
|
||||
ts: Date.now() + (offset ?? 0),
|
||||
type
|
||||
}]
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This class is responsible for sending all events relating to the own membership of a matrixRTC call.
|
||||
* It has the following tasks:
|
||||
* - Send the users leave delayed event before sending the membership
|
||||
* - Send the users membership if the state machine is started
|
||||
* - Check if the delayed event was canceled due to sending the membership
|
||||
* - update the delayed event (`restart`)
|
||||
* - Update the state event every ~5h = `DEFAULT_EXPIRE_DURATION` (so it does not get treated as expired)
|
||||
* - When the state machine is stopped:
|
||||
* - Disconnect the member
|
||||
* - Stop the timer for the delay refresh
|
||||
* - Stop the timer for updating the state event
|
||||
*/
|
||||
export class MembershipManager extends TypedEventEmitter {
|
||||
isActivated() {
|
||||
return this.activated;
|
||||
}
|
||||
// DEPRECATED use isActivated
|
||||
isJoined() {
|
||||
return this.isActivated();
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the MembershipManager in a state where it tries to be joined.
|
||||
* It will send delayed events and membership events
|
||||
* @param fociPreferred the list of preferred foci to use in the joined RTC membership event.
|
||||
* If multiSfuFocus is set, this is only needed if this client wants to publish to multiple transports simultaneously.
|
||||
* @param multiSfuFocus the active focus to use in the joined RTC membership event. Setting this implies the
|
||||
* membership manager will operate in a multi-SFU connection mode. If `undefined`, an `oldest_membership`
|
||||
* transport selection will be used instead.
|
||||
* @param onError This will be called once the membership manager encounters an unrecoverable error.
|
||||
* This should bubble up the the frontend to communicate that the call does not work in the current environment.
|
||||
* The original underlying error is preserved as `error.cause`, so consumers can `instanceof`-check the typed
|
||||
* cause.
|
||||
*/
|
||||
join(fociPreferred, multiSfuFocus, onError) {
|
||||
if (this.scheduler.running) {
|
||||
this.logger.error("MembershipManager is already running. Ignoring join request.");
|
||||
return;
|
||||
}
|
||||
this.fociPreferred = fociPreferred;
|
||||
this.rtcTransport = multiSfuFocus;
|
||||
this.leavePromiseResolvers = undefined;
|
||||
this.activated = true;
|
||||
this.oldStatus = this.status;
|
||||
this.state = MembershipManager.defaultState;
|
||||
this.scheduler.startWithJoin().catch(e => {
|
||||
this.logger.error("MembershipManager stopped because: ", e);
|
||||
onError?.(e);
|
||||
}).finally(() => {
|
||||
// Should already be set to false when calling `leave` in non error cases.
|
||||
this.activated = false;
|
||||
// Here the scheduler is not running anymore so we the `membershipLoopHandler` is not called to emit.
|
||||
if (this.oldStatus && this.oldStatus !== this.status) {
|
||||
this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
|
||||
}
|
||||
if (!this.scheduler.running) {
|
||||
this.leavePromiseResolvers?.resolve(true);
|
||||
this.leavePromiseResolvers = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Leave from the call (Send an rtc session event with content: `{}`)
|
||||
* @param timeout the maximum duration this promise will take to resolve
|
||||
* @returns true if it managed to leave and false if the timeout condition happened.
|
||||
*/
|
||||
leave(timeout) {
|
||||
if (!this.scheduler.running) {
|
||||
this.logger.warn("Called MembershipManager.leave() even though the MembershipManager is not running");
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// We use the promise to track if we already scheduled a leave event
|
||||
// So we do not check scheduler.actions/scheduler.insertions
|
||||
if (!this.leavePromiseResolvers) {
|
||||
// reset scheduled actions so we will not do any new actions.
|
||||
this.leavePromiseResolvers = Promise.withResolvers();
|
||||
this.activated = false;
|
||||
this.scheduler.initiateLeave();
|
||||
if (timeout) setTimeout(() => this.leavePromiseResolvers?.resolve(false), timeout);
|
||||
}
|
||||
return this.leavePromiseResolvers.promise;
|
||||
}
|
||||
onRTCSessionMemberUpdate(memberships) {
|
||||
if (!this.isActivated()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this._ownMembership = memberships.find(m => isMyMembership(m, this.userId, this.deviceId));
|
||||
if (!this._ownMembership) {
|
||||
// If one of these actions are scheduled or are getting inserted in the next iteration, we should already
|
||||
// take care of our missing membership.
|
||||
const sendingMembershipActions = [MembershipActionType.SendDelayedEvent, MembershipActionType.SendJoinEvent];
|
||||
this.logger.warn("Missing own membership: force re-join");
|
||||
this.state.hasMemberStateEvent = false;
|
||||
if (this.scheduler.actions.some(a => sendingMembershipActions.includes(a.type))) {
|
||||
this.logger.error("tried adding another `SendDelayedEvent` actions even though we already have one in the Queue\nActionQueueOnMemberUpdate:", this.scheduler.actions);
|
||||
} else {
|
||||
// Only react to our own membership missing if we have not already scheduled sending a new membership DirectMembershipManagerAction.Join
|
||||
this.scheduler.initiateJoin();
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
async updateCallIntent(callIntent) {
|
||||
if (!this.activated || !this.ownMembership) {
|
||||
throw Error("You cannot update your intent before joining the call");
|
||||
}
|
||||
if (this.ownMembership.callIntent === callIntent) {
|
||||
return; // No-op
|
||||
}
|
||||
this.callIntent = callIntent;
|
||||
// Kick off a new membership event as a result.
|
||||
await this.sendJoinEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws if the client does not return user or device id.
|
||||
* @param joinConfig
|
||||
* @param room
|
||||
* @param client
|
||||
*/
|
||||
constructor(joinConfig, room, client, slotDescription, parentLogger) {
|
||||
super();
|
||||
_defineProperty(this, "activated", false);
|
||||
_defineProperty(this, "logger", void 0);
|
||||
_defineProperty(this, "callIntent", void 0);
|
||||
_defineProperty(this, "leavePromiseResolvers", void 0);
|
||||
_defineProperty(this, "_ownMembership", void 0);
|
||||
// scheduler
|
||||
_defineProperty(this, "oldStatus", void 0);
|
||||
_defineProperty(this, "scheduler", void 0);
|
||||
// MembershipManager mutable state.
|
||||
_defineProperty(this, "state", void 0);
|
||||
// Membership Event static parameters:
|
||||
_defineProperty(this, "deviceId", void 0);
|
||||
_defineProperty(this, "userId", void 0);
|
||||
_defineProperty(this, "stateKey", void 0);
|
||||
_defineProperty(this, "rtcTransport", void 0);
|
||||
/** @deprecated This will be removed in favor or rtcTransport becoming a list of actively used transports */
|
||||
_defineProperty(this, "fociPreferred", void 0);
|
||||
// Config:
|
||||
_defineProperty(this, "delayedLeaveEventDelayMsOverride", void 0);
|
||||
// an abstraction to switch between sending state or a sticky event
|
||||
_defineProperty(this, "clientSendDelayedDisconnectMembership", () => this.client._unstable_sendDelayedStateEvent(this.room.roomId, {
|
||||
delay: this.delayedLeaveEventDelayMs
|
||||
}, EventType.GroupCallMemberPrefix, {}, this.stateKey));
|
||||
_defineProperty(this, "clientSendMembership", myMembership => {
|
||||
return this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, myMembership, this.stateKey);
|
||||
});
|
||||
this.joinConfig = joinConfig;
|
||||
this.room = room;
|
||||
this.client = client;
|
||||
this.slotDescription = slotDescription;
|
||||
this.logger = (parentLogger ?? rootLogger).getChild(`[MembershipManager]`);
|
||||
const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
||||
if (userId === null) throw Error("Missing userId in client");
|
||||
if (deviceId === null) throw Error("Missing deviceId in client");
|
||||
this.deviceId = deviceId;
|
||||
this.userId = userId;
|
||||
// this needs to become a uuid so that consecutive join/leaves result in a key rotation.
|
||||
// we keep it as a string for now for backwards compatibility.
|
||||
this.stateKey = this.makeMembershipStateKey(userId, deviceId);
|
||||
this.state = MembershipManager.defaultState;
|
||||
this.callIntent = joinConfig?.callIntent;
|
||||
this.scheduler = new ActionScheduler(type => {
|
||||
if (this.oldStatus) {
|
||||
// we put this at the beginning of the actions scheduler loop handle callback since it is a loop this
|
||||
// is equivalent to running it at the end of the loop. (just after applying the status/action list changes)
|
||||
// This order is required because this method needs to return the action updates.
|
||||
this.logger.debug(`MembershipManager applied action changes. Status: ${this.oldStatus} -> ${this.status}`);
|
||||
if (this.oldStatus !== this.status) {
|
||||
this.emit(MembershipManagerEvent.StatusChanged, this.oldStatus, this.status);
|
||||
}
|
||||
}
|
||||
this.oldStatus = this.status;
|
||||
this.logger.debug(`MembershipManager before processing action. status=${this.oldStatus}`);
|
||||
return this.membershipLoopHandler(type);
|
||||
}, this.logger);
|
||||
}
|
||||
get ownMembership() {
|
||||
return this._ownMembership;
|
||||
}
|
||||
static get defaultState() {
|
||||
return {
|
||||
hasMemberStateEvent: false,
|
||||
delayId: undefined,
|
||||
startTime: 0,
|
||||
rateLimitRetries: new Map(),
|
||||
networkErrorRetries: new Map(),
|
||||
expireUpdateIterations: 1,
|
||||
probablyLeft: false
|
||||
};
|
||||
}
|
||||
get networkErrorRetryMs() {
|
||||
return this.joinConfig?.networkErrorRetryMs ?? 3000;
|
||||
}
|
||||
get membershipEventExpiryMs() {
|
||||
return this.joinConfig?.membershipEventExpiryMs ?? DEFAULT_EXPIRE_DURATION;
|
||||
}
|
||||
get membershipEventExpiryHeadroomMs() {
|
||||
return this.joinConfig?.membershipEventExpiryHeadroomMs ?? 5000;
|
||||
}
|
||||
computeNextExpiryActionTs(iteration) {
|
||||
return this.state.startTime + Math.min(this.membershipEventExpiryMs, MEMBERSHIP_STICKY_DURATION_MS) * iteration - this.membershipEventExpiryHeadroomMs;
|
||||
}
|
||||
get delayedLeaveEventDelayMs() {
|
||||
return this.delayedLeaveEventDelayMsOverride ?? this.joinConfig?.delayedLeaveEventDelayMs ?? 8000;
|
||||
}
|
||||
get delayedLeaveEventRestartMs() {
|
||||
return this.joinConfig?.delayedLeaveEventRestartMs ?? 5000;
|
||||
}
|
||||
get maximumRateLimitRetryCount() {
|
||||
return this.joinConfig?.maximumRateLimitRetryCount ?? 10;
|
||||
}
|
||||
get maximumNetworkErrorRetryCount() {
|
||||
return this.joinConfig?.maximumNetworkErrorRetryCount ?? 10;
|
||||
}
|
||||
get delayedLeaveEventRestartLocalTimeoutMs() {
|
||||
return this.joinConfig?.delayedLeaveEventRestartLocalTimeoutMs ?? 2000;
|
||||
}
|
||||
|
||||
// LOOP HANDLER:
|
||||
async membershipLoopHandler(type) {
|
||||
switch (type) {
|
||||
case MembershipActionType.SendDelayedEvent:
|
||||
{
|
||||
// Before we start we check if we come from a state where we have a delay id.
|
||||
if (!this.state.delayId) {
|
||||
return this.sendOrResendDelayedLeaveEvent(); // Normal case without any previous delayed id.
|
||||
} else {
|
||||
// This can happen if someone else (or another client) removes our own membership event.
|
||||
// It will trigger `onRTCSessionMemberUpdate` queue `MembershipActionType.SendDelayedEvent`.
|
||||
// We might still have our delayed event from the previous participation and dependent on the server this might not
|
||||
// get removed automatically if the state changes. Hence, it would remove our membership unexpectedly shortly after the rejoin.
|
||||
//
|
||||
// In this block we will try to cancel this delayed event before setting up a new one.
|
||||
|
||||
return this.cancelKnownDelayIdBeforeSendDelayedEvent(this.state.delayId);
|
||||
}
|
||||
}
|
||||
case MembershipActionType.RestartDelayedEvent:
|
||||
{
|
||||
if (!this.state.delayId) {
|
||||
// Delay id got reset. This action was used to check if the hs canceled the delayed event when the join state got sent.
|
||||
return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
}
|
||||
return this.restartDelayedEvent(this.state.delayId);
|
||||
}
|
||||
case MembershipActionType.SendScheduledDelayedLeaveEvent:
|
||||
{
|
||||
// We are already good
|
||||
if (!this.state.hasMemberStateEvent) {
|
||||
return {
|
||||
replace: []
|
||||
};
|
||||
}
|
||||
if (this.state.delayId) {
|
||||
return this.sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(this.state.delayId);
|
||||
} else {
|
||||
return createInsertActionUpdate(MembershipActionType.SendLeaveEvent);
|
||||
}
|
||||
}
|
||||
case MembershipActionType.SendJoinEvent:
|
||||
{
|
||||
return this.sendJoinEvent();
|
||||
}
|
||||
case MembershipActionType.UpdateExpiry:
|
||||
{
|
||||
return this.updateExpiryOnJoinedEvent();
|
||||
}
|
||||
case MembershipActionType.SendLeaveEvent:
|
||||
{
|
||||
// We are good already
|
||||
if (!this.state.hasMemberStateEvent) {
|
||||
return {
|
||||
replace: []
|
||||
};
|
||||
}
|
||||
// This is only a fallback in case we do not have working delayed events support.
|
||||
// first we should try to just send the scheduled leave event
|
||||
return this.sendFallbackLeaveEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
// HANDLERS (used in the membershipLoopHandler)
|
||||
async sendOrResendDelayedLeaveEvent() {
|
||||
// We can reach this at the start of a call (where we do not yet have a membership: state.hasMemberStateEvent=false)
|
||||
// or during a call if the state event canceled our delayed event or caused by an unexpected error that removed our delayed event.
|
||||
// (Another client could have canceled it, the homeserver might have removed/lost it due to a restart, ...)
|
||||
// In the `then` and `catch` block we treat both cases differently. "if (this.state.hasMemberStateEvent) {} else {}"
|
||||
return await this.clientSendDelayedDisconnectMembership().then(response => {
|
||||
this.state.expectedServerDelayLeaveTs = Date.now() + this.delayedLeaveEventDelayMs;
|
||||
this.setAndEmitProbablyLeft(false);
|
||||
// On success we reset retries and set delayId.
|
||||
this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
|
||||
this.setAndEmitDelayId(response.delay_id);
|
||||
if (this.state.hasMemberStateEvent) {
|
||||
// This action was scheduled because the previous delayed event was cancelled
|
||||
// due to lack of https://github.com/element-hq/synapse/pull/17810
|
||||
return createInsertActionUpdate(MembershipActionType.RestartDelayedEvent, this.delayedLeaveEventRestartMs);
|
||||
} else {
|
||||
// This action was scheduled because we are in the process of joining
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
}
|
||||
}).catch(e => {
|
||||
const repeatActionType = MembershipActionType.SendDelayedEvent;
|
||||
if (this.manageMaxDelayExceededSituation(e)) {
|
||||
return createInsertActionUpdate(repeatActionType);
|
||||
}
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "_unstable_sendDelayedStateEvent");
|
||||
if (update) return update;
|
||||
if (this.state.hasMemberStateEvent) {
|
||||
// This action was scheduled because the previous delayed event was cancelled
|
||||
// due to lack of https://github.com/element-hq/synapse/pull/17810
|
||||
|
||||
// Don't do any other delayed event work if its not supported.
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
throw Error("Could not send delayed event, even though delayed events are supported. " + e);
|
||||
} else {
|
||||
// This action was scheduled because we are in the process of joining
|
||||
// log and fall through
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) {
|
||||
this.logger.info("Not using delayed event because the endpoint is not supported");
|
||||
} else {
|
||||
this.logger.info("Not using delayed event because: " + e);
|
||||
}
|
||||
// On any other error we fall back to not using delayed events and send the join state event immediately
|
||||
return createInsertActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
async cancelKnownDelayIdBeforeSendDelayedEvent(delayId) {
|
||||
// Remove all running updates and restarts
|
||||
return await this.client._unstable_cancelScheduledDelayedEvent(delayId).then(() => {
|
||||
this.setAndEmitDelayId(undefined);
|
||||
this.resetRateLimitCounter(MembershipActionType.SendDelayedEvent);
|
||||
return createReplaceActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
}).catch(e => {
|
||||
const repeatActionType = MembershipActionType.SendDelayedEvent;
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "cancelScheduledDelayedEvent");
|
||||
if (update) return update;
|
||||
if (this.isNotFoundError(e)) {
|
||||
// If we get a M_NOT_FOUND we know that the delayed event got already removed.
|
||||
// This means we are good and can set it to undefined and run this again.
|
||||
this.setAndEmitDelayId(undefined);
|
||||
return createReplaceActionUpdate(repeatActionType);
|
||||
}
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) {
|
||||
return createReplaceActionUpdate(MembershipActionType.SendJoinEvent);
|
||||
}
|
||||
// We do not just ignore and log this error since we would also need to reset the delayId.
|
||||
|
||||
// This becomes an unrecoverable error case since something is significantly off if we don't hit any of the above cases
|
||||
// when state.delayId !== undefined
|
||||
// We do not just ignore and log this error since we would also need to reset the delayId.
|
||||
// It is cleaner if we, the frontend, rejoins instead of resetting the delayId here and behaving like in the success case.
|
||||
throw Error("We failed to cancel a delayed event where we already had a delay id with an error we cannot automatically handle");
|
||||
});
|
||||
}
|
||||
setAndEmitProbablyLeft(probablyLeft) {
|
||||
if (this.state.probablyLeft === probablyLeft) {
|
||||
return;
|
||||
}
|
||||
this.state.probablyLeft = probablyLeft;
|
||||
this.emit(MembershipManagerEvent.ProbablyLeft, this.state.probablyLeft);
|
||||
}
|
||||
setAndEmitDelayId(delayId) {
|
||||
if (this.state.delayId === delayId) return;
|
||||
this.state.delayId = delayId;
|
||||
this.emit(MembershipManagerEvent.DelayIdChanged, this.state.delayId);
|
||||
}
|
||||
async restartDelayedEvent(delayId) {
|
||||
// Compute the duration until we expect the server to send the delayed leave event.
|
||||
const durationUntilServerDelayedLeave = this.state.expectedServerDelayLeaveTs ? this.state.expectedServerDelayLeaveTs - Date.now() : undefined;
|
||||
const abortPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new AbortError("Restart delayed event timed out before the HS responded"));
|
||||
},
|
||||
// We abort immediately at the time where we expect the server to send the delayed leave event.
|
||||
// At this point we want the catch block to run and set the `probablyLeft` state.
|
||||
//
|
||||
// While we are already in probablyLeft state, we use the unaltered delayedLeaveEventRestartLocalTimeoutMs.
|
||||
durationUntilServerDelayedLeave !== undefined && !this.state.probablyLeft ? Math.min(this.delayedLeaveEventRestartLocalTimeoutMs, durationUntilServerDelayedLeave) : this.delayedLeaveEventRestartLocalTimeoutMs);
|
||||
});
|
||||
|
||||
// The obvious choice here would be to use the `IRequestOpts` to set the timeout. Since this call might be forwarded
|
||||
// to the widget driver this information would get lost. That is why we mimic the AbortError using the race.
|
||||
return await Promise.race([this.client._unstable_restartScheduledDelayedEvent(delayId), abortPromise]).then(() => {
|
||||
// Whenever we successfully restart the delayed event we update the `state.expectedServerDelayLeaveTs`
|
||||
// which stores the predicted timestamp at which the server will send the delayed leave event if there wont be any further
|
||||
// successful restart requests.
|
||||
this.state.expectedServerDelayLeaveTs = Date.now() + this.delayedLeaveEventDelayMs;
|
||||
this.resetRateLimitCounter(MembershipActionType.RestartDelayedEvent);
|
||||
this.setAndEmitProbablyLeft(false);
|
||||
return createInsertActionUpdate(MembershipActionType.RestartDelayedEvent, this.delayedLeaveEventRestartMs);
|
||||
}).catch(e => {
|
||||
if (this.state.expectedServerDelayLeaveTs && this.state.expectedServerDelayLeaveTs <= Date.now()) {
|
||||
// Once we reach this point it's likely that the server is sending the delayed leave event so we emit `probablyLeft = true`.
|
||||
// It will emit `probablyLeft = false` once we notice about our leave through sync and successfully setup a new state event.
|
||||
this.setAndEmitProbablyLeft(true);
|
||||
}
|
||||
const repeatActionType = MembershipActionType.RestartDelayedEvent;
|
||||
if (this.isNotFoundError(e)) {
|
||||
this.setAndEmitDelayId(undefined);
|
||||
return createInsertActionUpdate(MembershipActionType.SendDelayedEvent);
|
||||
}
|
||||
// If the HS does not support delayed events we wont reschedule.
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
|
||||
// TODO this also needs a test: get rate limit while checking id delayed event is scheduled
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "restartScheduledDelayedEvent");
|
||||
if (update) return update;
|
||||
|
||||
// In other error cases we have no idea what is happening
|
||||
throw Error("Could not restart delayed event, even though delayed events are supported. " + e);
|
||||
});
|
||||
}
|
||||
async sendScheduledDelayedLeaveEventOrFallbackToSendLeaveEvent(delayId) {
|
||||
return await this.client._unstable_sendScheduledDelayedEvent(delayId).then(() => {
|
||||
this.state.hasMemberStateEvent = false;
|
||||
this.setAndEmitDelayId(undefined);
|
||||
this.resetRateLimitCounter(MembershipActionType.SendScheduledDelayedLeaveEvent);
|
||||
return {
|
||||
replace: []
|
||||
};
|
||||
}).catch(e => {
|
||||
const repeatActionType = MembershipActionType.SendLeaveEvent;
|
||||
if (this.isUnsupportedDelayedEndpoint(e)) return {};
|
||||
if (this.isNotFoundError(e)) {
|
||||
this.setAndEmitDelayId(undefined);
|
||||
return createInsertActionUpdate(repeatActionType);
|
||||
}
|
||||
const update = this.actionUpdateFromErrors(e, repeatActionType, "sendScheduledDelayedEvent");
|
||||
if (update) return update;
|
||||
|
||||
// On any other error we fall back to SendLeaveEvent (this includes hard errors from rate limiting)
|
||||
this.logger.warn("Encountered unexpected error during SendScheduledDelayedLeaveEvent. Falling back to SendLeaveEvent", e);
|
||||
return createInsertActionUpdate(repeatActionType);
|
||||
});
|
||||
}
|
||||
async sendJoinEvent() {
|
||||
return await this.clientSendMembership(this.makeMyMembership(this.membershipEventExpiryMs)).then(() => {
|
||||
this.setAndEmitProbablyLeft(false);
|
||||
this.state.startTime = Date.now();
|
||||
// The next update should already use twice the membershipEventExpiryTimeout
|
||||
this.state.expireUpdateIterations = 1;
|
||||
this.state.hasMemberStateEvent = true;
|
||||
this.resetRateLimitCounter(MembershipActionType.SendJoinEvent);
|
||||
// An UpdateExpiry action might be left over from a previous join event.
|
||||
// We can reach sendJoinEvent when the delayed leave event gets send by the HS.
|
||||
// The branch where we might have a leftover UpdateExpiry action is:
|
||||
// RestartDelayedEvent (cannot find it, server removed it)
|
||||
// -> SendDelayedEvent (send new delayed event)
|
||||
// -> SendJoinEvent (here with a still scheduled UpdateExpiry action)
|
||||
const actionsWithoutUpdateExpiry = this.scheduler.actions.filter(a => a.type !== MembershipActionType.UpdateExpiry &&
|
||||
// A new UpdateExpiry action with an updated will be scheduled,
|
||||
a.type !== MembershipActionType.SendJoinEvent // Manually remove the SendJoinEvent action,
|
||||
);
|
||||
return {
|
||||
replace: [...actionsWithoutUpdateExpiry,
|
||||
// To check if the delayed event is still there or got removed by inserting the stateEvent, we need to restart it.
|
||||
{
|
||||
ts: Date.now(),
|
||||
type: MembershipActionType.RestartDelayedEvent
|
||||
}, {
|
||||
ts: this.computeNextExpiryActionTs(this.state.expireUpdateIterations),
|
||||
type: MembershipActionType.UpdateExpiry
|
||||
}]
|
||||
};
|
||||
}).catch(e => {
|
||||
const update = this.actionUpdateFromErrors(e, MembershipActionType.SendJoinEvent, "sendStateEvent");
|
||||
if (update) return update;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
async updateExpiryOnJoinedEvent() {
|
||||
const nextExpireUpdateIteration = this.state.expireUpdateIterations + 1;
|
||||
return await this.clientSendMembership(this.makeMyMembership(this.membershipEventExpiryMs * nextExpireUpdateIteration)).then(() => {
|
||||
// Success, we reset retries and schedule update.
|
||||
this.resetRateLimitCounter(MembershipActionType.UpdateExpiry);
|
||||
this.state.expireUpdateIterations = nextExpireUpdateIteration;
|
||||
return {
|
||||
insert: [{
|
||||
ts: this.computeNextExpiryActionTs(nextExpireUpdateIteration),
|
||||
type: MembershipActionType.UpdateExpiry
|
||||
}]
|
||||
};
|
||||
}).catch(e => {
|
||||
const update = this.actionUpdateFromErrors(e, MembershipActionType.UpdateExpiry, "sendStateEvent");
|
||||
if (update) return update;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
async sendFallbackLeaveEvent() {
|
||||
return await this.clientSendMembership({}).then(() => {
|
||||
this.resetRateLimitCounter(MembershipActionType.SendLeaveEvent);
|
||||
this.state.hasMemberStateEvent = false;
|
||||
return {
|
||||
replace: []
|
||||
};
|
||||
}).catch(e => {
|
||||
const update = this.actionUpdateFromErrors(e, MembershipActionType.SendLeaveEvent, "sendStateEvent");
|
||||
if (update) return update;
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
/**
|
||||
* this creates `${localUserId}_${localDeviceId}_${this.slotDescription.application}${this.slotDescription.id}`
|
||||
* which is not compatible with membershipID of session type member events. They have to be `${localUserId}:${localDeviceId}`
|
||||
*/
|
||||
makeMembershipStateKey(localUserId, localDeviceId) {
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// Revert back to "" just for the state key (state keys are always legacy. we use sticky events for non legacy events)
|
||||
const application = this.slotDescription.application;
|
||||
const needsEmptyStringRoomFix = application === "m.call" && this.slotDescription.id === "ROOM";
|
||||
const slotId = needsEmptyStringRoomFix ? "" : this.slotDescription.id;
|
||||
const stateKey = `${localUserId}_${localDeviceId}_${application}${slotId}`;
|
||||
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
||||
return stateKey;
|
||||
} else {
|
||||
return `_${stateKey}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs our own membership
|
||||
* @returns Only returns `SessionMembershipData`
|
||||
*/
|
||||
makeMyMembership(expires) {
|
||||
const ownMembership = this.ownMembership;
|
||||
const needsEmptyStringRoomFix = this.slotDescription.application === "m.call" && this.slotDescription.id === "ROOM";
|
||||
const focusObjects = this.rtcTransport === undefined ? {
|
||||
focus_active: {
|
||||
type: "livekit",
|
||||
focus_selection: "oldest_membership"
|
||||
},
|
||||
foci_preferred: this.fociPreferred ?? []
|
||||
} : {
|
||||
focus_active: {
|
||||
type: "livekit",
|
||||
focus_selection: "multi_sfu"
|
||||
},
|
||||
foci_preferred: [this.rtcTransport, ...(this.fociPreferred ?? [])]
|
||||
};
|
||||
return _objectSpread(_objectSpread({
|
||||
"application": this.slotDescription.application,
|
||||
// INFO_SLOT_ID_LEGACY_CASE (search for all occurances of this INFO to get the full picture)
|
||||
// Revert back to "" just for the sending the event.
|
||||
"call_id": needsEmptyStringRoomFix ? "" : this.slotDescription.id,
|
||||
"scope": "m.room",
|
||||
"device_id": this.deviceId,
|
||||
// DO NOT use this.memberId here since that is the state key (using application...)
|
||||
// But for session events we use the colon seperated userId and deviceId. The SFU will automatically
|
||||
// assign those values to the media participant for those versions.
|
||||
"membershipID": `${this.userId}:${this.deviceId}`,
|
||||
expires,
|
||||
"m.call.intent": this.callIntent
|
||||
}, focusObjects), ownMembership !== undefined ? {
|
||||
created_ts: ownMembership.createdTs()
|
||||
} : undefined);
|
||||
}
|
||||
|
||||
// Error checks and handlers
|
||||
|
||||
/**
|
||||
* Check if its a NOT_FOUND error
|
||||
* @param error the error causing this handler check/execution
|
||||
* @returns true if its a not found error
|
||||
*/
|
||||
isNotFoundError(error) {
|
||||
return error instanceof MatrixError && error.errcode === "M_NOT_FOUND";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a DelayExceeded timeout and update the TimeoutOverride for the next try
|
||||
* @param error the error causing this handler check/execution
|
||||
* @returns true if its a delay exceeded error and we updated the local TimeoutOverride
|
||||
*/
|
||||
manageMaxDelayExceededSituation(error) {
|
||||
if (error instanceof MatrixError && error.errcode === "M_UNKNOWN" && error.data["org.matrix.msc4140.errcode"] === "M_MAX_DELAY_EXCEEDED") {
|
||||
const maxDelayAllowed = error.data["org.matrix.msc4140.max_delay"];
|
||||
if (typeof maxDelayAllowed === "number" && this.delayedLeaveEventDelayMs > maxDelayAllowed) {
|
||||
this.delayedLeaveEventDelayMsOverride = maxDelayAllowed;
|
||||
}
|
||||
this.logger.warn("Retry sending delayed disconnection event due to server timeout limitations:", error);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
actionUpdateFromErrors(error, type, method) {
|
||||
const updateLimit = this.actionUpdateFromRateLimitError(error, method, type);
|
||||
if (updateLimit) return updateLimit;
|
||||
const updateNetwork = this.actionUpdateFromNetworkErrorRetry(error, type);
|
||||
if (updateNetwork) return updateNetwork;
|
||||
}
|
||||
/**
|
||||
* Check if we have a rate limit error and schedule the same action again if we dont exceed the rate limit retry count yet.
|
||||
* @param error the error causing this handler check/execution
|
||||
* @param method the method used for the throw message
|
||||
* @param type which MembershipActionType we reschedule because of a rate limit.
|
||||
* @throws If it is a rate limit error and the retry count got exceeded
|
||||
* @returns Returns true if we handled the error by rescheduling the correct next action.
|
||||
* Returns false if it is not a network error.
|
||||
*/
|
||||
actionUpdateFromRateLimitError(error, method, type) {
|
||||
// "Is rate limit"-boundary
|
||||
if (!((error instanceof HTTPError || error instanceof MatrixError) && error.isRateLimitError())) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// retry boundary
|
||||
const rateLimitRetries = this.state.rateLimitRetries.get(type) ?? 0;
|
||||
if (rateLimitRetries < this.maximumRateLimitRetryCount) {
|
||||
let resendDelay;
|
||||
const defaultMs = 5000;
|
||||
try {
|
||||
resendDelay = error.getRetryAfterMs() ?? defaultMs;
|
||||
this.logger.info(`Rate limited by server, retrying in ${resendDelay}ms`);
|
||||
} catch (e) {
|
||||
this.logger.warn(`Error while retrieving a rate-limit retry delay, retrying after default delay of ${defaultMs}`, e);
|
||||
resendDelay = defaultMs;
|
||||
}
|
||||
this.state.rateLimitRetries.set(type, rateLimitRetries + 1);
|
||||
return createInsertActionUpdate(type, resendDelay);
|
||||
}
|
||||
throw Error("Exceeded maximum retries for " + type + " attempts (client." + method + ")", {
|
||||
cause: error
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* FIXME Don't Check the error and retry the same MembershipAction again in the configured time and for the configured retry count.
|
||||
* @param error the error causing this handler check/execution
|
||||
* @param type the action type that we need to repeat because of the error
|
||||
* @throws If it is a network error and the retry count got exceeded
|
||||
* @returns
|
||||
* Returns true if we handled the error by rescheduling the correct next action.
|
||||
* Returns false if it is not a network error.
|
||||
*/
|
||||
actionUpdateFromNetworkErrorRetry(error, type) {
|
||||
// "Is a network error"-boundary
|
||||
const retries = this.state.networkErrorRetries.get(type) ?? 0;
|
||||
|
||||
// Strings for error logging
|
||||
const retryDurationString = this.networkErrorRetryMs / 1000 + "s";
|
||||
const retryCounterString = "(" + retries + "/" + this.maximumNetworkErrorRetryCount + ")";
|
||||
|
||||
// Variables for scheduling the new event
|
||||
let retryDuration = this.networkErrorRetryMs;
|
||||
if (error instanceof Error && error.name === "AbortError") {
|
||||
// We do not wait for the timeout on local timeouts.
|
||||
retryDuration = 0;
|
||||
this.logger.warn("Network local timeout error while sending event, immediate retry (" + retryCounterString + ")", error);
|
||||
} else if (error instanceof Error && error.message.includes("updating delayed event")) {
|
||||
// TODO: We do not want error message matching here but instead the error should be a typed HTTPError
|
||||
// and be handled below automatically (the same as in the SPA case).
|
||||
//
|
||||
// The error originates because of https://github.com/matrix-org/matrix-widget-api/blob/5d81d4a26ff69e4bd3ddc79a884c9527999fb2f4/src/ClientWidgetApi.ts#L698-L701
|
||||
// uses `e` instance of HttpError (and not MatrixError)
|
||||
// The element web widget driver (only checks for MatrixError) is then failing to process (`processError`) it as a typed error: https://github.com/element-hq/element-web/blob/471712cbf06a067e5499bd5d2d7a75f693d9a12d/src/stores/widgets/StopGapWidgetDriver.ts#L711-L715
|
||||
// So it will not call: `error.asWidgetApiErrorData()` which is also missing for `HttpError`
|
||||
//
|
||||
// A proper fix would be to either find a place to convert the `HttpError` into a `MatrixError` and the `processError`
|
||||
// method to handle it as expected or to adjust `processError` to also process `HttpError`'s.
|
||||
this.logger.warn("delayed event update timeout error, retrying in " + retryDurationString + " " + retryCounterString, error);
|
||||
} else if (error instanceof ConnectionError) {
|
||||
this.logger.warn("Network connection error while sending event, retrying in " + retryDurationString + " " + retryCounterString, error);
|
||||
} else if ((error instanceof HTTPError || error instanceof MatrixError) && typeof error.httpStatus === "number" && error.httpStatus >= 500 && error.httpStatus < 600) {
|
||||
this.logger.warn("Server error while sending event, retrying in " + retryDurationString + " " + retryCounterString, error);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// retry boundary
|
||||
if (retries < this.maximumNetworkErrorRetryCount) {
|
||||
this.state.networkErrorRetries.set(type, retries + 1);
|
||||
return createInsertActionUpdate(type, retryDuration);
|
||||
}
|
||||
|
||||
// Failure
|
||||
throw Error("Reached maximum (" + this.maximumNetworkErrorRetryCount + ") retries cause by: " + error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if its an UnsupportedDelayedEventsEndpointError and which implies that we cannot do any delayed event logic
|
||||
* @param error The error to check
|
||||
* @returns true it its an UnsupportedDelayedEventsEndpointError
|
||||
*/
|
||||
isUnsupportedDelayedEndpoint(error) {
|
||||
return error instanceof UnsupportedDelayedEventsEndpointError;
|
||||
}
|
||||
resetRateLimitCounter(type) {
|
||||
this.state.rateLimitRetries.set(type, 0);
|
||||
this.state.networkErrorRetries.set(type, 0);
|
||||
}
|
||||
get status() {
|
||||
const actions = this.scheduler.actions;
|
||||
if (actions.length === 1) {
|
||||
const {
|
||||
type
|
||||
} = actions[0];
|
||||
switch (type) {
|
||||
case MembershipActionType.SendDelayedEvent:
|
||||
case MembershipActionType.SendJoinEvent:
|
||||
return Status.Connecting;
|
||||
case MembershipActionType.UpdateExpiry:
|
||||
// where no delayed events
|
||||
return Status.Connected;
|
||||
case MembershipActionType.SendScheduledDelayedLeaveEvent:
|
||||
case MembershipActionType.SendLeaveEvent:
|
||||
return Status.Disconnecting;
|
||||
default:
|
||||
// pass through as not expected
|
||||
}
|
||||
} else if (actions.length === 2) {
|
||||
const types = actions.map(a => a.type);
|
||||
// normal state for connected with delayed events
|
||||
if ((types.includes(MembershipActionType.RestartDelayedEvent) || types.includes(MembershipActionType.SendDelayedEvent) && this.state.hasMemberStateEvent) && types.includes(MembershipActionType.UpdateExpiry)) {
|
||||
return Status.Connected;
|
||||
}
|
||||
} else if (actions.length === 3) {
|
||||
const types = actions.map(a => a.type);
|
||||
// It is a correct connected state if we already schedule the next Restart but have not yet cleaned up
|
||||
// the current restart.
|
||||
if (types.filter(t => t === MembershipActionType.RestartDelayedEvent).length === 2 && types.includes(MembershipActionType.UpdateExpiry)) {
|
||||
return Status.Connected;
|
||||
}
|
||||
}
|
||||
if (!this.scheduler.running) {
|
||||
return Status.Disconnected;
|
||||
}
|
||||
this.logger.error("MembershipManager has an unknown state. Actions: ", actions);
|
||||
return Status.Unknown;
|
||||
}
|
||||
get probablyLeft() {
|
||||
return this.state.probablyLeft;
|
||||
}
|
||||
get delayId() {
|
||||
return this.state.delayId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the Membership manager that uses sticky events
|
||||
* rather than state events.
|
||||
*/
|
||||
export class StickyEventMembershipManager extends MembershipManager {
|
||||
constructor(joinConfig, room, clientWithSticky, sessionDescription,
|
||||
// this needs to become a uuid so that consecutive join/leaves result in a key rotation.
|
||||
// we keep it as a string for now for backwards compatibility.
|
||||
memberId, parentLogger) {
|
||||
super(joinConfig, room, clientWithSticky, sessionDescription, parentLogger);
|
||||
_defineProperty(this, "clientSendDelayedDisconnectMembership", () => this.clientWithSticky._unstable_sendStickyDelayedEvent(this.room.roomId, MEMBERSHIP_STICKY_DURATION_MS, {
|
||||
delay: this.delayedLeaveEventDelayMs
|
||||
}, null, EventType.RTCMembership, {
|
||||
msc4354_sticky_key: this.memberId
|
||||
}));
|
||||
_defineProperty(this, "clientSendMembership", myMembership => {
|
||||
return this.clientWithSticky._unstable_sendStickyEvent(this.room.roomId, MEMBERSHIP_STICKY_DURATION_MS, null, EventType.RTCMembership, _objectSpread(_objectSpread({}, myMembership), {}, {
|
||||
msc4354_sticky_key: this.memberId
|
||||
}));
|
||||
});
|
||||
this.clientWithSticky = clientWithSticky;
|
||||
this.memberId = memberId;
|
||||
}
|
||||
actionUpdateFromErrors(e, t, m) {
|
||||
return super.actionUpdateFromErrors(e, t, StickyEventMembershipManager.nameMap.get(m) ?? "unknown");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns Only returns `RtcMembershipData`
|
||||
*/
|
||||
makeMyMembership() {
|
||||
const ownMembership = this.ownMembership;
|
||||
const livekitTransport = isLivekitTransportConfig(this.rtcTransport) ? this.rtcTransport : undefined;
|
||||
const relationObject = ownMembership?.eventId ? {
|
||||
"m.relation": {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: ownMembership?.eventId
|
||||
}
|
||||
} : {};
|
||||
return _objectSpread({
|
||||
application: _objectSpread({
|
||||
type: this.slotDescription.application
|
||||
}, this.callIntent ? {
|
||||
"m.call.intent": this.callIntent
|
||||
} : {}),
|
||||
slot_id: computeSlotId(this.slotDescription),
|
||||
transports: {
|
||||
// Make sure we do not add the alias to the transport.
|
||||
// It is not needed in matrix2.0. The additional session information will be used to find the right alias on the sfu.
|
||||
published: livekitTransport ? [{
|
||||
type: livekitTransport.type,
|
||||
livekit_service_url: livekitTransport.livekit_service_url
|
||||
}] : [],
|
||||
can_subscribe: ["livekit"]
|
||||
},
|
||||
member: {
|
||||
device_id: this.deviceId,
|
||||
user_id: this.userId,
|
||||
id: this.memberId
|
||||
},
|
||||
versions: []
|
||||
}, relationObject);
|
||||
}
|
||||
}
|
||||
_defineProperty(StickyEventMembershipManager, "nameMap", new Map([["sendStateEvent", "_unstable_sendStickyEvent"], ["sendDelayedStateEvent", "_unstable_sendStickyDelayedEvent"]]));
|
||||
//# sourceMappingURL=MembershipManager.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManager.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
59
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.d.ts
generated
vendored
Normal file
59
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.d.ts
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { type EmptyObject } from "../matrix.ts";
|
||||
import { MembershipActionType } from "./MembershipManager.ts";
|
||||
/** @internal */
|
||||
export interface Action {
|
||||
/**
|
||||
* When this action should be executed
|
||||
*/
|
||||
ts: number;
|
||||
/**
|
||||
* The state of the different loops
|
||||
* can also be thought of as the type of the action
|
||||
*/
|
||||
type: MembershipActionType;
|
||||
}
|
||||
/** @internal */
|
||||
export type ActionUpdate = {
|
||||
/** Replace all existing scheduled actions with this new array */
|
||||
replace: Action[];
|
||||
} | {
|
||||
/** Add these actions to the existing scheduled actions */
|
||||
insert: Action[];
|
||||
} | EmptyObject;
|
||||
/**
|
||||
* This scheduler tracks the state of the current membership participation
|
||||
* and runs one central timer that wakes up a handler callback with the correct action + state
|
||||
* whenever necessary.
|
||||
*
|
||||
* It can also be awakened whenever a new action is added which is
|
||||
* earlier then the current "next awake".
|
||||
* @internal
|
||||
*/
|
||||
export declare class ActionScheduler {
|
||||
/** This is the callback called for each scheduled action (`this.addAction()`) */
|
||||
private membershipLoopHandler;
|
||||
private logger;
|
||||
/**
|
||||
* This is tracking the state of the scheduler loop.
|
||||
* Only used to prevent starting the loop twice.
|
||||
*/
|
||||
running: boolean;
|
||||
constructor(
|
||||
/** This is the callback called for each scheduled action (`this.addAction()`) */
|
||||
membershipLoopHandler: (type: MembershipActionType) => Promise<ActionUpdate>, parentLogger?: Logger);
|
||||
private wakeup;
|
||||
private _actions;
|
||||
get actions(): Action[];
|
||||
/**
|
||||
* This starts the main loop of the membership manager that handles event sending, delayed event sending and delayed event restarting.
|
||||
* @param initialActions The initial actions the manager will start with. It should be enough to pass: DelayedLeaveActionType.Initial
|
||||
* @returns Promise that resolves once all actions have run and no more are scheduled.
|
||||
* @throws This throws an error if one of the actions throws.
|
||||
* In most other error cases the manager will try to handle any server errors by itself.
|
||||
*/
|
||||
startWithJoin(): Promise<void>;
|
||||
initiateJoin(): void;
|
||||
initiateLeave(): void;
|
||||
}
|
||||
//# sourceMappingURL=MembershipManagerActionScheduler.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"MembershipManagerActionScheduler.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/MembershipManagerActionScheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAwB,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,gBAAgB;AAChB,MAAM,WAAW,MAAM;IACnB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;;OAGG;IACH,IAAI,EAAE,oBAAoB,CAAC;CAC9B;AAED,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAClB;IACI,iEAAiE;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB,GACD;IACI,0DAA0D;IAC1D,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB,GACD,WAAW,CAAC;AAElB;;;;;;;;GAQG;AACH,qBAAa,eAAe;IASpB,iFAAiF;IACjF,OAAO,CAAC,qBAAqB;IATjC,OAAO,CAAC,MAAM,CAAS;IACvB;;;OAGG;IACI,OAAO,UAAS;IAEvB;IACI,iFAAiF;IACzE,qBAAqB,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC,YAAY,CAAC,EACpF,YAAY,CAAC,EAAE,MAAM,EAGxB;IAGD,OAAO,CAAC,MAAM,CAEZ;IACF,OAAO,CAAC,QAAQ,CAAgB;IAChC,IAAW,OAAO,IAAI,MAAM,EAAE,CAE7B;IAED;;;;;;OAMG;IACU,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA0D1C;IAEM,YAAY,IAAI,IAAI,CAE1B;IACM,aAAa,IAAI,IAAI,CAE3B;CACJ"}
|
||||
119
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.js
generated
vendored
Normal file
119
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.js
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
import { logger as rootLogger } from "../logger.js";
|
||||
import { sleep } from "../utils.js";
|
||||
import { MembershipActionType } from "./MembershipManager.js";
|
||||
|
||||
/** @internal */
|
||||
|
||||
/** @internal */
|
||||
|
||||
/**
|
||||
* This scheduler tracks the state of the current membership participation
|
||||
* and runs one central timer that wakes up a handler callback with the correct action + state
|
||||
* whenever necessary.
|
||||
*
|
||||
* It can also be awakened whenever a new action is added which is
|
||||
* earlier then the current "next awake".
|
||||
* @internal
|
||||
*/
|
||||
export class ActionScheduler {
|
||||
constructor(/** This is the callback called for each scheduled action (`this.addAction()`) */
|
||||
membershipLoopHandler, parentLogger) {
|
||||
_defineProperty(this, "logger", void 0);
|
||||
/**
|
||||
* This is tracking the state of the scheduler loop.
|
||||
* Only used to prevent starting the loop twice.
|
||||
*/
|
||||
_defineProperty(this, "running", false);
|
||||
// function for the wakeup mechanism (in case we add an action externally and need to leave the current sleep)
|
||||
_defineProperty(this, "wakeup", update => {
|
||||
this.logger.error("Cannot call wakeup before calling `startWithJoin()`");
|
||||
});
|
||||
_defineProperty(this, "_actions", []);
|
||||
this.membershipLoopHandler = membershipLoopHandler;
|
||||
this.logger = (parentLogger ?? rootLogger).getChild(`[NewMembershipActionScheduler]`);
|
||||
}
|
||||
get actions() {
|
||||
return this._actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* This starts the main loop of the membership manager that handles event sending, delayed event sending and delayed event restarting.
|
||||
* @param initialActions The initial actions the manager will start with. It should be enough to pass: DelayedLeaveActionType.Initial
|
||||
* @returns Promise that resolves once all actions have run and no more are scheduled.
|
||||
* @throws This throws an error if one of the actions throws.
|
||||
* In most other error cases the manager will try to handle any server errors by itself.
|
||||
*/
|
||||
async startWithJoin() {
|
||||
if (this.running) {
|
||||
this.logger.error("Cannot call startWithJoin() on NewMembershipActionScheduler while already running");
|
||||
return;
|
||||
}
|
||||
this.running = true;
|
||||
this._actions = [{
|
||||
ts: Date.now(),
|
||||
type: MembershipActionType.SendDelayedEvent
|
||||
}];
|
||||
try {
|
||||
while (this._actions.length > 0) {
|
||||
// Sort so next (smallest ts) action is at the beginning
|
||||
this._actions.sort((a, b) => a.ts - b.ts);
|
||||
const nextAction = this._actions[0];
|
||||
let wakeupUpdate = undefined;
|
||||
|
||||
// while we await for the next action, wakeup has to resolve the wakeupPromise
|
||||
const wakeupPromise = new Promise(resolve => {
|
||||
this.wakeup = update => {
|
||||
wakeupUpdate = update;
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
if (nextAction.ts > Date.now()) await Promise.race([wakeupPromise, sleep(nextAction.ts - Date.now())]);
|
||||
let handlerResult = {};
|
||||
if (!wakeupUpdate) {
|
||||
this.logger.debug(`Current MembershipManager processing: ${nextAction.type}\nQueue:`, this._actions, `\nDate.now: "${Date.now()}`);
|
||||
try {
|
||||
// `this.wakeup` can also be called and sets the `wakeupUpdate` object while we are in the handler.
|
||||
handlerResult = await this.membershipLoopHandler(nextAction.type);
|
||||
} catch (e) {
|
||||
// Preserve the original error as `cause`.
|
||||
throw new Error(`The MembershipManager shut down because of the end condition: ${e}`, {
|
||||
cause: e
|
||||
});
|
||||
}
|
||||
}
|
||||
// remove the processed action only after we are done processing
|
||||
this._actions.splice(0, 1);
|
||||
// The wakeupUpdate always wins since that is a direct external update.
|
||||
const actionUpdate = wakeupUpdate ?? handlerResult;
|
||||
if ("replace" in actionUpdate) {
|
||||
this._actions = actionUpdate.replace;
|
||||
} else if ("insert" in actionUpdate) {
|
||||
this._actions.push(...actionUpdate.insert);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Set the rtc session running state since we cannot recover from here and the consumer user of the
|
||||
// MatrixRTCSession class needs to manually rejoin.
|
||||
this.running = false;
|
||||
}
|
||||
this.logger.debug("Leave MembershipManager ActionScheduler loop (no more actions)");
|
||||
}
|
||||
initiateJoin() {
|
||||
this.wakeup?.({
|
||||
replace: [{
|
||||
ts: Date.now(),
|
||||
type: MembershipActionType.SendDelayedEvent
|
||||
}]
|
||||
});
|
||||
}
|
||||
initiateLeave() {
|
||||
this.wakeup?.({
|
||||
replace: [{
|
||||
ts: Date.now(),
|
||||
type: MembershipActionType.SendScheduledDelayedLeaveEvent
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=MembershipManagerActionScheduler.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/MembershipManagerActionScheduler.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
110
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.d.ts
generated
vendored
Normal file
110
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.d.ts
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import { type CallMembershipIdentityParts, type IEncryptionManager } from "./EncryptionManager.ts";
|
||||
import { type EncryptionConfig, type MembershipConfig } from "./MatrixRTCSession.ts";
|
||||
import type { CallMembership } from "./CallMembership.ts";
|
||||
import { type IKeyTransport, type KeyTransportEventListener } from "./IKeyTransport.ts";
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { type EncryptionKeyMapKey } from "./types.ts";
|
||||
/**
|
||||
* RTCEncryptionManager is used to manage the encryption keys for a call.
|
||||
*
|
||||
* It is responsible for distributing the keys to the other participants and rotating the keys if needed.
|
||||
*
|
||||
* This manager when used with to-device transport will share the existing key only to new joiners, and rotate
|
||||
* if there is a leaver.
|
||||
*
|
||||
* XXX In the future we want to distribute a ratcheted key not the current one for new joiners.
|
||||
*/
|
||||
export declare class RTCEncryptionManager implements IEncryptionManager {
|
||||
private readonly ownMembership;
|
||||
private getMemberships;
|
||||
private transport;
|
||||
private onEncryptionKeysChanged;
|
||||
private manageMediaKeys;
|
||||
private useHashedRtcBackendIdentity;
|
||||
private ownRtcBackendIdentityCache;
|
||||
/**
|
||||
* Store the key rings for each participant.
|
||||
* The encryption manager stores the keys because the application layer might not be ready yet to handle the keys.
|
||||
* The keys are stored and can be retrieved later when the application layer is ready {@link RTCEncryptionManager#getEncryptionKeys}.
|
||||
*/
|
||||
private readonly participantKeyRings;
|
||||
private outboundSession;
|
||||
/**
|
||||
* Ensures that there is only one distribute operation at a time for that call.
|
||||
*/
|
||||
private currentKeyDistributionPromise;
|
||||
/**
|
||||
* The time to wait before using the outbound session after it has been distributed.
|
||||
* This is to ensure that the key is delivered to all participants before it is used.
|
||||
* When creating the first key, this is set to 0 so that the key can be used immediately.
|
||||
*/
|
||||
private useKeyDelay;
|
||||
/**
|
||||
* We want to avoid rolling out a new outbound key when the previous one was created less than `keyRotationGracePeriodMs` milliseconds ago.
|
||||
* This is to avoid expensive key rotations when users quickly join the call in a row.
|
||||
*
|
||||
* This must be higher than `useKeyDelay` to have an effect.
|
||||
* If it is lower, the current key will always be older than the grace period.
|
||||
* @private
|
||||
*/
|
||||
private keyRotationGracePeriodMs;
|
||||
/**
|
||||
* If a new key distribution is being requested while one is going on, we will set this flag to true.
|
||||
* This will ensure that a new round is started after the current one.
|
||||
* @private
|
||||
*/
|
||||
private needToEnsureKeyAgain;
|
||||
/**
|
||||
* There is a possibility that keys arrive in the wrong order.
|
||||
* For example, after a quick join/leave/join, there will be 2 keys of index 0 distributed, and
|
||||
* if they are received in the wrong order, the stream won't be decryptable.
|
||||
* For that reason we keep a small buffer of keys for a limited time to disambiguate.
|
||||
* @private
|
||||
*/
|
||||
private keyBuffer;
|
||||
private logger;
|
||||
private readonly rtcIdentityProvider;
|
||||
/**
|
||||
*
|
||||
* @param ownMembership - our own membership info
|
||||
* @param getMemberships - function to get current memberships
|
||||
* @param transport - key transport (room or to-device)
|
||||
* @param statistics - statistics collector
|
||||
* @param onEncryptionKeysChanged - callback to notify the media layer of new keys
|
||||
* @param parentLogger - optional parent logger
|
||||
* @param rtcBackendIdProvider - A function to compute the rtc backend identity, exposed for testing purposes
|
||||
*/
|
||||
constructor(ownMembership: CallMembershipIdentityParts, getMemberships: () => CallMembership[], transport: IKeyTransport, onEncryptionKeysChanged: (keyBin: Uint8Array<ArrayBuffer>, encryptionKeyIndex: number, membership: CallMembershipIdentityParts, rtcBackendIdentity: string) => void, parentLogger?: Logger, rtcBackendIdProvider?: (userId: string, deviceId: string, memberId: string) => Promise<string>);
|
||||
private getOwnRtcBackendIdentity;
|
||||
getEncryptionKeys(): ReadonlyMap<EncryptionKeyMapKey, ReadonlyArray<{
|
||||
key: Uint8Array<ArrayBuffer>;
|
||||
keyIndex: number;
|
||||
membership: CallMembershipIdentityParts;
|
||||
rtcBackendIdentity: string;
|
||||
}>>;
|
||||
private keysWithoutMatchingRTCMembership;
|
||||
private checkKeysWithoutMatchingRTCMembership;
|
||||
private addKeyToParticipant;
|
||||
private addKeyToParticipantWithBackendIdentity;
|
||||
join(joinConfig: (EncryptionConfig & MembershipConfig) | undefined): void;
|
||||
leave(): void;
|
||||
/**
|
||||
* Will ensure that a new key is distributed and used to encrypt our media.
|
||||
* If there is already a key distribution in progress, it will schedule a new distribution round just after the current one is completed.
|
||||
* If this function is called repeatedly while a distribution is in progress,
|
||||
* the calls will be coalesced to a single new distribution (that will start just after the current one has completed).
|
||||
*/
|
||||
private ensureKeyDistribution;
|
||||
onNewKeyReceived: KeyTransportEventListener;
|
||||
/**
|
||||
* Called when the ownMembership of the call changes.
|
||||
* This encryption manager is very basic, it will rotate the key everytime this is called.
|
||||
* @param oldMemberships - This parameter is not used here, but it is kept for compatibility with the interface.
|
||||
*/
|
||||
onMembershipsUpdate(oldMemberships?: CallMembership[]): void;
|
||||
private rolloutOutboundKey;
|
||||
private createNewOutboundSession;
|
||||
private nextKeyIndex;
|
||||
private generateRandomKey;
|
||||
}
|
||||
//# sourceMappingURL=RTCEncryptionManager.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RTCEncryptionManager.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/RTCEncryptionManager.ts"],"names":[],"mappings":"AAgBA,OAAO,EACH,KAAK,2BAA2B,EAEhC,KAAK,kBAAkB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,yBAAyB,EAAsB,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EACH,KAAK,mBAAmB,EAI3B,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;GASG;AACH,qBAAa,oBAAqB,YAAW,kBAAkB;IAgFvD,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,SAAS;IAEjB,OAAO,CAAC,uBAAuB;IAhFnC,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,2BAA2B,CAAS;IAC5C,OAAO,CAAC,0BAA0B,CAAqB;IAEvD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAQhC;IAGJ,OAAO,CAAC,eAAe,CAA0C;IAEjE;;OAEG;IACH,OAAO,CAAC,6BAA6B,CAA8B;IAEnE;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAAQ;IAE3B;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB,CAAU;IAE1C;;;;OAIG;IACH,OAAO,CAAC,oBAAoB,CAAS;IAErC;;;;;;OAMG;IACH,OAAO,CAAC,SAAS,CAA2B;IAE5C,OAAO,CAAC,MAAM,CAAiC;IAE/C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAA0E;IAE9G;;;;;;;;;OASG;IACH,YACqB,aAAa,EAAE,2BAA2B,EACnD,cAAc,EAAE,MAAM,cAAc,EAAE,EACtC,SAAS,EAAE,aAAa,EAExB,uBAAuB,EAAE,CAC7B,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,EAC/B,kBAAkB,EAAE,MAAM,EAC1B,UAAU,EAAE,2BAA2B,EACvC,kBAAkB,EAAE,MAAM,KACzB,IAAI,EACT,YAAY,CAAC,EAAE,MAAM,EACrB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAIjG;YAEa,wBAAwB;IAgB/B,iBAAiB,IAAI,WAAW,CACnC,mBAAmB,EACnB,aAAa,CAAC;QACV,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;QAC7B,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,2BAA2B,CAAC;QACxC,kBAAkB,EAAE,MAAM,CAAC;KAC9B,CAAC,CACL,CAEA;IAED,OAAO,CAAC,gCAAgC,CAIhC;IAER,OAAO,CAAC,qCAAqC;IAQ7C,OAAO,CAAC,mBAAmB;IAmB3B,OAAO,CAAC,sCAAsC;IAcvC,IAAI,CAAC,UAAU,EAAE,CAAC,gBAAgB,GAAG,gBAAgB,CAAC,GAAG,SAAS,GAAG,IAAI,CAW/E;IAEM,KAAK,IAAI,IAAI,CAInB;IAED;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IA0BtB,gBAAgB,EAAE,yBAAyB,CAiChD;IAEF;;;;OAIG;IACI,mBAAmB,CAAC,cAAc,GAAE,cAAc,EAAO,GAAG,IAAI,CAQtE;YAEa,kBAAkB;IAiHhC,OAAO,CAAC,wBAAwB;IAchC,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;CAK5B"}
|
||||
351
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.js
generated
vendored
Normal file
351
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.js
generated
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2025-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getEncryptionKeyMapKey } from "./EncryptionManager.js";
|
||||
import { decodeBase64, encodeBase64 } from "../base64.js";
|
||||
import { KeyTransportEvents } from "./IKeyTransport.js";
|
||||
import { sleep } from "../utils.js";
|
||||
import { OutdatedKeyFilter } from "./utils.js";
|
||||
import { computeRtcIdentityRaw } from "./membershipData/rtc.js";
|
||||
|
||||
/**
|
||||
* RTCEncryptionManager is used to manage the encryption keys for a call.
|
||||
*
|
||||
* It is responsible for distributing the keys to the other participants and rotating the keys if needed.
|
||||
*
|
||||
* This manager when used with to-device transport will share the existing key only to new joiners, and rotate
|
||||
* if there is a leaver.
|
||||
*
|
||||
* XXX In the future we want to distribute a ratcheted key not the current one for new joiners.
|
||||
*/
|
||||
export class RTCEncryptionManager {
|
||||
/**
|
||||
*
|
||||
* @param ownMembership - our own membership info
|
||||
* @param getMemberships - function to get current memberships
|
||||
* @param transport - key transport (room or to-device)
|
||||
* @param statistics - statistics collector
|
||||
* @param onEncryptionKeysChanged - callback to notify the media layer of new keys
|
||||
* @param parentLogger - optional parent logger
|
||||
* @param rtcBackendIdProvider - A function to compute the rtc backend identity, exposed for testing purposes
|
||||
*/
|
||||
constructor(ownMembership, getMemberships, transport,
|
||||
// Callback to notify the media layer of new keys
|
||||
onEncryptionKeysChanged, parentLogger, rtcBackendIdProvider) {
|
||||
// This is a stop-gap solution for now. The preferred way to handle this case would be instead
|
||||
// to create a NoOpEncryptionManager that does nothing and use it for the session.
|
||||
// This will be done when removing the legacy EncryptionManager.
|
||||
_defineProperty(this, "manageMediaKeys", false);
|
||||
_defineProperty(this, "useHashedRtcBackendIdentity", false);
|
||||
_defineProperty(this, "ownRtcBackendIdentityCache", void 0);
|
||||
/**
|
||||
* Store the key rings for each participant.
|
||||
* The encryption manager stores the keys because the application layer might not be ready yet to handle the keys.
|
||||
* The keys are stored and can be retrieved later when the application layer is ready {@link RTCEncryptionManager#getEncryptionKeys}.
|
||||
*/
|
||||
_defineProperty(this, "participantKeyRings", new Map());
|
||||
// The current per-sender media key for this device
|
||||
_defineProperty(this, "outboundSession", null);
|
||||
/**
|
||||
* Ensures that there is only one distribute operation at a time for that call.
|
||||
*/
|
||||
_defineProperty(this, "currentKeyDistributionPromise", null);
|
||||
/**
|
||||
* The time to wait before using the outbound session after it has been distributed.
|
||||
* This is to ensure that the key is delivered to all participants before it is used.
|
||||
* When creating the first key, this is set to 0 so that the key can be used immediately.
|
||||
*/
|
||||
_defineProperty(this, "useKeyDelay", 5000);
|
||||
/**
|
||||
* We want to avoid rolling out a new outbound key when the previous one was created less than `keyRotationGracePeriodMs` milliseconds ago.
|
||||
* This is to avoid expensive key rotations when users quickly join the call in a row.
|
||||
*
|
||||
* This must be higher than `useKeyDelay` to have an effect.
|
||||
* If it is lower, the current key will always be older than the grace period.
|
||||
* @private
|
||||
*/
|
||||
_defineProperty(this, "keyRotationGracePeriodMs", 10000);
|
||||
/**
|
||||
* If a new key distribution is being requested while one is going on, we will set this flag to true.
|
||||
* This will ensure that a new round is started after the current one.
|
||||
* @private
|
||||
*/
|
||||
_defineProperty(this, "needToEnsureKeyAgain", false);
|
||||
/**
|
||||
* There is a possibility that keys arrive in the wrong order.
|
||||
* For example, after a quick join/leave/join, there will be 2 keys of index 0 distributed, and
|
||||
* if they are received in the wrong order, the stream won't be decryptable.
|
||||
* For that reason we keep a small buffer of keys for a limited time to disambiguate.
|
||||
* @private
|
||||
*/
|
||||
_defineProperty(this, "keyBuffer", new OutdatedKeyFilter());
|
||||
_defineProperty(this, "logger", undefined);
|
||||
_defineProperty(this, "rtcIdentityProvider", void 0);
|
||||
_defineProperty(this, "keysWithoutMatchingRTCMembership", []);
|
||||
_defineProperty(this, "onNewKeyReceived", (membership, keyBase64Encoded, index, timestamp) => {
|
||||
// `manageMediaKeys` is a stop-gap solution for now. The preferred way to handle this case would be instead
|
||||
// to create a NoOpEncryptionManager that does nothing and use it for the session.
|
||||
// This will be done when removing the legacy EncryptionManager.
|
||||
if (!this.manageMediaKeys) {
|
||||
this.logger?.warn(`Received key over transport ${membership.userId}:${membership.deviceId} at index ${index} but media keys are disabled`);
|
||||
return;
|
||||
}
|
||||
this.logger?.debug(`Received key over transport ${membership.userId}:${membership.deviceId} at index ${index}`);
|
||||
|
||||
// We received a new key, notify the video layer of this new key so that it can decrypt the frames properly.
|
||||
const keyBin = decodeBase64(keyBase64Encoded);
|
||||
const candidateInboundSession = {
|
||||
key: keyBin,
|
||||
membership,
|
||||
keyIndex: index,
|
||||
creationTS: timestamp
|
||||
};
|
||||
const outdated = this.keyBuffer.isOutdated(membership, candidateInboundSession);
|
||||
if (!outdated) {
|
||||
this.addKeyToParticipant(candidateInboundSession.key, candidateInboundSession.keyIndex, candidateInboundSession.membership);
|
||||
} else {
|
||||
this.logger?.info(`Received an out of order key for ${membership.userId}:${membership.deviceId}, dropping it`);
|
||||
}
|
||||
});
|
||||
this.ownMembership = ownMembership;
|
||||
this.getMemberships = getMemberships;
|
||||
this.transport = transport;
|
||||
this.onEncryptionKeysChanged = onEncryptionKeysChanged;
|
||||
this.logger = parentLogger?.getChild(`[EncryptionManager]`);
|
||||
this.rtcIdentityProvider = rtcBackendIdProvider ?? computeRtcIdentityRaw;
|
||||
}
|
||||
async getOwnRtcBackendIdentity() {
|
||||
if (this.ownRtcBackendIdentityCache) return this.ownRtcBackendIdentityCache;
|
||||
if (this.useHashedRtcBackendIdentity) {
|
||||
const {
|
||||
userId,
|
||||
deviceId,
|
||||
memberId
|
||||
} = this.ownMembership;
|
||||
this.logger?.info(
|
||||
// If we see this log multiple times, we need to reconsider the precompute call of getOwnRtcBackendIdentity
|
||||
`Computing RTC backend identity for ${userId}:${deviceId}:${memberId} (SHOULD ONLY BE CALLED ONCE)`);
|
||||
this.ownRtcBackendIdentityCache = await this.rtcIdentityProvider(userId, deviceId, memberId);
|
||||
} else {
|
||||
this.ownRtcBackendIdentityCache = `${this.ownMembership.userId}:${this.ownMembership.deviceId}`;
|
||||
}
|
||||
return this.ownRtcBackendIdentityCache;
|
||||
}
|
||||
getEncryptionKeys() {
|
||||
return new Map(this.participantKeyRings);
|
||||
}
|
||||
checkKeysWithoutMatchingRTCMembership() {
|
||||
const keyInfoTemp = this.keysWithoutMatchingRTCMembership;
|
||||
this.keysWithoutMatchingRTCMembership = [];
|
||||
keyInfoTemp.forEach(keyInfo => {
|
||||
this.addKeyToParticipant(keyInfo.key, keyInfo.keyIndex, keyInfo.membership);
|
||||
});
|
||||
}
|
||||
addKeyToParticipant(key, keyIndex, membership) {
|
||||
const knownRtcMembership = this.getMemberships();
|
||||
const fullMembership = knownRtcMembership.find(member => member.userId === membership.userId && member.deviceId === membership.deviceId);
|
||||
if (!fullMembership) {
|
||||
this.logger?.info(`No matching RTC membership for key from ${membership.userId}:${membership.deviceId}, delaying key addition`);
|
||||
this.keysWithoutMatchingRTCMembership.push({
|
||||
key,
|
||||
keyIndex,
|
||||
membership
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.addKeyToParticipantWithBackendIdentity(key, keyIndex, membership, fullMembership.rtcBackendIdentity);
|
||||
}
|
||||
addKeyToParticipantWithBackendIdentity(key, keyIndex, membership, rtcBackendIdentity) {
|
||||
const mapKey = getEncryptionKeyMapKey(membership);
|
||||
if (!this.participantKeyRings.has(mapKey)) {
|
||||
this.participantKeyRings.set(mapKey, []);
|
||||
}
|
||||
this.participantKeyRings.get(mapKey).push({
|
||||
key,
|
||||
keyIndex,
|
||||
membership,
|
||||
rtcBackendIdentity
|
||||
});
|
||||
this.onEncryptionKeysChanged(key, keyIndex, membership, rtcBackendIdentity);
|
||||
}
|
||||
join(joinConfig) {
|
||||
this.manageMediaKeys = joinConfig?.manageMediaKeys ?? true; // default to true
|
||||
this.useHashedRtcBackendIdentity = joinConfig?.unstableSendStickyEvents ?? false;
|
||||
this.useKeyDelay = joinConfig?.useKeyDelay ?? 1000;
|
||||
this.keyRotationGracePeriodMs = joinConfig?.keyRotationGracePeriodMs ?? 10000;
|
||||
this.transport.on(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
||||
void this.getOwnRtcBackendIdentity(); // precompute own identity
|
||||
|
||||
this.logger?.info(`Joining room`);
|
||||
this.transport.start();
|
||||
}
|
||||
leave() {
|
||||
this.transport.off(KeyTransportEvents.ReceivedKeys, this.onNewKeyReceived);
|
||||
this.transport.stop();
|
||||
this.participantKeyRings.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Will ensure that a new key is distributed and used to encrypt our media.
|
||||
* If there is already a key distribution in progress, it will schedule a new distribution round just after the current one is completed.
|
||||
* If this function is called repeatedly while a distribution is in progress,
|
||||
* the calls will be coalesced to a single new distribution (that will start just after the current one has completed).
|
||||
*/
|
||||
ensureKeyDistribution() {
|
||||
// `manageMediaKeys` is a stop-gap solution for now. The preferred way to handle this case would be instead
|
||||
// to create a NoOpEncryptionManager that does nothing and use it for the session.
|
||||
// This will be done when removing the legacy EncryptionManager.
|
||||
if (!this.manageMediaKeys) return;
|
||||
if (this.currentKeyDistributionPromise == null) {
|
||||
this.logger?.debug(`No active rollout, start a new one`);
|
||||
// start a rollout
|
||||
this.currentKeyDistributionPromise = this.rolloutOutboundKey().then(() => {
|
||||
this.logger?.debug(`Rollout completed`);
|
||||
this.currentKeyDistributionPromise = null;
|
||||
if (this.needToEnsureKeyAgain) {
|
||||
this.logger?.debug(`New Rollout needed`);
|
||||
this.needToEnsureKeyAgain = false;
|
||||
// rollout a new one
|
||||
this.ensureKeyDistribution();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// There is a rollout in progress, but a key rotation is requested (could be caused by a ownMembership change)
|
||||
// Remember that a new rotation is needed after the current one.
|
||||
this.logger?.debug(`Rollout in progress, a new rollout will be started after the current one`);
|
||||
this.needToEnsureKeyAgain = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when the ownMembership of the call changes.
|
||||
* This encryption manager is very basic, it will rotate the key everytime this is called.
|
||||
* @param oldMemberships - This parameter is not used here, but it is kept for compatibility with the interface.
|
||||
*/
|
||||
onMembershipsUpdate(oldMemberships = []) {
|
||||
this.logger?.trace(`onMembershipsUpdate`);
|
||||
|
||||
// Ensure the key is distributed. This will be no-op if the key is already being distributed to everyone.
|
||||
// If there is an ongoing distribution, it will be completed before a new one is started.
|
||||
this.ensureKeyDistribution();
|
||||
// ensure key emission to the rtc backend
|
||||
this.checkKeysWithoutMatchingRTCMembership();
|
||||
}
|
||||
async rolloutOutboundKey() {
|
||||
const isFirstKey = this.outboundSession == null;
|
||||
if (isFirstKey) {
|
||||
// create the first key
|
||||
const firstKey = {
|
||||
key: this.generateRandomKey(),
|
||||
creationTS: Date.now(),
|
||||
sharedWith: [],
|
||||
keyId: 0
|
||||
};
|
||||
this.outboundSession = firstKey;
|
||||
this.addKeyToParticipantWithBackendIdentity(firstKey.key, firstKey.keyId, this.ownMembership, await this.getOwnRtcBackendIdentity());
|
||||
}
|
||||
// get current memberships
|
||||
const toShareWith = this.getMemberships().filter(membership => {
|
||||
return membership.sender != undefined;
|
||||
}).map(membership => {
|
||||
return {
|
||||
userId: membership.sender,
|
||||
deviceId: membership.deviceId,
|
||||
membershipTs: membership.createdTs()
|
||||
};
|
||||
});
|
||||
let alreadySharedWith = this.outboundSession?.sharedWith ?? [];
|
||||
|
||||
// Some users might have rotate their ownMembership event (formally called fingerprint) meaning they might have
|
||||
// clear their key. Reset the `alreadySharedWith` flag for them.
|
||||
alreadySharedWith = alreadySharedWith.filter(x =>
|
||||
// If there was a member with same userId and deviceId but different membershipTs, we need to clear it
|
||||
!toShareWith.some(o => x.userId == o.userId && x.deviceId == o.deviceId && x.membershipTs != o.membershipTs));
|
||||
const anyLeft = alreadySharedWith.filter(x => !toShareWith.some(o => x.userId == o.userId && x.deviceId == o.deviceId && x.membershipTs == o.membershipTs));
|
||||
const anyJoined = toShareWith.filter(x => !alreadySharedWith.some(o => x.userId == o.userId && x.deviceId == o.deviceId && x.membershipTs == o.membershipTs));
|
||||
let toDistributeTo = [];
|
||||
let outboundKey;
|
||||
let hasKeyChanged = false;
|
||||
if (anyLeft.length > 0) {
|
||||
// We need to rotate the key
|
||||
const newOutboundKey = this.createNewOutboundSession();
|
||||
hasKeyChanged = true;
|
||||
toDistributeTo = toShareWith;
|
||||
outboundKey = newOutboundKey;
|
||||
} else if (anyJoined.length > 0) {
|
||||
const now = Date.now();
|
||||
const keyAge = now - this.outboundSession.creationTS;
|
||||
// If the current key is recently created (less than `keyRotationGracePeriodMs`), we can keep it and just distribute it to the new joiners.
|
||||
if (keyAge < this.keyRotationGracePeriodMs) {
|
||||
// keep the same key
|
||||
// XXX In the future we want to distribute a ratcheted key, not the current one
|
||||
this.logger?.debug(`New joiners detected, but the key is recent enough (age:${keyAge}), keeping it`);
|
||||
toDistributeTo = anyJoined;
|
||||
outboundKey = this.outboundSession;
|
||||
} else {
|
||||
// We need to rotate the key
|
||||
this.logger?.debug(`New joiners detected, rotating the key`);
|
||||
const newOutboundKey = this.createNewOutboundSession();
|
||||
hasKeyChanged = true;
|
||||
toDistributeTo = toShareWith;
|
||||
outboundKey = newOutboundKey;
|
||||
}
|
||||
} else {
|
||||
// no changes
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.logger?.trace(`Sending key...`);
|
||||
await this.transport.sendKey(encodeBase64(outboundKey.key), outboundKey.keyId, toDistributeTo);
|
||||
outboundKey.sharedWith.push(...toDistributeTo);
|
||||
this.logger?.trace(`key index:${outboundKey.keyId} sent to ${outboundKey.sharedWith.map(m => `${m.userId}:${m.deviceId}`).join(",")}`);
|
||||
if (hasKeyChanged) {
|
||||
// Delay a bit before using this key
|
||||
// It is recommended not to start using a key immediately but instead wait for a short time to make sure it is delivered.
|
||||
this.logger?.trace(`Delay Rollout for key:${outboundKey.keyId}...`);
|
||||
await sleep(this.useKeyDelay);
|
||||
this.logger?.trace(`...Delayed rollout of index:${outboundKey.keyId} `);
|
||||
this.addKeyToParticipantWithBackendIdentity(outboundKey.key, outboundKey.keyId, this.ownMembership, await this.getOwnRtcBackendIdentity());
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger?.error(`Failed to rollout key`, err);
|
||||
}
|
||||
}
|
||||
createNewOutboundSession() {
|
||||
const newOutboundKey = {
|
||||
key: this.generateRandomKey(),
|
||||
creationTS: Date.now(),
|
||||
sharedWith: [],
|
||||
keyId: this.nextKeyIndex()
|
||||
};
|
||||
this.logger?.info(`creating new outbound key index:${newOutboundKey.keyId}`);
|
||||
// Set this new key as the current one
|
||||
this.outboundSession = newOutboundKey;
|
||||
return newOutboundKey;
|
||||
}
|
||||
nextKeyIndex() {
|
||||
if (this.outboundSession) {
|
||||
return (this.outboundSession.keyId + 1) % 256;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
generateRandomKey() {
|
||||
const key = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RTCEncryptionManager.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/RTCEncryptionManager.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
30
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.d.ts
generated
vendored
Normal file
30
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.d.ts
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||
import { type IKeyTransport, KeyTransportEvents, type KeyTransportEventsHandlerMap } from "./IKeyTransport.ts";
|
||||
import { type Logger } from "../logger.ts";
|
||||
import { type ParticipantDeviceInfo, type Statistics } from "./types.ts";
|
||||
import { type MatrixClient } from "../client.ts";
|
||||
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
|
||||
export declare class NotSupportedError extends Error {
|
||||
constructor(message?: string);
|
||||
get name(): string;
|
||||
}
|
||||
/**
|
||||
* ToDeviceKeyTransport is used to send MatrixRTC keys to other devices using the
|
||||
* to-device CS-API.
|
||||
*/
|
||||
export declare class ToDeviceKeyTransport extends TypedEventEmitter<KeyTransportEvents, KeyTransportEventsHandlerMap> implements IKeyTransport {
|
||||
private membership;
|
||||
private roomId;
|
||||
private client;
|
||||
private statistics;
|
||||
private logger;
|
||||
setParentLogger(parentLogger: Logger): void;
|
||||
constructor(membership: CallMembershipIdentityParts, roomId: string, client: Pick<MatrixClient, "encryptAndSendToDevice" | "on" | "off">, statistics: Statistics, parentLogger?: Logger);
|
||||
start(): void;
|
||||
stop(): void;
|
||||
sendKey(keyBase64Encoded: string, index: number, members: ParticipantDeviceInfo[]): Promise<void>;
|
||||
private receiveCallKeyEvent;
|
||||
private onToDeviceEvent;
|
||||
private getValidEventContent;
|
||||
}
|
||||
//# sourceMappingURL=ToDeviceKeyTransport.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ToDeviceKeyTransport.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/ToDeviceKeyTransport.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,KAAK,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAC/G,OAAO,EAAE,KAAK,MAAM,EAAwB,MAAM,cAAc,CAAC;AACjE,OAAO,EAA2C,KAAK,qBAAqB,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAClH,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAG9D,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAE1E,qBAAa,iBAAkB,SAAQ,KAAK;IACxC,YAAmB,OAAO,CAAC,EAAE,MAAM,EAElC;IACD,IAAW,IAAI,IAAI,MAAM,CAExB;CACJ;AACD;;;GAGG;AACH,qBAAa,oBACT,SAAQ,iBAAiB,CAAC,kBAAkB,EAAE,4BAA4B,CAC1E,YAAW,aAAa;IASpB,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU;IAVtB,OAAO,CAAC,MAAM,CAAsB;IAE7B,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEjD;IAED,YACY,UAAU,EAAE,2BAA2B,EACvC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,wBAAwB,GAAG,IAAI,GAAG,KAAK,CAAC,EACnE,UAAU,EAAE,UAAU,EAC9B,YAAY,CAAC,EAAE,MAAM,EAIxB;IAEM,KAAK,IAAI,IAAI,CAEnB;IAEM,IAAI,IAAI,IAAI,CAElB;IAEY,OAAO,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD7G;IAED,OAAO,CAAC,mBAAmB;IA2B3B,OAAO,CAAC,eAAe,CAoBrB;IAEF,OAAO,CAAC,oBAAoB;CA0B/B"}
|
||||
159
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.js
generated
vendored
Normal file
159
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.js
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { TypedEventEmitter } from "../models/typed-event-emitter.js";
|
||||
import { KeyTransportEvents } from "./IKeyTransport.js";
|
||||
import { logger as rootLogger } from "../logger.js";
|
||||
import { ClientEvent } from "../client.js";
|
||||
import { EventType } from "../@types/event.js";
|
||||
export class NotSupportedError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
}
|
||||
get name() {
|
||||
return "NotSupportedError";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ToDeviceKeyTransport is used to send MatrixRTC keys to other devices using the
|
||||
* to-device CS-API.
|
||||
*/
|
||||
export class ToDeviceKeyTransport extends TypedEventEmitter {
|
||||
setParentLogger(parentLogger) {
|
||||
this.logger = parentLogger.getChild(`[ToDeviceKeyTransport]`);
|
||||
}
|
||||
constructor(membership, roomId, client, statistics, parentLogger) {
|
||||
super();
|
||||
_defineProperty(this, "logger", rootLogger);
|
||||
_defineProperty(this, "onToDeviceEvent", event => {
|
||||
if (event.getType() !== EventType.CallEncryptionKeysPrefix) {
|
||||
// Ignore this is not a call encryption event
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Not possible to check if the event is encrypted or not
|
||||
// see https://github.com/matrix-org/matrix-rust-sdk/issues/4883
|
||||
// if (evnt.getWireType() != EventType.RoomMessageEncrypted) {
|
||||
// // WARN: The call keys were sent in clear. Ignore them
|
||||
// logger.warn(`Call encryption keys sent in clear from: ${event.getSender()}`);
|
||||
// return;
|
||||
// }
|
||||
|
||||
const content = this.getValidEventContent(event);
|
||||
if (!content) return;
|
||||
if (!event.getSender()) return;
|
||||
this.receiveCallKeyEvent(event.getSender(), content);
|
||||
});
|
||||
this.membership = membership;
|
||||
this.roomId = roomId;
|
||||
this.client = client;
|
||||
this.statistics = statistics;
|
||||
this.setParentLogger(parentLogger ?? rootLogger);
|
||||
}
|
||||
start() {
|
||||
this.client.on(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
||||
}
|
||||
stop() {
|
||||
this.client.off(ClientEvent.ToDeviceEvent, this.onToDeviceEvent);
|
||||
}
|
||||
async sendKey(keyBase64Encoded, index, members) {
|
||||
const content = {
|
||||
keys: {
|
||||
index: index,
|
||||
key: keyBase64Encoded
|
||||
},
|
||||
room_id: this.roomId,
|
||||
member: {
|
||||
claimed_device_id: this.membership.deviceId,
|
||||
id: this.membership.memberId
|
||||
},
|
||||
session: {
|
||||
call_id: "",
|
||||
application: "m.call",
|
||||
scope: "m.room"
|
||||
},
|
||||
sent_ts: Date.now()
|
||||
};
|
||||
const targets = members.map(member => {
|
||||
return {
|
||||
userId: member.userId,
|
||||
deviceId: member.deviceId
|
||||
};
|
||||
})
|
||||
// filter out me
|
||||
.filter(member => !(member.userId == this.membership.userId && member.deviceId == this.membership.deviceId));
|
||||
if (targets.length > 0) {
|
||||
await this.client.encryptAndSendToDevice(EventType.CallEncryptionKeysPrefix, targets, content).catch(error => {
|
||||
const msg = error.message;
|
||||
// This is not ideal. We would want to have a custom error type for unsupported actions.
|
||||
// This is not part of the widget API spec. Since as of now there are only two implementations:
|
||||
// Rust SDK + JS-SDK, and the JS-SDK does support to-device sending, we can assume that
|
||||
// this is a widget driver issue error message.
|
||||
if (msg.includes("unknown variant") && msg.includes("send_to_device") || msg.includes("not supported")) {
|
||||
throw new NotSupportedError("The widget driver does not support to-device encryption");
|
||||
}
|
||||
});
|
||||
this.statistics.counters.roomEventEncryptionKeysSent += 1;
|
||||
} else {
|
||||
this.logger.warn("No targets found for sending key");
|
||||
}
|
||||
}
|
||||
receiveCallKeyEvent(fromUser, content) {
|
||||
// The event has already been validated at this point.
|
||||
|
||||
this.statistics.counters.roomEventEncryptionKeysReceived += 1;
|
||||
|
||||
// What is this, and why is it needed?
|
||||
// Also to device events do not have an origin server ts
|
||||
const now = Date.now();
|
||||
const age = now - (typeof content.sent_ts === "number" ? content.sent_ts : now);
|
||||
this.statistics.totals.roomEventEncryptionKeysReceivedTotalAge += age;
|
||||
const hardcodedMemberIdAlternative = `${fromUser}:${content.member.claimed_device_id}`;
|
||||
this.emit(KeyTransportEvents.ReceivedKeys,
|
||||
// TODO userId this is claimed information, deviceId is claimed information
|
||||
{
|
||||
userId: fromUser,
|
||||
deviceId: content.member.claimed_device_id,
|
||||
memberId: content.member.id ?? hardcodedMemberIdAlternative
|
||||
}, content.keys.key, content.keys.index, now);
|
||||
}
|
||||
getValidEventContent(event) {
|
||||
const content = event.getContent();
|
||||
const roomId = content.room_id;
|
||||
if (!roomId) {
|
||||
// Invalid event
|
||||
this.logger.warn("Malformed Event: invalid call encryption keys event, no roomId");
|
||||
return;
|
||||
}
|
||||
if (roomId !== this.roomId) {
|
||||
this.logger.warn("Malformed Event: Mismatch roomId");
|
||||
return;
|
||||
}
|
||||
if (!content.keys || !content.keys.key || typeof content.keys.index !== "number") {
|
||||
this.logger.warn("Malformed Event: Missing keys field");
|
||||
return;
|
||||
}
|
||||
if (!content.member || !content.member.claimed_device_id) {
|
||||
this.logger.warn("Malformed Event: Missing claimed_device_id");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO check for session related fields once the to-device encryption uses the new format.
|
||||
return content;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=ToDeviceKeyTransport.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/ToDeviceKeyTransport.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
9
node_modules/matrix-js-sdk/lib/matrixrtc/index.d.ts
generated
vendored
Normal file
9
node_modules/matrix-js-sdk/lib/matrixrtc/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from "./CallMembership.ts";
|
||||
export * from "./LivekitTransport.ts";
|
||||
export * from "./MatrixRTCSession.ts";
|
||||
export * from "./MatrixRTCSessionManager.ts";
|
||||
export type * from "./types.ts";
|
||||
export { type SessionMembershipData, type RtcMembershipData } from "./membershipData/index.ts";
|
||||
export { Status, parseCallNotificationContent, isMyMembership, RTC_SLOT_ENCRYPTION_PER_MEMBER } from "./types.ts";
|
||||
export { MembershipManagerEvent } from "./IMembershipManager.ts";
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/index.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/index.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/index.ts"],"names":[],"mappings":"AAgBA,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,mBAAmB,YAAY,CAAC;AAChC,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,4BAA4B,EAAE,cAAc,EAAE,8BAA8B,EAAE,MAAM,YAAY,CAAC;AAClH,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC"}
|
||||
23
node_modules/matrix-js-sdk/lib/matrixrtc/index.js
generated
vendored
Normal file
23
node_modules/matrix-js-sdk/lib/matrixrtc/index.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export * from "./CallMembership.js";
|
||||
export * from "./LivekitTransport.js";
|
||||
export * from "./MatrixRTCSession.js";
|
||||
export * from "./MatrixRTCSessionManager.js";
|
||||
export { Status, parseCallNotificationContent, isMyMembership, RTC_SLOT_ENCRYPTION_PER_MEMBER } from "./types.js";
|
||||
export { MembershipManagerEvent } from "./IMembershipManager.js";
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/index.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":[],"sources":["../../src/matrixrtc/index.ts"],"sourcesContent":["/*\nCopyright 2024 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport * from \"./CallMembership.ts\";\nexport * from \"./LivekitTransport.ts\";\nexport * from \"./MatrixRTCSession.ts\";\nexport * from \"./MatrixRTCSessionManager.ts\";\nexport type * from \"./types.ts\";\nexport { type SessionMembershipData, type RtcMembershipData } from \"./membershipData/index.ts\";\nexport { Status, parseCallNotificationContent, isMyMembership, RTC_SLOT_ENCRYPTION_PER_MEMBER } from \"./types.ts\";\nexport { MembershipManagerEvent } from \"./IMembershipManager.ts\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,cAAc,qBAAqB;AACnC,cAAc,uBAAuB;AACrC,cAAc,uBAAuB;AACrC,cAAc,8BAA8B;AAG5C,SAAS,MAAM,EAAE,4BAA4B,EAAE,cAAc,EAAE,8BAA8B,QAAQ,YAAY;AACjH,SAAS,sBAAsB,QAAQ,yBAAyB","ignoreList":[]}
|
||||
8
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.d.ts
generated
vendored
Normal file
8
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.d.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Thrown when an event is not valid for use with MatrixRTC.
|
||||
*/
|
||||
export declare class MatrixRTCMembershipParseError extends AggregateError {
|
||||
readonly type: string;
|
||||
constructor(type: string, errors: string[]);
|
||||
}
|
||||
//# sourceMappingURL=common.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/matrixrtc/membershipData/common.ts"],"names":[],"mappings":"AAgBA;;GAEG;AACH,qBAAa,6BAA8B,SAAQ,cAAc;aAEzC,IAAI,EAAE,MAAM;IADhC,YACoB,IAAI,EAAE,MAAM,EAC5B,MAAM,EAAE,MAAM,EAAE,EAGnB;CACJ"}
|
||||
26
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.js
generated
vendored
Normal file
26
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Thrown when an event is not valid for use with MatrixRTC.
|
||||
*/
|
||||
export class MatrixRTCMembershipParseError extends AggregateError {
|
||||
constructor(type, errors) {
|
||||
super(errors, `Does not match ${type}:\n${errors.join("\n")}`);
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=common.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/common.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"common.js","names":[],"sources":["../../../src/matrixrtc/membershipData/common.ts"],"sourcesContent":["/*\nCopyright 2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/**\n * Thrown when an event is not valid for use with MatrixRTC.\n */\nexport class MatrixRTCMembershipParseError extends AggregateError {\n public constructor(\n public readonly type: string,\n errors: string[],\n ) {\n super(errors, `Does not match ${type}:\\n${errors.join(\"\\n\")}`);\n }\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,OAAO,MAAM,6BAA6B,SAAS,cAAc,CAAC;EACvD,WAAW,CACE,IAAY,EAC5B,MAAgB,EAClB;IACE,KAAK,CAAC,MAAM,EAAE,kBAAkB,IAAI,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAAC,KAH/C,IAAY,GAAZ,IAAY;EAIhC;AACJ","ignoreList":[]}
|
||||
4
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.d.ts
generated
vendored
Normal file
4
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
export { type SessionMembershipData, checkSessionsMembershipData } from "./session.ts";
|
||||
export { type RtcMembershipData, computeRtcIdentityRaw, checkRtcMembershipData } from "./rtc.ts";
|
||||
export { MatrixRTCMembershipParseError } from "./common.ts";
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/matrixrtc/membershipData/index.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,qBAAqB,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AACvF,OAAO,EAAE,KAAK,iBAAiB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AACjG,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC"}
|
||||
20
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.js
generated
vendored
Normal file
20
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export { checkSessionsMembershipData } from "./session.js";
|
||||
export { computeRtcIdentityRaw, checkRtcMembershipData } from "./rtc.js";
|
||||
export { MatrixRTCMembershipParseError } from "./common.js";
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/index.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","names":[],"sources":["../../../src/matrixrtc/membershipData/index.ts"],"sourcesContent":["/*\nCopyright 2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport { type SessionMembershipData, checkSessionsMembershipData } from \"./session.ts\";\nexport { type RtcMembershipData, computeRtcIdentityRaw, checkRtcMembershipData } from \"./rtc.ts\";\nexport { MatrixRTCMembershipParseError } from \"./common.ts\";\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAAqC,2BAA2B,QAAQ,cAAc;AACtF,SAAiC,qBAAqB,EAAE,sBAAsB,QAAQ,UAAU;AAChG,SAAS,6BAA6B,QAAQ,aAAa","ignoreList":[]}
|
||||
36
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.d.ts
generated
vendored
Normal file
36
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.d.ts
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { IContent } from "../../models/event.ts";
|
||||
import type { RelationType } from "../../types.ts";
|
||||
import { type RtcSlotApplicationContent, type Transport } from "../types.ts";
|
||||
/**
|
||||
* Represents the current form of MSC4143, which uses sticky events to store membership.
|
||||
*/
|
||||
export interface RtcMembershipData {
|
||||
"slot_id": string;
|
||||
"member": {
|
||||
user_id: string;
|
||||
device_id: string;
|
||||
id: string;
|
||||
};
|
||||
"m.relates_to"?: {
|
||||
event_id: string;
|
||||
rel_type: RelationType.Reference;
|
||||
};
|
||||
"application": RtcSlotApplicationContent;
|
||||
"transports": {
|
||||
published: Transport[];
|
||||
can_subscribe: string[];
|
||||
};
|
||||
"versions": string[];
|
||||
"msc4354_sticky_key"?: string;
|
||||
"sticky_key"?: string;
|
||||
}
|
||||
/**
|
||||
* Validates that `data` matches the format expected by MSC4143.
|
||||
* @param data The event content.
|
||||
* @param sender The sender of the event.
|
||||
* @returns true if `data` is valid RtcMembershipData
|
||||
* @throws {MatrixRTCMembershipParseError} if the content is not valid
|
||||
*/
|
||||
export declare const checkRtcMembershipData: (data: IContent, sender: string) => data is RtcMembershipData;
|
||||
export declare function computeRtcIdentityRaw(userId: string, deviceId: string, memberId: string): Promise<string>;
|
||||
//# sourceMappingURL=rtc.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"rtc.d.ts","sourceRoot":"","sources":["../../../src/matrixrtc/membershipData/rtc.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7E;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;KACd,CAAC;IACF,cAAc,CAAC,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC;KACpC,CAAC;IACF,aAAa,EAAE,yBAAyB,CAAC;IACzC,YAAY,EAAE;QACV,SAAS,EAAE,SAAS,EAAE,CAAC;QACvB,aAAa,EAAE,MAAM,EAAE,CAAC;KAC3B,CAAC;IACF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,SAAU,QAAQ,UAAU,MAAM,KAAG,IAAI,IAAI,iBAuG/E,CAAC;AAEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAM/G"}
|
||||
133
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.js
generated
vendored
Normal file
133
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MXID_PATTERN } from "../../models/room-member.js";
|
||||
import { MatrixRTCMembershipParseError } from "./common.js";
|
||||
import { sha256 } from "../../digest.js";
|
||||
import { encodeUnpaddedBase64 } from "../../base64.js";
|
||||
import { slotIdToDescription } from "../utils.js";
|
||||
|
||||
/**
|
||||
* Represents the current form of MSC4143, which uses sticky events to store membership.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Validates that `data` matches the format expected by MSC4143.
|
||||
* @param data The event content.
|
||||
* @param sender The sender of the event.
|
||||
* @returns true if `data` is valid RtcMembershipData
|
||||
* @throws {MatrixRTCMembershipParseError} if the content is not valid
|
||||
*/
|
||||
export const checkRtcMembershipData = (data, sender) => {
|
||||
const errors = [];
|
||||
const prefix = " - ";
|
||||
const expectedSlotPrefix = `${data?.application?.type}#`;
|
||||
|
||||
// required fields
|
||||
if (typeof data.slot_id !== "string") {
|
||||
errors.push(prefix + "slot_id must be string");
|
||||
} else if (!data.slot_id.startsWith(expectedSlotPrefix)) {
|
||||
errors.push(prefix + `slot_id must start with ${expectedSlotPrefix}`);
|
||||
} else {
|
||||
try {
|
||||
slotIdToDescription(data.slot_id);
|
||||
} catch (ex) {
|
||||
errors.push(prefix + `slot_id was badly formed${ex instanceof Error ? `: ${ex.message}` : ""}`);
|
||||
}
|
||||
}
|
||||
if (typeof data.member !== "object" || data.member === null) {
|
||||
errors.push(prefix + "member must be an object");
|
||||
} else {
|
||||
if (typeof data.member.user_id !== "string") {
|
||||
errors.push(prefix + "member.user_id must be string");
|
||||
} else if (!MXID_PATTERN.test(data.member.user_id)) {
|
||||
errors.push(prefix + "member.user_id must be a valid mxid");
|
||||
}
|
||||
// This is not what the spec enforces but there currently are no rules what power levels are required to
|
||||
// send a m.rtc.member event for a other user. So we add this check for simplicity and to avoid possible attacks until there
|
||||
// is a proper definition when this is allowed.
|
||||
else if (data.member.user_id !== sender) {
|
||||
errors.push(prefix + "member.user_id must match the sender");
|
||||
}
|
||||
if (typeof data.member.device_id !== "string") {
|
||||
errors.push(prefix + "member.device_id must be string");
|
||||
}
|
||||
if (typeof data.member.id !== "string") errors.push(prefix + "member.id must be string");
|
||||
}
|
||||
if (typeof data.application !== "object" || data.application === null) {
|
||||
errors.push(prefix + "application must be an object");
|
||||
} else {
|
||||
if (typeof data.application.type !== "string") {
|
||||
errors.push(prefix + "application.type must be a string");
|
||||
} else {
|
||||
if (data.application.type.includes("#")) errors.push(prefix + 'application.type must not include "#"');
|
||||
}
|
||||
}
|
||||
if (typeof data.transports !== "object" || data.transports === null || !Array.isArray(data.transports.published)) {
|
||||
errors.push(prefix + "transports.published must be an array");
|
||||
} else {
|
||||
// validate that each transport has at least a string 'type'
|
||||
for (const t of data.transports.published) {
|
||||
if (typeof t !== "object" || t === null || typeof t.type !== "string") {
|
||||
errors.push(prefix + "transports.published entries must be objects with a string type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof data.transports !== "object" || data.transports === null || !Array.isArray(data.transports.can_subscribe) || !data.transports.can_subscribe.every(t => typeof t === "string")) {
|
||||
errors.push(prefix + "transports.can_subscribe must be an array of strings");
|
||||
}
|
||||
if (data.versions === undefined || !Array.isArray(data.versions)) {
|
||||
errors.push(prefix + "versions must be an array");
|
||||
} else if (!data.versions.every(v => typeof v === "string")) {
|
||||
errors.push(prefix + "versions must be an array of strings");
|
||||
}
|
||||
|
||||
// optional fields
|
||||
if ((data.sticky_key ?? data.msc4354_sticky_key) === undefined) {
|
||||
errors.push(prefix + "sticky_key or msc4354_sticky_key must be a defined");
|
||||
}
|
||||
if (data.sticky_key !== undefined && typeof data.sticky_key !== "string") {
|
||||
errors.push(prefix + "sticky_key must be a string");
|
||||
}
|
||||
if (data.msc4354_sticky_key !== undefined && typeof data.msc4354_sticky_key !== "string") {
|
||||
errors.push(prefix + "msc4354_sticky_key must be a string");
|
||||
}
|
||||
if (data.sticky_key !== undefined && data.msc4354_sticky_key !== undefined && data.sticky_key !== data.msc4354_sticky_key) {
|
||||
errors.push(prefix + "sticky_key and msc4354_sticky_key must be equal if both are defined");
|
||||
}
|
||||
if (data["m.relates_to"] !== undefined) {
|
||||
const rel = data["m.relates_to"];
|
||||
if (typeof rel !== "object" || rel === null) {
|
||||
errors.push(prefix + "m.relates_to must be an object if provided");
|
||||
} else {
|
||||
if (typeof rel.event_id !== "string") errors.push(prefix + "m.relates_to.event_id must be a string");
|
||||
if (rel.rel_type !== "m.reference") errors.push(prefix + "m.relates_to.rel_type must be m.reference");
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
throw new MatrixRTCMembershipParseError("RtcMembership", errors);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
export async function computeRtcIdentityRaw(userId, deviceId, memberId) {
|
||||
// canonical JSON serialization (Matrix canonical JSON for arrays)
|
||||
const jsonStr = JSON.stringify([userId, deviceId, memberId]);
|
||||
const hashBuffer = await sha256(jsonStr);
|
||||
const hashedString = encodeUnpaddedBase64(hashBuffer);
|
||||
return hashedString;
|
||||
}
|
||||
//# sourceMappingURL=rtc.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/rtc.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
77
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.d.ts
generated
vendored
Normal file
77
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.d.ts
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import { type IContent } from "../../matrix.ts";
|
||||
import { type RTCCallIntent, type Transport } from "../types.ts";
|
||||
/**
|
||||
* (MatrixRTC) session membership data.
|
||||
* This represents the *OLD* form of MSC4143, which uses state events to store membership.
|
||||
* Represents the `session` 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.
|
||||
* NOTE: This is still included for legacy reasons, but not consumed by the SDK.
|
||||
*/
|
||||
"focus_active": {
|
||||
type: "livekit" | string;
|
||||
focus_selection: "oldest_membership" | "multi_sfu" | string;
|
||||
};
|
||||
/**
|
||||
* A list of possible foci this user knows about. One of them might be used based on the focus_active
|
||||
* selection system.
|
||||
*/
|
||||
"foci_preferred": Transport[];
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* If the `application` = `"m.call"` this defines if it is a room or user owned call.
|
||||
* There can always be one room scoped call but multiple user owned calls (breakout sessions)
|
||||
*/
|
||||
"scope"?: "m.room" | "m.user";
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* The intent of the call from the perspective of this user. This may be an audio call, video call or
|
||||
* something else.
|
||||
*/
|
||||
"m.call.intent"?: RTCCallIntent;
|
||||
/**
|
||||
* The id used on the media backend.
|
||||
* (With livekit this is the participant identity on the LK SFU)
|
||||
* This can be a UUID but right now it is `${this.matrixEventData.sender}:${data.device_id}`.
|
||||
*
|
||||
* It is compleatly valid to not set this field. Other clients will treat `undefined` as `${this.matrixEventData.sender}:${data.device_id}`
|
||||
*/
|
||||
"membershipID"?: string;
|
||||
};
|
||||
/**
|
||||
* Validates that `data` matches the format expected by the legacy form of MSC4143.
|
||||
* @param data The event content.
|
||||
* @returns true if `data` is valid SessionMembershipData
|
||||
* @throws {MatrixRTCMembershipParseError} if the content is not valid
|
||||
*/
|
||||
export declare const checkSessionsMembershipData: (data: IContent) => data is SessionMembershipData;
|
||||
//# sourceMappingURL=session.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/matrixrtc/membershipData/session.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAGjE;;;;IAII;AACJ,MAAM,MAAM,qBAAqB,GAAG;IAChC;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,cAAc,EAAE;QACZ,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;QACzB,eAAe,EAAE,mBAAmB,GAAG,WAAW,GAAG,MAAM,CAAC;KAC/D,CAAC;IAEF;;;OAGG;IACH,gBAAgB,EAAE,SAAS,EAAE,CAAC;IAE9B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAItB;;;OAGG;IACH,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAE9B;;;;QAII;IACJ,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC;IAEhC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,SAAU,QAAQ,KAAG,IAAI,IAAI,qBAwCpE,CAAC"}
|
||||
61
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.js
generated
vendored
Normal file
61
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright 2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixRTCMembershipParseError } from "./common.js";
|
||||
|
||||
/**
|
||||
* (MatrixRTC) session membership data.
|
||||
* This represents the *OLD* form of MSC4143, which uses state events to store membership.
|
||||
* Represents the `session` in the memberships section of an m.call.member event as it is on the wire.
|
||||
**/
|
||||
|
||||
/**
|
||||
* Validates that `data` matches the format expected by the legacy form of MSC4143.
|
||||
* @param data The event content.
|
||||
* @returns true if `data` is valid SessionMembershipData
|
||||
* @throws {MatrixRTCMembershipParseError} if the content is not valid
|
||||
*/
|
||||
export const checkSessionsMembershipData = data => {
|
||||
const prefix = " - ";
|
||||
const errors = [];
|
||||
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 (data.focus_active === undefined) {
|
||||
errors.push(prefix + "focus_active has an invalid type");
|
||||
}
|
||||
if (typeof data.focus_active?.type !== "string") {
|
||||
errors.push(prefix + "focus_active.type must be a string");
|
||||
}
|
||||
if (data.foci_preferred !== undefined && !(Array.isArray(data.foci_preferred) && data.foci_preferred.every(f => typeof f === "object" && f !== null && typeof f.type === "string"))) {
|
||||
errors.push(prefix + "foci_preferred must be an array of transport objects");
|
||||
}
|
||||
// optional parameters
|
||||
if (data.created_ts !== undefined && 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 !== undefined && typeof data.scope !== "string") errors.push(prefix + "scope must be string");
|
||||
if (data["m.call.intent"] !== undefined && typeof data["m.call.intent"] !== "string") {
|
||||
errors.push(prefix + "m.call.intent must be a string");
|
||||
}
|
||||
if (errors.length) {
|
||||
throw new MatrixRTCMembershipParseError("SessionMembership", errors);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
//# sourceMappingURL=session.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/membershipData/session.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
182
node_modules/matrix-js-sdk/lib/matrixrtc/types.d.ts
generated
vendored
Normal file
182
node_modules/matrix-js-sdk/lib/matrixrtc/types.d.ts
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
import type { IContent, IMentions } from "../matrix.ts";
|
||||
import type { RelationEvent } from "../types.ts";
|
||||
import type { CallMembership } from "./CallMembership.ts";
|
||||
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
|
||||
export type EncryptionKeyMapKey = string;
|
||||
export interface EncryptionKeyEntry {
|
||||
index: number;
|
||||
key: string;
|
||||
}
|
||||
/**
|
||||
* The mxID, deviceId and membership timestamp of a RTC session participant.
|
||||
*/
|
||||
export type ParticipantDeviceInfo = {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
membershipTs: number;
|
||||
};
|
||||
/**
|
||||
* A type representing the information needed to decrypt video streams.
|
||||
*/
|
||||
export type InboundEncryptionSession = {
|
||||
key: Uint8Array<ArrayBuffer>;
|
||||
membership: CallMembershipIdentityParts;
|
||||
keyIndex: number;
|
||||
creationTS: number;
|
||||
};
|
||||
/**
|
||||
* The information about the key used to encrypt video streams.
|
||||
*/
|
||||
export type OutboundEncryptionSession = {
|
||||
key: Uint8Array<ArrayBuffer>;
|
||||
creationTS: number;
|
||||
sharedWith: Array<ParticipantDeviceInfo>;
|
||||
keyId: number;
|
||||
};
|
||||
export interface EncryptionKeysEventContent {
|
||||
keys: EncryptionKeyEntry[];
|
||||
device_id: string;
|
||||
call_id: string;
|
||||
sent_ts?: number;
|
||||
}
|
||||
/**
|
||||
* THe content of a to-device event that contains encryption keys.
|
||||
*/
|
||||
export interface EncryptionKeysToDeviceEventContent {
|
||||
keys: {
|
||||
index: number;
|
||||
key: string;
|
||||
};
|
||||
member: {
|
||||
id: string;
|
||||
claimed_device_id: string;
|
||||
};
|
||||
room_id: string;
|
||||
session: {
|
||||
application: string;
|
||||
call_id: string;
|
||||
scope: string;
|
||||
};
|
||||
sent_ts?: number;
|
||||
}
|
||||
/**
|
||||
* @deprecated Use `RTCNotificationType` instead.
|
||||
*/
|
||||
export type CallNotifyType = "ring" | "notify";
|
||||
/**
|
||||
* @deprecated Use `IRTCNotificationContent` instead.
|
||||
*/
|
||||
export interface ICallNotifyContent {
|
||||
"application": string;
|
||||
"m.mentions": IMentions;
|
||||
"notify_type": CallNotifyType;
|
||||
"call_id": string;
|
||||
}
|
||||
export type RTCNotificationType = "ring" | "notification";
|
||||
/**
|
||||
* Represents the intention of the call from the perspective of the sending user.
|
||||
* May be any string, although `"audio"` and `"video"` are commonly accepted values.
|
||||
*/
|
||||
export type RTCCallIntent = "audio" | "video" | string;
|
||||
/**
|
||||
* This will check if the content has all the expected fields to be a valid IRTCNotificationContent.
|
||||
* It will also cap the lifetime to 90000ms (1.5 min) if a higher value is provided.
|
||||
* @param content
|
||||
* @throws if the content is invalid
|
||||
* @returns a parsed IRTCNotificationContent
|
||||
*/
|
||||
export declare function parseCallNotificationContent(content: IContent): IRTCNotificationContent;
|
||||
/**
|
||||
* Interface for `org.matrix.msc4075.rtc.notification` events.
|
||||
* Don't cast event content to this directly. Use `parseCallNotificationContent` instead to validate the content first.
|
||||
*/
|
||||
export interface IRTCNotificationContent extends RelationEvent {
|
||||
"m.mentions"?: IMentions;
|
||||
"notification_type": RTCNotificationType;
|
||||
/**
|
||||
* The initial intent of the calling user.
|
||||
*/
|
||||
"m.call.intent"?: RTCCallIntent;
|
||||
"sender_ts": number;
|
||||
"lifetime": number;
|
||||
}
|
||||
/**
|
||||
* MSC4310 decline event content for `org.matrix.msc4310.rtc.decline`.
|
||||
* Sent as a standard m.reference relation to an `org.matrix.msc4075.rtc.notification` event.
|
||||
*/
|
||||
export interface IRTCDeclineContent extends RelationEvent {
|
||||
}
|
||||
export declare enum Status {
|
||||
Disconnected = "Disconnected",
|
||||
Connecting = "Connecting",
|
||||
Connected = "Connected",
|
||||
Disconnecting = "Disconnecting",
|
||||
Unknown = "Unknown"
|
||||
}
|
||||
/**
|
||||
* A type collecting call encryption statistics for a session.
|
||||
*/
|
||||
export type Statistics = {
|
||||
counters: {
|
||||
/**
|
||||
* The number of times we have sent a room event containing encryption keys.
|
||||
*/
|
||||
roomEventEncryptionKeysSent: number;
|
||||
/**
|
||||
* The number of times we have received a room event containing encryption keys.
|
||||
*/
|
||||
roomEventEncryptionKeysReceived: number;
|
||||
};
|
||||
totals: {
|
||||
/**
|
||||
* The total age (in milliseconds) of all room events containing encryption keys that we have received.
|
||||
* We track the total age so that we can later calculate the average age of all keys received.
|
||||
*/
|
||||
roomEventEncryptionKeysReceivedTotalAge: number;
|
||||
};
|
||||
};
|
||||
export declare const isMyMembership: (m: CallMembership, userId: string, deviceId: string) => boolean;
|
||||
/**
|
||||
* A RTC transport is a JSON object that describes how to connect to a RTC member.
|
||||
*/
|
||||
export interface Transport {
|
||||
type: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
/**
|
||||
* Event content for `org.matrix.msc4143.rtc.slot` state events.
|
||||
*/
|
||||
export interface RtcSlotEventContent<T extends string = string> {
|
||||
status: "open" | "closed";
|
||||
application?: RtcSlotApplicationContent<T>;
|
||||
encryption?: RtcSlotEncryptionContent;
|
||||
}
|
||||
/**
|
||||
* Content of the `application` object within `org.matrix.msc4143.rtc.slot` events.
|
||||
*/
|
||||
export interface RtcSlotApplicationContent<T extends string = string> {
|
||||
type: T;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
export declare const RTC_SLOT_ENCRYPTION_PER_MEMBER = "org.matrix.msc4143.per_member";
|
||||
/**
|
||||
* Content of the `encryption` object within `org.matrix.msc4143.rtc.slot` events.
|
||||
*/
|
||||
export interface RtcSlotEncryptionContent {
|
||||
type: typeof RTC_SLOT_ENCRYPTION_PER_MEMBER;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
/**
|
||||
* The session description is used to identify a session. Used in the state event.
|
||||
*/
|
||||
export interface SlotDescription {
|
||||
/**
|
||||
* The application type. e.g. "m.call".
|
||||
*/
|
||||
application: string;
|
||||
/**
|
||||
* The application-specific slot ID. e.g. "ROOM".
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/types.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/types.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/types.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAE1E,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEzC,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACnC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC7B,UAAU,EAAE,2BAA2B,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACpC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IAEnB,UAAU,EAAE,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAEzC,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACvC,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,kCAAkC;IAC/C,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACrC,MAAM,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QAGX,iBAAiB,EAAE,MAAM,CAAC;KAC7B,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AACD;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC/C;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,SAAS,CAAC;IACxB,aAAa,EAAE,cAAc,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,cAAc,CAAC;AAE1D;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,QAAQ,GAAG,uBAAuB,CAuBvF;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC1D,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC;;OAEG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,aAAa;CAAG;AAE5D,oBAAY,MAAM;IACd,YAAY,iBAAiB;IAC7B,UAAU,eAAe;IACzB,SAAS,cAAc;IACvB,aAAa,kBAAkB;IAC/B,OAAO,YAAY;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,QAAQ,EAAE;QACN;;WAEG;QACH,2BAA2B,EAAE,MAAM,CAAC;QACpC;;WAEG;QACH,+BAA+B,EAAE,MAAM,CAAC;KAC3C,CAAC;IACF,MAAM,EAAE;QACJ;;;WAGG;QACH,uCAAuC,EAAE,MAAM,CAAC;KACnD,CAAC;CACL,CAAC;AAEF,eAAO,MAAM,cAAc,MAAO,cAAc,UAAU,MAAM,YAAY,MAAM,KAAG,OACnC,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC1D,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,WAAW,CAAC,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;IAC3C,UAAU,CAAC,EAAE,wBAAwB,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAChE,IAAI,EAAE,CAAC,CAAC;IAER,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,eAAO,MAAM,8BAA8B,kCAAkC,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,OAAO,8BAA8B,CAAC;IAE5C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;CACd"}
|
||||
125
node_modules/matrix-js-sdk/lib/matrixrtc/types.js
generated
vendored
Normal file
125
node_modules/matrix-js-sdk/lib/matrixrtc/types.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
|
||||
/*
|
||||
Copyright 2023-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The mxID, deviceId and membership timestamp of a RTC session participant.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A type representing the information needed to decrypt video streams.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The information about the key used to encrypt video streams.
|
||||
*/
|
||||
|
||||
/**
|
||||
* THe content of a to-device event that contains encryption keys.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated Use `RTCNotificationType` instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @deprecated Use `IRTCNotificationContent` instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents the intention of the call from the perspective of the sending user.
|
||||
* May be any string, although `"audio"` and `"video"` are commonly accepted values.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This will check if the content has all the expected fields to be a valid IRTCNotificationContent.
|
||||
* It will also cap the lifetime to 90000ms (1.5 min) if a higher value is provided.
|
||||
* @param content
|
||||
* @throws if the content is invalid
|
||||
* @returns a parsed IRTCNotificationContent
|
||||
*/
|
||||
export function parseCallNotificationContent(content) {
|
||||
if (content["m.mentions"] && typeof content["m.mentions"] !== "object") {
|
||||
throw new Error("malformed m.mentions");
|
||||
}
|
||||
if (typeof content["notification_type"] !== "string") {
|
||||
throw new Error("Missing or invalid notification_type");
|
||||
}
|
||||
if (typeof content["sender_ts"] !== "number") {
|
||||
throw new Error("Missing or invalid sender_ts");
|
||||
}
|
||||
if (typeof content["lifetime"] !== "number") {
|
||||
throw new Error("Missing or invalid lifetime");
|
||||
}
|
||||
if (content["relation"] && content["relation"]["rel_type"] !== "m.reference") {
|
||||
throw new Error("Invalid relation");
|
||||
}
|
||||
if (content["m.call.intent"] && typeof content["m.call.intent"] !== "string") {
|
||||
throw new Error("Invalid m.call.intent");
|
||||
}
|
||||
const cappedLifetime = content["lifetime"] >= 90000 ? 90000 : content["lifetime"];
|
||||
return _objectSpread(_objectSpread({}, content), {}, {
|
||||
lifetime: cappedLifetime
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for `org.matrix.msc4075.rtc.notification` events.
|
||||
* Don't cast event content to this directly. Use `parseCallNotificationContent` instead to validate the content first.
|
||||
*/
|
||||
|
||||
/**
|
||||
* MSC4310 decline event content for `org.matrix.msc4310.rtc.decline`.
|
||||
* Sent as a standard m.reference relation to an `org.matrix.msc4075.rtc.notification` event.
|
||||
*/
|
||||
|
||||
export let Status = /*#__PURE__*/function (Status) {
|
||||
Status["Disconnected"] = "Disconnected";
|
||||
Status["Connecting"] = "Connecting";
|
||||
Status["Connected"] = "Connected";
|
||||
Status["Disconnecting"] = "Disconnecting";
|
||||
Status["Unknown"] = "Unknown";
|
||||
return Status;
|
||||
}({});
|
||||
|
||||
/**
|
||||
* A type collecting call encryption statistics for a session.
|
||||
*/
|
||||
|
||||
export const isMyMembership = (m, userId, deviceId) => m.sender === userId && m.deviceId === deviceId;
|
||||
|
||||
/**
|
||||
* A RTC transport is a JSON object that describes how to connect to a RTC member.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Event content for `org.matrix.msc4143.rtc.slot` state events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Content of the `application` object within `org.matrix.msc4143.rtc.slot` events.
|
||||
*/
|
||||
|
||||
export const RTC_SLOT_ENCRYPTION_PER_MEMBER = "org.matrix.msc4143.per_member";
|
||||
|
||||
/**
|
||||
* Content of the `encryption` object within `org.matrix.msc4143.rtc.slot` events.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The session description is used to identify a session. Used in the state event.
|
||||
*/
|
||||
//# sourceMappingURL=types.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/types.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/types.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
26
node_modules/matrix-js-sdk/lib/matrixrtc/utils.d.ts
generated
vendored
Normal file
26
node_modules/matrix-js-sdk/lib/matrixrtc/utils.d.ts
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type CallMembershipIdentityParts } from "./EncryptionManager.ts";
|
||||
import type { InboundEncryptionSession, SlotDescription } from "./types.ts";
|
||||
/**
|
||||
* Detects when a key for a given index is outdated.
|
||||
*/
|
||||
export declare class OutdatedKeyFilter {
|
||||
private tsBuffer;
|
||||
/**
|
||||
* Check if there is a recent key with the same keyId (index) and then use the creationTS to decide what to
|
||||
* do with the key. If the key received is older than the one already in the buffer, it is ignored.
|
||||
* @param participantId
|
||||
* @param item
|
||||
*/
|
||||
isOutdated(membership: CallMembershipIdentityParts, item: InboundEncryptionSession): boolean;
|
||||
}
|
||||
/**
|
||||
* Converts a slot ID into it's component application and ID portions.
|
||||
* @param slotId e.g. `m.call#call_id`
|
||||
* @throws If the format of `slotId` is invalid.
|
||||
*/
|
||||
export declare function slotIdToDescription(slotId: string): SlotDescription;
|
||||
/**
|
||||
* Converts a SlotDescription into it's slot ID format.
|
||||
*/
|
||||
export declare function computeSlotId(slotDescription: SlotDescription): string;
|
||||
//# sourceMappingURL=utils.d.ts.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/utils.d.ts.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/utils.d.ts.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/matrixrtc/utils.ts"],"names":[],"mappings":"AAgBA,OAAO,EAA0B,KAAK,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAClG,OAAO,KAAK,EAAE,wBAAwB,EAAuB,eAAe,EAAE,MAAM,YAAY,CAAC;AAEjG;;GAEG;AACH,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAA4D;IAE5E;;;;;OAKG;IACI,UAAU,CAAC,UAAU,EAAE,2BAA2B,EAAE,IAAI,EAAE,wBAAwB,GAAG,OAAO,CAalG;CACJ;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAQnE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,eAAe,EAAE,eAAe,GAAG,MAAM,CAEtE"}
|
||||
70
node_modules/matrix-js-sdk/lib/matrixrtc/utils.js
generated
vendored
Normal file
70
node_modules/matrix-js-sdk/lib/matrixrtc/utils.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
||||
/*
|
||||
Copyright 2025-2026 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { getEncryptionKeyMapKey } from "./EncryptionManager.js";
|
||||
/**
|
||||
* Detects when a key for a given index is outdated.
|
||||
*/
|
||||
export class OutdatedKeyFilter {
|
||||
constructor() {
|
||||
// Map of participantId -> keyIndex -> timestamp
|
||||
_defineProperty(this, "tsBuffer", new Map());
|
||||
}
|
||||
/**
|
||||
* Check if there is a recent key with the same keyId (index) and then use the creationTS to decide what to
|
||||
* do with the key. If the key received is older than the one already in the buffer, it is ignored.
|
||||
* @param participantId
|
||||
* @param item
|
||||
*/
|
||||
isOutdated(membership, item) {
|
||||
const mapKey = getEncryptionKeyMapKey(membership);
|
||||
if (!this.tsBuffer.has(mapKey)) {
|
||||
this.tsBuffer.set(mapKey, new Map());
|
||||
}
|
||||
const latestTimestamp = this.tsBuffer.get(mapKey)?.get(item.keyIndex);
|
||||
if (latestTimestamp && latestTimestamp > item.creationTS) {
|
||||
// The existing key is more recent, ignore this one
|
||||
return true;
|
||||
}
|
||||
this.tsBuffer.get(mapKey).set(item.keyIndex, item.creationTS);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a slot ID into it's component application and ID portions.
|
||||
* @param slotId e.g. `m.call#call_id`
|
||||
* @throws If the format of `slotId` is invalid.
|
||||
*/
|
||||
export function slotIdToDescription(slotId) {
|
||||
const [application, id, ...unexpectedAdditionalValues] = slotId.split("#");
|
||||
if (unexpectedAdditionalValues.length) {
|
||||
throw Error("MatrixRTC Slot IDs *must* only contain two components seperated by one '#'. Additional '#' characters detected.");
|
||||
}
|
||||
return {
|
||||
application,
|
||||
id
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a SlotDescription into it's slot ID format.
|
||||
*/
|
||||
export function computeSlotId(slotDescription) {
|
||||
return `${slotDescription.application}#${slotDescription.id}`;
|
||||
}
|
||||
//# sourceMappingURL=utils.js.map
|
||||
1
node_modules/matrix-js-sdk/lib/matrixrtc/utils.js.map
generated
vendored
Normal file
1
node_modules/matrix-js-sdk/lib/matrixrtc/utils.js.map
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"utils.js","names":[],"sources":["../../src/matrixrtc/utils.ts"],"sourcesContent":["/*\nCopyright 2025-2026 The Matrix.org Foundation C.I.C.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { getEncryptionKeyMapKey, type CallMembershipIdentityParts } from \"./EncryptionManager.ts\";\nimport type { InboundEncryptionSession, EncryptionKeyMapKey, SlotDescription } from \"./types.ts\";\n\n/**\n * Detects when a key for a given index is outdated.\n */\nexport class OutdatedKeyFilter {\n // Map of participantId -> keyIndex -> timestamp\n private tsBuffer: Map<EncryptionKeyMapKey, Map<number, number>> = new Map();\n\n /**\n * Check if there is a recent key with the same keyId (index) and then use the creationTS to decide what to\n * do with the key. If the key received is older than the one already in the buffer, it is ignored.\n * @param participantId\n * @param item\n */\n public isOutdated(membership: CallMembershipIdentityParts, item: InboundEncryptionSession): boolean {\n const mapKey = getEncryptionKeyMapKey(membership);\n if (!this.tsBuffer.has(mapKey)) {\n this.tsBuffer.set(mapKey, new Map<number, number>());\n }\n\n const latestTimestamp = this.tsBuffer.get(mapKey)?.get(item.keyIndex);\n if (latestTimestamp && latestTimestamp > item.creationTS) {\n // The existing key is more recent, ignore this one\n return true;\n }\n this.tsBuffer.get(mapKey)!.set(item.keyIndex, item.creationTS);\n return false;\n }\n}\n\n/**\n * Converts a slot ID into it's component application and ID portions.\n * @param slotId e.g. `m.call#call_id`\n * @throws If the format of `slotId` is invalid.\n */\nexport function slotIdToDescription(slotId: string): SlotDescription {\n const [application, id, ...unexpectedAdditionalValues] = slotId.split(\"#\");\n if (unexpectedAdditionalValues.length) {\n throw Error(\n \"MatrixRTC Slot IDs *must* only contain two components seperated by one '#'. Additional '#' characters detected.\",\n );\n }\n return { application, id };\n}\n\n/**\n * Converts a SlotDescription into it's slot ID format.\n */\nexport function computeSlotId(slotDescription: SlotDescription): string {\n return `${slotDescription.application}#${slotDescription.id}`;\n}\n"],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,SAAS,sBAAsB,QAA0C,wBAAwB;AAGjG;AACA;AACA;AACA,OAAO,MAAM,iBAAiB,CAAC;EAAA;IAC3B;IAAA,kCACkE,IAAI,GAAG,CAAC,CAAC;EAAA;EAE3E;AACJ;AACA;AACA;AACA;AACA;EACW,UAAU,CAAC,UAAuC,EAAE,IAA8B,EAAW;IAChG,MAAM,MAAM,GAAG,sBAAsB,CAAC,UAAU,CAAC;IACjD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;MAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,CAAiB,CAAC,CAAC;IACxD;IAEA,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;IACrE,IAAI,eAAe,IAAI,eAAe,GAAG,IAAI,CAAC,UAAU,EAAE;MACtD;MACA,OAAO,IAAI;IACf;IACA,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;IAC9D,OAAO,KAAK;EAChB;AACJ;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS,mBAAmB,CAAC,MAAc,EAAmB;EACjE,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE,GAAG,0BAA0B,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;EAC1E,IAAI,0BAA0B,CAAC,MAAM,EAAE;IACnC,MAAM,KAAK,CACP,iHACJ,CAAC;EACL;EACA,OAAO;IAAE,WAAW;IAAE;EAAG,CAAC;AAC9B;;AAEA;AACA;AACA;AACA,OAAO,SAAS,aAAa,CAAC,eAAgC,EAAU;EACpE,OAAO,GAAG,eAAe,CAAC,WAAW,IAAI,eAAe,CAAC,EAAE,EAAE;AACjE","ignoreList":[]}
|
||||
Reference in New Issue
Block a user