Enable lint rules for Promise handling to discourage misuse of them. (#2607)
* Enable lint rules for Promise handling to discourage misuse of them. Squashed all of Hugh's commits into one. --------- Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
This commit is contained in:
@@ -39,6 +39,12 @@ module.exports = {
|
|||||||
// We should use the js-sdk logger, never console directly.
|
// We should use the js-sdk logger, never console directly.
|
||||||
"no-console": ["error"],
|
"no-console": ["error"],
|
||||||
"react/display-name": "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: {
|
settings: {
|
||||||
react: {
|
react: {
|
||||||
|
|||||||
13
src/App.tsx
13
src/App.tsx
@@ -15,6 +15,7 @@ import {
|
|||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { History } from "history";
|
import { History } from "history";
|
||||||
import { TooltipProvider } from "@vector-im/compound-web";
|
import { TooltipProvider } from "@vector-im/compound-web";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { HomePage } from "./home/HomePage";
|
import { HomePage } from "./home/HomePage";
|
||||||
import { LoginPage } from "./auth/LoginPage";
|
import { LoginPage } from "./auth/LoginPage";
|
||||||
@@ -61,11 +62,13 @@ interface AppProps {
|
|||||||
export const App: FC<AppProps> = ({ history }) => {
|
export const App: FC<AppProps> = ({ history }) => {
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Initializer.init()?.then(() => {
|
Initializer.init()
|
||||||
if (loaded) return;
|
?.then(async () => {
|
||||||
setLoaded(true);
|
if (loaded) return;
|
||||||
widget?.api.sendContentLoaded();
|
setLoaded(true);
|
||||||
});
|
await widget?.api.sendContentLoaded();
|
||||||
|
})
|
||||||
|
.catch(logger.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorPage = <CrashView />;
|
const errorPage = <CrashView />;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const UserMenuContainer: FC<Props> = ({ preventNavigation = false }) => {
|
|||||||
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
|
const [settingsTab, setSettingsTab] = useState(defaultSettingsTab);
|
||||||
|
|
||||||
const onAction = useCallback(
|
const onAction = useCallback(
|
||||||
async (value: string) => {
|
(value: string) => {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case "user":
|
case "user":
|
||||||
setSettingsTab("profile");
|
setSettingsTab("profile");
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export class PosthogAnalytics {
|
|||||||
this.posthog.identify(analyticsID);
|
this.posthog.identify(analyticsID);
|
||||||
} else {
|
} else {
|
||||||
logger.info(
|
logger.info(
|
||||||
"No analyticsID is availble. Should not try to setup posthog",
|
"No analyticsID is available. Should not try to setup posthog",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,9 @@ export class PosthogAnalytics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public onLoginStatusChanged(): void {
|
public onLoginStatusChanged(): void {
|
||||||
this.maybeIdentifyUser();
|
this.maybeIdentifyUser().catch(() =>
|
||||||
|
logger.log("Could not identify user on login status change"),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateSuperProperties(): void {
|
private updateSuperProperties(): void {
|
||||||
@@ -373,20 +375,27 @@ export class PosthogAnalytics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async trackEvent<E extends IPosthogEvent>(
|
public trackEvent<E extends IPosthogEvent>(
|
||||||
{ eventName, ...properties }: E,
|
{ eventName, ...properties }: E,
|
||||||
options?: CaptureOptions,
|
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) {
|
if (this.identificationPromise) {
|
||||||
// only make calls to posthog after the identificaion is done
|
// only make calls to posthog after the identification is done
|
||||||
await this.identificationPromise;
|
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 {
|
private startListeningToSettingsChanges(): void {
|
||||||
@@ -400,7 +409,9 @@ export class PosthogAnalytics {
|
|||||||
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
// won't be called (i.e. this.anonymity will be left as the default, until the setting changes)
|
||||||
optInAnalytics.value.subscribe((optIn) => {
|
optInAnalytics.value.subscribe((optIn) => {
|
||||||
this.setAnonymity(optIn ? Anonymity.Pseudonymous : Anonymity.Disabled);
|
this.setAnonymity(optIn ? Anonymity.Pseudonymous : Anonymity.Disabled);
|
||||||
this.maybeIdentifyUser();
|
this.maybeIdentifyUser().catch(() =>
|
||||||
|
logger.log("Could not identify user"),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
|||||||
|
|
||||||
public onStart(span: Span): void {
|
public onStart(span: Span): void {
|
||||||
// Hack: Yield to allow attributes to be set before processing
|
// Hack: Yield to allow attributes to be set before processing
|
||||||
Promise.resolve().then(() => {
|
try {
|
||||||
switch (span.name) {
|
switch (span.name) {
|
||||||
case "matrix.groupCallMembership":
|
case "matrix.groupCallMembership":
|
||||||
this.onGroupCallMembershipStart(span);
|
this.onGroupCallMembershipStart(span);
|
||||||
@@ -43,7 +43,10 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
|||||||
this.onSummaryReportStart(span);
|
this.onSummaryReportStart(span);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
|
// log to avoid tripping @typescript-eslint/no-unused-vars
|
||||||
|
logger.debug(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onEnd(span: ReadableSpan): void {
|
public onEnd(span: ReadableSpan): void {
|
||||||
@@ -148,7 +151,7 @@ export class PosthogSpanProcessor implements SpanProcessor {
|
|||||||
/**
|
/**
|
||||||
* Shutdown the processor.
|
* Shutdown the processor.
|
||||||
*/
|
*/
|
||||||
public shutdown(): Promise<void> {
|
public async shutdown(): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function useInteractiveLogin(
|
|||||||
|
|
||||||
const interactiveAuth = new InteractiveAuth({
|
const interactiveAuth = new InteractiveAuth({
|
||||||
matrixClient: authClient,
|
matrixClient: authClient,
|
||||||
doRequest: (): Promise<LoginResponse> =>
|
doRequest: async (): Promise<LoginResponse> =>
|
||||||
authClient.login("m.login.password", {
|
authClient.login("m.login.password", {
|
||||||
identifier: {
|
identifier: {
|
||||||
type: "m.id.user",
|
type: "m.id.user",
|
||||||
@@ -49,9 +49,8 @@ export function useInteractiveLogin(
|
|||||||
password,
|
password,
|
||||||
}),
|
}),
|
||||||
stateUpdated: (): void => {},
|
stateUpdated: (): void => {},
|
||||||
requestEmailToken: (): Promise<{ sid: string }> => {
|
requestEmailToken: async (): Promise<{ sid: string }> =>
|
||||||
return Promise.resolve({ sid: "" });
|
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
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
MatrixClient,
|
MatrixClient,
|
||||||
RegisterResponse,
|
RegisterResponse,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { initClient } from "../utils/matrix";
|
import { initClient } from "../utils/matrix";
|
||||||
import { Session } from "../ClientContext";
|
import { Session } from "../ClientContext";
|
||||||
@@ -66,7 +67,7 @@ export const useInteractiveRegistration = (
|
|||||||
): Promise<[MatrixClient, Session]> => {
|
): Promise<[MatrixClient, Session]> => {
|
||||||
const interactiveAuth = new InteractiveAuth({
|
const interactiveAuth = new InteractiveAuth({
|
||||||
matrixClient: authClient.current!,
|
matrixClient: authClient.current!,
|
||||||
doRequest: (auth): Promise<RegisterResponse> =>
|
doRequest: async (auth): Promise<RegisterResponse> =>
|
||||||
authClient.current!.registerRequest({
|
authClient.current!.registerRequest({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
@@ -78,19 +79,26 @@ export const useInteractiveRegistration = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nextStage === "m.login.terms") {
|
if (nextStage === "m.login.terms") {
|
||||||
interactiveAuth.submitAuthDict({
|
interactiveAuth
|
||||||
type: "m.login.terms",
|
.submitAuthDict({
|
||||||
});
|
type: "m.login.terms",
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(e);
|
||||||
|
});
|
||||||
} else if (nextStage === "m.login.recaptcha") {
|
} else if (nextStage === "m.login.recaptcha") {
|
||||||
interactiveAuth.submitAuthDict({
|
interactiveAuth
|
||||||
type: "m.login.recaptcha",
|
.submitAuthDict({
|
||||||
response: recaptchaResponse,
|
type: "m.login.recaptcha",
|
||||||
});
|
response: recaptchaResponse,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestEmailToken: (): Promise<{ sid: string }> => {
|
requestEmailToken: async (): Promise<{ sid: string }> =>
|
||||||
return Promise.resolve({ sid: "dummy" });
|
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
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function useRecaptcha(sitekey?: string): {
|
|||||||
}
|
}
|
||||||
}, [recaptchaId, sitekey]);
|
}, [recaptchaId, sitekey]);
|
||||||
|
|
||||||
const execute = useCallback((): Promise<string> => {
|
const execute = useCallback(async (): Promise<string> => {
|
||||||
if (!sitekey) {
|
if (!sitekey) {
|
||||||
return Promise.resolve("");
|
return Promise.resolve("");
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,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>(
|
const iframe = document.querySelector<HTMLIFrameElement>(
|
||||||
'iframe[src*="recaptcha/api2/bframe"]',
|
'iframe[src*="recaptcha/api2/bframe"]',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "./ConfigOptions";
|
} from "./ConfigOptions";
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
private static internalInstance: Config;
|
private static internalInstance: Config | undefined;
|
||||||
|
|
||||||
public static get(): ConfigOptions {
|
public static get(): ConfigOptions {
|
||||||
if (!this.internalInstance?.config)
|
if (!this.internalInstance?.config)
|
||||||
@@ -21,23 +21,23 @@ export class Config {
|
|||||||
return this.internalInstance.config;
|
return this.internalInstance.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static init(): Promise<void> {
|
public static async init(): Promise<void> {
|
||||||
if (Config.internalInstance?.initPromise) {
|
if (!Config.internalInstance?.initPromise) {
|
||||||
return Config.internalInstance.initPromise;
|
const internalInstance = new Config();
|
||||||
}
|
Config.internalInstance = internalInstance;
|
||||||
Config.internalInstance = new Config();
|
|
||||||
Config.internalInstance.initPromise = new Promise<void>((resolve) => {
|
Config.internalInstance.initPromise = downloadConfig(
|
||||||
downloadConfig("../config.json").then((config) => {
|
"../config.json",
|
||||||
Config.internalInstance.config = { ...DEFAULT_CONFIG, ...config };
|
).then((config) => {
|
||||||
resolve();
|
internalInstance.config = { ...DEFAULT_CONFIG, ...config };
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
return Config.internalInstance.initPromise;
|
return Config.internalInstance.initPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a alternative initializer that does not load anything
|
* 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.
|
* default config.
|
||||||
*
|
*
|
||||||
* It is supposed to only be used in tests. (It is executed in `vite.setup.js`)
|
* It is supposed to only be used in tests. (It is executed in `vite.setup.js`)
|
||||||
|
|||||||
@@ -46,19 +46,25 @@ export class MatrixKeyProvider extends BaseKeyProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onEncryptionKeyChanged = async (
|
private onEncryptionKeyChanged = (
|
||||||
encryptionKey: Uint8Array,
|
encryptionKey: Uint8Array,
|
||||||
encryptionKeyIndex: number,
|
encryptionKeyIndex: number,
|
||||||
participantId: string,
|
participantId: string,
|
||||||
): Promise<void> => {
|
): void => {
|
||||||
this.onSetEncryptionKey(
|
createKeyMaterialFromBuffer(encryptionKey).then(
|
||||||
await createKeyMaterialFromBuffer(encryptionKey),
|
(keyMaterial) => {
|
||||||
participantId,
|
this.onSetEncryptionKey(keyMaterial, participantId, encryptionKeyIndex);
|
||||||
encryptionKeyIndex,
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Sent new key to livekit room=${this.rtcSession?.room.roomId} participantId=${participantId} encryptionKeyIndex=${encryptionKeyIndex}`,
|
`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,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import styles from "./Grid.module.css";
|
import styles from "./Grid.module.css";
|
||||||
import { useMergedRefs } from "../useMergedRefs";
|
import { useMergedRefs } from "../useMergedRefs";
|
||||||
@@ -353,7 +354,7 @@ export function Grid<
|
|||||||
// Because we're using react-spring in imperative mode, we're responsible for
|
// Because we're using react-spring in imperative mode, we're responsible for
|
||||||
// firing animations manually whenever the tiles array updates
|
// firing animations manually whenever the tiles array updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
springRef.start();
|
springRef.start().forEach((p) => void p.catch(logger.error));
|
||||||
}, [placedTiles, springRef]);
|
}, [placedTiles, springRef]);
|
||||||
|
|
||||||
const animateDraggedTile = (
|
const animateDraggedTile = (
|
||||||
@@ -390,7 +391,8 @@ export function Grid<
|
|||||||
((key): boolean =>
|
((key): boolean =>
|
||||||
key === "zIndex" || key === "x" || key === "y"),
|
key === "zIndex" || key === "x" || key === "y"),
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
|
.catch(logger.error);
|
||||||
|
|
||||||
if (endOfGesture)
|
if (endOfGesture)
|
||||||
callback({
|
callback({
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe("CallList", () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should show room", async () => {
|
it("should show room", () => {
|
||||||
const rooms = [
|
const rooms = [
|
||||||
{
|
{
|
||||||
roomName: "Room #1",
|
roomName: "Room #1",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { initReactI18next } from "react-i18next";
|
|||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import Backend from "i18next-http-backend";
|
import Backend from "i18next-http-backend";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { getUrlParams } from "./UrlParams";
|
import { getUrlParams } from "./UrlParams";
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
@@ -73,6 +74,9 @@ export class Initializer {
|
|||||||
order: ["urlFragment", "navigator"],
|
order: ["urlFragment", "navigator"],
|
||||||
caches: [],
|
caches: [],
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Failed to initialize i18n", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom Themeing
|
// Custom Themeing
|
||||||
@@ -120,10 +124,15 @@ export class Initializer {
|
|||||||
// config
|
// config
|
||||||
if (this.loadStates.config === LoadState.None) {
|
if (this.loadStates.config === LoadState.None) {
|
||||||
this.loadStates.config = LoadState.Loading;
|
this.loadStates.config = LoadState.Loading;
|
||||||
Config.init().then(() => {
|
Config.init().then(
|
||||||
this.loadStates.config = LoadState.Loaded;
|
() => {
|
||||||
this.initStep(resolve);
|
this.loadStates.config = LoadState.Loaded;
|
||||||
});
|
this.initStep(resolve);
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
logger.error("Failed to load config", e);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//sentry (only initialize after the config is ready)
|
//sentry (only initialize after the config is ready)
|
||||||
|
|||||||
@@ -40,12 +40,18 @@ export function useOpenIDSFU(
|
|||||||
const activeFocus = useActiveLivekitFocus(rtcSession);
|
const activeFocus = useActiveLivekitFocus(rtcSession);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async (): Promise<void> => {
|
if (activeFocus) {
|
||||||
const sfuConfig = activeFocus
|
getSFUConfigWithOpenID(client, activeFocus).then(
|
||||||
? await getSFUConfigWithOpenID(client, activeFocus)
|
(sfuConfig) => {
|
||||||
: undefined;
|
setSFUConfig(sfuConfig);
|
||||||
setSFUConfig(sfuConfig);
|
},
|
||||||
})();
|
(e) => {
|
||||||
|
logger.error("Failed to get SFU config", e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setSFUConfig(undefined);
|
||||||
|
}
|
||||||
}, [client, activeFocus]);
|
}, [client, activeFocus]);
|
||||||
|
|
||||||
return sfuConfig;
|
return sfuConfig;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export enum ECAddonConnectionState {
|
|||||||
// We are switching from one focus to another (or between livekit room aliases on the same focus)
|
// We are switching from one focus to another (or between livekit room aliases on the same focus)
|
||||||
ECSwitchingFocus = "ec_switching_focus",
|
ECSwitchingFocus = "ec_switching_focus",
|
||||||
// The call has just been initialised and is waiting for credentials to arrive before attempting
|
// 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.
|
// gives up on connectivity and we consider the call to have failed.
|
||||||
ECWaiting = "ec_waiting",
|
ECWaiting = "ec_waiting",
|
||||||
}
|
}
|
||||||
@@ -151,9 +151,13 @@ async function connectAndPublish(
|
|||||||
`Publishing ${screenshareTracks.length} precreated screenshare tracks`,
|
`Publishing ${screenshareTracks.length} precreated screenshare tracks`,
|
||||||
);
|
);
|
||||||
for (const st of screenshareTracks) {
|
for (const st of screenshareTracks) {
|
||||||
livekitRoom.localParticipant.publishTrack(st, {
|
livekitRoom.localParticipant
|
||||||
source: Track.Source.ScreenShare,
|
.publishTrack(st, {
|
||||||
});
|
source: Track.Source.ScreenShare,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Failed to publish screenshare track", e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +235,9 @@ export function useECConnectionState(
|
|||||||
`SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}`,
|
`SFU config changed! URL was ${currentSFUConfig.current?.url} now ${sfuConfig?.url}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
doFocusSwitch();
|
doFocusSwitch().catch((e) => {
|
||||||
|
logger.error("Failed to switch focus", e);
|
||||||
|
});
|
||||||
} else if (
|
} else if (
|
||||||
!sfuConfigValid(currentSFUConfig.current) &&
|
!sfuConfigValid(currentSFUConfig.current) &&
|
||||||
sfuConfigValid(sfuConfig)
|
sfuConfigValid(sfuConfig)
|
||||||
@@ -248,7 +254,11 @@ export function useECConnectionState(
|
|||||||
sfuConfig!,
|
sfuConfig!,
|
||||||
initialAudioEnabled,
|
initialAudioEnabled,
|
||||||
initialAudioOptions,
|
initialAudioOptions,
|
||||||
).finally(() => setIsInDoConnect(false));
|
)
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Failed to connect to SFU", e);
|
||||||
|
})
|
||||||
|
.finally(() => setIsInDoConnect(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
||||||
|
|||||||
@@ -67,9 +67,11 @@ export function useLiveKit(
|
|||||||
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
|
if (e2eeSystem.kind === E2eeType.PER_PARTICIPANT) {
|
||||||
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
|
(e2eeOptions.keyProvider as MatrixKeyProvider).setRTCSession(rtcSession);
|
||||||
} else if (e2eeSystem.kind === E2eeType.SHARED_KEY && e2eeSystem.secret) {
|
} else if (e2eeSystem.kind === E2eeType.SHARED_KEY && e2eeSystem.secret) {
|
||||||
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider).setKey(
|
(e2eeOptions.keyProvider as ExternalE2EEKeyProvider)
|
||||||
e2eeSystem.secret,
|
.setKey(e2eeSystem.secret)
|
||||||
);
|
.catch((e) => {
|
||||||
|
logger.error("Failed to set shared key for E2EE", e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [e2eeOptions, e2eeSystem, rtcSession]);
|
}, [e2eeOptions, e2eeSystem, rtcSession]);
|
||||||
|
|
||||||
@@ -112,7 +114,9 @@ export function useLiveKit(
|
|||||||
// useEffect() with an argument that references itself, if E2EE is enabled
|
// useEffect() with an argument that references itself, if E2EE is enabled
|
||||||
const room = useMemo(() => {
|
const room = useMemo(() => {
|
||||||
const r = new Room(roomOptions);
|
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;
|
return r;
|
||||||
}, [roomOptions, e2eeSystem]);
|
}, [roomOptions, e2eeSystem]);
|
||||||
|
|
||||||
@@ -217,7 +221,7 @@ export function useLiveKit(
|
|||||||
// itself we need might need to update the mute state right away.
|
// itself we need might need to update the mute state right away.
|
||||||
// This async recursion makes sure that setCamera/MicrophoneEnabled is
|
// This async recursion makes sure that setCamera/MicrophoneEnabled is
|
||||||
// called as little times as possible.
|
// called as little times as possible.
|
||||||
syncMuteState(iterCount + 1, type);
|
await syncMuteState(iterCount + 1, type);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"track with new mute state could not be published",
|
"track with new mute state could not be published",
|
||||||
@@ -226,7 +230,7 @@ export function useLiveKit(
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if ((e as DOMException).name === "NotAllowedError") {
|
if ((e as DOMException).name === "NotAllowedError") {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Fatal errror while syncing mute state: resetting",
|
"Fatal error while syncing mute state: resetting",
|
||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
if (type === MuteDevice.Microphone) {
|
if (type === MuteDevice.Microphone) {
|
||||||
@@ -241,14 +245,25 @@ export function useLiveKit(
|
|||||||
"Failed to sync audio mute state with LiveKit (will retry to sync in 1s):",
|
"Failed to sync audio mute state with LiveKit (will retry to sync in 1s):",
|
||||||
e,
|
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.Microphone).catch((e) => {
|
||||||
syncMuteState(0, MuteDevice.Camera);
|
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]);
|
}, [room, muteStates, connectionState]);
|
||||||
|
|
||||||
@@ -295,7 +310,10 @@ export function useLiveKit(
|
|||||||
// the deviceId hasn't changed (was & still is default).
|
// the deviceId hasn't changed (was & still is default).
|
||||||
room.localParticipant
|
room.localParticipant
|
||||||
.getTrackPublication(Track.Source.Microphone)
|
.getTrackPublication(Track.Source.Microphone)
|
||||||
?.audioTrack?.restartTrack();
|
?.audioTrack?.restartTrack()
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error(`Failed to restart audio device track`, e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (id !== undefined && room.getActiveDevice(kind) !== id) {
|
if (id !== undefined && room.getActiveDevice(kind) !== id) {
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ import { App } from "./App";
|
|||||||
import { init as initRageshake } from "./settings/rageshake";
|
import { init as initRageshake } from "./settings/rageshake";
|
||||||
import { Initializer } from "./initializer";
|
import { Initializer } from "./initializer";
|
||||||
|
|
||||||
initRageshake();
|
initRageshake().catch((e) => {
|
||||||
|
logger.error("Failed to initialize rageshake", e);
|
||||||
|
});
|
||||||
|
|
||||||
setLogLevel("debug");
|
setLogLevel("debug");
|
||||||
setLKLogExtension(global.mx_rage_logger.log);
|
setLKLogExtension(global.mx_rage_logger.log);
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ export class ElementCallOpenTelemetry {
|
|||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
opentelemetry.trace.disable();
|
opentelemetry.trace.disable();
|
||||||
this._provider?.shutdown();
|
this._provider?.shutdown().catch((e) => {
|
||||||
|
logger.error("Failed to shutdown OpenTelemetry", e);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isOtlpEnabled(): boolean {
|
public get isOtlpEnabled(): boolean {
|
||||||
|
|||||||
@@ -138,11 +138,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
if (audioInput === null) {
|
if (audioInput === null) {
|
||||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||||
} else {
|
} else {
|
||||||
const deviceId = await findDeviceByName(
|
const deviceId = findDeviceByName(audioInput, "audioinput", devices);
|
||||||
audioInput,
|
|
||||||
"audioinput",
|
|
||||||
devices,
|
|
||||||
);
|
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
logger.warn("Unknown audio input: " + audioInput);
|
logger.warn("Unknown audio input: " + audioInput);
|
||||||
latestMuteStates.current!.audio.setEnabled?.(false);
|
latestMuteStates.current!.audio.setEnabled?.(false);
|
||||||
@@ -158,11 +154,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
if (videoInput === null) {
|
if (videoInput === null) {
|
||||||
latestMuteStates.current!.video.setEnabled?.(false);
|
latestMuteStates.current!.video.setEnabled?.(false);
|
||||||
} else {
|
} else {
|
||||||
const deviceId = await findDeviceByName(
|
const deviceId = findDeviceByName(videoInput, "videoinput", devices);
|
||||||
videoInput,
|
|
||||||
"videoinput",
|
|
||||||
devices,
|
|
||||||
);
|
|
||||||
if (!deviceId) {
|
if (!deviceId) {
|
||||||
logger.warn("Unknown video input: " + videoInput);
|
logger.warn("Unknown video input: " + videoInput);
|
||||||
latestMuteStates.current!.video.setEnabled?.(false);
|
latestMuteStates.current!.video.setEnabled?.(false);
|
||||||
@@ -178,24 +170,27 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
|
|
||||||
if (widget && preload && skipLobby) {
|
if (widget && preload && skipLobby) {
|
||||||
// In preload mode without lobby we wait for a join action before entering
|
// In preload mode without lobby we wait for a join action before entering
|
||||||
const onJoin = async (
|
const onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||||
ev: CustomEvent<IWidgetApiRequest>,
|
(async (): Promise<void> => {
|
||||||
): Promise<void> => {
|
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
|
||||||
await defaultDeviceSetup(ev.detail.data as unknown as JoinCallData);
|
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
await widget!.api.transport.reply(ev.detail, {});
|
})().catch((e) => {
|
||||||
|
logger.error("Error joining RTC session", e);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
widget.lazyActions.on(ElementWidgetActions.JoinCall, onJoin);
|
||||||
return (): void => {
|
return (): void => {
|
||||||
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
widget!.lazyActions.off(ElementWidgetActions.JoinCall, onJoin);
|
||||||
};
|
};
|
||||||
} else if (widget && !preload && skipLobby) {
|
} 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 defaultDeviceSetup({ audioInput: null, videoInput: null });
|
||||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
await enterRTCSession(rtcSession, perParticipantE2EE);
|
||||||
};
|
})().catch((e) => {
|
||||||
// No lobby and no preload: we enter the RTC Session right away.
|
logger.error("Error joining RTC session", e);
|
||||||
join();
|
});
|
||||||
}
|
}
|
||||||
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
|
}, [rtcSession, preload, skipLobby, perParticipantE2EE]);
|
||||||
|
|
||||||
@@ -204,7 +199,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
const onLeave = useCallback(
|
const onLeave = useCallback(
|
||||||
async (leaveError?: Error) => {
|
(leaveError?: Error): void => {
|
||||||
setLeaveError(leaveError);
|
setLeaveError(leaveError);
|
||||||
setLeft(true);
|
setLeft(true);
|
||||||
|
|
||||||
@@ -218,15 +213,19 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
||||||
await leaveRTCSession(rtcSession);
|
leaveRTCSession(rtcSession)
|
||||||
|
.then(() => {
|
||||||
if (
|
if (
|
||||||
!isPasswordlessUser &&
|
!isPasswordlessUser &&
|
||||||
!confineToRoom &&
|
!confineToRoom &&
|
||||||
!PosthogAnalytics.instance.isEnabled()
|
!PosthogAnalytics.instance.isEnabled()
|
||||||
) {
|
) {
|
||||||
history.push("/");
|
history.push("/");
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Error leaving RTC session", e);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
[rtcSession, isPasswordlessUser, confineToRoom, history],
|
||||||
);
|
);
|
||||||
@@ -234,14 +233,16 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (widget && isJoined) {
|
if (widget && isJoined) {
|
||||||
// set widget to sticky once joined.
|
// 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 (
|
const onHangup = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||||
ev: CustomEvent<IWidgetApiRequest>,
|
|
||||||
): Promise<void> => {
|
|
||||||
widget!.api.transport.reply(ev.detail, {});
|
widget!.api.transport.reply(ev.detail, {});
|
||||||
// Only sends matrix leave event. The Livekit session will disconnect once the ActiveCall-view unmounts.
|
// 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);
|
widget.lazyActions.once(ElementWidgetActions.HangupCall, onHangup);
|
||||||
return (): void => {
|
return (): void => {
|
||||||
@@ -253,7 +254,9 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const onReconnect = useCallback(() => {
|
const onReconnect = useCallback(() => {
|
||||||
setLeft(false);
|
setLeft(false);
|
||||||
setLeaveError(undefined);
|
setLeaveError(undefined);
|
||||||
enterRTCSession(rtcSession, perParticipantE2EE);
|
enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => {
|
||||||
|
logger.error("Error re-entering RTC session on reconnect", e);
|
||||||
|
});
|
||||||
}, [rtcSession, perParticipantE2EE]);
|
}, [rtcSession, perParticipantE2EE]);
|
||||||
|
|
||||||
const joinRule = useJoinRule(rtcSession.room);
|
const joinRule = useJoinRule(rtcSession.room);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { BehaviorSubject, of } from "rxjs";
|
import { BehaviorSubject, of } from "rxjs";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import LogoMark from "../icons/LogoMark.svg?react";
|
import LogoMark from "../icons/LogoMark.svg?react";
|
||||||
import LogoType from "../icons/LogoType.svg?react";
|
import LogoType from "../icons/LogoType.svg?react";
|
||||||
@@ -100,7 +101,9 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return (): void => {
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
@@ -296,12 +299,16 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
widget?.api.transport.send(
|
widget?.api.transport
|
||||||
gridMode === "grid"
|
.send(
|
||||||
? ElementWidgetActions.TileLayout
|
gridMode === "grid"
|
||||||
: ElementWidgetActions.SpotlightLayout,
|
? ElementWidgetActions.TileLayout
|
||||||
{},
|
: ElementWidgetActions.SpotlightLayout,
|
||||||
);
|
{},
|
||||||
|
)
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Failed to send layout change to widget API", e);
|
||||||
|
});
|
||||||
}, [gridMode]);
|
}, [gridMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -461,13 +468,15 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
rtcSession.room.roomId,
|
rtcSession.room.roomId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleScreensharing = useCallback(async () => {
|
const toggleScreensharing = useCallback(() => {
|
||||||
await localParticipant.setScreenShareEnabled(!isScreenShareEnabled, {
|
localParticipant
|
||||||
audio: true,
|
.setScreenShareEnabled(!isScreenShareEnabled, {
|
||||||
selfBrowserSurface: "include",
|
audio: true,
|
||||||
surfaceSwitching: "include",
|
selfBrowserSurface: "include",
|
||||||
systemAudio: "include",
|
surfaceSwitching: "include",
|
||||||
});
|
systemAudio: "include",
|
||||||
|
})
|
||||||
|
.catch(logger.error);
|
||||||
}, [localParticipant, isScreenShareEnabled]);
|
}, [localParticipant, isScreenShareEnabled]);
|
||||||
|
|
||||||
let footer: JSX.Element | null;
|
let footer: JSX.Element | null;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { IWidgetApiRequest } from "matrix-widget-api";
|
import { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
|
import { MediaDevice, useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
@@ -74,10 +75,14 @@ export function useMuteStates(): MuteStates {
|
|||||||
const video = useMuteState(devices.videoInput, () => true);
|
const video = useMuteState(devices.videoInput, () => true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
widget?.api.transport.send(ElementWidgetActions.DeviceMute, {
|
widget?.api.transport
|
||||||
audio_enabled: audio.enabled,
|
.send(ElementWidgetActions.DeviceMute, {
|
||||||
video_enabled: video.enabled,
|
audio_enabled: audio.enabled,
|
||||||
});
|
video_enabled: video.enabled,
|
||||||
|
})
|
||||||
|
.catch((e) =>
|
||||||
|
logger.warn("Could not send DeviceMute action to widget", e),
|
||||||
|
);
|
||||||
}, [audio, video]);
|
}, [audio, video]);
|
||||||
|
|
||||||
const onMuteStateChangeRequest = useCallback(
|
const onMuteStateChangeRequest = useCallback(
|
||||||
|
|||||||
@@ -59,9 +59,13 @@ export const RoomPage: FC = () => {
|
|||||||
// a URL param, automatically register a passwordless user
|
// a URL param, automatically register a passwordless user
|
||||||
if (!loading && !authenticated && displayName && !widget) {
|
if (!loading && !authenticated && displayName && !widget) {
|
||||||
setIsRegistering(true);
|
setIsRegistering(true);
|
||||||
registerPasswordlessUser(displayName).finally(() => {
|
registerPasswordlessUser(displayName)
|
||||||
setIsRegistering(false);
|
.catch((e) => {
|
||||||
});
|
logger.error("Failed to register passwordless user", e);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setIsRegistering(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
loading,
|
loading,
|
||||||
|
|||||||
@@ -150,23 +150,22 @@ export const useLoadGroupCall = (
|
|||||||
viaServers: string[],
|
viaServers: string[],
|
||||||
onKnockSent: () => void,
|
onKnockSent: () => void,
|
||||||
): Promise<Room> => {
|
): Promise<Room> => {
|
||||||
let joinedRoom: Room | null = null;
|
|
||||||
await client.knockRoom(roomId, { viaServers });
|
await client.knockRoom(roomId, { viaServers });
|
||||||
onKnockSent();
|
onKnockSent();
|
||||||
const invitePromise = new Promise<void>((resolve, reject) => {
|
return await new Promise<Room>((resolve, reject) => {
|
||||||
client.on(
|
client.on(
|
||||||
RoomEvent.MyMembership,
|
RoomEvent.MyMembership,
|
||||||
async (room, membership, prevMembership) => {
|
(room, membership, prevMembership): void => {
|
||||||
if (roomId !== room.roomId) return;
|
if (roomId !== room.roomId) return;
|
||||||
activeRoom.current = room;
|
activeRoom.current = room;
|
||||||
if (
|
if (
|
||||||
membership === KnownMembership.Invite &&
|
membership === KnownMembership.Invite &&
|
||||||
prevMembership === KnownMembership.Knock
|
prevMembership === KnownMembership.Knock
|
||||||
) {
|
) {
|
||||||
await client.joinRoom(room.roomId, { viaServers });
|
client.joinRoom(room.roomId, { viaServers }).then((room) => {
|
||||||
joinedRoom = room;
|
logger.log("Auto-joined %s", room.roomId);
|
||||||
logger.log("Auto-joined %s", room.roomId);
|
resolve(room);
|
||||||
resolve();
|
}, reject);
|
||||||
}
|
}
|
||||||
if (membership === KnownMembership.Ban) reject(bannedError());
|
if (membership === KnownMembership.Ban) reject(bannedError());
|
||||||
if (membership === KnownMembership.Leave)
|
if (membership === KnownMembership.Leave)
|
||||||
@@ -174,11 +173,6 @@ export const useLoadGroupCall = (
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await invitePromise;
|
|
||||||
if (!joinedRoom) {
|
|
||||||
throw new Error("Failed to join room after knocking.");
|
|
||||||
}
|
|
||||||
return joinedRoom;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchOrCreateRoom = async (): Promise<Room> => {
|
const fetchOrCreateRoom = async (): Promise<Room> => {
|
||||||
@@ -308,7 +302,7 @@ export const useLoadGroupCall = (
|
|||||||
|
|
||||||
const observeMyMembership = async (): Promise<void> => {
|
const observeMyMembership = async (): Promise<void> => {
|
||||||
await new Promise((_, reject) => {
|
await new Promise((_, reject) => {
|
||||||
client.on(RoomEvent.MyMembership, async (_, membership) => {
|
client.on(RoomEvent.MyMembership, (_, membership) => {
|
||||||
if (membership === KnownMembership.Leave) reject(removeNoticeError());
|
if (membership === KnownMembership.Leave) reject(removeNoticeError());
|
||||||
if (membership === KnownMembership.Ban) reject(bannedError());
|
if (membership === KnownMembership.Ban) reject(bannedError());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ async function makePreferredLivekitFoci(
|
|||||||
`No livekit_service_url is configured so we could not create a focus.
|
`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.`,
|
Currently we skip computing a focus based on other users in the room.`,
|
||||||
);
|
);
|
||||||
|
return Promise.resolve(preferredFoci);
|
||||||
return preferredFoci;
|
|
||||||
|
|
||||||
// TODO: we want to do something like this:
|
// TODO: we want to do something like this:
|
||||||
//
|
//
|
||||||
@@ -119,13 +118,18 @@ const widgetPostHangupProcedure = async (
|
|||||||
// we need to wait until the callEnded event is tracked on posthog.
|
// we need to wait until the callEnded event is tracked on posthog.
|
||||||
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
// Otherwise the iFrame gets killed before the callEnded event got tracked.
|
||||||
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
await new Promise((resolve) => window.setTimeout(resolve, 10)); // 10ms
|
||||||
widget.api.setAlwaysOnScreen(false);
|
|
||||||
PosthogAnalytics.instance.logout();
|
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
|
// We send the hangup event after the memberships have been updated
|
||||||
// calling leaveRTCSession.
|
// calling leaveRTCSession.
|
||||||
// We need to wait because this makes the client hosting this widget killing the IFrame.
|
// 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(
|
export async function leaveRTCSession(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { FC, useCallback } from "react";
|
|||||||
import { randomString } from "matrix-js-sdk/src/randomstring";
|
import { randomString } from "matrix-js-sdk/src/randomstring";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||||
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
|
import { useSubmitRageshake, useRageshakeRequest } from "./submit-rageshake";
|
||||||
@@ -41,6 +42,8 @@ export const FeedbackSettingsTab: FC<Props> = ({ roomId }) => {
|
|||||||
sendLogs,
|
sendLogs,
|
||||||
rageshakeRequestId,
|
rageshakeRequestId,
|
||||||
roomId,
|
roomId,
|
||||||
|
}).catch((e) => {
|
||||||
|
logger.error("Failed to send feedback rageshake", e);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (roomId && sendLogs) {
|
if (roomId && sendLogs) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
import { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { useProfile } from "../profile/useProfile";
|
import { useProfile } from "../profile/useProfile";
|
||||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
|
||||||
@@ -61,6 +62,8 @@ export const ProfileSettingsTab: FC<Props> = ({ client }) => {
|
|||||||
// @ts-ignore
|
// @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),
|
||||||
|
}).catch((e) => {
|
||||||
|
logger.error("Failed to save profile", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FC, useCallback } from "react";
|
import { FC, useCallback } from "react";
|
||||||
import { Button } from "@vector-im/compound-web";
|
import { Button } from "@vector-im/compound-web";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { Config } from "../config/Config";
|
import { Config } from "../config/Config";
|
||||||
import styles from "./RageshakeButton.module.css";
|
import styles from "./RageshakeButton.module.css";
|
||||||
@@ -25,6 +26,8 @@ export const RageshakeButton: FC<Props> = ({ description }) => {
|
|||||||
submitRageshake({
|
submitRageshake({
|
||||||
description,
|
description,
|
||||||
sendLogs: true,
|
sendLogs: true,
|
||||||
|
}).catch((e) => {
|
||||||
|
logger.error("Failed to send rageshake", e);
|
||||||
});
|
});
|
||||||
}, [submitRageshake, description]);
|
}, [submitRageshake, description]);
|
||||||
|
|
||||||
|
|||||||
@@ -128,13 +128,17 @@ class IndexedDBLogStore {
|
|||||||
this.id = "instance-" + randomString(16);
|
this.id = "instance-" + randomString(16);
|
||||||
|
|
||||||
loggerInstance.on(ConsoleLoggerEvent.Log, this.onLoggerLog);
|
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.
|
* @return {Promise} Resolves when the store is ready.
|
||||||
*/
|
*/
|
||||||
public connect(): Promise<void> {
|
public async connect(): Promise<void> {
|
||||||
const req = this.indexedDB.open("logs");
|
const req = this.indexedDB.open("logs");
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
req.onsuccess = (): void => {
|
req.onsuccess = (): void => {
|
||||||
@@ -190,16 +194,10 @@ class IndexedDBLogStore {
|
|||||||
// Throttled function to flush logs. We use throttle rather
|
// Throttled function to flush logs. We use throttle rather
|
||||||
// than debounce as we want logs to be written regularly, otherwise
|
// than debounce as we want logs to be written regularly, otherwise
|
||||||
// if there's a constant stream of logging, we'd never write anything.
|
// if there's a constant stream of logging, we'd never write anything.
|
||||||
private throttledFlush = throttle(
|
private throttledFlush = throttle(() => this.flush, MAX_FLUSH_INTERVAL_MS, {
|
||||||
() => {
|
leading: false,
|
||||||
this.flush();
|
trailing: true,
|
||||||
},
|
});
|
||||||
MAX_FLUSH_INTERVAL_MS,
|
|
||||||
{
|
|
||||||
leading: false,
|
|
||||||
trailing: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush logs to disk.
|
* Flush logs to disk.
|
||||||
@@ -220,7 +218,7 @@ class IndexedDBLogStore {
|
|||||||
*
|
*
|
||||||
* @return {Promise} Resolved when the logs have been flushed.
|
* @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
|
// check if a flush() operation is ongoing
|
||||||
if (this.flushPromise) {
|
if (this.flushPromise) {
|
||||||
if (this.flushAgainPromise) {
|
if (this.flushAgainPromise) {
|
||||||
@@ -228,13 +226,9 @@ class IndexedDBLogStore {
|
|||||||
return this.flushAgainPromise;
|
return this.flushAgainPromise;
|
||||||
}
|
}
|
||||||
// queue up a flush to occur immediately after the pending one completes.
|
// queue up a flush to occur immediately after the pending one completes.
|
||||||
this.flushAgainPromise = this.flushPromise
|
this.flushAgainPromise = this.flushPromise.then(this.flush).then(() => {
|
||||||
.then(() => {
|
this.flushAgainPromise = undefined;
|
||||||
return this.flush();
|
});
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
this.flushAgainPromise = undefined;
|
|
||||||
});
|
|
||||||
return this.flushAgainPromise;
|
return this.flushAgainPromise;
|
||||||
}
|
}
|
||||||
// there is no flush promise or there was but it has finished, so do
|
// there is no flush promise or there was but it has finished, so do
|
||||||
@@ -286,7 +280,7 @@ class IndexedDBLogStore {
|
|||||||
|
|
||||||
// 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> {
|
async function fetchLogs(id: string, maxSize: number): Promise<string> {
|
||||||
const objectStore = db!
|
const objectStore = db!
|
||||||
.transaction("logs", "readonly")
|
.transaction("logs", "readonly")
|
||||||
.objectStore("logs");
|
.objectStore("logs");
|
||||||
@@ -316,7 +310,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[]> {
|
async 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")
|
||||||
@@ -336,7 +330,7 @@ class IndexedDBLogStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteLogs(id: number): Promise<void> {
|
async 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");
|
||||||
@@ -394,7 +388,7 @@ class IndexedDBLogStore {
|
|||||||
logger.log("Removing logs: ", removeLogIds);
|
logger.log("Removing logs: ", removeLogIds);
|
||||||
// Don't await this because it's non-fatal if we can't clean up
|
// Don't await this because it's non-fatal if we can't clean up
|
||||||
// logs.
|
// 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.`);
|
logger.log(`Removed ${removeLogIds.length} old logs.`);
|
||||||
},
|
},
|
||||||
@@ -432,7 +426,7 @@ class IndexedDBLogStore {
|
|||||||
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
|
* @return {Promise<T[]>} Resolves to an array of whatever you returned from
|
||||||
* resultMapper.
|
* resultMapper.
|
||||||
*/
|
*/
|
||||||
function selectQuery<T>(
|
async function selectQuery<T>(
|
||||||
store: IDBObjectStore,
|
store: IDBObjectStore,
|
||||||
keyRange: IDBKeyRange | undefined,
|
keyRange: IDBKeyRange | undefined,
|
||||||
resultMapper: (cursor: IDBCursorWithValue) => T,
|
resultMapper: (cursor: IDBCursorWithValue) => T,
|
||||||
@@ -461,7 +455,7 @@ declare global {
|
|||||||
// eslint-disable-next-line no-var, camelcase
|
// eslint-disable-next-line no-var, camelcase
|
||||||
var mx_rage_logger: ConsoleLogger;
|
var mx_rage_logger: ConsoleLogger;
|
||||||
// eslint-disable-next-line no-var, camelcase
|
// eslint-disable-next-line no-var, camelcase
|
||||||
var mx_rage_initStoragePromise: Promise<void>;
|
var mx_rage_initStoragePromise: Promise<void> | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -471,7 +465,7 @@ declare global {
|
|||||||
* be set up immediately for the logs.
|
* be set up immediately for the logs.
|
||||||
* @return {Promise} Resolves when set up.
|
* @return {Promise} Resolves when set up.
|
||||||
*/
|
*/
|
||||||
export function init(): Promise<void> {
|
export async function init(): Promise<void> {
|
||||||
global.mx_rage_logger = new ConsoleLogger();
|
global.mx_rage_logger = new ConsoleLogger();
|
||||||
setLogExtension(global.mx_rage_logger.log);
|
setLogExtension(global.mx_rage_logger.log);
|
||||||
|
|
||||||
@@ -483,7 +477,7 @@ export function init(): Promise<void> {
|
|||||||
* then this no-ops.
|
* then this no-ops.
|
||||||
* @return {Promise} Resolves when complete.
|
* @return {Promise} Resolves when complete.
|
||||||
*/
|
*/
|
||||||
function tryInitStorage(): Promise<void> {
|
async function tryInitStorage(): Promise<void> {
|
||||||
if (global.mx_rage_initStoragePromise) {
|
if (global.mx_rage_initStoragePromise) {
|
||||||
return global.mx_rage_initStoragePromise;
|
return global.mx_rage_initStoragePromise;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,10 +300,14 @@ export function useRageshakeRequest(): (
|
|||||||
|
|
||||||
const sendRageshakeRequest = useCallback(
|
const sendRageshakeRequest = useCallback(
|
||||||
(roomId: string, rageshakeRequestId: string) => {
|
(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!
|
||||||
client!.sendEvent(roomId, "org.matrix.rageshake_request", {
|
// @ts-expect-error - org.matrix.rageshake_request is not part of `keyof TimelineEvents` but it is okay to sent a custom event.
|
||||||
request_id: rageshakeRequestId,
|
.sendEvent(roomId, "org.matrix.rageshake_request", {
|
||||||
});
|
request_id: rageshakeRequestId,
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
logger.error("Failed to send org.matrix.rageshake_request event", e);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[client],
|
[client],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export class CallViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
// Then unhold them
|
// Then unhold them
|
||||||
]).then(() => Promise.resolve({ unhold: ps })),
|
]).then(() => ({ unhold: ps })),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
|
|||||||
@@ -19,19 +19,22 @@ export function useWakeLock(): void {
|
|||||||
|
|
||||||
// The lock is automatically released whenever the window goes invisible,
|
// The lock is automatically released whenever the window goes invisible,
|
||||||
// so we need to reacquire it on visibility changes
|
// so we need to reacquire it on visibility changes
|
||||||
const onVisibilityChange = async (): Promise<void> => {
|
const onVisibilityChange = (): void => {
|
||||||
if (document.visibilityState === "visible") {
|
if (document.visibilityState === "visible") {
|
||||||
try {
|
navigator.wakeLock.request("screen").then(
|
||||||
lock = await navigator.wakeLock.request("screen");
|
(newLock) => {
|
||||||
// Handle the edge case where this component unmounts before the
|
lock = newLock;
|
||||||
// promise resolves
|
// Handle the edge case where this component unmounts before the
|
||||||
if (!mounted)
|
// promise resolves
|
||||||
lock
|
if (!mounted)
|
||||||
.release()
|
lock
|
||||||
.catch((e) => logger.warn("Can't release wake lock", e));
|
.release()
|
||||||
} catch (e) {
|
.catch((e) => logger.warn("Can't release wake lock", e));
|
||||||
logger.warn("Can't acquire wake lock", e);
|
},
|
||||||
}
|
(e) => {
|
||||||
|
logger.warn("Can't acquire wake lock", e);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -260,34 +260,40 @@ export async function createRoom(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Wait for the room to arrive
|
// Wait for the room to arrive
|
||||||
await new Promise<void>((resolve, reject) => {
|
const roomId = await new Promise<string>((resolve, reject) => {
|
||||||
const onRoom = async (room: Room): Promise<void> => {
|
|
||||||
if (room.roomId === (await createPromise).room_id) {
|
|
||||||
resolve();
|
|
||||||
cleanUp();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
createPromise.catch((e) => {
|
createPromise.catch((e) => {
|
||||||
reject(e);
|
reject(e);
|
||||||
cleanUp();
|
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 => {
|
const cleanUp = (): void => {
|
||||||
client.off(ClientEvent.Room, onRoom);
|
client.off(ClientEvent.Room, onRoom);
|
||||||
};
|
};
|
||||||
client.on(ClientEvent.Room, onRoom);
|
client.on(ClientEvent.Room, onRoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await createPromise;
|
let password: string | undefined;
|
||||||
|
|
||||||
let password;
|
|
||||||
if (e2ee == E2eeType.SHARED_KEY) {
|
if (e2ee == E2eeType.SHARED_KEY) {
|
||||||
password = secureRandomBase64Url(16);
|
password = secureRandomBase64Url(16);
|
||||||
saveKeyForRoom(result.room_id, password);
|
saveKeyForRoom(roomId, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roomId: result.room_id,
|
roomId,
|
||||||
alias: e2ee ? undefined : fullAliasFromRoomName(name, client),
|
alias: e2ee ? undefined : fullAliasFromRoomName(name, client),
|
||||||
password,
|
password,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ Please see LICENSE in the repository root for full details.
|
|||||||
* @param devices The list of devices to search
|
* @param devices The list of devices to search
|
||||||
* @returns A matching media device or undefined if no matching device was found
|
* @returns A matching media device or undefined if no matching device was found
|
||||||
*/
|
*/
|
||||||
export async function findDeviceByName(
|
export function findDeviceByName(
|
||||||
deviceName: string,
|
deviceName: string,
|
||||||
kind: MediaDeviceKind,
|
kind: MediaDeviceKind,
|
||||||
devices: MediaDeviceInfo[],
|
devices: MediaDeviceInfo[],
|
||||||
): Promise<string | undefined> {
|
): string | undefined {
|
||||||
const deviceInfo = devices.find(
|
const deviceInfo = devices.find(
|
||||||
(d) => d.kind === kind && d.label === deviceName,
|
(d) => d.kind === kind && d.label === deviceName,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,17 +11,21 @@ import posthog from "posthog-js";
|
|||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import { afterEach, beforeEach } from "vitest";
|
import { afterEach, beforeEach } from "vitest";
|
||||||
import { cleanup } from "@testing-library/react";
|
import { cleanup } from "@testing-library/react";
|
||||||
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { Config } from "./config/Config";
|
import { Config } from "./config/Config";
|
||||||
|
|
||||||
// Bare-minimum i18n config
|
// Bare-minimum i18n config
|
||||||
i18n.use(initReactI18next).init({
|
i18n
|
||||||
lng: "en-GB",
|
.use(initReactI18next)
|
||||||
fallbackLng: "en-GB",
|
.init({
|
||||||
interpolation: {
|
lng: "en-GB",
|
||||||
escapeValue: false, // React has built-in XSS protections
|
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();
|
Config.initDefault();
|
||||||
posthog.opt_out_capturing();
|
posthog.opt_out_capturing();
|
||||||
|
|||||||
@@ -158,17 +158,15 @@ export const widget = ((): WidgetHelpers | null => {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const clientPromise = new Promise<MatrixClient>((resolve) => {
|
const clientPromise = async (): Promise<MatrixClient> => {
|
||||||
(async (): Promise<void> => {
|
// Wait for the config file to be ready (we load very early on so it might not
|
||||||
// Wait for the config file to be ready (we load very early on so it might not
|
// be otherwise)
|
||||||
// be otherwise)
|
await Config.init();
|
||||||
await Config.init();
|
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
||||||
await client.startClient({ clientWellKnownPollPeriod: 60 * 10 });
|
return client;
|
||||||
resolve(client);
|
};
|
||||||
})();
|
|
||||||
});
|
|
||||||
|
|
||||||
return { api, lazyActions, client: clientPromise };
|
return { api, lazyActions, client: clientPromise() };
|
||||||
} else {
|
} else {
|
||||||
if (import.meta.env.MODE !== "test")
|
if (import.meta.env.MODE !== "test")
|
||||||
logger.info("No widget API available");
|
logger.info("No widget API available");
|
||||||
|
|||||||
Reference in New Issue
Block a user