Files
element-call/src/initializer.tsx
T

176 lines
5.1 KiB
TypeScript
Raw Normal View History

2022-11-03 19:43:41 +01:00
/*
Copyright 2022-2024 New Vector Ltd.
2022-11-03 19:43:41 +01:00
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
2022-11-03 19:43:41 +01:00
*/
import i18n from "i18next";
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";
2022-11-03 19:43:41 +01:00
import { getUrlParams } from "./UrlParams";
import { Config } from "./config/Config";
2023-03-22 12:41:33 +00:00
import { ElementCallOpenTelemetry } from "./otel/otel";
2023-09-17 12:54:18 -04:00
import { platform } from "./Platform";
2022-11-03 19:43:41 +01:00
enum LoadState {
None,
Loading,
Loaded,
}
class DependencyLoadStates {
2023-09-22 18:05:13 -04:00
public config: LoadState = LoadState.None;
public sentry: LoadState = LoadState.None;
public openTelemetry: LoadState = LoadState.None;
2022-11-03 19:43:41 +01:00
2023-09-22 18:05:13 -04:00
public allDepsAreLoaded(): boolean {
2022-11-03 19:43:41 +01:00
return !Object.values(this).some((s) => s !== LoadState.Loaded);
}
}
export class Initializer {
private static internalInstance: Initializer;
2023-06-09 19:18:30 +02:00
private isInitialized = false;
public static isInitialized(): boolean {
2023-06-13 17:08:29 +02:00
return Initializer.internalInstance?.isInitialized;
2023-06-09 19:18:30 +02:00
}
2023-09-22 18:05:13 -04:00
public static initBeforeReact(): void {
// this maybe also needs to return a promise in the future,
// if we have to do async inits before showing the loading screen
2024-07-25 14:33:37 +02:00
// but this should be avoided if possible
//i18n
const languageDetector = new LanguageDetector();
languageDetector.addDetector({
name: "urlFragment",
// Look for a language code in the URL's fragment
2023-09-18 17:49:10 +01:00
lookup: () => getUrlParams().lang ?? undefined,
});
i18n
.use(Backend)
.use(languageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en-GB",
defaultNS: "app",
2023-11-20 13:47:36 +00:00
keySeparator: ".",
nsSeparator: false,
2023-11-17 12:08:27 +00:00
pluralSeparator: "_",
contextSeparator: "|",
interpolation: {
escapeValue: false, // React has built-in XSS protections
},
detection: {
// No localStorage detectors or caching here, since we don't have any way
// of letting the user manually select a language
order: ["urlFragment", "navigator"],
caches: [],
},
})
.catch((e) => {
logger.error("Failed to initialize i18n", e);
});
// Custom Themeing
2023-08-28 16:45:17 -04:00
if (import.meta.env.VITE_CUSTOM_CSS) {
const style = document.createElement("style");
style.textContent = import.meta.env.VITE_CUSTOM_CSS;
document.head.appendChild(style);
}
2022-12-09 14:25:02 -05:00
// Custom fonts
2023-09-18 17:49:10 +01:00
const { fonts, fontScale } = getUrlParams();
2022-12-09 14:25:02 -05:00
if (fontScale !== null) {
document.documentElement.style.setProperty(
"--font-scale",
2023-10-11 10:42:04 -04:00
fontScale.toString(),
2022-12-09 14:25:02 -05:00
);
}
if (fonts.length > 0) {
document.documentElement.style.setProperty(
"--font-family",
2023-10-11 10:42:04 -04:00
fonts.map((f) => `"${f}"`).join(", "),
2022-12-09 14:25:02 -05:00
);
}
2023-09-17 12:54:18 -04:00
// Add the platform to the DOM, so CSS can query it
document.body.setAttribute("data-platform", platform);
}
2022-11-03 19:43:41 +01:00
public static init(): Promise<void> | null {
if (Initializer?.internalInstance?.initPromise) {
return null;
}
Initializer.internalInstance = new Initializer();
Initializer.internalInstance.initPromise = new Promise<void>((resolve) => {
2024-07-25 14:33:37 +02:00
// initStep calls itself recursively until everything is initialized in the correct order.
2022-11-03 19:43:41 +01:00
// Then the promise gets resolved.
Initializer.internalInstance.initStep(resolve);
});
return Initializer.internalInstance.initPromise;
}
2023-09-22 18:05:13 -04:00
private loadStates = new DependencyLoadStates();
2022-11-03 19:43:41 +01:00
2023-09-22 18:05:13 -04:00
private initStep(resolve: (value: void | PromiseLike<void>) => void): void {
2022-11-03 19:43:41 +01:00
// config
if (this.loadStates.config === LoadState.None) {
this.loadStates.config = LoadState.Loading;
Config.init().then(
() => {
this.loadStates.config = LoadState.Loaded;
this.initStep(resolve);
},
(e) => {
logger.error("Failed to load config", e);
},
);
2022-11-03 19:43:41 +01:00
}
//sentry (only initialize after the config is ready)
if (
this.loadStates.sentry === LoadState.None &&
this.loadStates.config === LoadState.Loaded
) {
2022-12-21 10:17:53 +00:00
if (Config.get().sentry?.DSN && Config.get().sentry?.environment) {
Sentry.init({
2022-12-21 10:17:53 +00:00
dsn: Config.get().sentry?.DSN,
environment: Config.get().sentry?.environment,
integrations: [
2024-06-21 10:23:30 -04:00
Sentry.reactRouterV5BrowserTracingIntegration({ history }),
],
tracesSampleRate: 1.0,
});
}
// Sentry is now 'loadeed' (even if we actually skipped starting
// it due to to not being configured)
2022-11-03 19:43:41 +01:00
this.loadStates.sentry = LoadState.Loaded;
}
2023-03-22 12:41:33 +00:00
// OpenTelemetry (also only after config loaded)
if (
this.loadStates.openTelemetry === LoadState.None &&
this.loadStates.config === LoadState.Loaded
) {
ElementCallOpenTelemetry.globalInit();
this.loadStates.openTelemetry = LoadState.Loaded;
}
2022-11-03 19:43:41 +01:00
if (this.loadStates.allDepsAreLoaded()) {
// resolve if there is no dependency that is not loaded
resolve();
2023-06-09 19:18:30 +02:00
this.isInitialized = true;
2022-11-03 19:43:41 +01:00
}
}
2023-06-30 16:43:28 +01:00
private initPromise?: Promise<void>;
2022-11-03 19:43:41 +01:00
}