diff --git a/src/App.vue b/src/App.vue index c97b8ed..9f46442 100644 --- a/src/App.vue +++ b/src/App.vue @@ -86,7 +86,7 @@ export default { } else { this.loading = false; } - this.$config.promise.then(this.onConfigLoaded); + this.$config.load().then(this.onConfigLoaded); }, methods: { windowNotificationPermission, @@ -138,7 +138,7 @@ export default { }, computed: { showLoadingScreen() { - return this.loading || !(this.$config.loaded); + return this.loading || !(this.$config.isLoaded()); }, notificationCount, currentUser() { diff --git a/src/components/roomTypeMixin.js b/src/components/roomTypeMixin.js index 1f94a38..4e2b9fb 100644 --- a/src/components/roomTypeMixin.js +++ b/src/components/roomTypeMixin.js @@ -42,24 +42,4 @@ export default { } }, }, - computed: { - availableRoomTypes() { - let types = [{ title: this.$t("room_info.room_type_default"), description: "", value: ROOM_TYPE_DEFAULT }]; - if (this.$config.experimental_voice_mode) { - types.push({ - title: this.$t("room_info.voice_mode"), - description: this.$t("room_info.voice_mode_info"), - value: ROOM_TYPE_VOICE_MODE, - }); - } - if (this.$config.experimental_file_mode) { - types.push({ - title: this.$t("room_info.file_mode"), - description: this.$t("room_info.file_mode_info"), - value: ROOM_TYPE_FILE_MODE, - }); - } - return types; - }, - }, }; diff --git a/src/main.js b/src/main.js index 0a97090..e1b9423 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ import i18n from './plugins/lang'; import router from './router' import matrix from './services/matrix.service' import navigation from './services/navigation.service' -import config from './services/config.service' +import config from './services/config.service.ts' import analytics from './services/analytics.service' import audioPlayer from './services/audio.service'; import 'roboto-fontface/css/roboto/roboto-fontface.css' diff --git a/src/services/analytics.service.ts b/src/services/analytics.service.ts index 4da5018..e439d3a 100644 --- a/src/services/analytics.service.ts +++ b/src/services/analytics.service.ts @@ -1,8 +1,9 @@ import cleaninsights from "./cleaninsights.service"; +import { Config } from "./config.service"; import matomo from "./matomo.service"; export interface AnalyticsEngine { - event(category: string, action: string); + event(category: string, action: string): void; } type AnalyticsEvent = { @@ -11,7 +12,7 @@ type AnalyticsEvent = { } export default { - install(app) { + install(app: any) { class AnalyticsServiceClass { engines: AnalyticsEngine[]; @@ -23,7 +24,7 @@ export default { this.cachedEvents = []; this.initialized = false; - app.$config.promise.then((config) => { + app.$config.load().then((config: Config) => { var analytics = config.analytics || {}; if (!Array.isArray(analytics)) { analytics = [analytics]; @@ -58,7 +59,7 @@ export default { }); } - event(category, action) { + event(category: string, action: string) { if (!this.initialized) { this.cachedEvents.push({category, action}); return; diff --git a/src/services/config.service.js b/src/services/config.service.js deleted file mode 100644 index 8ac067a..0000000 --- a/src/services/config.service.js +++ /dev/null @@ -1,76 +0,0 @@ -import * as defaultConfig from "@/assets/config.json"; - -export default { - install(app, defaultServerFromLocation, onloaded) { - var config = defaultConfig.default; - config["loaded"] = false; - const getRuntimeConfig = () => { - return fetch('./config.json?ms=' + Date.now()).then((res) => res.json()).catch(err => { - console.error("Failed to get config:", err); - return {}; - }); - } - - config.promise = getRuntimeConfig().then((json) => { - // Reactively use all the config values - for (const key of Object.keys(json)) { - config[key] = json[key]; - } - // If default server is not set, default to current server address - if (!json.defaultBaseUrl) { - if (json.defaultServer) { - // TODO - Only to migrate old values (defaultServer was renamed defaultBaseUrl), can be removed later... - config["defaultBaseUrl"] = defaultServerFromLocation; - } else { - config["defaultBaseUrl"] = json.defaultServer; - } - } - if (json.useFullyQualifiedDMLinks == undefined) { - config["useFullyQualifiedDMLinks"] = true; // Default to true - } - if (json.disableMediaSharing == undefined) { - config["disableMediaSharing"] = false; - } - if (!json.maxSizeAutoDownloads) { - config["maxSizeAutoDownloads"] = 10 * 1024 * 1024; - } - if (!json.roomTypes) { - let roomTypes = ["group_chat", "channel"]; - const fileDropEnabled = (json.experimental_file_mode === undefined) ? true : !!json.experimental_file_mode; - if (fileDropEnabled) { - roomTypes.push("file_drop"); - } - config["roomTypes"] = roomTypes; - } - config["loaded"] = true; - - document.title = config.appName || ""; - - // Tell callback we are done loading runtime config - if (onloaded) { - onloaded(config); - } - return config; - }); - - /** - * If there is an explicit mapping for this MX domain in the config file, return the endpoint URL that it maps to. - * @param {*} domain - * @returns - */ - config.getMatrixDomainPartMapping = (domain) => { - console.log("Get domain endpoint mapping for", domain); - if (config.matrixDomainPartMapping && config.matrixDomainPartMapping[domain]) { - const mapping = config.matrixDomainPartMapping[domain]; - if (Array.isArray(mapping)) { - return mapping[0]; //TODO - Use the first one for now, but maybe rotate somehow? - } - return mapping; - } - return undefined; - } - - app.$config = config; - app.config.globalProperties.$config = config; - } -} diff --git a/src/services/config.service.ts b/src/services/config.service.ts new file mode 100644 index 0000000..a6515ff --- /dev/null +++ b/src/services/config.service.ts @@ -0,0 +1,114 @@ +import * as defaultConfig from "@/assets/config.json"; + +export class Config { + appName: string = ""; + appNames: { [key: string]: string } = {}; + languageSupportEmail: string = ""; + productLink: string = ""; + defaultBaseUrl: string = ""; + matrixDomainPartMapping: { [key: string]: string | string[] } = {}; + useFullyQualifiedDMLinks: boolean = true; + defaultMatrixDomainPart: string = ""; + identityServer?: string; + registrationToken?: string; + accentColor?: string; + logo?: string; + analytics: { type?: string; enabled: boolean; config?: any }[] = []; + experimental_voice_mode: boolean = true; + experimental_file_mode: boolean = true; + experimental_read_only_room: boolean = true; + experimental_public_room: boolean = true; + show_status_messages: "always" | "never" | "moderators" = "never"; + hide_add_room_on_home: boolean = false; + mirrors?: string[]; + maxSizeAutoDownloads: number = 10 * 1024 * 1024; + disableMediaSharing: boolean = false; + chat_backgrounds: { all?: string[]; direct?: string[]; public?: string[]; private?: string[] } = {}; + shortCodeStickers?: { packs: { name: string; stickers: string[] }[] }; + roomTypes: ("group_chat" | "channel" | "file_drop")[] = ["group_chat", "channel", "file_drop"]; + + _loaded: boolean; + _loadPromise: Promise | undefined; + + constructor() { + this._loaded = false; + } + + load = (): Promise => { + if (this._loadPromise) return this._loadPromise; + this._loadPromise = fetch("./config.json?ms=" + Date.now()) + .then((res) => res.json()) + .catch((err) => { + console.error("Failed to get config:", err); + return {}; + }) + .then((json: { [key: string]: any }) => { + this.fromJson(json); + this._loaded = true; + return this; + }); + return this._loadPromise; + }; + + isLoaded = () => { + return this._loaded; + }; + + fromJson = (json: { [key: string]: any }) => { + for (const key of Object.keys(json)) { + if (!Object.hasOwn(this, key)) { + console.error("NO SUCH CONFIG VALUE", key); + } + (this as any)[key] = json[key]; + } + }; + + /** + * If there is an explicit mapping for this MX domain in the config file, return the endpoint URL that it maps to. + * @param {*} domain + * @returns + */ + getMatrixDomainPartMapping = (domain: string) => { + console.log("Get domain endpoint mapping for", domain); + if (this.matrixDomainPartMapping && this.matrixDomainPartMapping[domain]) { + const mapping = this.matrixDomainPartMapping[domain]; + if (Array.isArray(mapping)) { + return mapping[0]; //TODO - Use the first one for now, but maybe rotate somehow? + } + return mapping; + } + return undefined; + }; +} + +export default { + install(app: any, defaultServerFromLocation: string, onloaded: (config: Config) => void) { + var config = new Config(); + config.defaultBaseUrl = defaultServerFromLocation; + config.fromJson(defaultConfig.default); + + config.load().then((config: Config) => { + document.title = config.appName; + + // Tell callback we are done loading runtime config + if (onloaded) { + onloaded(config); + } + return config; + }); + + // Proxy the object for debugging + const handler = { + get(target: any, prop: string, receiver: any) { + if (!Object.hasOwn(target, prop)) { + console.error("CONFIG - Invalid property", prop); + } + return Reflect.get(target, prop, receiver); + }, + }; + const configProxy = new Proxy(config, handler); + + app.$config = configProxy; + app.config.globalProperties.$config = configProxy; + }, +};