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
356 lines
15 KiB
TypeScript
356 lines
15 KiB
TypeScript
/*
|
|
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 MatrixEvent } from "../../../src";
|
|
import {
|
|
CallMembership,
|
|
type SessionMembershipData,
|
|
DEFAULT_EXPIRE_DURATION,
|
|
type RtcMembershipData,
|
|
} from "../../../src/matrixrtc/CallMembership";
|
|
import { membershipTemplate } from "./mocks";
|
|
|
|
function makeMockEvent(originTs = 0, content = {}): MatrixEvent {
|
|
return {
|
|
getTs: jest.fn().mockReturnValue(originTs),
|
|
getSender: jest.fn().mockReturnValue("@alice:example.org"),
|
|
getId: jest.fn().mockReturnValue("$eventid"),
|
|
getContent: jest.fn().mockReturnValue(content),
|
|
} as unknown as MatrixEvent;
|
|
}
|
|
|
|
describe("CallMembership", () => {
|
|
describe("SessionMembershipData", () => {
|
|
beforeEach(() => {
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
const membershipTemplate: SessionMembershipData = {
|
|
"call_id": "",
|
|
"scope": "m.room",
|
|
"application": "m.call",
|
|
"device_id": "AAAAAAA",
|
|
"focus_active": { type: "livekit", focus_selection: "oldest_membership" },
|
|
"foci_preferred": [{ type: "livekit" }],
|
|
"m.call.intent": "voice",
|
|
};
|
|
|
|
it("rejects membership with no device_id", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { device_id: undefined })));
|
|
}).toThrow();
|
|
});
|
|
|
|
it("rejects membership with no call_id", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { call_id: undefined })));
|
|
}).toThrow();
|
|
});
|
|
|
|
it("allow membership with no scope", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, Object.assign({}, membershipTemplate, { scope: undefined })));
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("uses event timestamp if no created_ts", () => {
|
|
const membership = new CallMembership(makeMockEvent(12345, membershipTemplate));
|
|
expect(membership.createdTs()).toEqual(12345);
|
|
});
|
|
|
|
it("uses created_ts if present", () => {
|
|
const membership = new CallMembership(
|
|
makeMockEvent(12345, Object.assign({}, membershipTemplate, { created_ts: 67890 })),
|
|
);
|
|
expect(membership.createdTs()).toEqual(67890);
|
|
});
|
|
|
|
it("considers memberships unexpired if local age low enough", () => {
|
|
const fakeEvent = makeMockEvent(1000, membershipTemplate);
|
|
fakeEvent.getTs = jest.fn().mockReturnValue(Date.now() - (DEFAULT_EXPIRE_DURATION - 1));
|
|
expect(new CallMembership(fakeEvent).isExpired()).toEqual(false);
|
|
});
|
|
|
|
it("considers memberships expired if local age large enough", () => {
|
|
const fakeEvent = makeMockEvent(1000, membershipTemplate);
|
|
fakeEvent.getTs = jest.fn().mockReturnValue(Date.now() - (DEFAULT_EXPIRE_DURATION + 1));
|
|
expect(new CallMembership(fakeEvent).isExpired()).toEqual(true);
|
|
});
|
|
|
|
it("returns preferred foci", () => {
|
|
const mockFocus = { type: "this_is_a_mock_focus" };
|
|
const fakeEvent = makeMockEvent(0, { ...membershipTemplate, foci_preferred: [mockFocus] });
|
|
const membership = new CallMembership(fakeEvent);
|
|
expect(membership.transports).toEqual([mockFocus]);
|
|
});
|
|
|
|
describe("getTransport", () => {
|
|
const mockFocus = { type: "this_is_a_mock_focus" };
|
|
const oldestMembership = new CallMembership(makeMockEvent(0, membershipTemplate));
|
|
it("gets the correct active transport with oldest_membership", () => {
|
|
const membership = new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
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);
|
|
|
|
// If there is an older member we use its focus.
|
|
expect(membership.getTransport(oldestMembership)).toBe(membershipTemplate.foci_preferred[0]);
|
|
});
|
|
|
|
it("gets the correct active transport with multi_sfu", () => {
|
|
const membership = new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
foci_preferred: [mockFocus],
|
|
focus_active: { type: "livekit", focus_selection: "multi_sfu" },
|
|
}),
|
|
);
|
|
|
|
// if we are the oldest member 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.
|
|
expect(membership.getTransport(oldestMembership)).toBe(mockFocus);
|
|
});
|
|
it("does not provide focus if the selection method is unknown", () => {
|
|
const membership = new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
foci_preferred: [mockFocus],
|
|
focus_active: { type: "livekit", focus_selection: "unknown" },
|
|
}),
|
|
);
|
|
|
|
// if we are the oldest member we use our focus.
|
|
expect(membership.getTransport(membership)).toBeUndefined();
|
|
});
|
|
});
|
|
describe("correct values from computed fields", () => {
|
|
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
|
|
it("returns correct sender", () => {
|
|
expect(membership.sender).toBe("@alice:example.org");
|
|
});
|
|
it("returns correct eventId", () => {
|
|
expect(membership.eventId).toBe("$eventid");
|
|
});
|
|
it("returns correct slot_id", () => {
|
|
expect(membership.slotId).toBe("m.call#");
|
|
expect(membership.slotDescription).toStrictEqual({ id: "", application: "m.call" });
|
|
});
|
|
it("returns correct deviceId", () => {
|
|
expect(membership.deviceId).toBe("AAAAAAA");
|
|
});
|
|
it("returns correct call intent", () => {
|
|
expect(membership.callIntent).toBe("voice");
|
|
});
|
|
it("returns correct application", () => {
|
|
expect(membership.application).toStrictEqual("m.call");
|
|
});
|
|
it("returns correct applicationData", () => {
|
|
expect(membership.applicationData).toStrictEqual({ "type": "m.call", "m.call.intent": "voice" });
|
|
});
|
|
it("returns correct scope", () => {
|
|
expect(membership.scope).toBe("m.room");
|
|
});
|
|
it("returns correct membershipID", () => {
|
|
expect(membership.membershipID).toBe("0");
|
|
});
|
|
it("returns correct unused fields", () => {
|
|
expect(membership.getAbsoluteExpiry()).toBe(DEFAULT_EXPIRE_DURATION);
|
|
expect(membership.getMsUntilExpiry()).toBe(DEFAULT_EXPIRE_DURATION - Date.now());
|
|
expect(membership.isExpired()).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("RtcMembershipData", () => {
|
|
const membershipTemplate: 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": [],
|
|
};
|
|
|
|
it("rejects membership with no slot_id", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, { ...membershipTemplate, slot_id: undefined }));
|
|
}).toThrow();
|
|
});
|
|
|
|
it("rejects membership with no application", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, { ...membershipTemplate, application: undefined }));
|
|
}).toThrow();
|
|
});
|
|
|
|
it("rejects membership with incorrect application", () => {
|
|
expect(() => {
|
|
new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
application: { wrong_type_key: "unknown" },
|
|
}),
|
|
);
|
|
}).toThrow();
|
|
});
|
|
|
|
it("rejects membership with no member", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, { ...membershipTemplate, member: undefined }));
|
|
}).toThrow();
|
|
});
|
|
|
|
it("rejects membership with incorrect member", () => {
|
|
expect(() => {
|
|
new CallMembership(makeMockEvent(0, { ...membershipTemplate, member: { i: "test" } }));
|
|
}).toThrow();
|
|
expect(() => {
|
|
new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
member: { id: "test", device_id: "test", user_id_wrong: "test" },
|
|
}),
|
|
);
|
|
}).toThrow();
|
|
expect(() => {
|
|
new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
member: { id: "test", device_id_wrong: "test", user_id_wrong: "test" },
|
|
}),
|
|
);
|
|
}).toThrow();
|
|
expect(() => {
|
|
new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
member: { id: "test", device_id: "test", user_id: "@@test" },
|
|
}),
|
|
);
|
|
}).toThrow();
|
|
expect(() => {
|
|
new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
member: { id: "test", device_id: "test", user_id: "@test:user.id" },
|
|
}),
|
|
);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("considers memberships unexpired if local age low enough", () => {
|
|
// TODO link prev event
|
|
});
|
|
|
|
it("considers memberships expired if local age large enough", () => {
|
|
// TODO link prev event
|
|
});
|
|
|
|
describe("getTransport", () => {
|
|
it("gets the correct active transport with oldest_membership", () => {
|
|
const oldestMembership = new CallMembership(
|
|
makeMockEvent(0, {
|
|
...membershipTemplate,
|
|
rtc_transports: [{ type: "oldest_transport" }],
|
|
}),
|
|
);
|
|
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
|
|
|
|
// if we are the oldest member we use our focus.
|
|
expect(membership.getTransport(membership)).toStrictEqual({ type: "livekit" });
|
|
|
|
// If there is an older member we use our own focus focus. (RtcMembershipData always uses multi sfu)
|
|
expect(membership.getTransport(oldestMembership)).toStrictEqual({ type: "livekit" });
|
|
});
|
|
});
|
|
describe("correct values from computed fields", () => {
|
|
const membership = new CallMembership(makeMockEvent(0, membershipTemplate));
|
|
it("returns correct sender", () => {
|
|
expect(membership.sender).toBe("@alice:example.org");
|
|
});
|
|
it("returns correct eventId", () => {
|
|
expect(membership.eventId).toBe("$eventid");
|
|
});
|
|
it("returns correct slot_id", () => {
|
|
expect(membership.slotId).toBe("m.call#");
|
|
expect(membership.slotDescription).toStrictEqual({ id: "", application: "m.call" });
|
|
});
|
|
it("returns correct deviceId", () => {
|
|
expect(membership.deviceId).toBe("AAAAAAA");
|
|
});
|
|
it("returns correct call intent", () => {
|
|
expect(membership.callIntent).toBe("voice");
|
|
});
|
|
it("returns correct application", () => {
|
|
expect(membership.application).toStrictEqual("m.call");
|
|
});
|
|
it("returns correct applicationData", () => {
|
|
expect(membership.applicationData).toStrictEqual({
|
|
"type": "m.call",
|
|
"m.call.id": "",
|
|
"m.call.intent": "voice",
|
|
});
|
|
});
|
|
it("returns correct scope", () => {
|
|
expect(membership.scope).toBe(undefined);
|
|
});
|
|
it("returns correct membershipID", () => {
|
|
expect(membership.membershipID).toBe("xyzHASHxyz");
|
|
});
|
|
it("returns correct expiration fields", () => {
|
|
expect(membership.getAbsoluteExpiry()).toBe(DEFAULT_EXPIRE_DURATION);
|
|
expect(membership.getMsUntilExpiry()).toBe(DEFAULT_EXPIRE_DURATION - Date.now());
|
|
expect(membership.isExpired()).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("expiry calculation", () => {
|
|
let fakeEvent: MatrixEvent;
|
|
let membership: CallMembership;
|
|
|
|
beforeEach(() => {
|
|
// server origin timestamp for this event is 1000
|
|
fakeEvent = makeMockEvent(1000, membershipTemplate);
|
|
membership = new CallMembership(fakeEvent!);
|
|
|
|
jest.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it("calculates time until expiry", () => {
|
|
jest.setSystemTime(2000);
|
|
// should be using absolute expiry time
|
|
expect(membership.getMsUntilExpiry()).toEqual(DEFAULT_EXPIRE_DURATION - 1000);
|
|
});
|
|
});
|
|
});
|