From d5e638c8f7849525ad8f6c6854b0d80e6f948f5d Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Mon, 27 Jun 2022 17:41:07 -0400 Subject: [PATCH 1/7] 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" From fc26bef80a6aa7d24103d0420e66db4130d0c2ab Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 15 Jul 2022 11:24:38 -0400 Subject: [PATCH 2/7] Make Vite work with matrix-widget-api --- vite.config.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vite.config.js b/vite.config.js index 95f6c3d..83440ca 100644 --- a/vite.config.js +++ b/vite.config.js @@ -41,6 +41,12 @@ export default defineConfig(({ mode }) => { }, }, resolve: { + alias: { + // matrix-widget-api has its transpiled lib/index.js as its entry point, + // which Vite for some reason refuses to work with, so we point it to + // src/index.ts instead + "matrix-widget-api": "matrix-widget-api/src/index.ts", + }, dedupe: [ "react", "react-dom", From cf56b24ddab36af9b5c2667d9cdcf3d22507a956 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Wed, 27 Jul 2022 16:14:05 -0400 Subject: [PATCH 3/7] Add a URL param for room ID And consolidate our URL params logic --- src/ClientContext.tsx | 6 +- src/matrix-utils.ts | 49 ++++++++-------- src/room/GroupCallLoader.jsx | 4 +- src/room/GroupCallView.jsx | 8 +-- src/room/InCallView.jsx | 6 +- src/room/InviteModal.jsx | 7 ++- src/room/LobbyView.jsx | 6 +- src/room/OverflowMenu.jsx | 4 +- src/room/PTTCallView.tsx | 8 +-- src/room/RageshakeRequestModal.jsx | 8 ++- src/room/RoomPage.jsx | 30 ++++------ src/room/VideoPreview.jsx | 4 +- src/room/useRoomParams.ts | 93 ++++++++++++++++++++++++++++++ 13 files changed, 166 insertions(+), 67 deletions(-) create mode 100644 src/room/useRoomParams.ts diff --git a/src/ClientContext.tsx b/src/ClientContext.tsx index 85f07cb..7960209 100644 --- a/src/ClientContext.tsx +++ b/src/ClientContext.tsx @@ -29,7 +29,11 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { logger } from "matrix-js-sdk/src/logger"; import { ErrorView } from "./FullScreenView"; -import { initClient, initMatroskaClient, defaultHomeserver } from "./matrix-utils"; +import { + initClient, + initMatroskaClient, + defaultHomeserver, +} from "./matrix-utils"; declare global { interface Window { diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 90cd00e..0d2e58d 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -5,7 +5,11 @@ 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, createRoomWidgetClient, 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"; @@ -15,6 +19,7 @@ import { WidgetApi } from "matrix-widget-api"; import { logger } from "matrix-js-sdk/src/logger"; import IndexedDBWorker from "./IndexedDBWorker?worker"; +import { getRoomParams } from "./room/useRoomParams"; export const defaultHomeserver = (import.meta.env.VITE_DEFAULT_HOMESERVER as string) ?? @@ -82,20 +87,20 @@ const SEND_RECV_TO_DEVICE = [ * @returns The MatrixClient instance */ export async function initMatroskaClient( - widgetId: string, parentUrl: string, + 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)); + const { roomId, userId, deviceId } = getRoomParams(); + if (!roomId) throw new Error("Room ID must be supplied"); + if (!userId) throw new Error("User ID must be supplied"); + if (!deviceId) throw new Error("Device ID must be supplied"); // 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! + // We don't even need to set up crypto const client = createRoomWidgetClient( new WidgetApi(widgetId, new URL(parentUrl).origin), { @@ -103,14 +108,15 @@ export async function initMatroskaClient( receiveState: SEND_RECV_STATE, sendToDevice: SEND_RECV_TO_DEVICE, receiveToDevice: SEND_RECV_TO_DEVICE, + turnServers: true, }, roomId, { baseUrl: "", - userId: fragmentQuery.get("userId"), - deviceId: fragmentQuery.get("deviceId"), + userId, + deviceId, timelineSupport: true, - }, + } ); await client.startClient(); @@ -192,16 +198,13 @@ export async function initClient( storeOpts.cryptoStore = new MemoryCryptoStore(); } - // XXX: we read from the URL search params in RoomPage too: + // XXX: we read from the room params in RoomPage too: // it would be much better to read them in one place and pass // the values around, but we initialise the matrix client in // many different places so we'd have to pass it into all of // them. - const params = new URLSearchParams(window.location.search); - // disable e2e only if enableE2e=false is given - const enableE2e = params.get("enableE2e") !== "false"; - - if (!enableE2e) { + const { e2eEnabled } = getRoomParams(); + if (!e2eEnabled) { logger.info("Disabling E2E: group call signalling will NOT be encrypted."); } @@ -212,7 +215,7 @@ export async function initClient( // 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, - useE2eForGroupCall: enableE2e, + useE2eForGroupCall: e2eEnabled, }); try { @@ -319,17 +322,17 @@ export async function createRoom( return [fullAliasFromRoomName(name, client), result.room_id]; } -export function getRoomUrl(roomId: string): string { - if (roomId.startsWith("#")) { - const [localPart, host] = roomId.replace("#", "").split(":"); +export function getRoomUrl(roomIdOrAlias: string): string { + if (roomIdOrAlias.startsWith("#")) { + const [localPart, host] = roomIdOrAlias.replace("#", "").split(":"); if (host !== defaultHomeserverHost) { - return `${window.location.protocol}//${window.location.host}/room/${roomId}`; + return `${window.location.protocol}//${window.location.host}/room/${roomIdOrAlias}`; } else { return `${window.location.protocol}//${window.location.host}/${localPart}`; } } else { - return `${window.location.protocol}//${window.location.host}/room/${roomId}`; + return `${window.location.protocol}//${window.location.host}/room/#?roomId=${roomIdOrAlias}`; } } diff --git a/src/room/GroupCallLoader.jsx b/src/room/GroupCallLoader.jsx index 70791a2..5b7f481 100644 --- a/src/room/GroupCallLoader.jsx +++ b/src/room/GroupCallLoader.jsx @@ -21,14 +21,14 @@ import { usePageTitle } from "../usePageTitle"; export function GroupCallLoader({ client, - roomId, + roomIdOrAlias, viaServers, createPtt, children, }) { const { loading, error, groupCall } = useLoadGroupCall( client, - roomId, + roomIdOrAlias, viaServers, createPtt ); diff --git a/src/room/GroupCallView.jsx b/src/room/GroupCallView.jsx index 9794a00..445a507 100644 --- a/src/room/GroupCallView.jsx +++ b/src/room/GroupCallView.jsx @@ -31,7 +31,7 @@ export function GroupCallView({ client, isPasswordlessUser, isEmbedded, - roomId, + roomIdOrAlias, groupCall, }) { const { @@ -89,7 +89,7 @@ export function GroupCallView({ return ( ); @@ -153,7 +153,7 @@ export function GroupCallView({ localVideoMuted={localVideoMuted} toggleLocalVideoMuted={toggleLocalVideoMuted} toggleMicrophoneMuted={toggleMicrophoneMuted} - roomId={roomId} + roomIdOrAlias={roomIdOrAlias} isEmbedded={isEmbedded} /> ); diff --git a/src/room/InCallView.jsx b/src/room/InCallView.jsx index 43a71a8..5bdef29 100644 --- a/src/room/InCallView.jsx +++ b/src/room/InCallView.jsx @@ -65,7 +65,7 @@ export function InCallView({ toggleScreensharing, isScreensharing, screenshareFeeds, - roomId, + roomIdOrAlias, unencryptedEventsFromUsers, }) { usePreventScroll(); @@ -184,7 +184,7 @@ export function InCallView({ )} )} diff --git a/src/room/InviteModal.jsx b/src/room/InviteModal.jsx index 0a7e9ce..f804162 100644 --- a/src/room/InviteModal.jsx +++ b/src/room/InviteModal.jsx @@ -20,7 +20,7 @@ import { CopyButton } from "../button"; import { getRoomUrl } from "../matrix-utils"; import styles from "./InviteModal.module.css"; -export function InviteModal({ roomId, ...rest }) { +export function InviteModal({ roomIdOrAlias, ...rest }) { return (

Copy and share this meeting link

- +
); diff --git a/src/room/LobbyView.jsx b/src/room/LobbyView.jsx index de22db0..fd43560 100644 --- a/src/room/LobbyView.jsx +++ b/src/room/LobbyView.jsx @@ -41,7 +41,7 @@ export function LobbyView({ localVideoMuted, toggleLocalVideoMuted, toggleMicrophoneMuted, - roomId, + roomIdOrAlias, isEmbedded, }) { const { stream } = useCallFeed(localCallFeed); @@ -95,7 +95,7 @@ export function LobbyView({ Or diff --git a/src/room/OverflowMenu.jsx b/src/room/OverflowMenu.jsx index c5810f0..39c9ffe 100644 --- a/src/room/OverflowMenu.jsx +++ b/src/room/OverflowMenu.jsx @@ -30,7 +30,7 @@ import { TooltipTrigger } from "../Tooltip"; import { FeedbackModal } from "./FeedbackModal"; export function OverflowMenu({ - roomId, + roomIdOrAlias, inCall, groupCall, showInvite, @@ -88,7 +88,7 @@ export function OverflowMenu({ {settingsModalState.isOpen && } {inviteModalState.isOpen && ( - + )} {feedbackModalState.isOpen && ( = ({ client, - roomId, + roomIdOrAlias, roomName, avatarUrl, groupCall, @@ -204,7 +204,7 @@ export const PTTCallView: React.FC = ({
= ({
{inviteModalState.isOpen && showControls && ( - + )} ); diff --git a/src/room/RageshakeRequestModal.jsx b/src/room/RageshakeRequestModal.jsx index 201b308..29fbe9e 100644 --- a/src/room/RageshakeRequestModal.jsx +++ b/src/room/RageshakeRequestModal.jsx @@ -21,7 +21,11 @@ import { FieldRow, ErrorMessage } from "../input/Input"; import { useSubmitRageshake } from "../settings/submit-rageshake"; import { Body } from "../typography/Typography"; -export function RageshakeRequestModal({ rageshakeRequestId, roomId, ...rest }) { +export function RageshakeRequestModal({ + rageshakeRequestId, + roomIdOrAlias, + ...rest +}) { const { submitRageshake, sending, sent, error } = useSubmitRageshake(); useEffect(() => { @@ -43,7 +47,7 @@ export function RageshakeRequestModal({ rageshakeRequestId, roomId, ...rest }) { submitRageshake({ sendLogs: true, rageshakeRequestId, - roomId, + roomIdOrAlias, // Possibly not a room ID, but oh well }) } disabled={sending} diff --git a/src/room/RoomPage.jsx b/src/room/RoomPage.jsx index 72f6bf4..931eae1 100644 --- a/src/room/RoomPage.jsx +++ b/src/room/RoomPage.jsx @@ -1,5 +1,5 @@ /* -Copyright 2021 New Vector Ltd +Copyright 2021-2022 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useEffect, useMemo, useState } from "react"; -import { useLocation, useParams } from "react-router-dom"; +import React, { FC, useEffect, useState } from "react"; import { useClient } from "../ClientContext"; import { ErrorView, LoadingView } from "../FullScreenView"; import { RoomAuthView } from "./RoomAuthView"; import { GroupCallLoader } from "./GroupCallLoader"; import { GroupCallView } from "./GroupCallView"; +import { useRoomParams } from "./useRoomParams"; import { MediaHandlerProvider } from "../settings/useMediaHandler"; import { useRegisterPasswordlessUser } from "../auth/useRegisterPasswordlessUser"; @@ -28,20 +28,12 @@ export function RoomPage() { const { loading, isAuthenticated, error, client, isPasswordlessUser } = useClient(); - const { roomId: maybeRoomId } = useParams(); - const { hash, search } = useLocation(); - const [viaServers, isEmbedded, isPtt, displayName] = useMemo(() => { - const params = new URLSearchParams(search); - return [ - params.getAll("via"), - params.has("embed"), - params.get("ptt") === "true", - params.get("displayName"), - ]; - }, [search]); - const roomId = (maybeRoomId || hash || "").toLowerCase(); - const { registerPasswordlessUser, recaptchaId } = - useRegisterPasswordlessUser(); + const { roomAlias, roomId, viaServers, isEmbedded, isPtt, displayName } = + useRoomParams(); + const roomIdOrAlias = roomId ?? roomAlias; + if (!roomIdOrAlias) throw new Error("No room specified"); + + const { registerPasswordlessUser } = useRegisterPasswordlessUser(); const [isRegistering, setIsRegistering] = useState(false); useEffect(() => { @@ -76,14 +68,14 @@ export function RoomPage() { {(groupCall) => ( { + const fragmentQueryStart = fragment.indexOf("?"); + const fragmentParams = new URLSearchParams( + fragmentQueryStart === -1 ? "" : fragment.substring(fragmentQueryStart) + ); + const queryParams = new URLSearchParams(query); + + // Normally, room params should be encoded in the fragment so as to avoid + // leaking them to the server. However, we also check the normal query + // string for backwards compatibility with versions that only used that. + const hasParam = (name: string): boolean => + fragmentParams.has(name) || queryParams.has(name); + const getParam = (name: string): string | null => + fragmentParams.get(name) ?? queryParams.get(name); + const getAllParams = (name: string): string[] => [ + ...fragmentParams.getAll(name), + ...queryParams.getAll(name), + ]; + + // The part of the fragment before the ? + const fragmentRoute = + fragmentQueryStart === -1 + ? fragment + : fragment.substring(0, fragmentQueryStart); + + return { + roomAlias: fragmentRoute.length > 1 ? fragmentRoute : null, + roomId: getParam("roomId"), + viaServers: getAllParams("via"), + isEmbedded: hasParam("embed"), + isPtt: hasParam("ptt"), + e2eEnabled: getParam("enableE2e") !== "false", // Defaults to true + userId: getParam("userId"), + displayName: getParam("displayName"), + deviceId: getParam("deviceId"), + }; +}; + +/** + * Hook to simplify use of getRoomParams. + * @returns {RoomParams} The room parameters for the current URL + */ +export const useRoomParams = (): RoomParams => { + const { hash, search } = useLocation(); + return useMemo(() => getRoomParams(search, hash), [search, hash]); +}; From 549c54e311e97045159eef4f2f426a65cfc2e3a4 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Thu, 28 Jul 2022 16:26:14 -0400 Subject: [PATCH 4/7] Request fewer permissions --- src/matrix-utils.ts | 51 +++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 0d2e58d..68680f2 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -59,27 +59,6 @@ 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", -]; - /** * Initialises and returns a new widget-API-based Matrix Client. * @param widgetId The ID of the widget that the app is running inside. @@ -98,16 +77,38 @@ export async function initMatroskaClient( if (!userId) throw new Error("User ID must be supplied"); if (!deviceId) throw new Error("Device ID must be supplied"); + // These are all the to-device event types the app uses + const sendRecvToDevice = [ + 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", + ]; + // 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, + sendState: [ + { eventType: EventType.GroupCallPrefix }, + { eventType: EventType.GroupCallMemberPrefix, stateKey: userId }, + ], + receiveState: [ + { eventType: EventType.RoomMember }, + { eventType: EventType.GroupCallPrefix }, + { eventType: EventType.GroupCallMemberPrefix }, + ], + sendToDevice: sendRecvToDevice, + receiveToDevice: sendRecvToDevice, turnServers: true, }, roomId, From a2963adbeefffc4f374ab719857039cbee594ed7 Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Fri, 5 Aug 2022 15:41:25 -0400 Subject: [PATCH 5/7] Upgrade matrix-widget-api --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9287a93..bd85229 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "color-hash": "^2.0.1", "events": "^3.3.0", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#8ba2d257ae24bbed61cd7fe99af081324337161c", - "matrix-widget-api": "^0.1.0-beta.18", + "matrix-widget-api": "^1.0.0", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", "pako": "^2.0.4", From 2a1689009ad67569a7ff33d4fe91b79c7d4406ec Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 9 Aug 2022 09:43:12 -0400 Subject: [PATCH 6/7] Extract state event capabilities into a variable --- src/matrix-utils.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/matrix-utils.ts b/src/matrix-utils.ts index 68680f2..ed3cde3 100644 --- a/src/matrix-utils.ts +++ b/src/matrix-utils.ts @@ -77,7 +77,16 @@ export async function initMatroskaClient( if (!userId) throw new Error("User ID must be supplied"); if (!deviceId) throw new Error("Device ID must be supplied"); - // These are all the to-device event types the app uses + // These are all the event types the app uses + const sendState = [ + { eventType: EventType.GroupCallPrefix }, + { eventType: EventType.GroupCallMemberPrefix, stateKey: userId }, + ]; + const receiveState = [ + { eventType: EventType.RoomMember }, + { eventType: EventType.GroupCallPrefix }, + { eventType: EventType.GroupCallMemberPrefix }, + ]; const sendRecvToDevice = [ EventType.CallInvite, EventType.CallCandidates, @@ -98,15 +107,8 @@ export async function initMatroskaClient( const client = createRoomWidgetClient( new WidgetApi(widgetId, new URL(parentUrl).origin), { - sendState: [ - { eventType: EventType.GroupCallPrefix }, - { eventType: EventType.GroupCallMemberPrefix, stateKey: userId }, - ], - receiveState: [ - { eventType: EventType.RoomMember }, - { eventType: EventType.GroupCallPrefix }, - { eventType: EventType.GroupCallMemberPrefix }, - ], + sendState, + receiveState, sendToDevice: sendRecvToDevice, receiveToDevice: sendRecvToDevice, turnServers: true, From 7e98b1958719406a90917944a3b6c3adb73f779a Mon Sep 17 00:00:00 2001 From: Robin Townsend Date: Tue, 9 Aug 2022 09:53:45 -0400 Subject: [PATCH 7/7] Update matrix-js-sdk --- package.json | 2 +- yarn.lock | 35 ++++++++++++++++++----------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index bd85229..02a0590 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "classnames": "^2.3.1", "color-hash": "^2.0.1", "events": "^3.3.0", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#8ba2d257ae24bbed61cd7fe99af081324337161c", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#3334c01191bcd82b5243916284c9a08d08fd9795", "matrix-widget-api": "^1.0.0", "mermaid": "^8.13.8", "normalize.css": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index c0f9e40..26dcd48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3868,12 +3868,10 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base-x@^3.0.2: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== - dependencies: - safe-buffer "^5.0.1" +base-x@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" + integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== base16@^1.0.0: version "1.0.0" @@ -4110,12 +4108,12 @@ browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^ node-releases "^2.0.5" update-browserslist-db "^1.0.4" -bs58@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" - integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== +bs58@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" + integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== dependencies: - base-x "^3.0.2" + base-x "^4.0.0" buffer-from@^1.0.0: version "1.1.2" @@ -8392,20 +8390,23 @@ 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#8ba2d257ae24bbed61cd7fe99af081324337161c": - version "19.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8ba2d257ae24bbed61cd7fe99af081324337161c" +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#3334c01191bcd82b5243916284c9a08d08fd9795": + version "19.2.0" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/3334c01191bcd82b5243916284c9a08d08fd9795" dependencies: "@babel/runtime" "^7.12.5" + "@types/sdp-transform" "^2.4.5" another-json "^0.2.0" browser-request "^0.3.3" - bs58 "^4.0.1" + bs58 "^5.0.0" content-type "^1.0.4" loglevel "^1.7.1" matrix-events-sdk "^0.0.1-beta.7" - p-retry "^4.5.0" + matrix-widget-api "^1.0.0" + p-retry "4" qs "^6.9.6" request "^2.88.2" + sdp-transform "^2.14.1" unhomoglyph "^1.0.6" matrix-widget-api@^1.0.0: @@ -9200,7 +9201,7 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-retry@^4.5.0: +p-retry@4: version "4.6.2" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==