You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-23 17:02:25 +03:00
@@ -15,8 +15,8 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { type MatrixEvent } from "../../../src";
|
import { type MatrixEvent } from "../../../src";
|
||||||
import { CallMembership, DEFAULT_EXPIRE_DURATION, type RtcMembershipData } from "../../../src/matrixrtc/CallMembership";
|
import { CallMembership, DEFAULT_EXPIRE_DURATION } from "../../../src/matrixrtc/CallMembership";
|
||||||
import { membershipTemplate } from "./mocks";
|
import { rtcMembershipTemplate, sessionMembershipTemplate } from "./mocks";
|
||||||
|
|
||||||
function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
|
function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
|
||||||
return {
|
return {
|
||||||
@@ -29,6 +29,7 @@ function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
|
|||||||
|
|
||||||
describe("CallMembership", () => {
|
describe("CallMembership", () => {
|
||||||
describe("SessionMembershipData", () => {
|
describe("SessionMembershipData", () => {
|
||||||
|
const membershipTemplate = sessionMembershipTemplate;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
});
|
});
|
||||||
@@ -185,15 +186,7 @@ describe("CallMembership", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("RtcMembershipData", () => {
|
describe("RtcMembershipData", () => {
|
||||||
const membershipTemplate: RtcMembershipData = {
|
const membershipTemplate = rtcMembershipTemplate;
|
||||||
"slot_id": "m.call#",
|
|
||||||
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
|
|
||||||
"member": { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
|
|
||||||
"rtc_transports": [{ type: "livekit" }],
|
|
||||||
"m.call.intent": "voice",
|
|
||||||
"versions": [],
|
|
||||||
};
|
|
||||||
|
|
||||||
it("rejects membership with no slot_id", () => {
|
it("rejects membership with no slot_id", () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));
|
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));
|
||||||
|
|||||||
@@ -14,12 +14,29 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { encodeBase64, EventType, MatrixClient, type MatrixError, type MatrixEvent, type Room } from "../../../src";
|
import {
|
||||||
|
encodeBase64,
|
||||||
|
EventType,
|
||||||
|
MatrixClient,
|
||||||
|
MatrixEvent,
|
||||||
|
RelationType,
|
||||||
|
type MatrixError,
|
||||||
|
type Room,
|
||||||
|
} from "../../../src";
|
||||||
import { KnownMembership } from "../../../src/@types/membership";
|
import { KnownMembership } from "../../../src/@types/membership";
|
||||||
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
|
import { MatrixRTCSession, MatrixRTCSessionEvent } from "../../../src/matrixrtc/MatrixRTCSession";
|
||||||
import { Status, type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
|
import { Status, type EncryptionKeysEventContent } from "../../../src/matrixrtc/types";
|
||||||
import { secureRandomString } from "../../../src/randomstring";
|
import { secureRandomString } from "../../../src/randomstring";
|
||||||
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey, type MembershipData, mockRoomState } from "./mocks";
|
import {
|
||||||
|
makeMockEvent,
|
||||||
|
makeMockRoom,
|
||||||
|
sessionMembershipTemplate as membershipTemplate,
|
||||||
|
makeKey,
|
||||||
|
type MembershipData,
|
||||||
|
mockRoomState,
|
||||||
|
rtcMembershipTemplate,
|
||||||
|
sessionMembershipTemplate,
|
||||||
|
} from "./mocks";
|
||||||
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts";
|
import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts";
|
||||||
|
|
||||||
const mockFocus = { type: "mock" };
|
const mockFocus = { type: "mock" };
|
||||||
@@ -50,6 +67,7 @@ describe("MatrixRTCSession", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("roomSessionForRoom", () => {
|
describe("roomSessionForRoom", () => {
|
||||||
|
const membershipTemplate = sessionMembershipTemplate;
|
||||||
it("creates a room-scoped session from room state", async () => {
|
it("creates a room-scoped session from room state", async () => {
|
||||||
const mockRoom = makeMockRoom([membershipTemplate]);
|
const mockRoom = makeMockRoom([membershipTemplate]);
|
||||||
|
|
||||||
@@ -206,6 +224,41 @@ describe("MatrixRTCSession", () => {
|
|||||||
sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
sess = await MatrixRTCSession.sessionForRoom(client, mockRoom, callSession);
|
||||||
expect(sess.memberships).toHaveLength(0);
|
expect(sess.memberships).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
it("fetches related events if needed from room", async () => {
|
||||||
|
const testMembership = {
|
||||||
|
...rtcMembershipTemplate,
|
||||||
|
"m.relates_to": { event_id: "id", rel_type: RelationType.Reference as const },
|
||||||
|
// hack for simple makeMockRoom construction
|
||||||
|
"user_id": rtcMembershipTemplate.member.user_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockRoom = makeMockRoom([testMembership]);
|
||||||
|
mockRoom.findEventById = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation((id) =>
|
||||||
|
id === "id"
|
||||||
|
? new MatrixEvent({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 })
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
|
||||||
|
expect(sess.memberships[0].createdTs()).toBe(100);
|
||||||
|
});
|
||||||
|
it("fetches related events if needed from cs api", async () => {
|
||||||
|
const testMembership = {
|
||||||
|
...rtcMembershipTemplate,
|
||||||
|
"m.relates_to": { event_id: "id", rel_type: RelationType.Reference as const },
|
||||||
|
// hack for simple makeMockRoom construction
|
||||||
|
"user_id": rtcMembershipTemplate.member.user_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockRoom = makeMockRoom([testMembership]);
|
||||||
|
mockRoom.findEventById = jest.fn().mockReturnValue(undefined);
|
||||||
|
client.fetchRoomEvent = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ content: { ...rtcMembershipTemplate }, origin_server_ts: 100 });
|
||||||
|
sess = await MatrixRTCSession.sessionForSlot(client, mockRoom, callSession);
|
||||||
|
expect(sess.memberships[0].createdTs()).toBe(100);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getOldestMembership", () => {
|
describe("getOldestMembership", () => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { ClientEvent, EventTimeline, MatrixClient } from "../../../src";
|
import { ClientEvent, EventTimeline, MatrixClient } from "../../../src";
|
||||||
import { RoomStateEvent } from "../../../src/models/room-state";
|
import { RoomStateEvent } from "../../../src/models/room-state";
|
||||||
import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
|
import { MatrixRTCSessionManager, MatrixRTCSessionManagerEvents } from "../../../src/matrixrtc/MatrixRTCSessionManager";
|
||||||
import { makeMockRoom, membershipTemplate, mockRoomState } from "./mocks";
|
import { makeMockRoom, sessionMembershipTemplate, mockRoomState } from "./mocks";
|
||||||
import { logger } from "../../../src/logger";
|
import { logger } from "../../../src/logger";
|
||||||
|
|
||||||
describe("MatrixRTCSessionManager", () => {
|
describe("MatrixRTCSessionManager", () => {
|
||||||
@@ -42,7 +42,7 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const room1 = makeMockRoom([membershipTemplate]);
|
const room1 = makeMockRoom([sessionMembershipTemplate]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room1);
|
client.emit(ClientEvent.Room, room1);
|
||||||
@@ -58,7 +58,7 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, onStarted);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
|
const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other" }]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room1);
|
client.emit(ClientEvent.Room, room1);
|
||||||
@@ -76,14 +76,14 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionEnded, rEnd);
|
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionEnded, rEnd);
|
||||||
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionStarted, rStart);
|
client.matrixRTC.once(MatrixRTCSessionManagerEvents.SessionStarted, rStart);
|
||||||
|
|
||||||
const room1 = makeMockRoom([membershipTemplate]);
|
const room1 = makeMockRoom([sessionMembershipTemplate]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room1);
|
client.emit(ClientEvent.Room, room1);
|
||||||
await startPromise;
|
await startPromise;
|
||||||
|
|
||||||
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
|
||||||
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
||||||
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
|
client.emit(RoomStateEvent.Events, membEvent, roomState, null);
|
||||||
@@ -112,14 +112,14 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other" }]);
|
const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other" }]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room1);
|
client.emit(ClientEvent.Room, room1);
|
||||||
expect(onStarted).not.toHaveBeenCalled();
|
expect(onStarted).not.toHaveBeenCalled();
|
||||||
onStarted.mockClear();
|
onStarted.mockClear();
|
||||||
|
|
||||||
const room2 = makeMockRoom([{ ...membershipTemplate, application: "m.notCall", call_id: "test" }]);
|
const room2 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.notCall", call_id: "test" }]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1, room2]);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room2);
|
client.emit(ClientEvent.Room, room2);
|
||||||
@@ -127,7 +127,7 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
expect(onStarted).toHaveBeenCalled();
|
expect(onStarted).toHaveBeenCalled();
|
||||||
onStarted.mockClear();
|
onStarted.mockClear();
|
||||||
|
|
||||||
mockRoomState(room2, [{ user_id: membershipTemplate.user_id }]);
|
mockRoomState(room2, [{ user_id: sessionMembershipTemplate.user_id }]);
|
||||||
jest.spyOn(client, "getRoom").mockReturnValue(room2);
|
jest.spyOn(client, "getRoom").mockReturnValue(room2);
|
||||||
|
|
||||||
const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
const roomState = room2.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
@@ -137,7 +137,7 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
expect(onEnded).toHaveBeenCalled();
|
expect(onEnded).toHaveBeenCalled();
|
||||||
onEnded.mockClear();
|
onEnded.mockClear();
|
||||||
|
|
||||||
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
|
||||||
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
const roomStateOther = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
@@ -153,13 +153,13 @@ describe("MatrixRTCSessionManager", () => {
|
|||||||
it("Doesn't fire event if unrelated sessions ends", () => {
|
it("Doesn't fire event if unrelated sessions ends", () => {
|
||||||
const onEnded = jest.fn();
|
const onEnded = jest.fn();
|
||||||
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
client.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionEnded, onEnded);
|
||||||
const room1 = makeMockRoom([{ ...membershipTemplate, application: "m.other_app" }]);
|
const room1 = makeMockRoom([{ ...sessionMembershipTemplate, application: "m.other_app" }]);
|
||||||
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
jest.spyOn(client, "getRooms").mockReturnValue([room1]);
|
||||||
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
jest.spyOn(client, "getRoom").mockReturnValue(room1);
|
||||||
|
|
||||||
client.emit(ClientEvent.Room, room1);
|
client.emit(ClientEvent.Room, room1);
|
||||||
|
|
||||||
mockRoomState(room1, [{ user_id: membershipTemplate.user_id }]);
|
mockRoomState(room1, [{ user_id: sessionMembershipTemplate.user_id }]);
|
||||||
|
|
||||||
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
const roomState = room1.getLiveTimeline().getState(EventTimeline.FORWARDS)!;
|
||||||
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
const membEvent = roomState.getStateEvents("org.matrix.msc3401.call.member")[0];
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
type SessionMembershipData,
|
type SessionMembershipData,
|
||||||
type LivekitFocusSelection,
|
type LivekitFocusSelection,
|
||||||
} from "../../../src/matrixrtc";
|
} from "../../../src/matrixrtc";
|
||||||
import { makeMockClient, makeMockRoom, membershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
import { makeMockClient, makeMockRoom, sessionMembershipTemplate, mockCallMembership, type MockClient } from "./mocks";
|
||||||
import { MembershipManager } from "../../../src/matrixrtc/MembershipManager.ts";
|
import { MembershipManager } from "../../../src/matrixrtc/MembershipManager.ts";
|
||||||
import { waitFor } from "../../test-utils/test-utils.ts";
|
import { waitFor } from "../../test-utils/test-utils.ts";
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ describe("MembershipManager", () => {
|
|||||||
// Default to fake timers.
|
// Default to fake timers.
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
client = makeMockClient("@alice:example.org", "AAAAAAA");
|
client = makeMockClient("@alice:example.org", "AAAAAAA");
|
||||||
room = makeMockRoom([membershipTemplate]);
|
room = makeMockRoom([sessionMembershipTemplate]);
|
||||||
// Provide a default mock that is like the default "non error" server behaviour.
|
// Provide a default mock that is like the default "non error" server behaviour.
|
||||||
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
|
(client._unstable_sendDelayedStateEvent as Mock<any>).mockResolvedValue({ delay_id: "id" });
|
||||||
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
|
(client._unstable_updateDelayedEvent as Mock<any>).mockResolvedValue(undefined);
|
||||||
@@ -385,7 +385,7 @@ describe("MembershipManager", () => {
|
|||||||
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
await jest.advanceTimersByTimeAsync(RESTART_DELAY);
|
await jest.advanceTimersByTimeAsync(RESTART_DELAY);
|
||||||
// first simulate the sync, then resolve sending the delayed event.
|
// first simulate the sync, then resolve sending the delayed event.
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
|
||||||
resolve({ delay_id: "id" });
|
resolve({ delay_id: "id" });
|
||||||
// Let the scheduler run one iteration so that the new join gets sent
|
// Let the scheduler run one iteration so that the new join gets sent
|
||||||
await jest.runOnlyPendingTimersAsync();
|
await jest.runOnlyPendingTimersAsync();
|
||||||
@@ -468,7 +468,7 @@ describe("MembershipManager", () => {
|
|||||||
describe("onRTCSessionMemberUpdate()", () => {
|
describe("onRTCSessionMemberUpdate()", () => {
|
||||||
it("does nothing if not joined", async () => {
|
it("does nothing if not joined", async () => {
|
||||||
const manager = new MembershipManager({}, room, client, callSession);
|
const manager = new MembershipManager({}, room, client, callSession);
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
|
||||||
await jest.advanceTimersToNextTimerAsync();
|
await jest.advanceTimersToNextTimerAsync();
|
||||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||||
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
expect(client._unstable_sendDelayedStateEvent).not.toHaveBeenCalled();
|
||||||
@@ -485,7 +485,7 @@ describe("MembershipManager", () => {
|
|||||||
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
||||||
|
|
||||||
await manager.onRTCSessionMemberUpdate([
|
await manager.onRTCSessionMemberUpdate([
|
||||||
mockCallMembership(membershipTemplate, room.roomId),
|
mockCallMembership(sessionMembershipTemplate, room.roomId),
|
||||||
mockCallMembership(
|
mockCallMembership(
|
||||||
{ ...(myMembership as SessionMembershipData), user_id: client.getUserId()! },
|
{ ...(myMembership as SessionMembershipData), user_id: client.getUserId()! },
|
||||||
room.roomId,
|
room.roomId,
|
||||||
@@ -508,7 +508,7 @@ describe("MembershipManager", () => {
|
|||||||
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
(client._unstable_sendDelayedStateEvent as Mock).mockClear();
|
||||||
|
|
||||||
// Our own membership is removed:
|
// Our own membership is removed:
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
|
||||||
await jest.advanceTimersByTimeAsync(1);
|
await jest.advanceTimersByTimeAsync(1);
|
||||||
expect(client.sendStateEvent).toHaveBeenCalled();
|
expect(client.sendStateEvent).toHaveBeenCalled();
|
||||||
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled();
|
expect(client._unstable_sendDelayedStateEvent).toHaveBeenCalled();
|
||||||
@@ -531,7 +531,7 @@ describe("MembershipManager", () => {
|
|||||||
|
|
||||||
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
const { resolve } = createAsyncHandle(client._unstable_sendDelayedStateEvent);
|
||||||
await jest.advanceTimersByTimeAsync(10_000);
|
await jest.advanceTimersByTimeAsync(10_000);
|
||||||
await manager.onRTCSessionMemberUpdate([mockCallMembership(membershipTemplate, room.roomId)]);
|
await manager.onRTCSessionMemberUpdate([mockCallMembership(sessionMembershipTemplate, room.roomId)]);
|
||||||
resolve({ delay_id: "id" });
|
resolve({ delay_id: "id" });
|
||||||
await jest.advanceTimersByTimeAsync(10_000);
|
await jest.advanceTimersByTimeAsync(10_000);
|
||||||
|
|
||||||
@@ -900,7 +900,10 @@ describe("MembershipManager", () => {
|
|||||||
const manager = new MembershipManager({}, room, client, callSession);
|
const manager = new MembershipManager({}, room, client, callSession);
|
||||||
manager.join([]);
|
manager.join([]);
|
||||||
expect(manager.isActivated()).toEqual(true);
|
expect(manager.isActivated()).toEqual(true);
|
||||||
const membership = mockCallMembership({ ...membershipTemplate, user_id: client.getUserId()! }, room.roomId);
|
const membership = mockCallMembership(
|
||||||
|
{ ...sessionMembershipTemplate, user_id: client.getUserId()! },
|
||||||
|
room.roomId,
|
||||||
|
);
|
||||||
await manager.onRTCSessionMemberUpdate([membership]);
|
await manager.onRTCSessionMemberUpdate([membership]);
|
||||||
await manager.updateCallIntent("video");
|
await manager.updateCallIntent("video");
|
||||||
expect(client.sendStateEvent).toHaveBeenCalledTimes(2);
|
expect(client.sendStateEvent).toHaveBeenCalledTimes(2);
|
||||||
@@ -914,7 +917,7 @@ describe("MembershipManager", () => {
|
|||||||
manager.join([]);
|
manager.join([]);
|
||||||
expect(manager.isActivated()).toEqual(true);
|
expect(manager.isActivated()).toEqual(true);
|
||||||
const membership = mockCallMembership(
|
const membership = mockCallMembership(
|
||||||
{ ...membershipTemplate, "user_id": client.getUserId()!, "m.call.intent": "video" },
|
{ ...sessionMembershipTemplate, "user_id": client.getUserId()!, "m.call.intent": "video" },
|
||||||
room.roomId,
|
room.roomId,
|
||||||
);
|
);
|
||||||
await manager.onRTCSessionMemberUpdate([membership]);
|
await manager.onRTCSessionMemberUpdate([membership]);
|
||||||
@@ -927,7 +930,7 @@ describe("MembershipManager", () => {
|
|||||||
it("Should prefix log with MembershipManager used", async () => {
|
it("Should prefix log with MembershipManager used", async () => {
|
||||||
const spy = jest.spyOn(console, "error");
|
const spy = jest.spyOn(console, "error");
|
||||||
const client = makeMockClient("@alice:example.org", "AAAAAAA");
|
const client = makeMockClient("@alice:example.org", "AAAAAAA");
|
||||||
const room = makeMockRoom([membershipTemplate]);
|
const room = makeMockRoom([sessionMembershipTemplate]);
|
||||||
|
|
||||||
const membershipManager = new MembershipManager(undefined, room, client, callSession);
|
const membershipManager = new MembershipManager(undefined, room, client, callSession);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManage
|
|||||||
import { type CallMembership, type Statistics } from "../../../src/matrixrtc";
|
import { type CallMembership, type Statistics } from "../../../src/matrixrtc";
|
||||||
import { type ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts";
|
import { type ToDeviceKeyTransport } from "../../../src/matrixrtc/ToDeviceKeyTransport.ts";
|
||||||
import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts";
|
import { KeyTransportEvents, type KeyTransportEventsHandlerMap } from "../../../src/matrixrtc/IKeyTransport.ts";
|
||||||
import { membershipTemplate, mockCallMembership } from "./mocks.ts";
|
import { sessionMembershipTemplate, mockCallMembership } from "./mocks.ts";
|
||||||
import { decodeBase64, TypedEventEmitter } from "../../../src";
|
import { decodeBase64, TypedEventEmitter } from "../../../src";
|
||||||
import { RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts";
|
import { RoomAndToDeviceTransport } from "../../../src/matrixrtc/RoomAndToDeviceKeyTransport.ts";
|
||||||
import { type RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts";
|
import { type RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport.ts";
|
||||||
@@ -864,7 +864,7 @@ describe("RTCEncryptionManager", () => {
|
|||||||
|
|
||||||
function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership {
|
function aCallMembership(userId: string, deviceId: string, ts: number = 1000): CallMembership {
|
||||||
return mockCallMembership(
|
return mockCallMembership(
|
||||||
{ ...membershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts },
|
{ ...sessionMembershipTemplate, user_id: userId, device_id: deviceId, created_ts: ts },
|
||||||
"!room:id",
|
"!room:id",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { makeMockEvent, makeMockRoom, membershipTemplate, makeKey } from "./mocks";
|
import { makeMockEvent, makeMockRoom, sessionMembershipTemplate, makeKey } from "./mocks";
|
||||||
import { RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport";
|
import { RoomKeyTransport } from "../../../src/matrixrtc/RoomKeyTransport";
|
||||||
import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport";
|
import { KeyTransportEvents } from "../../../src/matrixrtc/IKeyTransport";
|
||||||
import { EventType, MatrixClient, RoomEvent } from "../../../src";
|
import { EventType, MatrixClient, RoomEvent } from "../../../src";
|
||||||
@@ -48,7 +48,7 @@ describe("RoomKeyTransport", () => {
|
|||||||
roomEventEncryptionKeysReceivedTotalAge: 0,
|
roomEventEncryptionKeysReceivedTotalAge: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
room = makeMockRoom([membershipTemplate]);
|
room = makeMockRoom([sessionMembershipTemplate]);
|
||||||
client = new MatrixClient({ baseUrl: "base_url" });
|
client = new MatrixClient({ baseUrl: "base_url" });
|
||||||
await client.matrixRTC.start();
|
await client.matrixRTC.start();
|
||||||
transport = new RoomKeyTransport(room, client, statistics, {
|
transport = new RoomKeyTransport(room, client, statistics, {
|
||||||
|
|||||||
@@ -16,13 +16,17 @@ limitations under the License.
|
|||||||
|
|
||||||
import { EventEmitter } from "stream";
|
import { EventEmitter } from "stream";
|
||||||
|
|
||||||
import { EventType, type Room, RoomEvent, type MatrixClient, type MatrixEvent } from "../../../src";
|
import { EventType, MatrixEvent, type Room, RoomEvent, type MatrixClient } from "../../../src";
|
||||||
import { CallMembership, type SessionMembershipData } from "../../../src/matrixrtc/CallMembership";
|
import {
|
||||||
|
CallMembership,
|
||||||
|
type RtcMembershipData,
|
||||||
|
type SessionMembershipData,
|
||||||
|
} from "../../../src/matrixrtc/CallMembership";
|
||||||
import { secureRandomString } from "../../../src/randomstring";
|
import { secureRandomString } from "../../../src/randomstring";
|
||||||
|
|
||||||
export type MembershipData = (SessionMembershipData | {}) & { user_id: string };
|
export type MembershipData = (SessionMembershipData | RtcMembershipData | {}) & { user_id: string };
|
||||||
|
|
||||||
export const membershipTemplate: SessionMembershipData & { user_id: string } = {
|
export const sessionMembershipTemplate: SessionMembershipData & { user_id: string } = {
|
||||||
"application": "m.call",
|
"application": "m.call",
|
||||||
"call_id": "",
|
"call_id": "",
|
||||||
"user_id": "@mock:user.example",
|
"user_id": "@mock:user.example",
|
||||||
@@ -44,6 +48,15 @@ export const membershipTemplate: SessionMembershipData & { user_id: string } = {
|
|||||||
"m.call.intent": "voice",
|
"m.call.intent": "voice",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const rtcMembershipTemplate: RtcMembershipData = {
|
||||||
|
"slot_id": "m.call#",
|
||||||
|
"application": { "type": "m.call", "m.call.id": "", "m.call.intent": "voice" },
|
||||||
|
"member": { user_id: "@alice:example.org", device_id: "AAAAAAA", id: "xyzHASHxyz" },
|
||||||
|
"rtc_transports": [{ type: "livekit" }],
|
||||||
|
"m.call.intent": "voice",
|
||||||
|
"versions": [],
|
||||||
|
};
|
||||||
|
|
||||||
export type MockClient = Pick<
|
export type MockClient = Pick<
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
| "getUserId"
|
| "getUserId"
|
||||||
@@ -131,15 +144,14 @@ export function makeMockEvent(
|
|||||||
content: any,
|
content: any,
|
||||||
timestamp?: number,
|
timestamp?: number,
|
||||||
): MatrixEvent {
|
): MatrixEvent {
|
||||||
return {
|
return new MatrixEvent({
|
||||||
getType: jest.fn().mockReturnValue(type),
|
event_id: "mock_event_id",
|
||||||
getContent: jest.fn().mockReturnValue(content),
|
sender,
|
||||||
getSender: jest.fn().mockReturnValue(sender),
|
type,
|
||||||
getTs: jest.fn().mockReturnValue(timestamp ?? Date.now()),
|
content,
|
||||||
getRoomId: jest.fn().mockReturnValue(roomId),
|
room_id: roomId,
|
||||||
getId: jest.fn().mockReturnValue(secureRandomString(8)),
|
origin_server_ts: timestamp ?? 0,
|
||||||
isDecryptionFailure: jest.fn().mockReturnValue(false),
|
});
|
||||||
} as unknown as MatrixEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mockRTCEvent({ user_id: sender, ...membershipData }: MembershipData, roomId: string): MatrixEvent {
|
export function mockRTCEvent({ user_id: sender, ...membershipData }: MembershipData, roomId: string): MatrixEvent {
|
||||||
|
|||||||
@@ -230,8 +230,8 @@ export class CallMembership {
|
|||||||
* @throws if the data does not match any known membership format.
|
* @throws if the data does not match any known membership format.
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private matrixEvent: MatrixEvent,
|
private readonly matrixEvent: MatrixEvent,
|
||||||
private relatedEvent?: MatrixEvent,
|
private readonly relatedEvent?: MatrixEvent,
|
||||||
) {
|
) {
|
||||||
const data = matrixEvent.getContent() as any;
|
const data = matrixEvent.getContent() as any;
|
||||||
const sessionErrors: string[] = [];
|
const sessionErrors: string[] = [];
|
||||||
@@ -252,8 +252,8 @@ export class CallMembership {
|
|||||||
const eventId = matrixEvent.getId();
|
const eventId = matrixEvent.getId();
|
||||||
const sender = matrixEvent.getSender();
|
const sender = matrixEvent.getSender();
|
||||||
|
|
||||||
if (eventId === undefined) throw new Error("parentEvent is missing eventId field");
|
if (eventId === undefined) throw new Error("CallMembership matrixEvent is missing eventId field");
|
||||||
if (sender === undefined) throw new Error("parentEvent is missing sender field");
|
if (sender === undefined) throw new Error("CallMembership matrixEvent is missing sender field");
|
||||||
this.matrixEventData = { eventId, sender };
|
this.matrixEventData = { eventId, sender };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -349,37 +349,40 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
|
const callMemberEvents = roomState.getStateEvents(EventType.GroupCallMemberPrefix);
|
||||||
const callMemberships: CallMembership[] = [];
|
const callMemberships: CallMembership[] = [];
|
||||||
|
|
||||||
|
const createMembership = async (memberEvent: MatrixEvent): Promise<CallMembership | undefined> => {
|
||||||
|
const relatedEventId = memberEvent.relationEventId;
|
||||||
|
const fetchRelatedEvent = async (): Promise<MatrixEvent | undefined> => {
|
||||||
|
const eventData = await client
|
||||||
|
.fetchRoomEvent(room.roomId, relatedEventId!)
|
||||||
|
.catch((e) => logger.error(`Could not get related event ${relatedEventId} for call membership`, e));
|
||||||
|
|
||||||
|
return eventData ? new MatrixEvent(eventData) : undefined;
|
||||||
|
};
|
||||||
|
const relatedEvent = relatedEventId
|
||||||
|
? (room.findEventById(relatedEventId) ?? (await fetchRelatedEvent()))
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let membership = undefined;
|
||||||
|
try {
|
||||||
|
membership = new CallMembership(memberEvent, relatedEvent);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Couldn't construct call membership: ", e);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// static check for newly created memberships
|
||||||
|
if (!deepCompare(membership.slotDescription, slotDescription)) {
|
||||||
|
logger.info(
|
||||||
|
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return membership;
|
||||||
|
};
|
||||||
|
|
||||||
for (const memberEvent of callMemberEvents) {
|
for (const memberEvent of callMemberEvents) {
|
||||||
let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId());
|
let membership = existingMemberships?.find((m) => m.eventId === memberEvent.getId());
|
||||||
if (!membership) {
|
if (!membership) membership = await createMembership(memberEvent);
|
||||||
const relatedEventId = memberEvent.relationEventId;
|
if (!membership) continue;
|
||||||
const getRelatedMatrixEvent = async (): Promise<MatrixEvent | undefined> => {
|
|
||||||
const eventData = await client
|
|
||||||
.fetchRoomEvent(room.roomId, relatedEventId!)
|
|
||||||
.catch((e) =>
|
|
||||||
logger.error(`Could not get related event ${relatedEventId} for call membership`, e),
|
|
||||||
);
|
|
||||||
|
|
||||||
return eventData ? new MatrixEvent(eventData) : undefined;
|
|
||||||
};
|
|
||||||
const relatedEvent = relatedEventId
|
|
||||||
? (room.findEventById(relatedEventId) ?? (await getRelatedMatrixEvent()))
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
membership = new CallMembership(memberEvent, relatedEvent);
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn("Couldn't construct call membership: ", e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// static check for newly created memberships
|
|
||||||
if (!deepCompare(membership.slotDescription, slotDescription)) {
|
|
||||||
logger.info(
|
|
||||||
`Ignoring membership of user ${membership.sender} for a different session: ${JSON.stringify(membership.slotDescription)}`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamic checks for all (including existing) memberships
|
// Dynamic checks for all (including existing) memberships
|
||||||
if (membership.isExpired()) {
|
if (membership.isExpired()) {
|
||||||
@@ -394,7 +397,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
}
|
}
|
||||||
|
|
||||||
callMemberships.sort((a, b) => a.createdTs() - b.createdTs());
|
callMemberships.sort((a, b) => a.createdTs() - b.createdTs());
|
||||||
if (callMemberships.length > 1) {
|
if (callMemberships.length >= 1) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Call memberships in room ${room.roomId}, in order: `,
|
`Call memberships in room ${room.roomId}, in order: `,
|
||||||
callMemberships.map((m) => [m.createdTs(), m.sender]),
|
callMemberships.map((m) => [m.createdTs(), m.sender]),
|
||||||
@@ -814,7 +817,7 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
*
|
*
|
||||||
* This function should be called when the room members or call memberships might have changed.
|
* This function should be called when the room members or call memberships might have changed.
|
||||||
*/
|
*/
|
||||||
private recalculateSessionMembers = async (): Promise<void> => {
|
private async recalculateSessionMembers(): Promise<void> {
|
||||||
const oldMemberships = this.memberships;
|
const oldMemberships = this.memberships;
|
||||||
const newMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
|
const newMemberships = await MatrixRTCSession.sessionMembershipsForSlot(
|
||||||
this.room,
|
this.room,
|
||||||
@@ -863,5 +866,5 @@ export class MatrixRTCSession extends TypedEventEmitter<
|
|||||||
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
|
void this.encryptionManager?.onMembershipsUpdate(oldMemberships);
|
||||||
|
|
||||||
this.setExpiryTimer();
|
this.setExpiryTimer();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user