diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts new file mode 100644 index 00000000..646fe6ea --- /dev/null +++ b/src/@types/global.d.ts @@ -0,0 +1,22 @@ +/* +Copyright 2020 New Vector Ltd + +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. +*/ + +interface Window { + Olm: { + init: () => Promise; + }; + mxSendRageshake: (text: string, withLogs?: boolean) => void; +} diff --git a/src/vector/app.js b/src/vector/app.js index 6a1635dd..131e1ca4 100644 --- a/src/vector/app.js +++ b/src/vector/app.js @@ -18,8 +18,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import olmWasmPath from 'olm/olm.wasm'; - import React from 'react'; // add React and ReactPerf to the global namespace, to make them easier to // access via the console @@ -29,7 +27,6 @@ import ReactDOM from 'react-dom'; import * as sdk from 'matrix-react-sdk'; import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg'; import * as VectorConferenceHandler from 'matrix-react-sdk/src/VectorConferenceHandler'; -import * as languageHandler from 'matrix-react-sdk/src/languageHandler'; import {_t, _td, newTranslatableError} from 'matrix-react-sdk/src/languageHandler'; import AutoDiscoveryUtils from 'matrix-react-sdk/src/utils/AutoDiscoveryUtils'; import {AutoDiscovery} from "matrix-js-sdk/src/autodiscovery"; @@ -40,14 +37,11 @@ import url from 'url'; import {parseQs, parseQsFromFragment} from './url_utils'; import {MatrixClientPeg} from 'matrix-react-sdk/src/MatrixClientPeg'; -import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore"; import SdkConfig from "matrix-react-sdk/src/SdkConfig"; import {setTheme} from "matrix-react-sdk/src/theme"; -import Olm from 'olm'; - import CallHandler from 'matrix-react-sdk/src/CallHandler'; -import {loadConfig, preparePlatform} from "./initial-load"; +import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init"; let lastLocationHashSet = null; @@ -193,7 +187,7 @@ export async function loadApp() { const platform = PlatformPeg.get(); // Load the config from the platform - const configInfo = await loadConfig(); + const configError = await loadConfig(); // Load language after loading config.json so that settingsDefaults.language can be applied await loadLanguage(); @@ -222,7 +216,7 @@ export async function loadApp() { await setTheme(); // Now that we've loaded the theme (CSS), display the config syntax error if needed. - if (configInfo.configSyntaxError) { + if (configError && configError.err && configError.err instanceof SyntaxError) { const errorMessage = (

@@ -234,7 +228,7 @@ export async function loadApp() {

{_t( "The message from the parser is: %(message)s", - {message: configInfo.configError.err.message || _t("Invalid JSON")}, + {message: configError.err.message || _t("Invalid JSON")}, )}

@@ -254,7 +248,7 @@ export async function loadApp() { const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname; console.log("Vector starting at " + urlWithoutQuery); - if (configInfo.configError) { + if (configError) { window.matrixChat = ReactDOM.render(
Unable to load config file: please refresh the page to try again.
, document.getElementById('matrixchat')); @@ -308,62 +302,6 @@ export async function loadApp() { } } -function loadOlm() { - /* Load Olm. We try the WebAssembly version first, and then the legacy, - * asm.js version if that fails. For this reason we need to wait for this - * to finish before continuing to load the rest of the app. In future - * we could somehow pass a promise down to react-sdk and have it wait on - * that so olm can be loading in parallel with the rest of the app. - * - * We also need to tell the Olm js to look for its wasm file at the same - * level as index.html. It really should be in the same place as the js, - * ie. in the bundle directory, but as far as I can tell this is - * completely impossible with webpack. We do, however, use a hashed - * filename to avoid caching issues. - */ - return Olm.init({ - locateFile: () => olmWasmPath, - }).then(() => { - console.log("Using WebAssembly Olm"); - }).catch((e) => { - console.log("Failed to load Olm: trying legacy version", e); - return new Promise((resolve, reject) => { - const s = document.createElement('script'); - s.src = 'olm_legacy.js'; // XXX: This should be cache-busted too - s.onload = resolve; - s.onerror = reject; - document.body.appendChild(s); - }).then(() => { - // Init window.Olm, ie. the one just loaded by the script tag, - // not 'Olm' which is still the failed wasm version. - return window.Olm.init(); - }).then(() => { - console.log("Using legacy Olm"); - }).catch((e) => { - console.log("Both WebAssembly and asm.js Olm failed!", e); - }); - }); -} - -async function loadLanguage() { - const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/true); - let langs = []; - - if (!prefLang) { - languageHandler.getLanguagesFromBrowser().forEach((l) => { - langs.push(...languageHandler.getNormalizedLanguageKeys(l)); - }); - } else { - langs = [prefLang]; - } - try { - await languageHandler.setLanguage(langs); - document.documentElement.setAttribute("lang", languageHandler.getCurrentLanguage()); - } catch (e) { - console.error("Unable to set language", e); - } -} - async function verifyServerConfig() { let validatedConfig; try { diff --git a/src/vector/getconfig.js b/src/vector/getconfig.ts similarity index 85% rename from src/vector/getconfig.js rename to src/vector/getconfig.ts index 6fb74d38..84b6d47d 100644 --- a/src/vector/getconfig.js +++ b/src/vector/getconfig.ts @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018, 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,22 +18,25 @@ import request from 'browser-request'; // Load the config file. First try to load up a domain-specific config of the // form "config.$domain.json" and if that fails, fall back to config.json. -export async function getVectorConfig(relativeLocation) { - if (relativeLocation === undefined) relativeLocation = ''; +export async function getVectorConfig(relativeLocation: string='') { if (relativeLocation !== '' && !relativeLocation.endsWith('/')) relativeLocation += '/'; + + const specificConfigPromise = getConfig(`${relativeLocation}config.${document.domain}.json`); + const generalConfigPromise = getConfig(relativeLocation + "config.json"); + try { - const configJson = await getConfig(`${relativeLocation}config.${document.domain}.json`); + const configJson = await specificConfigPromise; // 404s succeed with an empty json config, so check that there are keys if (Object.keys(configJson).length === 0) { throw new Error(); // throw to enter the catch } return configJson; } catch (e) { - return await getConfig(relativeLocation + "config.json"); + return await generalConfigPromise; } } -function getConfig(configJsonFilename) { +function getConfig(configJsonFilename: string): Promise<{}> { return new Promise(function(resolve, reject) { request( { method: "GET", url: configJsonFilename, qs: { cachebuster: Date.now() } }, diff --git a/src/vector/init.ts b/src/vector/init.ts new file mode 100644 index 00000000..96745f53 --- /dev/null +++ b/src/vector/init.ts @@ -0,0 +1,114 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd +Copyright 2017 Vector Creations Ltd +Copyright 2018, 2019, 2020 New Vector Ltd +Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> + +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. +*/ + +// @ts-ignore +import olmWasmPath from "olm/olm.wasm"; +import Olm from 'olm'; + +import * as languageHandler from 'matrix-react-sdk/src/languageHandler'; +import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore"; +import ElectronPlatform from "./platform/ElectronPlatform"; +import WebPlatform from "./platform/WebPlatform"; +import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg'; +import SdkConfig from "matrix-react-sdk/src/SdkConfig"; + + +export function preparePlatform() { + if ((window).ipcRenderer) { + console.log("Using Electron platform"); + const plaf = new ElectronPlatform(); + PlatformPeg.set(plaf); + } else { + console.log("Using Web platform"); + PlatformPeg.set(new WebPlatform()); + } +} + +export async function loadConfig(): Promise { + const platform = PlatformPeg.get(); + + let configJson; + try { + configJson = await platform.getConfig(); + } catch (e) { + return e; + } finally { + // XXX: We call this twice, once here and once in MatrixChat as a prop. We call it here to ensure + // granular settings are loaded correctly and to avoid duplicating the override logic for the theme. + // + // Note: this isn't called twice for some wrappers, like the Jitsi wrapper. + SdkConfig.put(configJson || {}); + } +} + +export function loadOlm(): Promise { + /* Load Olm. We try the WebAssembly version first, and then the legacy, + * asm.js version if that fails. For this reason we need to wait for this + * to finish before continuing to load the rest of the app. In future + * we could somehow pass a promise down to react-sdk and have it wait on + * that so olm can be loading in parallel with the rest of the app. + * + * We also need to tell the Olm js to look for its wasm file at the same + * level as index.html. It really should be in the same place as the js, + * ie. in the bundle directory, but as far as I can tell this is + * completely impossible with webpack. We do, however, use a hashed + * filename to avoid caching issues. + */ + return Olm.init({ + locateFile: () => olmWasmPath, + }).then(() => { + console.log("Using WebAssembly Olm"); + }).catch((e) => { + console.log("Failed to load Olm: trying legacy version", e); + return new Promise((resolve, reject) => { + const s = document.createElement('script'); + s.src = 'olm_legacy.js'; // XXX: This should be cache-busted too + s.onload = resolve; + s.onerror = reject; + document.body.appendChild(s); + }).then(() => { + // Init window.Olm, ie. the one just loaded by the script tag, + // not 'Olm' which is still the failed wasm version. + return window.Olm.init(); + }).then(() => { + console.log("Using legacy Olm"); + }).catch((e) => { + console.log("Both WebAssembly and asm.js Olm failed!", e); + }); + }); +} + +export async function loadLanguage() { + const prefLang = SettingsStore.getValue("language", null, /*excludeDefault=*/true); + let langs = []; + + if (!prefLang) { + languageHandler.getLanguagesFromBrowser().forEach((l) => { + langs.push(...languageHandler.getNormalizedLanguageKeys(l)); + }); + } else { + langs = [prefLang]; + } + try { + await languageHandler.setLanguage(langs); + document.documentElement.setAttribute("lang", languageHandler.getCurrentLanguage()); + } catch (e) { + console.error("Unable to set language", e); + } +} diff --git a/src/vector/initial-load.ts b/src/vector/initial-load.ts deleted file mode 100644 index 7bd73c5a..00000000 --- a/src/vector/initial-load.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2020 New Vector Ltd - -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 ElectronPlatform from './platform/ElectronPlatform'; -import WebPlatform from './platform/WebPlatform'; -import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg'; -import SdkConfig from "matrix-react-sdk/src/SdkConfig"; - -export function preparePlatform() { - if ((window).ipcRenderer) { - console.log("Using Electron platform"); - const plaf = new ElectronPlatform(); - PlatformPeg.set(plaf); - } else { - console.log("Using Web platform"); - PlatformPeg.set(new WebPlatform()); - } -} - -export async function loadConfig(): Promise<{configError?: Error, configSyntaxError: boolean}> { - const platform = PlatformPeg.get(); - - let configJson; - let configError; - let configSyntaxError = false; - try { - configJson = await platform.getConfig(); - } catch (e) { - configError = e; - - if (e && e.err && e.err instanceof SyntaxError) { - console.error("SyntaxError loading config:", e); - configSyntaxError = true; - configJson = {}; // to prevent errors between here and loading CSS for the error box - } - } - - // XXX: We call this twice, once here and once in MatrixChat as a prop. We call it here to ensure - // granular settings are loaded correctly and to avoid duplicating the override logic for the theme. - // - // Note: this isn't called twice for some wrappers, like the Jitsi wrapper. - SdkConfig.put(configJson); - - return {configError, configSyntaxError}; -} diff --git a/src/vector/rageshakesetup.js b/src/vector/rageshakesetup.ts similarity index 97% rename from src/vector/rageshakesetup.js rename to src/vector/rageshakesetup.ts index e9ce1c3b..6445f4e9 100644 --- a/src/vector/rageshakesetup.js +++ b/src/vector/rageshakesetup.ts @@ -50,7 +50,7 @@ function initRageshake() { initRageshake(); -global.mxSendRageshake = function(text, withLogs) { +window.mxSendRageshake = function(text: string, withLogs?: boolean) { if (withLogs === undefined) withLogs = true; if (!text || !text.trim()) { console.error("Cannot send a rageshake without a message - please tell us what went wrong"); diff --git a/src/vector/url_utils.ts b/src/vector/url_utils.ts index 935167aa..d35de505 100644 --- a/src/vector/url_utils.ts +++ b/src/vector/url_utils.ts @@ -1,5 +1,5 @@ /* -Copyright 2018 New Vector Ltd +Copyright 2018, 2020 New Vector Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import * as qs from 'querystring'; // so we're re-using query string like format // // returns {location, params} -export function parseQsFromFragment(location) { +export function parseQsFromFragment(location: Location) { // if we have a fragment, it will start with '#', which we need to drop. // (if we don't, this will return ''). const fragment = location.hash.substring(1); @@ -41,6 +41,6 @@ export function parseQsFromFragment(location) { return result; } -export function parseQs(location) { +export function parseQs(location: Location) { return qs.parse(location.search.substring(1)); } diff --git a/webpack.config.js b/webpack.config.js index d62b6f07..9d8f333c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,6 +56,9 @@ module.exports = (env, argv) => { enforce: true, // Do not add `chunks: 'all'` here because you'll break the app entry point. }, + default: { + reuseExistingChunk: true, + }, }, },