diff --git a/spec/unit/matrixrtc/CallMembership.spec.ts b/spec/unit/matrixrtc/CallMembership.spec.ts index a1fe08f7f..83c994961 100644 --- a/spec/unit/matrixrtc/CallMembership.spec.ts +++ b/spec/unit/matrixrtc/CallMembership.spec.ts @@ -81,29 +81,35 @@ describe("CallMembership", () => { }); it("returns preferred foci", () => { - const mockFocus = { type: "this_is_a_mock_focus" }; + const mockFocus = { type: "livekit", livekit_service_url: "https://example.org" }; const fakeEvent = makeMockEvent(0, { ...membershipTemplate, foci_preferred: [mockFocus] }); const membership = new CallMembership(fakeEvent); - expect(membership.transports).toEqual([mockFocus]); + expect(membership.transports.length).toEqual(1); + expect(membership.transports[0]).toEqual(expect.objectContaining({ type: "livekit" })); }); describe("getTransport", () => { - const mockFocus = { type: "this_is_a_mock_focus" }; + const mockFocus = { type: "livekit", livekit_service_url: "https://example.org" }; const oldestMembership = new CallMembership(makeMockEvent(0, membershipTemplate)); it("gets the correct active transport with oldest_membership", () => { const membership = new CallMembership( makeMockEvent(0, { ...membershipTemplate, + // The list of foci_preferred provided by the homeserver. (in the test example we just provide one) + // The oldest member logic will use the first item in this list. + // The multi-sfu logic will (theoretically) also use all the items in the list at once + // (currently the js-sdk sets it to only one item in multi-sfu mode). foci_preferred: [mockFocus], focus_active: { type: "livekit", focus_selection: "oldest_membership" }, }), ); // if we are the oldest member we use our focus. - expect(membership.getTransport(membership)).toStrictEqual(mockFocus); + expect(membership.getTransport(membership)).toEqual(expect.objectContaining({ type: "livekit" })); + expect(membership.transports[0]).toEqual(expect.objectContaining({ type: "livekit" })); // If there is an older member we use its focus. - expect(membership.getTransport(oldestMembership)).toBe(membershipTemplate.foci_preferred[0]); + expect(membership.getTransport(oldestMembership)).toEqual(expect.objectContaining({ type: "livekit" })); }); it("gets the correct active transport with multi_sfu", () => { @@ -115,7 +121,7 @@ describe("CallMembership", () => { }), ); - // if we are the oldest member we use our focus. + // We use our focus. expect(membership.getTransport(membership)).toStrictEqual(mockFocus); // If there is an older member we still use our own focus in multi sfu. @@ -130,7 +136,6 @@ describe("CallMembership", () => { }), ); - // if we are the oldest member we use our focus. expect(membership.getTransport(membership)).toBeUndefined(); }); }); diff --git a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts index 69c1c02a9..1c64e53bf 100644 --- a/spec/unit/matrixrtc/MatrixRTCSession.spec.ts +++ b/spec/unit/matrixrtc/MatrixRTCSession.spec.ts @@ -38,7 +38,7 @@ import { } from "./mocks"; import { RTCEncryptionManager } from "../../../src/matrixrtc/RTCEncryptionManager.ts"; -const mockFocus = { type: "mock" }; +const mockFocus = { type: "livekit", livekit_service_url: "https://test.org" }; const textEncoder = new TextEncoder(); diff --git a/src/matrixrtc/CallMembership.ts b/src/matrixrtc/CallMembership.ts index 77b2a2db6..99a34315d 100644 --- a/src/matrixrtc/CallMembership.ts +++ b/src/matrixrtc/CallMembership.ts @@ -22,6 +22,7 @@ import type { RTCCallIntent, Transport } from "./types.ts"; import { type IContent, type MatrixEvent } from "../models/event.ts"; import { type RelationType } from "../@types/event.ts"; import { logger } from "../logger.ts"; +import { UNSTABLE_STICKY_KEY, type StickyKeyContent } from "src/models/room-sticky-events.ts"; /** * The default duration in milliseconds that a membership is considered valid for. @@ -33,7 +34,7 @@ export const DEFAULT_EXPIRE_DURATION = 1000 * 60 * 60 * 4; type CallScope = "m.room" | "m.user"; type Member = { user_id: string; device_id: string; id: string }; -export interface RtcMembershipData { +export type RtcMembershipData = { "slot_id": string; "member": Member; "m.relates_to"?: { @@ -47,9 +48,7 @@ export interface RtcMembershipData { }; "rtc_transports": Transport[]; "versions": string[]; - "msc4354_sticky_key"?: string; - "sticky_key"?: string; -} +} & StickyKeyContent; const checkRtcMembershipData = ( data: IContent, @@ -103,19 +102,19 @@ const checkRtcMembershipData = ( } // optional fields - if ((data.sticky_key ?? data.msc4354_sticky_key) === undefined) { + if ((data[UNSTABLE_STICKY_KEY.name] ?? data[UNSTABLE_STICKY_KEY.altName]) === undefined) { errors.push(prefix + "sticky_key or msc4354_sticky_key must be a defined"); } - if (data.sticky_key !== undefined && typeof data.sticky_key !== "string") { + if (data[UNSTABLE_STICKY_KEY.name] !== undefined && typeof data[UNSTABLE_STICKY_KEY.name] !== "string") { errors.push(prefix + "sticky_key must be a string"); } - if (data.msc4354_sticky_key !== undefined && typeof data.msc4354_sticky_key !== "string") { + if (data[UNSTABLE_STICKY_KEY.altName] !== undefined && typeof data[UNSTABLE_STICKY_KEY.altName] !== "string") { errors.push(prefix + "msc4354_sticky_key must be a string"); } if ( - data.sticky_key !== undefined && - data.msc4354_sticky_key !== undefined && - data.sticky_key !== data.msc4354_sticky_key + data[UNSTABLE_STICKY_KEY.name] !== undefined && + data[UNSTABLE_STICKY_KEY.altName] !== undefined && + data[UNSTABLE_STICKY_KEY.name] !== data[UNSTABLE_STICKY_KEY.altName] ) { errors.push(prefix + "sticky_key and msc4354_sticky_key must be equal if both are defined"); } diff --git a/src/models/room-sticky-events.ts b/src/models/room-sticky-events.ts index 3e9e26a2b..d0fcaea72 100644 --- a/src/models/room-sticky-events.ts +++ b/src/models/room-sticky-events.ts @@ -1,9 +1,33 @@ +/* +Copyright 2023 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 { type EitherAnd, UnstableValue } from "matrix-events-sdk"; + import { logger as loggerInstance } from "../logger.ts"; import { type MatrixEvent } from "./event.ts"; import { TypedEventEmitter } from "./typed-event-emitter.ts"; const logger = loggerInstance.getChild("RoomStickyEvents"); +export const UNSTABLE_STICKY_KEY = new UnstableValue("sticky_key", "msc4354_sticky_key"); +export type StickyKeyContent = EitherAnd< + { [UNSTABLE_STICKY_KEY.name]: string }, + { [UNSTABLE_STICKY_KEY.altName]: string } +>; + export enum RoomStickyEventsEvent { Update = "RoomStickyEvents.Update", }