Implement MSC3846: Allowing widgets to access TURN servers (#9061)
* Implement MSC3819: Allowing widgets to send/receive to-device messages * Don't change the room events and state events drivers * Implement MSC3846: Allowing widgets to access TURN servers * Update to latest matrix-widget-api changes * Support sending encrypted to-device messages * Yield a TURN server immediately * Use queueToDevice for better reliability * Update types for latest WidgetDriver changes * Upgrade matrix-widget-api * Add tests * Test StopGapWidget * Fix a potential memory leak * Add tests * Empty commit to retry CI
This commit is contained in:
parent
103b60dfb5
commit
28ed87bffe
3 changed files with 106 additions and 4 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2020 - 2021 The Matrix.org Foundation C.I.C.
|
||||
* Copyright 2020 - 2022 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.
|
||||
|
@ -20,6 +20,7 @@ import {
|
|||
IOpenIDCredentials,
|
||||
IOpenIDUpdate,
|
||||
ISendEventDetails,
|
||||
ITurnServer,
|
||||
IRoomEvent,
|
||||
MatrixCapabilities,
|
||||
OpenIDRequestState,
|
||||
|
@ -30,6 +31,7 @@ import {
|
|||
WidgetEventCapability,
|
||||
WidgetKind,
|
||||
} from "matrix-widget-api";
|
||||
import { ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { IContent, IEvent, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
@ -62,6 +64,12 @@ function setRememberedCapabilitiesForWidget(widget: Widget, caps: Capability[])
|
|||
localStorage.setItem(`widget_${widget.id}_approved_caps`, JSON.stringify(caps));
|
||||
}
|
||||
|
||||
const normalizeTurnServer = ({ urls, username, credential }: IClientTurnServer): ITurnServer => ({
|
||||
uris: urls,
|
||||
username,
|
||||
password: credential,
|
||||
});
|
||||
|
||||
export class StopGapWidgetDriver extends WidgetDriver {
|
||||
private allowedCapabilities: Set<Capability>;
|
||||
|
||||
|
@ -326,4 +334,36 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
|||
public async navigate(uri: string): Promise<void> {
|
||||
navigateToPermalink(uri);
|
||||
}
|
||||
|
||||
public async* getTurnServers(): AsyncGenerator<ITurnServer> {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client.pollingTurnServers || !client.getTurnServers().length) return;
|
||||
|
||||
let setTurnServer: (server: ITurnServer) => void;
|
||||
let setError: (error: Error) => void;
|
||||
|
||||
const onTurnServers = ([server]: IClientTurnServer[]) => setTurnServer(normalizeTurnServer(server));
|
||||
const onTurnServersError = (error: Error, fatal: boolean) => { if (fatal) setError(error); };
|
||||
|
||||
client.on(ClientEvent.TurnServers, onTurnServers);
|
||||
client.on(ClientEvent.TurnServersError, onTurnServersError);
|
||||
|
||||
try {
|
||||
const initialTurnServer = client.getTurnServers()[0];
|
||||
yield normalizeTurnServer(initialTurnServer);
|
||||
|
||||
// Repeatedly listen for new TURN servers until an error occurs or
|
||||
// the caller stops this generator
|
||||
while (true) {
|
||||
yield await new Promise<ITurnServer>((resolve, reject) => {
|
||||
setTurnServer = resolve;
|
||||
setError = reject;
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
// The loop was broken - clean up
|
||||
client.off(ClientEvent.TurnServers, onTurnServers);
|
||||
client.off(ClientEvent.TurnServersError, onTurnServersError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { Widget, WidgetKind, WidgetDriver } from "matrix-widget-api";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { Widget, WidgetKind, WidgetDriver, ITurnServer } from "matrix-widget-api";
|
||||
import { MatrixClient, ClientEvent, ITurnServer as IClientTurnServer } from "matrix-js-sdk/src/client";
|
||||
import { DeviceInfo } from "matrix-js-sdk/src/crypto/deviceinfo";
|
||||
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
|
@ -76,4 +76,59 @@ describe("StopGapWidgetDriver", () => {
|
|||
expect(client.encryptAndSendToDevices.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTurnServers", () => {
|
||||
it("stops if VoIP isn't supported", async () => {
|
||||
jest.spyOn(client, "pollingTurnServers", "get").mockReturnValue(false);
|
||||
const servers = driver.getTurnServers();
|
||||
expect(await servers.next()).toEqual({ value: undefined, done: true });
|
||||
});
|
||||
|
||||
it("stops if the homeserver provides no TURN servers", async () => {
|
||||
const servers = driver.getTurnServers();
|
||||
expect(await servers.next()).toEqual({ value: undefined, done: true });
|
||||
});
|
||||
|
||||
it("gets TURN servers", async () => {
|
||||
const server1: ITurnServer = {
|
||||
uris: [
|
||||
"turn:turn.example.com:3478?transport=udp",
|
||||
"turn:10.20.30.40:3478?transport=tcp",
|
||||
"turns:10.20.30.40:443?transport=tcp",
|
||||
],
|
||||
username: "1443779631:@user:example.com",
|
||||
password: "JlKfBy1QwLrO20385QyAtEyIv0=",
|
||||
};
|
||||
const server2: ITurnServer = {
|
||||
uris: [
|
||||
"turn:turn.example.com:3478?transport=udp",
|
||||
"turn:10.20.30.40:3478?transport=tcp",
|
||||
"turns:10.20.30.40:443?transport=tcp",
|
||||
],
|
||||
username: "1448999322:@user:example.com",
|
||||
password: "hunter2",
|
||||
};
|
||||
const clientServer1: IClientTurnServer = {
|
||||
urls: server1.uris,
|
||||
username: server1.username,
|
||||
credential: server1.password,
|
||||
};
|
||||
const clientServer2: IClientTurnServer = {
|
||||
urls: server2.uris,
|
||||
username: server2.username,
|
||||
credential: server2.password,
|
||||
};
|
||||
|
||||
client.getTurnServers.mockReturnValue([clientServer1]);
|
||||
const servers = driver.getTurnServers();
|
||||
expect(await servers.next()).toEqual({ value: server1, done: false });
|
||||
|
||||
const nextServer = servers.next();
|
||||
client.getTurnServers.mockReturnValue([clientServer2]);
|
||||
client.emit(ClientEvent.TurnServers, [clientServer2]);
|
||||
expect(await nextServer).toEqual({ value: server2, done: false });
|
||||
|
||||
await servers.return(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ export function createTestClient(): MatrixClient {
|
|||
const eventEmitter = new EventEmitter();
|
||||
let txnId = 1;
|
||||
|
||||
return {
|
||||
const client = {
|
||||
getHomeserverUrl: jest.fn(),
|
||||
getIdentityServerUrl: jest.fn(),
|
||||
getDomain: jest.fn().mockReturnValue("matrix.org"),
|
||||
|
@ -118,6 +118,7 @@ export function createTestClient(): MatrixClient {
|
|||
getThirdpartyProtocols: jest.fn().mockResolvedValue({}),
|
||||
getClientWellKnown: jest.fn().mockReturnValue(null),
|
||||
supportsVoip: jest.fn().mockReturnValue(true),
|
||||
getTurnServers: jest.fn().mockReturnValue([]),
|
||||
getTurnServersExpiry: jest.fn().mockReturnValue(2 ^ 32),
|
||||
getThirdpartyUser: jest.fn().mockResolvedValue([]),
|
||||
getAccountData: (type) => {
|
||||
|
@ -173,6 +174,12 @@ export function createTestClient(): MatrixClient {
|
|||
queueToDevice: jest.fn().mockResolvedValue(undefined),
|
||||
encryptAndSendToDevices: jest.fn().mockResolvedValue(undefined),
|
||||
} as unknown as MatrixClient;
|
||||
|
||||
Object.defineProperty(client, "pollingTurnServers", {
|
||||
configurable: true,
|
||||
get: () => true,
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
type MakeEventPassThruProps = {
|
||||
|
|
Loading…
Reference in a new issue