From d5e638c8f7849525ad8f6c6854b0d80e6f948f5d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 27 Jun 2022 17:41:07 -0400 Subject: [PATCH] WIP --- package.json | 1 + src/ClientContext.tsx | 65 +++++++++++++++++++++++------------- src/matrix-utils.ts | 63 ++++++++++++++++++++++++++++++++-- src/room/useLoadGroupCall.js | 3 +- yarn.lock | 19 +++++++++-- 5 files changed, 121 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index c236782..a141e9c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#ebcb26f1b3b9e2d709615fde03f9ce6ac77871f1", + "matrix-widget-api": "^0.1.0-beta.18", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", "pako": "^2.0.4", diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 09d910b..275d8ef 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -26,9 +26,10 @@ import React, { import { useHistory } from "react-router-dom"; import { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { logger } from "matrix-js-sdk/src/logger"; import { ErrorView } from "./FullScreenView"; -import { initClient, defaultHomeserver } from "./matrix-utils"; +import { initClient, initMatroskaClient, defaultHomeserver } from "./matrix-utils"; declare global { interface Window { @@ -86,37 +87,52 @@ export const ClientProvider: FC = ({ children }) => { }); useEffect(() => { - const restore = async (): Promise< + const init = async (): Promise< Pick > => { - try { - const session = loadSession(); + const query = new URLSearchParams(window.location.search); + const widgetId = query.get("widgetId"); + const parentUrl = query.get("parentUrl"); - if (session) { - /* eslint-disable camelcase */ - const { user_id, device_id, access_token, passwordlessUser } = - session; + if (widgetId && parentUrl) { + // We're inside a widget, so let's engage *Matroska mode* + logger.log("Using a Matroska client"); - const client = await initClient({ - baseUrl: defaultHomeserver, - accessToken: access_token, - userId: user_id, - deviceId: device_id, - }); - /* eslint-enable camelcase */ + return { + client: await initMatroskaClient(widgetId, parentUrl), + isPasswordlessUser: false, + }; + } else { + // We're running as a standalone application + try { + const session = loadSession(); - return { client, isPasswordlessUser: passwordlessUser }; + if (session) { + /* eslint-disable camelcase */ + const { user_id, device_id, access_token, passwordlessUser } = + session; + + logger.log("Using a standalone client"); + const client = await initClient({ + baseUrl: defaultHomeserver, + accessToken: access_token, + userId: user_id, + deviceId: device_id, + }); + /* eslint-enable camelcase */ + + return { client, isPasswordlessUser: passwordlessUser }; + } + + return { client: undefined, isPasswordlessUser: false }; + } catch (err) { + clearSession(); + throw err; } - - return { client: undefined, isPasswordlessUser: false }; - } catch (err) { - console.error(err); - clearSession(); - throw err; } }; - restore() + init() .then(({ client, isPasswordlessUser }) => { setState({ client, @@ -126,7 +142,8 @@ export const ClientProvider: FC = ({ children }) => { userName: client?.getUserIdLocalpart(), }); }) - .catch(() => { + .catch((err) => { + logger.error(err); setState({ client: undefined, loading: false, diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index b76e913..1ee352b 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -5,15 +5,17 @@ import { MemoryStore } from "matrix-js-sdk/src/store/memory"; import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; import { LocalStorageCryptoStore } from "matrix-js-sdk/src/crypto/store/localStorage-crypto-store"; import { MemoryCryptoStore } from "matrix-js-sdk/src/crypto/store/memory-crypto-store"; -import { createClient, MatrixClient } from "matrix-js-sdk/src/matrix"; +import { createClient, createRoomWidgetClient, MatrixClient } from "matrix-js-sdk/src/matrix"; import { ICreateClientOpts } from "matrix-js-sdk/src/matrix"; import { ClientEvent } from "matrix-js-sdk/src/client"; +import { EventType } from "matrix-js-sdk/src/@types/event"; import { Visibility, Preset } from "matrix-js-sdk/src/@types/partials"; import { GroupCallIntent, GroupCallType, } from "matrix-js-sdk/src/webrtc/groupCall"; import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync"; +import { WidgetApi } from "matrix-widget-api"; import IndexedDBWorker from "./IndexedDBWorker?worker"; @@ -42,6 +44,63 @@ function waitForSync(client: MatrixClient) { }); } +// The event types that the app needs to be able to send/receive in Matroska +// mode in order to function +const SEND_RECV_STATE = [ + { eventType: EventType.RoomMember }, + { eventType: EventType.GroupCallPrefix }, + { eventType: EventType.GroupCallMemberPrefix }, +]; +const SEND_RECV_TO_DEVICE = [ + EventType.CallInvite, + EventType.CallCandidates, + EventType.CallAnswer, + EventType.CallHangup, + EventType.CallReject, + EventType.CallSelectAnswer, + EventType.CallNegotiate, + EventType.CallSDPStreamMetadataChanged, + EventType.CallSDPStreamMetadataChangedPrefix, + EventType.CallReplaces, + "org.matrix.call_duplicate_session", +]; + +export async function initMatroskaClient( + widgetId: string, parentUrl: string, +): Promise { + // In this mode, we use a special client which routes all requests through + // the host application via the widget API + + // The rest of the data we need is encoded in the fragment so as to avoid + // leaking it to the server + const fragmentQueryStart = window.location.hash.indexOf("?"); + const roomId = window.location.hash.substring(0, fragmentQueryStart); + const fragmentQuery = new URLSearchParams(window.location.hash.substring(fragmentQueryStart)); + + // Since all data should be coming from the host application, there's no + // need to persist anything, and therefore we can use the default stores + // We don't even need to set up crypto! + const client = createRoomWidgetClient( + new WidgetApi(widgetId, new URL(parentUrl).origin), + { + sendState: SEND_RECV_STATE, + receiveState: SEND_RECV_STATE, + sendToDevice: SEND_RECV_TO_DEVICE, + receiveToDevice: SEND_RECV_TO_DEVICE, + }, + roomId, + { + baseUrl: "", + userId: fragmentQuery.get("userId"), + deviceId: fragmentQuery.get("deviceId"), + timelineSupport: true, + }, + ); + + await client.startClient(); + return client; +} + export async function initClient( clientOptions: ICreateClientOpts ): Promise { @@ -83,7 +142,7 @@ export async function initClient( ...storeOpts, ...clientOptions, useAuthorizationHeader: true, - // Use a relatively low timeout for API calls: this is a realtime application + // Use a relatively low timeout for API calls: this is a realtime app // so we don't want API calls taking ages, we'd rather they just fail. localTimeoutMs: 5000, }); diff --git a/src/room/useLoadGroupCall.js b/src/room/useLoadGroupCall.js index 894fded..2637dca 100644 --- a/src/room/useLoadGroupCall.js +++ b/src/room/useLoadGroupCall.js @@ -23,7 +23,8 @@ async function fetchGroupCall( viaServers = undefined, timeout = 5000 ) { - const { roomId } = await client.joinRoom(roomIdOrAlias, { viaServers }); + //const { roomId } = await client.joinRoom(roomIdOrAlias, { viaServers }); + const roomId = roomIdOrAlias; return new Promise((resolve, reject) => { let timeoutId; diff --git a/yarn.lock b/yarn.lock index 68339b6..d1bf9f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2868,6 +2868,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/events@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + "@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.3": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -8602,9 +8607,9 @@ matrix-events-sdk@^0.0.1-beta.7: resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1-beta.7.tgz#5ffe45eba1f67cc8d7c2377736c728b322524934" integrity sha512-9jl4wtWanUFSy2sr2lCjErN/oC8KTAtaeaozJtrgot1JiQcEI4Rda9OLgQ7nLKaqb4Z/QUx/fR3XpDzm5Jy1JA== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#404f8e130e44a78b0159c55902df1b129b3816d1": - version "17.2.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/404f8e130e44a78b0159c55902df1b129b3816d1" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#ebcb26f1b3b9e2d709615fde03f9ce6ac77871f1": + version "18.1.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ebcb26f1b3b9e2d709615fde03f9ce6ac77871f1" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -8618,6 +8623,14 @@ matrix-events-sdk@^0.0.1-beta.7: request "^2.88.2" unhomoglyph "^1.0.6" +matrix-widget-api@^0.1.0-beta.18: + version "0.1.0-beta.18" + resolved "https://registry.yarnpkg.com/matrix-widget-api/-/matrix-widget-api-0.1.0-beta.18.tgz#4efd30edec3eeb4211285985464c062fcab59795" + integrity sha512-kCpcs6rrB94Mmr2/1gBJ+6auWyZ5UvOMOn5K2VFafz2/NDMzZg9OVWj9KFYnNAuwwBE5/tCztYEj6OQ+hgbwOQ== + dependencies: + "@types/events" "^3.0.0" + events "^3.2.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"