index.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. Copyright 2015, 2016 OpenMarket Ltd
  3. Copyright 2017 Vector Creations Ltd
  4. Copyright 2018, 2019 New Vector Ltd
  5. Copyright 2019 Michael Telatynski <[email protected]>
  6. Copyright 2020 The Matrix.org Foundation C.I.C.
  7. Licensed under the Apache License, Version 2.0 (the "License");
  8. you may not use this file except in compliance with the License.
  9. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. */
  17. import { logger } from "matrix-js-sdk/src/logger";
  18. // These are things that can run before the skin loads - be careful not to reference the react-sdk though.
  19. import { parseQsFromFragment } from "./url_utils";
  20. import "./modernizr";
  21. // Require common CSS here; this will make webpack process it into bundle.css.
  22. // Our own CSS (which is themed) is imported via separate webpack entry points
  23. // in webpack.config.js
  24. require("gfm.css/gfm.css");
  25. require("katex/dist/katex.css");
  26. /**
  27. * This require is necessary only for purposes of CSS hot-reload, as otherwise
  28. * webpack has some incredible problems figuring out which CSS files should be
  29. * hot-reloaded, even with proper hints for the loader.
  30. *
  31. * On production build it's going to be an empty module, so don't worry about that.
  32. */
  33. require("./devcss");
  34. require("./localstorage-fix");
  35. async function settled(...promises: Array<Promise<any>>): Promise<void> {
  36. for (const prom of promises) {
  37. try {
  38. await prom;
  39. } catch (e) {
  40. logger.error(e);
  41. }
  42. }
  43. }
  44. function checkBrowserFeatures(): boolean {
  45. if (!window.Modernizr) {
  46. logger.error("Cannot check features - Modernizr global is missing.");
  47. return false;
  48. }
  49. // Custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks
  50. // in it for some features we depend on.
  51. // Modernizr requires rules to be lowercase with no punctuation.
  52. // ES2018: http://262.ecma-international.org/9.0/#sec-promise.prototype.finally
  53. window.Modernizr.addTest("promiseprototypefinally", () => typeof window.Promise?.prototype?.finally === "function");
  54. // ES2020: http://262.ecma-international.org/#sec-promise.allsettled
  55. window.Modernizr.addTest("promiseallsettled", () => typeof window.Promise?.allSettled === "function");
  56. // ES2018: https://262.ecma-international.org/9.0/#sec-get-regexp.prototype.dotAll
  57. window.Modernizr.addTest(
  58. "regexpdotall",
  59. () => window.RegExp?.prototype && !!Object.getOwnPropertyDescriptor(window.RegExp.prototype, "dotAll")?.get,
  60. );
  61. // ES2019: http://262.ecma-international.org/10.0/#sec-object.fromentries
  62. window.Modernizr.addTest("objectfromentries", () => typeof window.Object?.fromEntries === "function");
  63. const featureList = Object.keys(window.Modernizr);
  64. let featureComplete = true;
  65. for (const feature of featureList) {
  66. if (window.Modernizr[feature] === undefined) {
  67. logger.error(
  68. "Looked for feature '%s' but Modernizr has no results for this. " + "Has it been configured correctly?",
  69. feature,
  70. );
  71. return false;
  72. }
  73. if (window.Modernizr[feature] === false) {
  74. logger.error("Browser missing feature: '%s'", feature);
  75. // toggle flag rather than return early so we log all missing features rather than just the first.
  76. featureComplete = false;
  77. }
  78. }
  79. return featureComplete;
  80. }
  81. const supportedBrowser = checkBrowserFeatures();
  82. // React depends on Map & Set which we check for using modernizr's es6collections
  83. // if modernizr fails we may not have a functional react to show the error message.
  84. // try in react but fallback to an `alert`
  85. // We start loading stuff but don't block on it until as late as possible to allow
  86. // the browser to use as much parallelism as it can.
  87. // Load parallelism is based on research in https://github.com/vector-im/element-web/issues/12253
  88. async function start(): Promise<void> {
  89. // load init.ts async so that its code is not executed immediately and we can catch any exceptions
  90. const {
  91. rageshakePromise,
  92. setupLogStorage,
  93. preparePlatform,
  94. loadOlm,
  95. loadConfig,
  96. loadLanguage,
  97. loadTheme,
  98. loadApp,
  99. loadModules,
  100. showError,
  101. showIncompatibleBrowser,
  102. _t,
  103. } = await import(
  104. /* webpackChunkName: "init" */
  105. /* webpackPreload: true */
  106. "./init"
  107. );
  108. try {
  109. // give rageshake a chance to load/fail, we don't actually assert rageshake loads, we allow it to fail if no IDB
  110. await settled(rageshakePromise);
  111. const fragparts = parseQsFromFragment(window.location);
  112. // don't try to redirect to the native apps if we're
  113. // verifying a 3pid (but after we've loaded the config)
  114. // or if the user is following a deep link
  115. // (https://github.com/vector-im/element-web/issues/7378)
  116. const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
  117. if (!preventRedirect) {
  118. const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
  119. const isAndroid = /Android/.test(navigator.userAgent);
  120. if (isIos || isAndroid) {
  121. if (document.cookie.indexOf("element_mobile_redirect_to_guide=false") === -1) {
  122. window.location.href = "mobile_guide/";
  123. return;
  124. }
  125. }
  126. }
  127. const loadOlmPromise = loadOlm();
  128. // set the platform for react sdk
  129. preparePlatform();
  130. // load config requires the platform to be ready
  131. const loadConfigPromise = loadConfig();
  132. await settled(loadConfigPromise); // wait for it to settle
  133. // keep initialising so that we can show any possible error with as many features (theme, i18n) as possible
  134. // now that the config is ready, try to persist logs
  135. const persistLogsPromise = setupLogStorage();
  136. // Load modules before language to ensure any custom translations are respected, and any app
  137. // startup functionality is run
  138. const loadModulesPromise = loadModules();
  139. await settled(loadModulesPromise);
  140. // Load language after loading config.json so that settingsDefaults.language can be applied
  141. const loadLanguagePromise = loadLanguage();
  142. // as quickly as we possibly can, set a default theme...
  143. const loadThemePromise = loadTheme();
  144. // await things settling so that any errors we have to render have features like i18n running
  145. await settled(loadThemePromise, loadLanguagePromise);
  146. let acceptBrowser = supportedBrowser;
  147. if (!acceptBrowser && window.localStorage) {
  148. acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
  149. }
  150. // ##########################
  151. // error handling begins here
  152. // ##########################
  153. if (!acceptBrowser) {
  154. await new Promise<void>((resolve) => {
  155. logger.error("Browser is missing required features.");
  156. // take to a different landing page to AWOOOOOGA at the user
  157. showIncompatibleBrowser(() => {
  158. if (window.localStorage) {
  159. window.localStorage.setItem("mx_accepts_unsupported_browser", String(true));
  160. }
  161. logger.log("User accepts the compatibility risks.");
  162. resolve();
  163. });
  164. });
  165. }
  166. try {
  167. // await config here
  168. await loadConfigPromise;
  169. } catch (error) {
  170. // Now that we've loaded the theme (CSS), display the config syntax error if needed.
  171. if (error.err && error.err instanceof SyntaxError) {
  172. // This uses the default brand since the app config is unavailable.
  173. return showError(_t("Your Element is misconfigured"), [
  174. _t(
  175. "Your Element configuration contains invalid JSON. " +
  176. "Please correct the problem and reload the page.",
  177. ),
  178. _t("The message from the parser is: %(message)s", {
  179. message: error.err.message || _t("Invalid JSON"),
  180. }),
  181. ]);
  182. }
  183. return showError(_t("Unable to load config file: please refresh the page to try again."));
  184. }
  185. // ##################################
  186. // app load critical path starts here
  187. // assert things started successfully
  188. // ##################################
  189. await loadOlmPromise;
  190. await loadModulesPromise;
  191. await loadThemePromise;
  192. await loadLanguagePromise;
  193. // We don't care if the log persistence made it through successfully, but we do want to
  194. // make sure it had a chance to load before we move on. It's prepared much higher up in
  195. // the process, making this the first time we check that it did something.
  196. await settled(persistLogsPromise);
  197. // Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
  198. // run on the components.
  199. await loadApp(fragparts.params);
  200. } catch (err) {
  201. logger.error(err);
  202. // Like the compatibility page, AWOOOOOGA at the user
  203. // This uses the default brand since the app config is unavailable.
  204. await showError(_t("Your Element is misconfigured"), [
  205. err.translatedMessage || _t("Unexpected error preparing the app. See console for details."),
  206. ]);
  207. }
  208. }
  209. start().catch((err) => {
  210. logger.error(err);
  211. // show the static error in an iframe to not lose any context / console data
  212. // with some basic styling to make the iframe full page
  213. delete document.body.style.height;
  214. const iframe = document.createElement("iframe");
  215. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  216. // @ts-ignore - typescript seems to only like the IE syntax for iframe sandboxing
  217. iframe["sandbox"] = "";
  218. iframe.src = supportedBrowser ? "static/unable-to-load.html" : "static/incompatible-browser.html";
  219. iframe.style.width = "100%";
  220. iframe.style.height = "100%";
  221. iframe.style.position = "absolute";
  222. iframe.style.top = "0";
  223. iframe.style.left = "0";
  224. iframe.style.right = "0";
  225. iframe.style.bottom = "0";
  226. iframe.style.border = "0";
  227. document.getElementById("matrixchat").appendChild(iframe);
  228. });