Merge branch 'livekit' into settings-tab-order
This commit is contained in:
@@ -24,7 +24,6 @@ import { HomePage } from "./home/HomePage";
|
||||
import { LoginPage } from "./auth/LoginPage";
|
||||
import { RegisterPage } from "./auth/RegisterPage";
|
||||
import { RoomPage } from "./room/RoomPage";
|
||||
import { RoomRedirect } from "./room/RoomRedirect";
|
||||
import { ClientProvider } from "./ClientContext";
|
||||
import { usePageFocusStyle } from "./usePageFocusStyle";
|
||||
import { SequenceDiagramViewerPage } from "./SequenceDiagramViewerPage";
|
||||
@@ -71,14 +70,11 @@ export default function App({ history }: AppProps) {
|
||||
<SentryRoute exact path="/register">
|
||||
<RegisterPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/room/:roomId?">
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="/inspector">
|
||||
<SequenceDiagramViewerPage />
|
||||
</SentryRoute>
|
||||
<SentryRoute path="*">
|
||||
<RoomRedirect />
|
||||
<RoomPage />
|
||||
</SentryRoute>
|
||||
</Switch>
|
||||
</OverlayProvider>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
Copyright 2022 - 2023 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.
|
||||
@@ -17,6 +17,8 @@ limitations under the License.
|
||||
import { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
import { Config } from "./config/Config";
|
||||
|
||||
interface UrlParams {
|
||||
roomAlias: string | null;
|
||||
roomId: string | null;
|
||||
@@ -88,19 +90,46 @@ interface UrlParams {
|
||||
|
||||
/**
|
||||
* Gets the app parameters for the current URL.
|
||||
* @param query The URL query string
|
||||
* @param fragment The URL fragment string
|
||||
* @param ignoreRoomAlias If true, does not try to parse a room alias from the URL
|
||||
* @param search The URL search string
|
||||
* @param pathname The URL path name
|
||||
* @param hash The URL hash
|
||||
* @returns The app parameters encoded in the URL
|
||||
*/
|
||||
export const getUrlParams = (
|
||||
query: string = window.location.search,
|
||||
fragment: string = window.location.hash
|
||||
ignoreRoomAlias?: boolean,
|
||||
search = window.location.search,
|
||||
pathname = window.location.pathname,
|
||||
hash = window.location.hash
|
||||
): UrlParams => {
|
||||
const fragmentQueryStart = fragment.indexOf("?");
|
||||
let roomAlias: string | undefined;
|
||||
if (!ignoreRoomAlias) {
|
||||
if (hash === "") {
|
||||
roomAlias = pathname.substring(1); // Strip the "/"
|
||||
|
||||
// Delete "/room/" and "?", if present
|
||||
if (roomAlias.startsWith("room/")) {
|
||||
roomAlias = roomAlias.substring("room/".length);
|
||||
}
|
||||
// Add "#", if not present
|
||||
if (!roomAlias.startsWith("#")) {
|
||||
roomAlias = `#${roomAlias}`;
|
||||
}
|
||||
} else {
|
||||
roomAlias = hash;
|
||||
}
|
||||
|
||||
// Add server part, if not present
|
||||
if (!roomAlias.includes(":")) {
|
||||
roomAlias = `${roomAlias}:${Config.defaultServerName()}`;
|
||||
}
|
||||
}
|
||||
|
||||
const fragmentQueryStart = hash.indexOf("?");
|
||||
const fragmentParams = new URLSearchParams(
|
||||
fragmentQueryStart === -1 ? "" : fragment.substring(fragmentQueryStart)
|
||||
fragmentQueryStart === -1 ? "" : hash.substring(fragmentQueryStart)
|
||||
);
|
||||
const queryParams = new URLSearchParams(query);
|
||||
const queryParams = new URLSearchParams(search);
|
||||
|
||||
// Normally, URL params should be encoded in the fragment so as to avoid
|
||||
// leaking them to the server. However, we also check the normal query
|
||||
@@ -114,16 +143,10 @@ export const getUrlParams = (
|
||||
...queryParams.getAll(name),
|
||||
];
|
||||
|
||||
// The part of the fragment before the ?
|
||||
const fragmentRoute =
|
||||
fragmentQueryStart === -1
|
||||
? fragment
|
||||
: fragment.substring(0, fragmentQueryStart);
|
||||
|
||||
const fontScale = parseFloat(getParam("fontScale") ?? "");
|
||||
|
||||
return {
|
||||
roomAlias: fragmentRoute.length > 1 ? fragmentRoute : null,
|
||||
roomAlias: !roomAlias || roomAlias.includes("!") ? null : roomAlias,
|
||||
roomId: getParam("roomId"),
|
||||
viaServers: getAllParams("via"),
|
||||
isEmbedded: hasParam("embed"),
|
||||
@@ -149,6 +172,9 @@ export const getUrlParams = (
|
||||
* @returns The app parameters for the current URL
|
||||
*/
|
||||
export const useUrlParams = (): UrlParams => {
|
||||
const { hash, search } = useLocation();
|
||||
return useMemo(() => getUrlParams(search, hash), [search, hash]);
|
||||
const { search, pathname, hash } = useLocation();
|
||||
return useMemo(
|
||||
() => getUrlParams(false, search, pathname, hash),
|
||||
[search, pathname, hash]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,11 +80,7 @@ export const RegisterPage: FC = () => {
|
||||
passwordlessUser
|
||||
);
|
||||
|
||||
if (!client || !client.groupCallEventHandler || !setClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordlessUser) {
|
||||
if (client && client?.groupCallEventHandler && passwordlessUser) {
|
||||
// Migrate the user's rooms
|
||||
for (const groupCall of client.groupCallEventHandler.groupCalls.values()) {
|
||||
const roomId = groupCall.room.roomId;
|
||||
@@ -107,7 +103,7 @@ export const RegisterPage: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
setClient({ client: newClient, session });
|
||||
setClient?.({ client: newClient, session });
|
||||
PosthogAnalytics.instance.eventSignup.cacheSignupEnd(new Date());
|
||||
};
|
||||
|
||||
|
||||
@@ -35,13 +35,13 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.callList}>
|
||||
{rooms.map(({ roomId, roomName, avatarUrl, participants }) => (
|
||||
{rooms.map(({ roomAlias, roomName, avatarUrl, participants }) => (
|
||||
<CallTile
|
||||
key={roomId}
|
||||
key={roomAlias}
|
||||
client={client}
|
||||
name={roomName}
|
||||
avatarUrl={avatarUrl}
|
||||
roomId={roomId}
|
||||
roomAlias={roomAlias}
|
||||
participants={participants}
|
||||
disableFacepile={disableFacepile}
|
||||
/>
|
||||
@@ -59,7 +59,7 @@ export function CallList({ rooms, client, disableFacepile }: CallListProps) {
|
||||
interface CallTileProps {
|
||||
name: string;
|
||||
avatarUrl: string;
|
||||
roomId: string;
|
||||
roomAlias: string;
|
||||
participants: RoomMember[];
|
||||
client: MatrixClient;
|
||||
disableFacepile?: boolean;
|
||||
@@ -67,14 +67,17 @@ interface CallTileProps {
|
||||
function CallTile({
|
||||
name,
|
||||
avatarUrl,
|
||||
roomId,
|
||||
roomAlias,
|
||||
participants,
|
||||
client,
|
||||
disableFacepile,
|
||||
}: CallTileProps) {
|
||||
return (
|
||||
<div className={styles.callTile}>
|
||||
<Link to={`/room/${roomId}`} className={styles.callTileLink}>
|
||||
<Link
|
||||
to={`/${roomAlias.substring(1).split(":")[0]}`}
|
||||
className={styles.callTileLink}
|
||||
>
|
||||
<Avatar
|
||||
size={Size.LG}
|
||||
bgKey={name}
|
||||
@@ -86,7 +89,7 @@ function CallTile({
|
||||
<Body overflowEllipsis fontWeight="semiBold">
|
||||
{name}
|
||||
</Body>
|
||||
<Caption overflowEllipsis>{getRoomUrl(roomId)}</Caption>
|
||||
<Caption overflowEllipsis>{getRoomUrl(roomAlias)}</Caption>
|
||||
{participants && !disableFacepile && (
|
||||
<Facepile
|
||||
className={styles.facePile}
|
||||
@@ -100,7 +103,7 @@ function CallTile({
|
||||
<CopyButton
|
||||
className={styles.copyButton}
|
||||
variant="icon"
|
||||
value={getRoomUrl(roomId)}
|
||||
value={getRoomUrl(roomAlias)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -71,8 +71,9 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
||||
setLoading(true);
|
||||
|
||||
const [roomAlias] = await createRoom(client, roomName, ptt);
|
||||
|
||||
if (roomAlias) {
|
||||
history.push(`/room/${roomAlias}`);
|
||||
history.push(`/${roomAlias.substring(1).split(":")[0]}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
setOnFinished(() => {
|
||||
setClient({ client, session });
|
||||
const aliasLocalpart = roomAliasLocalpartFromRoomName(roomName);
|
||||
const [, serverName] = client.getUserId()!.split(":");
|
||||
history.push(`/room/#${aliasLocalpart}:${serverName}`);
|
||||
history.push(`/${aliasLocalpart}`);
|
||||
});
|
||||
|
||||
setLoading(false);
|
||||
@@ -111,7 +110,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
}
|
||||
|
||||
setClient({ client, session });
|
||||
history.push(`/room/${roomAlias}`);
|
||||
history.push(`/${roomAlias.substring(1).split(":")[0]}`);
|
||||
}
|
||||
|
||||
submit().catch((error) => {
|
||||
|
||||
@@ -22,7 +22,7 @@ import { GroupCallEventHandlerEvent } from "matrix-js-sdk/src/webrtc/groupCallEv
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
export interface GroupCallRoom {
|
||||
roomId: string;
|
||||
roomAlias: string;
|
||||
roomName: string;
|
||||
avatarUrl: string;
|
||||
room: Room;
|
||||
@@ -89,12 +89,13 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
|
||||
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||
const rooms = Array.from(groupCalls).map((groupCall) => groupCall.room);
|
||||
const sortedRooms = sortRooms(client, rooms);
|
||||
const filteredRooms = rooms.filter((r) => r.getCanonicalAlias()); // We don't display rooms without an alias
|
||||
const sortedRooms = sortRooms(client, filteredRooms);
|
||||
const items = sortedRooms.map((room) => {
|
||||
const groupCall = client.getGroupCallForRoom(room.roomId)!;
|
||||
|
||||
return {
|
||||
roomId: room.getCanonicalAlias() || room.roomId,
|
||||
roomAlias: room.getCanonicalAlias(),
|
||||
roomName: room.name,
|
||||
avatarUrl: room.getMxcAvatarUrl()!,
|
||||
room,
|
||||
@@ -103,7 +104,7 @@ export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
};
|
||||
});
|
||||
|
||||
setRooms(items);
|
||||
setRooms(items as GroupCallRoom[]);
|
||||
}
|
||||
|
||||
updateRooms();
|
||||
|
||||
@@ -55,7 +55,7 @@ export class Initializer {
|
||||
languageDetector.addDetector({
|
||||
name: "urlFragment",
|
||||
// Look for a language code in the URL's fragment
|
||||
lookup: () => getUrlParams().lang ?? undefined,
|
||||
lookup: () => getUrlParams(true).lang ?? undefined,
|
||||
});
|
||||
|
||||
i18n
|
||||
@@ -140,7 +140,7 @@ export class Initializer {
|
||||
}
|
||||
|
||||
// Custom fonts
|
||||
const { fonts, fontScale } = getUrlParams();
|
||||
const { fonts, fontScale } = getUrlParams(true);
|
||||
if (fontScale !== null) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--font-scale",
|
||||
|
||||
@@ -34,7 +34,7 @@ import styles from "./AvatarInputField.module.css";
|
||||
interface Props extends AllHTMLAttributes<HTMLInputElement> {
|
||||
id: string;
|
||||
label: string;
|
||||
avatarUrl: string;
|
||||
avatarUrl: string | undefined;
|
||||
displayName: string;
|
||||
onRemoveAvatar: () => void;
|
||||
}
|
||||
|
||||
@@ -41,13 +41,16 @@ export async function getSFUConfigWithOpenID(
|
||||
// if the call has a livekit service URL, try it.
|
||||
if (groupCall.livekitServiceURL) {
|
||||
try {
|
||||
logger.info(`Trying to get JWT from ${groupCall.livekitServiceURL}...`);
|
||||
logger.info(
|
||||
`Trying to get JWT from call's configured URL of ${groupCall.livekitServiceURL}...`
|
||||
);
|
||||
const sfuConfig = await getLiveKitJWT(
|
||||
client,
|
||||
groupCall.livekitServiceURL,
|
||||
roomName,
|
||||
openIdToken
|
||||
);
|
||||
logger.info(`Got JWT from call state event URL.`);
|
||||
|
||||
return sfuConfig;
|
||||
} catch (e) {
|
||||
@@ -71,9 +74,12 @@ export async function getSFUConfigWithOpenID(
|
||||
openIdToken
|
||||
);
|
||||
|
||||
logger.info(`Updating call livekit service URL with: ${urlFromConf}...`);
|
||||
logger.info(
|
||||
`Got JWT, updating call livekit service URL with: ${urlFromConf}...`
|
||||
);
|
||||
try {
|
||||
await groupCall.updateLivekitServiceURL(urlFromConf);
|
||||
logger.info(`Call livekit service URL updated.`);
|
||||
} catch (e) {
|
||||
logger.warn(
|
||||
`Failed to update call livekit service URL: continuing anyway.`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Room, RoomOptions } from "livekit-client";
|
||||
import { Room, RoomOptions, setLogLevel } from "livekit-client";
|
||||
import { useLiveKitRoom } from "@livekit/components-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
@@ -15,6 +15,8 @@ export type DeviceChoices = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
setLogLevel("debug");
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
sfuConfig?: SFUConfig
|
||||
|
||||
@@ -345,15 +345,11 @@ export async function createRoom(
|
||||
// Returns a URL to that will load Element Call with the given room
|
||||
export function getRoomUrl(roomIdOrAlias: string): string {
|
||||
if (roomIdOrAlias.startsWith("#")) {
|
||||
const [localPart, host] = roomIdOrAlias.replace("#", "").split(":");
|
||||
|
||||
if (host !== Config.defaultServerName()) {
|
||||
return `${window.location.protocol}//${window.location.host}/room/${roomIdOrAlias}`;
|
||||
} else {
|
||||
return `${window.location.protocol}//${window.location.host}/${localPart}`;
|
||||
}
|
||||
return `${window.location.protocol}//${window.location.host}/${
|
||||
roomIdOrAlias.substring(1).split(":")[0]
|
||||
}`;
|
||||
} else {
|
||||
return `${window.location.protocol}//${window.location.host}/room/#?roomId=${roomIdOrAlias}`;
|
||||
return `${window.location.protocol}//${window.location.host}/room?roomId=${roomIdOrAlias}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
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 { useEffect } from "react";
|
||||
import { useLocation, useHistory } from "react-router-dom";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
import { LoadingView } from "../FullScreenView";
|
||||
|
||||
// A component that, when loaded, redirects the client to a full room URL
|
||||
// based on the current URL being an abbreviated room URL
|
||||
export function RoomRedirect() {
|
||||
const { pathname } = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
let roomId = pathname;
|
||||
|
||||
if (pathname.startsWith("/")) {
|
||||
roomId = roomId.substring(1, roomId.length);
|
||||
}
|
||||
|
||||
if (!roomId.startsWith("#") && !roomId.startsWith("!")) {
|
||||
roomId = `#${roomId}:${Config.defaultServerName()}`;
|
||||
}
|
||||
|
||||
history.replace(`/room/${roomId.toLowerCase()}`);
|
||||
}, [pathname, history]);
|
||||
|
||||
return <LoadingView />;
|
||||
}
|
||||
@@ -106,11 +106,11 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
onUserChoicesChanged({
|
||||
video: {
|
||||
selectedId: videoIn.selectedId,
|
||||
enabled: videoEnabled && !!videoTrack,
|
||||
enabled: videoEnabled,
|
||||
},
|
||||
audio: {
|
||||
selectedId: audioIn.selectedId,
|
||||
enabled: audioEnabled && !!audioTrack,
|
||||
enabled: audioEnabled,
|
||||
},
|
||||
});
|
||||
}, [
|
||||
|
||||
@@ -77,7 +77,7 @@ export function ProfileSettingsTab({ client }: Props) {
|
||||
return (
|
||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
{avatarUrl && displayName && (
|
||||
{displayName && (
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
|
||||
@@ -109,7 +109,7 @@ export const widget: WidgetHelpers | null = (() => {
|
||||
baseUrl,
|
||||
e2eEnabled,
|
||||
allowIceFallback,
|
||||
} = getUrlParams();
|
||||
} = getUrlParams(true);
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user