Element Call video rooms (#9267)

* Add an element_call_url config option

* Add a labs flag for Element Call video rooms

* Add Element Call as another video rooms backend

* Consolidate event power level defaults

* Remember to clean up participantsExpirationTimer

* Fix a code smell

* Test the clean method

* Fix some strict mode errors

* Test that clean still works when there are no state events

* Test auto-approval of Element Call widget capabilities

* Deduplicate some code to placate SonarCloud

* Fix more strict mode errors

* Test that calls disconnect when leaving the room

* Test the get methods of JitsiCall and ElementCall more

* Test Call.ts even more

* Test creation of Element video rooms

* Test that createRoom works for non-video-rooms

* Test Call's get method rather than the methods of derived classes

* Ensure that the clean method is able to preserve devices

* Remove duplicate clean method

* Fix lints

* Fix some strict mode errors in RoomPreviewCard

* Test RoomPreviewCard changes

* Quick and dirty hotfix for the community testing session

* Revert "Quick and dirty hotfix for the community testing session"

This reverts commit 37056514fbc040aaf1bff2539da770a1c8ba72a2.

* Fix the event schema for org.matrix.msc3401.call.member devices

* Remove org.matrix.call_duplicate_session from Element Call capabilities

It's no longer used by Element Call when running as a widget.

* Replace element_call_url with a map

* Make PiPs work for virtual widgets

* Auto-approve room timeline capability

Because Element Call uses this now

* Create a reusable isVideoRoom util
This commit is contained in:
Robin 2022-09-16 11:12:27 -04:00 committed by GitHub
parent db5716b776
commit cb735c9439
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1699 additions and 1384 deletions

View file

@ -39,6 +39,7 @@ describe("StopGapWidget", () => {
creatorUserId: "@alice:example.org",
type: "example",
url: "https://example.org",
roomId: "!1:example.org",
},
room: mkRoom(client, "!1:example.org"),
userId: "@alice:example.org",

View file

@ -15,10 +15,10 @@ limitations under the License.
*/
import { mocked, MockedObject } from "jest-mock";
import { ClientEvent, ITurnServer as IClientTurnServer, MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
import { Direction, MatrixEvent } from "matrix-js-sdk/src/matrix";
import { ITurnServer, Widget, WidgetDriver, WidgetKind } from "matrix-widget-api";
import { Widget, MatrixWidgetType, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api";
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
@ -27,22 +27,75 @@ import { stubClient } from "../../test-utils";
describe("StopGapWidgetDriver", () => {
let client: MockedObject<MatrixClient>;
let driver: WidgetDriver;
const mkDefaultDriver = (): WidgetDriver => new StopGapWidgetDriver(
[],
new Widget({
id: "test",
creatorUserId: "@alice:example.org",
type: "example",
url: "https://example.org",
}),
WidgetKind.Room,
false,
"!1:example.org",
);
beforeEach(() => {
stubClient();
client = mocked(MatrixClientPeg.get());
client.getUserId.mockReturnValue("@alice:example.org");
});
driver = new StopGapWidgetDriver(
it("auto-approves capabilities of virtual Element Call widgets", async () => {
const driver = new StopGapWidgetDriver(
[],
new Widget({
id: "test",
id: "group_call",
creatorUserId: "@alice:example.org",
type: "example",
url: "https://example.org",
type: MatrixWidgetType.Custom,
url: "https://call.element.io",
}),
WidgetKind.Room,
true,
"!1:example.org",
);
// These are intentionally raw identifiers rather than constants, so it's obvious what's being requested
const requestedCapabilities = new Set([
"m.always_on_screen",
"town.robin.msc3846.turn_servers",
"org.matrix.msc2762.timeline:!1:example.org",
"org.matrix.msc2762.receive.state_event:m.room.member",
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call",
"org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call",
"org.matrix.msc2762.send.state_event:org.matrix.msc3401.call.member#@alice:example.org",
"org.matrix.msc2762.receive.state_event:org.matrix.msc3401.call.member",
"org.matrix.msc3819.send.to_device:m.call.invite",
"org.matrix.msc3819.receive.to_device:m.call.invite",
"org.matrix.msc3819.send.to_device:m.call.candidates",
"org.matrix.msc3819.receive.to_device:m.call.candidates",
"org.matrix.msc3819.send.to_device:m.call.answer",
"org.matrix.msc3819.receive.to_device:m.call.answer",
"org.matrix.msc3819.send.to_device:m.call.hangup",
"org.matrix.msc3819.receive.to_device:m.call.hangup",
"org.matrix.msc3819.send.to_device:m.call.reject",
"org.matrix.msc3819.receive.to_device:m.call.reject",
"org.matrix.msc3819.send.to_device:m.call.select_answer",
"org.matrix.msc3819.receive.to_device:m.call.select_answer",
"org.matrix.msc3819.send.to_device:m.call.negotiate",
"org.matrix.msc3819.receive.to_device:m.call.negotiate",
"org.matrix.msc3819.send.to_device:m.call.sdp_stream_metadata_changed",
"org.matrix.msc3819.receive.to_device:m.call.sdp_stream_metadata_changed",
"org.matrix.msc3819.send.to_device:org.matrix.call.sdp_stream_metadata_changed",
"org.matrix.msc3819.receive.to_device:org.matrix.call.sdp_stream_metadata_changed",
"org.matrix.msc3819.send.to_device:m.call.replaces",
"org.matrix.msc3819.receive.to_device:m.call.replaces",
]);
// As long as this resolves, we'll know that it didn't try to pop up a modal
const approvedCapabilities = await driver.validateCapabilities(requestedCapabilities);
expect(approvedCapabilities).toEqual(requestedCapabilities);
});
describe("sendToDevice", () => {
@ -59,6 +112,10 @@ describe("StopGapWidgetDriver", () => {
},
};
let driver: WidgetDriver;
beforeEach(() => { driver = mkDefaultDriver(); });
it("sends unencrypted messages", async () => {
await driver.sendToDevice("org.example.foo", false, contentMap);
expect(client.queueToDevice.mock.calls).toMatchSnapshot();
@ -80,6 +137,10 @@ describe("StopGapWidgetDriver", () => {
});
describe("getTurnServers", () => {
let driver: WidgetDriver;
beforeEach(() => { driver = mkDefaultDriver(); });
it("stops if VoIP isn't supported", async () => {
jest.spyOn(client, "pollingTurnServers", "get").mockReturnValue(false);
const servers = driver.getTurnServers();
@ -135,6 +196,10 @@ describe("StopGapWidgetDriver", () => {
});
describe("readEventRelations", () => {
let driver: WidgetDriver;
beforeEach(() => { driver = mkDefaultDriver(); });
it('reads related events from the current room', async () => {
jest.spyOn(RoomViewStore.instance, 'getRoomId').mockReturnValue('!this-room-id');