/* Copyright 2024 New Vector Ltd. Copyright 2019-2022 The Matrix.org Foundation C.I.C. Copyright 2016 OpenMarket Ltd SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import { Optional } from "matrix-events-sdk"; import { mergeWith } from "lodash"; import { SnakedObject } from "./utils/SnakedObject"; import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions"; import { isObject, objectClone } from "./utils/objects"; import { DeepReadonly, Defaultize } from "./@types/common"; // see element-web config.md for docs, or the IConfigOptions interface for dev docs export const DEFAULTS: DeepReadonly = { brand: "Element", help_url: "https://element.io/help", help_encryption_url: "https://element.io/help#encryption", integrations_ui_url: "https://scalar.vector.im/", integrations_rest_url: "https://scalar.vector.im/api", uisi_autorageshake_app: "element-auto-uisi", show_labs_settings: false, jitsi: { preferred_domain: "meet.element.io", }, element_call: { url: "https://call.element.io", use_exclusively: false, participant_limit: 8, brand: "Element Call", }, // @ts-ignore - we deliberately use the camelCase version here so we trigger // the fallback behaviour. If we used the snake_case version then we'd break // everyone's config which has the camelCase property because our default would // be preferred over their config. desktopBuilds: { available: true, logo: require("../res/img/element-desktop-logo.svg").default, url: "https://element.io/get-started", }, voice_broadcast: { chunk_length: 2 * 60, // two minutes max_length: 4 * 60 * 60, // four hours }, feedback: { existing_issues_url: "https://github.com/vector-im/element-web/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc", new_issue_url: "https://github.com/vector-im/element-web/issues/new/choose", }, desktop_builds: { available: true, logo: "vector-icons/1024.png", url: "https://element.io/download", url_macos: "https://packages.element.io/desktop/install/macos/Element.dmg", url_win64: "https://packages.element.io/desktop/install/win32/x64/Element%20Setup.exe", url_win32: "https://packages.element.io/desktop/install/win32/ia32/Element%20Setup.exe", url_linux: "https://element.io/download#linux", }, mobile_builds: { ios: "https://apps.apple.com/app/vector/id1083446067", android: "https://play.google.com/store/apps/details?id=im.vector.app", fdroid: "https://f-droid.org/repository/browse/?fdid=im.vector.app", }, }; export type ConfigOptions = Defaultize; function mergeConfig( config: DeepReadonly, changes: DeepReadonly>, ): DeepReadonly { // return { ...config, ...changes }; return mergeWith(objectClone(config), changes, (objValue, srcValue) => { // Don't merge arrays, prefer values from newer object if (Array.isArray(objValue)) { return srcValue; } // Don't allow objects to get nulled out, this will break our types if (isObject(objValue) && !isObject(srcValue)) { return objValue; } }); } type ObjectType = IConfigOptions[K] extends object ? SnakedObject> : Optional>>; export default class SdkConfig { private static instance: DeepReadonly; private static fallback: SnakedObject>; private static setInstance(i: DeepReadonly): void { SdkConfig.instance = i; SdkConfig.fallback = new SnakedObject(i); // For debugging purposes window.mxReactSdkConfig = i; } public static get(): IConfigOptions; public static get(key: K, altCaseName?: string): IConfigOptions[K]; public static get( key?: K, altCaseName?: string, ): DeepReadonly | DeepReadonly[K] { if (key === undefined) { // safe to cast as a fallback - we want to break the runtime contract in this case return SdkConfig.instance || {}; } return SdkConfig.fallback.get(key, altCaseName); } public static getObject(key: K, altCaseName?: string): ObjectType { const val = SdkConfig.get(key, altCaseName); if (isObject(val)) { return new SnakedObject(val); } // return the same type for sensitive callers (some want `undefined` specifically) return (val === undefined ? undefined : null) as ObjectType; } public static put(cfg: DeepReadonly): void { SdkConfig.setInstance(mergeConfig(DEFAULTS, cfg)); } /** * Resets the config. */ public static reset(): void { SdkConfig.setInstance(mergeConfig(DEFAULTS, {})); // safe to cast - defaults will be applied } public static add(cfg: Partial): void { SdkConfig.put(mergeConfig(SdkConfig.get(), cfg)); } } export function parseSsoRedirectOptions(config: IConfigOptions): ISsoRedirectOptions { // Ignore deprecated options if the config is using new ones if (config.sso_redirect_options) return config.sso_redirect_options; // We can cheat here because the default is false anyways if (config.sso_immediate_redirect) return { immediate: true }; // Default: do nothing return {}; }