From 7b930da3436423c0428ff5093778367717afa275 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 4 Apr 2020 17:21:59 +0100
Subject: [PATCH 01/10] Make app load more async

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/index.js | 30 +++++++++++++++++++-----------
 src/vector/init.ts  | 32 ++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 11 deletions(-)

diff --git a/src/vector/index.js b/src/vector/index.js
index ff31d7b62c..6d4411cd71 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -33,15 +33,23 @@ if ('serviceWorker' in navigator) {
     navigator.serviceWorker.register('sw.js');
 }
 
-// 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 * as skin from "../component-index";
-sdk.loadSkin(skin);
-console.log("Skin loaded!");
+// React depends on Map & Set which we check for using modernizr's es6collections
+// if modernizr fails we may not have a functional react to show the error message.
+// try in react but fallback to an `alert`
+async function start() {
+    // load init.ts async so that its code is not executed immediately and we can catch any exceptions
+    const {loadSkin, loadApp} = await import(
+        /* webpackChunkName: "init" */
+        /* webpackPreload: true */
+        "./init");
 
-// 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();
+    await loadSkin();
+    await loadApp();
+}
+start().catch(err => {
+    // try show the error in React
+    console.error(err);
+}).catch(err => {
+    // fall back to showing the error in an alert
+    console.error(err);
+});
diff --git a/src/vector/init.ts b/src/vector/init.ts
index 96745f53cc..9b720c7a15 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -112,3 +112,35 @@ export async function loadLanguage() {
         console.error("Unable to set language", e);
     }
 }
+
+export async function loadSkin() {
+    // 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...");
+    const [sdk, skin] = await Promise.all([
+        import(
+            /* webpackChunkName: "matrix-react-sdk" */
+            /* webpackPreload: true */
+            "matrix-react-sdk"),
+        import(
+            /* webpackChunkName: "riot-web-component-index" */
+            /* webpackPreload: true */
+            "../component-index"),
+    ]);
+    sdk.loadSkin(skin);
+    console.log("Skin loaded!");
+}
+
+export async function 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.
+    const module = await import(
+        /* webpackChunkName: "riot-web-app" */
+        /* webpackPreload: true */
+        "./app");
+    await module.loadApp();
+}
+
+// throw new Error("foobar");
+window.Map = undefined;

From 37ed89c25fa6a89600e1f033e55f47245ea30901 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 4 Apr 2020 17:34:33 +0100
Subject: [PATCH 02/10] move rageshake init into init.ts to allow for
 code-splitting

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/index.js          | 13 +++++++++++--
 src/vector/init.ts           |  8 ++++++--
 src/vector/rageshakesetup.ts |  4 +---
 3 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/vector/index.js b/src/vector/index.js
index 6d4411cd71..44c827726c 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -25,7 +25,6 @@ require('gfm.css/gfm.css');
 require('highlight.js/styles/github.css');
 
 // These are things that can run before the skin loads - be careful not to reference the react-sdk though.
-import './rageshakesetup';
 import './modernizr';
 
 // load service worker if available on this platform
@@ -33,16 +32,26 @@ if ('serviceWorker' in navigator) {
     navigator.serviceWorker.register('sw.js');
 }
 
+async function settled(prom) {
+    try {
+        await prom;
+    } catch (e) {
+        console.error(e);
+    }
+}
+
 // React depends on Map & Set which we check for using modernizr's es6collections
 // if modernizr fails we may not have a functional react to show the error message.
 // try in react but fallback to an `alert`
 async function start() {
     // load init.ts async so that its code is not executed immediately and we can catch any exceptions
-    const {loadSkin, loadApp} = await import(
+    const {rageshakePromise, loadSkin, loadApp} = await import(
         /* webpackChunkName: "init" */
         /* webpackPreload: true */
         "./init");
 
+    await settled(rageshakePromise); // give rageshake a chance to load/fail
+
     await loadSkin();
     await loadApp();
 }
diff --git a/src/vector/init.ts b/src/vector/init.ts
index 9b720c7a15..f53b9231b9 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -21,13 +21,17 @@ limitations under the License.
 import olmWasmPath from "olm/olm.wasm";
 import Olm from 'olm';
 
-import * as languageHandler from 'matrix-react-sdk/src/languageHandler';
+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 PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
 import SdkConfig from "matrix-react-sdk/src/SdkConfig";
 
+import { initRageshake } from "./rageshakesetup";
+
+
+export const rageshakePromise = initRageshake();
 
 export function preparePlatform() {
     if ((<any>window).ipcRenderer) {
diff --git a/src/vector/rageshakesetup.ts b/src/vector/rageshakesetup.ts
index 6445f4e95c..cef9d53823 100644
--- a/src/vector/rageshakesetup.ts
+++ b/src/vector/rageshakesetup.ts
@@ -30,7 +30,7 @@ import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
 import SdkConfig from "matrix-react-sdk/src/SdkConfig";
 import sendBugReport from "matrix-react-sdk/src/rageshake/submit-rageshake";
 
-function initRageshake() {
+export function initRageshake() {
     rageshake.init().then(() => {
         console.log("Initialised rageshake.");
         console.log("To fix line numbers in Chrome: " +
@@ -48,8 +48,6 @@ function initRageshake() {
     });
 }
 
-initRageshake();
-
 window.mxSendRageshake = function(text: string, withLogs?: boolean) {
     if (withLogs === undefined) withLogs = true;
     if (!text || !text.trim()) {

From 4b6164d823cf53667fab2a15b8496934e597c50d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 4 Apr 2020 23:42:19 +0100
Subject: [PATCH 03/10] iterate app load order tweaks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/index.js | 6 ++++++
 src/vector/init.ts  | 8 ++------
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/vector/index.js b/src/vector/index.js
index 44c827726c..a919cefea2 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -53,12 +53,18 @@ async function start() {
     await settled(rageshakePromise); // give rageshake a chance to load/fail
 
     await loadSkin();
+
+    // 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.
     await loadApp();
 }
 start().catch(err => {
     // try show the error in React
+    console.log("Show React error page");
     console.error(err);
 }).catch(err => {
     // fall back to showing the error in an alert
+    console.log("Show fallback error page");
     console.error(err);
 });
diff --git a/src/vector/init.ts b/src/vector/init.ts
index f53b9231b9..2344a9387e 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -121,6 +121,7 @@ export async function loadSkin() {
     // 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...");
+    // load these async so that its code is not executed immediately and we can catch any exceptions
     const [sdk, skin] = await Promise.all([
         import(
             /* webpackChunkName: "matrix-react-sdk" */
@@ -136,15 +137,10 @@ export async function loadSkin() {
 }
 
 export async function 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.
+    // load app.js async so that its code is not executed immediately and we can catch any exceptions
     const module = await import(
         /* webpackChunkName: "riot-web-app" */
         /* webpackPreload: true */
         "./app");
     await module.loadApp();
 }
-
-// throw new Error("foobar");
-window.Map = undefined;

From b5318b4ebc0c3a117eee4b25ef90536b0da7791a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 5 Apr 2020 00:05:59 +0100
Subject: [PATCH 04/10] fix global.d.ts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/@types/global.d.ts |  3 ++
 src/vector/app.js      | 66 +++++++++++++++++-------------------------
 src/vector/init.ts     |  3 +-
 3 files changed, 31 insertions(+), 41 deletions(-)

diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 646fe6ea14..90411e7b08 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
+type ReactNode = import("react").ReactNode;
+
 interface Window {
     Olm: {
         init: () => Promise<void>;
     };
     mxSendRageshake: (text: string, withLogs?: boolean) => void;
+    matrixChat: ReactNode;
 }
diff --git a/src/vector/app.js b/src/vector/app.js
index 131e1ca41b..dcdc476998 100644
--- a/src/vector/app.js
+++ b/src/vector/app.js
@@ -23,7 +23,6 @@ import React from 'react';
 // 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';
@@ -235,11 +234,7 @@ export async function loadApp() {
         );
 
         const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
-        window.matrixChat = ReactDOM.render(
-            <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
-            document.getElementById('matrixchat'),
-        );
-        return;
+        return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
     }
 
     const validBrowser = checkBrowserFeatures();
@@ -249,31 +244,29 @@ export async function loadApp() {
     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">
+        return <div className="error">
             Unable to load config file: please refresh the page to try again.
-        </div>, document.getElementById('matrixchat'));
+        </div>;
     } else if (validBrowser || acceptInvalidBrowser) {
         platform.startUpdater();
 
-        // Don't bother loading the app until the config is verified
-        verifyServerConfig().then((newConfig) => {
+        try {
+            // Don't bother loading the app until the config is verified
+            const config = await verifyServerConfig();
             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={!SdkConfig.get().disable_guests}
-                    onTokenLoginCompleted={onTokenLoginCompleted}
-                    initialScreenAfterLogin={getScreenFromLocation(window.location)}
-                    defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
-                />,
-                document.getElementById('matrixchat'),
-            );
-        }).catch(err => {
+            return <MatrixChat
+                onNewScreen={onNewScreen}
+                makeRegistrationUrl={makeRegistrationUrl}
+                ConferenceHandler={VectorConferenceHandler}
+                config={config}
+                realQueryParams={params}
+                startingFragmentQueryParams={fragparts.params}
+                enableGuest={!config.disable_guests}
+                onTokenLoginCompleted={onTokenLoginCompleted}
+                initialScreenAfterLogin={getScreenFromLocation(window.location)}
+                defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
+            />;
+        } catch (err) {
             console.error(err);
 
             let errorMessage = err.translatedMessage
@@ -282,23 +275,17 @@ export async function loadApp() {
 
             // 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'),
-            );
-        });
+            return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
+        }
     } 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'),
-        );
+        return <CompatibilityPage onAccept={function() {
+            if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
+            console.log("User accepts the compatibility risks.");
+            loadApp();
+        }} />;
     }
 }
 
@@ -384,7 +371,6 @@ async function verifyServerConfig() {
         }
     }
 
-
     validatedConfig.isDefault = true;
 
     // Just in case we ever have to debug this
diff --git a/src/vector/init.ts b/src/vector/init.ts
index 2344a9387e..1933b78811 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -20,6 +20,7 @@ limitations under the License.
 // @ts-ignore
 import olmWasmPath from "olm/olm.wasm";
 import Olm from 'olm';
+import * as ReactDOM from "react-dom";
 
 import * as languageHandler from "matrix-react-sdk/src/languageHandler";
 import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
@@ -142,5 +143,5 @@ export async function loadApp() {
         /* webpackChunkName: "riot-web-app" */
         /* webpackPreload: true */
         "./app");
-    await module.loadApp();
+    window.matrixChat = ReactDOM.render(await module.loadApp(), document.getElementById('matrixchat'));
 }

From d4297560940fe8dd700e2e9602ca55db9f0e5809 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 5 Apr 2020 00:08:31 +0100
Subject: [PATCH 05/10] delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/init.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/vector/init.ts b/src/vector/init.ts
index 1933b78811..57941e88cb 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -131,6 +131,7 @@ export async function loadSkin() {
         import(
             /* webpackChunkName: "riot-web-component-index" */
             /* webpackPreload: true */
+            // @ts-ignore - this module is generated so may fail lint
             "../component-index"),
     ]);
     sdk.loadSkin(skin);

From 20442413bf84e6a28801e24ec850c08466fbea87 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 5 Apr 2020 00:12:37 +0100
Subject: [PATCH 06/10] remove future error handling path

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/index.js | 10 +---------
 1 file changed, 1 insertion(+), 9 deletions(-)

diff --git a/src/vector/index.js b/src/vector/index.js
index a919cefea2..9ad72870e1 100644
--- a/src/vector/index.js
+++ b/src/vector/index.js
@@ -59,12 +59,4 @@ async function start() {
     // import and thus running before the skin can load.
     await loadApp();
 }
-start().catch(err => {
-    // try show the error in React
-    console.log("Show React error page");
-    console.error(err);
-}).catch(err => {
-    // fall back to showing the error in an alert
-    console.log("Show fallback error page");
-    console.error(err);
-});
+start();

From 093b7bbf720f4eb60288fd97a3012716f939557e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 5 Apr 2020 00:27:59 +0100
Subject: [PATCH 07/10] convert index.js to typescript

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/{index.js => index.ts} | 2 +-
 src/vector/rageshakesetup.ts      | 4 +++-
 2 files changed, 4 insertions(+), 2 deletions(-)
 rename src/vector/{index.js => index.ts} (98%)

diff --git a/src/vector/index.js b/src/vector/index.ts
similarity index 98%
rename from src/vector/index.js
rename to src/vector/index.ts
index 9ad72870e1..74db1cd687 100644
--- a/src/vector/index.js
+++ b/src/vector/index.ts
@@ -32,7 +32,7 @@ if ('serviceWorker' in navigator) {
     navigator.serviceWorker.register('sw.js');
 }
 
-async function settled(prom) {
+async function settled(prom: Promise<any>) {
     try {
         await prom;
     } catch (e) {
diff --git a/src/vector/rageshakesetup.ts b/src/vector/rageshakesetup.ts
index cef9d53823..e495557429 100644
--- a/src/vector/rageshakesetup.ts
+++ b/src/vector/rageshakesetup.ts
@@ -31,7 +31,8 @@ import SdkConfig from "matrix-react-sdk/src/SdkConfig";
 import sendBugReport from "matrix-react-sdk/src/rageshake/submit-rageshake";
 
 export function initRageshake() {
-    rageshake.init().then(() => {
+    const prom = rageshake.init();
+    prom.then(() => {
         console.log("Initialised rageshake.");
         console.log("To fix line numbers in Chrome: " +
             "Meatball menu → Settings → Blackboxing → Add /rageshake\\.js$");
@@ -46,6 +47,7 @@ export function initRageshake() {
     }, (err) => {
         console.error("Failed to initialise rageshake: " + err);
     });
+    return prom;
 }
 
 window.mxSendRageshake = function(text: string, withLogs?: boolean) {

From b1575524aa853fa0653d9400937c28e7cd1fe8c5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 5 Apr 2020 00:55:36 +0100
Subject: [PATCH 08/10] Switch things to typescript, use @types/modernizr, fix
 global.d.ts. Move mobile_guide redirect to index.ts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 package.json            |  1 +
 src/@types/global.d.ts  | 24 +++++++++++-----
 src/vector/app.js       | 63 ++---------------------------------------
 src/vector/index.ts     | 62 +++++++++++++++++++++++++++++++++++++++-
 src/vector/init.ts      |  5 ++--
 src/vector/url_utils.ts |  2 +-
 webpack.config.js       |  2 +-
 yarn.lock               |  5 ++++
 8 files changed, 92 insertions(+), 72 deletions(-)

diff --git a/package.json b/package.json
index 22370b1706..fcb2b3e5d3 100644
--- a/package.json
+++ b/package.json
@@ -96,6 +96,7 @@
     "@babel/preset-typescript": "^7.7.4",
     "@babel/register": "^7.7.4",
     "@babel/runtime": "^7.7.6",
+    "@types/modernizr": "^3.5.3",
     "@types/react": "16.9",
     "@types/react-dom": "^16.9.4",
     "autoprefixer": "^9.7.3",
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 90411e7b08..a19e20b5c3 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -14,12 +14,22 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-type ReactNode = import("react").ReactNode;
+import {ReactNode} from "react";
+import "modernizr";
 
-interface Window {
-    Olm: {
-        init: () => Promise<void>;
-    };
-    mxSendRageshake: (text: string, withLogs?: boolean) => void;
-    matrixChat: ReactNode;
+declare global {
+    interface Window {
+        Modernizr: ModernizrAPI & FeatureDetects;
+        Olm: {
+            init: () => Promise<void>;
+        };
+
+        mxSendRageshake: (text: string, withLogs?: boolean) => void;
+        matrixChat: ReactNode;
+    }
+
+    // workaround for https://github.com/microsoft/TypeScript/issues/30933
+    interface ObjectConstructor {
+        fromEntries?(xs: [string|number|symbol, any][]): object
+    }
 }
diff --git a/src/vector/app.js b/src/vector/app.js
index dcdc476998..836b2ef1d0 100644
--- a/src/vector/app.js
+++ b/src/vector/app.js
@@ -44,41 +44,6 @@ import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init";
 
 let lastLocationHashSet = null;
 
-function checkBrowserFeatures() {
-    if (!window.Modernizr) {
-        console.error("Cannot check features - Modernizr global is missing.");
-        return false;
-    }
-
-    // custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
-    // Modernizr requires rules to be lowercase with no punctuation:
-    // ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
-    window.Modernizr.addTest("promiseprototypefinally", () =>
-        window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
-    // ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
-    window.Modernizr.addTest("objectfromentries", () =>
-        window.Object && typeof window.Object.fromEntries === "function");
-
-    const featureList = Object.keys(window.Modernizr);
-
-    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) {
@@ -163,7 +128,7 @@ function onTokenLoginCompleted() {
     window.location.href = formatted;
 }
 
-export async function loadApp() {
+export async function loadApp(fragParams: {}, acceptBrowser: boolean) {
     // XXX: the way we pass the path to the worker script from webpack via html in body's dataset is a hack
     // but alternatives seem to require changing the interface to passing Workers to js-sdk
     const vectorIndexeddbWorkerScript = document.body.dataset.vectorIndexeddbWorkerScript;
@@ -191,26 +156,8 @@ export async function loadApp() {
     // 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();
 
@@ -237,17 +184,13 @@ export async function loadApp() {
         return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
     }
 
-    const validBrowser = checkBrowserFeatures();
-
-    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) {
         return <div className="error">
             Unable to load config file: please refresh the page to try again.
         </div>;
-    } else if (validBrowser || acceptInvalidBrowser) {
+    } else if (acceptBrowser) {
         platform.startUpdater();
 
         try {
@@ -260,7 +203,7 @@ export async function loadApp() {
                 ConferenceHandler={VectorConferenceHandler}
                 config={config}
                 realQueryParams={params}
-                startingFragmentQueryParams={fragparts.params}
+                startingFragmentQueryParams={fragParams}
                 enableGuest={!config.disable_guests}
                 onTokenLoginCompleted={onTokenLoginCompleted}
                 initialScreenAfterLogin={getScreenFromLocation(window.location)}
diff --git a/src/vector/index.ts b/src/vector/index.ts
index 74db1cd687..d844827a85 100644
--- a/src/vector/index.ts
+++ b/src/vector/index.ts
@@ -25,6 +25,7 @@ require('gfm.css/gfm.css');
 require('highlight.js/styles/github.css');
 
 // These are things that can run before the skin loads - be careful not to reference the react-sdk though.
+import {parseQsFromFragment} from "./url_utils";
 import './modernizr';
 
 // load service worker if available on this platform
@@ -40,6 +41,41 @@ async function settled(prom: Promise<any>) {
     }
 }
 
+function checkBrowserFeatures() {
+    if (!window.Modernizr) {
+        console.error("Cannot check features - Modernizr global is missing.");
+        return false;
+    }
+
+    // custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
+    // Modernizr requires rules to be lowercase with no punctuation:
+    // ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
+    window.Modernizr.addTest("promiseprototypefinally", () =>
+        window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
+    // ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
+    window.Modernizr.addTest("objectfromentries", () =>
+        window.Object && typeof window.Object.fromEntries === "function");
+
+    const featureList = Object.keys(window.Modernizr);
+
+    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;
+}
+
 // React depends on Map & Set which we check for using modernizr's es6collections
 // if modernizr fails we may not have a functional react to show the error message.
 // try in react but fallback to an `alert`
@@ -52,11 +88,35 @@ async function start() {
 
     await settled(rageshakePromise); // give rageshake a chance to load/fail
 
+    const fragparts = parseQsFromFragment(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.href = "mobile_guide/";
+                return;
+            }
+        }
+    }
+
     await loadSkin();
 
+    let acceptBrowser = checkBrowserFeatures();
+    if (!acceptBrowser && window.localStorage) {
+        acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
+    }
+
     // 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.
-    await loadApp();
+    await loadApp(fragparts.params, acceptBrowser);
 }
 start();
diff --git a/src/vector/init.ts b/src/vector/init.ts
index 57941e88cb..8b8a62bb3c 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -138,11 +138,12 @@ export async function loadSkin() {
     console.log("Skin loaded!");
 }
 
-export async function loadApp() {
+export async function loadApp(fragParams: {}, acceptBrowser: boolean) {
     // load app.js async so that its code is not executed immediately and we can catch any exceptions
     const module = await import(
         /* webpackChunkName: "riot-web-app" */
         /* webpackPreload: true */
         "./app");
-    window.matrixChat = ReactDOM.render(await module.loadApp(), document.getElementById('matrixchat'));
+    window.matrixChat = ReactDOM.render(await module.loadApp(fragParams, acceptBrowser),
+        document.getElementById('matrixchat'));
 }
diff --git a/src/vector/url_utils.ts b/src/vector/url_utils.ts
index d35de505ed..1e14fb5c86 100644
--- a/src/vector/url_utils.ts
+++ b/src/vector/url_utils.ts
@@ -32,7 +32,7 @@ export function parseQsFromFragment(location: Location) {
 
     const result = {
         location: decodeURIComponent(hashparts[0]),
-        params: {},
+        params: <qs.ParsedUrlQuery>{},
     };
 
     if (hashparts.length > 1) {
diff --git a/webpack.config.js b/webpack.config.js
index 9d8f333c87..6e84842d6d 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -31,7 +31,7 @@ module.exports = (env, argv) => {
         ...development,
 
         entry: {
-            "bundle": "./src/vector/index.js",
+            "bundle": "./src/vector/index.ts",
             "indexeddb-worker": "./src/vector/indexeddb-worker.js",
             "mobileguide": "./src/vector/mobile_guide/index.js",
             "jitsi": "./src/vector/jitsi/index.ts",
diff --git a/yarn.lock b/yarn.lock
index ad7be626ed..a46162cf56 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1245,6 +1245,11 @@
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
+"@types/modernizr@^3.5.3":
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/@types/modernizr/-/modernizr-3.5.3.tgz#8ef99e6252191c1d88647809109dc29884ba6d7a"
+  integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA==
+
 "@types/node@*":
   version "13.9.0"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589"

From e788433c248b5c969b8014715d3cfb6642ad2600 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 6 Apr 2020 12:22:20 +0100
Subject: [PATCH 09/10] Combine two async imports into one by way of skin.ts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/init.ts | 15 ++++-----------
 src/vector/skin.ts | 22 ++++++++++++++++++++++
 2 files changed, 26 insertions(+), 11 deletions(-)
 create mode 100644 src/vector/skin.ts

diff --git a/src/vector/init.ts b/src/vector/init.ts
index 8b8a62bb3c..c43f48e3db 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -123,17 +123,10 @@ export async function loadSkin() {
     // the SDK until we have to in imports.
     console.log("Loading skin...");
     // load these async so that its code is not executed immediately and we can catch any exceptions
-    const [sdk, skin] = await Promise.all([
-        import(
-            /* webpackChunkName: "matrix-react-sdk" */
-            /* webpackPreload: true */
-            "matrix-react-sdk"),
-        import(
-            /* webpackChunkName: "riot-web-component-index" */
-            /* webpackPreload: true */
-            // @ts-ignore - this module is generated so may fail lint
-            "../component-index"),
-    ]);
+    const {sdk, skin} = await import(
+        /* webpackChunkName: "skin" */
+        /* webpackPreload: true */
+        "./skin");
     sdk.loadSkin(skin);
     console.log("Skin loaded!");
 }
diff --git a/src/vector/skin.ts b/src/vector/skin.ts
new file mode 100644
index 0000000000..c0c527fdb7
--- /dev/null
+++ b/src/vector/skin.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.
+*/
+
+import * as sdk from "matrix-react-sdk";
+// @ts-ignore - this module is generated so may fail lint
+import * as skin from "../component-index";
+
+// we re-export here so that we can async-load this one file and webpack will bundle them together.
+export {sdk, skin};

From 1b9112b876c050a9c6ef6f741470592134c6017f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 6 Apr 2020 12:24:56 +0100
Subject: [PATCH 10/10] Revert "Combine two async imports into one by way of
 skin.ts"

This reverts commit e788433c

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
 src/vector/init.ts | 15 +++++++++++----
 src/vector/skin.ts | 22 ----------------------
 2 files changed, 11 insertions(+), 26 deletions(-)
 delete mode 100644 src/vector/skin.ts

diff --git a/src/vector/init.ts b/src/vector/init.ts
index c43f48e3db..8b8a62bb3c 100644
--- a/src/vector/init.ts
+++ b/src/vector/init.ts
@@ -123,10 +123,17 @@ export async function loadSkin() {
     // the SDK until we have to in imports.
     console.log("Loading skin...");
     // load these async so that its code is not executed immediately and we can catch any exceptions
-    const {sdk, skin} = await import(
-        /* webpackChunkName: "skin" */
-        /* webpackPreload: true */
-        "./skin");
+    const [sdk, skin] = await Promise.all([
+        import(
+            /* webpackChunkName: "matrix-react-sdk" */
+            /* webpackPreload: true */
+            "matrix-react-sdk"),
+        import(
+            /* webpackChunkName: "riot-web-component-index" */
+            /* webpackPreload: true */
+            // @ts-ignore - this module is generated so may fail lint
+            "../component-index"),
+    ]);
     sdk.loadSkin(skin);
     console.log("Skin loaded!");
 }
diff --git a/src/vector/skin.ts b/src/vector/skin.ts
deleted file mode 100644
index c0c527fdb7..0000000000
--- a/src/vector/skin.ts
+++ /dev/null
@@ -1,22 +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 * as sdk from "matrix-react-sdk";
-// @ts-ignore - this module is generated so may fail lint
-import * as skin from "../component-index";
-
-// we re-export here so that we can async-load this one file and webpack will bundle them together.
-export {sdk, skin};