1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-25 05:23:13 +03:00

Merge branch 'develop' into toger5/use-relation-based-CallMembership-create-ts

This commit is contained in:
Timo K
2025-10-30 16:15:22 +01:00
32 changed files with 2450 additions and 1266 deletions

View File

@@ -17,7 +17,6 @@ limitations under the License.
import { type Logger, logger as rootLogger } from "../logger.ts";
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
import { EventTimeline } from "../models/event-timeline.ts";
import { MatrixEvent } from "../models/event.ts";
import { type Room } from "../models/room.ts";
import { type MatrixClient } from "../client.ts";
import { EventType, RelationType } from "../@types/event.ts";
@@ -25,7 +24,7 @@ import { KnownMembership } from "../@types/membership.ts";
import { type ISendEventResponse } from "../@types/requests.ts";
import { CallMembership } from "./CallMembership.ts";
import { RoomStateEvent } from "../models/room-state.ts";
import { MembershipManager } from "./MembershipManager.ts";
import { MembershipManager, StickyEventMembershipManager } from "./MembershipManager.ts";
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
import { deepCompare, logDurationSync } from "../utils.ts";
import type {
@@ -51,6 +50,8 @@ import {
} from "./RoomAndToDeviceKeyTransport.ts";
import { TypedReEmitter } from "../ReEmitter.ts";
import { ToDeviceKeyTransport } from "./ToDeviceKeyTransport.ts";
import { MatrixEvent } from "../models/event.ts";
import { RoomStickyEventsEvent, type RoomStickyEventsMap } from "../models/room-sticky-events.ts";
/**
* Events emitted by MatrixRTCSession
@@ -124,14 +125,6 @@ export function slotDescriptionToId(slotDescription: SlotDescription): string {
// - 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.
export interface MembershipConfig {
/**
* Use the new Manager.
*
* Default: `false`.
* @deprecated does nothing anymore we always default to the new membership manager.
*/
useNewMembershipManager?: boolean;
/**
* The timeout (in milliseconds) after we joined the call, that our membership should expire
* unless we have explicitly updated it.
@@ -193,7 +186,14 @@ export interface MembershipConfig {
* but only applies to calls to the `_unstable_updateDelayedEvent` endpoint with a body of `{action:"restart"}`.)
*/
delayedLeaveEventRestartLocalTimeoutMs?: number;
useRtcMemberFormat?: boolean;
/**
* 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 {
@@ -239,6 +239,19 @@ export interface EncryptionConfig {
}
export type JoinSessionConfig = SessionConfig & MembershipConfig & EncryptionConfig;
interface SessionMembershipsForRoomOpts {
/**
* 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.
@@ -308,7 +321,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
* @deprecated Use `MatrixRTCSession.sessionMembershipsForSlot` instead.
*/
public static async callMembershipsForRoom(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
room: Pick<
Room,
"getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "_unstable_getStickyEvents"
>,
client: Pick<MatrixClient, "fetchRoomEvent">,
): Promise<CallMembership[]> {
return await MatrixRTCSession.sessionMembershipsForSlot(room, client, {
@@ -321,7 +337,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
* @deprecated use `MatrixRTCSession.slotMembershipsForRoom` instead.
*/
public static async sessionMembershipsForRoom(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "client">,
room: Pick<
Room,
"getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "_unstable_getStickyEvents"
>,
client: Pick<MatrixClient, "fetchRoomEvent">,
sessionDescription: SlotDescription,
): Promise<CallMembership[]> {
@@ -331,23 +350,61 @@ export class MatrixRTCSession extends TypedEventEmitter<
/**
* 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.
*/
public static async sessionMembershipsForSlot(
room: Pick<Room, "getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById">,
room: Pick<
Room,
"getLiveTimeline" | "roomId" | "hasMembershipState" | "findEventById" | "_unstable_getStickyEvents"
>,
client: Pick<MatrixClient, "fetchRoomEvent">,
slotDescription: SlotDescription,
existingMemberships?: CallMembership[],
{ listenForStickyEvents, listenForMemberStateEvents }: SessionMembershipsForRoomOpts = {
listenForStickyEvents: true,
listenForMemberStateEvents: true,
},
): Promise<CallMembership[]> {
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId}]`);
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
if (!roomState) {
logger.warn("Couldn't get state for room " + room.roomId);
throw new Error("Could't get state for room " + room.roomId);
let callMemberEvents = [] as MatrixEvent[];
if (listenForStickyEvents) {
// 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);
throw new Error("Could't get state for room " + room.roomId);
}
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.
(stickyEvent) =>
stickyEvent.getContent().msc4354_sticky_key === callMemberStateEvent.getStateKey(),
),
),
);
}
const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
const callMemberships: CallMembership[] = [];
const createMembership = async (memberEvent: MatrixEvent): Promise<CallMembership | undefined> => {
const content = memberEvent.getContent();
// Ignore sticky keys for the count
const eventKeysCount = Object.keys(content).filter((k) => k !== "msc4354_sticky_key").length;
// Dont even bother about empty events (saves us from costly type/"key in" checks in bigger rooms)
if (eventKeysCount === 0) return undefined;
// We first decide if its a MSC4143 event (per device state key)
if (!(eventKeysCount > 1 && "application" in content)) return undefined;
const relatedEventId = memberEvent.relationEventId;
const fetchRelatedEvent = async (): Promise<MatrixEvent | undefined> => {
const eventData = await client
@@ -415,11 +472,21 @@ export class MatrixRTCSession extends TypedEventEmitter<
*
* @deprecated Use `MatrixRTCSession.sessionForSlot` with sessionDescription `{ id: "", application: "m.call" }` instead.
*/
public static async roomSessionForRoom(client: MatrixClient, room: Room): Promise<MatrixRTCSession> {
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, client, {
id: "",
application: "m.call",
});
public static async roomSessionForRoom(
client: MatrixClient,
room: Room,
opts?: SessionMembershipsForRoomOpts,
): Promise<MatrixRTCSession> {
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
room,
client,
{
id: "",
application: "m.call",
},
undefined,
opts,
);
return new MatrixRTCSession(client, room, callMemberships, { id: "", application: "m.call" });
}
@@ -443,8 +510,15 @@ export class MatrixRTCSession extends TypedEventEmitter<
client: MatrixClient,
room: Room,
slotDescription: SlotDescription,
opts?: SessionMembershipsForRoomOpts,
): Promise<MatrixRTCSession> {
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(room, client, slotDescription);
const callMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
room,
client,
slotDescription,
undefined,
opts,
);
return new MatrixRTCSession(client, room, callMemberships, slotDescription);
}
@@ -478,10 +552,12 @@ export class MatrixRTCSession extends TypedEventEmitter<
| "off"
| "getUserId"
| "getDeviceId"
| "sendEvent"
| "sendStateEvent"
| "_unstable_sendDelayedStateEvent"
| "_unstable_updateDelayedEvent"
| "sendEvent"
| "_unstable_sendStickyEvent"
| "_unstable_sendStickyDelayedEvent"
| "cancelPendingEvent"
| "encryptAndSendToDevice"
| "decryptEventIfNeeded"
@@ -489,7 +565,14 @@ export class MatrixRTCSession extends TypedEventEmitter<
>,
private roomSubset: Pick<
Room,
"on" | "off" | "getLiveTimeline" | "roomId" | "getVersion" | "hasMembershipState" | "findEventById"
| "on"
| "off"
| "getLiveTimeline"
| "roomId"
| "getVersion"
| "hasMembershipState"
| "findEventById"
| "_unstable_getStickyEvents"
>,
public memberships: CallMembership[],
/**
@@ -504,9 +587,10 @@ export class MatrixRTCSession extends TypedEventEmitter<
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
// TODO: double check if this is actually needed. Should be covered by refreshRoom in MatrixRTCSessionManager
roomState?.on(RoomStateEvent.Members, this.onRoomMemberUpdate);
this.roomSubset.on(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
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.
@@ -526,7 +610,9 @@ export class MatrixRTCSession extends TypedEventEmitter<
}
const roomState = this.roomSubset.getLiveTimeline().getState(EventTimeline.FORWARDS);
roomState?.off(RoomStateEvent.Members, this.onRoomMemberUpdate);
this.roomSubset.off(RoomStickyEventsEvent.Update, this.onStickyEventUpdate);
}
private reEmitter = new TypedReEmitter<
MatrixRTCSessionEvent | RoomAndToDeviceEvents | MembershipManagerEvent,
MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap & MembershipManagerEventHandlerMap
@@ -556,14 +642,15 @@ export class MatrixRTCSession extends TypedEventEmitter<
return;
} else {
// Create MembershipManager and pass the RTCSession logger (with room id info)
this.membershipManager = new MembershipManager(
joinConfig,
this.roomSubset,
this.client,
this.slotDescription,
this.logger,
);
this.membershipManager = joinConfig?.unstableSendStickyEvents
? new StickyEventMembershipManager(
joinConfig,
this.roomSubset,
this.client,
this.slotDescription,
this.logger,
)
: new MembershipManager(joinConfig, this.roomSubset, this.client, this.slotDescription, this.logger);
this.reEmitter.reEmit(this.membershipManager!, [
MembershipManagerEvent.ProbablyLeft,
@@ -802,10 +889,27 @@ export class MatrixRTCSession extends TypedEventEmitter<
/**
* Call this when the Matrix room members have changed.
*/
public onRoomMemberUpdate = (): void => {
public readonly onRoomMemberUpdate = (): void => {
void this.recalculateSessionMembers();
};
/**
* Call this when a sticky event update has occured.
*/
private readonly onStickyEventUpdate: RoomStickyEventsMap[RoomStickyEventsEvent.Update] = (
added,
updated,
removed,
): void => {
if (
[...added, ...removed, ...updated.flatMap((v) => [v.current, v.previous])].some(
(e) => e.getType() === EventType.RTCMembership,
)
) {
void this.recalculateSessionMembers();
}
};
/**
* Call this when something changed that may impacts the current MatrixRTC members in this session.
*/
@@ -861,6 +965,8 @@ export class MatrixRTCSession extends TypedEventEmitter<
// 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)