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>
This commit is contained in:
Michael Telatynski 2020-04-05 00:55:36 +01:00
parent 093b7bbf72
commit b1575524aa
8 changed files with 92 additions and 72 deletions

View File

@ -96,6 +96,7 @@
"@babel/preset-typescript": "^7.7.4", "@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4", "@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6", "@babel/runtime": "^7.7.6",
"@types/modernizr": "^3.5.3",
"@types/react": "16.9", "@types/react": "16.9",
"@types/react-dom": "^16.9.4", "@types/react-dom": "^16.9.4",
"autoprefixer": "^9.7.3", "autoprefixer": "^9.7.3",

View File

@ -14,12 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
type ReactNode = import("react").ReactNode; import {ReactNode} from "react";
import "modernizr";
interface Window { declare global {
Olm: { interface Window {
init: () => Promise<void>; Modernizr: ModernizrAPI & FeatureDetects;
}; Olm: {
mxSendRageshake: (text: string, withLogs?: boolean) => void; init: () => Promise<void>;
matrixChat: ReactNode; };
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
}
} }

View File

@ -44,41 +44,6 @@ import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init";
let lastLocationHashSet = null; 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 // Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params) // MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) { function getScreenFromLocation(location) {
@ -163,7 +128,7 @@ function onTokenLoginCompleted() {
window.location.href = formatted; 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 // 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 // but alternatives seem to require changing the interface to passing Workers to js-sdk
const vectorIndexeddbWorkerScript = document.body.dataset.vectorIndexeddbWorkerScript; 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 // Load language after loading config.json so that settingsDefaults.language can be applied
await loadLanguage(); await loadLanguage();
const fragparts = parseQsFromFragment(window.location);
const params = parseQs(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... // as quickly as we possibly can, set a default theme...
await setTheme(); await setTheme();
@ -237,17 +184,13 @@ export async function loadApp() {
return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />; 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; const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
console.log("Vector starting at " + urlWithoutQuery); console.log("Vector starting at " + urlWithoutQuery);
if (configError) { if (configError) {
return <div className="error"> return <div className="error">
Unable to load config file: please refresh the page to try again. Unable to load config file: please refresh the page to try again.
</div>; </div>;
} else if (validBrowser || acceptInvalidBrowser) { } else if (acceptBrowser) {
platform.startUpdater(); platform.startUpdater();
try { try {
@ -260,7 +203,7 @@ export async function loadApp() {
ConferenceHandler={VectorConferenceHandler} ConferenceHandler={VectorConferenceHandler}
config={config} config={config}
realQueryParams={params} realQueryParams={params}
startingFragmentQueryParams={fragparts.params} startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests} enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted} onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)} initialScreenAfterLogin={getScreenFromLocation(window.location)}

View File

@ -25,6 +25,7 @@ require('gfm.css/gfm.css');
require('highlight.js/styles/github.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. // 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'; import './modernizr';
// load service worker if available on this platform // 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 // 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. // if modernizr fails we may not have a functional react to show the error message.
// try in react but fallback to an `alert` // 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 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(); 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 // 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 // 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. // import and thus running before the skin can load.
await loadApp(); await loadApp(fragparts.params, acceptBrowser);
} }
start(); start();

View File

@ -138,11 +138,12 @@ export async function loadSkin() {
console.log("Skin loaded!"); 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 // load app.js async so that its code is not executed immediately and we can catch any exceptions
const module = await import( const module = await import(
/* webpackChunkName: "riot-web-app" */ /* webpackChunkName: "riot-web-app" */
/* webpackPreload: true */ /* webpackPreload: true */
"./app"); "./app");
window.matrixChat = ReactDOM.render(await module.loadApp(), document.getElementById('matrixchat')); window.matrixChat = ReactDOM.render(await module.loadApp(fragParams, acceptBrowser),
document.getElementById('matrixchat'));
} }

View File

@ -32,7 +32,7 @@ export function parseQsFromFragment(location: Location) {
const result = { const result = {
location: decodeURIComponent(hashparts[0]), location: decodeURIComponent(hashparts[0]),
params: {}, params: <qs.ParsedUrlQuery>{},
}; };
if (hashparts.length > 1) { if (hashparts.length > 1) {

View File

@ -31,7 +31,7 @@ module.exports = (env, argv) => {
...development, ...development,
entry: { entry: {
"bundle": "./src/vector/index.js", "bundle": "./src/vector/index.ts",
"indexeddb-worker": "./src/vector/indexeddb-worker.js", "indexeddb-worker": "./src/vector/indexeddb-worker.js",
"mobileguide": "./src/vector/mobile_guide/index.js", "mobileguide": "./src/vector/mobile_guide/index.js",
"jitsi": "./src/vector/jitsi/index.ts", "jitsi": "./src/vector/jitsi/index.ts",

View File

@ -1245,6 +1245,11 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== 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@*": "@types/node@*":
version "13.9.0" version "13.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589"