From 99e1ff9477a7bcf237e00fd612e4d8a07edf612a Mon Sep 17 00:00:00 2001
From: Travis Ralston <travpc@gmail.com>
Date: Mon, 20 Jan 2020 19:52:11 -0700
Subject: [PATCH] Load as little as possible in index.js for the skinner

Imports are optimized to be concurrent/async by webpack, which means that when the old index.js referenced the Lifecycle from the react-sdk it caused the app to explode. This is because in another branch the Lifecycle references a class member of a skinnable component, leading to the skinner complaining that the skin hasn't been loaded.

To work around this, we've shoved all the app stuff to a new app.js file, leaving just the skinning and some early bootstrap work in the index.js
---
 src/vector/app.js   | 475 ++++++++++++++++++++++++++++++++++++++++++++
 src/vector/index.js | 470 ++-----------------------------------------
 2 files changed, 488 insertions(+), 457 deletions(-)
 create mode 100644 src/vector/app.js

diff --git a/src/vector/app.js b/src/vector/app.js
new file mode 100644
index 00000000..c77049f5
--- /dev/null
+++ b/src/vector/app.js
@@ -0,0 +1,475 @@
+/*
+Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2017 Vector Creations Ltd
+Copyright 2018, 2019 New Vector Ltd
+Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2020 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.
+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 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
+global.React = React;
+
+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";
+import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
+
+import url from 'url';
+
+import {parseQs, parseQsFromFragment} from './url_utils';
+
+import ElectronPlatform from './platform/ElectronPlatform';
+import WebPlatform from './platform/WebPlatform';
+
+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';
+
+let lastLocationHashSet = null;
+
+function checkBrowserFeatures(featureList) {
+    if (!window.Modernizr) {
+        console.error("Cannot check features - Modernizr global is missing.");
+        return false;
+    }
+    let featureComplete = true;
+    for (let i = 0; i < featureList.length; i++) {
+        if (window.Modernizr[featureList[i]] === undefined) {
+            console.error(
+                "Looked for feature '%s' but Modernizr has no results for this. " +
+                "Has it been configured correctly?", featureList[i],
+            );
+            return false;
+        }
+        if (window.Modernizr[featureList[i]] === false) {
+            console.error("Browser missing feature: '%s'", featureList[i]);
+            // toggle flag rather than return early so we log all missing features
+            // rather than just the first.
+            featureComplete = false;
+        }
+    }
+    return featureComplete;
+}
+
+// Parse the given window.location and return parameters that can be used when calling
+// MatrixChat.showScreen(screen, params)
+function getScreenFromLocation(location) {
+    const fragparts = parseQsFromFragment(location);
+    return {
+        screen: fragparts.location.substring(1),
+        params: fragparts.params,
+    };
+}
+
+// Here, we do some crude URL analysis to allow
+// deep-linking.
+function routeUrl(location) {
+    if (!window.matrixChat) return;
+
+    console.log("Routing URL ", location.href);
+    const s = getScreenFromLocation(location);
+    window.matrixChat.showScreen(s.screen, s.params);
+}
+
+function onHashChange(ev) {
+    if (decodeURIComponent(window.location.hash) === lastLocationHashSet) {
+        // we just set this: no need to route it!
+        return;
+    }
+    routeUrl(window.location);
+}
+
+// This will be called whenever the SDK changes screens,
+// so a web page can update the URL bar appropriately.
+function onNewScreen(screen) {
+    console.log("newscreen "+screen);
+    const hash = '#/' + screen;
+    lastLocationHashSet = hash;
+    window.location.hash = hash;
+}
+
+// We use this to work out what URL the SDK should
+// pass through when registering to allow the user to
+// click back to the client having registered.
+// It's up to us to recognise if we're loaded with
+// this URL and tell MatrixClient to resume registration.
+//
+// If we're in electron, we should never pass through a file:// URL otherwise
+// the identity server will try to 302 the browser to it, which breaks horribly.
+// so in that instance, hardcode to use riot.im/app for now instead.
+function makeRegistrationUrl(params) {
+    let url;
+    if (window.location.protocol === "vector:") {
+        url = 'https://riot.im/app/#/register';
+    } else {
+        url = (
+            window.location.protocol + '//' +
+            window.location.host +
+            window.location.pathname +
+            '#/register'
+        );
+    }
+
+    const keys = Object.keys(params);
+    for (let i = 0; i < keys.length; ++i) {
+        if (i === 0) {
+            url += '?';
+        } else {
+            url += '&';
+        }
+        const k = keys[i];
+        url += k + '=' + encodeURIComponent(params[k]);
+    }
+    return url;
+}
+
+function onTokenLoginCompleted() {
+    // if we did a token login, we're now left with the token, hs and is
+    // url as query params in the url; a little nasty but let's redirect to
+    // clear them.
+    const parsedUrl = url.parse(window.location.href);
+    parsedUrl.search = "";
+    const formatted = url.format(parsedUrl);
+    console.log("Redirecting to " + formatted + " to drop loginToken " +
+        "from queryparams");
+    window.location.href = formatted;
+}
+
+export async function loadApp() {
+    if (window.vector_indexeddb_worker_script === undefined) {
+        // If this is missing, something has probably gone wrong with
+        // the bundling. The js-sdk will just fall back to accessing
+        // indexeddb directly with no worker script, but we want to
+        // make sure the indexeddb script is present, so fail hard.
+        throw new Error("Missing indexeddb worker script!");
+    }
+    MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);
+    CallHandler.setConferenceHandler(VectorConferenceHandler);
+
+    window.addEventListener('hashchange', onHashChange);
+
+    await loadOlm();
+
+    // set the platform for react sdk
+    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());
+    }
+
+    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.
+    SdkConfig.put(configJson);
+
+    // Load language after loading config.json so that settingsDefaults.language can be applied
+    await loadLanguage();
+
+    const fragparts = parseQsFromFragment(window.location);
+    const params = parseQs(window.location);
+
+    // don't try to redirect to the native apps if we're
+    // verifying a 3pid (but after we've loaded the config)
+    // or if the user is following a deep link
+    // (https://github.com/vector-im/riot-web/issues/7378)
+    const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
+
+    if (!preventRedirect) {
+        const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
+        const isAndroid = /Android/.test(navigator.userAgent);
+        if (isIos || isAndroid) {
+            if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
+                window.location = "mobile_guide/";
+                return;
+            }
+        }
+    }
+
+    // as quickly as we possibly can, set a default theme...
+    await setTheme();
+
+    // Now that we've loaded the theme (CSS), display the config syntax error if needed.
+    if (configSyntaxError) {
+        const errorMessage = (
+            <div>
+                <p>
+                    {_t(
+                        "Your Riot configuration contains invalid JSON. Please correct the problem " +
+                        "and reload the page.",
+                    )}
+                </p>
+                <p>
+                    {_t(
+                        "The message from the parser is: %(message)s",
+                        {message: configError.err.message || _t("Invalid JSON")},
+                    )}
+                </p>
+            </div>
+        );
+
+        const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
+        window.matrixChat = ReactDOM.render(
+            <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
+            document.getElementById('matrixchat'),
+        );
+        return;
+    }
+
+    const validBrowser = checkBrowserFeatures([
+        "displaytable", "flexbox", "es5object", "es5function", "localstorage",
+        "objectfit", "indexeddb", "webworkers",
+    ]);
+
+    const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
+
+    const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
+    console.log("Vector starting at " + urlWithoutQuery);
+    if (configError) {
+        window.matrixChat = ReactDOM.render(<div className="error">
+            Unable to load config file: please refresh the page to try again.
+        </div>, document.getElementById('matrixchat'));
+    } else if (validBrowser || acceptInvalidBrowser) {
+        platform.startUpdater();
+
+        // Don't bother loading the app until the config is verified
+        verifyServerConfig().then((newConfig) => {
+            const MatrixChat = sdk.getComponent('structures.MatrixChat');
+            window.matrixChat = ReactDOM.render(
+                <MatrixChat
+                    onNewScreen={onNewScreen}
+                    makeRegistrationUrl={makeRegistrationUrl}
+                    ConferenceHandler={VectorConferenceHandler}
+                    config={newConfig}
+                    realQueryParams={params}
+                    startingFragmentQueryParams={fragparts.params}
+                    enableGuest={!configJson.disable_guests}
+                    onTokenLoginCompleted={onTokenLoginCompleted}
+                    initialScreenAfterLogin={getScreenFromLocation(window.location)}
+                    defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
+                />,
+                document.getElementById('matrixchat'),
+            );
+        }).catch(err => {
+            console.error(err);
+
+            let errorMessage = err.translatedMessage
+                || _t("Unexpected error preparing the app. See console for details.");
+            errorMessage = <span>{errorMessage}</span>;
+
+            // Like the compatibility page, AWOOOOOGA at the user
+            const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
+            window.matrixChat = ReactDOM.render(
+                <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
+                document.getElementById('matrixchat'),
+            );
+        });
+    } else {
+        console.error("Browser is missing required features.");
+        // take to a different landing page to AWOOOOOGA at the user
+        const CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
+        window.matrixChat = ReactDOM.render(
+            <CompatibilityPage onAccept={function() {
+                if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
+                console.log("User accepts the compatibility risks.");
+                loadApp();
+            }} />,
+            document.getElementById('matrixchat'),
+        );
+    }
+}
+
+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 {
+        console.log("Verifying homeserver configuration");
+
+        // Note: the query string may include is_url and hs_url - we only respect these in the
+        // context of email validation. Because we don't respect them otherwise, we do not need
+        // to parse or consider them here.
+
+        // Note: Although we throw all 3 possible configuration options through a .well-known-style
+        // verification, we do not care if the servers are online at this point. We do moderately
+        // care if they are syntactically correct though, so we shove them through the .well-known
+        // validators for that purpose.
+
+        const config = SdkConfig.get();
+        let wkConfig = config['default_server_config']; // overwritten later under some conditions
+        const serverName = config['default_server_name'];
+        const hsUrl = config['default_hs_url'];
+        const isUrl = config['default_is_url'];
+
+        const incompatibleOptions = [wkConfig, serverName, hsUrl].filter(i => !!i);
+        if (incompatibleOptions.length > 1) {
+            // noinspection ExceptionCaughtLocallyJS
+            throw newTranslatableError(_td(
+                "Invalid configuration: can only specify one of default_server_config, default_server_name, " +
+                "or default_hs_url.",
+            ));
+        }
+        if (incompatibleOptions.length < 1) {
+            // noinspection ExceptionCaughtLocallyJS
+            throw newTranslatableError(_td("Invalid configuration: no default server specified."));
+        }
+
+        if (hsUrl) {
+            console.log("Config uses a default_hs_url - constructing a default_server_config using this information");
+            console.warn(
+                "DEPRECATED CONFIG OPTION: In the future, default_hs_url will not be accepted. Please use " +
+                "default_server_config instead.",
+            );
+
+            wkConfig = {
+                "m.homeserver": {
+                    "base_url": hsUrl,
+                },
+            };
+            if (isUrl) {
+                wkConfig["m.identity_server"] = {
+                    "base_url": isUrl,
+                };
+            }
+        }
+
+        let discoveryResult = null;
+        if (wkConfig) {
+            console.log("Config uses a default_server_config - validating object");
+            discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);
+        }
+
+        if (serverName) {
+            console.log("Config uses a default_server_name - doing .well-known lookup");
+            console.warn(
+                "DEPRECATED CONFIG OPTION: In the future, default_server_name will not be accepted. Please " +
+                "use default_server_config instead.",
+            );
+            discoveryResult = await AutoDiscovery.findClientConfig(serverName);
+        }
+
+        validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
+    } catch (e) {
+        const {hsUrl, isUrl, userId} = Lifecycle.getLocalStorageSessionVars();
+        if (hsUrl && userId) {
+            console.error(e);
+            console.warn("A session was found - suppressing config error and using the session's homeserver");
+
+            console.log("Using pre-existing hsUrl and isUrl: ", {hsUrl, isUrl});
+            validatedConfig = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
+        } else {
+            // the user is not logged in, so scream
+            throw e;
+        }
+    }
+
+
+    validatedConfig.isDefault = true;
+
+    // Just in case we ever have to debug this
+    console.log("Using homeserver config:", validatedConfig);
+
+    // Add the newly built config to the actual config for use by the app
+    console.log("Updating SdkConfig with validated discovery information");
+    SdkConfig.add({"validated_server_config": validatedConfig});
+
+    return SdkConfig.get();
+}
diff --git a/src/vector/index.js b/src/vector/index.js
index aa7bf3c1..278c6210 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -3,6 +3,7 @@ Copyright 2015, 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
 Copyright 2018, 2019 New Vector Ltd
 Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+Copyright 2020 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.
@@ -24,464 +25,19 @@ require('gemini-scrollbar/gemini-scrollbar.css');
 require('gfm.css/gfm.css');
 require('highlight.js/styles/github.css');
 
-import olmWasmPath from 'olm/olm.wasm';
-
+// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
 import './rageshakesetup';
-
-import React from 'react';
-// add React and ReactPerf to the global namespace, to make them easier to
-// access via the console
-global.React = React;
-
 import './modernizr';
-import ReactDOM from 'react-dom';
+
+// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference
+// the SDK until we have to in imports.
+console.log("Loading skin...");
 import * as sdk from 'matrix-react-sdk';
-import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
-sdk.loadSkin(require('../component-index'));
-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";
-import * as Lifecycle from "matrix-react-sdk/src/Lifecycle";
+import * as skin from "../component-index";
+sdk.loadSkin(skin);
+console.log("Skin loaded!");
 
-import url from 'url';
-
-import {parseQs, parseQsFromFragment} from './url_utils';
-
-import ElectronPlatform from './platform/ElectronPlatform';
-import WebPlatform from './platform/WebPlatform';
-
-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';
-
-let lastLocationHashSet = null;
-
-function checkBrowserFeatures(featureList) {
-    if (!window.Modernizr) {
-        console.error("Cannot check features - Modernizr global is missing.");
-        return false;
-    }
-    let featureComplete = true;
-    for (let i = 0; i < featureList.length; i++) {
-        if (window.Modernizr[featureList[i]] === undefined) {
-            console.error(
-                "Looked for feature '%s' but Modernizr has no results for this. " +
-                "Has it been configured correctly?", featureList[i],
-            );
-            return false;
-        }
-        if (window.Modernizr[featureList[i]] === false) {
-            console.error("Browser missing feature: '%s'", featureList[i]);
-            // toggle flag rather than return early so we log all missing features
-            // rather than just the first.
-            featureComplete = false;
-        }
-    }
-    return featureComplete;
-}
-
-// Parse the given window.location and return parameters that can be used when calling
-// MatrixChat.showScreen(screen, params)
-function getScreenFromLocation(location) {
-    const fragparts = parseQsFromFragment(location);
-    return {
-        screen: fragparts.location.substring(1),
-        params: fragparts.params,
-    };
-}
-
-// Here, we do some crude URL analysis to allow
-// deep-linking.
-function routeUrl(location) {
-    if (!window.matrixChat) return;
-
-    console.log("Routing URL ", location.href);
-    const s = getScreenFromLocation(location);
-    window.matrixChat.showScreen(s.screen, s.params);
-}
-
-function onHashChange(ev) {
-    if (decodeURIComponent(window.location.hash) === lastLocationHashSet) {
-        // we just set this: no need to route it!
-        return;
-    }
-    routeUrl(window.location);
-}
-
-// This will be called whenever the SDK changes screens,
-// so a web page can update the URL bar appropriately.
-function onNewScreen(screen) {
-    console.log("newscreen "+screen);
-    const hash = '#/' + screen;
-    lastLocationHashSet = hash;
-    window.location.hash = hash;
-}
-
-// We use this to work out what URL the SDK should
-// pass through when registering to allow the user to
-// click back to the client having registered.
-// It's up to us to recognise if we're loaded with
-// this URL and tell MatrixClient to resume registration.
-//
-// If we're in electron, we should never pass through a file:// URL otherwise
-// the identity server will try to 302 the browser to it, which breaks horribly.
-// so in that instance, hardcode to use riot.im/app for now instead.
-function makeRegistrationUrl(params) {
-    let url;
-    if (window.location.protocol === "vector:") {
-        url = 'https://riot.im/app/#/register';
-    } else {
-        url = (
-            window.location.protocol + '//' +
-            window.location.host +
-            window.location.pathname +
-            '#/register'
-        );
-    }
-
-    const keys = Object.keys(params);
-    for (let i = 0; i < keys.length; ++i) {
-        if (i === 0) {
-            url += '?';
-        } else {
-            url += '&';
-        }
-        const k = keys[i];
-        url += k + '=' + encodeURIComponent(params[k]);
-    }
-    return url;
-}
-
-function onTokenLoginCompleted() {
-    // if we did a token login, we're now left with the token, hs and is
-    // url as query params in the url; a little nasty but let's redirect to
-    // clear them.
-    const parsedUrl = url.parse(window.location.href);
-    parsedUrl.search = "";
-    const formatted = url.format(parsedUrl);
-    console.log("Redirecting to " + formatted + " to drop loginToken " +
-                "from queryparams");
-    window.location.href = formatted;
-}
-
-async function loadApp() {
-    if (window.vector_indexeddb_worker_script === undefined) {
-        // If this is missing, something has probably gone wrong with
-        // the bundling. The js-sdk will just fall back to accessing
-        // indexeddb directly with no worker script, but we want to
-        // make sure the indexeddb script is present, so fail hard.
-        throw new Error("Missing indexeddb worker script!");
-    }
-    MatrixClientPeg.setIndexedDbWorkerScript(window.vector_indexeddb_worker_script);
-    CallHandler.setConferenceHandler(VectorConferenceHandler);
-
-    window.addEventListener('hashchange', onHashChange);
-
-    await loadOlm();
-
-    // set the platform for react sdk
-    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());
-    }
-
-    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.
-    SdkConfig.put(configJson);
-
-    // Load language after loading config.json so that settingsDefaults.language can be applied
-    await loadLanguage();
-
-    const fragparts = parseQsFromFragment(window.location);
-    const params = parseQs(window.location);
-
-    // don't try to redirect to the native apps if we're
-    // verifying a 3pid (but after we've loaded the config)
-    // or if the user is following a deep link
-    // (https://github.com/vector-im/riot-web/issues/7378)
-    const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
-
-    if (!preventRedirect) {
-        const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-        const isAndroid = /Android/.test(navigator.userAgent);
-        if (isIos || isAndroid) {
-            if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
-                window.location = "mobile_guide/";
-                return;
-            }
-        }
-    }
-
-    // as quickly as we possibly can, set a default theme...
-    await setTheme();
-
-    // Now that we've loaded the theme (CSS), display the config syntax error if needed.
-    if (configSyntaxError) {
-        const errorMessage = (
-            <div>
-                <p>
-                    {_t(
-                        "Your Riot configuration contains invalid JSON. Please correct the problem " +
-                        "and reload the page.",
-                    )}
-                </p>
-                <p>
-                    {_t(
-                        "The message from the parser is: %(message)s",
-                        {message: configError.err.message || _t("Invalid JSON")},
-                    )}
-                </p>
-            </div>
-        );
-
-        const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
-        window.matrixChat = ReactDOM.render(
-            <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
-            document.getElementById('matrixchat'),
-        );
-        return;
-    }
-
-    const validBrowser = checkBrowserFeatures([
-        "displaytable", "flexbox", "es5object", "es5function", "localstorage",
-        "objectfit", "indexeddb", "webworkers",
-    ]);
-
-    const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
-
-    const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
-    console.log("Vector starting at " + urlWithoutQuery);
-    if (configError) {
-        window.matrixChat = ReactDOM.render(<div className="error">
-            Unable to load config file: please refresh the page to try again.
-        </div>, document.getElementById('matrixchat'));
-    } else if (validBrowser || acceptInvalidBrowser) {
-        platform.startUpdater();
-
-        // Don't bother loading the app until the config is verified
-        verifyServerConfig().then((newConfig) => {
-            const MatrixChat = sdk.getComponent('structures.MatrixChat');
-            window.matrixChat = ReactDOM.render(
-                <MatrixChat
-                    onNewScreen={onNewScreen}
-                    makeRegistrationUrl={makeRegistrationUrl}
-                    ConferenceHandler={VectorConferenceHandler}
-                    config={newConfig}
-                    realQueryParams={params}
-                    startingFragmentQueryParams={fragparts.params}
-                    enableGuest={!configJson.disable_guests}
-                    onTokenLoginCompleted={onTokenLoginCompleted}
-                    initialScreenAfterLogin={getScreenFromLocation(window.location)}
-                    defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
-                />,
-                document.getElementById('matrixchat'),
-            );
-        }).catch(err => {
-            console.error(err);
-
-            let errorMessage = err.translatedMessage
-                || _t("Unexpected error preparing the app. See console for details.");
-            errorMessage = <span>{errorMessage}</span>;
-
-            // Like the compatibility page, AWOOOOOGA at the user
-            const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
-            window.matrixChat = ReactDOM.render(
-                <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
-                document.getElementById('matrixchat'),
-            );
-        });
-    } else {
-        console.error("Browser is missing required features.");
-        // take to a different landing page to AWOOOOOGA at the user
-        const CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
-        window.matrixChat = ReactDOM.render(
-            <CompatibilityPage onAccept={function() {
-                if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
-                console.log("User accepts the compatibility risks.");
-                loadApp();
-            }} />,
-            document.getElementById('matrixchat'),
-        );
-    }
-}
-
-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 {
-        console.log("Verifying homeserver configuration");
-
-        // Note: the query string may include is_url and hs_url - we only respect these in the
-        // context of email validation. Because we don't respect them otherwise, we do not need
-        // to parse or consider them here.
-
-        // Note: Although we throw all 3 possible configuration options through a .well-known-style
-        // verification, we do not care if the servers are online at this point. We do moderately
-        // care if they are syntactically correct though, so we shove them through the .well-known
-        // validators for that purpose.
-
-        const config = SdkConfig.get();
-        let wkConfig = config['default_server_config']; // overwritten later under some conditions
-        const serverName = config['default_server_name'];
-        const hsUrl = config['default_hs_url'];
-        const isUrl = config['default_is_url'];
-
-        const incompatibleOptions = [wkConfig, serverName, hsUrl].filter(i => !!i);
-        if (incompatibleOptions.length > 1) {
-            // noinspection ExceptionCaughtLocallyJS
-            throw newTranslatableError(_td(
-                "Invalid configuration: can only specify one of default_server_config, default_server_name, " +
-                "or default_hs_url.",
-            ));
-        }
-        if (incompatibleOptions.length < 1) {
-            // noinspection ExceptionCaughtLocallyJS
-            throw newTranslatableError(_td("Invalid configuration: no default server specified."));
-        }
-
-        if (hsUrl) {
-            console.log("Config uses a default_hs_url - constructing a default_server_config using this information");
-            console.warn(
-                "DEPRECATED CONFIG OPTION: In the future, default_hs_url will not be accepted. Please use " +
-                "default_server_config instead.",
-            );
-
-            wkConfig = {
-                "m.homeserver": {
-                    "base_url": hsUrl,
-                },
-            };
-            if (isUrl) {
-                wkConfig["m.identity_server"] = {
-                    "base_url": isUrl,
-                };
-            }
-        }
-
-        let discoveryResult = null;
-        if (wkConfig) {
-            console.log("Config uses a default_server_config - validating object");
-            discoveryResult = await AutoDiscovery.fromDiscoveryConfig(wkConfig);
-        }
-
-        if (serverName) {
-            console.log("Config uses a default_server_name - doing .well-known lookup");
-            console.warn(
-                "DEPRECATED CONFIG OPTION: In the future, default_server_name will not be accepted. Please " +
-                "use default_server_config instead.",
-            );
-            discoveryResult = await AutoDiscovery.findClientConfig(serverName);
-        }
-
-        validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, discoveryResult, true);
-    } catch (e) {
-        const {hsUrl, isUrl, userId} = Lifecycle.getLocalStorageSessionVars();
-        if (hsUrl && userId) {
-            console.error(e);
-            console.warn("A session was found - suppressing config error and using the session's homeserver");
-
-            console.log("Using pre-existing hsUrl and isUrl: ", {hsUrl, isUrl});
-            validatedConfig = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true);
-        } else {
-            // the user is not logged in, so scream
-            throw e;
-        }
-    }
-
-
-    validatedConfig.isDefault = true;
-
-    // Just in case we ever have to debug this
-    console.log("Using homeserver config:", validatedConfig);
-
-    // Add the newly built config to the actual config for use by the app
-    console.log("Updating SdkConfig with validated discovery information");
-    SdkConfig.add({"validated_server_config": validatedConfig});
-
-    return SdkConfig.get();
-}
-
-loadApp();
+// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
+// run on the components. We use `require` here to make sure webpack doesn't optimize this into an async
+// import and thus running before the skin can load.
+require("./app").loadApp();