Add a URL param for room ID
And consolidate our URL params logic
This commit is contained in:
parent
2a8cb3c4e2
commit
cf56b24dda
13 changed files with 166 additions and 67 deletions
|
@ -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 {
|
||||
|
|
|
@ -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<MatrixClient> {
|
||||
// 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}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -31,7 +31,7 @@ export function GroupCallView({
|
|||
client,
|
||||
isPasswordlessUser,
|
||||
isEmbedded,
|
||||
roomId,
|
||||
roomIdOrAlias,
|
||||
groupCall,
|
||||
}) {
|
||||
const {
|
||||
|
@ -89,7 +89,7 @@ export function GroupCallView({
|
|||
return (
|
||||
<PTTCallView
|
||||
client={client}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
roomName={groupCall.room.name}
|
||||
avatarUrl={avatarUrl}
|
||||
groupCall={groupCall}
|
||||
|
@ -117,7 +117,7 @@ export function GroupCallView({
|
|||
isScreensharing={isScreensharing}
|
||||
localScreenshareFeed={localScreenshareFeed}
|
||||
screenshareFeeds={screenshareFeeds}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||
/>
|
||||
);
|
||||
|
@ -153,7 +153,7 @@ export function GroupCallView({
|
|||
localVideoMuted={localVideoMuted}
|
||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||
toggleMicrophoneMuted={toggleMicrophoneMuted}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
isEmbedded={isEmbedded}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -65,7 +65,7 @@ export function InCallView({
|
|||
toggleScreensharing,
|
||||
isScreensharing,
|
||||
screenshareFeeds,
|
||||
roomId,
|
||||
roomIdOrAlias,
|
||||
unencryptedEventsFromUsers,
|
||||
}) {
|
||||
usePreventScroll();
|
||||
|
@ -184,7 +184,7 @@ export function InCallView({
|
|||
)}
|
||||
<OverflowMenu
|
||||
inCall
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
showInvite={true}
|
||||
|
@ -201,7 +201,7 @@ export function InCallView({
|
|||
{rageshakeRequestModalState.isOpen && (
|
||||
<RageshakeRequestModal
|
||||
{...rageshakeRequestModalProps}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -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 (
|
||||
<Modal
|
||||
title="Invite People"
|
||||
|
@ -30,7 +30,10 @@ export function InviteModal({ roomId, ...rest }) {
|
|||
>
|
||||
<ModalContent>
|
||||
<p>Copy and share this meeting link</p>
|
||||
<CopyButton className={styles.copyButton} value={getRoomUrl(roomId)} />
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
value={getRoomUrl(roomIdOrAlias)}
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -41,7 +41,7 @@ export function LobbyView({
|
|||
localVideoMuted,
|
||||
toggleLocalVideoMuted,
|
||||
toggleMicrophoneMuted,
|
||||
roomId,
|
||||
roomIdOrAlias,
|
||||
isEmbedded,
|
||||
}) {
|
||||
const { stream } = useCallFeed(localCallFeed);
|
||||
|
@ -95,7 +95,7 @@ export function LobbyView({
|
|||
<VideoPreview
|
||||
state={state}
|
||||
client={client}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
microphoneMuted={microphoneMuted}
|
||||
localVideoMuted={localVideoMuted}
|
||||
toggleLocalVideoMuted={toggleLocalVideoMuted}
|
||||
|
@ -116,7 +116,7 @@ export function LobbyView({
|
|||
<Body>Or</Body>
|
||||
<CopyButton
|
||||
variant="secondaryCopy"
|
||||
value={getRoomUrl(roomId)}
|
||||
value={getRoomUrl(roomIdOrAlias)}
|
||||
className={styles.copyButton}
|
||||
copiedMessage="Call link copied"
|
||||
>
|
||||
|
|
|
@ -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({
|
|||
</PopoverMenuTrigger>
|
||||
{settingsModalState.isOpen && <SettingsModal {...settingsModalProps} />}
|
||||
{inviteModalState.isOpen && (
|
||||
<InviteModal roomId={roomId} {...inviteModalProps} />
|
||||
<InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} />
|
||||
)}
|
||||
{feedbackModalState.isOpen && (
|
||||
<FeedbackModal
|
||||
|
|
|
@ -86,7 +86,7 @@ function getPromptText(
|
|||
|
||||
interface Props {
|
||||
client: MatrixClient;
|
||||
roomId: string;
|
||||
roomIdOrAlias: string;
|
||||
roomName: string;
|
||||
avatarUrl: string;
|
||||
groupCall: GroupCall;
|
||||
|
@ -98,7 +98,7 @@ interface Props {
|
|||
|
||||
export const PTTCallView: React.FC<Props> = ({
|
||||
client,
|
||||
roomId,
|
||||
roomIdOrAlias,
|
||||
roomName,
|
||||
avatarUrl,
|
||||
groupCall,
|
||||
|
@ -204,7 +204,7 @@ export const PTTCallView: React.FC<Props> = ({
|
|||
<div className={styles.footer}>
|
||||
<OverflowMenu
|
||||
inCall
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
showInvite={false}
|
||||
|
@ -282,7 +282,7 @@ export const PTTCallView: React.FC<Props> = ({
|
|||
</div>
|
||||
|
||||
{inviteModalState.isOpen && showControls && (
|
||||
<InviteModal roomId={roomId} {...inviteModalProps} />
|
||||
<InviteModal roomIdOrAlias={roomIdOrAlias} {...inviteModalProps} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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() {
|
|||
<MediaHandlerProvider client={client}>
|
||||
<GroupCallLoader
|
||||
client={client}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
viaServers={viaServers}
|
||||
createPtt={isPtt}
|
||||
>
|
||||
{(groupCall) => (
|
||||
<GroupCallView
|
||||
client={client}
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
groupCall={groupCall}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
isEmbedded={isEmbedded}
|
||||
|
|
|
@ -30,7 +30,7 @@ import { useModalTriggerState } from "../Modal";
|
|||
export function VideoPreview({
|
||||
client,
|
||||
state,
|
||||
roomId,
|
||||
roomIdOrAlias,
|
||||
microphoneMuted,
|
||||
localVideoMuted,
|
||||
toggleLocalVideoMuted,
|
||||
|
@ -80,7 +80,7 @@ export function VideoPreview({
|
|||
onPress={toggleLocalVideoMuted}
|
||||
/>
|
||||
<OverflowMenu
|
||||
roomId={roomId}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
client={client}
|
||||
feedbackModalState={feedbackModalState}
|
||||
feedbackModalProps={feedbackModalProps}
|
||||
|
|
93
src/room/useRoomParams.ts
Normal file
93
src/room/useRoomParams.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 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.
|
||||
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 { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export interface RoomParams {
|
||||
roomAlias: string | null;
|
||||
roomId: string | null;
|
||||
viaServers: string[];
|
||||
// Whether the app is running in embedded mode, and should keep the user
|
||||
// confined to the current room
|
||||
isEmbedded: boolean;
|
||||
// Whether to start a walkie-talkie call instead of a video call
|
||||
isPtt: boolean;
|
||||
// Whether to use end-to-end encryption
|
||||
e2eEnabled: boolean;
|
||||
// The user's ID (only used in Matroska mode)
|
||||
userId: string | null;
|
||||
// The display name to use for auto-registration
|
||||
displayName: string | null;
|
||||
// The device's ID (only used in Matroska mode)
|
||||
deviceId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the room parameters for the current URL.
|
||||
* @param {string} query The URL query string
|
||||
* @param {string} fragment The URL fragment string
|
||||
* @returns {RoomParams} The room parameters encoded in the URL
|
||||
*/
|
||||
export const getRoomParams = (
|
||||
query: string = window.location.search,
|
||||
fragment: string = window.location.hash
|
||||
): RoomParams => {
|
||||
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]);
|
||||
};
|
Loading…
Add table
Reference in a new issue