* Force verification even for refreshed cients Set a flag on login to remember that the device needs to be verified so that we don't forget if the user refreshes the page, but still allow user with an existing unverified session to stay logged in. * Hopefully make matrixchat tests pass? Much, much tweaking to make the matrixchat tests pass again. Should hopefully make them a bit more solid in general with judicious use of waitFor rather than flushPromises(). Also lots of fun to stop the state bleeding between tests. * Manual yarn.lock manipulation to hopefully resolve infinite package sadness * Make final test pass(?) Mock out the createClient method to return the same client, because we've mocked the peg to always return that client, so if we let the code make another one having still overridden the peg, everything becomes cursed. Also mock out the autodiscovery stuff rather than relying on fetch-mock. * another waitFor * death to flushPromises * Put the logged in dispatch back Actually it breaks all sorts of other things too, having fixed all the MatrixChat tests (although this is useful anyway). * Try displaying the screen in onClientStarted instead * Put post login screen back in logged in but move ready transition to avoid flash of main UI * Rejig more in the hope it does the right thing * Make hook work before push rules are fetched * Add test for unskippable verification * Add test for use case selection * Fix test * Add playwright test for unskippable verification * Remove console log * Add log message to log line * Add tsdoc * Use useTypedEventEmitter * Remove commented code * Use catch instead of empty then on unawaited promises or in one case just await it because the caller was async anyway * Add new mock
160 lines
5.8 KiB
TypeScript
160 lines
5.8 KiB
TypeScript
/*
|
|
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<IConfigOptions> = {
|
|
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,
|
|
force_verification: 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<IConfigOptions, typeof DEFAULTS>;
|
|
|
|
function mergeConfig(
|
|
config: DeepReadonly<IConfigOptions>,
|
|
changes: DeepReadonly<Partial<IConfigOptions>>,
|
|
): DeepReadonly<IConfigOptions> {
|
|
// 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<K extends keyof IConfigOptions> = IConfigOptions[K] extends object
|
|
? SnakedObject<NonNullable<IConfigOptions[K]>>
|
|
: Optional<SnakedObject<NonNullable<IConfigOptions[K]>>>;
|
|
|
|
export default class SdkConfig {
|
|
private static instance: DeepReadonly<IConfigOptions>;
|
|
private static fallback: SnakedObject<DeepReadonly<IConfigOptions>>;
|
|
|
|
private static setInstance(i: DeepReadonly<IConfigOptions>): void {
|
|
SdkConfig.instance = i;
|
|
SdkConfig.fallback = new SnakedObject(i);
|
|
|
|
// For debugging purposes
|
|
window.mxReactSdkConfig = i;
|
|
}
|
|
|
|
public static get(): IConfigOptions;
|
|
public static get<K extends keyof IConfigOptions>(key: K, altCaseName?: string): IConfigOptions[K];
|
|
public static get<K extends keyof IConfigOptions = never>(
|
|
key?: K,
|
|
altCaseName?: string,
|
|
): DeepReadonly<IConfigOptions> | DeepReadonly<IConfigOptions>[K] {
|
|
if (key === undefined) {
|
|
// safe to cast as a fallback - we want to break the runtime contract in this case
|
|
return SdkConfig.instance || <IConfigOptions>{};
|
|
}
|
|
return SdkConfig.fallback.get(key, altCaseName);
|
|
}
|
|
|
|
public static getObject<K extends keyof IConfigOptions>(key: K, altCaseName?: string): ObjectType<K> {
|
|
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<K>;
|
|
}
|
|
|
|
public static put(cfg: DeepReadonly<ConfigOptions>): 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<ConfigOptions>): 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 {};
|
|
}
|