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

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

View file

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

View file

@ -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) {

View file

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

View file

@ -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",
"",

View file

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

View file

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

View file

@ -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"
/>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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) {

View file

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

View file

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

View file

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

View file

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

View file

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