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:
Travis Ralston 2022-03-18 10:12:36 -06:00 committed by GitHub
parent 09c57b228e
commit d8a939df5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 605 additions and 259 deletions

View file

@ -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;