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