You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
@@ -171,14 +171,6 @@ describe("CallMembership", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("RtcMembershipData", () => {
|
describe("RtcMembershipData", () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.useFakeTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
const membershipTemplate: RtcMembershipData = {
|
const membershipTemplate: RtcMembershipData = {
|
||||||
"slot_id": "m.call#",
|
"slot_id": "m.call#",
|
||||||
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
|
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export interface RtcMembershipData {
|
|||||||
};
|
};
|
||||||
"rtc_transports": Transport[];
|
"rtc_transports": Transport[];
|
||||||
"versions": string[];
|
"versions": string[];
|
||||||
|
"msc4354_sticky_key"?: string;
|
||||||
"sticky_key"?: string;
|
"sticky_key"?: string;
|
||||||
/**
|
/**
|
||||||
* The intent of the call from the perspective of this user. This may be an audio call, video call or
|
* The intent of the call from the perspective of this user. This may be an audio call, video call or
|
||||||
@@ -93,7 +94,8 @@ const checkRtcMembershipData = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// optional fields
|
// optional fields
|
||||||
if (data.sticky_key !== undefined && typeof data.sticky_key !== "string") {
|
const stickyKey = data.sticky_key ?? data.msc4354_sticky_key;
|
||||||
|
if (stickyKey !== undefined && typeof stickyKey !== "string") {
|
||||||
errors.push(prefix + "sticky_key must be a string");
|
errors.push(prefix + "sticky_key must be a string");
|
||||||
}
|
}
|
||||||
if (data["m.call.intent"] !== undefined && typeof data["m.call.intent"] !== "string") {
|
if (data["m.call.intent"] !== undefined && typeof data["m.call.intent"] !== "string") {
|
||||||
@@ -210,16 +212,20 @@ const checkSessionsMembershipData = (
|
|||||||
type MembershipData = { kind: "rtc"; data: RtcMembershipData } | { kind: "session"; data: SessionMembershipData };
|
type MembershipData = { kind: "rtc"; data: RtcMembershipData } | { kind: "session"; data: SessionMembershipData };
|
||||||
// TODO: Rename to RtcMembership once we removed the legacy SessionMembership from this file.
|
// TODO: Rename to RtcMembership once we removed the legacy SessionMembership from this file.
|
||||||
export class CallMembership {
|
export class CallMembership {
|
||||||
public static equal(a: CallMembership, b: CallMembership): boolean {
|
public static equal(a?: CallMembership, b?: CallMembership): boolean {
|
||||||
if (a === undefined || b === undefined) return a === b;
|
if (a === undefined || b === undefined) return a === b;
|
||||||
return deepCompare(a.membershipData, b.membershipData);
|
return deepCompare(a.membershipData, b.membershipData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private membershipData: MembershipData;
|
private membershipData: MembershipData;
|
||||||
|
|
||||||
private parentEventData: { eventId: string; sender: string };
|
/** 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 matrixEventData: { eventId: string; sender: string };
|
||||||
public constructor(
|
public constructor(
|
||||||
private parentEvent: MatrixEvent,
|
/** The Matrix event that this membership is based on */
|
||||||
|
private matrixEvent: MatrixEvent,
|
||||||
data: any,
|
data: any,
|
||||||
) {
|
) {
|
||||||
const sessionErrors: string[] = [];
|
const sessionErrors: string[] = [];
|
||||||
@@ -237,12 +243,12 @@ export class CallMembership {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventId = parentEvent.getId();
|
const eventId = matrixEvent.getId();
|
||||||
const sender = parentEvent.getSender();
|
const sender = matrixEvent.getSender();
|
||||||
|
|
||||||
if (eventId === undefined) throw new Error("parentEvent is missing eventId field");
|
if (eventId === undefined) throw new Error("parentEvent is missing eventId field");
|
||||||
if (sender === undefined) throw new Error("parentEvent is missing sender field");
|
if (sender === undefined) throw new Error("parentEvent is missing sender field");
|
||||||
this.parentEventData = { eventId, sender };
|
this.matrixEventData = { eventId, sender };
|
||||||
}
|
}
|
||||||
|
|
||||||
public get sender(): string {
|
public get sender(): string {
|
||||||
@@ -250,13 +256,14 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.member.user_id;
|
return data.member.user_id;
|
||||||
default: // "session":
|
case "session":
|
||||||
return this.parentEventData.sender;
|
default:
|
||||||
|
return this.matrixEventData.sender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get eventId(): string {
|
public get eventId(): string {
|
||||||
return this.parentEventData.eventId;
|
return this.matrixEventData.eventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +275,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.slot_id;
|
return data.slot_id;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return slotDescriptionToId({ application: this.application, id: data.call_id });
|
return slotDescriptionToId({ application: this.application, id: data.call_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +286,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.member.device_id;
|
return data.member.device_id;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return data.device_id;
|
return data.device_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,7 +308,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.application.type;
|
return data.application.type;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return data.application;
|
return data.application;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +318,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.application;
|
return data.application;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return { "type": data.application, "m.call.intent": data["m.call.intent"] };
|
return { "type": data.application, "m.call.intent": data["m.call.intent"] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -319,7 +330,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return undefined;
|
return undefined;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return data.scope;
|
return data.scope;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +344,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.member.id;
|
return data.member.id;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return (this.createdTs() ?? "").toString();
|
return (this.createdTs() ?? "").toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,9 +355,10 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
// TODO we need to read the referenced (relation) event if available to get the real created_ts
|
// TODO we need to read the referenced (relation) event if available to get the real created_ts
|
||||||
return this.parentEvent.getTs();
|
return this.matrixEvent.getTs();
|
||||||
default: // "session":
|
case "session":
|
||||||
return data.created_ts ?? this.parentEvent.getTs();
|
default:
|
||||||
|
return data.created_ts ?? this.matrixEvent.getTs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +371,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return undefined;
|
return undefined;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
// TODO: calculate this from the MatrixRTCSession join configuration directly
|
// TODO: calculate this from the MatrixRTCSession join configuration directly
|
||||||
return this.createdTs() + (data.expires ?? DEFAULT_EXPIRE_DURATION);
|
return this.createdTs() + (data.expires ?? DEFAULT_EXPIRE_DURATION);
|
||||||
}
|
}
|
||||||
@@ -371,7 +386,8 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return undefined;
|
return undefined;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
// Assume that local clock is sufficiently in sync with other clocks in the distributed system.
|
// 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.
|
// 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
|
// The current implementation allows for the local clock to be -infinity to +MatrixRTCSession.MEMBERSHIP_EXPIRY_TIME/2
|
||||||
@@ -387,14 +403,15 @@ export class CallMembership {
|
|||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return false;
|
return false;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return this.getMsUntilExpiry()! <= 0;
|
return this.getMsUntilExpiry()! <= 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## RTC Membership
|
* ## RTC Membership
|
||||||
* Gets the transport to use for this RTC membership (m.rtc.member).
|
* 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.
|
* This will return the primary transport that is used by this call membership to publish their media.
|
||||||
* Directly relates to the `rtc_transports` field.
|
* Directly relates to the `rtc_transports` field.
|
||||||
*
|
*
|
||||||
@@ -409,7 +426,6 @@ export class CallMembership {
|
|||||||
* Always required to make the consumer not care if it deals with RTC or session memberships.
|
* 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.
|
* @returns The transport this membership uses to publish media or undefined if no transport is available.
|
||||||
*/
|
*/
|
||||||
// TODO: make this return all transports used to publish media once this is supported.
|
|
||||||
public getTransport(oldestMembership: CallMembership): Transport | undefined {
|
public getTransport(oldestMembership: CallMembership): Transport | undefined {
|
||||||
const { kind, data } = this.membershipData;
|
const { kind, data } = this.membershipData;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
@@ -427,12 +443,17 @@ export class CallMembership {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The value of the `rtc_transports` field for RTC memberships (m.rtc.member).
|
||||||
|
* Or the value of the `foci_preferred` field for legacy session memberships (m.call.member).
|
||||||
|
*/
|
||||||
public get transports(): Transport[] {
|
public get transports(): Transport[] {
|
||||||
const { kind, data } = this.membershipData;
|
const { kind, data } = this.membershipData;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case "rtc":
|
case "rtc":
|
||||||
return data.rtc_transports;
|
return data.rtc_transports;
|
||||||
default: // "session":
|
case "session":
|
||||||
|
default:
|
||||||
return data.foci_preferred;
|
return data.foci_preferred;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2023 New Vector Ltd
|
Copyright 2025 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -363,6 +363,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
if (membershipContents.length === 0) continue;
|
if (membershipContents.length === 0) continue;
|
||||||
|
|
||||||
for (const membershipData of membershipContents) {
|
for (const membershipData of membershipContents) {
|
||||||
|
if (!("application" in membershipData)) {
|
||||||
|
// This is a left membership event, ignore it here to not log warnings.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const membership = new CallMembership(memberEvent, membershipData);
|
const membership = new CallMembership(memberEvent, membershipData);
|
||||||
|
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MXID_PATTERN = /@[^@:]+:[^@:]+/;
|
export const MXID_PATTERN = /@.+:.+/;
|
||||||
const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/;
|
const LTR_RTL_PATTERN = /[\u200E\u200F\u202A-\u202F]/;
|
||||||
|
|
||||||
function shouldDisambiguate(selfUserId: string, displayName?: string, roomState?: RoomState): boolean {
|
function shouldDisambiguate(selfUserId: string, displayName?: string, roomState?: RoomState): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user