Harden Settings using mapped types (#28775)
* Harden Settings using mapped types Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix issues found during hardening Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove oidc native flow stale key Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
committed by
GitHub
parent
4e1bd69e4d
commit
1e42f28a69
@@ -38,6 +38,13 @@ import { WatchManager } from "./WatchManager";
|
||||
import { CustomTheme } from "../theme";
|
||||
import AnalyticsController from "./controllers/AnalyticsController";
|
||||
import FallbackIceServerController from "./controllers/FallbackIceServerController";
|
||||
import { UseCase } from "./enums/UseCase.tsx";
|
||||
import { IRightPanelForRoomStored } from "../stores/right-panel/RightPanelStoreIPanelState.ts";
|
||||
import { ILayoutSettings } from "../stores/widgets/WidgetLayoutStore.ts";
|
||||
import { ReleaseAnnouncementData } from "../stores/ReleaseAnnouncementStore.ts";
|
||||
import { Json, JsonValue } from "../@types/json.ts";
|
||||
import { RecentEmojiData } from "../emojipicker/recent.ts";
|
||||
import { Assignable } from "../@types/common.ts";
|
||||
|
||||
export const defaultWatchManager = new WatchManager();
|
||||
|
||||
@@ -106,15 +113,7 @@ export const labGroupNames: Record<LabGroup, TranslationKey> = {
|
||||
[LabGroup.Ui]: _td("labs|group_ui"),
|
||||
};
|
||||
|
||||
export type SettingValueType =
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| number[]
|
||||
| string[]
|
||||
| Record<string, unknown>
|
||||
| Record<string, unknown>[]
|
||||
| null;
|
||||
export type SettingValueType = Json | JsonValue | Record<string, unknown> | Record<string, unknown>[];
|
||||
|
||||
export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
|
||||
isFeature?: false | undefined;
|
||||
@@ -164,7 +163,7 @@ export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
|
||||
image?: string; // require(...)
|
||||
feedbackSubheading?: TranslationKey;
|
||||
feedbackLabel?: string;
|
||||
extraSettings?: string[];
|
||||
extraSettings?: BooleanSettingKey[];
|
||||
requiresRefresh?: boolean;
|
||||
};
|
||||
|
||||
@@ -181,7 +180,179 @@ export interface IFeature extends Omit<IBaseSetting<boolean>, "isFeature"> {
|
||||
// Type using I-identifier for backwards compatibility from before it became a discriminated union
|
||||
export type ISetting = IBaseSetting | IFeature;
|
||||
|
||||
export const SETTINGS: { [setting: string]: ISetting } = {
|
||||
export interface Settings {
|
||||
[settingName: `UIFeature.${string}`]: IBaseSetting<boolean>;
|
||||
|
||||
// We can't use the following type because of `feature_sliding_sync_proxy_url` & `feature_hidebold` being in the namespace incorrectly
|
||||
// [settingName: `feature_${string}`]: IFeature;
|
||||
"feature_video_rooms": IFeature;
|
||||
[Features.NotificationSettings2]: IFeature;
|
||||
[Features.ReleaseAnnouncement]: IFeature;
|
||||
"feature_msc3531_hide_messages_pending_moderation": IFeature;
|
||||
"feature_report_to_moderators": IFeature;
|
||||
"feature_latex_maths": IFeature;
|
||||
"feature_wysiwyg_composer": IFeature;
|
||||
"feature_mjolnir": IFeature;
|
||||
"feature_custom_themes": IFeature;
|
||||
"feature_exclude_insecure_devices": IFeature;
|
||||
"feature_html_topic": IFeature;
|
||||
"feature_bridge_state": IFeature;
|
||||
"feature_jump_to_date": IFeature;
|
||||
"feature_sliding_sync": IFeature;
|
||||
"feature_element_call_video_rooms": IFeature;
|
||||
"feature_group_calls": IFeature;
|
||||
"feature_disable_call_per_sender_encryption": IFeature;
|
||||
"feature_allow_screen_share_only_mode": IFeature;
|
||||
"feature_location_share_live": IFeature;
|
||||
"feature_dynamic_room_predecessors": IFeature;
|
||||
"feature_render_reaction_images": IFeature;
|
||||
"feature_ask_to_join": IFeature;
|
||||
"feature_notifications": IFeature;
|
||||
// These are in the feature namespace but aren't actually features
|
||||
"feature_sliding_sync_proxy_url": IBaseSetting<string>;
|
||||
"feature_hidebold": IBaseSetting<boolean>;
|
||||
|
||||
"useOnlyCurrentProfiles": IBaseSetting<boolean>;
|
||||
"mjolnirRooms": IBaseSetting<string[]>;
|
||||
"mjolnirPersonalRoom": IBaseSetting<string | null>;
|
||||
"RoomList.backgroundImage": IBaseSetting<string | null>;
|
||||
"sendReadReceipts": IBaseSetting<boolean>;
|
||||
"baseFontSize": IBaseSetting<"" | number>;
|
||||
"baseFontSizeV2": IBaseSetting<"" | number>;
|
||||
"fontSizeDelta": IBaseSetting<number>;
|
||||
"useCustomFontSize": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.suggestEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.showStickersButton": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.showPollsButton": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.insertTrailingColon": IBaseSetting<boolean>;
|
||||
"Notifications.alwaysShowBadgeCounts": IBaseSetting<boolean>;
|
||||
"Notifications.showbold": IBaseSetting<boolean>;
|
||||
"Notifications.tac_only_notifications": IBaseSetting<boolean>;
|
||||
"useCompactLayout": IBaseSetting<boolean>;
|
||||
"showRedactions": IBaseSetting<boolean>;
|
||||
"showJoinLeaves": IBaseSetting<boolean>;
|
||||
"showAvatarChanges": IBaseSetting<boolean>;
|
||||
"showDisplaynameChanges": IBaseSetting<boolean>;
|
||||
"showReadReceipts": IBaseSetting<boolean>;
|
||||
"showTwelveHourTimestamps": IBaseSetting<boolean>;
|
||||
"alwaysShowTimestamps": IBaseSetting<boolean>;
|
||||
"userTimezone": IBaseSetting<string>;
|
||||
"userTimezonePublish": IBaseSetting<boolean>;
|
||||
"autoplayGifs": IBaseSetting<boolean>;
|
||||
"autoplayVideo": IBaseSetting<boolean>;
|
||||
"enableSyntaxHighlightLanguageDetection": IBaseSetting<boolean>;
|
||||
"expandCodeByDefault": IBaseSetting<boolean>;
|
||||
"showCodeLineNumbers": IBaseSetting<boolean>;
|
||||
"scrollToBottomOnMessageSent": IBaseSetting<boolean>;
|
||||
"Pill.shouldShowPillAvatar": IBaseSetting<boolean>;
|
||||
"TextualBody.enableBigEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.isRichTextEnabled": IBaseSetting<boolean>;
|
||||
"MessageComposer.showFormatting": IBaseSetting<boolean>;
|
||||
"sendTypingNotifications": IBaseSetting<boolean>;
|
||||
"showTypingNotifications": IBaseSetting<boolean>;
|
||||
"ctrlFForSearch": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.ctrlEnterToSend": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.surroundWith": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.autoReplaceEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.useMarkdown": IBaseSetting<boolean>;
|
||||
"VideoView.flipVideoHorizontally": IBaseSetting<boolean>;
|
||||
"theme": IBaseSetting<string>;
|
||||
"custom_themes": IBaseSetting<CustomTheme[]>;
|
||||
"use_system_theme": IBaseSetting<boolean>;
|
||||
"useBundledEmojiFont": IBaseSetting<boolean>;
|
||||
"useSystemFont": IBaseSetting<boolean>;
|
||||
"systemFont": IBaseSetting<string>;
|
||||
"webRtcAllowPeerToPeer": IBaseSetting<boolean>;
|
||||
"webrtc_audiooutput": IBaseSetting<string>;
|
||||
"webrtc_audioinput": IBaseSetting<string>;
|
||||
"webrtc_videoinput": IBaseSetting<string>;
|
||||
"webrtc_audio_autoGainControl": IBaseSetting<boolean>;
|
||||
"webrtc_audio_echoCancellation": IBaseSetting<boolean>;
|
||||
"webrtc_audio_noiseSuppression": IBaseSetting<boolean>;
|
||||
"language": IBaseSetting<string>;
|
||||
"breadcrumb_rooms": IBaseSetting<string[]>;
|
||||
"recent_emoji": IBaseSetting<RecentEmojiData>;
|
||||
"SpotlightSearch.recentSearches": IBaseSetting<string[]>;
|
||||
"SpotlightSearch.showNsfwPublicRooms": IBaseSetting<boolean>;
|
||||
"room_directory_servers": IBaseSetting<string[]>;
|
||||
"integrationProvisioning": IBaseSetting<boolean>;
|
||||
"allowedWidgets": IBaseSetting<{ [eventId: string]: boolean }>;
|
||||
"analyticsOptIn": IBaseSetting<boolean>;
|
||||
"pseudonymousAnalyticsOptIn": IBaseSetting<boolean | null>;
|
||||
"deviceClientInformationOptIn": IBaseSetting<boolean>;
|
||||
"FTUE.useCaseSelection": IBaseSetting<UseCase | null>;
|
||||
"Registration.mobileRegistrationHelper": IBaseSetting<boolean>;
|
||||
"autocompleteDelay": IBaseSetting<number>;
|
||||
"readMarkerInViewThresholdMs": IBaseSetting<number>;
|
||||
"readMarkerOutOfViewThresholdMs": IBaseSetting<number>;
|
||||
"blacklistUnverifiedDevices": IBaseSetting<boolean>;
|
||||
"urlPreviewsEnabled": IBaseSetting<boolean>;
|
||||
"urlPreviewsEnabled_e2ee": IBaseSetting<boolean>;
|
||||
"notificationsEnabled": IBaseSetting<boolean>;
|
||||
"deviceNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"notificationSound": IBaseSetting<
|
||||
| {
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
url: string;
|
||||
}
|
||||
| false
|
||||
>;
|
||||
"notificationBodyEnabled": IBaseSetting<boolean>;
|
||||
"audioNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"enableWidgetScreenshots": IBaseSetting<boolean>;
|
||||
"promptBeforeInviteUnknownUsers": IBaseSetting<boolean>;
|
||||
"widgetOpenIDPermissions": IBaseSetting<{
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
}>;
|
||||
"breadcrumbs": IBaseSetting<boolean>;
|
||||
"FTUE.userOnboardingButton": IBaseSetting<boolean>;
|
||||
"showHiddenEventsInTimeline": IBaseSetting<boolean>;
|
||||
"lowBandwidth": IBaseSetting<boolean>;
|
||||
"fallbackICEServerAllowed": IBaseSetting<boolean | null>;
|
||||
"showImages": IBaseSetting<boolean>;
|
||||
"RightPanel.phasesGlobal": IBaseSetting<IRightPanelForRoomStored | null>;
|
||||
"RightPanel.phases": IBaseSetting<IRightPanelForRoomStored | null>;
|
||||
"enableEventIndexing": IBaseSetting<boolean>;
|
||||
"crawlerSleepTime": IBaseSetting<number>;
|
||||
"showCallButtonsInComposer": IBaseSetting<boolean>;
|
||||
"ircDisplayNameWidth": IBaseSetting<number>;
|
||||
"layout": IBaseSetting<Layout>;
|
||||
"Images.size": IBaseSetting<ImageSize>;
|
||||
"showChatEffects": IBaseSetting<boolean>;
|
||||
"Performance.addSendMessageTimingMetadata": IBaseSetting<boolean>;
|
||||
"Widgets.pinned": IBaseSetting<{ [widgetId: string]: boolean }>;
|
||||
"Widgets.layout": IBaseSetting<ILayoutSettings | null>;
|
||||
"Spaces.allRoomsInHome": IBaseSetting<boolean>;
|
||||
"Spaces.enabledMetaSpaces": IBaseSetting<Partial<Record<MetaSpace, boolean>>>;
|
||||
"Spaces.showPeopleInSpace": IBaseSetting<boolean>;
|
||||
"developerMode": IBaseSetting<boolean>;
|
||||
"automaticErrorReporting": IBaseSetting<boolean>;
|
||||
"automaticDecryptionErrorReporting": IBaseSetting<boolean>;
|
||||
"automaticKeyBackNotEnabledReporting": IBaseSetting<boolean>;
|
||||
"debug_scroll_panel": IBaseSetting<boolean>;
|
||||
"debug_timeline_panel": IBaseSetting<boolean>;
|
||||
"debug_registration": IBaseSetting<boolean>;
|
||||
"debug_animation": IBaseSetting<boolean>;
|
||||
"debug_legacy_call_handler": IBaseSetting<boolean>;
|
||||
"audioInputMuted": IBaseSetting<boolean>;
|
||||
"videoInputMuted": IBaseSetting<boolean>;
|
||||
"activeCallRoomIds": IBaseSetting<string[]>;
|
||||
"releaseAnnouncementData": IBaseSetting<ReleaseAnnouncementData>;
|
||||
"Electron.autoLaunch": IBaseSetting<boolean>;
|
||||
"Electron.warnBeforeExit": IBaseSetting<boolean>;
|
||||
"Electron.alwaysShowMenuBar": IBaseSetting<boolean>;
|
||||
"Electron.showTrayIcon": IBaseSetting<boolean>;
|
||||
"Electron.enableHardwareAcceleration": IBaseSetting<boolean>;
|
||||
}
|
||||
|
||||
export type SettingKey = keyof Settings;
|
||||
export type FeatureSettingKey = Assignable<Settings, IFeature>;
|
||||
export type BooleanSettingKey = Assignable<Settings, IBaseSetting<boolean>> | FeatureSettingKey;
|
||||
|
||||
export const SETTINGS: Settings = {
|
||||
"feature_video_rooms": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.VoiceAndVideo,
|
||||
@@ -710,7 +881,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
||||
},
|
||||
"custom_themes": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: [] as CustomTheme[],
|
||||
default: [],
|
||||
},
|
||||
"use_system_theme": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
|
||||
@@ -20,7 +20,7 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
|
||||
import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
|
||||
import { _t } from "../languageHandler";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { IFeature, ISetting, LabGroup, SETTINGS, defaultWatchManager } from "./Settings";
|
||||
import { IFeature, ISetting, LabGroup, SETTINGS, defaultWatchManager, SettingKey, Settings } from "./Settings";
|
||||
import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
|
||||
import { CallbackFn as WatchCallbackFn } from "./WatchManager";
|
||||
import { SettingLevel } from "./SettingLevel";
|
||||
@@ -34,11 +34,11 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
// Convert the settings to easier to manage objects for the handlers
|
||||
const defaultSettings: Record<string, any> = {};
|
||||
const invertedDefaultSettings: Record<string, boolean> = {};
|
||||
const featureNames: string[] = [];
|
||||
const featureNames: SettingKey[] = [];
|
||||
for (const key in SETTINGS) {
|
||||
const setting = SETTINGS[key];
|
||||
const setting = SETTINGS[key as SettingKey];
|
||||
defaultSettings[key] = setting.default;
|
||||
if (setting.isFeature) featureNames.push(key);
|
||||
if (setting.isFeature) featureNames.push(key as SettingKey);
|
||||
if (setting.invertedSettingName) {
|
||||
// Invert now so that the rest of the system will invert it back to what was intended.
|
||||
invertedDefaultSettings[setting.invertedSettingName] = !setting.default;
|
||||
@@ -80,7 +80,7 @@ function getLevelOrder(setting: ISetting): SettingLevel[] {
|
||||
}
|
||||
|
||||
export type CallbackFn = (
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
atLevel: SettingLevel,
|
||||
newValAtLevel: any,
|
||||
@@ -138,8 +138,8 @@ export default class SettingsStore {
|
||||
* Gets all the feature-style setting names.
|
||||
* @returns {string[]} The names of the feature settings.
|
||||
*/
|
||||
public static getFeatureSettingNames(): string[] {
|
||||
return Object.keys(SETTINGS).filter((n) => SettingsStore.isFeature(n));
|
||||
public static getFeatureSettingNames(): SettingKey[] {
|
||||
return (Object.keys(SETTINGS) as SettingKey[]).filter((n) => SettingsStore.isFeature(n));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,33 +158,30 @@ export default class SettingsStore {
|
||||
* if the change in value is worthwhile enough to react upon.
|
||||
* @returns {string} A reference to the watcher that was employed.
|
||||
*/
|
||||
public static watchSetting(settingName: string, roomId: string | null, callbackFn: CallbackFn): string {
|
||||
public static watchSetting(settingName: SettingKey, roomId: string | null, callbackFn: CallbackFn): string {
|
||||
const setting = SETTINGS[settingName];
|
||||
const originalSettingName = settingName;
|
||||
if (!setting) throw new Error(`${settingName} is not a setting`);
|
||||
|
||||
if (setting.invertedSettingName) {
|
||||
settingName = setting.invertedSettingName;
|
||||
}
|
||||
const finalSettingName: string = setting.invertedSettingName ?? settingName;
|
||||
|
||||
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${settingName}_${roomId}`;
|
||||
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${finalSettingName}_${roomId}`;
|
||||
|
||||
const localizedCallback = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any): void => {
|
||||
if (!SettingsStore.doesSettingSupportLevel(originalSettingName, atLevel)) {
|
||||
if (!SettingsStore.doesSettingSupportLevel(settingName, atLevel)) {
|
||||
logger.warn(
|
||||
`Setting handler notified for an update of an invalid setting level: ` +
|
||||
`${originalSettingName}@${atLevel} - this likely means a weird setting value ` +
|
||||
`${settingName}@${atLevel} - this likely means a weird setting value ` +
|
||||
`made it into the level's storage. The notification will be ignored.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const newValue = SettingsStore.getValue(originalSettingName);
|
||||
const newValueAtLevel = SettingsStore.getValueAt(atLevel, originalSettingName) ?? newValAtLevel;
|
||||
callbackFn(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue);
|
||||
const newValue = SettingsStore.getValue(settingName);
|
||||
const newValueAtLevel = SettingsStore.getValueAt(atLevel, settingName) ?? newValAtLevel;
|
||||
callbackFn(settingName, changedInRoomId, atLevel, newValueAtLevel, newValue);
|
||||
};
|
||||
|
||||
SettingsStore.watchers.set(watcherId, localizedCallback);
|
||||
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
|
||||
defaultWatchManager.watchSetting(finalSettingName, roomId, localizedCallback);
|
||||
|
||||
return watcherId;
|
||||
}
|
||||
@@ -214,7 +211,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting name to monitor.
|
||||
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
|
||||
*/
|
||||
public static monitorSetting(settingName: string, roomId: string | null): void {
|
||||
public static monitorSetting(settingName: SettingKey, roomId: string | null): void {
|
||||
roomId = roomId || null; // the thing wants null specifically to work, so appease it.
|
||||
|
||||
if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map());
|
||||
@@ -262,7 +259,7 @@ export default class SettingsStore {
|
||||
* The level to get the display name for; Defaults to 'default'.
|
||||
* @return {String} The display name for the setting, or null if not found.
|
||||
*/
|
||||
public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string | null {
|
||||
public static getDisplayName(settingName: SettingKey, atLevel = SettingLevel.DEFAULT): string | null {
|
||||
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
|
||||
|
||||
const displayName = SETTINGS[settingName].displayName;
|
||||
@@ -285,7 +282,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting to look up.
|
||||
* @return {String} The description for the setting, or null if not found.
|
||||
*/
|
||||
public static getDescription(settingName: string): string | ReactNode {
|
||||
public static getDescription(settingName: SettingKey): string | ReactNode {
|
||||
const description = SETTINGS[settingName]?.description;
|
||||
if (!description) return null;
|
||||
if (typeof description !== "string") return description();
|
||||
@@ -297,7 +294,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting to look up.
|
||||
* @return {boolean} True if the setting is a feature.
|
||||
*/
|
||||
public static isFeature(settingName: string): boolean {
|
||||
public static isFeature(settingName: SettingKey): boolean {
|
||||
if (!SETTINGS[settingName]) return false;
|
||||
return !!SETTINGS[settingName].isFeature;
|
||||
}
|
||||
@@ -307,12 +304,12 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting to look up.
|
||||
* @return {boolean} True if the setting should have a warning sign.
|
||||
*/
|
||||
public static shouldHaveWarning(settingName: string): boolean {
|
||||
public static shouldHaveWarning(settingName: SettingKey): boolean {
|
||||
if (!SETTINGS[settingName]) return false;
|
||||
return SETTINGS[settingName].shouldWarn ?? false;
|
||||
}
|
||||
|
||||
public static getBetaInfo(settingName: string): ISetting["betaInfo"] {
|
||||
public static getBetaInfo(settingName: SettingKey): ISetting["betaInfo"] {
|
||||
// consider a beta disabled if the config is explicitly set to false, in which case treat as normal Labs flag
|
||||
if (
|
||||
SettingsStore.isFeature(settingName) &&
|
||||
@@ -327,7 +324,7 @@ export default class SettingsStore {
|
||||
}
|
||||
}
|
||||
|
||||
public static getLabGroup(settingName: string): LabGroup | undefined {
|
||||
public static getLabGroup(settingName: SettingKey): LabGroup | undefined {
|
||||
if (SettingsStore.isFeature(settingName)) {
|
||||
return (<IFeature>SETTINGS[settingName]).labsGroup;
|
||||
}
|
||||
@@ -340,7 +337,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting to look up.
|
||||
* @return {string} The reason the setting is disabled.
|
||||
*/
|
||||
public static disabledMessage(settingName: string): string | undefined {
|
||||
public static disabledMessage(settingName: SettingKey): string | undefined {
|
||||
const disabled = SETTINGS[settingName].controller?.settingDisabled;
|
||||
return typeof disabled === "string" ? disabled : undefined;
|
||||
}
|
||||
@@ -353,7 +350,21 @@ export default class SettingsStore {
|
||||
* @param {boolean} excludeDefault True to disable using the default value.
|
||||
* @return {*} The value, or null if not found
|
||||
*/
|
||||
public static getValue<T = any>(settingName: string, roomId: string | null = null, excludeDefault = false): T {
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
excludeDefault: true,
|
||||
): Settings[S]["default"] | undefined;
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId?: string | null,
|
||||
excludeDefault?: false,
|
||||
): Settings[S]["default"];
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
excludeDefault = false,
|
||||
): Settings[S]["default"] | undefined {
|
||||
// Verify that the setting is actually a setting
|
||||
if (!SETTINGS[settingName]) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
@@ -362,7 +373,7 @@ export default class SettingsStore {
|
||||
const setting = SETTINGS[settingName];
|
||||
const levelOrder = getLevelOrder(setting);
|
||||
|
||||
return SettingsStore.getValueAt<T>(levelOrder[0], settingName, roomId, false, excludeDefault);
|
||||
return SettingsStore.getValueAt(levelOrder[0], settingName, roomId, false, excludeDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,13 +387,13 @@ export default class SettingsStore {
|
||||
* @param {boolean} excludeDefault True to disable using the default value.
|
||||
* @return {*} The value, or null if not found.
|
||||
*/
|
||||
public static getValueAt<T = any>(
|
||||
public static getValueAt<S extends SettingKey>(
|
||||
level: SettingLevel,
|
||||
settingName: string,
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
explicit = false,
|
||||
excludeDefault = false,
|
||||
): T {
|
||||
): Settings[S]["default"] {
|
||||
// Verify that the setting is actually a setting
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
@@ -399,9 +410,10 @@ export default class SettingsStore {
|
||||
|
||||
// Check if we need to invert the setting at all. Do this after we get the setting
|
||||
// handlers though, otherwise we'll fail to read the value.
|
||||
let finalSettingName: string = settingName;
|
||||
if (setting.invertedSettingName) {
|
||||
//console.warn(`Inverting ${settingName} to be ${setting.invertedSettingName} - legacy setting`);
|
||||
settingName = setting.invertedSettingName;
|
||||
finalSettingName = setting.invertedSettingName;
|
||||
}
|
||||
|
||||
if (explicit) {
|
||||
@@ -409,7 +421,7 @@ export default class SettingsStore {
|
||||
if (!handler) {
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, null, null);
|
||||
}
|
||||
const value = handler.getValue(settingName, roomId);
|
||||
const value = handler.getValue(finalSettingName, roomId);
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, value, level);
|
||||
}
|
||||
|
||||
@@ -418,7 +430,7 @@ export default class SettingsStore {
|
||||
if (!handler) continue;
|
||||
if (excludeDefault && levelOrder[i] === "default") continue;
|
||||
|
||||
const value = handler.getValue(settingName, roomId);
|
||||
const value = handler.getValue(finalSettingName, roomId);
|
||||
if (value === null || value === undefined) continue;
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, value, levelOrder[i]);
|
||||
}
|
||||
@@ -432,7 +444,7 @@ export default class SettingsStore {
|
||||
* @param {String} roomId The room ID to read the setting value in, may be null.
|
||||
* @return {*} The default value
|
||||
*/
|
||||
public static getDefaultValue(settingName: string): any {
|
||||
public static getDefaultValue(settingName: SettingKey): any {
|
||||
// Verify that the setting is actually a setting
|
||||
if (!SETTINGS[settingName]) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
@@ -474,7 +486,7 @@ export default class SettingsStore {
|
||||
|
||||
/* eslint-enable valid-jsdoc */
|
||||
public static async setValue(
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
value: any,
|
||||
@@ -490,24 +502,25 @@ export default class SettingsStore {
|
||||
throw new Error("Setting " + settingName + " does not have a handler for " + level);
|
||||
}
|
||||
|
||||
let finalSettingName: string = settingName;
|
||||
if (setting.invertedSettingName) {
|
||||
// Note: We can't do this when the `level` is "default", however we also
|
||||
// know that the user can't possible change the default value through this
|
||||
// function so we don't bother checking it.
|
||||
//console.warn(`Inverting ${settingName} to be ${setting.invertedSettingName} - legacy setting`);
|
||||
settingName = setting.invertedSettingName;
|
||||
finalSettingName = setting.invertedSettingName;
|
||||
value = !value;
|
||||
}
|
||||
|
||||
if (!handler.canSetValue(settingName, roomId)) {
|
||||
throw new Error("User cannot set " + settingName + " at " + level + " in " + roomId);
|
||||
if (!handler.canSetValue(finalSettingName, roomId)) {
|
||||
throw new Error("User cannot set " + finalSettingName + " at " + level + " in " + roomId);
|
||||
}
|
||||
|
||||
if (setting.controller && !(await setting.controller.beforeChange(level, roomId, value))) {
|
||||
return; // controller says no
|
||||
}
|
||||
|
||||
await handler.setValue(settingName, roomId, value);
|
||||
await handler.setValue(finalSettingName, roomId, value);
|
||||
|
||||
setting.controller?.onChange(level, roomId, value);
|
||||
}
|
||||
@@ -530,7 +543,7 @@ export default class SettingsStore {
|
||||
* @param {SettingLevel} level The level to check at.
|
||||
* @return {boolean} True if the user may set the setting, false otherwise.
|
||||
*/
|
||||
public static canSetValue(settingName: string, roomId: string | null, level: SettingLevel): boolean {
|
||||
public static canSetValue(settingName: SettingKey, roomId: string | null, level: SettingLevel): boolean {
|
||||
const setting = SETTINGS[settingName];
|
||||
// Verify that the setting is actually a setting
|
||||
if (!setting) {
|
||||
@@ -563,7 +576,7 @@ export default class SettingsStore {
|
||||
* @returns
|
||||
*/
|
||||
public static settingIsOveriddenAtConfigLevel(
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
): boolean {
|
||||
@@ -597,7 +610,7 @@ export default class SettingsStore {
|
||||
* the level itself can be supported by the runtime (ie: you will need to call #isLevelSupported()
|
||||
* on your own).
|
||||
*/
|
||||
public static doesSettingSupportLevel(settingName: string, level: SettingLevel): boolean {
|
||||
public static doesSettingSupportLevel(settingName: SettingKey, level: SettingLevel): boolean {
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
@@ -612,7 +625,7 @@ export default class SettingsStore {
|
||||
* @param {string} settingName The setting name.
|
||||
* @return {SettingLevel}
|
||||
*/
|
||||
public static firstSupportedLevel(settingName: string): SettingLevel | null {
|
||||
public static firstSupportedLevel(settingName: SettingKey): SettingLevel | null {
|
||||
// Verify that the setting is actually a setting
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
@@ -699,7 +712,7 @@ export default class SettingsStore {
|
||||
* @param {string} realSettingName The setting name to try and read.
|
||||
* @param {string} roomId Optional room ID to test the setting in.
|
||||
*/
|
||||
public static debugSetting(realSettingName: string, roomId: string): void {
|
||||
public static debugSetting(realSettingName: SettingKey, roomId: string): void {
|
||||
logger.log(`--- DEBUG ${realSettingName}`);
|
||||
|
||||
// Note: we intentionally use JSON.stringify here to avoid the console masking the
|
||||
@@ -711,7 +724,7 @@ export default class SettingsStore {
|
||||
logger.log(`--- default level order: ${JSON.stringify(LEVEL_ORDER)}`);
|
||||
logger.log(`--- registered handlers: ${JSON.stringify(Object.keys(LEVEL_HANDLERS))}`);
|
||||
|
||||
const doChecks = (settingName: string): void => {
|
||||
const doChecks = (settingName: SettingKey): void => {
|
||||
for (const handlerName of Object.keys(LEVEL_HANDLERS)) {
|
||||
const handler = LEVEL_HANDLERS[handlerName as SettingLevel];
|
||||
|
||||
@@ -803,19 +816,19 @@ export default class SettingsStore {
|
||||
if (def.invertedSettingName) {
|
||||
logger.log(`--- TESTING INVERTED SETTING NAME`);
|
||||
logger.log(`--- inverted: ${def.invertedSettingName}`);
|
||||
doChecks(def.invertedSettingName);
|
||||
doChecks(def.invertedSettingName as SettingKey);
|
||||
}
|
||||
|
||||
logger.log(`--- END DEBUG`);
|
||||
}
|
||||
|
||||
private static getHandler(settingName: string, level: SettingLevel): SettingsHandler | null {
|
||||
private static getHandler(settingName: SettingKey, level: SettingLevel): SettingsHandler | null {
|
||||
const handlers = SettingsStore.getHandlers(settingName);
|
||||
if (!handlers[level]) return null;
|
||||
return handlers[level]!;
|
||||
}
|
||||
|
||||
private static getHandlers(settingName: string): HandlerMap {
|
||||
private static getHandlers(settingName: SettingKey): HandlerMap {
|
||||
if (!SETTINGS[settingName]) return {};
|
||||
|
||||
const handlers: Partial<Record<SettingLevel, SettingsHandler>> = {};
|
||||
|
||||
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import SettingController from "./SettingController";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { BooleanSettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Enforces that a boolean setting cannot be enabled if the incompatible setting
|
||||
@@ -17,7 +18,7 @@ import SettingsStore from "../SettingsStore";
|
||||
*/
|
||||
export default class IncompatibleController extends SettingController {
|
||||
public constructor(
|
||||
private settingName: string,
|
||||
private settingName: BooleanSettingKey,
|
||||
private forcedValue: any = false,
|
||||
private incompatibleValue: any | ((v: any) => boolean) = true,
|
||||
) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SettingLevel } from "../SettingLevel";
|
||||
import MatrixClientBackedController from "./MatrixClientBackedController";
|
||||
import { WatchManager } from "../WatchManager";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { SettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Disables a given setting if the server unstable feature it requires is not supported
|
||||
@@ -28,7 +29,7 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
|
||||
* the features in the group are supported (all features in a group are required).
|
||||
*/
|
||||
public constructor(
|
||||
private readonly settingName: string,
|
||||
private readonly settingName: SettingKey,
|
||||
private readonly watchers: WatchManager,
|
||||
private readonly unstableFeatureGroups: string[][],
|
||||
private readonly stableVersion?: string,
|
||||
|
||||
@@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
import SettingController from "./SettingController";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { SettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Enforces that a boolean setting cannot be enabled if the corresponding
|
||||
@@ -19,7 +20,7 @@ import SettingsStore from "../SettingsStore";
|
||||
*/
|
||||
export default class UIFeatureController extends SettingController {
|
||||
public constructor(
|
||||
private uiFeatureName: string,
|
||||
private uiFeatureName: SettingKey,
|
||||
private forcedValue = false,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -57,7 +57,7 @@ export class FontWatcher implements IWatcher {
|
||||
* @private
|
||||
*/
|
||||
private async migrateBaseFontV1toFontSizeDelta(): Promise<void> {
|
||||
const legacyBaseFontSize = SettingsStore.getValue<number>("baseFontSize");
|
||||
const legacyBaseFontSize = SettingsStore.getValue("baseFontSize");
|
||||
// No baseFontV1 found, nothing to migrate
|
||||
if (!legacyBaseFontSize) return;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class FontWatcher implements IWatcher {
|
||||
* @private
|
||||
*/
|
||||
private async migrateBaseFontV2toFontSizeDelta(): Promise<void> {
|
||||
const legacyBaseFontV2Size = SettingsStore.getValue<number>("baseFontSizeV2");
|
||||
const legacyBaseFontV2Size = SettingsStore.getValue("baseFontSizeV2");
|
||||
// No baseFontV2 found, nothing to migrate
|
||||
if (!legacyBaseFontV2Size) return;
|
||||
|
||||
@@ -140,7 +140,7 @@ export class FontWatcher implements IWatcher {
|
||||
* @returns {number} the default font size of the browser
|
||||
*/
|
||||
public static getBrowserDefaultFontSize(): number {
|
||||
return this.getRootFontSize() - SettingsStore.getValue<number>("fontSizeDelta");
|
||||
return this.getRootFontSize() - SettingsStore.getValue("fontSizeDelta");
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
@@ -148,7 +148,7 @@ export class FontWatcher implements IWatcher {
|
||||
}
|
||||
|
||||
private updateFont(): void {
|
||||
this.setRootFontSize(SettingsStore.getValue<number>("fontSizeDelta"));
|
||||
this.setRootFontSize(SettingsStore.getValue("fontSizeDelta"));
|
||||
this.setSystemFont({
|
||||
useBundledEmojiFont: SettingsStore.getValue("useBundledEmojiFont"),
|
||||
useSystemFont: SettingsStore.getValue("useSystemFont"),
|
||||
|
||||
Reference in New Issue
Block a user