Enable strict lints
An attempt to fix https://github.com/vector-im/element-call/issues/1132
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org"],
|
||||
extends: ["plugin:matrix-org/react", "plugin:matrix-org/a11y", "prettier"],
|
||||
extends: [
|
||||
"prettier",
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:matrix-org/a11y",
|
||||
"plugin:matrix-org/typescript",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: "module",
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
"build-storybook": "build-storybook",
|
||||
"prettier:check": "prettier -c .",
|
||||
"prettier:format": "prettier -w .",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 0 src",
|
||||
"lint": "yarn lint:types && yarn lint:eslint",
|
||||
"lint:eslint": "eslint --max-warnings 0 src",
|
||||
"lint:eslint-fix": "eslint --max-warnings 0 src --fix",
|
||||
"lint:types": "tsc",
|
||||
"i18n": "node_modules/i18next-parser/bin/cli.js",
|
||||
"i18n:check": "node_modules/i18next-parser/bin/cli.js --fail-on-warnings --fail-on-update",
|
||||
@@ -46,6 +47,7 @@
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@types/grecaptcha": "^3.0.4",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/sdp-transform": "^2.4.5",
|
||||
"@use-gesture/react": "^10.2.11",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
@@ -69,7 +71,6 @@
|
||||
"react-dom": "18",
|
||||
"react-i18next": "^11.18.6",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-router": "6",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use-clipboard": "^1.0.7",
|
||||
"react-use-measure": "^2.1.1",
|
||||
|
||||
@@ -18,6 +18,7 @@ import { Suspense, useEffect, useState } from "react";
|
||||
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { OverlayProvider } from "@react-aria/overlays";
|
||||
import { History } from "history";
|
||||
|
||||
import { HomePage } from "./home/HomePage";
|
||||
import { LoginPage } from "./auth/LoginPage";
|
||||
@@ -51,6 +52,8 @@ export default function App({ history }: AppProps) {
|
||||
const errorPage = <CrashView />;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<Router history={history}>
|
||||
{loaded ? (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -99,10 +99,10 @@ export const Avatar: FC<Props> = ({
|
||||
[size]
|
||||
);
|
||||
|
||||
const resolvedSrc = useMemo(
|
||||
() => resolveAvatarSrc(client, src, sizePx),
|
||||
[client, src, sizePx]
|
||||
);
|
||||
const resolvedSrc = useMemo(() => {
|
||||
if (!client || !src || !sizePx) return undefined;
|
||||
return resolveAvatarSrc(client, src, sizePx);
|
||||
}, [client, src, sizePx]);
|
||||
|
||||
const backgroundColor = useMemo(() => {
|
||||
const index = hashStringToArrIndex(
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
createContext,
|
||||
useMemo,
|
||||
useContext,
|
||||
useRef,
|
||||
} from "react";
|
||||
@@ -31,9 +30,9 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ErrorView } from "./FullScreenView";
|
||||
import {
|
||||
initClient,
|
||||
CryptoStoreIntegrityError,
|
||||
fallbackICEServerAllowed,
|
||||
initClient,
|
||||
} from "./matrix-utils";
|
||||
import { widget } from "./widget";
|
||||
import {
|
||||
@@ -47,184 +46,141 @@ import { Config } from "./config/Config";
|
||||
declare global {
|
||||
interface Window {
|
||||
matrixclient: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
passwordlessUser: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
user_id: string;
|
||||
device_id: string;
|
||||
access_token: string;
|
||||
export type ClientState = ValidClientState | ErrorState;
|
||||
|
||||
export type ValidClientState = {
|
||||
state: "valid";
|
||||
authenticated?: AuthenticatedClient;
|
||||
setClient: (params?: SetClientParams) => void;
|
||||
};
|
||||
|
||||
export type AuthenticatedClient = {
|
||||
client: MatrixClient;
|
||||
isPasswordlessUser: boolean;
|
||||
changePassword: (password: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
export type ErrorState = {
|
||||
state: "error";
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export type SetClientParams = {
|
||||
client: MatrixClient;
|
||||
session: Session;
|
||||
};
|
||||
|
||||
const ClientContext = createContext<ClientState | undefined>(undefined);
|
||||
|
||||
export const useClientState = () => useContext(ClientContext);
|
||||
|
||||
export function useClient(): {
|
||||
client?: MatrixClient;
|
||||
setClient?: (params?: SetClientParams) => void;
|
||||
} {
|
||||
let client;
|
||||
let setClient;
|
||||
|
||||
const clientState = useClientState();
|
||||
if (clientState?.state === "valid") {
|
||||
client = clientState.authenticated?.client;
|
||||
setClient = clientState.setClient;
|
||||
}
|
||||
|
||||
return { client, setClient };
|
||||
}
|
||||
|
||||
// Plain representation of the `ClientContext` as a helper for old components that expected an object with multiple fields.
|
||||
export function useClientLegacy(): {
|
||||
client?: MatrixClient;
|
||||
setClient?: (params?: SetClientParams) => void;
|
||||
passwordlessUser: boolean;
|
||||
tempPassword?: string;
|
||||
loading: boolean;
|
||||
authenticated: boolean;
|
||||
logout?: () => void;
|
||||
error?: Error;
|
||||
} {
|
||||
const clientState = useClientState();
|
||||
|
||||
let client;
|
||||
let setClient;
|
||||
let passwordlessUser = false;
|
||||
let loading = true;
|
||||
let error;
|
||||
let authenticated = false;
|
||||
let logout;
|
||||
|
||||
if (clientState?.state === "valid") {
|
||||
client = clientState.authenticated?.client;
|
||||
setClient = clientState.setClient;
|
||||
passwordlessUser = clientState.authenticated?.isPasswordlessUser ?? false;
|
||||
loading = false;
|
||||
authenticated = client !== undefined;
|
||||
logout = clientState.authenticated?.logout;
|
||||
} else if (clientState?.state === "error") {
|
||||
error = clientState.error;
|
||||
loading = false;
|
||||
}
|
||||
|
||||
return {
|
||||
client,
|
||||
setClient,
|
||||
passwordlessUser,
|
||||
loading,
|
||||
authenticated,
|
||||
logout,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
const loadChannel =
|
||||
"BroadcastChannel" in window ? new BroadcastChannel("load") : null;
|
||||
|
||||
const loadSession = (): Session => {
|
||||
const data = localStorage.getItem("matrix-auth-store");
|
||||
if (data) return JSON.parse(data);
|
||||
return null;
|
||||
};
|
||||
const saveSession = (session: Session) =>
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(session));
|
||||
const clearSession = () => localStorage.removeItem("matrix-auth-store");
|
||||
|
||||
interface ClientState {
|
||||
loading: boolean;
|
||||
isAuthenticated: boolean;
|
||||
isPasswordlessUser: boolean;
|
||||
client: MatrixClient;
|
||||
userName: string;
|
||||
changePassword: (password: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
setClient: (client: MatrixClient, session: Session) => void;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
const ClientContext = createContext<ClientState>(null);
|
||||
|
||||
type ClientProviderState = Omit<
|
||||
ClientState,
|
||||
"changePassword" | "logout" | "setClient"
|
||||
> & { error?: Error };
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
const history = useHistory();
|
||||
const initializing = useRef(false);
|
||||
const [
|
||||
{ loading, isAuthenticated, isPasswordlessUser, client, userName, error },
|
||||
setState,
|
||||
] = useState<ClientProviderState>({
|
||||
loading: true,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
client: undefined,
|
||||
userName: null,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const [initClientState, setInitClientState] = useState<
|
||||
InitResult | undefined
|
||||
>(undefined);
|
||||
|
||||
const initializing = useRef(false);
|
||||
useEffect(() => {
|
||||
// In case the component is mounted, unmounted, and remounted quickly (as
|
||||
// React does in strict mode), we need to make sure not to doubly initialize
|
||||
// the client
|
||||
// the client.
|
||||
if (initializing.current) return;
|
||||
initializing.current = true;
|
||||
|
||||
const init = async (): Promise<
|
||||
Pick<ClientProviderState, "client" | "isPasswordlessUser">
|
||||
> => {
|
||||
if (widget) {
|
||||
// We're inside a widget, so let's engage *matryoshka mode*
|
||||
logger.log("Using a matryoshka client");
|
||||
return {
|
||||
client: await widget.client,
|
||||
isPasswordlessUser: false,
|
||||
};
|
||||
} else {
|
||||
// We're running as a standalone application
|
||||
try {
|
||||
const session = loadSession();
|
||||
if (!session) return { client: undefined, isPasswordlessUser: false };
|
||||
|
||||
logger.log("Using a standalone client");
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
const { user_id, device_id, access_token, passwordlessUser } =
|
||||
session;
|
||||
|
||||
const livekit = Config.get().livekit;
|
||||
const foci = livekit
|
||||
? [
|
||||
{
|
||||
livekitServiceUrl: livekit.livekit_service_url,
|
||||
},
|
||||
]
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
return {
|
||||
client: await initClient(
|
||||
{
|
||||
baseUrl: Config.defaultHomeserverUrl(),
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
||||
foci,
|
||||
},
|
||||
true
|
||||
),
|
||||
isPasswordlessUser: passwordlessUser,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof CryptoStoreIntegrityError) {
|
||||
// We can't use this session anymore, so let's log it out
|
||||
try {
|
||||
const client = await initClient(
|
||||
{
|
||||
baseUrl: Config.defaultHomeserverUrl(),
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
||||
foci,
|
||||
},
|
||||
false // Don't need the crypto store just to log out
|
||||
);
|
||||
await client.logout(true);
|
||||
} catch (err_) {
|
||||
logger.warn(
|
||||
"The previous session was lost, and we couldn't log it out, " +
|
||||
"either"
|
||||
);
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
} catch (err) {
|
||||
clearSession();
|
||||
throw err;
|
||||
loadClient()
|
||||
.then((maybeClient) => {
|
||||
if (!maybeClient) {
|
||||
logger.error("Failed to initialize client");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
init()
|
||||
.then(({ client, isPasswordlessUser }) => {
|
||||
setState({
|
||||
client,
|
||||
loading: false,
|
||||
isAuthenticated: Boolean(client),
|
||||
isPasswordlessUser,
|
||||
userName: client?.getUserIdLocalpart(),
|
||||
error: undefined,
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error(err);
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
error: undefined,
|
||||
});
|
||||
setInitClientState(maybeClient);
|
||||
})
|
||||
.catch((err) => logger.error(err))
|
||||
.finally(() => (initializing.current = false));
|
||||
}, []);
|
||||
|
||||
const changePassword = useCallback(
|
||||
async (password: string) => {
|
||||
const { tempPassword, ...session } = loadSession();
|
||||
const session = loadSession();
|
||||
if (!initClientState?.client || !session) {
|
||||
return;
|
||||
}
|
||||
|
||||
await client.setPassword(
|
||||
await initClientState.client.setPassword(
|
||||
{
|
||||
type: "m.login.password",
|
||||
identifier: {
|
||||
@@ -232,73 +188,56 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
user: session.user_id,
|
||||
},
|
||||
user: session.user_id,
|
||||
password: tempPassword,
|
||||
password: session.tempPassword,
|
||||
},
|
||||
password
|
||||
);
|
||||
|
||||
saveSession({ ...session, passwordlessUser: false });
|
||||
|
||||
setState({
|
||||
client,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: false,
|
||||
userName: client.getUserIdLocalpart(),
|
||||
error: undefined,
|
||||
setInitClientState({
|
||||
client: initClientState.client,
|
||||
passwordlessUser: false,
|
||||
});
|
||||
},
|
||||
[client]
|
||||
[initClientState?.client]
|
||||
);
|
||||
|
||||
const setClient = useCallback(
|
||||
(newClient: MatrixClient, session: Session) => {
|
||||
if (client && client !== newClient) {
|
||||
client.stopClient();
|
||||
(clientParams?: SetClientParams) => {
|
||||
const oldClient = initClientState?.client;
|
||||
const newClient = clientParams?.client;
|
||||
if (oldClient && oldClient !== newClient) {
|
||||
oldClient.stopClient();
|
||||
}
|
||||
|
||||
if (newClient) {
|
||||
saveSession(session);
|
||||
|
||||
setState({
|
||||
client: newClient,
|
||||
loading: false,
|
||||
isAuthenticated: true,
|
||||
isPasswordlessUser: session.passwordlessUser,
|
||||
userName: newClient.getUserIdLocalpart(),
|
||||
error: undefined,
|
||||
if (clientParams) {
|
||||
saveSession(clientParams.session);
|
||||
setInitClientState({
|
||||
client: clientParams.client,
|
||||
passwordlessUser: clientParams.session.passwordlessUser,
|
||||
});
|
||||
} else {
|
||||
clearSession();
|
||||
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: false,
|
||||
userName: null,
|
||||
error: undefined,
|
||||
});
|
||||
setInitClientState(undefined);
|
||||
}
|
||||
},
|
||||
[client]
|
||||
[initClientState?.client]
|
||||
);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
const client = initClientState?.client;
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
await client.logout(true);
|
||||
await client.clearStores();
|
||||
clearSession();
|
||||
setState({
|
||||
client: undefined,
|
||||
loading: false,
|
||||
isAuthenticated: false,
|
||||
isPasswordlessUser: true,
|
||||
userName: "",
|
||||
error: undefined,
|
||||
});
|
||||
setInitClientState(undefined);
|
||||
history.push("/");
|
||||
PosthogAnalytics.instance.setRegistrationType(RegistrationType.Guest);
|
||||
}, [history, client]);
|
||||
}, [history, initClientState?.client]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -310,61 +249,146 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
if (!widget) loadChannel?.postMessage({});
|
||||
}, []);
|
||||
|
||||
const [alreadyOpenedErr, setAlreadyOpenedErr] = useState<Error | undefined>(
|
||||
undefined
|
||||
);
|
||||
useEventTarget(
|
||||
loadChannel,
|
||||
"message",
|
||||
useCallback(() => {
|
||||
client?.stopClient();
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: translatedError(
|
||||
"This application has been opened in another tab.",
|
||||
t
|
||||
),
|
||||
}));
|
||||
}, [client, setState, t])
|
||||
initClientState?.client.stopClient();
|
||||
setAlreadyOpenedErr(
|
||||
translatedError("This application has been opened in another tab.", t)
|
||||
);
|
||||
}, [initClientState?.client, setAlreadyOpenedErr, t])
|
||||
);
|
||||
|
||||
const context = useMemo<ClientState>(
|
||||
() => ({
|
||||
loading,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
client,
|
||||
changePassword,
|
||||
logout,
|
||||
userName,
|
||||
setClient,
|
||||
error: undefined,
|
||||
}),
|
||||
[
|
||||
loading,
|
||||
isAuthenticated,
|
||||
isPasswordlessUser,
|
||||
client,
|
||||
changePassword,
|
||||
logout,
|
||||
userName,
|
||||
setClient,
|
||||
]
|
||||
);
|
||||
const [state, setState] = useState<ClientState | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
if (alreadyOpenedErr) {
|
||||
setState({ state: "error", error: alreadyOpenedErr });
|
||||
return;
|
||||
}
|
||||
|
||||
let authenticated = undefined;
|
||||
if (initClientState) {
|
||||
authenticated = {
|
||||
client: initClientState.client,
|
||||
isPasswordlessUser: initClientState.passwordlessUser,
|
||||
changePassword,
|
||||
logout,
|
||||
};
|
||||
}
|
||||
|
||||
setState({ state: "valid", authenticated, setClient });
|
||||
}, [alreadyOpenedErr, changePassword, initClientState, logout, setClient]);
|
||||
|
||||
useEffect(() => {
|
||||
window.matrixclient = client;
|
||||
window.isPasswordlessUser = isPasswordlessUser;
|
||||
if (!initClientState) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.matrixclient = initClientState.client;
|
||||
window.passwordlessUser = initClientState.passwordlessUser;
|
||||
|
||||
if (PosthogAnalytics.hasInstance())
|
||||
PosthogAnalytics.instance.onLoginStatusChanged();
|
||||
}, [client, isPasswordlessUser]);
|
||||
}, [initClientState]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
if (alreadyOpenedErr) {
|
||||
return <ErrorView error={alreadyOpenedErr} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ClientContext.Provider value={context}>{children}</ClientContext.Provider>
|
||||
<ClientContext.Provider value={state}>{children}</ClientContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useClient = () => useContext(ClientContext);
|
||||
type InitResult = {
|
||||
client: MatrixClient;
|
||||
passwordlessUser: boolean;
|
||||
};
|
||||
|
||||
async function loadClient(): Promise<InitResult> {
|
||||
if (widget) {
|
||||
// We're inside a widget, so let's engage *matryoshka mode*
|
||||
logger.log("Using a matryoshka client");
|
||||
const client = await widget.client;
|
||||
return {
|
||||
client,
|
||||
passwordlessUser: false,
|
||||
};
|
||||
} else {
|
||||
// We're running as a standalone application
|
||||
try {
|
||||
const session = loadSession();
|
||||
if (!session) {
|
||||
throw new Error("No session stored");
|
||||
}
|
||||
|
||||
logger.log("Using a standalone client");
|
||||
|
||||
const foci = Config.get().livekit
|
||||
? [{ livekitServiceUrl: Config.get().livekit!.livekit_service_url }]
|
||||
: undefined;
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
const { user_id, device_id, access_token, passwordlessUser } = session;
|
||||
const initClientParams = {
|
||||
baseUrl: Config.defaultHomeserverUrl()!,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
fallbackICEServerAllowed: fallbackICEServerAllowed,
|
||||
foci,
|
||||
};
|
||||
|
||||
try {
|
||||
const client = await initClient(initClientParams, true);
|
||||
return {
|
||||
client,
|
||||
passwordlessUser,
|
||||
};
|
||||
} catch (err) {
|
||||
if (err instanceof CryptoStoreIntegrityError) {
|
||||
// We can't use this session anymore, so let's log it out
|
||||
try {
|
||||
const client = await initClient(initClientParams, false); // Don't need the crypto store just to log out)
|
||||
await client.logout(true);
|
||||
} catch (err) {
|
||||
logger.warn(
|
||||
"The previous session was lost, and we couldn't log it out, " +
|
||||
err +
|
||||
"either"
|
||||
);
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
} catch (err) {
|
||||
clearSession();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
user_id: string;
|
||||
device_id: string;
|
||||
access_token: string;
|
||||
passwordlessUser: boolean;
|
||||
tempPassword?: string;
|
||||
}
|
||||
|
||||
const clearSession = () => localStorage.removeItem("matrix-auth-store");
|
||||
const saveSession = (s: Session) =>
|
||||
localStorage.setItem("matrix-auth-store", JSON.stringify(s));
|
||||
const loadSession = (): Session | undefined => {
|
||||
const data = localStorage.getItem("matrix-auth-store");
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return JSON.parse(data);
|
||||
};
|
||||
|
||||
@@ -47,8 +47,8 @@ export function Facepile({
|
||||
}: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const _size = sizes.get(size);
|
||||
const _overlap = overlapMap[size];
|
||||
const _size = sizes.get(size)!;
|
||||
const _overlap = overlapMap[size]!;
|
||||
|
||||
const title = useMemo(() => {
|
||||
return members.reduce<string | null>(
|
||||
|
||||
@@ -36,15 +36,16 @@ export function ListBox<T>({
|
||||
listBoxRef,
|
||||
...rest
|
||||
}: ListBoxProps<T>) {
|
||||
const ref = useRef<HTMLUListElement>();
|
||||
if (!listBoxRef) listBoxRef = ref;
|
||||
const ref = useRef<HTMLUListElement>(null);
|
||||
|
||||
const { listBoxProps } = useListBox(rest, state, listBoxRef);
|
||||
const listRef = listBoxRef ?? ref;
|
||||
|
||||
const { listBoxProps } = useListBox(rest, state, listRef);
|
||||
|
||||
return (
|
||||
<ul
|
||||
{...listBoxProps}
|
||||
ref={listBoxRef}
|
||||
ref={listRef}
|
||||
className={classNames(styles.listBox, className)}
|
||||
>
|
||||
{[...state.collection].map((item) => (
|
||||
@@ -66,7 +67,7 @@ interface OptionProps<T> {
|
||||
}
|
||||
|
||||
function Option<T>({ item, state, className }: OptionProps<T>) {
|
||||
const ref = useRef();
|
||||
const ref = useRef(null);
|
||||
const { optionProps, isSelected, isFocused, isDisabled } = useOption(
|
||||
{ key: item.key },
|
||||
state,
|
||||
@@ -83,7 +84,11 @@ function Option<T>({ item, state, className }: OptionProps<T>) {
|
||||
const origPointerUp = optionProps.onPointerUp;
|
||||
delete optionProps.onPointerUp;
|
||||
optionProps.onClick = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
origPointerUp(e as unknown as PointerEvent<HTMLElement>);
|
||||
},
|
||||
[origPointerUp]
|
||||
|
||||
@@ -26,7 +26,7 @@ import styles from "./Menu.module.css";
|
||||
|
||||
interface MenuProps<T> extends AriaMenuOptions<T> {
|
||||
className?: String;
|
||||
onClose?: () => void;
|
||||
onClose: () => void;
|
||||
onAction: (value: Key) => void;
|
||||
label?: string;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ export function Menu<T extends object>({
|
||||
...rest
|
||||
}: MenuProps<T>) {
|
||||
const state = useTreeState<T>({ ...rest, selectionMode: "none" });
|
||||
const menuRef = useRef();
|
||||
const menuRef = useRef(null);
|
||||
const { menuProps } = useMenu<T>(rest, state, menuRef);
|
||||
|
||||
return (
|
||||
@@ -69,7 +69,7 @@ interface MenuItemProps<T> {
|
||||
}
|
||||
|
||||
function MenuItem<T>({ item, state, onAction, onClose }: MenuItemProps<T>) {
|
||||
const ref = useRef();
|
||||
const ref = useRef(null);
|
||||
const { menuItemProps } = useMenuItem(
|
||||
{
|
||||
key: item.key,
|
||||
|
||||
@@ -55,7 +55,7 @@ export function Modal({
|
||||
...rest
|
||||
}: ModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const modalRef = useRef();
|
||||
const modalRef = useRef(null);
|
||||
const { overlayProps, underlayProps } = useOverlay(
|
||||
{ ...rest, onClose },
|
||||
modalRef
|
||||
@@ -63,7 +63,7 @@ export function Modal({
|
||||
usePreventScroll();
|
||||
const { modalProps } = useModal();
|
||||
const { dialogProps, titleProps } = useDialog(rest, modalRef);
|
||||
const closeButtonRef = useRef();
|
||||
const closeButtonRef = useRef(null);
|
||||
const { buttonProps: closeButtonProps } = useButton(
|
||||
{
|
||||
onPress: () => onClose(),
|
||||
|
||||
@@ -36,6 +36,9 @@ export function SequenceDiagramViewerPage() {
|
||||
|
||||
const [debugLog, setDebugLog] = useState<DebugLog>();
|
||||
const [selectedUserId, setSelectedUserId] = useState<string>();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const onChangeDebugLog = useCallback((e) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
e.target.files[0].text().then((text: string) => {
|
||||
@@ -55,7 +58,7 @@ export function SequenceDiagramViewerPage() {
|
||||
onChange={onChangeDebugLog}
|
||||
/>
|
||||
</FieldRow>
|
||||
{debugLog && (
|
||||
{debugLog && selectedUserId && (
|
||||
<SequenceDiagramViewer
|
||||
localUserId={debugLog.localUserId}
|
||||
selectedUserId={selectedUserId}
|
||||
|
||||
@@ -74,7 +74,7 @@ export const TooltipTrigger = forwardRef<HTMLElement, TooltipTriggerProps>(
|
||||
const tooltipTriggerProps = { delay: 250, ...rest };
|
||||
const tooltipState = useTooltipTriggerState(tooltipTriggerProps);
|
||||
const triggerRef = useObjectRef<HTMLElement>(ref);
|
||||
const overlayRef = useRef();
|
||||
const overlayRef = useRef<HTMLDivElement>(null);
|
||||
const { triggerProps, tooltipProps } = useTooltipTrigger(
|
||||
tooltipTriggerProps,
|
||||
tooltipState,
|
||||
|
||||
@@ -119,7 +119,7 @@ export function UserMenu({
|
||||
)}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{(props) => (
|
||||
{(props: any) => (
|
||||
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
||||
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
||||
<Item key={key} textValue={label}>
|
||||
|
||||
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
import { useCallback, useState } from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
|
||||
import { useClient } from "./ClientContext";
|
||||
import { useClientLegacy } from "./ClientContext";
|
||||
import { useProfile } from "./profile/useProfile";
|
||||
import { useModalTriggerState } from "./Modal";
|
||||
import { SettingsModal } from "./settings/SettingsModal";
|
||||
@@ -30,8 +30,7 @@ interface Props {
|
||||
export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const { isAuthenticated, isPasswordlessUser, logout, userName, client } =
|
||||
useClient();
|
||||
const { client, logout, authenticated, passwordlessUser } = useClientLegacy();
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
const { modalState, modalProps } = useModalTriggerState();
|
||||
|
||||
@@ -49,7 +48,9 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
modalState.open();
|
||||
break;
|
||||
case "logout":
|
||||
logout();
|
||||
if (logout) {
|
||||
logout();
|
||||
}
|
||||
break;
|
||||
case "login":
|
||||
history.push("/login", { state: { from: location } });
|
||||
@@ -59,19 +60,22 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
||||
[history, location, logout, modalState]
|
||||
);
|
||||
|
||||
const userName = client?.getUserIdLocalpart() ?? "";
|
||||
return (
|
||||
<>
|
||||
<UserMenu
|
||||
preventNavigation={preventNavigation}
|
||||
isAuthenticated={isAuthenticated}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
avatarUrl={avatarUrl}
|
||||
onAction={onAction}
|
||||
displayName={
|
||||
displayName || (userName ? userName.replace("@", "") : undefined)
|
||||
}
|
||||
/>
|
||||
{modalState.isOpen && (
|
||||
{avatarUrl && (
|
||||
<UserMenu
|
||||
preventNavigation={preventNavigation}
|
||||
isAuthenticated={authenticated}
|
||||
isPasswordlessUser={passwordlessUser}
|
||||
avatarUrl={avatarUrl}
|
||||
onAction={onAction}
|
||||
displayName={
|
||||
displayName || (userName ? userName.replace("@", "") : "")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{modalState.isOpen && client && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
defaultTab={defaultSettingsTab}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class PosthogAnalytics {
|
||||
// set true during the constructor if posthog config is present, otherwise false
|
||||
private static internalInstance: PosthogAnalytics | null = null;
|
||||
|
||||
private identificationPromise: Promise<void>;
|
||||
private identificationPromise?: Promise<void>;
|
||||
private readonly enabled: boolean = false;
|
||||
private anonymity = Anonymity.Disabled;
|
||||
private platformSuperProperties = {};
|
||||
@@ -255,7 +255,9 @@ export class PosthogAnalytics {
|
||||
} catch (e) {
|
||||
// The above could fail due to network requests, but not essential to starting the application,
|
||||
// so swallow it.
|
||||
logger.log("Unable to identify user for tracking" + e.toString());
|
||||
logger.log(
|
||||
"Unable to identify user for tracking" + (e as Error)?.toString()
|
||||
);
|
||||
}
|
||||
if (analyticsID) {
|
||||
this.posthog.identify(analyticsID);
|
||||
@@ -366,7 +368,7 @@ export class PosthogAnalytics {
|
||||
|
||||
if (anonymity === Anonymity.Pseudonymous) {
|
||||
this.setRegistrationType(
|
||||
window.matrixclient.isGuest() || window.isPasswordlessUser
|
||||
window.matrixclient.isGuest() || window.passwordlessUser
|
||||
? RegistrationType.Guest
|
||||
: RegistrationType.Registered
|
||||
);
|
||||
|
||||
@@ -35,8 +35,8 @@ export const LoginPage: FC = () => {
|
||||
const { setClient } = useClient();
|
||||
const login = useInteractiveLogin();
|
||||
const homeserver = Config.defaultHomeserverUrl(); // TODO: Make this configurable
|
||||
const usernameRef = useRef<HTMLInputElement>();
|
||||
const passwordRef = useRef<HTMLInputElement>();
|
||||
const usernameRef = useRef<HTMLInputElement>(null);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -49,12 +49,27 @@ export const LoginPage: FC = () => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
if (!homeserver || !usernameRef.current || !passwordRef.current) {
|
||||
setError(Error("Login parameters are undefined"));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
login(homeserver, usernameRef.current.value, passwordRef.current.value)
|
||||
.then(([client, session]) => {
|
||||
setClient(client, session);
|
||||
if (!setClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (location.state && location.state.from) {
|
||||
history.push(location.state.from);
|
||||
setClient({ client, session });
|
||||
|
||||
const locationState = location.state;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (locationState && locationState.from) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
history.push(locationState.from);
|
||||
} else {
|
||||
history.push("/");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { Button } from "../button";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { useClientLegacy } from "../ClientContext";
|
||||
import { useInteractiveRegistration } from "./useInteractiveRegistration";
|
||||
import styles from "./LoginPage.module.css";
|
||||
import { ReactComponent as Logo } from "../icons/LogoLarge.svg";
|
||||
@@ -45,9 +45,10 @@ export const RegisterPage: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
usePageTitle(t("Register"));
|
||||
|
||||
const { loading, isAuthenticated, isPasswordlessUser, client, setClient } =
|
||||
useClient();
|
||||
const confirmPasswordRef = useRef<HTMLInputElement>();
|
||||
const { loading, authenticated, passwordlessUser, client, setClient } =
|
||||
useClientLegacy();
|
||||
|
||||
const confirmPasswordRef = useRef<HTMLInputElement>(null);
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [registering, setRegistering] = useState(false);
|
||||
@@ -75,10 +76,15 @@ export const RegisterPage: FC = () => {
|
||||
userName,
|
||||
password,
|
||||
userName,
|
||||
recaptchaResponse
|
||||
recaptchaResponse,
|
||||
passwordlessUser
|
||||
);
|
||||
|
||||
if (client && isPasswordlessUser) {
|
||||
if (!client || !client.groupCallEventHandler || !setClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordlessUser) {
|
||||
// Migrate the user's rooms
|
||||
for (const groupCall of client.groupCallEventHandler.groupCalls.values()) {
|
||||
const roomId = groupCall.room.roomId;
|
||||
@@ -86,7 +92,11 @@ export const RegisterPage: FC = () => {
|
||||
try {
|
||||
await newClient.joinRoom(roomId);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (error.errcode === "M_LIMIT_EXCEEDED") {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
await sleep(error.data.retry_after_ms);
|
||||
await newClient.joinRoom(roomId);
|
||||
} else {
|
||||
@@ -97,13 +107,17 @@ export const RegisterPage: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
setClient(newClient, session);
|
||||
setClient({ client: newClient, session });
|
||||
PosthogAnalytics.instance.eventSignup.cacheSignupEnd(new Date());
|
||||
};
|
||||
|
||||
submit()
|
||||
.then(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (location.state?.from) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
history.push(location.state?.from);
|
||||
} else {
|
||||
history.push("/");
|
||||
@@ -119,7 +133,7 @@ export const RegisterPage: FC = () => {
|
||||
register,
|
||||
location,
|
||||
history,
|
||||
isPasswordlessUser,
|
||||
passwordlessUser,
|
||||
reset,
|
||||
execute,
|
||||
client,
|
||||
@@ -136,10 +150,10 @@ export const RegisterPage: FC = () => {
|
||||
}, [password, passwordConfirmation, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && isAuthenticated && !isPasswordlessUser && !registering) {
|
||||
if (!loading && authenticated && !passwordlessUser && !registering) {
|
||||
history.push("/");
|
||||
}
|
||||
}, [loading, history, isAuthenticated, isPasswordlessUser, registering]);
|
||||
}, [loading, history, authenticated, passwordlessUser, registering]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingView />;
|
||||
|
||||
@@ -41,8 +41,10 @@ export const useInteractiveLogin = () =>
|
||||
},
|
||||
password,
|
||||
}),
|
||||
stateUpdated: null,
|
||||
requestEmailToken: null,
|
||||
stateUpdated: (...args) => {},
|
||||
requestEmailToken: (...args): Promise<{ sid: string }> => {
|
||||
return Promise.resolve({ sid: "" });
|
||||
},
|
||||
});
|
||||
|
||||
// XXX: This claims to return an IAuthData which contains none of these
|
||||
|
||||
@@ -23,28 +23,32 @@ import { Session } from "../ClientContext";
|
||||
import { Config } from "../config/Config";
|
||||
|
||||
export const useInteractiveRegistration = (): {
|
||||
privacyPolicyUrl: string;
|
||||
recaptchaKey: string;
|
||||
privacyPolicyUrl?: string;
|
||||
recaptchaKey?: string;
|
||||
register: (
|
||||
username: string,
|
||||
password: string,
|
||||
displayName: string,
|
||||
recaptchaResponse: string,
|
||||
passwordlessUser?: boolean
|
||||
passwordlessUser: boolean
|
||||
) => Promise<[MatrixClient, Session]>;
|
||||
} => {
|
||||
const [privacyPolicyUrl, setPrivacyPolicyUrl] = useState<string>();
|
||||
const [recaptchaKey, setRecaptchaKey] = useState<string>();
|
||||
const [privacyPolicyUrl, setPrivacyPolicyUrl] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [recaptchaKey, setRecaptchaKey] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const authClient = useRef<MatrixClient>();
|
||||
if (!authClient.current) {
|
||||
authClient.current = createClient({
|
||||
baseUrl: Config.defaultHomeserverUrl(),
|
||||
baseUrl: Config.defaultHomeserverUrl()!,
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
authClient.current.registerRequest({}).catch((error) => {
|
||||
authClient.current!.registerRequest({}).catch((error) => {
|
||||
setPrivacyPolicyUrl(
|
||||
error.data?.params["m.login.terms"]?.policies?.privacy_policy?.en?.url
|
||||
);
|
||||
@@ -58,12 +62,12 @@ export const useInteractiveRegistration = (): {
|
||||
password: string,
|
||||
displayName: string,
|
||||
recaptchaResponse: string,
|
||||
passwordlessUser?: boolean
|
||||
passwordlessUser: boolean
|
||||
): Promise<[MatrixClient, Session]> => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClient.current,
|
||||
matrixClient: authClient.current!,
|
||||
doRequest: (auth) =>
|
||||
authClient.current.registerRequest({
|
||||
authClient.current!.registerRequest({
|
||||
username,
|
||||
password,
|
||||
auth: auth || undefined,
|
||||
@@ -84,7 +88,9 @@ export const useInteractiveRegistration = (): {
|
||||
});
|
||||
}
|
||||
},
|
||||
requestEmailToken: null,
|
||||
requestEmailToken: (...args) => {
|
||||
return Promise.resolve({ sid: "dummy" });
|
||||
},
|
||||
});
|
||||
|
||||
// XXX: This claims to return an IAuthData which contains none of these
|
||||
@@ -95,7 +101,7 @@ export const useInteractiveRegistration = (): {
|
||||
|
||||
const client = await initClient(
|
||||
{
|
||||
baseUrl: Config.defaultHomeserverUrl(),
|
||||
baseUrl: Config.defaultHomeserverUrl()!,
|
||||
accessToken: access_token,
|
||||
userId: user_id,
|
||||
deviceId: device_id,
|
||||
@@ -117,7 +123,7 @@ export const useInteractiveRegistration = (): {
|
||||
session.tempPassword = password;
|
||||
}
|
||||
|
||||
const user = client.getUser(client.getUserId());
|
||||
const user = client.getUser(client.getUserId()!)!;
|
||||
user.setRawDisplayName(displayName);
|
||||
user.setDisplayName(displayName);
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ interface RecaptchaPromiseRef {
|
||||
reject: (error: Error) => void;
|
||||
}
|
||||
|
||||
export const useRecaptcha = (sitekey: string) => {
|
||||
export const useRecaptcha = (sitekey?: string) => {
|
||||
const { t } = useTranslation();
|
||||
const [recaptchaId] = useState(() => randomString(16));
|
||||
const promiseRef = useRef<RecaptchaPromiseRef>();
|
||||
@@ -68,9 +68,9 @@ export const useRecaptcha = (sitekey: string) => {
|
||||
}
|
||||
}, [recaptchaId, sitekey]);
|
||||
|
||||
const execute = useCallback(() => {
|
||||
const execute = useCallback((): Promise<string> => {
|
||||
if (!sitekey) {
|
||||
return Promise.resolve(null);
|
||||
return Promise.resolve("");
|
||||
}
|
||||
|
||||
if (!window.grecaptcha) {
|
||||
|
||||
@@ -23,9 +23,9 @@ import { generateRandomName } from "../auth/generateRandomName";
|
||||
import { useRecaptcha } from "../auth/useRecaptcha";
|
||||
|
||||
interface UseRegisterPasswordlessUserType {
|
||||
privacyPolicyUrl: string;
|
||||
privacyPolicyUrl?: string;
|
||||
registerPasswordlessUser: (displayName: string) => Promise<void>;
|
||||
recaptchaId: string;
|
||||
recaptchaId?: string;
|
||||
}
|
||||
|
||||
export function useRegisterPasswordlessUser(): UseRegisterPasswordlessUserType {
|
||||
@@ -36,6 +36,10 @@ export function useRegisterPasswordlessUser(): UseRegisterPasswordlessUserType {
|
||||
|
||||
const registerPasswordlessUser = useCallback(
|
||||
async (displayName: string) => {
|
||||
if (!setClient) {
|
||||
throw new Error("No client context");
|
||||
}
|
||||
|
||||
try {
|
||||
const recaptchaResponse = await execute();
|
||||
const userName = generateRandomName();
|
||||
@@ -46,7 +50,7 @@ export function useRegisterPasswordlessUser(): UseRegisterPasswordlessUserType {
|
||||
recaptchaResponse,
|
||||
true
|
||||
);
|
||||
setClient(client, session);
|
||||
setClient({ client, session });
|
||||
} catch (e) {
|
||||
reset();
|
||||
throw e;
|
||||
|
||||
@@ -46,7 +46,7 @@ export function LinkButton({
|
||||
<Link
|
||||
className={classNames(
|
||||
variantToClassName[variant || "secondary"],
|
||||
sizeToClassName[size],
|
||||
size ? sizeToClassName.lg : [],
|
||||
className
|
||||
)}
|
||||
to={to}
|
||||
|
||||
@@ -45,11 +45,11 @@ export class Config {
|
||||
|
||||
// Convenience accessors
|
||||
public static defaultHomeserverUrl(): string | undefined {
|
||||
return Config.get().default_server_config["m.homeserver"].base_url;
|
||||
return Config.get().default_server_config?.["m.homeserver"].base_url;
|
||||
}
|
||||
|
||||
public static defaultServerName(): string | undefined {
|
||||
return Config.get().default_server_config["m.homeserver"].server_name;
|
||||
return Config.get().default_server_config?.["m.homeserver"].server_name;
|
||||
}
|
||||
|
||||
public config?: ResolvedConfigOptions;
|
||||
|
||||
@@ -52,7 +52,15 @@ export const CallTypeDropdown: FC<Props> = ({ callType, setCallType }) => {
|
||||
</Headline>
|
||||
</Button>
|
||||
{(props: JSX.IntrinsicAttributes) => (
|
||||
<Menu {...props} label={t("Call type menu")} onAction={setCallType}>
|
||||
<Menu
|
||||
{...props}
|
||||
label={t("Call type menu")}
|
||||
onAction={(key) => {
|
||||
const callType = key.toString();
|
||||
setCallType(callType as CallType);
|
||||
}}
|
||||
onClose={() => {}}
|
||||
>
|
||||
<Item key={CallType.Video} textValue={t("Video call")}>
|
||||
<VideoIcon />
|
||||
<span>{t("Video call")}</span>
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useClient } from "../ClientContext";
|
||||
import { useClientState } from "../ClientContext";
|
||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||
import { UnauthenticatedView } from "./UnauthenticatedView";
|
||||
import { RegisteredView } from "./RegisteredView";
|
||||
@@ -26,16 +26,18 @@ export function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
usePageTitle(t("Home"));
|
||||
|
||||
const { isAuthenticated, isPasswordlessUser, loading, error, client } =
|
||||
useClient();
|
||||
const clientState = useClientState();
|
||||
|
||||
if (loading) {
|
||||
if (!clientState) {
|
||||
return <LoadingView />;
|
||||
} else if (error) {
|
||||
return <ErrorView error={error} />;
|
||||
} else if (clientState.state === "error") {
|
||||
return <ErrorView error={clientState.error} />;
|
||||
} else {
|
||||
return isAuthenticated ? (
|
||||
<RegisteredView isPasswordlessUser={isPasswordlessUser} client={client} />
|
||||
return clientState.authenticated ? (
|
||||
<RegisteredView
|
||||
isPasswordlessUser={clientState.authenticated.isPasswordlessUser}
|
||||
client={clientState.authenticated.client}
|
||||
/>
|
||||
) : (
|
||||
<UnauthenticatedView />
|
||||
);
|
||||
|
||||
@@ -83,11 +83,17 @@ export const UnauthenticatedView: FC = () => {
|
||||
try {
|
||||
[roomIdOrAlias] = await createRoom(client, roomName, ptt);
|
||||
} catch (error) {
|
||||
if (!setClient) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (error.errcode === "M_ROOM_IN_USE") {
|
||||
setOnFinished(() => {
|
||||
setClient(client, session);
|
||||
setClient({ client, session });
|
||||
const aliasLocalpart = roomAliasLocalpartFromRoomName(roomName);
|
||||
const [, serverName] = client.getUserId().split(":");
|
||||
const [, serverName] = client.getUserId()!.split(":");
|
||||
history.push(`/room/#${aliasLocalpart}:${serverName}`);
|
||||
});
|
||||
|
||||
@@ -100,7 +106,11 @@ export const UnauthenticatedView: FC = () => {
|
||||
}
|
||||
|
||||
// Only consider the registration successful if we managed to create the room, too
|
||||
setClient(client, session);
|
||||
if (!setClient) {
|
||||
throw new Error("setClient is undefined");
|
||||
}
|
||||
|
||||
setClient({ client, session });
|
||||
history.push(`/room/${roomIdOrAlias}`);
|
||||
}
|
||||
|
||||
@@ -204,7 +214,7 @@ export const UnauthenticatedView: FC = () => {
|
||||
</Body>
|
||||
</footer>
|
||||
</div>
|
||||
{modalState.isOpen && (
|
||||
{modalState.isOpen && onFinished && (
|
||||
<JoinExistingCallModal onJoin={onFinished} {...modalProps} />
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -42,7 +42,7 @@ function getLastTs(client: MatrixClient, r: Room) {
|
||||
return ts;
|
||||
}
|
||||
|
||||
const myUserId = client.getUserId();
|
||||
const myUserId = client.getUserId()!;
|
||||
|
||||
if (r.getMyMembership() !== "join") {
|
||||
const membershipEvent = r.currentState.getStateEvents(
|
||||
@@ -79,25 +79,30 @@ function sortRooms(client: MatrixClient, rooms: Room[]): Room[] {
|
||||
}
|
||||
|
||||
export function useGroupCallRooms(client: MatrixClient): GroupCallRoom[] {
|
||||
const [rooms, setRooms] = useState([]);
|
||||
const [rooms, setRooms] = useState<GroupCallRoom[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
function updateRooms() {
|
||||
if (!client.groupCallEventHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupCalls = client.groupCallEventHandler.groupCalls.values();
|
||||
const rooms = Array.from(groupCalls).map((groupCall) => groupCall.room);
|
||||
const sortedRooms = sortRooms(client, rooms);
|
||||
const items = sortedRooms.map((room) => {
|
||||
const groupCall = client.getGroupCallForRoom(room.roomId);
|
||||
const groupCall = client.getGroupCallForRoom(room.roomId)!;
|
||||
|
||||
return {
|
||||
roomId: room.getCanonicalAlias() || room.roomId,
|
||||
roomName: room.name,
|
||||
avatarUrl: room.getMxcAvatarUrl(),
|
||||
avatarUrl: room.getMxcAvatarUrl()!,
|
||||
room,
|
||||
groupCall,
|
||||
participants: [...groupCall.participants],
|
||||
participants: [...groupCall!.participants.keys()],
|
||||
};
|
||||
});
|
||||
|
||||
setRooms(items);
|
||||
}
|
||||
|
||||
|
||||
@@ -229,5 +229,6 @@ export class Initializer {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
private initPromise: Promise<void> | null;
|
||||
|
||||
private initPromise?: Promise<void>;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,14 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { useObjectRef } from "@react-aria/utils";
|
||||
import { AllHTMLAttributes, ChangeEvent, useEffect } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useState } from "react";
|
||||
import { forwardRef } from "react";
|
||||
import {
|
||||
AllHTMLAttributes,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useState,
|
||||
forwardRef,
|
||||
ChangeEvent,
|
||||
} from "react";
|
||||
import classNames from "classnames";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -43,7 +47,7 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [removed, setRemoved] = useState(false);
|
||||
const [objUrl, setObjUrl] = useState<string>(null);
|
||||
const [objUrl, setObjUrl] = useState<string | undefined>(undefined);
|
||||
|
||||
const fileInputRef = useObjectRef(ref);
|
||||
|
||||
@@ -52,11 +56,11 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||
|
||||
const onChange = (e: Event) => {
|
||||
const inputEvent = e as unknown as ChangeEvent<HTMLInputElement>;
|
||||
if (inputEvent.target.files.length > 0) {
|
||||
if (inputEvent.target.files && inputEvent.target.files.length > 0) {
|
||||
setObjUrl(URL.createObjectURL(inputEvent.target.files[0]));
|
||||
setRemoved(false);
|
||||
} else {
|
||||
setObjUrl(null);
|
||||
setObjUrl(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +81,7 @@ export const AvatarInputField = forwardRef<HTMLInputElement, Props>(
|
||||
<div className={styles.avatarContainer}>
|
||||
<Avatar
|
||||
size={Size.XL}
|
||||
src={removed ? null : objUrl || avatarUrl}
|
||||
src={removed ? undefined : objUrl || avatarUrl}
|
||||
fallback={displayName.slice(0, 1).toUpperCase()}
|
||||
/>
|
||||
<input
|
||||
|
||||
@@ -82,7 +82,7 @@ interface InputFieldProps {
|
||||
defaultValue?: string;
|
||||
placeholder?: string;
|
||||
defaultChecked?: boolean;
|
||||
onChange?: (event: ChangeEvent) => void;
|
||||
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||
}
|
||||
|
||||
export const InputField = forwardRef<
|
||||
@@ -119,6 +119,8 @@ export const InputField = forwardRef<
|
||||
>
|
||||
{prefix && <span>{prefix}</span>}
|
||||
{type === "textarea" ? (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
<textarea
|
||||
id={id}
|
||||
ref={ref as ForwardedRef<HTMLTextAreaElement>}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function SelectInput(props: Props): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const state = useSelectState(props);
|
||||
|
||||
const ref = useRef();
|
||||
const ref = useRef(null);
|
||||
const { labelProps, triggerProps, valueProps, menuProps } = useSelect(
|
||||
props,
|
||||
state,
|
||||
|
||||
@@ -37,7 +37,7 @@ interface Props {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const SFUConfigContext = createContext<SFUConfig>(undefined);
|
||||
const SFUConfigContext = createContext<SFUConfig | undefined>(undefined);
|
||||
|
||||
export const useSFUConfig = () => useContext(SFUConfigContext);
|
||||
|
||||
@@ -62,7 +62,7 @@ export function OpenIDLoader({
|
||||
setState({ kind: "loaded", sfuConfig: result });
|
||||
} catch (e) {
|
||||
logger.error("Failed to fetch SFU config: ", e);
|
||||
setState({ kind: "failed", error: e });
|
||||
setState({ kind: "failed", error: e as Error });
|
||||
}
|
||||
})();
|
||||
}, [client, livekitServiceURL, roomName]);
|
||||
|
||||
@@ -17,7 +17,7 @@ export type DeviceChoices = {
|
||||
|
||||
export function useLiveKit(
|
||||
userChoices: UserChoices,
|
||||
sfuConfig: SFUConfig
|
||||
sfuConfig?: SFUConfig
|
||||
): Room | undefined {
|
||||
const roomOptions = useMemo((): RoomOptions => {
|
||||
const options = defaultLiveKitOptions;
|
||||
@@ -33,8 +33,8 @@ export function useLiveKit(
|
||||
}, [userChoices.video, userChoices.audio]);
|
||||
|
||||
const { room } = useLiveKitRoom({
|
||||
token: sfuConfig.jwt,
|
||||
serverUrl: sfuConfig.url,
|
||||
token: sfuConfig?.jwt,
|
||||
serverUrl: sfuConfig?.url,
|
||||
audio: userChoices.audio?.enabled ?? false,
|
||||
video: userChoices.video?.enabled ?? false,
|
||||
options: roomOptions,
|
||||
|
||||
@@ -19,8 +19,7 @@ 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 } from "matrix-js-sdk/src/matrix";
|
||||
import { ICreateClientOpts } from "matrix-js-sdk/src/matrix";
|
||||
import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { Visibility, Preset } from "matrix-js-sdk/src/@types/partials";
|
||||
import { ISyncStateData, SyncState } from "matrix-js-sdk/src/sync";
|
||||
@@ -57,8 +56,8 @@ function waitForSync(client: MatrixClient) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const onSync = (
|
||||
state: SyncState,
|
||||
_old: SyncState,
|
||||
data: ISyncStateData
|
||||
_old: SyncState | null,
|
||||
data?: ISyncStateData
|
||||
) => {
|
||||
if (state === "PREPARED") {
|
||||
resolve();
|
||||
@@ -87,7 +86,7 @@ export async function initClient(
|
||||
): Promise<MatrixClient> {
|
||||
await loadOlm();
|
||||
|
||||
let indexedDB: IDBFactory;
|
||||
let indexedDB: IDBFactory | undefined;
|
||||
try {
|
||||
indexedDB = window.indexedDB;
|
||||
} catch (e) {}
|
||||
@@ -247,7 +246,7 @@ export function sanitiseRoomNameInput(input: string): string {
|
||||
*/
|
||||
export function roomNameFromRoomId(roomId: string): string {
|
||||
return roomId
|
||||
.match(/([^:]+):.*$/)[1]
|
||||
.match(/([^:]+):.*$/)![1]
|
||||
.substring(1)
|
||||
.split("-")
|
||||
.map((part) =>
|
||||
@@ -262,7 +261,7 @@ export function isLocalRoomId(roomId: string, client: MatrixClient): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = roomId.match(/[^:]+:(.*)$/);
|
||||
const parts = roomId.match(/[^:]+:(.*)$/)!;
|
||||
|
||||
if (parts.length < 2) {
|
||||
return false;
|
||||
@@ -302,7 +301,7 @@ export async function createRoom(
|
||||
"org.matrix.msc3401.call.member": 0,
|
||||
},
|
||||
users: {
|
||||
[client.getUserId()]: 100,
|
||||
[client.getUserId()!]: 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,46 +61,46 @@ export class OTelCall {
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.call.peerConn.removeEventListener(
|
||||
this.call.peerConn?.removeEventListener(
|
||||
"connectionstatechange",
|
||||
this.onCallConnectionStateChanged
|
||||
);
|
||||
this.call.peerConn.removeEventListener(
|
||||
this.call.peerConn?.removeEventListener(
|
||||
"signalingstatechange",
|
||||
this.onCallSignalingStateChanged
|
||||
);
|
||||
this.call.peerConn.removeEventListener(
|
||||
this.call.peerConn?.removeEventListener(
|
||||
"iceconnectionstatechange",
|
||||
this.onIceConnectionStateChanged
|
||||
);
|
||||
this.call.peerConn.removeEventListener(
|
||||
this.call.peerConn?.removeEventListener(
|
||||
"icegatheringstatechange",
|
||||
this.onIceGatheringStateChanged
|
||||
);
|
||||
this.call.peerConn.removeEventListener(
|
||||
this.call.peerConn?.removeEventListener(
|
||||
"icecandidateerror",
|
||||
this.onIceCandidateError
|
||||
);
|
||||
}
|
||||
|
||||
private addCallPeerConnListeners = (): void => {
|
||||
this.call.peerConn.addEventListener(
|
||||
this.call.peerConn?.addEventListener(
|
||||
"connectionstatechange",
|
||||
this.onCallConnectionStateChanged
|
||||
);
|
||||
this.call.peerConn.addEventListener(
|
||||
this.call.peerConn?.addEventListener(
|
||||
"signalingstatechange",
|
||||
this.onCallSignalingStateChanged
|
||||
);
|
||||
this.call.peerConn.addEventListener(
|
||||
this.call.peerConn?.addEventListener(
|
||||
"iceconnectionstatechange",
|
||||
this.onIceConnectionStateChanged
|
||||
);
|
||||
this.call.peerConn.addEventListener(
|
||||
this.call.peerConn?.addEventListener(
|
||||
"icegatheringstatechange",
|
||||
this.onIceGatheringStateChanged
|
||||
);
|
||||
this.call.peerConn.addEventListener(
|
||||
this.call.peerConn?.addEventListener(
|
||||
"icecandidateerror",
|
||||
this.onIceCandidateError
|
||||
);
|
||||
@@ -108,25 +108,25 @@ export class OTelCall {
|
||||
|
||||
public onCallConnectionStateChanged = (): void => {
|
||||
this.span.addEvent("matrix.call.callConnectionStateChange", {
|
||||
callConnectionState: this.call.peerConn.connectionState,
|
||||
callConnectionState: this.call.peerConn?.connectionState,
|
||||
});
|
||||
};
|
||||
|
||||
public onCallSignalingStateChanged = (): void => {
|
||||
this.span.addEvent("matrix.call.callSignalingStateChange", {
|
||||
callSignalingState: this.call.peerConn.signalingState,
|
||||
callSignalingState: this.call.peerConn?.signalingState,
|
||||
});
|
||||
};
|
||||
|
||||
public onIceConnectionStateChanged = (): void => {
|
||||
this.span.addEvent("matrix.call.iceConnectionStateChange", {
|
||||
iceConnectionState: this.call.peerConn.iceConnectionState,
|
||||
iceConnectionState: this.call.peerConn?.iceConnectionState,
|
||||
});
|
||||
};
|
||||
|
||||
public onIceGatheringStateChanged = (): void => {
|
||||
this.span.addEvent("matrix.call.iceGatheringStateChange", {
|
||||
iceGatheringState: this.call.peerConn.iceGatheringState,
|
||||
iceGatheringState: this.call.peerConn?.iceGatheringState,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ export class OTelGroupCallMembership {
|
||||
if (
|
||||
!userCalls ||
|
||||
!userCalls.has(callTrackingInfo.deviceId) ||
|
||||
userCalls.get(callTrackingInfo.deviceId).callId !==
|
||||
userCalls.get(callTrackingInfo.deviceId)?.callId !==
|
||||
callTrackingInfo.call.callId
|
||||
) {
|
||||
callTrackingInfo.end();
|
||||
@@ -420,7 +420,7 @@ export class OTelGroupCallMembership {
|
||||
ctx
|
||||
);
|
||||
|
||||
span.setAttribute("matrix.callId", callId);
|
||||
span.setAttribute("matrix.callId", callId ?? "unknown");
|
||||
span.setAttribute(
|
||||
"matrix.opponentMemberId",
|
||||
report.opponentMemberId ? report.opponentMemberId : "unknown"
|
||||
|
||||
@@ -23,7 +23,6 @@ import { SemanticResourceAttributes } from "@opentelemetry/semantic-conventions"
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { PosthogSpanProcessor } from "../analytics/PosthogSpanProcessor";
|
||||
import { Anonymity } from "../analytics/PosthogAnalytics";
|
||||
import { Config } from "../config/Config";
|
||||
import { RageshakeSpanProcessor } from "../analytics/RageshakeSpanProcessor";
|
||||
|
||||
@@ -34,8 +33,7 @@ let sharedInstance: ElementCallOpenTelemetry;
|
||||
export class ElementCallOpenTelemetry {
|
||||
private _provider: WebTracerProvider;
|
||||
private _tracer: Tracer;
|
||||
private _anonymity: Anonymity;
|
||||
private otlpExporter: OTLPTraceExporter;
|
||||
private otlpExporter?: OTLPTraceExporter;
|
||||
public readonly rageshakeProcessor?: RageshakeSpanProcessor;
|
||||
|
||||
static globalInit(): void {
|
||||
@@ -100,7 +98,7 @@ export class ElementCallOpenTelemetry {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
opentelemetry.trace.setGlobalTracerProvider(null);
|
||||
opentelemetry.trace.disable();
|
||||
this._provider?.shutdown();
|
||||
}
|
||||
|
||||
@@ -115,8 +113,4 @@ export class ElementCallOpenTelemetry {
|
||||
public get provider(): WebTracerProvider {
|
||||
return this._provider;
|
||||
}
|
||||
|
||||
public get anonymity(): Anonymity {
|
||||
return this._anonymity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export const PopoverMenuTrigger = forwardRef<
|
||||
buttonRef
|
||||
);
|
||||
|
||||
const popoverRef = useRef();
|
||||
const popoverRef = useRef(null);
|
||||
|
||||
const { overlayProps } = useOverlayPosition({
|
||||
targetRef: buttonRef,
|
||||
|
||||
@@ -21,10 +21,10 @@ import { FileType } from "matrix-js-sdk/src/http-api";
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
|
||||
interface ProfileLoadState {
|
||||
success?: boolean;
|
||||
loading?: boolean;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
success: boolean;
|
||||
loading: boolean;
|
||||
displayName?: string;
|
||||
avatarUrl?: string;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
@@ -38,23 +38,26 @@ type ProfileSaveCallback = ({
|
||||
removeAvatar: boolean;
|
||||
}) => Promise<void>;
|
||||
|
||||
export function useProfile(client: MatrixClient) {
|
||||
const [{ loading, displayName, avatarUrl, error, success }, setState] =
|
||||
export function useProfile(client?: MatrixClient) {
|
||||
const [{ success, loading, displayName, avatarUrl, error }, setState] =
|
||||
useState<ProfileLoadState>(() => {
|
||||
const user = client?.getUser(client.getUserId());
|
||||
let user: User | undefined = undefined;
|
||||
if (client) {
|
||||
user = client.getUser(client.getUserId()!) ?? undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
loading: false,
|
||||
displayName: user?.rawDisplayName,
|
||||
avatarUrl: user?.avatarUrl,
|
||||
error: null,
|
||||
error: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const onChangeUser = (
|
||||
_event: MatrixEvent,
|
||||
_event: MatrixEvent | undefined,
|
||||
{ displayName, avatarUrl }: User
|
||||
) => {
|
||||
setState({
|
||||
@@ -62,17 +65,16 @@ export function useProfile(client: MatrixClient) {
|
||||
loading: false,
|
||||
displayName,
|
||||
avatarUrl,
|
||||
error: null,
|
||||
error: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
let user: User;
|
||||
|
||||
let user: User | null;
|
||||
if (client) {
|
||||
const userId = client.getUserId();
|
||||
const userId = client.getUserId()!;
|
||||
user = client.getUser(userId);
|
||||
user.on(UserEvent.DisplayName, onChangeUser);
|
||||
user.on(UserEvent.AvatarUrl, onChangeUser);
|
||||
user?.on(UserEvent.DisplayName, onChangeUser);
|
||||
user?.on(UserEvent.AvatarUrl, onChangeUser);
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -89,7 +91,7 @@ export function useProfile(client: MatrixClient) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
loading: true,
|
||||
error: null,
|
||||
error: undefined,
|
||||
success: false,
|
||||
}));
|
||||
|
||||
@@ -110,7 +112,9 @@ export function useProfile(client: MatrixClient) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
displayName,
|
||||
avatarUrl: removeAvatar ? null : mxcAvatarUrl ?? prev.avatarUrl,
|
||||
avatarUrl: removeAvatar
|
||||
? undefined
|
||||
: mxcAvatarUrl ?? prev.avatarUrl,
|
||||
loading: false,
|
||||
success: true,
|
||||
}));
|
||||
|
||||
@@ -46,7 +46,12 @@ export function GridLayoutMenu({ layout, setLayout }: Props) {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
{(props: JSX.IntrinsicAttributes) => (
|
||||
<Menu {...props} label={t("Grid layout menu")} onAction={setLayout}>
|
||||
<Menu
|
||||
{...props}
|
||||
label={t("Grid layout menu")}
|
||||
onAction={(key) => setLayout(key.toString() as Layout)}
|
||||
onClose={() => {}}
|
||||
>
|
||||
<Item key="freedom" textValue={t("Freedom")}>
|
||||
<FreedomIcon />
|
||||
<span>Freedom</span>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
|
||||
/*
|
||||
Copyright 2022 New Vector Ltd
|
||||
|
||||
@@ -70,7 +73,7 @@ const defaultCollapsedFields = [
|
||||
];
|
||||
|
||||
function shouldCollapse({ name }: CollapsedFieldProps) {
|
||||
return defaultCollapsedFields.includes(name);
|
||||
return name ? defaultCollapsedFields.includes(name) : false;
|
||||
}
|
||||
|
||||
function getUserName(userId: string) {
|
||||
@@ -196,7 +199,7 @@ export function SequenceDiagramViewer({
|
||||
onSelectUserId,
|
||||
events,
|
||||
}: SequenceDiagramViewerProps) {
|
||||
const mermaidElRef = useRef<HTMLDivElement>();
|
||||
const mermaidElRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
mermaid.initialize({
|
||||
@@ -217,6 +220,7 @@ export function SequenceDiagramViewer({
|
||||
`;
|
||||
|
||||
mermaid.mermaidAPI.render("mermaid", graphDefinition, (svgCode: string) => {
|
||||
if (!mermaidElRef.current) return;
|
||||
mermaidElRef.current.innerHTML = svgCode;
|
||||
});
|
||||
}, [events, localUserId, selectedUserId]);
|
||||
@@ -228,7 +232,7 @@ export function SequenceDiagramViewer({
|
||||
className={styles.selectInput}
|
||||
label="Remote User"
|
||||
selectedKey={selectedUserId}
|
||||
onSelectionChange={onSelectUserId}
|
||||
onSelectionChange={(key) => onSelectUserId(key.toString())}
|
||||
>
|
||||
{remoteUserIds.map((userId) => (
|
||||
<Item key={userId}>{userId}</Item>
|
||||
@@ -498,7 +502,7 @@ export function GroupCallInspector({
|
||||
return (
|
||||
<Resizable
|
||||
enable={{ top: true }}
|
||||
defaultSize={{ height: 200, width: undefined }}
|
||||
defaultSize={{ height: 200, width: 0 }}
|
||||
className={styles.inspector}
|
||||
>
|
||||
<div className={styles.toolbar}>
|
||||
@@ -507,15 +511,19 @@ export function GroupCallInspector({
|
||||
</button>
|
||||
<button onClick={() => setCurrentTab("inspector")}>Inspector</button>
|
||||
</div>
|
||||
{currentTab === "sequence-diagrams" && (
|
||||
<SequenceDiagramViewer
|
||||
localUserId={state.localUserId}
|
||||
selectedUserId={selectedUserId}
|
||||
onSelectUserId={setSelectedUserId}
|
||||
remoteUserIds={state.remoteUserIds}
|
||||
events={state.eventsByUserId[selectedUserId]}
|
||||
/>
|
||||
)}
|
||||
{currentTab === "sequence-diagrams" &&
|
||||
state.localUserId &&
|
||||
selectedUserId &&
|
||||
state.eventsByUserId &&
|
||||
state.remoteUserIds && (
|
||||
<SequenceDiagramViewer
|
||||
localUserId={state.localUserId}
|
||||
selectedUserId={selectedUserId}
|
||||
onSelectUserId={setSelectedUserId}
|
||||
remoteUserIds={state.remoteUserIds}
|
||||
events={state.eventsByUserId[selectedUserId]}
|
||||
/>
|
||||
)}
|
||||
{currentTab === "inspector" && (
|
||||
<ReactJson
|
||||
theme="monokai"
|
||||
|
||||
@@ -60,5 +60,5 @@ export function GroupCallLoader({
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
return <>{children(groupCall)}</>;
|
||||
return groupCall ? <>{children(groupCall)}</> : <></>;
|
||||
}
|
||||
|
||||
@@ -85,8 +85,8 @@ export function GroupCallView({
|
||||
const { displayName, avatarUrl } = useProfile(client);
|
||||
|
||||
const matrixInfo: MatrixInfo = {
|
||||
displayName,
|
||||
avatarUrl,
|
||||
displayName: displayName!,
|
||||
avatarUrl: avatarUrl!,
|
||||
roomName: groupCall.room.name,
|
||||
roomIdOrAlias,
|
||||
};
|
||||
@@ -140,14 +140,14 @@ export function GroupCallView({
|
||||
PosthogAnalytics.instance.eventCallStarted.track(groupCall.groupCallId);
|
||||
|
||||
await Promise.all([
|
||||
widget.api.setAlwaysOnScreen(true),
|
||||
widget.api.transport.reply(ev.detail, {}),
|
||||
widget?.api.setAlwaysOnScreen(true),
|
||||
widget?.api.transport.reply(ev.detail, {}),
|
||||
]);
|
||||
};
|
||||
|
||||
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
||||
return () => {
|
||||
widget.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
widget?.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
};
|
||||
}
|
||||
}, [groupCall, preload, enter]);
|
||||
@@ -206,12 +206,12 @@ export function GroupCallView({
|
||||
if (widget && state === GroupCallState.Entered) {
|
||||
const onHangup = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
leave();
|
||||
await widget.api.transport.reply(ev.detail, {});
|
||||
widget.api.setAlwaysOnScreen(false);
|
||||
await widget?.api.transport.reply(ev.detail, {});
|
||||
widget?.api.setAlwaysOnScreen(false);
|
||||
};
|
||||
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
||||
return () => {
|
||||
widget.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
||||
widget?.lazyActions.off(ElementWidgetActions.HangupCall, onHangup);
|
||||
};
|
||||
}
|
||||
}, [groupCall, state, leave]);
|
||||
@@ -222,7 +222,7 @@ export function GroupCallView({
|
||||
|
||||
const livekitServiceURL =
|
||||
groupCall.foci[0]?.livekitServiceUrl ??
|
||||
Config.get().livekit.livekit_service_url;
|
||||
Config.get().livekit?.livekit_service_url;
|
||||
if (!livekitServiceURL) {
|
||||
return <ErrorView error={new Error("No livekit_service_url defined")} />;
|
||||
}
|
||||
|
||||
@@ -71,14 +71,13 @@ import styles from "./InCallView.module.css";
|
||||
import { MatrixInfo } from "./VideoPreview";
|
||||
import { useJoinRule } from "./useJoinRule";
|
||||
import { ParticipantInfo } from "./useGroupCall";
|
||||
import { ItemData, TileContent } from "../video-grid/VideoTile";
|
||||
import { ItemData, TileContent, VideoTile } from "../video-grid/VideoTile";
|
||||
import { NewVideoGrid } from "../video-grid/NewVideoGrid";
|
||||
import { OTelGroupCallMembership } from "../otel/OTelGroupCallMembership";
|
||||
import { SettingsModal } from "../settings/SettingsModal";
|
||||
import { InviteModal } from "./InviteModal";
|
||||
import { useRageshakeRequestModal } from "../settings/submit-rageshake";
|
||||
import { RageshakeRequestModal } from "./RageshakeRequestModal";
|
||||
import { VideoTile } from "../video-grid/VideoTile";
|
||||
import { UserChoices, useLiveKit } from "../livekit/useLiveKit";
|
||||
import { useMediaDevicesSwitcher } from "../livekit/useMediaDevicesSwitcher";
|
||||
import { useFullscreen } from "./useFullscreen";
|
||||
@@ -100,12 +99,14 @@ export function ActiveCall(props: ActiveCallProps) {
|
||||
const sfuConfig = useSFUConfig();
|
||||
const livekitRoom = useLiveKit(props.userChoices, sfuConfig);
|
||||
|
||||
if (!livekitRoom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
livekitRoom && (
|
||||
<RoomContext.Provider value={livekitRoom}>
|
||||
<InCallView {...props} livekitRoom={livekitRoom} />
|
||||
</RoomContext.Provider>
|
||||
)
|
||||
<RoomContext.Provider value={livekitRoom}>
|
||||
<InCallView {...props} livekitRoom={livekitRoom} />
|
||||
</RoomContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ export interface InCallViewProps {
|
||||
unencryptedEventsFromUsers: Set<string>;
|
||||
hideHeader: boolean;
|
||||
matrixInfo: MatrixInfo;
|
||||
otelGroupCallMembership: OTelGroupCallMembership;
|
||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||
}
|
||||
|
||||
export function InCallView({
|
||||
@@ -203,11 +204,11 @@ export function InCallView({
|
||||
if (widget) {
|
||||
const onTileLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
setLayout("freedom");
|
||||
await widget.api.transport.reply(ev.detail, {});
|
||||
await widget?.api.transport.reply(ev.detail, {});
|
||||
};
|
||||
const onSpotlightLayout = async (ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
setLayout("spotlight");
|
||||
await widget.api.transport.reply(ev.detail, {});
|
||||
await widget?.api.transport.reply(ev.detail, {});
|
||||
};
|
||||
|
||||
widget.lazyActions.on(ElementWidgetActions.TileLayout, onTileLayout);
|
||||
@@ -217,8 +218,8 @@ export function InCallView({
|
||||
);
|
||||
|
||||
return () => {
|
||||
widget.lazyActions.off(ElementWidgetActions.TileLayout, onTileLayout);
|
||||
widget.lazyActions.off(
|
||||
widget?.lazyActions.off(ElementWidgetActions.TileLayout, onTileLayout);
|
||||
widget?.lazyActions.off(
|
||||
ElementWidgetActions.SpotlightLayout,
|
||||
onSpotlightLayout
|
||||
);
|
||||
@@ -416,12 +417,14 @@ export function InCallView({
|
||||
{renderContent()}
|
||||
{footer}
|
||||
</div>
|
||||
<GroupCallInspector
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
show={showInspector}
|
||||
/>
|
||||
{otelGroupCallMembership && (
|
||||
<GroupCallInspector
|
||||
client={client}
|
||||
groupCall={groupCall}
|
||||
otelGroupCallMembership={otelGroupCallMembership}
|
||||
show={showInspector}
|
||||
/>
|
||||
)}
|
||||
{rageshakeRequestModalState.isOpen && !noControls && (
|
||||
<RageshakeRequestModal
|
||||
{...rageshakeRequestModalProps}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function LobbyView(props: Props) {
|
||||
const { t } = useTranslation();
|
||||
useLocationNavigation();
|
||||
|
||||
const joinCallButtonRef = useRef<HTMLButtonElement>();
|
||||
const joinCallButtonRef = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
if (joinCallButtonRef.current) {
|
||||
joinCallButtonRef.current.focus();
|
||||
|
||||
@@ -36,6 +36,8 @@ export function RoomAuthView() {
|
||||
useRegisterPasswordlessUser();
|
||||
|
||||
const onSubmit = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { FC, useEffect, useState, useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||
import { useClient } from "../ClientContext";
|
||||
import { useClientLegacy } from "../ClientContext";
|
||||
import { ErrorView, LoadingView } from "../FullScreenView";
|
||||
import { RoomAuthView } from "./RoomAuthView";
|
||||
import { GroupCallLoader } from "./GroupCallLoader";
|
||||
@@ -30,8 +30,6 @@ import { useOptInAnalytics } from "../settings/useSetting";
|
||||
|
||||
export const RoomPage: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { loading, isAuthenticated, error, client, isPasswordlessUser } =
|
||||
useClient();
|
||||
|
||||
const {
|
||||
roomAlias,
|
||||
@@ -52,39 +50,42 @@ export const RoomPage: FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
// During the beta, opt into analytics by default
|
||||
if (optInAnalytics === null) setOptInAnalytics(true);
|
||||
if (optInAnalytics === null && setOptInAnalytics) setOptInAnalytics(true);
|
||||
}, [optInAnalytics, setOptInAnalytics]);
|
||||
|
||||
const { loading, authenticated, client, error, passwordlessUser } =
|
||||
useClientLegacy();
|
||||
|
||||
useEffect(() => {
|
||||
// If we've finished loading, are not already authed and we've been given a display name as
|
||||
// a URL param, automatically register a passwordless user
|
||||
if (!loading && !isAuthenticated && displayName) {
|
||||
if (!loading && !authenticated && displayName) {
|
||||
setIsRegistering(true);
|
||||
registerPasswordlessUser(displayName).finally(() => {
|
||||
setIsRegistering(false);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
isAuthenticated,
|
||||
loading,
|
||||
authenticated,
|
||||
displayName,
|
||||
setIsRegistering,
|
||||
registerPasswordlessUser,
|
||||
loading,
|
||||
]);
|
||||
|
||||
const groupCallView = useCallback(
|
||||
(groupCall: GroupCall) => (
|
||||
<GroupCallView
|
||||
client={client}
|
||||
client={client!}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
groupCall={groupCall}
|
||||
isPasswordlessUser={isPasswordlessUser}
|
||||
isPasswordlessUser={passwordlessUser}
|
||||
isEmbedded={isEmbedded}
|
||||
preload={preload}
|
||||
hideHeader={hideHeader}
|
||||
/>
|
||||
),
|
||||
[client, roomIdOrAlias, isPasswordlessUser, isEmbedded, preload, hideHeader]
|
||||
[client, roomIdOrAlias, passwordlessUser, isEmbedded, preload, hideHeader]
|
||||
);
|
||||
|
||||
if (loading || isRegistering) {
|
||||
@@ -95,7 +96,7 @@ export const RoomPage: FC = () => {
|
||||
return <ErrorView error={error} />;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
if (!client) {
|
||||
return <RoomAuthView />;
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ export function VideoPreview({ matrixInfo, onUserChoicesChanged }: Props) {
|
||||
<SettingsButton onPress={openSettings} />
|
||||
</div>
|
||||
</>
|
||||
{settingsModalState.isOpen && (
|
||||
{settingsModalState.isOpen && client && (
|
||||
<SettingsModal
|
||||
client={client}
|
||||
mediaDevicesSwitcher={mediaSwitcher}
|
||||
|
||||
@@ -58,12 +58,12 @@ export interface ParticipantInfo {
|
||||
|
||||
interface UseGroupCallReturnType {
|
||||
state: GroupCallState;
|
||||
localCallFeed: CallFeed;
|
||||
activeSpeaker: CallFeed | null;
|
||||
localCallFeed?: CallFeed;
|
||||
activeSpeaker?: CallFeed;
|
||||
userMediaFeeds: CallFeed[];
|
||||
microphoneMuted: boolean;
|
||||
localVideoMuted: boolean;
|
||||
error: TranslatedError | null;
|
||||
error?: TranslatedError;
|
||||
initLocalCallFeed: () => void;
|
||||
enter: () => Promise<void>;
|
||||
leave: () => void;
|
||||
@@ -74,23 +74,21 @@ interface UseGroupCallReturnType {
|
||||
requestingScreenshare: boolean;
|
||||
isScreensharing: boolean;
|
||||
screenshareFeeds: CallFeed[];
|
||||
localDesktopCapturerSourceId: string; // XXX: This looks unused?
|
||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||
hasLocalParticipant: boolean;
|
||||
unencryptedEventsFromUsers: Set<string>;
|
||||
otelGroupCallMembership: OTelGroupCallMembership;
|
||||
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||
}
|
||||
|
||||
interface State {
|
||||
state: GroupCallState;
|
||||
localCallFeed: CallFeed;
|
||||
activeSpeaker: CallFeed | null;
|
||||
localCallFeed?: CallFeed;
|
||||
activeSpeaker?: CallFeed;
|
||||
userMediaFeeds: CallFeed[];
|
||||
error: TranslatedError | null;
|
||||
error?: TranslatedError;
|
||||
microphoneMuted: boolean;
|
||||
localVideoMuted: boolean;
|
||||
screenshareFeeds: CallFeed[];
|
||||
localDesktopCapturerSourceId: string;
|
||||
isScreensharing: boolean;
|
||||
requestingScreenshare: boolean;
|
||||
participants: Map<RoomMember, Map<string, ParticipantInfo>>;
|
||||
@@ -101,7 +99,7 @@ interface State {
|
||||
// level so that it doesn't pop in & out of existence as react mounts & unmounts
|
||||
// components. The right solution is probably for this to live in the js-sdk and have
|
||||
// the same lifetime as groupcalls themselves.
|
||||
let groupCallOTelMembership: OTelGroupCallMembership;
|
||||
let groupCallOTelMembership: OTelGroupCallMembership | undefined;
|
||||
let groupCallOTelMembershipGroupCallId: string;
|
||||
|
||||
function getParticipants(
|
||||
@@ -159,7 +157,6 @@ export function useGroupCall(
|
||||
localVideoMuted,
|
||||
isScreensharing,
|
||||
screenshareFeeds,
|
||||
localDesktopCapturerSourceId,
|
||||
participants,
|
||||
hasLocalParticipant,
|
||||
requestingScreenshare,
|
||||
@@ -167,15 +164,11 @@ export function useGroupCall(
|
||||
setState,
|
||||
] = useState<State>({
|
||||
state: GroupCallState.LocalCallFeedUninitialized,
|
||||
localCallFeed: null,
|
||||
activeSpeaker: null,
|
||||
userMediaFeeds: [],
|
||||
error: null,
|
||||
microphoneMuted: false,
|
||||
localVideoMuted: false,
|
||||
isScreensharing: false,
|
||||
screenshareFeeds: [],
|
||||
localDesktopCapturerSourceId: null,
|
||||
requestingScreenshare: false,
|
||||
participants: new Map(),
|
||||
hasLocalParticipant: false,
|
||||
@@ -248,12 +241,11 @@ export function useGroupCall(
|
||||
updateState({
|
||||
state: groupCall.state,
|
||||
localCallFeed: groupCall.localCallFeed,
|
||||
activeSpeaker: groupCall.activeSpeaker ?? null,
|
||||
activeSpeaker: groupCall.activeSpeaker,
|
||||
userMediaFeeds: [...groupCall.userMediaFeeds],
|
||||
microphoneMuted: groupCall.isMicrophoneMuted(),
|
||||
localVideoMuted: groupCall.isLocalVideoMuted(),
|
||||
isScreensharing: groupCall.isScreensharing(),
|
||||
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
||||
screenshareFeeds: [...groupCall.screenshareFeeds],
|
||||
});
|
||||
}
|
||||
@@ -303,7 +295,7 @@ export function useGroupCall(
|
||||
|
||||
function onActiveSpeakerChanged(activeSpeaker: CallFeed | undefined): void {
|
||||
updateState({
|
||||
activeSpeaker: activeSpeaker ?? null,
|
||||
activeSpeaker: activeSpeaker,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -319,12 +311,11 @@ export function useGroupCall(
|
||||
|
||||
function onLocalScreenshareStateChanged(
|
||||
isScreensharing: boolean,
|
||||
_localScreenshareFeed: CallFeed,
|
||||
localDesktopCapturerSourceId: string
|
||||
_localScreenshareFeed?: CallFeed,
|
||||
localDesktopCapturerSourceId?: string
|
||||
): void {
|
||||
updateState({
|
||||
isScreensharing,
|
||||
localDesktopCapturerSourceId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -405,15 +396,14 @@ export function useGroupCall(
|
||||
);
|
||||
|
||||
updateState({
|
||||
error: null,
|
||||
error: undefined,
|
||||
state: groupCall.state,
|
||||
localCallFeed: groupCall.localCallFeed,
|
||||
activeSpeaker: groupCall.activeSpeaker ?? null,
|
||||
activeSpeaker: groupCall.activeSpeaker,
|
||||
userMediaFeeds: [...groupCall.userMediaFeeds],
|
||||
microphoneMuted: groupCall.isMicrophoneMuted(),
|
||||
localVideoMuted: groupCall.isLocalVideoMuted(),
|
||||
isScreensharing: groupCall.isScreensharing(),
|
||||
localDesktopCapturerSourceId: groupCall.localDesktopCapturerSourceId,
|
||||
screenshareFeeds: [...groupCall.screenshareFeeds],
|
||||
participants: getParticipants(groupCall),
|
||||
hasLocalParticipant: groupCall.hasLocalParticipant(),
|
||||
@@ -516,7 +506,7 @@ export function useGroupCall(
|
||||
}, [groupCall]);
|
||||
|
||||
const setMicrophoneMuted = useCallback(
|
||||
(setMuted) => {
|
||||
(setMuted: boolean) => {
|
||||
groupCall.setMicrophoneMuted(setMuted);
|
||||
groupCallOTelMembership?.onSetMicrophoneMuted(setMuted);
|
||||
PosthogAnalytics.instance.eventMuteMicrophone.track(
|
||||
@@ -575,7 +565,7 @@ export function useGroupCall(
|
||||
desktopCapturerSourceId: data.desktopCapturerSourceId as string,
|
||||
audio: !data.desktopCapturerSourceId,
|
||||
});
|
||||
await widget.api.transport.reply(ev.detail, {});
|
||||
await widget?.api.transport.reply(ev.detail, {});
|
||||
},
|
||||
[groupCall, updateState]
|
||||
);
|
||||
@@ -584,7 +574,7 @@ export function useGroupCall(
|
||||
async (ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
updateState({ requestingScreenshare: false });
|
||||
await groupCall.setScreensharingEnabled(false);
|
||||
await widget.api.transport.reply(ev.detail, {});
|
||||
await widget?.api.transport.reply(ev.detail, {});
|
||||
},
|
||||
[groupCall, updateState]
|
||||
);
|
||||
@@ -601,11 +591,11 @@ export function useGroupCall(
|
||||
);
|
||||
|
||||
return () => {
|
||||
widget.lazyActions.off(
|
||||
widget?.lazyActions.off(
|
||||
ElementWidgetActions.ScreenshareStart,
|
||||
onScreenshareStart
|
||||
);
|
||||
widget.lazyActions.off(
|
||||
widget?.lazyActions.off(
|
||||
ElementWidgetActions.ScreenshareStop,
|
||||
onScreenshareStop
|
||||
);
|
||||
@@ -644,7 +634,6 @@ export function useGroupCall(
|
||||
requestingScreenshare,
|
||||
isScreensharing,
|
||||
screenshareFeeds,
|
||||
localDesktopCapturerSourceId,
|
||||
participants,
|
||||
hasLocalParticipant,
|
||||
unencryptedEventsFromUsers,
|
||||
|
||||
@@ -74,8 +74,14 @@ export const useLoadGroupCall = (
|
||||
} catch (error) {
|
||||
if (
|
||||
isLocalRoomId(roomIdOrAlias, client) &&
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(error.errcode === "M_NOT_FOUND" ||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(error.message &&
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
error.message.indexOf("Failed to fetch alias") !== -1))
|
||||
) {
|
||||
// The room doesn't exist, but we can create it
|
||||
@@ -86,7 +92,7 @@ export const useLoadGroupCall = (
|
||||
);
|
||||
// likewise, wait for the room
|
||||
await client.waitUntilRoomReadyForGroupCalls(roomId);
|
||||
return client.getRoom(roomId);
|
||||
return client.getRoom(roomId)!;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -55,14 +55,22 @@ export function usePageUnload(callback: () => void) {
|
||||
// iOS doesn't fire beforeunload event, so leave the call when you hide the page.
|
||||
if (isIOS()) {
|
||||
window.addEventListener("pagehide", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
document.addEventListener("visibilitychange", onBeforeUnload);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("pagehide", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
document.removeEventListener("visibilitychange", onBeforeUnload);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
clearTimeout(pageVisibilityTimeout);
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ export function FeedbackSettingsTab({ roomId }: Props) {
|
||||
const sendRageshakeRequest = useRageshakeRequest();
|
||||
|
||||
const onSubmitFeedback = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.target);
|
||||
|
||||
@@ -59,8 +59,14 @@ export function ProfileSettingsTab({ client }: Props) {
|
||||
? displayNameDataEntry
|
||||
: displayNameDataEntry?.name ?? null;
|
||||
|
||||
if (!displayName) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveProfile({
|
||||
displayName,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
avatar: avatar && avatarSize > 0 ? avatar : undefined,
|
||||
removeAvatar: removeAvatar.current && (!avatar || avatarSize === 0),
|
||||
});
|
||||
@@ -71,14 +77,16 @@ export function ProfileSettingsTab({ client }: Props) {
|
||||
return (
|
||||
<form onChange={onFormChange} ref={formRef} className={styles.content}>
|
||||
<FieldRow className={styles.avatarFieldRow}>
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
label={t("Avatar")}
|
||||
avatarUrl={avatarUrl}
|
||||
displayName={displayName}
|
||||
onRemoveAvatar={onRemoveAvatar}
|
||||
/>
|
||||
{avatarUrl && displayName && (
|
||||
<AvatarInputField
|
||||
id="avatar"
|
||||
name="avatar"
|
||||
label={t("Avatar")}
|
||||
avatarUrl={avatarUrl}
|
||||
displayName={displayName}
|
||||
onRemoveAvatar={onRemoveAvatar}
|
||||
/>
|
||||
)}
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChangeEvent, useCallback, useState } from "react";
|
||||
import { ChangeEvent, Key, useCallback, useState } from "react";
|
||||
import { Item } from "@react-stately/collections";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { MatrixClient } from "matrix-js-sdk";
|
||||
@@ -99,8 +99,8 @@ export const SettingsModal = (props: Props) => {
|
||||
const [selectedTab, setSelectedTab] = useState<string | undefined>();
|
||||
|
||||
const onSelectedTabChanged = useCallback(
|
||||
(tab) => {
|
||||
setSelectedTab(tab);
|
||||
(tab: Key) => {
|
||||
setSelectedTab(tab.toString());
|
||||
},
|
||||
[setSelectedTab]
|
||||
);
|
||||
@@ -118,6 +118,144 @@ export const SettingsModal = (props: Props) => {
|
||||
|
||||
const devices = props.mediaDevicesSwitcher;
|
||||
|
||||
const tabs = [
|
||||
<TabItem
|
||||
key="audio"
|
||||
title={
|
||||
<>
|
||||
<AudioIcon width={16} height={16} />
|
||||
<span className={styles.tabLabel}>{t("Audio")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.audioIn, t("Microphone"))}
|
||||
{devices && generateDeviceSelection(devices.audioOut, t("Speaker"))}
|
||||
</TabItem>,
|
||||
<TabItem
|
||||
key="video"
|
||||
title={
|
||||
<>
|
||||
<VideoIcon width={16} height={16} />
|
||||
<span>{t("Video")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.videoIn, t("Camera"))}
|
||||
</TabItem>,
|
||||
<TabItem
|
||||
key="feedback"
|
||||
title={
|
||||
<>
|
||||
<FeedbackIcon width={16} height={16} />
|
||||
<span>{t("Feedback")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FeedbackSettingsTab roomId={props.roomId} />
|
||||
</TabItem>,
|
||||
<TabItem
|
||||
key="more"
|
||||
title={
|
||||
<>
|
||||
<OverflowIcon width={16} height={16} />
|
||||
<span>{t("More")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<h4>Developer</h4>
|
||||
<p>Version: {(import.meta.env.VITE_APP_VERSION as string) || "dev"}</p>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="developerSettingsTab"
|
||||
type="checkbox"
|
||||
checked={developerSettingsTab}
|
||||
label={t("Developer Settings")}
|
||||
description={t("Expose developer settings in the settings window.")}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setDeveloperSettingsTab(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<h4>Analytics</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics ?? undefined}
|
||||
description={optInDescription}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setOptInAnalytics?.(event.target.checked);
|
||||
}}
|
||||
/>
|
||||
</FieldRow>
|
||||
</TabItem>,
|
||||
];
|
||||
|
||||
if (!isEmbedded) {
|
||||
tabs.push(
|
||||
<TabItem
|
||||
key="profile"
|
||||
title={
|
||||
<>
|
||||
<UserIcon width={15} height={15} />
|
||||
<span>{t("Profile")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProfileSettingsTab client={props.client} />
|
||||
</TabItem>
|
||||
);
|
||||
}
|
||||
|
||||
if (developerSettingsTab) {
|
||||
tabs.push(
|
||||
<TabItem
|
||||
key="developer"
|
||||
title={
|
||||
<>
|
||||
<DeveloperIcon width={16} height={16} />
|
||||
<span>{t("Developer")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FieldRow>
|
||||
<Body className={styles.fieldRowText}>
|
||||
{t("Version: {{version}}", {
|
||||
version: import.meta.env.VITE_APP_VERSION || "dev",
|
||||
})}
|
||||
</Body>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showInspector"
|
||||
name="inspector"
|
||||
label={t("Show call inspector")}
|
||||
type="checkbox"
|
||||
checked={showInspector}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowInspector(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showConnectionStats"
|
||||
name="connection-stats"
|
||||
label={t("Show connection stats")}
|
||||
type="checkbox"
|
||||
checked={showConnectionStats}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowConnectionStats(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button onPress={downloadDebugLog}>{t("Download debug logs")}</Button>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("Settings")}
|
||||
@@ -131,141 +269,7 @@ export const SettingsModal = (props: Props) => {
|
||||
selectedKey={selectedTab ?? props.defaultTab ?? "audio"}
|
||||
className={styles.tabContainer}
|
||||
>
|
||||
<TabItem
|
||||
key="audio"
|
||||
title={
|
||||
<>
|
||||
<AudioIcon width={16} height={16} />
|
||||
<span className={styles.tabLabel}>{t("Audio")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.audioIn, t("Microphone"))}
|
||||
{devices && generateDeviceSelection(devices.audioOut, t("Speaker"))}
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="video"
|
||||
title={
|
||||
<>
|
||||
<VideoIcon width={16} height={16} />
|
||||
<span>{t("Video")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{devices && generateDeviceSelection(devices.videoIn, t("Camera"))}
|
||||
</TabItem>
|
||||
{!isEmbedded && (
|
||||
<TabItem
|
||||
key="profile"
|
||||
title={
|
||||
<>
|
||||
<UserIcon width={15} height={15} />
|
||||
<span>{t("Profile")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ProfileSettingsTab client={props.client} />
|
||||
</TabItem>
|
||||
)}
|
||||
<TabItem
|
||||
key="feedback"
|
||||
title={
|
||||
<>
|
||||
<FeedbackIcon width={16} height={16} />
|
||||
<span>{t("Feedback")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FeedbackSettingsTab roomId={props.roomId} />
|
||||
</TabItem>
|
||||
<TabItem
|
||||
key="more"
|
||||
title={
|
||||
<>
|
||||
<OverflowIcon width={16} height={16} />
|
||||
<span>{t("More")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<h4>Developer</h4>
|
||||
<p>
|
||||
Version: {(import.meta.env.VITE_APP_VERSION as string) || "dev"}
|
||||
</p>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="developerSettingsTab"
|
||||
type="checkbox"
|
||||
checked={developerSettingsTab}
|
||||
label={t("Developer Settings")}
|
||||
description={t(
|
||||
"Expose developer settings in the settings window."
|
||||
)}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setDeveloperSettingsTab(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<h4>Analytics</h4>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="optInAnalytics"
|
||||
type="checkbox"
|
||||
checked={optInAnalytics}
|
||||
description={optInDescription}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setOptInAnalytics(event.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
{developerSettingsTab && (
|
||||
<TabItem
|
||||
key="developer"
|
||||
title={
|
||||
<>
|
||||
<DeveloperIcon width={16} height={16} />
|
||||
<span>{t("Developer")}</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<FieldRow>
|
||||
<Body className={styles.fieldRowText}>
|
||||
{t("Version: {{version}}", {
|
||||
version: import.meta.env.VITE_APP_VERSION || "dev",
|
||||
})}
|
||||
</Body>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showInspector"
|
||||
name="inspector"
|
||||
label={t("Show call inspector")}
|
||||
type="checkbox"
|
||||
checked={showInspector}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowInspector(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<InputField
|
||||
id="showConnectionStats"
|
||||
name="connection-stats"
|
||||
label={t("Show connection stats")}
|
||||
type="checkbox"
|
||||
checked={showConnectionStats}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setShowConnectionStats(e.target.checked)
|
||||
}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow>
|
||||
<Button onPress={downloadDebugLog}>
|
||||
{t("Download debug logs")}
|
||||
</Button>
|
||||
</FieldRow>
|
||||
</TabItem>
|
||||
)}
|
||||
{tabs}
|
||||
</TabContainer>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -79,11 +79,17 @@ class ConsoleLogger extends EventEmitter {
|
||||
warn: "W",
|
||||
error: "E",
|
||||
};
|
||||
Object.keys(consoleFunctionsToLevels).forEach((fnName) => {
|
||||
const level = consoleFunctionsToLevels[fnName];
|
||||
const originalFn = consoleObj[fnName].bind(consoleObj);
|
||||
this.originalFunctions[fnName] = originalFn;
|
||||
consoleObj[fnName] = (...args) => {
|
||||
|
||||
Object.entries(consoleFunctionsToLevels).forEach(([name, level]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const originalFn = consoleObj[name].bind(consoleObj);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this.originalFunctions[name] = originalFn;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
consoleObj[name] = (...args) => {
|
||||
this.log(level, ...args);
|
||||
originalFn(...args);
|
||||
};
|
||||
@@ -147,9 +153,9 @@ class ConsoleLogger extends EventEmitter {
|
||||
// A class which stores log lines in an IndexedDB instance.
|
||||
class IndexedDBLogStore {
|
||||
private index = 0;
|
||||
private db: IDBDatabase = null;
|
||||
private flushPromise: Promise<void> = null;
|
||||
private flushAgainPromise: Promise<void> = null;
|
||||
private db?: IDBDatabase;
|
||||
private flushPromise?: Promise<void>;
|
||||
private flushAgainPromise?: Promise<void>;
|
||||
private id: string;
|
||||
|
||||
constructor(
|
||||
@@ -175,7 +181,7 @@ class IndexedDBLogStore {
|
||||
};
|
||||
|
||||
req.onerror = () => {
|
||||
const err = "Failed to open log database: " + req.error.name;
|
||||
const err = "Failed to open log database: " + req?.error?.name;
|
||||
logger.error(err);
|
||||
reject(new Error(err));
|
||||
};
|
||||
@@ -264,7 +270,7 @@ class IndexedDBLogStore {
|
||||
return this.flush();
|
||||
})
|
||||
.then(() => {
|
||||
this.flushAgainPromise = null;
|
||||
this.flushAgainPromise = undefined;
|
||||
});
|
||||
return this.flushAgainPromise;
|
||||
}
|
||||
@@ -288,13 +294,13 @@ class IndexedDBLogStore {
|
||||
};
|
||||
txn.onerror = (event) => {
|
||||
logger.error("Failed to flush logs : ", event);
|
||||
reject(new Error("Failed to write logs: " + txn.error.message));
|
||||
reject(new Error("Failed to write logs: " + txn?.error?.message));
|
||||
};
|
||||
objStore.add(this.generateLogEntry(lines));
|
||||
const lastModStore = txn.objectStore("logslastmod");
|
||||
lastModStore.put(this.generateLastModifiedTime());
|
||||
}).then(() => {
|
||||
this.flushPromise = null;
|
||||
this.flushPromise = undefined;
|
||||
});
|
||||
return this.flushPromise;
|
||||
};
|
||||
@@ -311,11 +317,14 @@ class IndexedDBLogStore {
|
||||
*/
|
||||
public async consume(): Promise<LogEntry[]> {
|
||||
const db = this.db;
|
||||
if (!db) {
|
||||
return Promise.reject(new Error("No connected database"));
|
||||
}
|
||||
|
||||
// Returns: a string representing the concatenated logs for this ID.
|
||||
// Stops adding log fragments when the size exceeds maxSize
|
||||
function fetchLogs(id: string, maxSize: number): Promise<string> {
|
||||
const objectStore = db
|
||||
const objectStore = db!
|
||||
.transaction("logs", "readonly")
|
||||
.objectStore("logs");
|
||||
|
||||
@@ -325,7 +334,7 @@ class IndexedDBLogStore {
|
||||
.openCursor(IDBKeyRange.only(id), "prev");
|
||||
let lines = "";
|
||||
query.onerror = () => {
|
||||
reject(new Error("Query failed: " + query.error.message));
|
||||
reject(new Error("Query failed: " + query?.error?.message));
|
||||
};
|
||||
query.onsuccess = () => {
|
||||
const cursor = query.result;
|
||||
@@ -346,7 +355,7 @@ class IndexedDBLogStore {
|
||||
// Returns: A sorted array of log IDs. (newest first)
|
||||
function fetchLogIds(): Promise<string[]> {
|
||||
// To gather all the log IDs, query for all records in logslastmod.
|
||||
const o = db
|
||||
const o = db!
|
||||
.transaction("logslastmod", "readonly")
|
||||
.objectStore("logslastmod");
|
||||
return selectQuery<{ ts: number; id: string }>(o, undefined, (cursor) => {
|
||||
@@ -366,7 +375,7 @@ class IndexedDBLogStore {
|
||||
|
||||
function deleteLogs(id: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const txn = db.transaction(["logs", "logslastmod"], "readwrite");
|
||||
const txn = db!.transaction(["logs", "logslastmod"], "readwrite");
|
||||
const o = txn.objectStore("logs");
|
||||
// only load the key path, not the data which may be huge
|
||||
const query = o.index("id").openKeyCursor(IDBKeyRange.only(id));
|
||||
@@ -384,7 +393,7 @@ class IndexedDBLogStore {
|
||||
txn.onerror = () => {
|
||||
reject(
|
||||
new Error(
|
||||
"Failed to delete logs for " + `'${id}' : ${txn.error.message}`
|
||||
"Failed to delete logs for " + `'${id}' : ${txn?.error?.message}`
|
||||
)
|
||||
);
|
||||
};
|
||||
@@ -395,7 +404,7 @@ class IndexedDBLogStore {
|
||||
}
|
||||
|
||||
const allLogIds = await fetchLogIds();
|
||||
let removeLogIds = [];
|
||||
let removeLogIds: number[] = [];
|
||||
const logs: LogEntry[] = [];
|
||||
let size = 0;
|
||||
for (let i = 0; i < allLogIds.length; i++) {
|
||||
@@ -414,7 +423,7 @@ class IndexedDBLogStore {
|
||||
if (size >= MAX_LOG_SIZE) {
|
||||
// the remaining log IDs should be removed. If we go out of
|
||||
// bounds this is just []
|
||||
removeLogIds = allLogIds.slice(i + 1);
|
||||
removeLogIds = allLogIds.slice(i + 1).map((id) => parseInt(id, 10));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -462,14 +471,14 @@ class IndexedDBLogStore {
|
||||
*/
|
||||
function selectQuery<T>(
|
||||
store: IDBObjectStore,
|
||||
keyRange: IDBKeyRange,
|
||||
keyRange: IDBKeyRange | undefined,
|
||||
resultMapper: (cursor: IDBCursorWithValue) => T
|
||||
): Promise<T[]> {
|
||||
const query = store.openCursor(keyRange);
|
||||
return new Promise((resolve, reject) => {
|
||||
const results = [];
|
||||
const results: T[] = [];
|
||||
query.onerror = () => {
|
||||
reject(new Error("Query failed: " + query.error.message));
|
||||
reject(new Error("Query failed: " + query?.error?.message));
|
||||
};
|
||||
// collect results
|
||||
query.onsuccess = () => {
|
||||
|
||||
@@ -15,10 +15,12 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import pako from "pako";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { OverlayTriggerState } from "@react-stately/overlays";
|
||||
import { MatrixClient, ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import { useClient } from "../ClientContext";
|
||||
@@ -46,20 +48,27 @@ export function useSubmitRageshake(): {
|
||||
submitRageshake: (opts: RageShakeSubmitOptions) => Promise<void>;
|
||||
sending: boolean;
|
||||
sent: boolean;
|
||||
error: Error;
|
||||
error?: Error;
|
||||
} {
|
||||
const client: MatrixClient = useClient().client;
|
||||
const { client } = useClient();
|
||||
|
||||
// The value of the context is the whole tuple returned from setState,
|
||||
// so we just want the current state.
|
||||
const [inspectorState] = useContext(InspectorContext);
|
||||
|
||||
const [{ sending, sent, error }, setState] = useState({
|
||||
const [{ sending, sent, error }, setState] = useState<{
|
||||
sending: boolean;
|
||||
sent: boolean;
|
||||
error?: Error;
|
||||
}>({
|
||||
sending: false,
|
||||
sent: false,
|
||||
error: null,
|
||||
error: undefined,
|
||||
});
|
||||
|
||||
const submitRageshake = useCallback(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
async (opts) => {
|
||||
if (!Config.get().rageshake?.submit_url) {
|
||||
throw new Error("No rageshake URL is configured");
|
||||
@@ -70,7 +79,7 @@ export function useSubmitRageshake(): {
|
||||
}
|
||||
|
||||
try {
|
||||
setState({ sending: true, sent: false, error: null });
|
||||
setState({ sending: true, sent: false, error: undefined });
|
||||
|
||||
let userAgent = "UNKNOWN";
|
||||
if (window.navigator && window.navigator.userAgent) {
|
||||
@@ -104,11 +113,11 @@ export function useSubmitRageshake(): {
|
||||
body.append("call_backend", "livekit");
|
||||
|
||||
if (client) {
|
||||
const userId = client.getUserId();
|
||||
const userId = client.getUserId()!;
|
||||
const user = client.getUser(userId);
|
||||
body.append("display_name", user?.displayName);
|
||||
body.append("user_id", client.credentials.userId);
|
||||
body.append("device_id", client.deviceId);
|
||||
body.append("display_name", user?.displayName ?? "");
|
||||
body.append("user_id", client.credentials.userId ?? "");
|
||||
body.append("device_id", client.deviceId ?? "");
|
||||
|
||||
if (opts.roomId) {
|
||||
body.append("room_id", opts.roomId);
|
||||
@@ -120,11 +129,11 @@ export function useSubmitRageshake(): {
|
||||
keys.push(`curve25519:${client.getDeviceCurve25519Key()}`);
|
||||
}
|
||||
body.append("device_keys", keys.join(", "));
|
||||
body.append("cross_signing_key", client.getCrossSigningId());
|
||||
body.append("cross_signing_key", client.getCrossSigningId()!);
|
||||
|
||||
// add cross-signing status information
|
||||
const crossSigning = client.crypto.crossSigningInfo;
|
||||
const secretStorage = client.crypto.secretStorage;
|
||||
const crossSigning = client.crypto!.crossSigningInfo;
|
||||
const secretStorage = client.crypto!.secretStorage;
|
||||
|
||||
body.append(
|
||||
"cross_signing_ready",
|
||||
@@ -138,7 +147,7 @@ export function useSubmitRageshake(): {
|
||||
)
|
||||
)
|
||||
);
|
||||
body.append("cross_signing_key", crossSigning.getId());
|
||||
body.append("cross_signing_key", crossSigning.getId()!);
|
||||
body.append(
|
||||
"cross_signing_privkey_in_secret_storage",
|
||||
String(
|
||||
@@ -150,14 +159,17 @@ export function useSubmitRageshake(): {
|
||||
body.append(
|
||||
"cross_signing_master_privkey_cached",
|
||||
String(
|
||||
!!(pkCache && (await pkCache.getCrossSigningKeyCache("master")))
|
||||
!!(
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("master"))
|
||||
)
|
||||
)
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_self_signing_privkey_cached",
|
||||
String(
|
||||
!!(
|
||||
pkCache &&
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("self_signing"))
|
||||
)
|
||||
)
|
||||
@@ -166,7 +178,7 @@ export function useSubmitRageshake(): {
|
||||
"cross_signing_user_signing_privkey_cached",
|
||||
String(
|
||||
!!(
|
||||
pkCache &&
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("user_signing"))
|
||||
)
|
||||
)
|
||||
@@ -186,7 +198,7 @@ export function useSubmitRageshake(): {
|
||||
String(!!(await client.isKeyBackupKeyStored()))
|
||||
);
|
||||
const sessionBackupKeyFromCache =
|
||||
await client.crypto.getSessionBackupPrivateKey();
|
||||
await client.crypto!.getSessionBackupPrivateKey();
|
||||
body.append(
|
||||
"session_backup_key_cached",
|
||||
String(!!sessionBackupKeyFromCache)
|
||||
@@ -233,7 +245,7 @@ export function useSubmitRageshake(): {
|
||||
Object.keys(estimate.usageDetails).forEach((k) => {
|
||||
body.append(
|
||||
`storageManager_usage_${k}`,
|
||||
String(estimate.usageDetails[k])
|
||||
String(estimate.usageDetails![k])
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -271,14 +283,14 @@ export function useSubmitRageshake(): {
|
||||
);
|
||||
}
|
||||
|
||||
await fetch(Config.get().rageshake?.submit_url, {
|
||||
await fetch(Config.get().rageshake!.submit_url, {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
|
||||
setState({ sending: false, sent: true, error: null });
|
||||
setState({ sending: false, sent: true, error: undefined });
|
||||
} catch (error) {
|
||||
setState({ sending: false, sent: false, error });
|
||||
setState({ sending: false, sent: false, error: error as Error });
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
@@ -307,7 +319,7 @@ export function useDownloadDebugLog(): () => void {
|
||||
el.click();
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url);
|
||||
el.parentNode.removeChild(el);
|
||||
el.parentNode!.removeChild(el);
|
||||
}, 0);
|
||||
}, [json]);
|
||||
|
||||
@@ -321,8 +333,8 @@ export function useRageshakeRequest(): (
|
||||
const { client } = useClient();
|
||||
|
||||
const sendRageshakeRequest = useCallback(
|
||||
(roomId, rageshakeRequestId) => {
|
||||
client.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||
(roomId: string, rageshakeRequestId: string) => {
|
||||
client!.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||
request_id: rageshakeRequestId,
|
||||
});
|
||||
},
|
||||
@@ -347,10 +359,12 @@ export function useRageshakeRequestModal(roomId: string): {
|
||||
modalState: OverlayTriggerState;
|
||||
modalProps: ModalProps;
|
||||
};
|
||||
const client: MatrixClient = useClient().client;
|
||||
const { client } = useClient();
|
||||
const [rageshakeRequestId, setRageshakeRequestId] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!client) return;
|
||||
|
||||
const onEvent = (event: MatrixEvent) => {
|
||||
const type = event.getType();
|
||||
|
||||
@@ -371,5 +385,8 @@ export function useRageshakeRequestModal(roomId: string): {
|
||||
};
|
||||
}, [modalState.open, roomId, client, modalState]);
|
||||
|
||||
return { modalState, modalProps: { ...modalProps, rageshakeRequestId } };
|
||||
return {
|
||||
modalState,
|
||||
modalProps: { ...modalProps, rageshakeRequestId: rageshakeRequestId ?? "" },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function TabContainer<T extends object>(
|
||||
props: TabContainerProps<T>
|
||||
): JSX.Element {
|
||||
const state = useTabListState<T>(props);
|
||||
const ref = useRef<HTMLUListElement>();
|
||||
const ref = useRef<HTMLUListElement>(null);
|
||||
const { tabListProps } = useTabList(props, state, ref);
|
||||
return (
|
||||
<div className={classNames(styles.tabContainer, props.className)}>
|
||||
@@ -53,7 +53,7 @@ interface TabProps<T> {
|
||||
|
||||
function Tab<T>({ item, state }: TabProps<T>): JSX.Element {
|
||||
const { key, rendered } = item;
|
||||
const ref = useRef<HTMLLIElement>();
|
||||
const ref = useRef<HTMLLIElement>(null);
|
||||
const { tabProps } = useTab({ key }, state, ref);
|
||||
|
||||
return (
|
||||
@@ -75,7 +75,7 @@ interface TabPanelProps<T> extends AriaTabPanelProps {
|
||||
}
|
||||
|
||||
function TabPanel<T>({ state, ...props }: TabPanelProps<T>): JSX.Element {
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { tabPanelProps } = useTabPanel(props, state, ref);
|
||||
return (
|
||||
<div {...tabPanelProps} ref={ref} className={styles.tabPanel}>
|
||||
|
||||
@@ -46,7 +46,7 @@ export const Headline = forwardRef<HTMLHeadingElement, TypographyProps>(
|
||||
{
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -74,7 +74,7 @@ export const Title = forwardRef<HTMLHeadingElement, TypographyProps>(
|
||||
{
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -102,7 +102,7 @@ export const Subtitle = forwardRef<HTMLParagraphElement, TypographyProps>(
|
||||
{
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -130,7 +130,7 @@ export const Body = forwardRef<HTMLParagraphElement, TypographyProps>(
|
||||
{
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -159,7 +159,7 @@ export const Caption = forwardRef<HTMLParagraphElement, TypographyProps>(
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles.caption,
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -188,7 +188,7 @@ export const Micro = forwardRef<HTMLParagraphElement, TypographyProps>(
|
||||
...rest,
|
||||
className: classNames(
|
||||
styles.micro,
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
@@ -219,6 +219,8 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const Component: string | RouterLink = as || (to ? RouterLink : "a");
|
||||
let externalLinkProps: { href: string; target: string; rel: string };
|
||||
|
||||
@@ -233,12 +235,16 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
|
||||
return createElement(
|
||||
Component,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
...externalLinkProps,
|
||||
...rest,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
to: to,
|
||||
className: classNames(
|
||||
styles[color],
|
||||
styles[fontWeight],
|
||||
styles[fontWeight ?? ""],
|
||||
{ [styles.overflowEllipsis]: overflowEllipsis },
|
||||
className
|
||||
),
|
||||
|
||||
@@ -31,8 +31,13 @@ export const useEventTarget = <T extends Event>(
|
||||
) => {
|
||||
useEffect(() => {
|
||||
if (target) {
|
||||
target.addEventListener(eventType, listener, options);
|
||||
return () => target.removeEventListener(eventType, listener, options);
|
||||
target.addEventListener(eventType, listener as EventListener, options);
|
||||
return () =>
|
||||
target.removeEventListener(
|
||||
eventType,
|
||||
listener as EventListener,
|
||||
options
|
||||
);
|
||||
}
|
||||
}, [target, eventType, listener, options]);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,9 @@ export function useLocationNavigation(enabled = false): void {
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
let unblock;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
let unblock = undefined;
|
||||
|
||||
if (enabled) {
|
||||
unblock = history.block((tx) => {
|
||||
@@ -33,6 +35,8 @@ export function useLocationNavigation(enabled = false): void {
|
||||
}
|
||||
|
||||
return () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (unblock) {
|
||||
unblock();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export interface Grid {
|
||||
cells: Cell[];
|
||||
}
|
||||
|
||||
interface SparseGrid {
|
||||
export interface SparseGrid {
|
||||
columns: number;
|
||||
/**
|
||||
* The cells of the grid, in left-to-right top-to-bottom order.
|
||||
@@ -803,7 +803,7 @@ export function setTileSize<G extends Grid | SparseGrid>(
|
||||
|
||||
const result: SparseGrid = gridWithoutTile;
|
||||
placeTile(to, toEnd, result);
|
||||
return fillGaps(result, true, (i) => inArea(i, to, toEnd, g)) as G;
|
||||
return fillGaps(result, true, (i: number) => inArea(i, to, toEnd, g)) as G;
|
||||
} else if (toWidth >= fromWidth && toHeight >= fromHeight) {
|
||||
// The tile is growing, which might be able to happen in-place
|
||||
const to = findNearestCell(
|
||||
@@ -1054,7 +1054,8 @@ export const BigGrid: Layout<Grid> = {
|
||||
emptyState: { columns: 4, cells: [] },
|
||||
updateTiles,
|
||||
updateBounds,
|
||||
getTiles: <T,>(g) => g.cells.filter((c) => c.origin).map((c) => c!.item as T),
|
||||
getTiles: <T,>(g: Grid) =>
|
||||
g.cells.filter((c) => c.origin).map((c) => c!.item as T),
|
||||
canDragTile: () => true,
|
||||
dragTile,
|
||||
toggleFocus: cycleTileSize,
|
||||
|
||||
@@ -271,10 +271,19 @@ export function NewVideoGrid<T>({
|
||||
// gesture using the much more sensible ref-based method.
|
||||
const onTileDrag = (
|
||||
tileId: string,
|
||||
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
tap,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
initial: [initialX, initialY],
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
delta: [dx, dy],
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
last,
|
||||
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||
) => {
|
||||
@@ -325,6 +334,8 @@ export function NewVideoGrid<T>({
|
||||
const scrollOffset = useRef(0);
|
||||
|
||||
useScroll(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
({ xy: [, y], delta: [, dy] }) => {
|
||||
scrollOffset.current = y;
|
||||
|
||||
|
||||
@@ -695,7 +695,7 @@ function getSubGridPositions(
|
||||
// Calculates the number of possible tiles that can be displayed
|
||||
function displayedTileCount(
|
||||
layout: Layout,
|
||||
tileCount,
|
||||
tileCount: number,
|
||||
gridWidth: number,
|
||||
gridHeight: number
|
||||
): number {
|
||||
@@ -854,7 +854,7 @@ export function VideoGrid<T>({
|
||||
tilePositions: [],
|
||||
});
|
||||
const [scrollPosition, setScrollPosition] = useState<number>(0);
|
||||
const draggingTileRef = useRef<DragTileData>(null);
|
||||
const draggingTileRef = useRef<DragTileData | null>(null);
|
||||
const lastTappedRef = useRef<{ [index: Key]: number }>({});
|
||||
const lastLayoutRef = useRef<Layout>(layout);
|
||||
const isMounted = useIsMounted();
|
||||
@@ -1189,11 +1189,23 @@ export function VideoGrid<T>({
|
||||
const onTileDrag = (
|
||||
tileId: string,
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
active,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
xy,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
movement,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
tap,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
last,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
event,
|
||||
}: Parameters<Handler<"drag", EventTypes["drag"]>>[0]
|
||||
) => {
|
||||
@@ -1345,7 +1357,11 @@ export function VideoGrid<T>({
|
||||
|
||||
const bindGrid = useGesture(
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
onWheel: (e) => onGridGesture(e, true),
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
onDrag: (e) => onGridGesture(e, false),
|
||||
},
|
||||
{}
|
||||
|
||||
@@ -136,7 +136,7 @@ export const VideoTile = forwardRef<HTMLDivElement, Props>(
|
||||
<AudioButton
|
||||
key="localVolume"
|
||||
className={styles.button}
|
||||
volume={(sfuParticipant as RemoteParticipant).getVolume()}
|
||||
volume={(sfuParticipant as RemoteParticipant).getVolume() ?? 0}
|
||||
onPress={onOptionsPress}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ const LocalVolume: React.FC<LocalVolumeProps> = ({
|
||||
participant,
|
||||
}: LocalVolumeProps) => {
|
||||
const [localVolume, setLocalVolume] = useState<number>(
|
||||
participant.getVolume()
|
||||
participant.getVolume() ?? 0
|
||||
);
|
||||
|
||||
const onLocalVolumeChanged = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
@@ -102,7 +102,7 @@ describe("ObjectFlattener", () => {
|
||||
localCandidateType: "srfx",
|
||||
remoteCandidateType: "srfx",
|
||||
networkType: "ethernet",
|
||||
rtt: null,
|
||||
rtt: 0,
|
||||
},
|
||||
],
|
||||
audioConcealment: new Map([
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
fillGaps,
|
||||
forEachCellInArea,
|
||||
Grid,
|
||||
SparseGrid,
|
||||
resize,
|
||||
row,
|
||||
moveTile,
|
||||
@@ -339,7 +340,9 @@ function testAddItems(
|
||||
output: string
|
||||
): void {
|
||||
test(`addItems ${title}`, () => {
|
||||
expect(showGrid(addItems(items, mkGrid(input)))).toBe(output);
|
||||
expect(showGrid(addItems(items, mkGrid(input) as SparseGrid) as Grid)).toBe(
|
||||
output
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"esModuleInterop": true,
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["es2020", "dom", "dom.iterable"],
|
||||
"strict": false,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "typescript-strict-plugin",
|
||||
"paths": ["src"]
|
||||
}
|
||||
]
|
||||
|
||||
// From Matrix-JS-SDK
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"noEmitOnError": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"noUnusedLocals": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
|
||||
// TODO: Enable the following options later.
|
||||
// "forceConsistentCasingInFileNames": true,
|
||||
// "noFallthroughCasesInSwitch": true,
|
||||
// "noImplicitOverride": true,
|
||||
// "noImplicitReturns": true,
|
||||
// "noPropertyAccessFromIndexSignature": true,
|
||||
// "noUncheckedIndexedAccess": true,
|
||||
// "noUnusedParameters": true,
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.tsx",
|
||||
"./test/**/*.ts",
|
||||
"./test/**/*.tsx"
|
||||
]
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@@ -3956,6 +3956,11 @@
|
||||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/history@^4.7.11":
|
||||
version "4.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
|
||||
integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
|
||||
|
||||
"@types/html-minifier-terser@^5.0.0":
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57"
|
||||
@@ -4104,6 +4109,23 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-router-dom@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
|
||||
integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
|
||||
dependencies:
|
||||
"@types/history" "^4.7.11"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c"
|
||||
integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
|
||||
dependencies:
|
||||
"@types/history" "^4.7.11"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-syntax-highlighter@11.0.5":
|
||||
version "11.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087"
|
||||
@@ -9170,13 +9192,6 @@ history@^4.9.0:
|
||||
tiny-warning "^1.0.0"
|
||||
value-equal "^1.0.1"
|
||||
|
||||
history@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b"
|
||||
integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.6"
|
||||
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
@@ -12981,13 +12996,6 @@ react-router@5.3.3:
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@6:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557"
|
||||
integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==
|
||||
dependencies:
|
||||
history "^5.2.0"
|
||||
|
||||
react-syntax-highlighter@^15.4.5:
|
||||
version "15.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
|
||||
|
||||
Reference in New Issue
Block a user