1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-23 17:02:25 +03:00
Signed-off-by: Timo K <toger5@hotmail.de>
This commit is contained in:
Timo K
2025-10-07 14:44:57 +02:00
parent a343e8c92a
commit 11f610d7c7
5 changed files with 52 additions and 35 deletions

View File

@@ -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" },

View File

@@ -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;
} }
} }

View File

@@ -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.

View File

@@ -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);

View File

@@ -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 {