You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Remove LegacyMembershipManager
(#4862)
* Remove `LegacyMemberhsipManager` * remove tests from rtc session Those tests were only run with the legacy membership manager and are redundant with the memberhsip manager test spec. * fix tests * dont use non existing TestManager anymore * remove fails for legacy * fix another test
This commit is contained in:
@@ -16,7 +16,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
|
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
|
||||||
import { KnownMembership } from "../../../src/@types/membership";
|
import { KnownMembership } from "../../../src/@types/membership";
|
||||||
import { DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
import { type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
||||||
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
|
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
|
||||||
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
|
import { type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
|
||||||
import { secureRandomString } from "../../../src/randomstring";
|
import { secureRandomString } from "../../../src/randomstring";
|
||||||
@@ -201,58 +201,6 @@ describe("MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updateCallMembershipEvent", () => {
|
|
||||||
const mockFocus = { type: "livekit", livekit_service_url: "https://test.org" };
|
|
||||||
const joinSessionConfig = {};
|
|
||||||
|
|
||||||
const sessionMembershipData: SessionMembershipData = {
|
|
||||||
call_id: "",
|
|
||||||
scope: "m.room",
|
|
||||||
application: "m.call",
|
|
||||||
device_id: "AAAAAAA_session",
|
|
||||||
focus_active: mockFocus,
|
|
||||||
foci_preferred: [mockFocus],
|
|
||||||
};
|
|
||||||
|
|
||||||
let sendStateEventMock: jest.Mock;
|
|
||||||
let sendDelayedStateMock: jest.Mock;
|
|
||||||
|
|
||||||
let sentStateEvent: Promise<void>;
|
|
||||||
let sentDelayedState: Promise<void>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
sentStateEvent = new Promise((resolve) => {
|
|
||||||
sendStateEventMock = jest.fn(resolve);
|
|
||||||
});
|
|
||||||
sentDelayedState = new Promise((resolve) => {
|
|
||||||
sendDelayedStateMock = jest.fn(() => {
|
|
||||||
resolve();
|
|
||||||
return {
|
|
||||||
delay_id: "id",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
client.sendStateEvent = sendStateEventMock;
|
|
||||||
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function testSession(membershipData: SessionMembershipData): Promise<void> {
|
|
||||||
sess = MatrixRTCSession.roomSessionForRoom(client, makeMockRoom(membershipData));
|
|
||||||
|
|
||||||
sess.joinRoomSession([mockFocus], mockFocus, joinSessionConfig);
|
|
||||||
await Promise.race([sentStateEvent, new Promise((resolve) => setTimeout(resolve, 500))]);
|
|
||||||
|
|
||||||
expect(sendStateEventMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
await Promise.race([sentDelayedState, new Promise((resolve) => setTimeout(resolve, 500))]);
|
|
||||||
expect(sendDelayedStateMock).toHaveBeenCalledTimes(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
it("sends events", async () => {
|
|
||||||
await testSession(sessionMembershipData);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getOldestMembership", () => {
|
describe("getOldestMembership", () => {
|
||||||
it("returns the oldest membership event", () => {
|
it("returns the oldest membership event", () => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
@@ -320,28 +268,10 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
describe("joining", () => {
|
describe("joining", () => {
|
||||||
let mockRoom: Room;
|
let mockRoom: Room;
|
||||||
let sendStateEventMock: jest.Mock;
|
|
||||||
let sendDelayedStateMock: jest.Mock;
|
|
||||||
let sendEventMock: jest.Mock;
|
let sendEventMock: jest.Mock;
|
||||||
|
|
||||||
let sentStateEvent: Promise<void>;
|
|
||||||
let sentDelayedState: Promise<void>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sentStateEvent = new Promise((resolve) => {
|
|
||||||
sendStateEventMock = jest.fn(resolve);
|
|
||||||
});
|
|
||||||
sentDelayedState = new Promise((resolve) => {
|
|
||||||
sendDelayedStateMock = jest.fn(() => {
|
|
||||||
resolve();
|
|
||||||
return {
|
|
||||||
delay_id: "id",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
sendEventMock = jest.fn();
|
sendEventMock = jest.fn();
|
||||||
client.sendStateEvent = sendStateEventMock;
|
|
||||||
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
|
||||||
client.sendEvent = sendEventMock;
|
client.sendEvent = sendEventMock;
|
||||||
|
|
||||||
client._unstable_updateDelayedEvent = jest.fn();
|
client._unstable_updateDelayedEvent = jest.fn();
|
||||||
@@ -367,67 +297,6 @@ describe("MatrixRTCSession", () => {
|
|||||||
sess!.joinRoomSession([mockFocus], mockFocus);
|
sess!.joinRoomSession([mockFocus], mockFocus);
|
||||||
expect(sess!.isJoined()).toEqual(true);
|
expect(sess!.isJoined()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("sends a membership event when joining a call", async () => {
|
|
||||||
const realSetTimeout = setTimeout;
|
|
||||||
jest.useFakeTimers();
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus);
|
|
||||||
await Promise.race([sentStateEvent, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
|
||||||
mockRoom!.roomId,
|
|
||||||
EventType.GroupCallMemberPrefix,
|
|
||||||
{
|
|
||||||
application: "m.call",
|
|
||||||
scope: "m.room",
|
|
||||||
call_id: "",
|
|
||||||
device_id: "AAAAAAA",
|
|
||||||
expires: DEFAULT_EXPIRE_DURATION,
|
|
||||||
foci_preferred: [mockFocus],
|
|
||||||
focus_active: {
|
|
||||||
focus_selection: "oldest_membership",
|
|
||||||
type: "livekit",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"_@alice:example.org_AAAAAAA",
|
|
||||||
);
|
|
||||||
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
|
||||||
// Because we actually want to send the state
|
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
|
||||||
// For checking if the delayed event is still there or got removed while sending the state.
|
|
||||||
expect(client._unstable_updateDelayedEvent).toHaveBeenCalledTimes(1);
|
|
||||||
// For scheduling the delayed event
|
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
|
||||||
// This returns no error so we do not check if we reschedule the event again. this is done in another test.
|
|
||||||
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses membershipEventExpiryMs from join config", async () => {
|
|
||||||
const realSetTimeout = setTimeout;
|
|
||||||
jest.useFakeTimers();
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { membershipEventExpiryMs: 60000 });
|
|
||||||
await Promise.race([sentStateEvent, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
|
||||||
mockRoom!.roomId,
|
|
||||||
EventType.GroupCallMemberPrefix,
|
|
||||||
{
|
|
||||||
application: "m.call",
|
|
||||||
scope: "m.room",
|
|
||||||
call_id: "",
|
|
||||||
device_id: "AAAAAAA",
|
|
||||||
expires: 60000,
|
|
||||||
foci_preferred: [mockFocus],
|
|
||||||
focus_active: {
|
|
||||||
focus_selection: "oldest_membership",
|
|
||||||
type: "livekit",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"_@alice:example.org_AAAAAAA",
|
|
||||||
);
|
|
||||||
await Promise.race([sentDelayedState, new Promise((resolve) => realSetTimeout(resolve, 500))]);
|
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
|
||||||
jest.useRealTimers();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("onMembershipsChanged", () => {
|
describe("onMembershipsChanged", () => {
|
||||||
@@ -489,9 +358,9 @@ describe("MatrixRTCSession", () => {
|
|||||||
let sendToDeviceMock: jest.Mock;
|
let sendToDeviceMock: jest.Mock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sendStateEventMock = jest.fn();
|
sendStateEventMock = jest.fn().mockResolvedValue({ event_id: "id" });
|
||||||
sendDelayedStateMock = jest.fn();
|
sendDelayedStateMock = jest.fn().mockResolvedValue({ event_id: "id" });
|
||||||
sendEventMock = jest.fn();
|
sendEventMock = jest.fn().mockResolvedValue({ event_id: "id" });
|
||||||
sendToDeviceMock = jest.fn();
|
sendToDeviceMock = jest.fn();
|
||||||
client.sendStateEvent = sendStateEventMock;
|
client.sendStateEvent = sendStateEventMock;
|
||||||
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
client._unstable_sendDelayedStateEvent = sendDelayedStateMock;
|
||||||
@@ -569,24 +438,22 @@ describe("MatrixRTCSession", () => {
|
|||||||
let firstEventSent = false;
|
let firstEventSent = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const eventSentPromise = new Promise<void>((resolve) => {
|
const eventSentPromise = new Promise<{ event_id: string }>((resolve) => {
|
||||||
sendEventMock.mockImplementation(() => {
|
sendEventMock.mockImplementation(() => {
|
||||||
if (!firstEventSent) {
|
if (!firstEventSent) {
|
||||||
jest.advanceTimersByTime(10000);
|
|
||||||
|
|
||||||
firstEventSent = true;
|
firstEventSent = true;
|
||||||
const e = new Error() as MatrixError;
|
const e = new Error() as MatrixError;
|
||||||
e.data = {};
|
e.data = {};
|
||||||
throw e;
|
throw e;
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve({ event_id: "id" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
sess!.joinRoomSession([mockFocus], mockFocus, { manageMediaKeys: true });
|
||||||
await jest.runAllTimersAsync();
|
// wait for the encryption event to get sent
|
||||||
|
await jest.advanceTimersByTimeAsync(5000);
|
||||||
await eventSentPromise;
|
await eventSentPromise;
|
||||||
|
|
||||||
expect(sendEventMock).toHaveBeenCalledTimes(2);
|
expect(sendEventMock).toHaveBeenCalledTimes(2);
|
||||||
@@ -993,7 +860,6 @@ describe("MatrixRTCSession", () => {
|
|||||||
|
|
||||||
sess!.joinRoomSession([mockFocus], mockFocus, {
|
sess!.joinRoomSession([mockFocus], mockFocus, {
|
||||||
manageMediaKeys: true,
|
manageMediaKeys: true,
|
||||||
useNewMembershipManager: true,
|
|
||||||
useExperimentalToDeviceTransport: true,
|
useExperimentalToDeviceTransport: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
/**
|
|
||||||
* @jest-environment ./spec/unit/matrixrtc/memberManagerTestEnvironment.ts
|
|
||||||
*/
|
|
||||||
/*
|
/*
|
||||||
Copyright 2025 The Matrix.org Foundation C.I.C.
|
Copyright 2025 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
@@ -27,10 +24,9 @@ import {
|
|||||||
type LivekitFocusActive,
|
type LivekitFocusActive,
|
||||||
type SessionMembershipData,
|
type SessionMembershipData,
|
||||||
} from "../../../src/matrixrtc";
|
} from "../../../src/matrixrtc";
|
||||||
import { LegacyMembershipManager } from "../../../src/matrixrtc/LegacyMembershipManager";
|
|
||||||
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
||||||
import { MembershipManager } from "../../../src/matrixrtc/NewMembershipManager";
|
|
||||||
import { logger } from "../../../src/logger.ts";
|
import { logger } from "../../../src/logger.ts";
|
||||||
|
import { MembershipManager } from "../../../src/matrixrtc/MembershipManager.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a promise that will resolve once a mocked method is called.
|
* Create a promise that will resolve once a mocked method is called.
|
||||||
@@ -68,15 +64,7 @@ function createAsyncHandle<T>(method: MockedFunction<any>) {
|
|||||||
return { reject, resolve };
|
return { reject, resolve };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
describe("MembershipManager", () => {
|
||||||
* Tests different MembershipManager implementations. Some tests don't apply to `LegacyMembershipManager`
|
|
||||||
* use !FailsForLegacy to skip those. See: testEnvironment for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
describe.each([
|
|
||||||
{ TestMembershipManager: LegacyMembershipManager, description: "LegacyMembershipManager" },
|
|
||||||
{ TestMembershipManager: MembershipManager, description: "MembershipManager" },
|
|
||||||
])("$description", ({ TestMembershipManager }) => {
|
|
||||||
let client: MockClient;
|
let client: MockClient;
|
||||||
let room: Room;
|
let room: Room;
|
||||||
const focusActive: LivekitFocusActive = {
|
const focusActive: LivekitFocusActive = {
|
||||||
@@ -107,12 +95,12 @@ describe.each([
|
|||||||
|
|
||||||
describe("isActivated()", () => {
|
describe("isActivated()", () => {
|
||||||
it("defaults to false", () => {
|
it("defaults to false", () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
expect(manager.isActivated()).toEqual(false);
|
expect(manager.isActivated()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns true after join()", () => {
|
it("returns true after join()", () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([]);
|
manager.join([]);
|
||||||
expect(manager.isActivated()).toEqual(true);
|
expect(manager.isActivated()).toEqual(true);
|
||||||
});
|
});
|
||||||
@@ -126,7 +114,7 @@ describe.each([
|
|||||||
const updateDelayedEventHandle = createAsyncHandle<void>(client._unstable_updateDelayedEvent as Mock);
|
const updateDelayedEventHandle = createAsyncHandle<void>(client._unstable_updateDelayedEvent as Mock);
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
const memberManager = new TestMembershipManager(undefined, room, client, () => undefined);
|
const memberManager = new MembershipManager(undefined, room, client, () => undefined);
|
||||||
memberManager.join([focus], focusActive);
|
memberManager.join([focus], focusActive);
|
||||||
// expects
|
// expects
|
||||||
await waitForMockCall(client.sendStateEvent, Promise.resolve({ event_id: "id" }));
|
await waitForMockCall(client.sendStateEvent, Promise.resolve({ event_id: "id" }));
|
||||||
@@ -156,7 +144,7 @@ describe.each([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("reschedules delayed leave event if sending state cancels it", async () => {
|
it("reschedules delayed leave event if sending state cancels it", async () => {
|
||||||
const memberManager = new TestMembershipManager(undefined, room, client, () => undefined);
|
const memberManager = new MembershipManager(undefined, room, client, () => undefined);
|
||||||
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
const waitForSendState = waitForMockCall(client.sendStateEvent);
|
||||||
const waitForUpdateDelaye = waitForMockCallOnce(
|
const waitForUpdateDelaye = waitForMockCallOnce(
|
||||||
client._unstable_updateDelayedEvent,
|
client._unstable_updateDelayedEvent,
|
||||||
@@ -225,7 +213,7 @@ describe.each([
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{
|
{
|
||||||
delayedLeaveEventDelayMs: 9000,
|
delayedLeaveEventDelayMs: 9000,
|
||||||
},
|
},
|
||||||
@@ -288,7 +276,7 @@ describe.each([
|
|||||||
describe("delayed leave event", () => {
|
describe("delayed leave event", () => {
|
||||||
it("does not try again to schedule a delayed leave event if not supported", () => {
|
it("does not try again to schedule a delayed leave event if not supported", () => {
|
||||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
delayedHandle.reject?.(
|
delayedHandle.reject?.(
|
||||||
new UnsupportedDelayedEventsEndpointError(
|
new UnsupportedDelayedEventsEndpointError(
|
||||||
@@ -300,14 +288,14 @@ describe.each([
|
|||||||
});
|
});
|
||||||
it("does try to schedule a delayed leave event again if rate limited", async () => {
|
it("does try to schedule a delayed leave event again if rate limited", async () => {
|
||||||
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
const delayedHandle = createAsyncHandle(client._unstable_sendDelayedStateEvent as Mock);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
delayedHandle.reject?.(new HTTPError("rate limited", 429, undefined));
|
delayedHandle.reject?.(new HTTPError("rate limited", 429, undefined));
|
||||||
await jest.advanceTimersByTimeAsync(5000);
|
await jest.advanceTimersByTimeAsync(5000);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
it("uses delayedLeaveEventDelayMs from config", () => {
|
it("uses delayedLeaveEventDelayMs from config", () => {
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{ delayedLeaveEventDelayMs: 123456 },
|
{ delayedLeaveEventDelayMs: 123456 },
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
@@ -324,9 +312,9 @@ describe.each([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("rejoins if delayed event is not found (404) !FailsForLegacy", async () => {
|
it("rejoins if delayed event is not found (404)", async () => {
|
||||||
const RESTART_DELAY = 15000;
|
const RESTART_DELAY = 15000;
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{ delayedLeaveEventRestartMs: RESTART_DELAY },
|
{ delayedLeaveEventRestartMs: RESTART_DELAY },
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
@@ -363,12 +351,7 @@ describe.each([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("uses membershipEventExpiryMs from config", async () => {
|
it("uses membershipEventExpiryMs from config", async () => {
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager({ membershipEventExpiryMs: 1234567 }, room, client, () => undefined);
|
||||||
{ membershipEventExpiryMs: 1234567 },
|
|
||||||
room,
|
|
||||||
client,
|
|
||||||
() => undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
@@ -392,7 +375,7 @@ describe.each([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does nothing if join called when already joined", async () => {
|
it("does nothing if join called when already joined", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
@@ -404,7 +387,7 @@ describe.each([
|
|||||||
describe("leave()", () => {
|
describe("leave()", () => {
|
||||||
// TODO add rate limit cases.
|
// TODO add rate limit cases.
|
||||||
it("resolves delayed leave event when leave is called", async () => {
|
it("resolves delayed leave event when leave is called", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
await manager.leave();
|
await manager.leave();
|
||||||
@@ -412,7 +395,7 @@ describe.each([
|
|||||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("send leave event when leave is called and resolving delayed leave fails", async () => {
|
it("send leave event when leave is called and resolving delayed leave fails", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue("unknown");
|
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue("unknown");
|
||||||
@@ -426,9 +409,8 @@ describe.each([
|
|||||||
"_@alice:example.org_AAAAAAA",
|
"_@alice:example.org_AAAAAAA",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
// FailsForLegacy because legacy implementation always sends the empty state event even though it isn't needed
|
it("does nothing if not joined", () => {
|
||||||
it("does nothing if not joined !FailsForLegacy", () => {
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
|
||||||
expect(async () => await manager.leave()).not.toThrow();
|
expect(async () => await manager.leave()).not.toThrow();
|
||||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
@@ -438,7 +420,7 @@ describe.each([
|
|||||||
describe("getsActiveFocus", () => {
|
describe("getsActiveFocus", () => {
|
||||||
it("gets the correct active focus with oldest_membership", () => {
|
it("gets the correct active focus with oldest_membership", () => {
|
||||||
const getOldestMembership = jest.fn();
|
const getOldestMembership = jest.fn();
|
||||||
const manager = new TestMembershipManager({}, room, client, getOldestMembership);
|
const manager = new MembershipManager({}, room, client, getOldestMembership);
|
||||||
// Before joining the active focus should be undefined (see FocusInUse on MatrixRTCSession)
|
// Before joining the active focus should be undefined (see FocusInUse on MatrixRTCSession)
|
||||||
expect(manager.getActiveFocus()).toBe(undefined);
|
expect(manager.getActiveFocus()).toBe(undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -473,7 +455,7 @@ describe.each([
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("does not provide focus if the selection method is unknown", () => {
|
it("does not provide focus if the selection method is unknown", () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], Object.assign(focusActive, { type: "unknown_type" }));
|
manager.join([focus], Object.assign(focusActive, { type: "unknown_type" }));
|
||||||
expect(manager.getActiveFocus()).toBe(undefined);
|
expect(manager.getActiveFocus()).toBe(undefined);
|
||||||
});
|
});
|
||||||
@@ -481,7 +463,7 @@ describe.each([
|
|||||||
|
|
||||||
describe("onRTCSessionMemberUpdate()", () => {
|
describe("onRTCSessionMemberUpdate()", () => {
|
||||||
it("does nothing if not joined", async () => {
|
it("does nothing if not joined", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
||||||
await jest.advanceTimersToNextTimerAsync();
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
@@ -489,7 +471,7 @@ describe.each([
|
|||||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("does nothing if own membership still present", async () => {
|
it("does nothing if own membership still present", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
const myMembership = (client.sendStateEvent as Mock).mock.calls[0][2];
|
const myMembership = (client.sendStateEvent as Mock).mock.calls[0][2];
|
||||||
@@ -510,7 +492,7 @@ describe.each([
|
|||||||
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
expect(client._unstable_updateDelayedEvent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("recreates membership if it is missing", async () => {
|
it("recreates membership if it is missing", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
// clearing all mocks before checking what happens when calling: `onRTCSessionMemberUpdate`
|
||||||
@@ -531,7 +513,7 @@ describe.each([
|
|||||||
// TODO: Not sure about this name
|
// TODO: Not sure about this name
|
||||||
describe("background timers", () => {
|
describe("background timers", () => {
|
||||||
it("sends only one keep-alive for delayed leave event per `delayedLeaveEventRestartMs`", async () => {
|
it("sends only one keep-alive for delayed leave event per `delayedLeaveEventRestartMs`", async () => {
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{ delayedLeaveEventRestartMs: 10_000, delayedLeaveEventDelayMs: 30_000 },
|
{ delayedLeaveEventRestartMs: 10_000, delayedLeaveEventDelayMs: 30_000 },
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
@@ -557,12 +539,12 @@ describe.each([
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// !FailsForLegacy because the expires logic was removed for the legacy call manager.
|
// because the expires logic was removed for the legacy call manager.
|
||||||
// Delayed events should replace it entirely but before they have wide adoption
|
// Delayed events should replace it entirely but before they have wide adoption
|
||||||
// the expiration logic still makes sense.
|
// the expiration logic still makes sense.
|
||||||
// TODO: Add git commit when we removed it.
|
// TODO: Add git commit when we removed it.
|
||||||
async function testExpires(expire: number, headroom?: number) {
|
async function testExpires(expire: number, headroom?: number) {
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{ membershipEventExpiryMs: expire, membershipEventExpiryHeadroomMs: headroom },
|
{ membershipEventExpiryMs: expire, membershipEventExpiryHeadroomMs: headroom },
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
@@ -580,23 +562,23 @@ describe.each([
|
|||||||
expect(sentMembership.expires).toBe(expire * i);
|
expect(sentMembership.expires).toBe(expire * i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it("extends `expires` when call still active !FailsForLegacy", async () => {
|
it("extends `expires` when call still active", async () => {
|
||||||
await testExpires(10_000);
|
await testExpires(10_000);
|
||||||
});
|
});
|
||||||
it("extends `expires` using headroom configuration !FailsForLegacy", async () => {
|
it("extends `expires` using headroom configuration", async () => {
|
||||||
await testExpires(10_000, 1_000);
|
await testExpires(10_000, 1_000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("status updates", () => {
|
describe("status updates", () => {
|
||||||
it("starts 'Disconnected' !FailsForLegacy", () => {
|
it("starts 'Disconnected'", () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
expect(manager.status).toBe(Status.Disconnected);
|
expect(manager.status).toBe(Status.Disconnected);
|
||||||
});
|
});
|
||||||
it("emits 'Connection' and 'Connected' after join !FailsForLegacy", async () => {
|
it("emits 'Connection' and 'Connected' after join", async () => {
|
||||||
const handleDelayedEvent = createAsyncHandle<void>(client._unstable_sendDelayedStateEvent);
|
const handleDelayedEvent = createAsyncHandle<void>(client._unstable_sendDelayedStateEvent);
|
||||||
const handleStateEvent = createAsyncHandle<void>(client.sendStateEvent);
|
const handleStateEvent = createAsyncHandle<void>(client.sendStateEvent);
|
||||||
|
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
expect(manager.status).toBe(Status.Disconnected);
|
expect(manager.status).toBe(Status.Disconnected);
|
||||||
const connectEmit = jest.fn();
|
const connectEmit = jest.fn();
|
||||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||||
@@ -609,8 +591,8 @@ describe.each([
|
|||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
expect(connectEmit).toHaveBeenCalledWith(Status.Connecting, Status.Connected);
|
expect(connectEmit).toHaveBeenCalledWith(Status.Connecting, Status.Connected);
|
||||||
});
|
});
|
||||||
it("emits 'Disconnecting' and 'Disconnected' after leave !FailsForLegacy", async () => {
|
it("emits 'Disconnecting' and 'Disconnected' after leave", async () => {
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
const connectEmit = jest.fn();
|
const connectEmit = jest.fn();
|
||||||
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
manager.on(MembershipManagerEvent.StatusChanged, connectEmit);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -626,7 +608,7 @@ describe.each([
|
|||||||
it("sends retry if call membership event is still valid at time of retry", async () => {
|
it("sends retry if call membership event is still valid at time of retry", async () => {
|
||||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
|
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
@@ -643,8 +625,7 @@ describe.each([
|
|||||||
|
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
// FailsForLegacy as implementation does not re-check membership before retrying.
|
it("abandons retry loop and sends new own membership if not present anymore", async () => {
|
||||||
it("abandons retry loop and sends new own membership if not present anymore !FailsForLegacy", async () => {
|
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||||
@@ -654,7 +635,7 @@ describe.each([
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
// Should call _unstable_sendDelayedStateEvent but not sendStateEvent because of the
|
// Should call _unstable_sendDelayedStateEvent but not sendStateEvent because of the
|
||||||
// RateLimit error.
|
// RateLimit error.
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
@@ -671,11 +652,10 @@ describe.each([
|
|||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalledTimes(2);
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
// FailsForLegacy as implementation does not re-check membership before retrying.
|
it("abandons retry loop if leave() was called before sending state event", async () => {
|
||||||
it("abandons retry loop if leave() was called before sending state event !FailsForLegacy", async () => {
|
|
||||||
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const handle = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
|
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
handle.reject?.(
|
handle.reject?.(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
@@ -700,7 +680,7 @@ describe.each([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("retries sending update delayed leave event restart", () => {
|
describe("retries sending update delayed leave event restart", () => {
|
||||||
it("resends the initial check delayed update event !FailsForLegacy", async () => {
|
it("resends the initial check delayed update event", async () => {
|
||||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
{ errcode: "M_LIMIT_EXCEEDED" },
|
{ errcode: "M_LIMIT_EXCEEDED" },
|
||||||
@@ -710,7 +690,7 @@ describe.each([
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive);
|
manager.join([focus], focusActive);
|
||||||
|
|
||||||
// Hit rate limit
|
// Hit rate limit
|
||||||
@@ -731,8 +711,8 @@ describe.each([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe("unrecoverable errors", () => {
|
describe("unrecoverable errors", () => {
|
||||||
// !FailsForLegacy because legacy does not have a retry limit and no mechanism to communicate unrecoverable errors.
|
// because legacy does not have a retry limit and no mechanism to communicate unrecoverable errors.
|
||||||
it("throws, when reaching maximum number of retries for initial delayed event creation !FailsForLegacy", async () => {
|
it("throws, when reaching maximum number of retries for initial delayed event creation", async () => {
|
||||||
const delayEventSendError = jest.fn();
|
const delayEventSendError = jest.fn();
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
@@ -743,7 +723,7 @@ describe.each([
|
|||||||
new Headers({ "Retry-After": "2" }),
|
new Headers({ "Retry-After": "2" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive, delayEventSendError);
|
manager.join([focus], focusActive, delayEventSendError);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
@@ -751,8 +731,8 @@ describe.each([
|
|||||||
}
|
}
|
||||||
expect(delayEventSendError).toHaveBeenCalled();
|
expect(delayEventSendError).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
// !FailsForLegacy because legacy does not have a retry limit and no mechanism to communicate unrecoverable errors.
|
// because legacy does not have a retry limit and no mechanism to communicate unrecoverable errors.
|
||||||
it("throws, when reaching maximum number of retries !FailsForLegacy", async () => {
|
it("throws, when reaching maximum number of retries", async () => {
|
||||||
const delayEventRestartError = jest.fn();
|
const delayEventRestartError = jest.fn();
|
||||||
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_updateDelayedEvent as Mock<any>).mockRejectedValue(
|
||||||
new MatrixError(
|
new MatrixError(
|
||||||
@@ -763,7 +743,7 @@ describe.each([
|
|||||||
new Headers({ "Retry-After": "1" }),
|
new Headers({ "Retry-After": "1" }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive, delayEventRestartError);
|
manager.join([focus], focusActive, delayEventRestartError);
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
@@ -771,19 +751,19 @@ describe.each([
|
|||||||
}
|
}
|
||||||
expect(delayEventRestartError).toHaveBeenCalled();
|
expect(delayEventRestartError).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("falls back to using pure state events when some error occurs while sending delayed events !FailsForLegacy", async () => {
|
it("falls back to using pure state events when some error occurs while sending delayed events", async () => {
|
||||||
const unrecoverableError = jest.fn();
|
const unrecoverableError = jest.fn();
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 601));
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 601));
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive, unrecoverableError);
|
manager.join([focus], focusActive, unrecoverableError);
|
||||||
await waitForMockCall(client.sendStateEvent);
|
await waitForMockCall(client.sendStateEvent);
|
||||||
expect(unrecoverableError).not.toHaveBeenCalledWith();
|
expect(unrecoverableError).not.toHaveBeenCalledWith();
|
||||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("retries before failing in case its a network error !FailsForLegacy", async () => {
|
it("retries before failing in case its a network error", async () => {
|
||||||
const unrecoverableError = jest.fn();
|
const unrecoverableError = jest.fn();
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 501));
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(new HTTPError("unknown", 501));
|
||||||
const manager = new TestMembershipManager(
|
const manager = new MembershipManager(
|
||||||
{ networkErrorRetryMs: 1000, maximumNetworkErrorRetryCount: 7 },
|
{ networkErrorRetryMs: 1000, maximumNetworkErrorRetryCount: 7 },
|
||||||
room,
|
room,
|
||||||
client,
|
client,
|
||||||
@@ -800,12 +780,12 @@ describe.each([
|
|||||||
);
|
);
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("falls back to using pure state events when UnsupportedDelayedEventsEndpointError encountered for delayed events !FailsForLegacy", async () => {
|
it("falls back to using pure state events when UnsupportedDelayedEventsEndpointError encountered for delayed events", async () => {
|
||||||
const unrecoverableError = jest.fn();
|
const unrecoverableError = jest.fn();
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockRejectedValue(
|
||||||
new UnsupportedDelayedEventsEndpointError("not supported", "sendDelayedStateEvent"),
|
new UnsupportedDelayedEventsEndpointError("not supported", "sendDelayedStateEvent"),
|
||||||
);
|
);
|
||||||
const manager = new TestMembershipManager({}, room, client, () => undefined);
|
const manager = new MembershipManager({}, room, client, () => undefined);
|
||||||
manager.join([focus], focusActive, unrecoverableError);
|
manager.join([focus], focusActive, unrecoverableError);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
|
|
||||||
@@ -828,5 +808,5 @@ it("Should prefix log with MembershipManager used", () => {
|
|||||||
|
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
const logline: string = spy.mock.calls[0][0];
|
const logline: string = spy.mock.calls[0][0];
|
||||||
expect(logline.startsWith("[NewMembershipManager]")).toBe(true);
|
expect(logline.startsWith("[MembershipManager]")).toBe(true);
|
||||||
});
|
});
|
||||||
|
@@ -1,54 +0,0 @@
|
|||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
This file adds a custom test environment for the MembershipManager.spec.ts
|
|
||||||
It can be used with the comment at the top of the file:
|
|
||||||
|
|
||||||
@jest-environment ./spec/unit/matrixrtc/memberManagerTestEnvironment.ts
|
|
||||||
|
|
||||||
It is very specific to the MembershipManager.spec.ts file and introduces the following behaviour:
|
|
||||||
- The describe each block in the MembershipManager.spec.ts will go through describe block names `LegacyMembershipManager` and `MembershipManager`
|
|
||||||
- It will check all tests that are a child or indirect child of the `LegacyMembershipManager` block and skip the ones which include "!FailsForLegacy"
|
|
||||||
in their test name.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TestEnvironment } from "jest-environment-jsdom";
|
|
||||||
|
|
||||||
import { logger as rootLogger } from "../../../src/logger";
|
|
||||||
const logger = rootLogger.getChild("[MatrixRTCSession]");
|
|
||||||
|
|
||||||
class MemberManagerTestEnvironment extends TestEnvironment {
|
|
||||||
handleTestEvent(event: any) {
|
|
||||||
if (event.name === "test_start" && event.test.name.includes("!FailsForLegacy")) {
|
|
||||||
let parent = event.test.parent;
|
|
||||||
let isLegacy = false;
|
|
||||||
while (parent) {
|
|
||||||
if (parent.name === "LegacyMembershipManager") {
|
|
||||||
isLegacy = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
parent = parent.parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isLegacy) {
|
|
||||||
logger.info("skip test: ", event.test.name);
|
|
||||||
event.test.mode = "skip";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = MemberManagerTestEnvironment;
|
|
@@ -1,414 +0,0 @@
|
|||||||
/*
|
|
||||||
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 { EventType } from "../@types/event.ts";
|
|
||||||
import { UpdateDelayedEventAction } from "../@types/requests.ts";
|
|
||||||
import type { MatrixClient } from "../client.ts";
|
|
||||||
import { HTTPError, MatrixError } from "../http-api/errors.ts";
|
|
||||||
import { logger } from "../logger.ts";
|
|
||||||
import { EventTimeline } from "../models/event-timeline.ts";
|
|
||||||
import { type Room } from "../models/room.ts";
|
|
||||||
import { sleep } from "../utils.ts";
|
|
||||||
import { type CallMembership, DEFAULT_EXPIRE_DURATION, type SessionMembershipData } from "./CallMembership.ts";
|
|
||||||
import { type Focus } from "./focus.ts";
|
|
||||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
|
||||||
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
|
||||||
import { type EmptyObject } from "../@types/common.ts";
|
|
||||||
import { Status } from "./types.ts";
|
|
||||||
import type { IMembershipManager, MembershipManagerEvent } from "./IMembershipManager.ts";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This internal class is used by the MatrixRTCSession to manage the local user's own membership of the session.
|
|
||||||
*
|
|
||||||
* Its responsibitiy is to manage the locals user membership:
|
|
||||||
* - send that sate event
|
|
||||||
* - send the delayed leave event
|
|
||||||
* - update the delayed leave event while connected
|
|
||||||
* - update the state event when it times out (for calls longer than membershipExpiryTimeout ~ 4h)
|
|
||||||
*
|
|
||||||
* It is possible to test this class on its own. The api surface (to use for tests) is
|
|
||||||
* defined in `MembershipManagerInterface`.
|
|
||||||
*
|
|
||||||
* It is recommended to only use this interface for testing to allow replacing this class.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
* @deprecated Use {@link MembershipManager} instead
|
|
||||||
*/
|
|
||||||
export class LegacyMembershipManager implements IMembershipManager {
|
|
||||||
private relativeExpiry: number | undefined;
|
|
||||||
|
|
||||||
private memberEventTimeout?: ReturnType<typeof setTimeout>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a Foci array that contains the Focus objects this user is aware of and proposes to use.
|
|
||||||
*/
|
|
||||||
private ownFociPreferred?: Focus[];
|
|
||||||
/**
|
|
||||||
* This is a Focus with the specified fields for an ActiveFocus (e.g. LivekitFocusActive for type="livekit")
|
|
||||||
*/
|
|
||||||
private ownFocusActive?: Focus;
|
|
||||||
|
|
||||||
private updateCallMembershipRunning = false;
|
|
||||||
private needCallMembershipUpdate = false;
|
|
||||||
/**
|
|
||||||
* If the server disallows the configured {@link delayedLeaveEventDelayMs},
|
|
||||||
* this stores a delay that the server does allow.
|
|
||||||
*/
|
|
||||||
private delayedLeaveEventDelayMsOverride?: number;
|
|
||||||
private disconnectDelayId: string | undefined;
|
|
||||||
|
|
||||||
private get networkErrorRetryMs(): number {
|
|
||||||
return this.joinConfig?.networkErrorRetryMs ?? this.joinConfig?.callMemberEventRetryDelayMinimum ?? 3_000;
|
|
||||||
}
|
|
||||||
private get membershipEventExpiryMs(): number {
|
|
||||||
return (
|
|
||||||
this.joinConfig?.membershipEventExpiryMs ??
|
|
||||||
this.joinConfig?.membershipExpiryTimeout ??
|
|
||||||
DEFAULT_EXPIRE_DURATION
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private get delayedLeaveEventDelayMs(): number {
|
|
||||||
return (
|
|
||||||
this.delayedLeaveEventDelayMsOverride ??
|
|
||||||
this.joinConfig?.delayedLeaveEventDelayMs ??
|
|
||||||
this.joinConfig?.membershipServerSideExpiryTimeout ??
|
|
||||||
8_000
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private get delayedLeaveEventRestartMs(): number {
|
|
||||||
return this.joinConfig?.delayedLeaveEventRestartMs ?? this.joinConfig?.membershipKeepAlivePeriod ?? 5_000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
private joinConfig: MembershipConfig | undefined,
|
|
||||||
private room: Pick<Room, "getLiveTimeline" | "roomId" | "getVersion">,
|
|
||||||
private client: Pick<
|
|
||||||
MatrixClient,
|
|
||||||
| "getUserId"
|
|
||||||
| "getDeviceId"
|
|
||||||
| "sendStateEvent"
|
|
||||||
| "_unstable_sendDelayedStateEvent"
|
|
||||||
| "_unstable_updateDelayedEvent"
|
|
||||||
>,
|
|
||||||
private getOldestMembership: () => CallMembership | undefined,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public off(
|
|
||||||
event: MembershipManagerEvent.StatusChanged,
|
|
||||||
listener: (oldStatus: Status, newStatus: Status) => void,
|
|
||||||
): this {
|
|
||||||
logger.error("off is not implemented on LegacyMembershipManager");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public on(
|
|
||||||
event: MembershipManagerEvent.StatusChanged,
|
|
||||||
listener: (oldStatus: Status, newStatus: Status) => void,
|
|
||||||
): this {
|
|
||||||
logger.error("on is not implemented on LegacyMembershipManager");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isJoined(): boolean {
|
|
||||||
return this.relativeExpiry !== undefined;
|
|
||||||
}
|
|
||||||
public isActivated(): boolean {
|
|
||||||
return this.isJoined();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Unimplemented
|
|
||||||
* @returns Status.Unknown
|
|
||||||
*/
|
|
||||||
public get status(): Status {
|
|
||||||
return Status.Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public join(fociPreferred: Focus[], fociActive?: Focus): void {
|
|
||||||
this.ownFocusActive = fociActive;
|
|
||||||
this.ownFociPreferred = fociPreferred;
|
|
||||||
this.relativeExpiry = this.membershipEventExpiryMs;
|
|
||||||
// We don't wait for this, mostly because it may fail and schedule a retry, so this
|
|
||||||
// function returning doesn't really mean anything at all.
|
|
||||||
void this.triggerCallMembershipEventUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async leave(timeout: number | undefined = undefined): Promise<boolean> {
|
|
||||||
this.relativeExpiry = undefined;
|
|
||||||
this.ownFocusActive = undefined;
|
|
||||||
|
|
||||||
if (this.memberEventTimeout) {
|
|
||||||
clearTimeout(this.memberEventTimeout);
|
|
||||||
this.memberEventTimeout = undefined;
|
|
||||||
}
|
|
||||||
if (timeout) {
|
|
||||||
// The sleep promise returns the string 'timeout' and the membership update void
|
|
||||||
// A success implies that the membership update was quicker then the timeout.
|
|
||||||
const raceResult = await Promise.race([this.triggerCallMembershipEventUpdate(), sleep(timeout, "timeout")]);
|
|
||||||
return raceResult !== "timeout";
|
|
||||||
} else {
|
|
||||||
await this.triggerCallMembershipEventUpdate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async onRTCSessionMemberUpdate(memberships: CallMembership[]): Promise<void> {
|
|
||||||
const isMyMembership = (m: CallMembership): boolean =>
|
|
||||||
m.sender === this.client.getUserId() && m.deviceId === this.client.getDeviceId();
|
|
||||||
|
|
||||||
if (this.isJoined() && !memberships.some(isMyMembership)) {
|
|
||||||
logger.warn("Missing own membership: force re-join");
|
|
||||||
// TODO: Should this be awaited? And is there anything to tell the focus?
|
|
||||||
return this.triggerCallMembershipEventUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getActiveFocus(): Focus | undefined {
|
|
||||||
if (this.ownFocusActive) {
|
|
||||||
// A livekit active focus
|
|
||||||
if (isLivekitFocusActive(this.ownFocusActive)) {
|
|
||||||
if (this.ownFocusActive.focus_selection === "oldest_membership") {
|
|
||||||
const oldestMembership = this.getOldestMembership();
|
|
||||||
return oldestMembership?.getPreferredFoci()[0];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.warn("Unknown own ActiveFocus type. This makes it impossible to connect to an SFU.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We do not understand the membership format (could be legacy). We default to oldestMembership
|
|
||||||
// Once there are other methods this is a hard error!
|
|
||||||
const oldestMembership = this.getOldestMembership();
|
|
||||||
return oldestMembership?.getPreferredFoci()[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private triggerCallMembershipEventUpdate = async (): Promise<void> => {
|
|
||||||
// TODO: Should this await on a shared promise?
|
|
||||||
if (this.updateCallMembershipRunning) {
|
|
||||||
this.needCallMembershipUpdate = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateCallMembershipRunning = true;
|
|
||||||
try {
|
|
||||||
// if anything triggers an update while the update is running, do another update afterwards
|
|
||||||
do {
|
|
||||||
this.needCallMembershipUpdate = false;
|
|
||||||
await this.updateCallMembershipEvent();
|
|
||||||
} while (this.needCallMembershipUpdate);
|
|
||||||
} finally {
|
|
||||||
this.updateCallMembershipRunning = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private makeNewMembership(deviceId: string): SessionMembershipData | EmptyObject {
|
|
||||||
// If we're joined, add our own
|
|
||||||
if (this.isJoined()) {
|
|
||||||
return this.makeMyMembership(deviceId);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs our own membership
|
|
||||||
*/
|
|
||||||
private makeMyMembership(deviceId: string): SessionMembershipData {
|
|
||||||
return {
|
|
||||||
call_id: "",
|
|
||||||
scope: "m.room",
|
|
||||||
application: "m.call",
|
|
||||||
device_id: deviceId,
|
|
||||||
expires: this.relativeExpiry,
|
|
||||||
focus_active: { type: "livekit", focus_selection: "oldest_membership" },
|
|
||||||
foci_preferred: this.ownFociPreferred ?? [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateCallMembershipEvent(): Promise<void> {
|
|
||||||
if (this.memberEventTimeout) {
|
|
||||||
clearTimeout(this.memberEventTimeout);
|
|
||||||
this.memberEventTimeout = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const roomState = this.room.getLiveTimeline().getState(EventTimeline.FORWARDS);
|
|
||||||
if (!roomState) throw new Error("Couldn't get room state for room " + this.room.roomId);
|
|
||||||
|
|
||||||
const localUserId = this.client.getUserId();
|
|
||||||
const localDeviceId = this.client.getDeviceId();
|
|
||||||
if (!localUserId || !localDeviceId) throw new Error("User ID or device ID was null!");
|
|
||||||
|
|
||||||
let newContent: EmptyObject | SessionMembershipData = {};
|
|
||||||
// TODO: add back expiary logic to non-legacy events
|
|
||||||
// previously we checked here if the event is timed out and scheduled a check if not.
|
|
||||||
// maybe there is a better way.
|
|
||||||
newContent = this.makeNewMembership(localDeviceId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.isJoined()) {
|
|
||||||
const stateKey = this.makeMembershipStateKey(localUserId, localDeviceId);
|
|
||||||
const prepareDelayedDisconnection = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const res = await resendIfRateLimited(() =>
|
|
||||||
this.client._unstable_sendDelayedStateEvent(
|
|
||||||
this.room.roomId,
|
|
||||||
{
|
|
||||||
delay: this.delayedLeaveEventDelayMs,
|
|
||||||
},
|
|
||||||
EventType.GroupCallMemberPrefix,
|
|
||||||
{}, // leave event
|
|
||||||
stateKey,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
this.disconnectDelayId = res.delay_id;
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
e instanceof MatrixError &&
|
|
||||||
e.errcode === "M_UNKNOWN" &&
|
|
||||||
e.data["org.matrix.msc4140.errcode"] === "M_MAX_DELAY_EXCEEDED"
|
|
||||||
) {
|
|
||||||
const maxDelayAllowed = e.data["org.matrix.msc4140.max_delay"];
|
|
||||||
if (
|
|
||||||
typeof maxDelayAllowed === "number" &&
|
|
||||||
this.delayedLeaveEventDelayMs > maxDelayAllowed
|
|
||||||
) {
|
|
||||||
this.delayedLeaveEventDelayMsOverride = maxDelayAllowed;
|
|
||||||
return prepareDelayedDisconnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.error("Failed to prepare delayed disconnection event:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await prepareDelayedDisconnection();
|
|
||||||
// Send join event _after_ preparing the delayed disconnection event
|
|
||||||
await resendIfRateLimited(() =>
|
|
||||||
this.client.sendStateEvent(this.room.roomId, EventType.GroupCallMemberPrefix, newContent, stateKey),
|
|
||||||
);
|
|
||||||
// If sending state cancels your own delayed state, prepare another delayed state
|
|
||||||
// TODO: Remove this once MSC4140 is stable & doesn't cancel own delayed state
|
|
||||||
if (this.disconnectDelayId !== undefined) {
|
|
||||||
try {
|
|
||||||
const knownDisconnectDelayId = this.disconnectDelayId;
|
|
||||||
await resendIfRateLimited(() =>
|
|
||||||
this.client._unstable_updateDelayedEvent(
|
|
||||||
knownDisconnectDelayId,
|
|
||||||
UpdateDelayedEventAction.Restart,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof MatrixError && e.errcode === "M_NOT_FOUND") {
|
|
||||||
// If we get a M_NOT_FOUND we prepare a new delayed event.
|
|
||||||
// In other error cases we do not want to prepare anything since we do not have the guarantee, that the
|
|
||||||
// future is not still running.
|
|
||||||
logger.warn("Failed to update delayed disconnection event, prepare it again:", e);
|
|
||||||
this.disconnectDelayId = undefined;
|
|
||||||
await prepareDelayedDisconnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.disconnectDelayId !== undefined) {
|
|
||||||
this.scheduleDelayDisconnection();
|
|
||||||
}
|
|
||||||
// TODO throw or log an error if this.disconnectDelayId === undefined
|
|
||||||
} else {
|
|
||||||
// Not joined
|
|
||||||
let sentDelayedDisconnect = false;
|
|
||||||
if (this.disconnectDelayId !== undefined) {
|
|
||||||
try {
|
|
||||||
const knownDisconnectDelayId = this.disconnectDelayId;
|
|
||||||
await resendIfRateLimited(() =>
|
|
||||||
this.client._unstable_updateDelayedEvent(
|
|
||||||
knownDisconnectDelayId,
|
|
||||||
UpdateDelayedEventAction.Send,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
sentDelayedDisconnect = true;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to send our delayed disconnection event:", e);
|
|
||||||
}
|
|
||||||
this.disconnectDelayId = undefined;
|
|
||||||
}
|
|
||||||
if (!sentDelayedDisconnect) {
|
|
||||||
await resendIfRateLimited(() =>
|
|
||||||
this.client.sendStateEvent(
|
|
||||||
this.room.roomId,
|
|
||||||
EventType.GroupCallMemberPrefix,
|
|
||||||
{},
|
|
||||||
this.makeMembershipStateKey(localUserId, localDeviceId),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("Sent updated call member event.");
|
|
||||||
} catch (e) {
|
|
||||||
const resendDelay = this.networkErrorRetryMs;
|
|
||||||
logger.warn(`Failed to send call member event (retrying in ${resendDelay}): ${e}`);
|
|
||||||
await sleep(resendDelay);
|
|
||||||
await this.triggerCallMembershipEventUpdate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleDelayDisconnection(): void {
|
|
||||||
this.memberEventTimeout = setTimeout(() => void this.delayDisconnection(), this.delayedLeaveEventRestartMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly delayDisconnection = async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
const knownDisconnectDelayId = this.disconnectDelayId!;
|
|
||||||
await resendIfRateLimited(() =>
|
|
||||||
this.client._unstable_updateDelayedEvent(knownDisconnectDelayId, UpdateDelayedEventAction.Restart),
|
|
||||||
);
|
|
||||||
this.scheduleDelayDisconnection();
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to delay our disconnection event:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private makeMembershipStateKey(localUserId: string, localDeviceId: string): string {
|
|
||||||
const stateKey = `${localUserId}_${localDeviceId}`;
|
|
||||||
if (/^org\.matrix\.msc(3757|3779)\b/.exec(this.room.getVersion())) {
|
|
||||||
return stateKey;
|
|
||||||
} else {
|
|
||||||
return `_${stateKey}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resendIfRateLimited<T>(func: () => Promise<T>, numRetriesAllowed: number = 1): Promise<T> {
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
return await func();
|
|
||||||
} catch (e) {
|
|
||||||
if (numRetriesAllowed > 0 && e instanceof HTTPError && e.isRateLimitError()) {
|
|
||||||
numRetriesAllowed--;
|
|
||||||
let resendDelay: number;
|
|
||||||
const defaultMs = 5000;
|
|
||||||
try {
|
|
||||||
resendDelay = e.getRetryAfterMs() ?? defaultMs;
|
|
||||||
logger.info(`Rate limited by server, retrying in ${resendDelay}ms`);
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn(
|
|
||||||
`Error while retrieving a rate-limit retry delay, retrying after default delay of ${defaultMs}`,
|
|
||||||
e,
|
|
||||||
);
|
|
||||||
resendDelay = defaultMs;
|
|
||||||
}
|
|
||||||
await sleep(resendDelay);
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -24,9 +24,8 @@ import { CallMembership } from "./CallMembership.ts";
|
|||||||
import { RoomStateEvent } from "../models/room-state.ts";
|
import { RoomStateEvent } from "../models/room-state.ts";
|
||||||
import { type Focus } from "./focus.ts";
|
import { type Focus } from "./focus.ts";
|
||||||
import { KnownMembership } from "../@types/membership.ts";
|
import { KnownMembership } from "../@types/membership.ts";
|
||||||
import { MembershipManager } from "./NewMembershipManager.ts";
|
import { MembershipManager } from "./MembershipManager.ts";
|
||||||
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
|
import { EncryptionManager, type IEncryptionManager } from "./EncryptionManager.ts";
|
||||||
import { LegacyMembershipManager } from "./LegacyMembershipManager.ts";
|
|
||||||
import { logDurationSync } from "../utils.ts";
|
import { logDurationSync } from "../utils.ts";
|
||||||
import { type Statistics } from "./types.ts";
|
import { type Statistics } from "./types.ts";
|
||||||
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
import { RoomKeyTransport } from "./RoomKeyTransport.ts";
|
||||||
@@ -77,6 +76,7 @@ export interface MembershipConfig {
|
|||||||
* Use the new Manager.
|
* Use the new Manager.
|
||||||
*
|
*
|
||||||
* Default: `false`.
|
* Default: `false`.
|
||||||
|
* @deprecated does nothing anymore we always default to the new memberhip manager.
|
||||||
*/
|
*/
|
||||||
useNewMembershipManager?: boolean;
|
useNewMembershipManager?: boolean;
|
||||||
|
|
||||||
@@ -387,19 +387,15 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// Create MembershipManager and pass the RTCSession logger (with room id info)
|
// Create MembershipManager and pass the RTCSession logger (with room id info)
|
||||||
if (joinConfig?.useNewMembershipManager ?? false) {
|
|
||||||
this.membershipManager = new MembershipManager(
|
this.membershipManager = new MembershipManager(
|
||||||
joinConfig,
|
joinConfig,
|
||||||
this.roomSubset,
|
this.roomSubset,
|
||||||
this.client,
|
this.client,
|
||||||
() => this.getOldestMembership(),
|
() => this.getOldestMembership(),
|
||||||
this.logger,
|
this.logger,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.membershipManager = new LegacyMembershipManager(joinConfig, this.roomSubset, this.client, () =>
|
|
||||||
this.getOldestMembership(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Create Encryption manager
|
// Create Encryption manager
|
||||||
let transport;
|
let transport;
|
||||||
if (joinConfig?.useExperimentalToDeviceTransport) {
|
if (joinConfig?.useExperimentalToDeviceTransport) {
|
||||||
|
@@ -26,7 +26,7 @@ import { type Focus } from "./focus.ts";
|
|||||||
import { isMyMembership, Status } from "./types.ts";
|
import { isMyMembership, Status } from "./types.ts";
|
||||||
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
import { isLivekitFocusActive } from "./LivekitFocus.ts";
|
||||||
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
import { type MembershipConfig } from "./MatrixRTCSession.ts";
|
||||||
import { ActionScheduler, type ActionUpdate } from "./NewMembershipManagerActionScheduler.ts";
|
import { ActionScheduler, type ActionUpdate } from "./MembershipManagerActionScheduler.ts";
|
||||||
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
import { TypedEventEmitter } from "../models/typed-event-emitter.ts";
|
||||||
import {
|
import {
|
||||||
MembershipManagerEvent,
|
MembershipManagerEvent,
|
||||||
@@ -233,7 +233,7 @@ export class MembershipManager
|
|||||||
|
|
||||||
if (this.scheduler.actions.find((a) => sendingMembershipActions.includes(a.type as MembershipActionType))) {
|
if (this.scheduler.actions.find((a) => sendingMembershipActions.includes(a.type as MembershipActionType))) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
"NewMembershipManger tried adding another `SendDelayedEvent` actions even though we already have one in the Queue\nActionQueueOnMemberUpdate:",
|
"tried adding another `SendDelayedEvent` actions even though we already have one in the Queue\nActionQueueOnMemberUpdate:",
|
||||||
this.scheduler.actions,
|
this.scheduler.actions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -285,7 +285,7 @@ export class MembershipManager
|
|||||||
parentLogger?: Logger,
|
parentLogger?: Logger,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = (parentLogger ?? rootLogger).getChild(`[NewMembershipManager]`);
|
this.logger = (parentLogger ?? rootLogger).getChild(`[MembershipManager]`);
|
||||||
const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
const [userId, deviceId] = [this.client.getUserId(), this.client.getDeviceId()];
|
||||||
if (userId === null) throw Error("Missing userId in client");
|
if (userId === null) throw Error("Missing userId in client");
|
||||||
if (deviceId === null) throw Error("Missing deviceId in client");
|
if (deviceId === null) throw Error("Missing deviceId in client");
|
@@ -1,7 +1,7 @@
|
|||||||
import { type Logger, logger as rootLogger } from "../logger.ts";
|
import { type Logger, logger as rootLogger } from "../logger.ts";
|
||||||
import { type EmptyObject } from "../matrix.ts";
|
import { type EmptyObject } from "../matrix.ts";
|
||||||
import { sleep } from "../utils.ts";
|
import { sleep } from "../utils.ts";
|
||||||
import { MembershipActionType } from "./NewMembershipManager.ts";
|
import { MembershipActionType } from "./MembershipManager.ts";
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export interface Action {
|
export interface Action {
|
Reference in New Issue
Block a user