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

Updates and tests

This commit is contained in:
Half-Shot
2025-09-29 12:36:05 +01:00
parent 953b7d7dea
commit d376e942c9
4 changed files with 275 additions and 220 deletions

View File

@@ -18,7 +18,6 @@ import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEve
import { KnownMembership } from "../../../src/@types/membership";
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
import { Status, type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
import { secureRandomString } from "../../../src/randomstring";
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey, type MembershipData, mockRoomState } from "./mocks";
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts";
@@ -47,11 +46,34 @@ describe("MatrixRTCSession", () => {
sess = undefined;
});
describe("roomSessionForRoom", () => {
describe.each([
{
listenForStickyEvents: true,
listenForMemberStateEvents: true,
testCreateSticky: false,
},
{
listenForStickyEvents: false,
listenForMemberStateEvents: true,
testCreateSticky: false,
},
{
listenForStickyEvents: true,
listenForMemberStateEvents: true,
testCreateSticky: true,
},
{
listenForStickyEvents: true,
listenForMemberStateEvents: false,
testCreateSticky: true,
},
])(
"roomSessionForRoom listenForSticky=$listenForStickyEvents listenForMemberStateEvents=$listenForStickyEvents testCreateSticky=$testCreateSticky",
(testConfig) => {
it("creates a room-scoped session from room state", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
const mockRoom = makeMockRoom([membershipTemplate], testConfig.testCreateSticky);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].sessionDescription.id).toEqual("");
expect(sess?.memberships[0].scope).toEqual("m.room");
@@ -65,8 +87,8 @@ describe("MatrixRTCSession", () => {
const testMembership = Object.assign({}, membershipTemplate, {
application: "not-m.call",
});
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const mockRoom = makeMockRoom([testMembership], testConfig.testCreateSticky);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships).toHaveLength(0);
});
@@ -75,8 +97,8 @@ describe("MatrixRTCSession", () => {
call_id: "not-empty",
scope: "m.room",
});
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const mockRoom = makeMockRoom([testMembership], testConfig.testCreateSticky);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships).toHaveLength(0);
});
@@ -85,19 +107,19 @@ describe("MatrixRTCSession", () => {
const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.expires = 1000;
expiredMembership.device_id = "EXPIRED";
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership]);
const mockRoom = makeMockRoom([membershipTemplate, expiredMembership], testConfig.testCreateSticky);
jest.advanceTimersByTime(2000);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships.length).toEqual(1);
expect(sess?.memberships[0].deviceId).toEqual("AAAAAAA");
jest.useRealTimers();
});
it("ignores memberships events of members not in the room", () => {
const mockRoom = makeMockRoom([membershipTemplate]);
const mockRoom = makeMockRoom([membershipTemplate], testConfig.testCreateSticky);
mockRoom.hasMembershipState = (state) => state === KnownMembership.Join;
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships.length).toEqual(0);
});
@@ -107,20 +129,19 @@ describe("MatrixRTCSession", () => {
const expiredMembership = Object.assign({}, membershipTemplate);
expiredMembership.created_ts = 500;
expiredMembership.expires = 1000;
const mockRoom = makeMockRoom([expiredMembership]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const mockRoom = makeMockRoom([expiredMembership], testConfig.testCreateSticky);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships[0].getAbsoluteExpiry()).toEqual(1500);
jest.useRealTimers();
});
it("returns empty session if no membership events are present", () => {
const mockRoom = makeMockRoom([]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const mockRoom = makeMockRoom([], testConfig.testCreateSticky);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess?.memberships).toHaveLength(0);
});
it("safely ignores events with no memberships section", () => {
const roomId = secureRandomString(8);
const event = {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
getContent: jest.fn().mockReturnValue({}),
@@ -128,10 +149,8 @@ describe("MatrixRTCSession", () => {
getTs: jest.fn().mockReturnValue(1000),
getLocalAge: jest.fn().mockReturnValue(0),
};
const mockRoom = {
...makeMockRoom([]),
roomId,
getLiveTimeline: jest.fn().mockReturnValue({
const mockRoom = makeMockRoom([]);
mockRoom.getLiveTimeline = jest.fn().mockReturnValue({
getState: jest.fn().mockReturnValue({
on: jest.fn(),
off: jest.fn(),
@@ -148,14 +167,12 @@ describe("MatrixRTCSession", () => {
],
]),
}),
}),
};
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
});
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess.memberships).toHaveLength(0);
});
it("safely ignores events with junk memberships section", () => {
const roomId = secureRandomString(8);
const event = {
getType: jest.fn().mockReturnValue(EventType.GroupCallMemberPrefix),
getContent: jest.fn().mockReturnValue({ memberships: ["i am a fish"] }),
@@ -163,10 +180,8 @@ describe("MatrixRTCSession", () => {
getTs: jest.fn().mockReturnValue(1000),
getLocalAge: jest.fn().mockReturnValue(0),
};
const mockRoom = {
...makeMockRoom([]),
roomId,
getLiveTimeline: jest.fn().mockReturnValue({
const mockRoom = makeMockRoom([]);
mockRoom.getLiveTimeline = jest.fn().mockReturnValue({
getState: jest.fn().mockReturnValue({
on: jest.fn(),
off: jest.fn(),
@@ -183,9 +198,8 @@ describe("MatrixRTCSession", () => {
],
]),
}),
}),
};
sess = MatrixRTCSession.sessionForRoom(client, mockRoom as unknown as Room, callSession);
});
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess.memberships).toHaveLength(0);
});
@@ -193,7 +207,7 @@ describe("MatrixRTCSession", () => {
const testMembership = Object.assign({}, membershipTemplate);
(testMembership.device_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
const sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess.memberships).toHaveLength(0);
});
@@ -201,10 +215,11 @@ describe("MatrixRTCSession", () => {
const testMembership = Object.assign({}, membershipTemplate);
(testMembership.call_id as string | undefined) = undefined;
const mockRoom = makeMockRoom([testMembership]);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
sess = MatrixRTCSession.sessionForRoom(client, mockRoom, callSession, testConfig);
expect(sess.memberships).toHaveLength(0);
});
});
},
);
describe("getOldestMembership", () => {
it("returns the oldest membership event", () => {

View File

@@ -74,10 +74,11 @@ export function makeMockClient(userId: string, deviceId: string): MockClient {
export function makeMockRoom(
membershipData: MembershipData[],
useStickyEvents = false,
): Room & { emitTimelineEvent: (event: MatrixEvent) => void } {
const roomId = secureRandomString(8);
// Caching roomState here so it does not get recreated when calling `getLiveTimeline.getState()`
const roomState = makeMockRoomState(membershipData, roomId);
const roomState = makeMockRoomState(useStickyEvents ? [] : membershipData, roomId);
const room = Object.assign(new EventEmitter(), {
roomId: roomId,
hasMembershipState: jest.fn().mockReturnValue(true),
@@ -85,7 +86,9 @@ export function makeMockRoom(
getState: jest.fn().mockReturnValue(roomState),
}),
getVersion: jest.fn().mockReturnValue("default"),
unstableGetStickyEvents: jest.fn().mockReturnValue([]),
unstableGetStickyEvents: jest
.fn()
.mockReturnValue(useStickyEvents ? membershipData.map((m) => mockRTCEvent(m, roomId)) : []),
}) as unknown as Room;
return Object.assign(room, {
emitTimelineEvent: (event: MatrixEvent) =>

View File

@@ -25,7 +25,7 @@ import { type ISendEventResponse } from "../@types/requests.ts";
import { CallMembership } from "./CallMembership.ts";
import { RoomStateEvent } from "../models/room-state.ts";
import { type Focus } from "./focus.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 {
@@ -117,14 +117,6 @@ export interface SessionDescription {
// - 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.
@@ -188,10 +180,11 @@ export interface MembershipConfig {
delayedLeaveEventRestartLocalTimeoutMs?: number;
/**
* If the membership manager should publish its own membership via sticky events or via the room state.
* @default false (room state)
* Send membership using sticky events rather than state events.
*
* **WARNING**: This is an unstable feature and not all clients will support it.
*/
useStickyEvents?: boolean;
unstableSendStickyEvents?: boolean;
}
export interface EncryptionConfig {
@@ -237,6 +230,11 @@ export interface EncryptionConfig {
}
export type JoinSessionConfig = SessionConfig & MembershipConfig & EncryptionConfig;
interface SessionMembershipsForRoomOpts {
listenForStickyEvents: boolean;
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.
@@ -315,18 +313,21 @@ export class MatrixRTCSession extends TypedEventEmitter<
sessionDescription: SessionDescription,
// default both true this implied we combine sticky and state events for the final call state
// (prefer sticky events in case of a duplicate)
useStickyEvents: boolean = true,
useStateEvents: boolean = true,
{ listenForStickyEvents, listenForMemberStateEvents }: SessionMembershipsForRoomOpts = {
listenForStickyEvents: true,
listenForMemberStateEvents: true,
},
): CallMembership[] {
const logger = rootLogger.getChild(`[MatrixRTCSession ${room.roomId}]`);
let callMemberEvents = [] as MatrixEvent[];
if (useStickyEvents) {
if (listenForStickyEvents) {
logger.info("useStickyEvents");
// prefill with sticky events
callMemberEvents = Array.from(room.unstableGetStickyEvents()).filter(
(e) => e.getType() === EventType.GroupCallMemberPrefix,
);
}
if (useStateEvents) {
if (listenForMemberStateEvents) {
const roomState = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
if (!roomState) {
logger.warn("Couldn't get state for room " + room.roomId);
@@ -337,7 +338,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
callMemberStateEvents.filter((e) =>
callMemberEvents.some((stickyEvent) => stickyEvent.getContent().state_key === e.getStateKey()),
);
callMemberEvents.concat(callMemberStateEvents);
callMemberEvents = callMemberEvents.concat(callMemberStateEvents);
}
const callMemberships: CallMembership[] = [];
@@ -406,8 +407,16 @@ export class MatrixRTCSession extends TypedEventEmitter<
*
* @deprecated Use `MatrixRTCSession.sessionForRoom` with sessionDescription `{ id: "", application: "m.call" }` instead.
*/
public static roomSessionForRoom(client: MatrixClient, room: Room): MatrixRTCSession {
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(room, { id: "", application: "m.call" });
public static roomSessionForRoom(
client: MatrixClient,
room: Room,
opts?: SessionMembershipsForRoomOpts,
): MatrixRTCSession {
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(
room,
{ id: "", application: "m.call" },
opts,
);
return new MatrixRTCSession(client, room, callMemberships, { id: "", application: "m.call" });
}
@@ -420,8 +429,9 @@ export class MatrixRTCSession extends TypedEventEmitter<
client: MatrixClient,
room: Room,
sessionDescription: SessionDescription,
opts?: SessionMembershipsForRoomOpts,
): MatrixRTCSession {
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(room, sessionDescription);
const callMemberships = MatrixRTCSession.sessionMembershipsForRoom(room, sessionDescription, opts);
return new MatrixRTCSession(client, room, callMemberships, sessionDescription);
}
@@ -507,6 +517,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
roomState?.off(RoomStateEvent.Members, this.onRoomMemberUpdate);
this.roomSubset.off(RoomEvent.StickyEvents, this.onStickyEventUpdate);
}
private reEmitter = new TypedReEmitter<
MatrixRTCSessionEvent | RoomAndToDeviceEvents | MembershipManagerEvent,
MatrixRTCSessionEventHandlerMap & RoomAndToDeviceEventsHandlerMap & MembershipManagerEventHandlerMap
@@ -532,8 +543,16 @@ export class MatrixRTCSession extends TypedEventEmitter<
return;
} else {
// Create MembershipManager and pass the RTCSession logger (with room id info)
this.membershipManager = new MembershipManager(
this.membershipManager = joinConfig?.unstableSendStickyEvents
? new StickyEventMembershipManager(
joinConfig,
this.roomSubset,
this.client,
() => this.getOldestMembership(),
this.sessionDescription,
this.logger,
)
: new MembershipManager(
joinConfig,
this.roomSubset,
this.client,
@@ -790,7 +809,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
this.recalculateSessionMembers();
};
private onStickyEventUpdate = (added: MatrixEvent[], _removed: MatrixEvent[], room: Room): void => {
private onStickyEventUpdate = (added: MatrixEvent[], _removed: MatrixEvent[]): void => {
if ([...added, ..._removed].some((e) => e.getType() === EventType.GroupCallMemberPrefix)) {
this.recalculateSessionMembers();
}

View File

@@ -144,6 +144,18 @@ export interface MembershipManagerState {
probablyLeft: boolean;
}
function createInsertActionUpdate(type: MembershipActionType, offset?: number): ActionUpdate {
return {
insert: [{ ts: Date.now() + (offset ?? 0), type }],
};
}
function createReplaceActionUpdate(type: MembershipActionType, offset?: number): ActionUpdate {
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:
@@ -313,7 +325,7 @@ export class MembershipManager
*/
public constructor(
private joinConfig: (SessionConfig & MembershipConfig) | undefined,
private room: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion">,
protected room: Pick<Room, "roomId" | "getVersion">,
private client: Pick<
MatrixClient,
| "getUserId"
@@ -321,8 +333,6 @@ export class MembershipManager
| "sendStateEvent"
| "_unstable_sendDelayedStateEvent"
| "_unstable_updateDelayedEvent"
| "_unstable_sendStickyEvent"
| "_unstable_sendStickyDelayedEvent"
>,
private getOldestMembership: () => CallMembership | undefined,
public readonly sessionDescription: SessionDescription,
@@ -380,7 +390,7 @@ export class MembershipManager
}
// Membership Event static parameters:
private deviceId: string;
private stateKey: string;
protected stateKey: string;
private fociPreferred?: Focus[];
private focusActive?: Focus;
@@ -403,7 +413,7 @@ export class MembershipManager
this.membershipEventExpiryHeadroomMs
);
}
private get delayedLeaveEventDelayMs(): number {
protected get delayedLeaveEventDelayMs(): number {
return this.delayedLeaveEventDelayMsOverride ?? this.joinConfig?.delayedLeaveEventDelayMs ?? 8_000;
}
private get delayedLeaveEventRestartMs(): number {
@@ -420,10 +430,6 @@ export class MembershipManager
return this.joinConfig?.delayedLeaveEventRestartLocalTimeoutMs ?? 2000;
}
private get useStickyEvents(): boolean {
return this.joinConfig?.useStickyEvents ?? false;
}
// LOOP HANDLER:
private async membershipLoopHandler(type: MembershipActionType): Promise<ActionUpdate> {
switch (type) {
@@ -479,27 +485,16 @@ export class MembershipManager
}
// an abstraction to switch between sending state or a sticky event
private clientSendDelayedEvent: (myMembership: EmptyObject) => Promise<SendDelayedEventResponse> = (
protected clientSendDelayedEvent: (myMembership: EmptyObject) => Promise<SendDelayedEventResponse> = (
myMembership,
) =>
this.useStickyEvents
? this.client._unstable_sendStickyDelayedEvent(
this.room.roomId,
STICK_DURATION_MS,
{ delay: this.delayedLeaveEventDelayMs },
null,
EventType.GroupCallMemberPrefix,
Object.assign(myMembership, { sticky_key: this.stateKey }),
)
: this.client._unstable_sendDelayedStateEvent(
this.client._unstable_sendDelayedStateEvent(
this.room.roomId,
{ delay: this.delayedLeaveEventDelayMs },
EventType.GroupCallMemberPrefix,
myMembership,
this.stateKey,
);
private sendDelayedEventMethodName: () => string = () =>
this.useStickyEvents ? "_unstable_sendStickyDelayedEvent" : "_unstable_sendDelayedStateEvent";
// HANDLERS (used in the membershipLoopHandler)
private async sendOrResendDelayedLeaveEvent(): Promise<ActionUpdate> {
@@ -531,7 +526,7 @@ export class MembershipManager
if (this.manageMaxDelayExceededSituation(e)) {
return createInsertActionUpdate(repeatActionType);
}
const update = this.actionUpdateFromErrors(e, repeatActionType, this.sendDelayedEventMethodName());
const update = this.actionUpdateFromErrors(e, repeatActionType, "_unstable_sendDelayedStateEvent");
if (update) return update;
if (this.state.hasMemberStateEvent) {
@@ -687,25 +682,10 @@ export class MembershipManager
});
}
private clientSendMembership: (myMembership: SessionMembershipData | EmptyObject) => Promise<ISendEventResponse> = (
myMembership,
) =>
this.useStickyEvents
? this.client._unstable_sendStickyEvent(
this.room.roomId,
STICK_DURATION_MS,
null,
EventType.GroupCallMemberPrefix,
Object.assign(myMembership, { sticky_key: this.stateKey }),
)
: this.client.sendStateEvent(
this.room.roomId,
EventType.GroupCallMemberPrefix,
myMembership,
this.stateKey,
);
private sendMembershipMethodName: () => string = () =>
this.useStickyEvents ? "_unstable_sendStickyEvent" : "sendStateEvent";
protected clientSendMembership: (myMembership: SessionMembershipData | EmptyObject) => Promise<ISendEventResponse> =
(myMembership) =>
this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, myMembership, this.stateKey);
private async sendJoinEvent(): Promise<ActionUpdate> {
return await this.clientSendMembership(this.makeMyMembership(this.membershipEventExpiryMs))
.then(() => {
@@ -739,11 +719,7 @@ export class MembershipManager
};
})
.catch((e) => {
const update = this.actionUpdateFromErrors(
e,
MembershipActionType.SendJoinEvent,
this.sendMembershipMethodName(),
);
const update = this.actionUpdateFromErrors(e, MembershipActionType.SendJoinEvent, "sendStateEvent");
if (update) return update;
throw e;
});
@@ -768,11 +744,7 @@ export class MembershipManager
};
})
.catch((e) => {
const update = this.actionUpdateFromErrors(
e,
MembershipActionType.UpdateExpiry,
this.sendMembershipMethodName(),
);
const update = this.actionUpdateFromErrors(e, MembershipActionType.UpdateExpiry, "sendStateEvent");
if (update) return update;
throw e;
@@ -786,11 +758,7 @@ export class MembershipManager
return { replace: [] };
})
.catch((e) => {
const update = this.actionUpdateFromErrors(
e,
MembershipActionType.SendLeaveEvent,
this.sendMembershipMethodName(),
);
const update = this.actionUpdateFromErrors(e, MembershipActionType.SendLeaveEvent, "sendStateEvent");
if (update) return update;
throw e;
});
@@ -857,7 +825,7 @@ export class MembershipManager
return false;
}
private actionUpdateFromErrors(
protected actionUpdateFromErrors(
error: unknown,
type: MembershipActionType,
method: string,
@@ -905,7 +873,7 @@ export class MembershipManager
return createInsertActionUpdate(type, resendDelay);
}
throw Error("Exceeded maximum retries for " + type + " attempts (client." + method + "): " + (error as Error));
throw Error("Exceeded maximum retries for " + type + " attempts (client." + method + ")", { cause: error });
}
/**
@@ -1049,14 +1017,64 @@ export class MembershipManager
}
}
function createInsertActionUpdate(type: MembershipActionType, offset?: number): ActionUpdate {
return {
insert: [{ ts: Date.now() + (offset ?? 0), type }],
};
/**
* Implementation of the Membership manager that uses sticky events
* rather than state events.
*/
export class StickyEventMembershipManager extends MembershipManager {
public constructor(
joinConfig: (SessionConfig & MembershipConfig) | undefined,
room: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion">,
private readonly clientWithSticky: Pick<
MatrixClient,
| "getUserId"
| "getDeviceId"
| "sendStateEvent"
| "_unstable_sendDelayedStateEvent"
| "_unstable_updateDelayedEvent"
| "_unstable_sendStickyEvent"
| "_unstable_sendStickyDelayedEvent"
>,
getOldestMembership: () => CallMembership | undefined,
sessionDescription: SessionDescription,
parentLogger?: Logger,
) {
super(joinConfig, room, clientWithSticky, getOldestMembership, sessionDescription, parentLogger);
}
function createReplaceActionUpdate(type: MembershipActionType, offset?: number): ActionUpdate {
return {
replace: [{ ts: Date.now() + (offset ?? 0), type }],
};
protected clientSendDelayedEvent: (myMembership: EmptyObject) => Promise<SendDelayedEventResponse> = (
myMembership,
) =>
this.clientWithSticky._unstable_sendStickyDelayedEvent(
this.room.roomId,
STICK_DURATION_MS,
{ delay: this.delayedLeaveEventDelayMs },
null,
EventType.GroupCallMemberPrefix,
Object.assign(myMembership, { sticky_key: this.stateKey }),
);
protected clientSendMembership: (myMembership: SessionMembershipData | EmptyObject) => Promise<ISendEventResponse> =
(myMembership) =>
this.clientWithSticky._unstable_sendStickyEvent(
this.room.roomId,
STICK_DURATION_MS,
null,
EventType.GroupCallMemberPrefix,
Object.assign(myMembership, { sticky_key: this.stateKey }),
);
protected actionUpdateFromErrors(
error: unknown,
type: MembershipActionType,
method: string,
): ActionUpdate | undefined {
// Override method name.
if (method === "sendStateEvent") {
method = "_unstable_sendStickyEvent";
} else if (method === "_unstable_sendDelayedStateEvent") {
method = "_unstable_sendStickyDelayedEvent";
}
return super.actionUpdateFromErrors(error, type, method);
}
}