mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Compare commits
4 Commits
v0.6.4
...
toger5/pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3404fbbf3 | ||
|
|
ab457be4bb | ||
|
|
55f01ee0d5 | ||
|
|
480a995be1 |
@@ -39,6 +39,12 @@ module.exports = {
|
||||
// We should use the js-sdk logger, never console directly.
|
||||
"no-console": ["error"],
|
||||
"react/display-name": "error",
|
||||
// Encourage proper usage of Promises:
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/promise-function-async": "error",
|
||||
"@typescript-eslint/require-await": "error",
|
||||
"@typescript-eslint/await-thenable": "error",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
|
||||
@@ -9,7 +9,5 @@ coverage:
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
# Encourage (but don't enforce) 80% coverage on all lines that a PR
|
||||
# touches
|
||||
# Expect 80% coverage on all lines that a PR touches
|
||||
target: 80%
|
||||
informational: true
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"@babel/preset-env": "^7.22.20",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"@babel/preset-typescript": "^7.23.0",
|
||||
"@juggle/resize-observer": "^3.3.1",
|
||||
"@livekit/components-core": "^0.11.0",
|
||||
"@livekit/components-react": "^2.0.0",
|
||||
"@opentelemetry/api": "^1.4.0",
|
||||
@@ -83,7 +84,7 @@
|
||||
"livekit-client": "^2.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"loglevel": "^1.9.1",
|
||||
"matrix-js-sdk": "matrix-org/matrix-js-sdk#169e8f86139111574a3738f8557c6fa4b2a199db",
|
||||
"matrix-js-sdk": "^v34.4.0",
|
||||
"matrix-widget-api": "^1.8.2",
|
||||
"normalize.css": "^8.0.1",
|
||||
"observable-hooks": "^4.2.3",
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
"disconnected_banner": "Die Verbindung zum Server wurde getrennt.",
|
||||
"full_screen_view_description": "<0>Übermittelte Problemberichte helfen uns, Fehler zu beheben.</0>",
|
||||
"full_screen_view_h1": "<0>Hoppla, etwas ist schiefgelaufen.</0>",
|
||||
"group_call_loader_failed_heading": "Anruf nicht gefunden",
|
||||
"group_call_loader_failed_text": "Anrufe sind nun Ende-zu-Ende-verschlüsselt und müssen auf der Startseite erstellt werden. Damit stellen wir sicher, dass alle denselben Schlüssel verwenden.",
|
||||
"hangup_button_label": "Anruf beenden",
|
||||
"header_label": "Element Call-Startseite",
|
||||
"header_participants_label": "Teilnehmende",
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
"disconnected_banner": "Võrguühendus serveriga on katkenud.",
|
||||
"full_screen_view_description": "<0>Kui saadad meile vealogid, siis on lihtsam vea põhjust otsida.</0>",
|
||||
"full_screen_view_h1": "<0>Ohoo, midagi on nüüd katki.</0>",
|
||||
"group_call_loader_failed_heading": "Kõnet ei leidu",
|
||||
"group_call_loader_failed_text": "Kõned on nüüd läbivalt krüptitud ning need pead looma kodulehelt. Sellega tagad, et kõik kasutavad samu krüptovõtmeid.",
|
||||
"hangup_button_label": "Lõpeta kõne",
|
||||
"header_participants_label": "Osalejad",
|
||||
"invite_modal": {
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
"disconnected_banner": "La connexion avec le serveur a été perdue.",
|
||||
"full_screen_view_description": "<0>Soumettre les journaux de débogage nous aidera à déterminer le problème.</0>",
|
||||
"full_screen_view_h1": "<0>Oups, quelque chose s’est mal passé.</0>",
|
||||
"group_call_loader_failed_heading": "Appel non trouvé",
|
||||
"group_call_loader_failed_text": "Les appels sont maintenant chiffrés de bout-en-bout et doivent être créés depuis la page d’accueil. Cela permet d’être sûr que tout le monde utilise la même clé de chiffrement.",
|
||||
"hangup_button_label": "Terminer l’appel",
|
||||
"header_label": "Accueil Element Call",
|
||||
"invite_modal": {
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
"disconnected_banner": "Koneksi ke server telah hilang.",
|
||||
"full_screen_view_description": "<0>Mengirim catatan pengawakutuan akan membantu kami melacak masalahnya.</0>",
|
||||
"full_screen_view_h1": "<0>Aduh, ada yang salah.</0>",
|
||||
"group_call_loader_failed_heading": "Panggilan tidak ditemukan",
|
||||
"group_call_loader_failed_text": "Panggilan sekarang terenkripsi secara ujung ke ujung dan harus dibuat dari laman beranda. Ini memastikan bahwa semuanya menggunakan kunci enkripsi yang sama.",
|
||||
"hangup_button_label": "Akhiri panggilan",
|
||||
"header_label": "Beranda Element Call",
|
||||
"header_participants_label": "Peserta",
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
"disconnected_banner": "La connessione al server è stata persa.",
|
||||
"full_screen_view_description": "<0>L'invio di registri di debug ci aiuterà ad individuare il problema.</0>",
|
||||
"full_screen_view_h1": "<0>Ops, qualcosa è andato storto.</0>",
|
||||
"group_call_loader_failed_heading": "Chiamata non trovata",
|
||||
"group_call_loader_failed_text": "Le chiamate ora sono cifrate end-to-end e devono essere create dalla pagina principale. Ciò assicura che chiunque usi la stessa chiave di crittografia.",
|
||||
"hangup_button_label": "Termina chiamata",
|
||||
"header_label": "Inizio di Element Call",
|
||||
"header_participants_label": "Partecipanti",
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
"disconnected_banner": "Utracono połączenie z serwerem.",
|
||||
"full_screen_view_description": "<0>Wysłanie dzienników debuggowania pomoże nam ustalić przyczynę problemu.</0>",
|
||||
"full_screen_view_h1": "<0>Ojej, coś poszło nie tak.</0>",
|
||||
"group_call_loader_failed_heading": "Nie znaleziono połączenia",
|
||||
"group_call_loader_failed_text": "Połączenia są teraz szyfrowane end-to-end i muszą zostać utworzone ze strony głównej. Pomaga to upewnić się, że każdy korzysta z tego samego klucza szyfrującego.",
|
||||
"hangup_button_label": "Zakończ połączenie",
|
||||
"header_label": "Strona główna Element Call",
|
||||
"header_participants_label": "Uczestnicy",
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"disconnected_banner": "Spojenie so serverom sa stratilo.",
|
||||
"full_screen_view_description": "<0>Odoslanie záznamov ladenia nám pomôže nájsť problém.</0>",
|
||||
"full_screen_view_h1": "<0>Hups, niečo sa pokazilo.</0>",
|
||||
"group_call_loader_failed_heading": "Hovor nebol nájdený",
|
||||
"group_call_loader_failed_text": "Hovory sú teraz end-to-end šifrované a je potrebné ich vytvoriť z domovskej stránky. To pomáha zabezpečiť, aby všetci používali rovnaký šifrovací kľúč.",
|
||||
"hangup_button_label": "Ukončiť hovor",
|
||||
"header_label": "Domov Element Call",
|
||||
"header_participants_label": "Účastníci",
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
"disconnected_banner": "Втрачено зв'язок з сервером.",
|
||||
"full_screen_view_description": "<0>Надсилання журналів налагодження допоможе нам виявити проблему.</0>",
|
||||
"full_screen_view_h1": "<0>Йой, щось пішло не за планом.</0>",
|
||||
"group_call_loader_failed_heading": "Виклик не знайдено",
|
||||
"group_call_loader_failed_text": "Відтепер виклики захищено наскрізним шифруванням, і їх потрібно створювати з домашньої сторінки. Це допомагає переконатися, що всі користувачі використовують один і той самий ключ шифрування.",
|
||||
"hangup_button_label": "Завершити виклик",
|
||||
"header_label": "Домівка Element Call",
|
||||
"header_participants_label": "Учасники",
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
"disconnected_banner": "与服务器的连接中断。",
|
||||
"full_screen_view_description": "<0>提交日志以帮助我们修复问题。</0>",
|
||||
"full_screen_view_h1": "<0>哎哟,出问题了。</0>",
|
||||
"group_call_loader_failed_heading": "未找到通话",
|
||||
"group_call_loader_failed_text": "现在,通话是端对端加密的,需要从主页创建。这有助于确保每个人都使用相同的加密密钥。",
|
||||
"hangup_button_label": "通话结束",
|
||||
"header_label": "Element Call主页",
|
||||
"join_existing_call_modal": {
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
"disconnected_banner": "到伺服器的連線已遺失。",
|
||||
"full_screen_view_description": "<0>送出除錯紀錄,可幫助我們修正問題。</0>",
|
||||
"full_screen_view_h1": "<0>喔喔,有些地方怪怪的。</0>",
|
||||
"group_call_loader_failed_heading": "找不到通話",
|
||||
"group_call_loader_failed_text": "通話現在是端對端加密的,必須從首頁建立。這有助於確保每個人都使用相同的加密金鑰。",
|
||||
"hangup_button_label": "結束通話",
|
||||
"header_label": "Element Call 首頁",
|
||||
"header_participants_label": "參與者",
|
||||
|
||||
13
src/App.tsx
13
src/App.tsx
@@ -24,6 +24,7 @@ import {
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { History } from "history";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { HomePage } from "./home/HomePage";
|
||||
import { LoginPage } from "./auth/LoginPage";
|
||||
@@ -70,11 +71,13 @@ interface AppProps {
|
||||
export const App: FC<AppProps> = ({ history }) => {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
useEffect(() => {
|
||||
Initializer.init()?.then(() => {
|
||||
if (loaded) return;
|
||||
setLoaded(true);
|
||||
widget?.api.sendContentLoaded();
|
||||
});
|
||||
Initializer.init()
|
||||
?.then(async () => {
|
||||
if (loaded) return;
|
||||
setLoaded(true);
|
||||
await widget?.api.sendContentLoaded();
|
||||
})
|
||||
.catch(logger.error);
|
||||
});
|
||||
|
||||
const errorPage = <CrashView />;
|
||||
|
||||
@@ -40,7 +40,7 @@ export const UserMenuContainer: FC<Props> = ({ preventNavigation = false }) => {
|
||||
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
|
||||
|
||||
const onAction = useCallback(
|
||||
async (value: string) => {
|
||||
(value: string) => {
|
||||
switch (value) {
|
||||
case "user":
|
||||
setSettingsTab("profile");
|
||||
|
||||
@@ -265,7 +265,7 @@ export class PosthogAnalytics {
|
||||
this.posthog.identify(analyticsID);
|
||||
} else {
|
||||
logger.info(
|
||||
"No analyticsID is availble. Should not try to setup posthog",
|
||||
"No analyticsID is available. Should not try to setup posthog",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -333,7 +333,9 @@ export class PosthogAnalytics {
|
||||
}
|
||||
|
||||
public onLoginStatusChanged(): void {
|
||||
this.maybeIdentifyUser();
|
||||
this.maybeIdentifyUser().catch(() =>
|
||||
logger.log("Could not identify user on login status change"),
|
||||
);
|
||||
}
|
||||
|
||||
private updateSuperProperties(): void {
|
||||
@@ -382,20 +384,27 @@ export class PosthogAnalytics {
|
||||
}
|
||||
}
|
||||
|
||||
public async trackEvent<E extends IPosthogEvent>(
|
||||
public trackEvent<E extends IPosthogEvent>(
|
||||
{ eventName, ...properties }: E,
|
||||
options?: CaptureOptions,
|
||||
): Promise<void> {
|
||||
): void {
|
||||
const doCapture = (): void => {
|
||||
if (
|
||||
this.anonymity == Anonymity.Disabled ||
|
||||
this.anonymity == Anonymity.Anonymous
|
||||
)
|
||||
return;
|
||||
this.capture(eventName, properties, options);
|
||||
};
|
||||
|
||||
if (this.identificationPromise) {
|
||||
// only make calls to posthog after the identificaion is done
|
||||
await this.identificationPromise;
|
||||
// only make calls to posthog after the identification is done
|
||||
this.identificationPromise.then(doCapture, (e) => {
|
||||
logger.error("Failed to identify user for tracking", e);
|
||||
});
|
||||
} else {
|
||||
doCapture();
|
||||
}
|
||||
if (
|
||||
this.anonymity == Anonymity.Disabled ||
|
||||
this.anonymity == Anonymity.Anonymous
|
||||
)
|
||||
return;
|
||||
this.capture(eventName, properties, options);
|
||||
}
|
||||
|
||||
private startListeningToSettingsChanges(): void {
|
||||
@@ -409,7 +418,9 @@ export class PosthogAnalytics {
|
||||
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
||||
optInAnalytics.value.subscribe((optIn) => {
|
||||
this.setAnonymity(optIn ? Anonymity.Pseudonymous : Anonymity.Disabled);
|
||||
this.maybeIdentifyUser();
|
||||
this.maybeIdentifyUser().catch(() =>
|
||||
logger.log("Could not identify user"),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
||||
|
||||
public onStart(span: Span): void {
|
||||
// Hack: Yield to allow attributes to be set before processing
|
||||
Promise.resolve().then(() => {
|
||||
try {
|
||||
switch (span.name) {
|
||||
case "matrix.groupCallMembership":
|
||||
this.onGroupCallMembershipStart(span);
|
||||
@@ -52,7 +52,10 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
||||
this.onSummaryReportStart(span);
|
||||
return;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
// log to avoid tripping @typescript-eslint/no-unused-vars
|
||||
logger.debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
public onEnd(span: ReadableSpan): void {
|
||||
@@ -157,7 +160,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
||||
/**
|
||||
* Shutdown the processor.
|
||||
*/
|
||||
public shutdown(): Promise<void> {
|
||||
public async shutdown(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export function useInteractiveLogin(
|
||||
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClient,
|
||||
doRequest: (): Promise<LoginResponse> =>
|
||||
doRequest: async (): Promise<LoginResponse> =>
|
||||
authClient.login("m.login.password", {
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
@@ -58,9 +58,8 @@ export function useInteractiveLogin(
|
||||
password,
|
||||
}),
|
||||
stateUpdated: (): void => {},
|
||||
requestEmailToken: (): Promise<{ sid: string }> => {
|
||||
return Promise.resolve({ sid: "" });
|
||||
},
|
||||
requestEmailToken: async (): Promise<{ sid: string }> =>
|
||||
Promise.resolve({ sid: "" }),
|
||||
});
|
||||
|
||||
// XXX: This claims to return an IAuthData which contains none of these
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
MatrixClient,
|
||||
RegisterResponse,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { initClient } from "../utils/matrix";
|
||||
import { Session } from "../ClientContext";
|
||||
@@ -75,7 +76,7 @@ export const useInteractiveRegistration = (
|
||||
): Promise<[MatrixClient, Session]> => {
|
||||
const interactiveAuth = new InteractiveAuth({
|
||||
matrixClient: authClient.current!,
|
||||
doRequest: (auth): Promise<RegisterResponse> =>
|
||||
doRequest: async (auth): Promise<RegisterResponse> =>
|
||||
authClient.current!.registerRequest({
|
||||
username,
|
||||
password,
|
||||
@@ -87,19 +88,26 @@ export const useInteractiveRegistration = (
|
||||
}
|
||||
|
||||
if (nextStage === "m.login.terms") {
|
||||
interactiveAuth.submitAuthDict({
|
||||
type: "m.login.terms",
|
||||
});
|
||||
interactiveAuth
|
||||
.submitAuthDict({
|
||||
type: "m.login.terms",
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
});
|
||||
} else if (nextStage === "m.login.recaptcha") {
|
||||
interactiveAuth.submitAuthDict({
|
||||
type: "m.login.recaptcha",
|
||||
response: recaptchaResponse,
|
||||
});
|
||||
interactiveAuth
|
||||
.submitAuthDict({
|
||||
type: "m.login.recaptcha",
|
||||
response: recaptchaResponse,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
});
|
||||
}
|
||||
},
|
||||
requestEmailToken: (): Promise<{ sid: string }> => {
|
||||
return Promise.resolve({ sid: "dummy" });
|
||||
},
|
||||
requestEmailToken: async (): Promise<{ sid: string }> =>
|
||||
Promise.resolve({ sid: "dummy" }),
|
||||
});
|
||||
|
||||
// XXX: This claims to return an IAuthData which contains none of these
|
||||
|
||||
@@ -72,7 +72,7 @@ export function useRecaptcha(sitekey?: string): {
|
||||
}
|
||||
}, [recaptchaId, sitekey]);
|
||||
|
||||
const execute = useCallback((): Promise<string> => {
|
||||
const execute = useCallback(async (): Promise<string> => {
|
||||
if (!sitekey) {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
@@ -104,7 +104,12 @@ export function useRecaptcha(sitekey?: string): {
|
||||
},
|
||||
};
|
||||
|
||||
window.grecaptcha.execute();
|
||||
window.grecaptcha.execute().then(
|
||||
() => {}, // noop
|
||||
(e) => {
|
||||
logger.error("Recaptcha execution failed", e);
|
||||
},
|
||||
);
|
||||
|
||||
const iframe = document.querySelector<HTMLIFrameElement>(
|
||||
'iframe[src*="recaptcha/api2/bframe"]',
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from "./ConfigOptions";
|
||||
|
||||
export class Config {
|
||||
private static internalInstance: Config;
|
||||
private static internalInstance: Config | undefined;
|
||||
|
||||
public static get(): ConfigOptions {
|
||||
if (!this.internalInstance?.config)
|
||||
@@ -30,23 +30,23 @@ export class Config {
|
||||
return this.internalInstance.config;
|
||||
}
|
||||
|
||||
public static init(): Promise<void> {
|
||||
if (Config.internalInstance?.initPromise) {
|
||||
return Config.internalInstance.initPromise;
|
||||
}
|
||||
Config.internalInstance = new Config();
|
||||
Config.internalInstance.initPromise = new Promise<void>((resolve) => {
|
||||
downloadConfig("../config.json").then((config) => {
|
||||
Config.internalInstance.config = { ...DEFAULT_CONFIG, ...config };
|
||||
resolve();
|
||||
public static async init(): Promise<void> {
|
||||
if (!Config.internalInstance?.initPromise) {
|
||||
const internalInstance = new Config();
|
||||
Config.internalInstance = internalInstance;
|
||||
|
||||
Config.internalInstance.initPromise = downloadConfig(
|
||||
"../config.json",
|
||||
).then((config) => {
|
||||
internalInstance.config = { ...DEFAULT_CONFIG, ...config };
|
||||
});
|
||||
});
|
||||
}
|
||||
return Config.internalInstance.initPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a alternative initializer that does not load anything
|
||||
* from a hosted config file but instead just initializes the conifg using the
|
||||
* from a hosted config file but instead just initializes the config using the
|
||||
* default config.
|
||||
*
|
||||
* It is supposed to only be used in tests. (It is executed in `vite.setup.js`)
|
||||
|
||||
@@ -55,19 +55,25 @@ export class MatrixKeyProvider extends BaseKeyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private onEncryptionKeyChanged = async (
|
||||
private onEncryptionKeyChanged = (
|
||||
encryptionKey: Uint8Array,
|
||||
encryptionKeyIndex: number,
|
||||
participantId: string,
|
||||
): Promise<void> => {
|
||||
this.onSetEncryptionKey(
|
||||
await createKeyMaterialFromBuffer(encryptionKey),
|
||||
participantId,
|
||||
encryptionKeyIndex,
|
||||
);
|
||||
): void => {
|
||||
createKeyMaterialFromBuffer(encryptionKey).then(
|
||||
(keyMaterial) => {
|
||||
this.onSetEncryptionKey(keyMaterial, participantId, encryptionKeyIndex);
|
||||
|
||||
logger.debug(
|
||||
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
|
||||
logger.debug(
|
||||
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
|
||||
);
|
||||
},
|
||||
(e) => {
|
||||
logger.error(
|
||||
`Failed to create key material from buffer for livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
|
||||
e,
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from "react";
|
||||
import useMeasure from "react-use-measure";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import styles from "./Grid.module.css";
|
||||
import { useMergedRefs } from "../useMergedRefs";
|
||||
@@ -362,7 +363,7 @@ export function Grid<
|
||||
// Because we're using react-spring in imperative mode, we're responsible for
|
||||
// firing animations manually whenever the tiles array updates
|
||||
useEffect(() => {
|
||||
springRef.start();
|
||||
springRef.start().forEach((p) => void p.catch(logger.error));
|
||||
}, [placedTiles, springRef]);
|
||||
|
||||
const animateDraggedTile = (
|
||||
@@ -399,7 +400,8 @@ export function Grid<
|
||||
((key): boolean =>
|
||||
key === "zIndex" || key === "x" || key === "y"),
|
||||
},
|
||||
);
|
||||
)
|
||||
.catch(logger.error);
|
||||
|
||||
if (endOfGesture)
|
||||
callback({
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("CallList", () => {
|
||||
);
|
||||
};
|
||||
|
||||
it("should show room", async () => {
|
||||
it("should show room", () => {
|
||||
const rooms = [
|
||||
{
|
||||
roomName: "Room #1",
|
||||
|
||||
@@ -19,6 +19,7 @@ import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
import Backend from "i18next-http-backend";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { getUrlParams } from "./UrlParams";
|
||||
import { Config } from "./config/Config";
|
||||
@@ -82,6 +83,9 @@ export class Initializer {
|
||||
order: ["urlFragment", "navigator"],
|
||||
caches: [],
|
||||
},
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("Failed to initialize i18n", e);
|
||||
});
|
||||
|
||||
// Custom Themeing
|
||||
@@ -129,10 +133,15 @@ export class Initializer {
|
||||
// config
|
||||
if (this.loadStates.config === LoadState.None) {
|
||||
this.loadStates.config = LoadState.Loading;
|
||||
Config.init().then(() => {
|
||||
this.loadStates.config = LoadState.Loaded;
|
||||
this.initStep(resolve);
|
||||
});
|
||||
Config.init().then(
|
||||
() => {
|
||||
this.loadStates.config = LoadState.Loaded;
|
||||
this.initStep(resolve);
|
||||
},
|
||||
(e) => {
|
||||
logger.error("Failed to load config", e);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
//sentry (only initialize after the config is ready)
|
||||
|
||||
@@ -49,12 +49,18 @@ export function useOpenIDSFU(
|
||||
const activeFocus = useActiveLivekitFocus(rtcSession);
|
||||
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
const sfuConfig = activeFocus
|
||||
? await getSFUConfigWithOpenID(client, activeFocus)
|
||||
: undefined;
|
||||
setSFUConfig(sfuConfig);
|
||||
})();
|
||||
if (activeFocus) {
|
||||
getSFUConfigWithOpenID(client, activeFocus).then(
|
||||
(sfuConfig) => {
|
||||
setSFUConfig(sfuConfig);
|
||||
},
|
||||
(e) => {
|
||||
logger.error("Failed to get SFU config", e);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
setSFUConfig(undefined);
|
||||
}
|
||||
}, [client, activeFocus]);
|
||||
|
||||
return sfuConfig;
|
||||
|
||||
@@ -45,7 +45,7 @@ export enum ECAddonConnectionState {
|
||||
// We are switching from one focus to another (or between livekit room aliases on the same focus)
|
||||
ECSwitchingFocus = "ec_switching_focus",
|
||||
// The call has just been initialised and is waiting for credentials to arrive before attempting
|
||||
// to connect. This distinguishes from the 'Disconected' state which is now just for when livekit
|
||||
// to connect. This distinguishes from the 'Disconnected' state which is now just for when livekit
|
||||
// gives up on connectivity and we consider the call to have failed.
|
||||
ECWaiting = "ec_waiting",
|
||||
}
|
||||
@@ -160,9 +160,13 @@ async function connectAndPublish(
|
||||
`Publishing ${screenshareTracks.length} precreated screenshare tracks`,
|
||||
);
|
||||
for (const st of screenshareTracks) {
|
||||
livekitRoom.localParticipant.publishTrack(st, {
|
||||
source: Track.Source.ScreenShare,
|
||||
});
|
||||
livekitRoom.localParticipant
|
||||
.publishTrack(st, {
|
||||
source: Track.Source.ScreenShare,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("Failed to publish screenshare track", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +244,9 @@ export function useECConnectionState(
|
||||
`SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}`,
|
||||
);
|
||||
|
||||
doFocusSwitch();
|
||||
doFocusSwitch().catch((e) => {
|
||||
logger.error("Failed to switch focus", e);
|
||||
});
|
||||
} else if (
|
||||
!sfuConfigValid(currentSFUConfig.current) &&
|
||||
sfuConfigValid(sfuConfig)
|
||||
@@ -257,7 +263,11 @@ export function useECConnectionState(
|
||||
sfuConfig!,
|
||||
initialAudioEnabled,
|
||||
initialAudioOptions,
|
||||
).finally(() => setIsInDoConnect(false));
|
||||
)
|
||||
.catch((e) => {
|
||||
logger.error("Failed to connect to SFU", e);
|
||||
})
|
||||
.finally(() => setIsInDoConnect(false));
|
||||
}
|
||||
|
||||
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
||||
|
||||
@@ -76,9 +76,11 @@ export function useLiveKit(
|
||||
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
|
||||
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
|
||||
} else if (e2eeSystem.kind === E2eeType.SHARED_KEY && e2eeSystem.secret) {
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
||||
e2eeSystem.secret,
|
||||
);
|
||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider)
|
||||
.setKey(e2eeSystem.secret)
|
||||
.catch((e) => {
|
||||
logger.error("Failed to set shared key for E2EE", e);
|
||||
});
|
||||
}
|
||||
}, [e2eeOptions, e2eeSystem, rtcSession]);
|
||||
|
||||
@@ -121,7 +123,9 @@ export function useLiveKit(
|
||||
// useEffect() with an argument that references itself, if E2EE is enabled
|
||||
const room = useMemo(() => {
|
||||
const r = new Room(roomOptions);
|
||||
r.setE2EEEnabled(e2eeSystem.kind !== E2eeType.NONE);
|
||||
r.setE2EEEnabled(e2eeSystem.kind !== E2eeType.NONE).catch((e) => {
|
||||
logger.error("Failed to set E2EE enabled on room", e);
|
||||
});
|
||||
return r;
|
||||
}, [roomOptions, e2eeSystem]);
|
||||
|
||||
@@ -226,7 +230,7 @@ export function useLiveKit(
|
||||
// itself we need might need to update the mute state right away.
|
||||
// This async recursion makes sure that setCamera/MicrophoneEnabled is
|
||||
// called as little times as possible.
|
||||
syncMuteState(iterCount + 1, type);
|
||||
await syncMuteState(iterCount + 1, type);
|
||||
} else {
|
||||
throw new Error(
|
||||
"track with new mute state could not be published",
|
||||
@@ -235,7 +239,7 @@ export function useLiveKit(
|
||||
} catch (e) {
|
||||
if ((e as DOMException).name === "NotAllowedError") {
|
||||
logger.error(
|
||||
"Fatal errror while syncing mute state: resetting",
|
||||
"Fatal error while syncing mute state: resetting",
|
||||
e,
|
||||
);
|
||||
if (type === MuteDevice.Microphone) {
|
||||
@@ -250,14 +254,25 @@ export function useLiveKit(
|
||||
"Failed to sync audio mute state with LiveKit (will retry to sync in 1s):",
|
||||
e,
|
||||
);
|
||||
setTimeout(() => syncMuteState(iterCount + 1, type), 1000);
|
||||
setTimeout(() => {
|
||||
syncMuteState(iterCount + 1, type).catch((e) => {
|
||||
logger.error(
|
||||
`Failed to sync ${MuteDevice[type]} mute state with LiveKit iterCount=${iterCount + 1}`,
|
||||
e,
|
||||
);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
syncMuteState(0, MuteDevice.Microphone);
|
||||
syncMuteState(0, MuteDevice.Camera);
|
||||
syncMuteState(0, MuteDevice.Microphone).catch((e) => {
|
||||
logger.error("Failed to sync audio mute state with LiveKit", e);
|
||||
});
|
||||
syncMuteState(0, MuteDevice.Camera).catch((e) => {
|
||||
logger.error("Failed to sync video mute state with LiveKit", e);
|
||||
});
|
||||
}
|
||||
}, [room, muteStates, connectionState]);
|
||||
|
||||
@@ -304,7 +319,10 @@ export function useLiveKit(
|
||||
// the deviceId hasn't changed (was & still is default).
|
||||
room.localParticipant
|
||||
.getTrackPublication(Track.Source.Microphone)
|
||||
?.audioTrack?.restartTrack();
|
||||
?.audioTrack?.restartTrack()
|
||||
.catch((e) => {
|
||||
logger.error(`Failed to restart audio device track`, e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (id !== undefined && room.getActiveDevice(kind) !== id) {
|
||||
|
||||
@@ -34,7 +34,10 @@ import { App } from "./App";
|
||||
import { init as initRageshake } from "./settings/rageshake";
|
||||
import { Initializer } from "./initializer";
|
||||
|
||||
initRageshake();
|
||||
initRageshake().catch((e) => {
|
||||
logger.error("Failed to initialize rageshake", e);
|
||||
});
|
||||
|
||||
setLogLevel("debug");
|
||||
setLKLogExtension(global.mx_rage_logger.log);
|
||||
|
||||
|
||||
@@ -99,7 +99,9 @@ export class ElementCallOpenTelemetry {
|
||||
|
||||
public dispose(): void {
|
||||
opentelemetry.trace.disable();
|
||||
this._provider?.shutdown();
|
||||
this._provider?.shutdown().catch((e) => {
|
||||
logger.error("Failed to shutdown OpenTelemetry", e);
|
||||
});
|
||||
}
|
||||
|
||||
public get isOtlpEnabled(): boolean {
|
||||
|
||||
@@ -147,11 +147,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
if (audioInput === null) {
|
||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||
} else {
|
||||
const deviceId = await findDeviceByName(
|
||||
audioInput,
|
||||
"audioinput",
|
||||
devices,
|
||||
);
|
||||
const deviceId = findDeviceByName(audioInput, "audioinput", devices);
|
||||
if (!deviceId) {
|
||||
logger.warn("Unknown audio input: " + audioInput);
|
||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||
@@ -167,11 +163,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
if (videoInput === null) {
|
||||
latestMuteStates.current!.video.setEnabled?.(false);
|
||||
} else {
|
||||
const deviceId = await findDeviceByName(
|
||||
videoInput,
|
||||
"videoinput",
|
||||
devices,
|
||||
);
|
||||
const deviceId = findDeviceByName(videoInput, "videoinput", devices);
|
||||
if (!deviceId) {
|
||||
logger.warn("Unknown video input: " + videoInput);
|
||||
latestMuteStates.current!.video.setEnabled?.(false);
|
||||
@@ -187,24 +179,27 @@ export const GroupCallView: FC<Props> = ({
|
||||
|
||||
if (widget && preload && skipLobby) {
|
||||
// In preload mode without lobby we wait for a join action before entering
|
||||
const onJoin = async (
|
||||
ev: CustomEvent<IWidgetApiRequest>,
|
||||
): Promise<void> => {
|
||||
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
|
||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
await widget!.api.transport.reply(ev.detail, {});
|
||||
const onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
(async (): Promise<void> => {
|
||||
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
|
||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
widget!.api.transport.reply(ev.detail, {});
|
||||
})().catch((e) => {
|
||||
logger.error("Error joining RTC session", e);
|
||||
});
|
||||
};
|
||||
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
||||
return (): void => {
|
||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||
};
|
||||
} else if (widget && !preload && skipLobby) {
|
||||
const join = async (): Promise<void> => {
|
||||
// No lobby and no preload: we enter the rtc session right away
|
||||
(async (): Promise<void> => {
|
||||
await defaultDeviceSetup({ audioInput: null, videoInput: null });
|
||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
};
|
||||
// No lobby and no preload: we enter the RTC Session right away.
|
||||
join();
|
||||
})().catch((e) => {
|
||||
logger.error("Error joining RTC session", e);
|
||||
});
|
||||
}
|
||||
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||
|
||||
@@ -213,7 +208,7 @@ export const GroupCallView: FC<Props> = ({
|
||||
const history = useHistory();
|
||||
|
||||
const onLeave = useCallback(
|
||||
async (leaveError?: Error) => {
|
||||
(leaveError?: Error): void => {
|
||||
setLeaveError(leaveError);
|
||||
setLeft(true);
|
||||
|
||||
@@ -227,15 +222,19 @@ export const GroupCallView: FC<Props> = ({
|
||||
);
|
||||
|
||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||
await leaveRTCSession(rtcSession);
|
||||
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
!confineToRoom &&
|
||||
!PosthogAnalytics.instance.isEnabled()
|
||||
) {
|
||||
history.push("/");
|
||||
}
|
||||
leaveRTCSession(rtcSession)
|
||||
.then(() => {
|
||||
if (
|
||||
!isPasswordlessUser &&
|
||||
!confineToRoom &&
|
||||
!PosthogAnalytics.instance.isEnabled()
|
||||
) {
|
||||
history.push("/");
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("Error leaving RTC session", e);
|
||||
});
|
||||
},
|
||||
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
||||
);
|
||||
@@ -243,14 +242,16 @@ export const GroupCallView: FC<Props> = ({
|
||||
useEffect(() => {
|
||||
if (widget && isJoined) {
|
||||
// set widget to sticky once joined.
|
||||
widget!.api.setAlwaysOnScreen(true);
|
||||
widget!.api.setAlwaysOnScreen(true).catch((e) => {
|
||||
logger.error("Error calling setAlwaysOnScreen(true)", e);
|
||||
});
|
||||
|
||||
const onHangup = async (
|
||||
ev: CustomEvent<IWidgetApiRequest>,
|
||||
): Promise<void> => {
|
||||
const onHangup = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
widget!.api.transport.reply(ev.detail, {});
|
||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||
await leaveRTCSession(rtcSession);
|
||||
leaveRTCSession(rtcSession).catch((e) => {
|
||||
logger.error("Failed to leave RTC session", e);
|
||||
});
|
||||
};
|
||||
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
||||
return (): void => {
|
||||
@@ -262,7 +263,9 @@ export const GroupCallView: FC<Props> = ({
|
||||
const onReconnect = useCallback(() => {
|
||||
setLeft(false);
|
||||
setLeaveError(undefined);
|
||||
enterRTCSession(rtcSession, perParticipantE2EE);
|
||||
enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => {
|
||||
logger.error("Error re-entering RTC session on reconnect", e);
|
||||
});
|
||||
}, [rtcSession, perParticipantE2EE]);
|
||||
|
||||
const joinRule = useJoinRule(rtcSession.room);
|
||||
|
||||
@@ -38,6 +38,7 @@ import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||
import classNames from "classnames";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
import { useObservableEagerState } from "observable-hooks";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import LogoMark from "../icons/LogoMark.svg?react";
|
||||
import LogoType from "../icons/LogoType.svg?react";
|
||||
@@ -109,7 +110,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
livekitRoom?.disconnect();
|
||||
livekitRoom?.disconnect().catch((e) => {
|
||||
logger.error("Failed to disconnect from livekit room", e);
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -305,12 +308,16 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
widget?.api.transport.send(
|
||||
gridMode === "grid"
|
||||
? ElementWidgetActions.TileLayout
|
||||
: ElementWidgetActions.SpotlightLayout,
|
||||
{},
|
||||
);
|
||||
widget?.api.transport
|
||||
.send(
|
||||
gridMode === "grid"
|
||||
? ElementWidgetActions.TileLayout
|
||||
: ElementWidgetActions.SpotlightLayout,
|
||||
{},
|
||||
)
|
||||
.catch((e) => {
|
||||
logger.error("Failed to send layout change to widget API", e);
|
||||
});
|
||||
}, [gridMode]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -470,13 +477,15 @@ export const InCallView: FC<InCallViewProps> = ({
|
||||
rtcSession.room.roomId,
|
||||
);
|
||||
|
||||
const toggleScreensharing = useCallback(async () => {
|
||||
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled, {
|
||||
audio: true,
|
||||
selfBrowserSurface: "include",
|
||||
surfaceSwitching: "include",
|
||||
systemAudio: "include",
|
||||
});
|
||||
const toggleScreensharing = useCallback(() => {
|
||||
localParticipant
|
||||
.setScreenShareEnabled(!isScreenShareEnabled, {
|
||||
audio: true,
|
||||
selfBrowserSurface: "include",
|
||||
surfaceSwitching: "include",
|
||||
systemAudio: "include",
|
||||
})
|
||||
.catch(logger.error);
|
||||
}, [localParticipant, isScreenShareEnabled]);
|
||||
|
||||
let footer: JSX.Element | null;
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { IWidgetApiRequest } from "matrix-widget-api";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||
import { useReactiveState } from "../useReactiveState";
|
||||
@@ -83,10 +84,14 @@ export function useMuteStates(): MuteStates {
|
||||
const video = useMuteState(devices.videoInput, () => true);
|
||||
|
||||
useEffect(() => {
|
||||
widget?.api.transport.send(ElementWidgetActions.DeviceMute, {
|
||||
audio_enabled: audio.enabled,
|
||||
video_enabled: video.enabled,
|
||||
});
|
||||
widget?.api.transport
|
||||
.send(ElementWidgetActions.DeviceMute, {
|
||||
audio_enabled: audio.enabled,
|
||||
video_enabled: video.enabled,
|
||||
})
|
||||
.catch((e) =>
|
||||
logger.warn("Could not send DeviceMute action to widget", e),
|
||||
);
|
||||
}, [audio, video]);
|
||||
|
||||
const onMuteStateChangeRequest = useCallback(
|
||||
|
||||
@@ -68,9 +68,13 @@ export const RoomPage: FC = () => {
|
||||
// a URL param, automatically register a passwordless user
|
||||
if (!loading && !authenticated && displayName && !widget) {
|
||||
setIsRegistering(true);
|
||||
registerPasswordlessUser(displayName).finally(() => {
|
||||
setIsRegistering(false);
|
||||
});
|
||||
registerPasswordlessUser(displayName)
|
||||
.catch((e) => {
|
||||
logger.error("Failed to register passwordless user", e);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsRegistering(false);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
|
||||
import { useEffect, useMemo, useRef, FC, ReactNode, useCallback } from "react";
|
||||
import useMeasure from "react-use-measure";
|
||||
import { ResizeObserver } from "@juggle/resize-observer";
|
||||
import { usePreviewTracks } from "@livekit/components-react";
|
||||
import { LocalVideoTrack, Track } from "livekit-client";
|
||||
import classNames from "classnames";
|
||||
@@ -50,7 +51,7 @@ export const VideoPreview: FC<Props> = ({
|
||||
muteStates,
|
||||
children,
|
||||
}) => {
|
||||
const [previewRef, previewBounds] = useMeasure();
|
||||
const [previewRef, previewBounds] = useMeasure({ polyfill: ResizeObserver });
|
||||
|
||||
const devices = useMediaDevices();
|
||||
|
||||
|
||||
@@ -159,23 +159,22 @@ export const useLoadGroupCall = (
|
||||
viaServers: string[],
|
||||
onKnockSent: () => void,
|
||||
): Promise<Room> => {
|
||||
let joinedRoom: Room | null = null;
|
||||
await client.knockRoom(roomId, { viaServers });
|
||||
onKnockSent();
|
||||
const invitePromise = new Promise<void>((resolve, reject) => {
|
||||
return await new Promise<Room>((resolve, reject) => {
|
||||
client.on(
|
||||
RoomEvent.MyMembership,
|
||||
async (room, membership, prevMembership) => {
|
||||
(room, membership, prevMembership): void => {
|
||||
if (roomId !== room.roomId) return;
|
||||
activeRoom.current = room;
|
||||
if (
|
||||
membership === KnownMembership.Invite &&
|
||||
prevMembership === KnownMembership.Knock
|
||||
) {
|
||||
await client.joinRoom(room.roomId, { viaServers });
|
||||
joinedRoom = room;
|
||||
logger.log("Auto-joined %s", room.roomId);
|
||||
resolve();
|
||||
client.joinRoom(room.roomId, { viaServers }).then((room) => {
|
||||
logger.log("Auto-joined %s", room.roomId);
|
||||
resolve(room);
|
||||
}, reject);
|
||||
}
|
||||
if (membership === KnownMembership.Ban) reject(bannedError());
|
||||
if (membership === KnownMembership.Leave)
|
||||
@@ -183,11 +182,6 @@ export const useLoadGroupCall = (
|
||||
},
|
||||
);
|
||||
});
|
||||
await invitePromise;
|
||||
if (!joinedRoom) {
|
||||
throw new Error("Failed to join room after knocking.");
|
||||
}
|
||||
return joinedRoom;
|
||||
};
|
||||
|
||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||
@@ -317,7 +311,7 @@ export const useLoadGroupCall = (
|
||||
|
||||
const observeMyMembership = async (): Promise<void> => {
|
||||
await new Promise((_, reject) => {
|
||||
client.on(RoomEvent.MyMembership, async (_, membership) => {
|
||||
client.on(RoomEvent.MyMembership, (_, membership) => {
|
||||
if (membership === KnownMembership.Leave) reject(removeNoticeError());
|
||||
if (membership === KnownMembership.Ban) reject(bannedError());
|
||||
});
|
||||
|
||||
@@ -83,8 +83,7 @@ async function makePreferredLivekitFoci(
|
||||
`No livekit_service_url is configured so we could not create a focus.
|
||||
Currently we skip computing a focus based on other users in the room.`,
|
||||
);
|
||||
|
||||
return preferredFoci;
|
||||
return Promise.resolve(preferredFoci);
|
||||
|
||||
// TODO: we want to do something like this:
|
||||
//
|
||||
@@ -128,13 +127,18 @@ const widgetPostHangupProcedure = async (
|
||||
// we need to wait until the callEnded event is tracked on posthog.
|
||||
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||
widget.api.setAlwaysOnScreen(false);
|
||||
PosthogAnalytics.instance.logout();
|
||||
|
||||
try {
|
||||
await widget.api.setAlwaysOnScreen(false);
|
||||
} catch (e) {
|
||||
logger.error("Failed to set call widget `alwaysOnScreen` to false", e);
|
||||
}
|
||||
|
||||
// We send the hangup event after the memberships have been updated
|
||||
// calling leaveRTCSession.
|
||||
// We need to wait because this makes the client hosting this widget killing the IFrame.
|
||||
widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
await widget.api.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
};
|
||||
|
||||
export async function leaveRTCSession(
|
||||
|
||||
@@ -18,6 +18,7 @@ import { FC, useCallback } from "react";
|
||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
|
||||
@@ -50,6 +51,8 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
||||
sendLogs,
|
||||
rageshakeRequestId,
|
||||
roomId,
|
||||
}).catch((e) => {
|
||||
logger.error("Failed to send feedback rageshake", e);
|
||||
});
|
||||
|
||||
if (roomId && sendLogs) {
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
import { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { useProfile } from "../profile/useProfile";
|
||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||
@@ -70,6 +71,8 @@ export const ProfileSettingsTab: FC<Props> = ({ client }) => {
|
||||
// @ts-ignore
|
||||
avatar: avatar && avatarSize > 0 ? avatar : undefined,
|
||||
removeAvatar: removeAvatar.current && (!avatar || avatarSize === 0),
|
||||
}).catch((e) => {
|
||||
logger.error("Failed to save profile", e);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FC, useCallback } from "react";
|
||||
import { Button } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Config } from "../config/Config";
|
||||
import styles from "./RageshakeButton.module.css";
|
||||
@@ -34,6 +35,8 @@ export const RageshakeButton: FC<Props> = ({ description }) => {
|
||||
submitRageshake({
|
||||
description,
|
||||
sendLogs: true,
|
||||
}).catch((e) => {
|
||||
logger.error("Failed to send rageshake", e);
|
||||
});
|
||||
}, [submitRageshake, description]);
|
||||
|
||||
|
||||
@@ -138,13 +138,17 @@ class IndexedDBLogStore {
|
||||
this.id = "instance-" + randomString(16);
|
||||
|
||||
loggerInstance.on(ConsoleLoggerEvent.Log, this.onLoggerLog);
|
||||
window.addEventListener("beforeunload", this.flush);
|
||||
window.addEventListener("beforeunload", () => {
|
||||
this.flush().catch((e) =>
|
||||
logger.error("Failed to flush logs before unload", e),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise} Resolves when the store is ready.
|
||||
*/
|
||||
public connect(): Promise<void> {
|
||||
public async connect(): Promise<void> {
|
||||
const req = this.indexedDB.open("logs");
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = (): void => {
|
||||
@@ -200,16 +204,10 @@ class IndexedDBLogStore {
|
||||
// Throttled function to flush logs. We use throttle rather
|
||||
// than debounce as we want logs to be written regularly, otherwise
|
||||
// if there's a constant stream of logging, we'd never write anything.
|
||||
private throttledFlush = throttle(
|
||||
() => {
|
||||
this.flush();
|
||||
},
|
||||
MAX_FLUSH_INTERVAL_MS,
|
||||
{
|
||||
leading: false,
|
||||
trailing: true,
|
||||
},
|
||||
);
|
||||
private throttledFlush = throttle(() => this.flush, MAX_FLUSH_INTERVAL_MS, {
|
||||
leading: false,
|
||||
trailing: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Flush logs to disk.
|
||||
@@ -230,7 +228,7 @@ class IndexedDBLogStore {
|
||||
*
|
||||
* @return {Promise} Resolved when the logs have been flushed.
|
||||
*/
|
||||
public flush = (): Promise<void> => {
|
||||
public flush = async (): Promise<void> => {
|
||||
// check if a flush() operation is ongoing
|
||||
if (this.flushPromise) {
|
||||
if (this.flushAgainPromise) {
|
||||
@@ -238,13 +236,9 @@ class IndexedDBLogStore {
|
||||
return this.flushAgainPromise;
|
||||
}
|
||||
// queue up a flush to occur immediately after the pending one completes.
|
||||
this.flushAgainPromise = this.flushPromise
|
||||
.then(() => {
|
||||
return this.flush();
|
||||
})
|
||||
.then(() => {
|
||||
this.flushAgainPromise = undefined;
|
||||
});
|
||||
this.flushAgainPromise = this.flushPromise.then(this.flush).then(() => {
|
||||
this.flushAgainPromise = undefined;
|
||||
});
|
||||
return this.flushAgainPromise;
|
||||
}
|
||||
// there is no flush promise or there was but it has finished, so do
|
||||
@@ -296,7 +290,7 @@ class IndexedDBLogStore {
|
||||
|
||||
// Returns: a string representing the concatenated logs for this ID.
|
||||
// Stops adding log fragments when the size exceeds maxSize
|
||||
function fetchLogs(id: string, maxSize: number): Promise<string> {
|
||||
async function fetchLogs(id: string, maxSize: number): Promise<string> {
|
||||
const objectStore = db!
|
||||
.transaction("logs", "readonly")
|
||||
.objectStore("logs");
|
||||
@@ -326,7 +320,7 @@ class IndexedDBLogStore {
|
||||
}
|
||||
|
||||
// Returns: A sorted array of log IDs. (newest first)
|
||||
function fetchLogIds(): Promise<string[]> {
|
||||
async function fetchLogIds(): Promise<string[]> {
|
||||
// To gather all the log IDs, query for all records in logslastmod.
|
||||
const o = db!
|
||||
.transaction("logslastmod", "readonly")
|
||||
@@ -346,7 +340,7 @@ class IndexedDBLogStore {
|
||||
});
|
||||
}
|
||||
|
||||
function deleteLogs(id: number): Promise<void> {
|
||||
async function deleteLogs(id: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const txn = db!.transaction(["logs", "logslastmod"], "readwrite");
|
||||
const o = txn.objectStore("logs");
|
||||
@@ -404,7 +398,7 @@ class IndexedDBLogStore {
|
||||
logger.log("Removing logs: ", removeLogIds);
|
||||
// Don't await this because it's non-fatal if we can't clean up
|
||||
// logs.
|
||||
Promise.all(removeLogIds.map((id) => deleteLogs(id))).then(
|
||||
Promise.all(removeLogIds.map(async (id) => deleteLogs(id))).then(
|
||||
() => {
|
||||
logger.log(`Removed ${removeLogIds.length} old logs.`);
|
||||
},
|
||||
@@ -442,7 +436,7 @@ class IndexedDBLogStore {
|
||||
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
|
||||
* resultMapper.
|
||||
*/
|
||||
function selectQuery<T>(
|
||||
async function selectQuery<T>(
|
||||
store: IDBObjectStore,
|
||||
keyRange: IDBKeyRange | undefined,
|
||||
resultMapper: (cursor: IDBCursorWithValue) => T,
|
||||
@@ -471,7 +465,7 @@ declare global {
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_logger: ConsoleLogger;
|
||||
// eslint-disable-next-line no-var, camelcase
|
||||
var mx_rage_initStoragePromise: Promise<void>;
|
||||
var mx_rage_initStoragePromise: Promise<void> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -481,7 +475,7 @@ declare global {
|
||||
* be set up immediately for the logs.
|
||||
* @return {Promise} Resolves when set up.
|
||||
*/
|
||||
export function init(): Promise<void> {
|
||||
export async function init(): Promise<void> {
|
||||
global.mx_rage_logger = new ConsoleLogger();
|
||||
setLogExtension(global.mx_rage_logger.log);
|
||||
|
||||
@@ -493,7 +487,7 @@ export function init(): Promise<void> {
|
||||
* then this no-ops.
|
||||
* @return {Promise} Resolves when complete.
|
||||
*/
|
||||
function tryInitStorage(): Promise<void> {
|
||||
async function tryInitStorage(): Promise<void> {
|
||||
if (global.mx_rage_initStoragePromise) {
|
||||
return global.mx_rage_initStoragePromise;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,9 @@ import { ComponentProps, useCallback, useEffect, useState } from "react";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import pako from "pako";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { ClientEvent } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import {
|
||||
ClientEvent,
|
||||
Crypto,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getLogsForReport } from "./rageshake";
|
||||
import { useClient } from "../ClientContext";
|
||||
@@ -39,84 +35,6 @@ const gzip = (text: string): Blob => {
|
||||
return new Blob([pako.gzip(buf)]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Collects crypto related information.
|
||||
*/
|
||||
async function collectCryptoInfo(
|
||||
cryptoApi: Crypto.CryptoApi,
|
||||
body: FormData,
|
||||
): Promise<void> {
|
||||
body.append("crypto_version", cryptoApi.getVersion());
|
||||
|
||||
const ownDeviceKeys = await cryptoApi.getOwnDeviceKeys();
|
||||
const keys = [
|
||||
`curve25519:${ownDeviceKeys.curve25519}`,
|
||||
`ed25519:${ownDeviceKeys.ed25519}`,
|
||||
];
|
||||
|
||||
body.append("device_keys", keys.join(", "));
|
||||
|
||||
// add cross-signing status information
|
||||
const crossSigningStatus = await cryptoApi.getCrossSigningStatus();
|
||||
|
||||
body.append(
|
||||
"cross_signing_ready",
|
||||
String(await cryptoApi.isCrossSigningReady()),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_key",
|
||||
(await cryptoApi.getCrossSigningKeyId()) ?? "n/a",
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_privkey_in_secret_storage",
|
||||
String(crossSigningStatus.privateKeysInSecretStorage),
|
||||
);
|
||||
|
||||
body.append(
|
||||
"cross_signing_master_privkey_cached",
|
||||
String(crossSigningStatus.privateKeysCachedLocally.masterKey),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_self_signing_privkey_cached",
|
||||
String(crossSigningStatus.privateKeysCachedLocally.selfSigningKey),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_user_signing_privkey_cached",
|
||||
String(crossSigningStatus.privateKeysCachedLocally.userSigningKey),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects information about secret storage and backup.
|
||||
*/
|
||||
async function collectRecoveryInfo(
|
||||
client: MatrixClient,
|
||||
cryptoApi: Crypto.CryptoApi,
|
||||
body: FormData,
|
||||
): Promise<void> {
|
||||
const secretStorage = client.secretStorage;
|
||||
body.append(
|
||||
"secret_storage_ready",
|
||||
String(await cryptoApi.isSecretStorageReady()),
|
||||
);
|
||||
body.append(
|
||||
"secret_storage_key_in_account",
|
||||
String(await secretStorage.hasKey()),
|
||||
);
|
||||
|
||||
body.append(
|
||||
"session_backup_key_in_secret_storage",
|
||||
String(!!(await client.isKeyBackupKeyStored())),
|
||||
);
|
||||
const sessionBackupKeyFromCache =
|
||||
await cryptoApi.getSessionBackupPrivateKey();
|
||||
body.append("session_backup_key_cached", String(!!sessionBackupKeyFromCache));
|
||||
body.append(
|
||||
"session_backup_key_well_formed",
|
||||
String(sessionBackupKeyFromCache instanceof Uint8Array),
|
||||
);
|
||||
}
|
||||
|
||||
interface RageShakeSubmitOptions {
|
||||
sendLogs: boolean;
|
||||
rageshakeRequestId?: string;
|
||||
@@ -202,10 +120,90 @@ export function useSubmitRageshake(): {
|
||||
body.append("room_id", opts.roomId);
|
||||
}
|
||||
|
||||
const crypto = client.getCrypto();
|
||||
if (crypto) {
|
||||
await collectCryptoInfo(crypto, body);
|
||||
await collectRecoveryInfo(client, crypto, body);
|
||||
if (client.isCryptoEnabled()) {
|
||||
const keys = [`ed25519:${client.getDeviceEd25519Key()}`];
|
||||
if (client.getDeviceCurve25519Key) {
|
||||
keys.push(`curve25519:${client.getDeviceCurve25519Key()}`);
|
||||
}
|
||||
body.append("device_keys", keys.join(", "));
|
||||
body.append("cross_signing_key", client.getCrossSigningId()!);
|
||||
|
||||
// add cross-signing status information
|
||||
const crossSigning = client.crypto!.crossSigningInfo;
|
||||
const secretStorage = client.crypto!.secretStorage;
|
||||
|
||||
body.append(
|
||||
"cross_signing_ready",
|
||||
String(await client.isCrossSigningReady()),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_supported_by_hs",
|
||||
String(
|
||||
await client.doesServerSupportUnstableFeature(
|
||||
"org.matrix.e2e_cross_signing",
|
||||
),
|
||||
),
|
||||
);
|
||||
body.append("cross_signing_key", crossSigning.getId()!);
|
||||
body.append(
|
||||
"cross_signing_privkey_in_secret_storage",
|
||||
String(
|
||||
!!(await crossSigning.isStoredInSecretStorage(secretStorage)),
|
||||
),
|
||||
);
|
||||
|
||||
const pkCache = client.getCrossSigningCacheCallbacks();
|
||||
body.append(
|
||||
"cross_signing_master_privkey_cached",
|
||||
String(
|
||||
!!(
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("master"))
|
||||
),
|
||||
),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_self_signing_privkey_cached",
|
||||
String(
|
||||
!!(
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("self_signing"))
|
||||
),
|
||||
),
|
||||
);
|
||||
body.append(
|
||||
"cross_signing_user_signing_privkey_cached",
|
||||
String(
|
||||
!!(
|
||||
pkCache?.getCrossSigningKeyCache &&
|
||||
(await pkCache.getCrossSigningKeyCache("user_signing"))
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
body.append(
|
||||
"secret_storage_ready",
|
||||
String(await client.isSecretStorageReady()),
|
||||
);
|
||||
body.append(
|
||||
"secret_storage_key_in_account",
|
||||
String(!!(await secretStorage.hasKey())),
|
||||
);
|
||||
|
||||
body.append(
|
||||
"session_backup_key_in_secret_storage",
|
||||
String(!!(await client.isKeyBackupKeyStored())),
|
||||
);
|
||||
const sessionBackupKeyFromCache =
|
||||
await client.crypto!.getSessionBackupPrivateKey();
|
||||
body.append(
|
||||
"session_backup_key_cached",
|
||||
String(!!sessionBackupKeyFromCache),
|
||||
);
|
||||
body.append(
|
||||
"session_backup_key_well_formed",
|
||||
String(sessionBackupKeyFromCache instanceof Uint8Array),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,10 +306,14 @@ export function useRageshakeRequest(): (
|
||||
|
||||
const sendRageshakeRequest = useCallback(
|
||||
(roomId: string, rageshakeRequestId: string) => {
|
||||
// @ts-expect-error - org.matrix.rageshake_request is not part of `keyof TimelineEvents` but it is okay to sent a custom event.
|
||||
client!.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||
request_id: rageshakeRequestId,
|
||||
});
|
||||
client!
|
||||
// @ts-expect-error - org.matrix.rageshake_request is not part of `keyof TimelineEvents` but it is okay to sent a custom event.
|
||||
.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||
request_id: rageshakeRequestId,
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("Failed to send org.matrix.rageshake_request event", e);
|
||||
});
|
||||
},
|
||||
[client],
|
||||
);
|
||||
|
||||
@@ -288,7 +288,7 @@ export class CallViewModel extends ViewModel {
|
||||
});
|
||||
}),
|
||||
// Then unhold them
|
||||
]).then(() => Promise.resolve({ unhold: ps })),
|
||||
]).then(() => ({ unhold: ps })),
|
||||
);
|
||||
} else {
|
||||
return EMPTY;
|
||||
|
||||
@@ -28,19 +28,22 @@ export function useWakeLock(): void {
|
||||
|
||||
// The lock is automatically released whenever the window goes invisible,
|
||||
// so we need to reacquire it on visibility changes
|
||||
const onVisibilityChange = async (): Promise<void> => {
|
||||
const onVisibilityChange = (): void => {
|
||||
if (document.visibilityState === "visible") {
|
||||
try {
|
||||
lock = await navigator.wakeLock.request("screen");
|
||||
// Handle the edge case where this component unmounts before the
|
||||
// promise resolves
|
||||
if (!mounted)
|
||||
lock
|
||||
.release()
|
||||
.catch((e) => logger.warn("Can't release wake lock", e));
|
||||
} catch (e) {
|
||||
logger.warn("Can't acquire wake lock", e);
|
||||
}
|
||||
navigator.wakeLock.request("screen").then(
|
||||
(newLock) => {
|
||||
lock = newLock;
|
||||
// Handle the edge case where this component unmounts before the
|
||||
// promise resolves
|
||||
if (!mounted)
|
||||
lock
|
||||
.release()
|
||||
.catch((e) => logger.warn("Can't release wake lock", e));
|
||||
},
|
||||
(e) => {
|
||||
logger.warn("Can't acquire wake lock", e);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -269,34 +269,40 @@ export async function createRoom(
|
||||
});
|
||||
|
||||
// Wait for the room to arrive
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const onRoom = async (room: Room): Promise<void> => {
|
||||
if (room.roomId === (await createPromise).room_id) {
|
||||
resolve();
|
||||
cleanUp();
|
||||
}
|
||||
};
|
||||
const roomId = await new Promise<string>((resolve, reject) => {
|
||||
createPromise.catch((e) => {
|
||||
reject(e);
|
||||
cleanUp();
|
||||
});
|
||||
|
||||
const onRoom = (room: Room): void => {
|
||||
createPromise.then(
|
||||
(result) => {
|
||||
if (room.roomId === result.room_id) {
|
||||
resolve(room.roomId);
|
||||
cleanUp();
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
logger.error("Failed to wait for the room to arrive", e);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const cleanUp = (): void => {
|
||||
client.off(ClientEvent.Room, onRoom);
|
||||
};
|
||||
client.on(ClientEvent.Room, onRoom);
|
||||
});
|
||||
|
||||
const result = await createPromise;
|
||||
|
||||
let password;
|
||||
let password: string | undefined;
|
||||
if (e2ee == E2eeType.SHARED_KEY) {
|
||||
password = secureRandomBase64Url(16);
|
||||
saveKeyForRoom(result.room_id, password);
|
||||
saveKeyForRoom(roomId, password);
|
||||
}
|
||||
|
||||
return {
|
||||
roomId: result.room_id,
|
||||
roomId,
|
||||
alias: e2ee ? undefined : fullAliasFromRoomName(name, client),
|
||||
password,
|
||||
};
|
||||
|
||||
@@ -20,11 +20,11 @@ limitations under the License.
|
||||
* @param devices The list of devices to search
|
||||
* @returns A matching media device or undefined if no matching device was found
|
||||
*/
|
||||
export async function findDeviceByName(
|
||||
export function findDeviceByName(
|
||||
deviceName: string,
|
||||
kind: MediaDeviceKind,
|
||||
devices: MediaDeviceInfo[],
|
||||
): Promise<string | undefined> {
|
||||
): string | undefined {
|
||||
const deviceInfo = devices.find(
|
||||
(d) => d.kind === kind && d.label === deviceName,
|
||||
);
|
||||
|
||||
@@ -20,17 +20,21 @@ import posthog from "posthog-js";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import { afterEach, beforeEach } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { Config } from "./config/Config";
|
||||
|
||||
// Bare-minimum i18n config
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: "en-GB",
|
||||
fallbackLng: "en-GB",
|
||||
interpolation: {
|
||||
escapeValue: false, // React has built-in XSS protections
|
||||
},
|
||||
});
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: "en-GB",
|
||||
fallbackLng: "en-GB",
|
||||
interpolation: {
|
||||
escapeValue: false, // React has built-in XSS protections
|
||||
},
|
||||
})
|
||||
.catch((e) => logger.warn("Failed to init i18n for testing", e));
|
||||
|
||||
Config.initDefault();
|
||||
posthog.opt_out_capturing();
|
||||
|
||||
@@ -167,17 +167,15 @@ export const widget = ((): WidgetHelpers | null => {
|
||||
false,
|
||||
);
|
||||
|
||||
const clientPromise = new Promise<MatrixClient>((resolve) => {
|
||||
(async (): Promise<void> => {
|
||||
// Wait for the config file to be ready (we load very early on so it might not
|
||||
// be otherwise)
|
||||
await Config.init();
|
||||
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
||||
resolve(client);
|
||||
})();
|
||||
});
|
||||
const clientPromise = async (): Promise<MatrixClient> => {
|
||||
// Wait for the config file to be ready (we load very early on so it might not
|
||||
// be otherwise)
|
||||
await Config.init();
|
||||
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
||||
return client;
|
||||
};
|
||||
|
||||
return { api, lazyActions, client: clientPromise };
|
||||
return { api, lazyActions, client: clientPromise() };
|
||||
} else {
|
||||
if (import.meta.env.MODE !== "test")
|
||||
logger.info("No widget API available");
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
"resolveJsonModule": true,
|
||||
// Workaround for https://github.com/microsoft/TypeScript/issues/55132
|
||||
"useDefineForClassFields": false,
|
||||
"allowImportingTsExtensions": true,
|
||||
"paths": {
|
||||
// These imports within @livekit/components-core and
|
||||
// @livekit/components-react are broken under the "bundler" module
|
||||
|
||||
@@ -76,6 +76,7 @@ export default defineConfig(({ mode }) => {
|
||||
"react-dom",
|
||||
"matrix-js-sdk",
|
||||
"react-use-measure",
|
||||
"@juggle/resize-observer",
|
||||
// These packages modify the document based on some module-level global
|
||||
// state, and don't play nicely with duplicate copies of themselves
|
||||
// https://github.com/radix-ui/primitives/issues/1241#issuecomment-1847837850
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@@ -1864,6 +1864,11 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@juggle/resize-observer@^3.3.1":
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
|
||||
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
|
||||
|
||||
"@livekit/components-core@0.11.2", "@livekit/components-core@^0.11.0":
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@livekit/components-core/-/components-core-0.11.2.tgz#fded2e207155e4737ed52830d48b75ae2eaaf449"
|
||||
@@ -5950,9 +5955,10 @@ matrix-events-sdk@0.0.1:
|
||||
resolved "https://registry.yarnpkg.com/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz#c8c38911e2cb29023b0bbac8d6f32e0de2c957dd"
|
||||
integrity sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==
|
||||
|
||||
matrix-js-sdk@matrix-org/matrix-js-sdk#169e8f86139111574a3738f8557c6fa4b2a199db:
|
||||
matrix-js-sdk@^v34.4.0:
|
||||
version "34.4.0"
|
||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/169e8f86139111574a3738f8557c6fa4b2a199db"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-34.4.0.tgz#ceb3403c92dbff3b37e776745a2997ee78fa1eac"
|
||||
integrity sha512-bI5xJZS3/qhjPQqQL5HhOQ1iBvnHxiqhS2zgzk9SarEuXiH08wbVl9gAAuDqOYE3miNGs4WQQJ19MoaUEOnNwg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@matrix-org/matrix-sdk-crypto-wasm" "^7.0.0"
|
||||
|
||||
Reference in New Issue
Block a user