Merge pull request #1173 from vector-im/lint-fixes
Enable stricter lints
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,
|
||||||
|
|||||||
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@@ -19,6 +19,6 @@ jobs:
|
|||||||
- name: i18n
|
- name: i18n
|
||||||
run: "yarn run i18n:check"
|
run: "yarn run i18n:check"
|
||||||
- name: ESLint
|
- name: ESLint
|
||||||
run: "yarn run lint:js"
|
run: "yarn run lint:eslint"
|
||||||
- name: Type check
|
- name: Type check
|
||||||
run: "yarn run lint:types"
|
run: "yarn run lint:types"
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -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-basic-ssl": "^1.0.1",
|
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
"i18next-http-backend": "^1.4.4",
|
"i18next-http-backend": "^1.4.4",
|
||||||
"livekit-client": "1.12.0",
|
"livekit-client": "1.12.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#9ba44b96202e36d8bd6fbcff3222b7c70fc371d7",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd",
|
||||||
"matrix-widget-api": "^1.3.1",
|
"matrix-widget-api": "^1.3.1",
|
||||||
"mermaid": "^8.13.8",
|
"mermaid": "^8.13.8",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
@@ -70,13 +72,15 @@
|
|||||||
"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",
|
||||||
"sdp-transform": "^2.14.1",
|
"sdp-transform": "^2.14.1",
|
||||||
"tinyqueue": "^2.0.3",
|
"tinyqueue": "^2.0.3",
|
||||||
"unique-names-generator": "^4.6.0"
|
"unique-names-generator": "^4.6.0",
|
||||||
|
"uuid": "9",
|
||||||
|
"@types/uuid": "9",
|
||||||
|
"@types/content-type": "^1.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.16.5",
|
"@babel/core": "^7.16.5",
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ limitations under the License.
|
|||||||
|
|
||||||
import { useMemo, CSSProperties, HTMLAttributes, FC } from "react";
|
import { useMemo, CSSProperties, HTMLAttributes, FC } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
|
||||||
|
|
||||||
import { getAvatarUrl } from "./matrix-utils";
|
import { getAvatarUrl } from "./matrix-utils";
|
||||||
import { useClient } from "./ClientContext";
|
import { useClient } from "./ClientContext";
|
||||||
@@ -59,9 +58,6 @@ function hashStringToArrIndex(str: string, arrLength: number) {
|
|||||||
return sum % arrLength;
|
return sum % arrLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveAvatarSrc = (client: MatrixClient, src: string, size: number) =>
|
|
||||||
src?.startsWith("mxc://") ? client && getAvatarUrl(client, src, size) : src;
|
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||||
bgKey?: string;
|
bgKey?: string;
|
||||||
src?: string;
|
src?: string;
|
||||||
@@ -99,10 +95,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 src.startsWith("mxc://") ? getAvatarUrl(client, src, sizePx) : src;
|
||||||
);
|
}, [client, src, sizePx]);
|
||||||
|
|
||||||
const backgroundColor = useMemo(() => {
|
const backgroundColor = useMemo(() => {
|
||||||
const index = hashStringToArrIndex(
|
const index = hashStringToArrIndex(
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
createContext,
|
createContext,
|
||||||
useMemo,
|
|
||||||
useContext,
|
useContext,
|
||||||
useRef,
|
useRef,
|
||||||
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
@@ -31,9 +31,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 +47,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 +189,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 +250,144 @@ 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: ClientState = useMemo(() => {
|
||||||
() => ({
|
if (alreadyOpenedErr) {
|
||||||
loading,
|
return { state: "error", error: alreadyOpenedErr };
|
||||||
isAuthenticated,
|
}
|
||||||
isPasswordlessUser,
|
|
||||||
client,
|
let authenticated = undefined;
|
||||||
changePassword,
|
if (initClientState) {
|
||||||
logout,
|
authenticated = {
|
||||||
userName,
|
client: initClientState.client,
|
||||||
setClient,
|
isPasswordlessUser: initClientState.passwordlessUser,
|
||||||
error: undefined,
|
changePassword,
|
||||||
}),
|
logout,
|
||||||
[
|
};
|
||||||
loading,
|
}
|
||||||
isAuthenticated,
|
|
||||||
isPasswordlessUser,
|
return { state: "valid", authenticated, setClient };
|
||||||
client,
|
}, [alreadyOpenedErr, changePassword, initClientState, logout, setClient]);
|
||||||
changePassword,
|
|
||||||
logout,
|
|
||||||
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,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface UserMenuProps {
|
|||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isPasswordlessUser: boolean;
|
isPasswordlessUser: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatarUrl: string;
|
avatarUrl?: string;
|
||||||
onAction: (value: string) => void;
|
onAction: (value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,21 +119,24 @@ export function UserMenu({
|
|||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
{(props) => (
|
{
|
||||||
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
(props: any) => (
|
||||||
<Item key={key} textValue={label}>
|
<Menu {...props} label={t("User menu")} onAction={onAction}>
|
||||||
<Icon
|
{items.map(({ key, icon: Icon, label, dataTestid }) => (
|
||||||
width={24}
|
<Item key={key} textValue={label}>
|
||||||
height={24}
|
<Icon
|
||||||
className={styles.menuIcon}
|
width={24}
|
||||||
data-testid={dataTestid}
|
height={24}
|
||||||
/>
|
className={styles.menuIcon}
|
||||||
<Body overflowEllipsis>{label}</Body>
|
data-testid={dataTestid}
|
||||||
</Item>
|
/>
|
||||||
))}
|
<Body overflowEllipsis>{label}</Body>
|
||||||
</Menu>
|
</Item>
|
||||||
)}
|
))}
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
||||||
</PopoverMenuTrigger>
|
</PopoverMenuTrigger>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
|||||||
modalState.open();
|
modalState.open();
|
||||||
break;
|
break;
|
||||||
case "logout":
|
case "logout":
|
||||||
logout();
|
logout?.();
|
||||||
break;
|
break;
|
||||||
case "login":
|
case "login":
|
||||||
history.push("/login", { state: { from: location } });
|
history.push("/login", { state: { from: location } });
|
||||||
@@ -59,19 +58,18 @@ export function UserMenuContainer({ preventNavigation = false }: Props) {
|
|||||||
[history, location, logout, modalState]
|
[history, location, logout, modalState]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const userName = client?.getUserIdLocalpart() ?? "";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UserMenu
|
<UserMenu
|
||||||
preventNavigation={preventNavigation}
|
preventNavigation={preventNavigation}
|
||||||
isAuthenticated={isAuthenticated}
|
isAuthenticated={authenticated}
|
||||||
isPasswordlessUser={isPasswordlessUser}
|
isPasswordlessUser={passwordlessUser}
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
onAction={onAction}
|
onAction={onAction}
|
||||||
displayName={
|
displayName={displayName || (userName ? userName.replace("@", "") : "")}
|
||||||
displayName || (userName ? userName.replace("@", "") : undefined)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{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[size] : [],
|
||||||
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;
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ interface Props {
|
|||||||
export const CallTypeDropdown: FC<Props> = ({ callType, setCallType }) => {
|
export const CallTypeDropdown: FC<Props> = ({ callType, setCallType }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onAction = (key: React.Key) => {
|
||||||
|
setCallType(key.toString() as CallType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClose = () => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverMenuTrigger placement="bottom">
|
<PopoverMenuTrigger placement="bottom">
|
||||||
<Button variant="dropdown" className={commonStyles.headline}>
|
<Button variant="dropdown" className={commonStyles.headline}>
|
||||||
@@ -52,7 +58,12 @@ 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={onAction}
|
||||||
|
onClose={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 />
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,10 +70,9 @@ export function RegisteredView({ client, isPasswordlessUser }: Props) {
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const [roomIdOrAlias] = await createRoom(client, roomName, ptt);
|
const [roomAlias] = await createRoom(client, roomName, ptt);
|
||||||
|
if (roomAlias) {
|
||||||
if (roomIdOrAlias) {
|
history.push(`/room/${roomAlias}`);
|
||||||
history.push(`/room/${roomIdOrAlias}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,15 +79,21 @@ export const UnauthenticatedView: FC = () => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
let roomIdOrAlias: string;
|
let roomAlias: string;
|
||||||
try {
|
try {
|
||||||
[roomIdOrAlias] = await createRoom(client, roomName, ptt);
|
[roomAlias] = 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,8 +106,12 @@ 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) {
|
||||||
history.push(`/room/${roomIdOrAlias}`);
|
throw new Error("setClient is undefined");
|
||||||
|
}
|
||||||
|
|
||||||
|
setClient({ client, session });
|
||||||
|
history.push(`/room/${roomAlias}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
submit().catch((error) => {
|
submit().catch((error) => {
|
||||||
@@ -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 | undefined) {
|
||||||
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,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ export function GridLayoutMenu({ layout, setLayout }: Props) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const tooltip = useCallback(() => t("Change layout"), [t]);
|
const tooltip = useCallback(() => t("Change layout"), [t]);
|
||||||
|
|
||||||
|
const onAction = useCallback(
|
||||||
|
(key: React.Key) => {
|
||||||
|
setLayout(key.toString() as Layout);
|
||||||
|
},
|
||||||
|
[setLayout]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClose = useCallback(() => {}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverMenuTrigger placement="bottom right">
|
<PopoverMenuTrigger placement="bottom right">
|
||||||
<TooltipTrigger tooltip={tooltip}>
|
<TooltipTrigger tooltip={tooltip}>
|
||||||
@@ -46,7 +55,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={onAction}
|
||||||
|
onClose={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"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { useLoadGroupCall } from "./useLoadGroupCall";
|
import { useLoadGroupCall } from "./useLoadGroupCall";
|
||||||
import { ErrorView, FullScreenView } from "../FullScreenView";
|
import { ErrorView, FullScreenView } from "../FullScreenView";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
client: MatrixClient;
|
client: MatrixClient;
|
||||||
@@ -39,26 +38,23 @@ export function GroupCallLoader({
|
|||||||
createPtt,
|
createPtt,
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { loading, error, groupCall } = useLoadGroupCall(
|
const groupCallState = useLoadGroupCall(
|
||||||
client,
|
client,
|
||||||
roomIdOrAlias,
|
roomIdOrAlias,
|
||||||
viaServers,
|
viaServers,
|
||||||
createPtt
|
createPtt
|
||||||
);
|
);
|
||||||
|
|
||||||
usePageTitle(groupCall ? groupCall.room.name : t("Loading…"));
|
switch (groupCallState.kind) {
|
||||||
|
case "loading":
|
||||||
if (loading) {
|
return (
|
||||||
return (
|
<FullScreenView>
|
||||||
<FullScreenView>
|
<h1>{t("Loading…")}</h1>
|
||||||
<h1>{t("Loading…")}</h1>
|
</FullScreenView>
|
||||||
</FullScreenView>
|
);
|
||||||
);
|
case "loaded":
|
||||||
|
return <>{children(groupCallState.groupCall)}</>;
|
||||||
|
case "failed":
|
||||||
|
return <ErrorView error={groupCallState.error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <ErrorView error={error} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <>{children(groupCall)}</>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { GroupCall, GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
import { GroupCall, GroupCallState } from "matrix-js-sdk/src/webrtc/groupCall";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
@@ -50,7 +50,6 @@ interface Props {
|
|||||||
isEmbedded: boolean;
|
isEmbedded: boolean;
|
||||||
preload: boolean;
|
preload: boolean;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
roomIdOrAlias: string;
|
|
||||||
groupCall: GroupCall;
|
groupCall: GroupCall;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,6 @@ export function GroupCallView({
|
|||||||
isEmbedded,
|
isEmbedded,
|
||||||
preload,
|
preload,
|
||||||
hideHeader,
|
hideHeader,
|
||||||
roomIdOrAlias,
|
|
||||||
groupCall,
|
groupCall,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const {
|
const {
|
||||||
@@ -83,13 +81,14 @@ export function GroupCallView({
|
|||||||
}, [groupCall]);
|
}, [groupCall]);
|
||||||
|
|
||||||
const { displayName, avatarUrl } = useProfile(client);
|
const { displayName, avatarUrl } = useProfile(client);
|
||||||
|
const matrixInfo = useMemo((): MatrixInfo => {
|
||||||
const matrixInfo: MatrixInfo = {
|
return {
|
||||||
displayName,
|
displayName: displayName!,
|
||||||
avatarUrl,
|
avatarUrl: avatarUrl!,
|
||||||
roomName: groupCall.room.name,
|
roomId: groupCall.room.roomId,
|
||||||
roomIdOrAlias,
|
roomName: groupCall.room.name,
|
||||||
};
|
};
|
||||||
|
}, [displayName, avatarUrl, groupCall]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && preload) {
|
if (widget && preload) {
|
||||||
@@ -140,14 +139,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 +205,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 +221,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")} />;
|
||||||
}
|
}
|
||||||
@@ -243,7 +242,6 @@ export function GroupCallView({
|
|||||||
onLeave={onLeave}
|
onLeave={onLeave}
|
||||||
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
unencryptedEventsFromUsers={unencryptedEventsFromUsers}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
matrixInfo={matrixInfo}
|
|
||||||
userChoices={userChoices}
|
userChoices={userChoices}
|
||||||
otelGroupCallMembership={otelGroupCallMembership}
|
otelGroupCallMembership={otelGroupCallMembership}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -68,17 +68,15 @@ import { ElementWidgetActions, widget } from "../widget";
|
|||||||
import { GridLayoutMenu } from "./GridLayoutMenu";
|
import { GridLayoutMenu } from "./GridLayoutMenu";
|
||||||
import { GroupCallInspector } from "./GroupCallInspector";
|
import { GroupCallInspector } from "./GroupCallInspector";
|
||||||
import styles from "./InCallView.module.css";
|
import styles from "./InCallView.module.css";
|
||||||
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 +98,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>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,8 +117,7 @@ export interface InCallViewProps {
|
|||||||
onLeave: () => void;
|
onLeave: () => void;
|
||||||
unencryptedEventsFromUsers: Set<string>;
|
unencryptedEventsFromUsers: Set<string>;
|
||||||
hideHeader: boolean;
|
hideHeader: boolean;
|
||||||
matrixInfo: MatrixInfo;
|
otelGroupCallMembership?: OTelGroupCallMembership;
|
||||||
otelGroupCallMembership: OTelGroupCallMembership;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InCallView({
|
export function InCallView({
|
||||||
@@ -129,7 +128,6 @@ export function InCallView({
|
|||||||
onLeave,
|
onLeave,
|
||||||
unencryptedEventsFromUsers,
|
unencryptedEventsFromUsers,
|
||||||
hideHeader,
|
hideHeader,
|
||||||
matrixInfo,
|
|
||||||
otelGroupCallMembership,
|
otelGroupCallMembership,
|
||||||
}: InCallViewProps) {
|
}: InCallViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -203,11 +201,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 +215,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
|
||||||
);
|
);
|
||||||
@@ -396,7 +394,7 @@ export function InCallView({
|
|||||||
{!hideHeader && maximisedParticipant === null && (
|
{!hideHeader && maximisedParticipant === null && (
|
||||||
<Header>
|
<Header>
|
||||||
<LeftNav>
|
<LeftNav>
|
||||||
<RoomHeaderInfo roomName={matrixInfo.roomName} />
|
<RoomHeaderInfo roomName={groupCall.room.name} />
|
||||||
<VersionMismatchWarning
|
<VersionMismatchWarning
|
||||||
users={unencryptedEventsFromUsers}
|
users={unencryptedEventsFromUsers}
|
||||||
room={groupCall.room}
|
room={groupCall.room}
|
||||||
@@ -416,16 +414,18 @@ 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}
|
||||||
roomIdOrAlias={matrixInfo.roomIdOrAlias}
|
roomId={groupCall.room.roomId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{settingsModalState.isOpen && (
|
{settingsModalState.isOpen && (
|
||||||
@@ -437,10 +437,7 @@ export function InCallView({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{inviteModalState.isOpen && (
|
{inviteModalState.isOpen && (
|
||||||
<InviteModal
|
<InviteModal roomId={groupCall.room.roomId} {...inviteModalProps} />
|
||||||
roomIdOrAlias={matrixInfo.roomIdOrAlias}
|
|
||||||
{...inviteModalProps}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import { getRoomUrl } from "../matrix-utils";
|
|||||||
import styles from "./InviteModal.module.css";
|
import styles from "./InviteModal.module.css";
|
||||||
|
|
||||||
interface Props extends Omit<ModalProps, "title" | "children"> {
|
interface Props extends Omit<ModalProps, "title" | "children"> {
|
||||||
roomIdOrAlias: string;
|
roomId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InviteModal: FC<Props> = ({ roomIdOrAlias, ...rest }) => {
|
export const InviteModal: FC<Props> = ({ roomId, ...rest }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -40,7 +40,7 @@ export const InviteModal: FC<Props> = ({ roomIdOrAlias, ...rest }) => {
|
|||||||
<p>{t("Copy and share this call link")}</p>
|
<p>{t("Copy and share this call link")}</p>
|
||||||
<CopyButton
|
<CopyButton
|
||||||
className={styles.copyButton}
|
className={styles.copyButton}
|
||||||
value={getRoomUrl(roomIdOrAlias)}
|
value={getRoomUrl(roomId)}
|
||||||
data-testid="modal_inviteLink"
|
data-testid="modal_inviteLink"
|
||||||
/>
|
/>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -81,7 +81,7 @@ export function LobbyView(props: Props) {
|
|||||||
<Body>Or</Body>
|
<Body>Or</Body>
|
||||||
<CopyButton
|
<CopyButton
|
||||||
variant="secondaryCopy"
|
variant="secondaryCopy"
|
||||||
value={getRoomUrl(props.matrixInfo.roomName)}
|
value={getRoomUrl(props.matrixInfo.roomId)}
|
||||||
className={styles.copyButton}
|
className={styles.copyButton}
|
||||||
copiedMessage={t("Call link copied")}
|
copiedMessage={t("Call link copied")}
|
||||||
data-testid="lobby_inviteLink"
|
data-testid="lobby_inviteLink"
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ import { Body } from "../typography/Typography";
|
|||||||
|
|
||||||
interface Props extends Omit<ModalProps, "title" | "children"> {
|
interface Props extends Omit<ModalProps, "title" | "children"> {
|
||||||
rageshakeRequestId: string;
|
rageshakeRequestId: string;
|
||||||
roomIdOrAlias: string;
|
roomId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RageshakeRequestModal: FC<Props> = ({
|
export const RageshakeRequestModal: FC<Props> = ({
|
||||||
rageshakeRequestId,
|
rageshakeRequestId,
|
||||||
roomIdOrAlias,
|
roomId,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -57,7 +57,7 @@ export const RageshakeRequestModal: FC<Props> = ({
|
|||||||
submitRageshake({
|
submitRageshake({
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
rageshakeRequestId,
|
rageshakeRequestId,
|
||||||
roomId: roomIdOrAlias, // Possibly not a room ID, but oh well
|
roomId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
|
|||||||
@@ -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,41 @@ 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}
|
|
||||||
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, passwordlessUser, isEmbedded, preload, hideHeader]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (loading || isRegistering) {
|
if (loading || isRegistering) {
|
||||||
@@ -95,7 +95,7 @@ export const RoomPage: FC = () => {
|
|||||||
return <ErrorView error={error} />;
|
return <ErrorView error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!client) {
|
||||||
return <RoomAuthView />;
|
return <RoomAuthView />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import { useDefaultDevices } from "../settings/useSetting";
|
|||||||
export type MatrixInfo = {
|
export type MatrixInfo = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
|
roomId: string;
|
||||||
roomName: string;
|
roomName: string;
|
||||||
roomIdOrAlias: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -34,8 +34,26 @@ import { widget } from "../widget";
|
|||||||
|
|
||||||
const STATS_COLLECT_INTERVAL_TIME_MS = 10000;
|
const STATS_COLLECT_INTERVAL_TIME_MS = 10000;
|
||||||
|
|
||||||
|
export type GroupCallLoaded = {
|
||||||
|
kind: "loaded";
|
||||||
|
groupCall: GroupCall;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupCallLoadFailed = {
|
||||||
|
kind: "failed";
|
||||||
|
error: Error;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupCallLoading = {
|
||||||
|
kind: "loading";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GroupCallStatus =
|
||||||
|
| GroupCallLoaded
|
||||||
|
| GroupCallLoadFailed
|
||||||
|
| GroupCallLoading;
|
||||||
|
|
||||||
export interface GroupCallLoadState {
|
export interface GroupCallLoadState {
|
||||||
loading: boolean;
|
|
||||||
error?: Error;
|
error?: Error;
|
||||||
groupCall?: GroupCall;
|
groupCall?: GroupCall;
|
||||||
}
|
}
|
||||||
@@ -45,13 +63,11 @@ export const useLoadGroupCall = (
|
|||||||
roomIdOrAlias: string,
|
roomIdOrAlias: string,
|
||||||
viaServers: string[],
|
viaServers: string[],
|
||||||
createPtt: boolean
|
createPtt: boolean
|
||||||
): GroupCallLoadState => {
|
): GroupCallStatus => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState<GroupCallLoadState>({ loading: true });
|
const [state, setState] = useState<GroupCallStatus>({ kind: "loading" });
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setState({ loading: true });
|
|
||||||
|
|
||||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||||
try {
|
try {
|
||||||
// We lowercase the localpart when we create the room, so we must lowercase
|
// We lowercase the localpart when we create the room, so we must lowercase
|
||||||
@@ -74,8 +90,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 +108,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;
|
||||||
}
|
}
|
||||||
@@ -170,12 +192,8 @@ export const useLoadGroupCall = (
|
|||||||
|
|
||||||
waitForClientSyncing()
|
waitForClientSyncing()
|
||||||
.then(fetchOrCreateGroupCall)
|
.then(fetchOrCreateGroupCall)
|
||||||
.then((groupCall) =>
|
.then((groupCall) => setState({ kind: "loaded", groupCall }))
|
||||||
setState((prevState) => ({ ...prevState, loading: false, groupCall }))
|
.catch((error) => setState({ kind: "failed", error }));
|
||||||
)
|
|
||||||
.catch((error) =>
|
|
||||||
setState((prevState) => ({ ...prevState, loading: false, error }))
|
|
||||||
);
|
|
||||||
}, [client, roomIdOrAlias, viaServers, createPtt, t]);
|
}, [client, roomIdOrAlias, viaServers, createPtt, t]);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
|||||||
@@ -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([
|
||||||
@@ -167,7 +167,7 @@ describe("ObjectFlattener", () => {
|
|||||||
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
||||||
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
||||||
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
||||||
"matrix.call.stats.connection.transport.1.rtt": "null",
|
"matrix.call.stats.connection.transport.1.rtt": 0,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -234,7 +234,7 @@ describe("ObjectFlattener", () => {
|
|||||||
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
"matrix.call.stats.connection.transport.1.localCandidateType": "srfx",
|
||||||
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
"matrix.call.stats.connection.transport.1.remoteCandidateType": "srfx",
|
||||||
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
"matrix.call.stats.connection.transport.1.networkType": "ethernet",
|
||||||
"matrix.call.stats.connection.transport.1.rtt": "null",
|
"matrix.call.stats.connection.transport.1.rtt": 0,
|
||||||
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0,
|
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.concealedAudio": 0,
|
||||||
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0,
|
"matrix.call.stats.connection.audioConcealment.REMOTE_AUDIO_TRACK_ID.totalAudioDuration": 0,
|
||||||
"matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0,
|
"matrix.call.stats.connection.audioConcealment.REMOTE_VIDEO_TRACK_ID.concealedAudio": 0,
|
||||||
|
|||||||
@@ -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,23 +1,31 @@
|
|||||||
{
|
{
|
||||||
"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": [
|
||||||
|
"./node_modules/matrix-js-sdk/src/@types/*.d.ts",
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
"./src/**/*.tsx",
|
"./src/**/*.tsx",
|
||||||
"./test/**/*.ts",
|
"./test/**/*.ts",
|
||||||
|
|||||||
82
yarn.lock
82
yarn.lock
@@ -2186,10 +2186,10 @@
|
|||||||
"@react-hook/latest" "^1.0.3"
|
"@react-hook/latest" "^1.0.3"
|
||||||
clsx "^1.2.1"
|
clsx "^1.2.1"
|
||||||
|
|
||||||
"@matrix-org/matrix-sdk-crypto-js@^0.1.0-alpha.10":
|
"@matrix-org/matrix-sdk-crypto-js@^0.1.1":
|
||||||
version "0.1.0-alpha.10"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.0-alpha.10.tgz#b6a6395cffd3197ae2e0a88f4eeae8b315571fd2"
|
resolved "https://registry.yarnpkg.com/@matrix-org/matrix-sdk-crypto-js/-/matrix-sdk-crypto-js-0.1.2.tgz#b58679e161f3d734359a8665922956309b1a4417"
|
||||||
integrity sha512-8V2NKuzGOFzEZeZVgF2is7gmuopdRbMZ064tzPDE0vN34iX6s3O8A4oxIT7SA3qtymwm3t1yEvTnT+0gfbmh4g==
|
integrity sha512-bbal0RcWwerS/DgqhOgM7wkXJ2YSv9fySK/qgLlrAsdYLpMSTqG8wDQ89/v+RYo9WmA5hwUN/wXcCDdFaFEXQQ==
|
||||||
|
|
||||||
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.14.tgz":
|
||||||
version "3.2.14"
|
version "3.2.14"
|
||||||
@@ -3893,6 +3893,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
|
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
|
||||||
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
||||||
|
|
||||||
|
"@types/content-type@^1.1.5":
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.5.tgz#aa02dca40864749a9e2bf0161a6216da57e3ede5"
|
||||||
|
integrity sha512-dgMN+syt1xb7Hk8LU6AODOfPlvz5z1CbXpPuJE5ZrX9STfBOIXF09pEB8N7a97WT9dbngt3ksDCm6GW6yMrxfQ==
|
||||||
|
|
||||||
"@types/eslint-scope@^3.7.3":
|
"@types/eslint-scope@^3.7.3":
|
||||||
version "3.7.4"
|
version "3.7.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
|
||||||
@@ -3956,6 +3961,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 +4114,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"
|
||||||
@@ -4194,6 +4221,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d"
|
||||||
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==
|
||||||
|
|
||||||
|
"@types/uuid@9":
|
||||||
|
version "9.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
|
||||||
|
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
|
||||||
|
|
||||||
"@types/webpack-env@^1.16.0":
|
"@types/webpack-env@^1.16.0":
|
||||||
version "1.17.0"
|
version "1.17.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0"
|
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0"
|
||||||
@@ -6404,6 +6436,11 @@ crypto-browserify@^3.11.0:
|
|||||||
randombytes "^2.0.0"
|
randombytes "^2.0.0"
|
||||||
randomfill "^1.0.3"
|
randomfill "^1.0.3"
|
||||||
|
|
||||||
|
crypto-js@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
|
||||||
|
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
|
||||||
|
|
||||||
css-blank-pseudo@^3.0.3:
|
css-blank-pseudo@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561"
|
||||||
@@ -9175,13 +9212,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"
|
||||||
@@ -10618,6 +10648,11 @@ junk@^3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1"
|
||||||
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==
|
||||||
|
|
||||||
|
jwt-decode@^3.1.2:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59"
|
||||||
|
integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==
|
||||||
|
|
||||||
khroma@^1.4.1:
|
khroma@^1.4.1:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/khroma/-/khroma-1.4.1.tgz#ad6a5b6a972befc5112ce5129887a1a83af2c003"
|
resolved "https://registry.yarnpkg.com/khroma/-/khroma-1.4.1.tgz#ad6a5b6a972befc5112ce5129887a1a83af2c003"
|
||||||
@@ -10977,18 +11012,20 @@ matrix-events-sdk@0.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#9ba44b96202e36d8bd6fbcff3222b7c70fc371d7":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#d79d9ae69c3220c02406706d4a1ec52c22c44fbd":
|
||||||
version "26.0.1"
|
version "26.2.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/9ba44b96202e36d8bd6fbcff3222b7c70fc371d7"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/d79d9ae69c3220c02406706d4a1ec52c22c44fbd"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
"@matrix-org/matrix-sdk-crypto-js" "^0.1.0-alpha.10"
|
"@matrix-org/matrix-sdk-crypto-js" "^0.1.1"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
bs58 "^5.0.0"
|
bs58 "^5.0.0"
|
||||||
content-type "^1.0.4"
|
content-type "^1.0.4"
|
||||||
|
jwt-decode "^3.1.2"
|
||||||
loglevel "^1.7.1"
|
loglevel "^1.7.1"
|
||||||
matrix-events-sdk "0.0.1"
|
matrix-events-sdk "0.0.1"
|
||||||
matrix-widget-api "^1.3.1"
|
matrix-widget-api "^1.3.1"
|
||||||
|
oidc-client-ts "^2.2.4"
|
||||||
p-retry "4"
|
p-retry "4"
|
||||||
sdp-transform "^2.14.1"
|
sdp-transform "^2.14.1"
|
||||||
unhomoglyph "^1.0.6"
|
unhomoglyph "^1.0.6"
|
||||||
@@ -11675,6 +11712,14 @@ objectorarray@^1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
|
resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5"
|
||||||
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
|
integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==
|
||||||
|
|
||||||
|
oidc-client-ts@^2.2.4:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-2.2.4.tgz#7d86b5efe2248f3637a6f3a0ee1af86764aea125"
|
||||||
|
integrity sha512-nOZwIomju+AmXObl5Oq5PjrES/qTt8bLsENJCIydVgi9TEWk7SCkOU6X3RNkY7yfySRM1OJJvDKdREZdmnDT2g==
|
||||||
|
dependencies:
|
||||||
|
crypto-js "^4.1.1"
|
||||||
|
jwt-decode "^3.1.2"
|
||||||
|
|
||||||
on-finished@2.4.1:
|
on-finished@2.4.1:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
@@ -12986,13 +13031,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