Use & enforce snake_case naming convention on config.json settings (#8062)
* Document and support the established naming convention for config opts This change: * Rename `ConfigOptions` to `IConfigOptions` to match code convention/style, plus move it to a dedicated file * Update comments and surrounding documentation * Define every single documented option (from element-web's config.md) * Enable a linter to enforce the convention * Invent a translation layer for a different change to use * No attempt to fix build errors from doing this (at this stage) * Add demo of lint rule in action * Fix all obvious instances of SdkConfig case conflicts * Fix tests to use SdkConfig directly * Add docs to make unset() calling safer * Appease the linter * Update documentation to match snake_case_config * Fix more instances of square brackets off SdkConfig
This commit is contained in:
parent
09c57b228e
commit
d8a939df5d
56 changed files with 605 additions and 259 deletions
|
@ -15,7 +15,7 @@ order of priority, are:
|
|||
* `room-account` - The current user's account, but only when in a specific room
|
||||
* `account` - The current user's account
|
||||
* `room` - A specific room (setting for all members of the room)
|
||||
* `config` - Values are defined by the `settingDefaults` key (usually) in `config.json`
|
||||
* `config` - Values are defined by the `setting_defaults` key (usually) in `config.json`
|
||||
* `default` - The hardcoded default for the settings
|
||||
|
||||
Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure
|
||||
|
@ -27,12 +27,12 @@ that room administrators cannot force account-only settings upon participants.
|
|||
Settings are the different options a user may set or experience in the application. These are pre-defined in
|
||||
`src/settings/Settings.tsx` under the `SETTINGS` constant, and match the `ISetting` interface as defined there.
|
||||
|
||||
Settings that support the config level can be set in the config file under the `settingDefaults` key (note that some
|
||||
Settings that support the config level can be set in the config file under the `setting_defaults` key (note that some
|
||||
settings, like the "theme" setting, are special cased in the config file):
|
||||
```json
|
||||
```json5
|
||||
{
|
||||
...
|
||||
"settingDefaults": {
|
||||
"setting_defaults": {
|
||||
"settingName": true
|
||||
},
|
||||
...
|
||||
|
|
|
@ -41,3 +41,11 @@ export type RecursivePartial<T> = {
|
|||
T[P] extends object ? RecursivePartial<T[P]> :
|
||||
T[P];
|
||||
};
|
||||
|
||||
// Inspired by https://stackoverflow.com/a/60206860
|
||||
export type KeysWithObjectShape<Input> = {
|
||||
[P in keyof Input]: Input[P] extends object
|
||||
// Arrays are counted as objects - exclude them
|
||||
? (Input[P] extends Array<unknown> ? never : P)
|
||||
: never;
|
||||
}[keyof Input];
|
||||
|
|
4
src/@types/global.d.ts
vendored
4
src/@types/global.d.ts
vendored
|
@ -52,7 +52,7 @@ import { ConsoleLogger, IndexedDBLogStore } from "../rageshake/rageshake";
|
|||
import ActiveWidgetStore from "../stores/ActiveWidgetStore";
|
||||
import { Skinner } from "../Skinner";
|
||||
import AutoRageshakeStore from "../stores/AutoRageshakeStore";
|
||||
import { ConfigOptions } from "../SdkConfig";
|
||||
import { IConfigOptions } from "../IConfigOptions";
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
|
@ -63,7 +63,7 @@ declare global {
|
|||
Olm: {
|
||||
init: () => Promise<void>;
|
||||
};
|
||||
mxReactSdkConfig: ConfigOptions;
|
||||
mxReactSdkConfig: IConfigOptions;
|
||||
|
||||
// Needed for Safari, unknown to TypeScript
|
||||
webkitAudioContext: typeof AudioContext;
|
||||
|
|
|
@ -17,12 +17,15 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import { getCurrentLanguage, _t, _td, IVariables } from './languageHandler';
|
||||
import PlatformPeg from './PlatformPeg';
|
||||
import SdkConfig from './SdkConfig';
|
||||
import Modal from './Modal';
|
||||
import * as sdk from './index';
|
||||
import { SnakedObject } from "./utils/SnakedObject";
|
||||
import { IConfigOptions } from "./IConfigOptions";
|
||||
|
||||
const hashRegex = /#\/(groups?|room|user|settings|register|login|forgot_password|home|directory)/;
|
||||
const hashVarRegex = /#\/(group|room|user)\/.*$/;
|
||||
|
@ -193,8 +196,12 @@ export class Analytics {
|
|||
}
|
||||
|
||||
public canEnable() {
|
||||
const config = SdkConfig.get();
|
||||
return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId;
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
|
||||
if (typeof piwikConfig === 'object') {
|
||||
piwik = new SnakedObject(piwikConfig);
|
||||
}
|
||||
return navigator.doNotTrack !== "1" && piwik?.get("site_id");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,12 +211,16 @@ export class Analytics {
|
|||
public async enable() {
|
||||
if (!this.disabled) return;
|
||||
if (!this.canEnable()) return;
|
||||
const config = SdkConfig.get();
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
|
||||
if (typeof piwikConfig === 'object') {
|
||||
piwik = new SnakedObject(piwikConfig);
|
||||
}
|
||||
|
||||
this.baseUrl = new URL("piwik.php", config.piwik.url);
|
||||
this.baseUrl = new URL("piwik.php", piwik.get("url"));
|
||||
// set constants
|
||||
this.baseUrl.searchParams.set("rec", "1"); // rec is required for tracking
|
||||
this.baseUrl.searchParams.set("idsite", config.piwik.siteId); // rec is required for tracking
|
||||
this.baseUrl.searchParams.set("idsite", piwik.get("site_id")); // idsite is required for tracking
|
||||
this.baseUrl.searchParams.set("apiv", "1"); // API version to use
|
||||
this.baseUrl.searchParams.set("send_image", "0"); // we want a 204, not a tiny GIF
|
||||
// set user parameters
|
||||
|
@ -347,10 +358,14 @@ export class Analytics {
|
|||
public setLoggedIn(isGuest: boolean, homeserverUrl: string) {
|
||||
if (this.disabled) return;
|
||||
|
||||
const config = SdkConfig.get();
|
||||
if (!config.piwik) return;
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
|
||||
if (typeof piwikConfig === 'object') {
|
||||
piwik = new SnakedObject(piwikConfig);
|
||||
}
|
||||
if (!piwik) return;
|
||||
|
||||
const whitelistedHSUrls = config.piwik.whitelistedHSUrls || [];
|
||||
const whitelistedHSUrls = piwik.get("whitelisted_hs_urls", "whitelistedHSUrls") || [];
|
||||
|
||||
this.setVisitVariable('User Type', isGuest ? 'Guest' : 'Logged In');
|
||||
this.setVisitVariable('Homeserver URL', whitelistRedact(whitelistedHSUrls, homeserverUrl));
|
||||
|
@ -391,7 +406,12 @@ export class Analytics {
|
|||
];
|
||||
|
||||
// FIXME: Using an import will result in test failures
|
||||
const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
|
||||
if (typeof piwikConfig === 'object') {
|
||||
piwik = new SnakedObject(piwikConfig);
|
||||
}
|
||||
const cookiePolicyUrl = piwik?.get("policy_url");
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
const cookiePolicyLink = _t(
|
||||
"Our complete cookie policy can be found <CookiePolicyLink>here</CookiePolicyLink>.",
|
||||
|
|
|
@ -32,6 +32,7 @@ import { hideToast as hideUpdateToast } from "./toasts/UpdateToast";
|
|||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import { idbLoad, idbSave, idbDelete } from "./utils/StorageManager";
|
||||
import { ViewRoomPayload } from "./dispatcher/payloads/ViewRoomPayload";
|
||||
import { IConfigOptions } from "./IConfigOptions";
|
||||
|
||||
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
|
||||
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
|
||||
|
@ -62,7 +63,7 @@ export default abstract class BasePlatform {
|
|||
this.startUpdateCheck = this.startUpdateCheck.bind(this);
|
||||
}
|
||||
|
||||
abstract getConfig(): Promise<{}>;
|
||||
abstract getConfig(): Promise<IConfigOptions>;
|
||||
|
||||
abstract getDefaultDeviceDisplayName(): string;
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@ export default class CallHandler extends EventEmitter {
|
|||
}
|
||||
|
||||
private shouldObeyAssertedfIdentity(): boolean {
|
||||
return SdkConfig.get()['voip']?.obeyAssertedIdentity;
|
||||
return SdkConfig.getObject("voip")?.get("obey_asserted_identity");
|
||||
}
|
||||
|
||||
public getSupportsPstnProtocol(): boolean {
|
||||
|
|
186
src/IConfigOptions.ts
Normal file
186
src/IConfigOptions.ts
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IClientWellKnown } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { ValidatedServerConfig } from "./utils/AutoDiscoveryUtils";
|
||||
|
||||
// Convention decision: All config options are lower_snake_case
|
||||
// We use an isolated file for the interface so we can mess around with the eslint options.
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
/* eslint @typescript-eslint/naming-convention: ["error", { "selector": "property", "format": ["snake_case"] } ] */
|
||||
|
||||
// see element-web config.md for non-developer docs
|
||||
export interface IConfigOptions {
|
||||
// dev note: while true that this is arbitrary JSON, it's valuable to enforce that all
|
||||
// config options are documented for "find all usages" sort of searching.
|
||||
// [key: string]: any;
|
||||
|
||||
// Properties of this interface are roughly grouped by their subject matter, such as
|
||||
// "instance customisation", "login stuff", "branding", etc. Use blank lines to denote
|
||||
// a logical separation of properties, but keep similar ones near each other.
|
||||
|
||||
// Exactly one of the following must be supplied
|
||||
default_server_config?: IClientWellKnown; // copy/paste of client well-known
|
||||
default_server_name?: string; // domain to do well-known lookup on
|
||||
default_hs_url?: string; // http url
|
||||
|
||||
default_is_url?: string; // used in combination with default_hs_url, but for the identity server
|
||||
|
||||
// This is intended to be overridden by app startup and not specified by the user
|
||||
// This is also why it's allowed to have an interface that isn't snake_case
|
||||
validated_server_config?: ValidatedServerConfig;
|
||||
|
||||
fallback_hs_url?: string;
|
||||
|
||||
disable_custom_urls?: boolean;
|
||||
disable_guests?: boolean;
|
||||
disable_login_language_selector?: boolean;
|
||||
disable_3pid_login?: boolean;
|
||||
|
||||
brand: string;
|
||||
branding?: {
|
||||
welcome_background_url?: string | string[]; // chosen at random if array
|
||||
auth_header_logo_url?: string;
|
||||
auth_footer_links?: {text: string, url: string}[];
|
||||
};
|
||||
|
||||
map_style_url?: string; // for location-shared maps
|
||||
|
||||
embedded_pages?: {
|
||||
welcome_url?: string;
|
||||
home_url?: string;
|
||||
login_for_welcome?: boolean;
|
||||
};
|
||||
|
||||
permalink_prefix?: string;
|
||||
|
||||
update_base_url?: string;
|
||||
desktop_builds?: {
|
||||
available: boolean;
|
||||
logo: string; // url
|
||||
url: string; // download url
|
||||
};
|
||||
mobile_builds?: {
|
||||
ios?: string; // download url
|
||||
android?: string; // download url
|
||||
fdroid?: string; // download url
|
||||
};
|
||||
|
||||
mobile_guide_toast?: boolean;
|
||||
|
||||
default_theme?: "light" | "dark" | string; // custom themes are strings
|
||||
default_country_code?: string; // ISO 3166 alpha2 country code
|
||||
default_federate?: boolean;
|
||||
default_device_display_name?: string; // for device naming on login+registration
|
||||
|
||||
setting_defaults?: Record<string, any>; // <SettingName, Value>
|
||||
|
||||
integrations_ui_url?: string;
|
||||
integrations_rest_url?: string;
|
||||
integrations_widgets_urls?: string[];
|
||||
|
||||
show_labs_settings?: boolean;
|
||||
features?: Record<string, boolean>; // <FeatureName, EnabledBool>
|
||||
|
||||
bug_report_endpoint_url?: string; // omission disables bug reporting
|
||||
uisi_autorageshake_app?: string;
|
||||
sentry?: {
|
||||
dsn: string;
|
||||
environment?: string; // "production", etc
|
||||
};
|
||||
|
||||
widget_build_url?: string; // url called to replace jitsi/call widget creation
|
||||
audio_stream_url?: string;
|
||||
jitsi?: {
|
||||
preferred_domain: string;
|
||||
};
|
||||
jitsi_widget?: {
|
||||
skip_built_in_welcome_screen?: boolean;
|
||||
};
|
||||
voip?: {
|
||||
obey_asserted_identity?: boolean; // MSC3086
|
||||
};
|
||||
|
||||
logout_redirect_url?: string;
|
||||
|
||||
// sso_immediate_redirect is deprecated in favour of sso_redirect_options.immediate
|
||||
sso_immediate_redirect?: boolean;
|
||||
sso_redirect_options?: ISsoRedirectOptions;
|
||||
|
||||
custom_translations_url?: string;
|
||||
|
||||
report_event?: {
|
||||
admin_message_md: string; // message for how to contact the server owner when reporting an event
|
||||
};
|
||||
|
||||
welcome_user_id?: string;
|
||||
|
||||
room_directory?: {
|
||||
servers: string[];
|
||||
};
|
||||
|
||||
// piwik (matomo) is deprecated in favour of posthog
|
||||
piwik?: false | {
|
||||
url: string; // piwik instance
|
||||
site_id: string;
|
||||
policy_url: string; // cookie policy
|
||||
whitelisted_hs_urls: string[];
|
||||
};
|
||||
posthog?: {
|
||||
project_api_key: string;
|
||||
api_host: string; // hostname
|
||||
};
|
||||
analytics_owner?: string; // defaults to `brand`
|
||||
|
||||
// Server hosting upsell options
|
||||
hosting_signup_link?: string; // slightly different from `host_signup`
|
||||
host_signup?: {
|
||||
brand?: string; // acts as the enabled flag too (truthy == show)
|
||||
|
||||
// Required-ness denotes when `brand` is truthy
|
||||
cookie_policy_url: string;
|
||||
privacy_policy_url: string;
|
||||
terms_of_service_url: string;
|
||||
url: string;
|
||||
domains?: string[];
|
||||
};
|
||||
|
||||
enable_presence_by_hs_url?: Record<string, boolean>; // <HomeserverName, Enabled>
|
||||
|
||||
terms_and_conditions_links?: { url: string, text: string }[];
|
||||
|
||||
latex_maths_delims?: {
|
||||
inline?: {
|
||||
left?: string;
|
||||
right?: string;
|
||||
};
|
||||
display?: {
|
||||
left?: string;
|
||||
right?: string;
|
||||
};
|
||||
};
|
||||
|
||||
sync_timeline_limit?: number;
|
||||
dangerously_allow_unsafe_and_insecure_passwords?: boolean; // developer option
|
||||
}
|
||||
|
||||
export interface ISsoRedirectOptions {
|
||||
immediate?: boolean;
|
||||
on_welcome_page?: boolean;
|
||||
}
|
|
@ -161,7 +161,7 @@ const callBindings = (): KeyBinding[] => {
|
|||
};
|
||||
|
||||
const labsBindings = (): KeyBinding[] => {
|
||||
if (!SdkConfig.get()['showLabsSettings']) return [];
|
||||
if (!SdkConfig.get("show_labs_settings")) return [];
|
||||
|
||||
return getBindingsByCategory(CategoryName.LABS);
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ import SdkConfig from "./SdkConfig";
|
|||
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
||||
|
||||
export function getConfigLivestreamUrl() {
|
||||
return SdkConfig.get()["audioStreamUrl"];
|
||||
return SdkConfig.get("audio_stream_url");
|
||||
}
|
||||
|
||||
// Dummy rtmp URL used to signal that we want a special audio-only stream
|
||||
|
|
|
@ -126,10 +126,10 @@ export class PosthogAnalytics {
|
|||
}
|
||||
|
||||
constructor(private readonly posthog: PostHog) {
|
||||
const posthogConfig = SdkConfig.get()["posthog"];
|
||||
const posthogConfig = SdkConfig.getObject("posthog");
|
||||
if (posthogConfig) {
|
||||
this.posthog.init(posthogConfig.projectApiKey, {
|
||||
api_host: posthogConfig.apiHost,
|
||||
this.posthog.init(posthogConfig.get("project_api_key"), {
|
||||
api_host: posthogConfig.get("api_host"),
|
||||
autocapture: false,
|
||||
mask_all_text: true,
|
||||
mask_all_element_attributes: true,
|
||||
|
|
|
@ -44,8 +44,8 @@ export default class ScalarAuthClient {
|
|||
|
||||
// We try and store the token on a per-manager basis, but need a fallback
|
||||
// for the default manager.
|
||||
const configApiUrl = SdkConfig.get()['integrations_rest_url'];
|
||||
const configUiUrl = SdkConfig.get()['integrations_ui_url'];
|
||||
const configApiUrl = SdkConfig.get("integrations_rest_url");
|
||||
const configUiUrl = SdkConfig.get("integrations_ui_url");
|
||||
this.isDefaultManager = apiUrl === configApiUrl && configUiUrl === uiUrl;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,39 +15,26 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
export interface ISsoRedirectOptions {
|
||||
immediate?: boolean;
|
||||
on_welcome_page?: boolean; // eslint-disable-line camelcase
|
||||
}
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
export interface ConfigOptions {
|
||||
[key: string]: any;
|
||||
import { SnakedObject } from "./utils/SnakedObject";
|
||||
import { IConfigOptions, ISsoRedirectOptions } from "./IConfigOptions";
|
||||
import { KeysWithObjectShape } from "./@types/common";
|
||||
|
||||
logout_redirect_url?: string;
|
||||
|
||||
// sso_immediate_redirect is deprecated in favour of sso_redirect_options.immediate
|
||||
sso_immediate_redirect?: boolean;
|
||||
sso_redirect_options?: ISsoRedirectOptions;
|
||||
|
||||
custom_translations_url?: string;
|
||||
}
|
||||
/* eslint-enable camelcase*/
|
||||
|
||||
export const DEFAULTS: ConfigOptions = {
|
||||
// Brand name of the app
|
||||
// see element-web config.md for docs, or the IConfigOptions interface for dev docs
|
||||
export const DEFAULTS: Partial<IConfigOptions> = {
|
||||
brand: "Element",
|
||||
// URL to a page we show in an iframe to configure integrations
|
||||
integrations_ui_url: "https://scalar.vector.im/",
|
||||
// Base URL to the REST interface of the integrations server
|
||||
integrations_rest_url: "https://scalar.vector.im/api",
|
||||
// Where to send bug reports. If not specified, bugs cannot be sent.
|
||||
bug_report_endpoint_url: null,
|
||||
// Jitsi conference options
|
||||
jitsi: {
|
||||
// Default conference domain
|
||||
preferredDomain: "meet.element.io",
|
||||
preferred_domain: "meet.element.io",
|
||||
},
|
||||
|
||||
// @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,
|
||||
|
@ -56,20 +43,42 @@ export const DEFAULTS: ConfigOptions = {
|
|||
};
|
||||
|
||||
export default class SdkConfig {
|
||||
private static instance: ConfigOptions;
|
||||
private static instance: IConfigOptions;
|
||||
private static fallback: SnakedObject<IConfigOptions>;
|
||||
|
||||
private static setInstance(i: ConfigOptions) {
|
||||
private static setInstance(i: IConfigOptions) {
|
||||
SdkConfig.instance = i;
|
||||
SdkConfig.fallback = new SnakedObject(i);
|
||||
|
||||
// For debugging purposes
|
||||
window.mxReactSdkConfig = i;
|
||||
}
|
||||
|
||||
public static get() {
|
||||
return SdkConfig.instance || {};
|
||||
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,
|
||||
): IConfigOptions | 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 put(cfg: ConfigOptions) {
|
||||
public static getObject<K extends KeysWithObjectShape<IConfigOptions>>(
|
||||
key: K, altCaseName?: string,
|
||||
): Optional<SnakedObject<IConfigOptions[K]>> {
|
||||
const val = SdkConfig.get(key, altCaseName);
|
||||
if (val !== null && val !== undefined) {
|
||||
return new SnakedObject(val);
|
||||
}
|
||||
|
||||
// return the same type for sensitive callers (some want `undefined` specifically)
|
||||
return val === undefined ? undefined : null;
|
||||
}
|
||||
|
||||
public static put(cfg: IConfigOptions) {
|
||||
const defaultKeys = Object.keys(DEFAULTS);
|
||||
for (let i = 0; i < defaultKeys.length; ++i) {
|
||||
if (cfg[defaultKeys[i]] === undefined) {
|
||||
|
@ -79,18 +88,21 @@ export default class SdkConfig {
|
|||
SdkConfig.setInstance(cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the config to be completely empty.
|
||||
*/
|
||||
public static unset() {
|
||||
SdkConfig.setInstance({});
|
||||
SdkConfig.setInstance(<IConfigOptions>{}); // safe to cast - defaults will be applied
|
||||
}
|
||||
|
||||
public static add(cfg: ConfigOptions) {
|
||||
public static add(cfg: Partial<IConfigOptions>) {
|
||||
const liveConfig = SdkConfig.get();
|
||||
const newConfig = Object.assign({}, liveConfig, cfg);
|
||||
SdkConfig.put(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSsoRedirectOptions(config: ConfigOptions): ISsoRedirectOptions {
|
||||
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;
|
||||
|
||||
|
|
|
@ -103,11 +103,8 @@ const HomePage: React.FC<IProps> = ({ justRegistered = false }) => {
|
|||
if (justRegistered) {
|
||||
introSection = <UserWelcomeTop />;
|
||||
} else {
|
||||
const brandingConfig = config.branding;
|
||||
let logoUrl = "themes/element/img/logos/element-logo.svg";
|
||||
if (brandingConfig && brandingConfig.authHeaderLogoUrl) {
|
||||
logoUrl = brandingConfig.authHeaderLogoUrl;
|
||||
}
|
||||
const brandingConfig = SdkConfig.getObject("branding");
|
||||
const logoUrl = brandingConfig?.get("auth_header_logo_url") ?? "themes/element/img/logos/element-logo.svg";
|
||||
|
||||
introSection = <React.Fragment>
|
||||
<img src={logoUrl} alt={config.brand} />
|
||||
|
|
|
@ -39,8 +39,8 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
|
|||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const hostSignupConfig = SdkConfig.get().hostSignup;
|
||||
if (!hostSignupConfig?.brand) {
|
||||
const hostSignupConfig = SdkConfig.getObject("host_signup");
|
||||
if (!hostSignupConfig?.get("brand")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ export default class HostSignupAction extends React.PureComponent<IProps, IState
|
|||
label={_t(
|
||||
"Upgrade to %(hostSignupBrand)s",
|
||||
{
|
||||
hostSignupBrand: hostSignupConfig.brand,
|
||||
hostSignupBrand: hostSignupConfig.get("brand"),
|
||||
},
|
||||
)}
|
||||
onClick={this.openDialog}
|
||||
|
|
|
@ -75,6 +75,7 @@ import RightPanelStore from '../../stores/right-panel/RightPanelStore';
|
|||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
import { SwitchSpacePayload } from "../../dispatcher/payloads/SwitchSpacePayload";
|
||||
import { IConfigOptions } from "../../IConfigOptions";
|
||||
import LeftPanelLiveShareWarning from '../views/beacon/LeftPanelLiveShareWarning';
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
|
@ -103,12 +104,7 @@ interface IProps {
|
|||
roomOobData?: IOOBData;
|
||||
currentRoomId: string;
|
||||
collapseLhs: boolean;
|
||||
config: {
|
||||
piwik: {
|
||||
policyUrl: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
config: IConfigOptions;
|
||||
currentUserId?: string;
|
||||
currentGroupId?: string;
|
||||
currentGroupIsNew?: boolean;
|
||||
|
|
|
@ -131,6 +131,8 @@ import { ViewHomePagePayload } from '../../dispatcher/payloads/ViewHomePagePaylo
|
|||
import { AfterLeaveRoomPayload } from '../../dispatcher/payloads/AfterLeaveRoomPayload';
|
||||
import { DoAfterSyncPreparedPayload } from '../../dispatcher/payloads/DoAfterSyncPreparedPayload';
|
||||
import { ViewStartChatOrReusePayload } from '../../dispatcher/payloads/ViewStartChatOrReusePayload';
|
||||
import { IConfigOptions } from "../../IConfigOptions";
|
||||
import { SnakedObject } from "../../utils/SnakedObject";
|
||||
import InfoDialog from '../views/dialogs/InfoDialog';
|
||||
|
||||
// legacy export
|
||||
|
@ -154,12 +156,7 @@ interface IScreen {
|
|||
}
|
||||
|
||||
interface IProps { // TODO type things better
|
||||
config: {
|
||||
piwik: {
|
||||
policyUrl: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
};
|
||||
config: IConfigOptions;
|
||||
serverConfig?: ValidatedServerConfig;
|
||||
onNewScreen: (screen: string, replaceLast: boolean) => void;
|
||||
enableGuest?: boolean;
|
||||
|
@ -355,7 +352,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
Analytics.enable();
|
||||
}
|
||||
|
||||
initSentry(SdkConfig.get()["sentry"]);
|
||||
initSentry(SdkConfig.get("sentry"));
|
||||
}
|
||||
|
||||
private async postLoginSetup() {
|
||||
|
@ -474,7 +471,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
private getServerProperties() {
|
||||
let props = this.state.serverConfig;
|
||||
if (!props) props = this.props.serverConfig; // for unit tests
|
||||
if (!props) props = SdkConfig.get()["validated_server_config"];
|
||||
if (!props) props = SdkConfig.get("validated_server_config");
|
||||
return { serverConfig: props };
|
||||
}
|
||||
|
||||
|
@ -865,7 +862,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
);
|
||||
|
||||
// If the hs url matches then take the hs name we know locally as it is likely prettier
|
||||
const defaultConfig = SdkConfig.get()["validated_server_config"] as ValidatedServerConfig;
|
||||
const defaultConfig = SdkConfig.get("validated_server_config");
|
||||
if (defaultConfig && defaultConfig.hsUrl === newState.serverConfig.hsUrl) {
|
||||
newState.serverConfig.hsName = defaultConfig.hsName;
|
||||
newState.serverConfig.hsNameIsDifferent = defaultConfig.hsNameIsDifferent;
|
||||
|
@ -1062,11 +1059,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
|
||||
private chatCreateOrReuse(userId: string) {
|
||||
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
|
||||
// Use a deferred action to reshow the dialog once the user has registered
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
// No point in making 2 DMs with welcome bot. This assumes view_set_mxid will
|
||||
// result in a new DM with the welcome user.
|
||||
if (userId !== this.props.config.welcomeUserId) {
|
||||
if (userId !== snakedConfig.get("welcome_user_id")) {
|
||||
dis.dispatch<DoAfterSyncPreparedPayload<ViewStartChatOrReusePayload>>({
|
||||
action: Action.DoAfterSyncPrepared,
|
||||
deferred_action: {
|
||||
|
@ -1083,7 +1081,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// `_chatCreateOrReuse` again)
|
||||
go_welcome_on_cancel: true,
|
||||
screen_after: {
|
||||
screen: `user/${this.props.config.welcomeUserId}`,
|
||||
screen: `user/${snakedConfig.get("welcome_user_id")}`,
|
||||
params: { action: 'chat' },
|
||||
},
|
||||
});
|
||||
|
@ -1231,12 +1229,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
await waitFor;
|
||||
|
||||
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
|
||||
const welcomeUserRooms = DMRoomMap.shared().getDMRoomsForUserId(
|
||||
this.props.config.welcomeUserId,
|
||||
snakedConfig.get("welcome_user_id"),
|
||||
);
|
||||
if (welcomeUserRooms.length === 0) {
|
||||
const roomId = await createRoom({
|
||||
dmUserId: this.props.config.welcomeUserId,
|
||||
dmUserId: snakedConfig.get("welcome_user_id"),
|
||||
// Only view the welcome user if we're NOT looking at a room
|
||||
andView: !this.state.currentRoomId,
|
||||
spinner: false, // we're already showing one: we don't need another one
|
||||
|
@ -1250,7 +1249,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
// user room (it doesn't wait for new data from the server, just
|
||||
// the saved sync to be loaded).
|
||||
const saveWelcomeUser = (ev: MatrixEvent) => {
|
||||
if (ev.getType() === EventType.Direct && ev.getContent()[this.props.config.welcomeUserId]) {
|
||||
if (ev.getType() === EventType.Direct && ev.getContent()[snakedConfig.get("welcome_user_id")]) {
|
||||
MatrixClientPeg.get().store.save(true);
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.AccountData, saveWelcomeUser);
|
||||
}
|
||||
|
@ -1280,7 +1279,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
} else if (MatrixClientPeg.currentUserIsJustRegistered()) {
|
||||
MatrixClientPeg.setJustRegisteredUserId(null);
|
||||
|
||||
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
|
||||
const snakedConfig = new SnakedObject<IConfigOptions>(this.props.config);
|
||||
if (snakedConfig.get("welcome_user_id") && getCurrentLanguage().startsWith("en")) {
|
||||
const welcomeUserRoom = await this.startWelcomeUserChat();
|
||||
if (welcomeUserRoom === null) {
|
||||
// We didn't redirect to the welcome user room, so show
|
||||
|
@ -1312,7 +1312,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
showAnonymousAnalyticsOptInToast();
|
||||
}
|
||||
|
||||
if (SdkConfig.get().mobileGuideToast) {
|
||||
if (SdkConfig.get("mobile_guide_toast")) {
|
||||
// The toast contains further logic to detect mobile platforms,
|
||||
// check if it has been dismissed before, etc.
|
||||
showMobileGuideToast();
|
||||
|
@ -1463,7 +1463,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
|
||||
if (!localStorage.getItem("mx_seen_feature_thread_experimental")) {
|
||||
setTimeout(() => {
|
||||
if (SettingsStore.getValue("feature_thread") && SdkConfig.get()['showLabsSettings']) {
|
||||
if (SettingsStore.getValue("feature_thread") && SdkConfig.get("show_labs_settings")) {
|
||||
Modal.createDialog(InfoDialog, {
|
||||
title: _t("Threads are no longer experimental! 🎉"),
|
||||
description: <>
|
||||
|
|
|
@ -103,7 +103,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
|
||||
let roomServer = myHomeserver;
|
||||
if (
|
||||
SdkConfig.get().roomDirectory?.servers?.includes(lsRoomServer) ||
|
||||
SdkConfig.getObject("room_directory")?.get("servers")?.includes(lsRoomServer) ||
|
||||
SettingsStore.getValue("room_directory_servers")?.includes(lsRoomServer)
|
||||
) {
|
||||
roomServer = lsRoomServer;
|
||||
|
|
|
@ -53,7 +53,6 @@ import IconizedContextMenu, {
|
|||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||
import { UIFeature } from "../../settings/UIFeature";
|
||||
import HostSignupAction from "./HostSignupAction";
|
||||
import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes";
|
||||
import SpaceStore from "../../stores/spaces/SpaceStore";
|
||||
import { UPDATE_SELECTED_SPACE } from "../../stores/spaces";
|
||||
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||
|
@ -375,7 +374,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
if (!this.state.contextMenuPosition) return null;
|
||||
|
||||
let topSection;
|
||||
const hostSignupConfig: IHostSignupConfig = SdkConfig.get().hostSignup;
|
||||
const hostSignupConfig = SdkConfig.getObject("host_signup");
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
topSection = (
|
||||
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
|
||||
|
@ -395,18 +394,16 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
}) }
|
||||
</div>
|
||||
);
|
||||
} else if (hostSignupConfig) {
|
||||
if (hostSignupConfig && hostSignupConfig.url) {
|
||||
} else if (hostSignupConfig?.get("url")) {
|
||||
// If hostSignup.domains is set to a non-empty array, only show
|
||||
// dialog if the user is on the domain or a subdomain.
|
||||
const hostSignupDomains = hostSignupConfig.domains || [];
|
||||
const hostSignupDomains = hostSignupConfig.get("domains") || [];
|
||||
const mxDomain = MatrixClientPeg.get().getDomain();
|
||||
const validDomains = hostSignupDomains.filter(d => (d === mxDomain || mxDomain.endsWith(`.${d}`)));
|
||||
if (!hostSignupConfig.domains || validDomains.length > 0) {
|
||||
if (!hostSignupConfig.get("domains") || validDomains.length > 0) {
|
||||
topSection = <HostSignupAction onClick={this.onCloseMenu} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let homeButton = null;
|
||||
if (this.hasHomePage) {
|
||||
|
|
|
@ -245,7 +245,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
|
||||
if (error.errcode === 'M_USER_DEACTIVATED') {
|
||||
errorText = _t('This account has been deactivated.');
|
||||
} else if (SdkConfig.get()['disable_custom_urls']) {
|
||||
} else if (SdkConfig.get("disable_custom_urls")) {
|
||||
errorText = (
|
||||
<div>
|
||||
<div>{ _t('Incorrect username and/or password.') }</div>
|
||||
|
|
|
@ -59,7 +59,7 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
|||
super(props);
|
||||
|
||||
let defaultCountry: PhoneNumberCountryDefinition = COUNTRIES[0];
|
||||
const defaultCountryCode = SdkConfig.get()["defaultCountryCode"];
|
||||
const defaultCountryCode = SdkConfig.get("default_country_code");
|
||||
if (defaultCountryCode) {
|
||||
const country = COUNTRIES.find(c => c.iso2 === defaultCountryCode.toUpperCase());
|
||||
if (country) defaultCountry = country;
|
||||
|
|
|
@ -35,7 +35,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default function LanguageSelector({ disabled }: IProps): JSX.Element {
|
||||
if (SdkConfig.get()['disable_login_language_selector']) return <div />;
|
||||
if (SdkConfig.get("disable_login_language_selector")) return <div />;
|
||||
return <LanguageDropdown
|
||||
className="mx_AuthBody_language"
|
||||
onOptionChange={onChange}
|
||||
|
|
|
@ -73,7 +73,7 @@ class PassphraseField extends PureComponent<IProps> {
|
|||
return false;
|
||||
}
|
||||
const safe = complexity.score >= this.props.minScore;
|
||||
const allowUnsafe = SdkConfig.get()["dangerously_allow_unsafe_and_insecure_passwords"];
|
||||
const allowUnsafe = SdkConfig.get("dangerously_allow_unsafe_and_insecure_passwords");
|
||||
return allowUnsafe || safe;
|
||||
},
|
||||
valid: function(complexity) {
|
||||
|
|
|
@ -39,10 +39,10 @@ export default class Welcome extends React.PureComponent<IProps> {
|
|||
// FIXME: Using an import will result in wrench-element-tests failures
|
||||
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
|
||||
|
||||
const pagesConfig = SdkConfig.get().embeddedPages;
|
||||
const pagesConfig = SdkConfig.getObject("embedded_pages");
|
||||
let pageUrl = null;
|
||||
if (pagesConfig) {
|
||||
pageUrl = pagesConfig.welcomeUrl;
|
||||
pageUrl = pagesConfig.get("welcome_url");
|
||||
}
|
||||
if (!pageUrl) {
|
||||
pageUrl = 'welcome.html';
|
||||
|
|
|
@ -15,12 +15,14 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import Modal from "../../../Modal";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
|
||||
export enum ButtonClicked {
|
||||
Primary,
|
||||
|
@ -96,8 +98,12 @@ const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
|
|||
};
|
||||
|
||||
export const showDialog = (props: Omit<IProps, "cookiePolicyUrl" | "analyticsOwner">): void => {
|
||||
const privacyPolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
const analyticsOwner = SdkConfig.get().analyticsOwner ?? SdkConfig.get().brand;
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let privacyPolicyUrl: Optional<string>;
|
||||
if (piwikConfig && typeof piwikConfig === "object") {
|
||||
privacyPolicyUrl = (new SnakedObject(piwikConfig)).get("policy_url");
|
||||
}
|
||||
const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get("brand");
|
||||
Modal.createTrackedDialog(
|
||||
"Analytics Learn More",
|
||||
"",
|
||||
|
|
|
@ -71,7 +71,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
|||
}
|
||||
|
||||
private buildSuggestions(): IPerson[] {
|
||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get()['welcomeUserId']]);
|
||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]);
|
||||
if (this.props.roomId) {
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
||||
|
|
|
@ -75,7 +75,6 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
joinRule = JoinRule.Restricted;
|
||||
}
|
||||
|
||||
const config = SdkConfig.get();
|
||||
this.state = {
|
||||
isPublic: this.props.defaultPublic || false,
|
||||
isEncrypted: this.props.defaultEncrypted ?? privateShouldBeEncrypted(),
|
||||
|
@ -84,7 +83,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
topic: "",
|
||||
alias: "",
|
||||
detailsOpen: false,
|
||||
noFederate: config.default_federate === false,
|
||||
noFederate: SdkConfig.get().default_federate === false,
|
||||
nameIsValid: false,
|
||||
canChangeEncryption: true,
|
||||
};
|
||||
|
|
|
@ -28,12 +28,13 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
|||
import { HostSignupStore } from "../../../stores/HostSignupStore";
|
||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import {
|
||||
IHostSignupConfig,
|
||||
IPostmessage,
|
||||
IPostmessageResponseData,
|
||||
PostmessageAction,
|
||||
} from "./HostSignupDialogTypes";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IConfigOptions } from "../../../IConfigOptions";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
|
||||
const HOST_SIGNUP_KEY = "host_signup";
|
||||
|
||||
|
@ -48,7 +49,7 @@ interface IState {
|
|||
@replaceableComponent("views.dialogs.HostSignupDialog")
|
||||
export default class HostSignupDialog extends React.PureComponent<IProps, IState> {
|
||||
private iframeRef: React.RefObject<HTMLIFrameElement> = React.createRef();
|
||||
private readonly config: IHostSignupConfig;
|
||||
private readonly config: SnakedObject<IConfigOptions["host_signup"]>;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -59,11 +60,11 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
minimized: false,
|
||||
};
|
||||
|
||||
this.config = SdkConfig.get().hostSignup;
|
||||
this.config = SdkConfig.getObject("host_signup");
|
||||
}
|
||||
|
||||
private messageHandler = async (message: IPostmessage) => {
|
||||
if (!this.config.url.startsWith(message.origin)) {
|
||||
if (!this.config.get("url").startsWith(message.origin)) {
|
||||
return;
|
||||
}
|
||||
switch (message.data.action) {
|
||||
|
@ -142,7 +143,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
};
|
||||
|
||||
private sendMessage = (message: IPostmessageResponseData) => {
|
||||
this.iframeRef.current.contentWindow.postMessage(message, this.config.url);
|
||||
this.iframeRef.current.contentWindow.postMessage(message, this.config.get("url"));
|
||||
};
|
||||
|
||||
private async sendAccountDetails() {
|
||||
|
@ -176,12 +177,16 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
};
|
||||
|
||||
private onAccountDetailsRequest = () => {
|
||||
const cookiePolicyUrl = this.config.get("cookie_policy_url");
|
||||
const privacyPolicyUrl = this.config.get("privacy_policy_url");
|
||||
const tosUrl = this.config.get("terms_of_service_url");
|
||||
|
||||
const textComponent = (
|
||||
<>
|
||||
<p>
|
||||
{ _t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
|
||||
"account to fetch verified email addresses. This data is not stored.", {
|
||||
hostSignupBrand: this.config.brand,
|
||||
hostSignupBrand: this.config.get("brand"),
|
||||
}) }
|
||||
</p>
|
||||
<p>
|
||||
|
@ -189,17 +194,17 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
{},
|
||||
{
|
||||
cookiePolicyLink: () => (
|
||||
<a href={this.config.cookiePolicyUrl} target="_blank" rel="noreferrer noopener">
|
||||
<a href={cookiePolicyUrl} target="_blank" rel="noreferrer noopener">
|
||||
{ _t("Cookie Policy") }
|
||||
</a>
|
||||
),
|
||||
privacyPolicyLink: () => (
|
||||
<a href={this.config.privacyPolicyUrl} target="_blank" rel="noreferrer noopener">
|
||||
<a href={privacyPolicyUrl} target="_blank" rel="noreferrer noopener">
|
||||
{ _t("Privacy Policy") }
|
||||
</a>
|
||||
),
|
||||
termsOfServiceLink: () => (
|
||||
<a href={this.config.termsOfServiceUrl} target="_blank" rel="noreferrer noopener">
|
||||
<a href={tosUrl} target="_blank" rel="noreferrer noopener">
|
||||
{ _t("Terms of Service") }
|
||||
</a>
|
||||
),
|
||||
|
@ -247,7 +252,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
<div className="mx_Dialog_header mx_Dialog_headerWithButton">
|
||||
<div className="mx_Dialog_title">
|
||||
{ _t("%(hostSignupBrand)s Setup", {
|
||||
hostSignupBrand: this.config.brand,
|
||||
hostSignupBrand: this.config.get("brand"),
|
||||
}) }
|
||||
</div>
|
||||
<AccessibleButton
|
||||
|
@ -284,10 +289,10 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
|
|||
title={_t(
|
||||
"Upgrade to %(hostSignupBrand)s",
|
||||
{
|
||||
hostSignupBrand: this.config.brand,
|
||||
hostSignupBrand: this.config.get("brand"),
|
||||
},
|
||||
)}
|
||||
src={this.config.url}
|
||||
src={this.config.get("url")}
|
||||
ref={this.iframeRef}
|
||||
sandbox="allow-forms allow-scripts allow-same-origin allow-popups"
|
||||
/>
|
||||
|
|
|
@ -45,12 +45,3 @@ export interface IPostmessage {
|
|||
data: IPostmessageRequestData;
|
||||
origin: string;
|
||||
}
|
||||
|
||||
export interface IHostSignupConfig {
|
||||
brand: string;
|
||||
cookiePolicyUrl: string;
|
||||
domains: Array<string>;
|
||||
privacyPolicyUrl: string;
|
||||
termsOfServiceUrl: string;
|
||||
url: string;
|
||||
}
|
||||
|
|
|
@ -412,7 +412,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
throw new Error("When using KIND_CALL_TRANSFER a call is required for an InviteDialog");
|
||||
}
|
||||
|
||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get()['welcomeUserId']]);
|
||||
const alreadyInvited = new Set([MatrixClientPeg.get().getUserId(), SdkConfig.get("welcome_user_id")]);
|
||||
if (props.roomId) {
|
||||
const room = MatrixClientPeg.get().getRoom(props.roomId);
|
||||
if (!room) throw new Error("Room ID given to InviteDialog does not look like a room");
|
||||
|
|
|
@ -259,9 +259,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
const adminMessageMD =
|
||||
SdkConfig.get().reportEvent &&
|
||||
SdkConfig.get().reportEvent.adminMessageMD;
|
||||
const adminMessageMD = SdkConfig
|
||||
.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
|
||||
let adminMessage;
|
||||
if (adminMessageMD) {
|
||||
const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
|
||||
|
@ -272,7 +271,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
// Display report-to-moderator dialog.
|
||||
// We let the user pick a nature.
|
||||
const client = MatrixClientPeg.get();
|
||||
const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
|
||||
const homeServerName = SdkConfig.get("validated_server_config").hsName;
|
||||
let subtitle;
|
||||
switch (this.state.nature) {
|
||||
case Nature.Disagreement:
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
|||
super(props);
|
||||
|
||||
const config = SdkConfig.get();
|
||||
this.defaultServer = config["validated_server_config"] as ValidatedServerConfig;
|
||||
this.defaultServer = config["validated_server_config"];
|
||||
const { serverConfig } = this.props;
|
||||
|
||||
let otherHomeserver = "";
|
||||
|
|
|
@ -159,7 +159,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
"UserSettingsSecurityPrivacy",
|
||||
));
|
||||
// Show the Labs tab if enabled or if there are any active betas
|
||||
if (SdkConfig.get()['showLabsSettings']
|
||||
if (SdkConfig.get("show_labs_settings")
|
||||
|| SettingsStore.getFeatureSettingNames().some(k => SettingsStore.getBetaInfo(k))
|
||||
) {
|
||||
tabs.push(new Tab(
|
||||
|
|
|
@ -40,6 +40,8 @@ import TextInputDialog from "../dialogs/TextInputDialog";
|
|||
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||
import UIStore from "../../../stores/UIStore";
|
||||
import { compare } from "../../../utils/strings";
|
||||
import { SnakedObject } from "../../../utils/SnakedObject";
|
||||
import { IConfigOptions } from "../../../IConfigOptions";
|
||||
|
||||
// XXX: We would ideally use a symbol here but we can't since we save this value to localStorage
|
||||
export const ALL_ROOMS = "ALL_ROOMS";
|
||||
|
@ -122,11 +124,11 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
|
|||
// we either show the button or the dropdown in its place.
|
||||
let content;
|
||||
if (menuDisplayed) {
|
||||
const config = SdkConfig.get();
|
||||
const roomDirectory = config.roomDirectory || {};
|
||||
const roomDirectory = SdkConfig.getObject("room_directory")
|
||||
?? new SnakedObject<IConfigOptions["room_directory"]>({ servers: [] });
|
||||
|
||||
const hsName = MatrixClientPeg.getHomeserverName();
|
||||
const configServers = new Set<string>(roomDirectory.servers);
|
||||
const configServers = new Set<string>(roomDirectory.get("servers"));
|
||||
|
||||
// configured servers take preference over user-defined ones, if one occurs in both ignore the latter one.
|
||||
const removableServers = new Set(userDefinedServers.filter(s => !configServers.has(s) && s !== hsName));
|
||||
|
|
|
@ -59,21 +59,23 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
|
|||
</>;
|
||||
}
|
||||
|
||||
const { desktopBuilds, brand } = SdkConfig.get();
|
||||
const brand = SdkConfig.get("brand");
|
||||
const desktopBuilds = SdkConfig.getObject("desktop_builds");
|
||||
|
||||
let text = null;
|
||||
let logo = null;
|
||||
if (desktopBuilds.available) {
|
||||
logo = <img src={desktopBuilds.logo} />;
|
||||
if (desktopBuilds.get("available")) {
|
||||
logo = <img src={desktopBuilds.get("logo")} />;
|
||||
const buildUrl = desktopBuilds.get("url");
|
||||
switch (kind) {
|
||||
case WarningKind.Files:
|
||||
text = _t("Use the <a>Desktop app</a> to see all encrypted files", {}, {
|
||||
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{ sub }</a>),
|
||||
a: sub => (<a href={buildUrl} target="_blank" rel="noreferrer noopener">{ sub }</a>),
|
||||
});
|
||||
break;
|
||||
case WarningKind.Search:
|
||||
text = _t("Use the <a>Desktop app</a> to search encrypted messages", {}, {
|
||||
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{ sub }</a>),
|
||||
a: sub => (<a href={buildUrl} target="_blank" rel="noreferrer noopener">{ sub }</a>),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ const onHelpClick = () => {
|
|||
};
|
||||
|
||||
const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }: IProps) => {
|
||||
const disableCustomUrls = SdkConfig.get()["disable_custom_urls"];
|
||||
const disableCustomUrls = SdkConfig.get("disable_custom_urls");
|
||||
|
||||
let editBtn;
|
||||
if (!disableCustomUrls && onServerConfigChange) {
|
||||
|
|
|
@ -1575,7 +1575,7 @@ const UserInfoHeader: React.FC<{
|
|||
presenceCurrentlyActive = member.user.currentlyActive;
|
||||
}
|
||||
|
||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||
const enablePresenceByHsUrl = SdkConfig.get("enable_presence_by_hs_url");
|
||||
let showPresence = true;
|
||||
if (enablePresenceByHsUrl && enablePresenceByHsUrl[cli.baseUrl] !== undefined) {
|
||||
showPresence = enablePresenceByHsUrl[cli.baseUrl];
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
cli.on(ClientEvent.Room, this.onRoom); // invites & joining after peek
|
||||
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
|
||||
const enablePresenceByHsUrl = SdkConfig.get("enable_presence_by_hs_url");
|
||||
const hsUrl = MatrixClientPeg.get().baseUrl;
|
||||
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
private onStartBotChat = (e) => {
|
||||
this.props.closeSettingsFn();
|
||||
createRoom({
|
||||
dmUserId: SdkConfig.get().welcomeUserId,
|
||||
dmUserId: SdkConfig.get("welcome_user_id"),
|
||||
andView: true,
|
||||
});
|
||||
};
|
||||
|
@ -105,7 +105,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
if (!tocLinks) return null;
|
||||
|
||||
const legalLinks = [];
|
||||
for (const tocEntry of SdkConfig.get().terms_and_conditions_links) {
|
||||
for (const tocEntry of tocLinks) {
|
||||
legalLinks.push(<div key={tocEntry.url}>
|
||||
<a href={tocEntry.url} rel="noreferrer noopener" target="_blank">{ tocEntry.text }</a>
|
||||
</div>);
|
||||
|
@ -198,7 +198,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
</a>,
|
||||
},
|
||||
);
|
||||
if (SdkConfig.get().welcomeUserId && getCurrentLanguage().startsWith('en')) {
|
||||
if (SdkConfig.get("welcome_user_id") && getCurrentLanguage().startsWith('en')) {
|
||||
faqText = (
|
||||
<div>
|
||||
{ _t(
|
||||
|
|
|
@ -35,7 +35,7 @@ interface IKeyboardShortcutRowProps {
|
|||
|
||||
// Filter out the labs section if labs aren't enabled.
|
||||
const visibleCategories = Object.entries(CATEGORIES).filter(([categoryName]) =>
|
||||
categoryName !== CategoryName.LABS || SdkConfig.get()['showLabsSettings']);
|
||||
categoryName !== CategoryName.LABS || SdkConfig.get("show_labs_settings"));
|
||||
|
||||
const KeyboardShortcutRow: React.FC<IKeyboardShortcutRowProps> = ({ name }) => {
|
||||
const displayName = getKeyboardShortcutDisplayName(name);
|
||||
|
|
|
@ -86,7 +86,7 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
|
|||
}
|
||||
|
||||
let labsSection;
|
||||
if (SdkConfig.get()['showLabsSettings']) {
|
||||
if (SdkConfig.get("show_labs_settings")) {
|
||||
const groups = new EnhancedMap<LabGroup, JSX.Element[]>();
|
||||
labs.forEach(f => {
|
||||
groups.getOrCreate(SettingsStore.getLabGroup(f), []).push(
|
||||
|
|
|
@ -95,7 +95,7 @@ export function htmlSerializeIfNeeded(model: EditorModel, { forceHTML = false }
|
|||
patternNames.forEach(function(patternName) {
|
||||
patternTypes.forEach(function(patternType) {
|
||||
// get the regex replace pattern from config or use the default
|
||||
const pattern = (((SdkConfig.get()["latex_maths_delims"] ||
|
||||
const pattern = (((SdkConfig.get("latex_maths_delims") ||
|
||||
{})[patternType] || {})["pattern"] || {})[patternName] ||
|
||||
patternDefaults[patternName][patternType];
|
||||
|
||||
|
|
|
@ -77,8 +77,8 @@ export class IntegrationManagers {
|
|||
}
|
||||
|
||||
private setupConfiguredManager() {
|
||||
const apiUrl: string = SdkConfig.get()['integrations_rest_url'];
|
||||
const uiUrl: string = SdkConfig.get()['integrations_ui_url'];
|
||||
const apiUrl: string = SdkConfig.get("integrations_rest_url");
|
||||
const uiUrl: string = SdkConfig.get("integrations_ui_url");
|
||||
|
||||
if (apiUrl && uiUrl) {
|
||||
this.managers.push(new IntegrationManagerInstance(Kind.Config, apiUrl, uiUrl));
|
||||
|
|
|
@ -20,6 +20,7 @@ import { MatrixClient } from "matrix-js-sdk/src/client";
|
|||
import SdkConfig from "./SdkConfig";
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { IConfigOptions } from "./IConfigOptions";
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
|
@ -173,7 +174,7 @@ async function getContexts(): Promise<Contexts> {
|
|||
}
|
||||
|
||||
export async function sendSentryReport(userText: string, issueUrl: string, error: Error): Promise<void> {
|
||||
const sentryConfig = SdkConfig.get()["sentry"];
|
||||
const sentryConfig = SdkConfig.getObject("sentry");
|
||||
if (!sentryConfig) return;
|
||||
|
||||
const captureContext = {
|
||||
|
@ -198,12 +199,7 @@ export function setSentryUser(mxid: string): void {
|
|||
Sentry.setUser({ username: mxid });
|
||||
}
|
||||
|
||||
interface ISentryConfig {
|
||||
dsn: string;
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
export async function initSentry(sentryConfig: ISentryConfig): Promise<void> {
|
||||
export async function initSentry(sentryConfig: IConfigOptions["sentry"]): Promise<void> {
|
||||
if (!sentryConfig) return;
|
||||
// Only enable Integrations.GlobalHandlers, which hooks uncaught exceptions, if automaticErrorReporting is true
|
||||
const integrations = [
|
||||
|
|
|
@ -19,6 +19,8 @@ import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
|
|||
|
||||
import SettingsHandler from "./SettingsHandler";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import { SnakedObject } from "../../utils/SnakedObject";
|
||||
import { IConfigOptions } from "../../IConfigOptions";
|
||||
|
||||
/**
|
||||
* Gets and sets settings at the "config" level. This handler does not make use of the
|
||||
|
@ -30,10 +32,10 @@ export default class ConfigSettingsHandler extends SettingsHandler {
|
|||
}
|
||||
|
||||
public getValue(settingName: string, roomId: string): any {
|
||||
const config = SdkConfig.get() || {};
|
||||
const config = new SnakedObject<IConfigOptions>(SdkConfig.get());
|
||||
|
||||
if (this.featureNames.includes(settingName)) {
|
||||
const labsConfig = config["features"] || {};
|
||||
const labsConfig = config.get("features") || {};
|
||||
const val = labsConfig[settingName];
|
||||
if (isNullOrUndefined(val)) return null; // no definition at this level
|
||||
if (val === true || val === false) return val; // new style: mapped as a boolean
|
||||
|
@ -45,10 +47,10 @@ export default class ConfigSettingsHandler extends SettingsHandler {
|
|||
|
||||
// Special case themes
|
||||
if (settingName === "theme") {
|
||||
return config["default_theme"];
|
||||
return config.get("default_theme");
|
||||
}
|
||||
|
||||
const settingsConfig = config["settingDefaults"];
|
||||
const settingsConfig = config.get("setting_defaults");
|
||||
if (!settingsConfig || isNullOrUndefined(settingsConfig[settingName])) return null;
|
||||
return settingsConfig[settingName];
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
||||
import { _t } from "../languageHandler";
|
||||
import SdkConfig from "../SdkConfig";
|
||||
|
@ -28,6 +29,8 @@ import {
|
|||
showDialog as showAnalyticsLearnMoreDialog,
|
||||
} from "../components/views/dialogs/AnalyticsLearnMoreDialog";
|
||||
import { Action } from "../dispatcher/actions";
|
||||
import { SnakedObject } from "../utils/SnakedObject";
|
||||
import { IConfigOptions } from "../IConfigOptions";
|
||||
|
||||
const onAccept = () => {
|
||||
dis.dispatch({
|
||||
|
@ -81,7 +84,12 @@ const TOAST_KEY = "analytics";
|
|||
const getAnonymousDescription = (): ReactNode => {
|
||||
// get toast description for anonymous tracking (the previous scheme pre-posthog)
|
||||
const brand = SdkConfig.get().brand;
|
||||
const cookiePolicyUrl = SdkConfig.get().piwik?.policyUrl;
|
||||
const piwikConfig = SdkConfig.get("piwik");
|
||||
let piwik: Optional<SnakedObject<Extract<IConfigOptions["piwik"], object>>>;
|
||||
if (typeof piwikConfig === 'object') {
|
||||
piwik = new SnakedObject(piwikConfig);
|
||||
}
|
||||
const cookiePolicyUrl = piwik?.get("policy_url");
|
||||
return _t(
|
||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. " +
|
||||
"This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||
|
@ -100,7 +108,7 @@ const getAnonymousDescription = (): ReactNode => {
|
|||
};
|
||||
|
||||
const showToast = (props: Omit<React.ComponentProps<typeof GenericToast>, "toastKey">) => {
|
||||
const analyticsOwner = SdkConfig.get().analyticsOwner ?? SdkConfig.get().brand;
|
||||
const analyticsOwner = SdkConfig.get("analytics_owner") ?? SdkConfig.get().brand;
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: TOAST_KEY,
|
||||
title: _t("Help improve %(analyticsOwner)s", { analyticsOwner }),
|
||||
|
|
|
@ -207,7 +207,7 @@ export default class AutoDiscoveryUtils {
|
|||
const hsResult = discoveryResult['m.homeserver'];
|
||||
const isResult = discoveryResult['m.identity_server'];
|
||||
|
||||
const defaultConfig = SdkConfig.get()["validated_server_config"];
|
||||
const defaultConfig = SdkConfig.get("validated_server_config");
|
||||
|
||||
// Validate the identity server first because an invalid identity server causes
|
||||
// an invalid homeserver, which may not be picked up correctly.
|
||||
|
|
|
@ -21,7 +21,7 @@ import SdkConfig from '../SdkConfig';
|
|||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
|
||||
export function getDefaultIdentityServerUrl(): string {
|
||||
return SdkConfig.get()['validated_server_config']['isUrl'];
|
||||
return SdkConfig.get("validated_server_config").isUrl;
|
||||
}
|
||||
|
||||
export function useDefaultIdentityServer(): void {
|
||||
|
|
36
src/utils/SnakedObject.ts
Normal file
36
src/utils/SnakedObject.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
export function snakeToCamel(s: string): string {
|
||||
return s.replace(/._./g, v => `${v[0]}${v[2].toUpperCase()}`);
|
||||
}
|
||||
|
||||
export class SnakedObject<T = Record<string, any>> {
|
||||
public constructor(private obj: T) {
|
||||
}
|
||||
|
||||
public get<K extends string & keyof T>(key: K, altCaseName?: string): T[K] {
|
||||
const val = this.obj[key];
|
||||
if (val !== undefined) return val;
|
||||
|
||||
return this.obj[altCaseName ?? snakeToCamel(key)];
|
||||
}
|
||||
|
||||
// Make JSON.stringify() pretend that everything is fine
|
||||
public toJSON() {
|
||||
return this.obj;
|
||||
}
|
||||
}
|
|
@ -14,23 +14,37 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { ConfigOptions } from "../SdkConfig";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
export function getHomePageUrl(appConfig: ConfigOptions): string | null {
|
||||
const pagesConfig = appConfig.embeddedPages;
|
||||
let pageUrl = pagesConfig?.homeUrl;
|
||||
import { IConfigOptions } from "../IConfigOptions";
|
||||
import { SnakedObject } from "./SnakedObject";
|
||||
|
||||
export function getHomePageUrl(appConfig: IConfigOptions): string | null {
|
||||
const config = new SnakedObject(appConfig);
|
||||
|
||||
const pagesConfig = config.get("embedded_pages");
|
||||
let pageUrl = pagesConfig ? (new SnakedObject(pagesConfig).get("home_url")) : null;
|
||||
|
||||
if (!pageUrl) {
|
||||
// This is a deprecated config option for the home page
|
||||
// (despite the name, given we also now have a welcome
|
||||
// page, which is not the same).
|
||||
pageUrl = appConfig.welcomePageUrl;
|
||||
pageUrl = (<any>appConfig).welcomePageUrl;
|
||||
if (pageUrl) {
|
||||
logger.warn(
|
||||
"You are using a deprecated config option: `welcomePageUrl`. Please use " +
|
||||
"`embedded_pages.home_url` instead, per https://github.com/vector-im/element-web/issues/21428",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return pageUrl;
|
||||
}
|
||||
|
||||
export function shouldUseLoginForWelcome(appConfig: ConfigOptions): boolean {
|
||||
const pagesConfig = appConfig.embeddedPages;
|
||||
return pagesConfig?.loginForWelcome === true;
|
||||
export function shouldUseLoginForWelcome(appConfig: IConfigOptions): boolean {
|
||||
const config = new SnakedObject(appConfig);
|
||||
const pagesConfig = config.get("embedded_pages");
|
||||
return pagesConfig
|
||||
? ((new SnakedObject(pagesConfig).get("login_for_welcome")) === true)
|
||||
: false;
|
||||
}
|
||||
|
|
|
@ -413,7 +413,7 @@ export function getPrimaryPermalinkEntity(permalink: string): string {
|
|||
}
|
||||
|
||||
function getPermalinkConstructor(): PermalinkConstructor {
|
||||
const elementPrefix = SdkConfig.get()['permalinkPrefix'];
|
||||
const elementPrefix = SdkConfig.get("permalink_prefix");
|
||||
if (elementPrefix && elementPrefix !== matrixtoBaseUrl) {
|
||||
return new ElementPermalinkConstructor(elementPrefix);
|
||||
}
|
||||
|
@ -423,7 +423,7 @@ function getPermalinkConstructor(): PermalinkConstructor {
|
|||
|
||||
export function parsePermalink(fullUrl: string): PermalinkParts {
|
||||
try {
|
||||
const elementPrefix = SdkConfig.get()['permalinkPrefix'];
|
||||
const elementPrefix = SdkConfig.get("permalink_prefix");
|
||||
if (decodeURIComponent(fullUrl).startsWith(matrixtoBaseUrl)) {
|
||||
return new MatrixToPermalinkConstructor().parsePermalink(decodeURIComponent(fullUrl));
|
||||
} else if (fullUrl.startsWith("matrix:")) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import SdkConfig from "../SdkConfig";
|
|||
|
||||
export function isPresenceEnabled() {
|
||||
const hsUrl = MatrixClientPeg.get().baseUrl;
|
||||
const urls = SdkConfig.get()['enable_presence_by_hs_url'];
|
||||
const urls = SdkConfig.get("enable_presence_by_hs_url");
|
||||
if (!urls) return true;
|
||||
if (urls[hsUrl] || urls[hsUrl] === undefined) return true;
|
||||
return false;
|
||||
|
|
|
@ -70,7 +70,7 @@ export class Jitsi {
|
|||
|
||||
private update = async (discoveryResponse: IClientWellKnown): Promise<any> => {
|
||||
// Start with a default of the config's domain
|
||||
let domain = SdkConfig.get().jitsi?.preferredDomain || "meet.element.io";
|
||||
let domain = SdkConfig.getObject("jitsi")?.get("preferred_domain") || "meet.element.io";
|
||||
|
||||
logger.log("Attempting to get Jitsi conference information from homeserver");
|
||||
const wkPreferredDomain = discoveryResponse?.[JITSI_WK_PROPERTY]?.['preferredDomain'];
|
||||
|
|
|
@ -72,20 +72,23 @@ describe("PosthogAnalytics", () => {
|
|||
|
||||
afterEach(() => {
|
||||
window.crypto = null;
|
||||
SdkConfig.unset(); // we touch the config, so clean up
|
||||
});
|
||||
|
||||
describe("Initialisation", () => {
|
||||
it("Should not be enabled without config being set", () => {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({});
|
||||
// force empty/invalid state for posthog options
|
||||
SdkConfig.put({ brand: "Testing" });
|
||||
const analytics = new PosthogAnalytics(fakePosthog);
|
||||
expect(analytics.isEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it("Should be enabled if config is set", () => {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
projectApiKey: "foo",
|
||||
apiHost: "bar",
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
const analytics = new PosthogAnalytics(fakePosthog);
|
||||
|
@ -98,10 +101,11 @@ describe("PosthogAnalytics", () => {
|
|||
let analytics: PosthogAnalytics;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
SdkConfig.put({
|
||||
brand: "Testing",
|
||||
posthog: {
|
||||
projectApiKey: "foo",
|
||||
apiHost: "bar",
|
||||
project_api_key: "foo",
|
||||
api_host: "bar",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -42,9 +42,9 @@ describe('Login', function() {
|
|||
} as unknown as MatrixClient);
|
||||
|
||||
beforeEach(function() {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
SdkConfig.put({
|
||||
brand: "test-brand",
|
||||
disable_custom_urls: true,
|
||||
brand: 'test-brand',
|
||||
});
|
||||
mockClient.login.mockClear().mockResolvedValue({});
|
||||
mockClient.loginFlows.mockClear().mockResolvedValue({ flows: [{ type: "m.login.password" }] });
|
||||
|
@ -57,6 +57,7 @@ describe('Login', function() {
|
|||
afterEach(function() {
|
||||
ReactDOM.unmountComponentAtNode(parentDiv);
|
||||
parentDiv.remove();
|
||||
SdkConfig.unset(); // we touch the config, so clean up
|
||||
});
|
||||
|
||||
function render() {
|
||||
|
@ -69,9 +70,9 @@ describe('Login', function() {
|
|||
}
|
||||
|
||||
it('should show form with change server link', async () => {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
SdkConfig.put({
|
||||
brand: "test-brand",
|
||||
disable_custom_urls: false,
|
||||
brand: 'test',
|
||||
});
|
||||
const root = render();
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Registration', function() {
|
|||
let parentDiv;
|
||||
|
||||
beforeEach(function() {
|
||||
jest.spyOn(SdkConfig, "get").mockReturnValue({
|
||||
SdkConfig.put({
|
||||
...DEFAULTS,
|
||||
disable_custom_urls: true,
|
||||
});
|
||||
|
@ -46,6 +46,7 @@ describe('Registration', function() {
|
|||
afterEach(function() {
|
||||
ReactDOM.unmountComponentAtNode(parentDiv);
|
||||
parentDiv.remove();
|
||||
SdkConfig.unset(); // we touch the config, so clean up
|
||||
});
|
||||
|
||||
function render() {
|
||||
|
|
63
test/utils/SnakedObject-test.ts
Normal file
63
test/utils/SnakedObject-test.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { SnakedObject, snakeToCamel } from "../../src/utils/SnakedObject";
|
||||
|
||||
describe('snakeToCamel', () => {
|
||||
it('should convert snake_case to camelCase in simple scenarios', () => {
|
||||
expect(snakeToCamel("snake_case")).toBe("snakeCase");
|
||||
expect(snakeToCamel("snake_case_but_longer")).toBe("snakeCaseButLonger");
|
||||
expect(snakeToCamel("numbered_123")).toBe("numbered123"); // not a thing we would see normally
|
||||
});
|
||||
|
||||
// Not really something we expect to see, but it's defined behaviour of the function
|
||||
it('should not camelCase a trailing or leading underscore', () => {
|
||||
expect(snakeToCamel("_snake")).toBe("_snake");
|
||||
expect(snakeToCamel("snake_")).toBe("snake_");
|
||||
expect(snakeToCamel("_snake_case")).toBe("_snakeCase");
|
||||
expect(snakeToCamel("snake_case_")).toBe("snakeCase_");
|
||||
});
|
||||
|
||||
// Another thing we don't really expect to see, but is "defined behaviour"
|
||||
it('should be predictable with double underscores', () => {
|
||||
expect(snakeToCamel("__snake__")).toBe("_Snake_");
|
||||
expect(snakeToCamel("snake__case")).toBe("snake_case");
|
||||
});
|
||||
});
|
||||
|
||||
describe('SnakedObject', () => {
|
||||
/* eslint-disable camelcase*/
|
||||
const input = {
|
||||
snake_case: "woot",
|
||||
snakeCase: "oh no", // ensure different value from snake_case for tests
|
||||
camelCase: "fallback",
|
||||
};
|
||||
const snake = new SnakedObject(input);
|
||||
/* eslint-enable camelcase*/
|
||||
|
||||
it('should prefer snake_case keys', () => {
|
||||
expect(snake.get("snake_case")).toBe(input.snake_case);
|
||||
expect(snake.get("snake_case", "camelCase")).toBe(input.snake_case);
|
||||
});
|
||||
|
||||
it('should fall back to camelCase keys when needed', () => {
|
||||
// @ts-ignore - we're deliberately supplying a key that doesn't exist
|
||||
expect(snake.get("camel_case")).toBe(input.camelCase);
|
||||
|
||||
// @ts-ignore - we're deliberately supplying a key that doesn't exist
|
||||
expect(snake.get("e_no_exist", "camelCase")).toBe(input.camelCase);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue