From 922ed597d668cd17aacb04c342c4698297dddaf0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 16:55:07 +0000 Subject: [PATCH 01/13] Fix webpack to babel js-sdk & react-sdk but no other deps This was happening implicitly in our dev setups and the CI build because of the comment on the last line. --- webpack.config.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 255f0ce2..f0dd47f7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -109,7 +109,23 @@ module.exports = (env, argv) => { rules: [ { test: /\.(ts|js)x?$/, - exclude: /node_modules/, + include: (f) => { + // we use the original source files of react-sdk and js-sdk, so we need to + // run them through babel. + if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-js-sdk'))) return true; + if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-react-sdk'))) return true; + // but we can't run all of our dependencies through babel (many of them still + // use module.exports which breaks if babel injects an 'include' for its + // polyfills: probably fixable but babeling all our dependencies is probably + // not necessary anyway). + if (f.startsWith(path.resolve(__dirname, 'node_modules'))) return false; + // anything else gets babeled (our own source files, and also modules that + // are yarn linked from somewhere else because this tests the absolute, + // resolved path, so react-sdk and js-sdk fall under this case in a standard + // dev setup. This will presumably start running any other module through + // babel if yarn linked... caveat emptor. + return true; + }, loader: 'babel-loader', options: { cacheDirectory: true From 5d8d5d70d05f7c1d0a281bb1ba96ec482db82676 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 18:00:38 +0000 Subject: [PATCH 02/13] Fix build to not babel modules inside js/react sdk Adds 'src' to react-sdk & js-sdk babel test path so we don't run node modules inside js & react sdk through babel --- webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index f0dd47f7..01d38339 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -112,8 +112,8 @@ module.exports = (env, argv) => { include: (f) => { // we use the original source files of react-sdk and js-sdk, so we need to // run them through babel. - if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-js-sdk'))) return true; - if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-react-sdk'))) return true; + if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-js-sdk', 'src'))) return true; + if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-react-sdk', 'src'))) return true; // but we can't run all of our dependencies through babel (many of them still // use module.exports which breaks if babel injects an 'include' for its // polyfills: probably fixable but babeling all our dependencies is probably From 42743c3ead17ff92a693fab2f233f731428c37a1 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 20 Jan 2020 18:20:41 +0000 Subject: [PATCH 03/13] Third try at fixing build --- webpack.config.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 01d38339..95087f7d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -110,21 +110,21 @@ module.exports = (env, argv) => { { test: /\.(ts|js)x?$/, include: (f) => { + // our own source needs babel-ing + if (f.startsWith(path.resolve(__dirname, 'src'))) return true; + // we use the original source files of react-sdk and js-sdk, so we need to - // run them through babel. - if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-js-sdk', 'src'))) return true; - if (f.startsWith(path.resolve(__dirname, 'node_modules', 'matrix-react-sdk', 'src'))) return true; + // run them through babel. Because the path tested is the resolved, absolute + // path, these could be anywhere thanks to yarn link. We must also not + // include node modules inside these modules, so we add 'src'. + if (f.includes(path.join('matrix-js-sdk', 'src'))) return true; + if (f.includes(path.join('matrix-react-sdk', 'src'))) return true; + // but we can't run all of our dependencies through babel (many of them still // use module.exports which breaks if babel injects an 'include' for its // polyfills: probably fixable but babeling all our dependencies is probably - // not necessary anyway). - if (f.startsWith(path.resolve(__dirname, 'node_modules'))) return false; - // anything else gets babeled (our own source files, and also modules that - // are yarn linked from somewhere else because this tests the absolute, - // resolved path, so react-sdk and js-sdk fall under this case in a standard - // dev setup. This will presumably start running any other module through - // babel if yarn linked... caveat emptor. - return true; + // not necessary anyway). So, for anything else, don't babel. + return false; }, loader: 'babel-loader', options: { From 5b575d5627b30442ee8c69d8cb48a3a7f8137816 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 14:28:29 -0700 Subject: [PATCH 04/13] Force Jest to resolve the js-sdk and react-sdk to src directories --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c878557..9c6f3a70 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,9 @@ "\\$webapp/i18n/languages.json": "/node_modules/matrix-react-sdk/__mocks__/languages.json", "^browser-request$": "/node_modules/matrix-react-sdk/__mocks__/browser-request.js", "^react$": "/node_modules/react", - "^react-dom$": "/node_modules/react-dom" + "^react-dom$": "/node_modules/react-dom", + "^matrix-js-sdk$": "/node_modules/matrix-js-sdk/src", + "^matrix-react-sdk$": "/node_modules/matrix-react-sdk/src" }, "transformIgnorePatterns": [ "/node_modules/(?!matrix-js-sdk).+$", From 932c221548003b5e4dceee5d5ace6b27b549a0ae Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 18:09:48 -0700 Subject: [PATCH 05/13] Fix webpack config (by stealing Dave's config) Without doing something like this it's hard to use `yarn link`ed resources. --- webpack.config.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 95087f7d..ae92e815 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,12 @@ module.exports = (env, argv) => { development['devtool'] = 'eval-source-map'; } + // Resolve the directories for the react-sdk and js-sdk for later use. We resolve these early so we + // don't have to call them over and over. We also resolve to the package.json instead of the src + // directory so we don't have to rely on a index.js or similar file existing. + const reactSdkSrcDir = path.resolve(path.join(require.resolve("matrix-react-sdk/package.json"), '..', 'src')); + const jsSdkSrcDir = path.resolve(path.join(require.resolve("matrix-js-sdk/package.json"), '..', 'src')); + return { ...development, @@ -117,8 +123,8 @@ module.exports = (env, argv) => { // run them through babel. Because the path tested is the resolved, absolute // path, these could be anywhere thanks to yarn link. We must also not // include node modules inside these modules, so we add 'src'. - if (f.includes(path.join('matrix-js-sdk', 'src'))) return true; - if (f.includes(path.join('matrix-react-sdk', 'src'))) return true; + if (f.startsWith(reactSdkSrcDir)) return true; + if (f.startsWith(jsSdkSrcDir)) return true; // but we can't run all of our dependencies through babel (many of them still // use module.exports which breaks if babel injects an 'include' for its From 99e1ff9477a7bcf237e00fd612e4d8a07edf612a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 20 Jan 2020 19:52:11 -0700 Subject: [PATCH 06/13] 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 = ( +
+

+ {_t( + "Your Riot configuration contains invalid JSON. Please correct the problem " + + "and reload the page.", + )} +

+

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

+
+ ); + + const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage"); + window.matrixChat = ReactDOM.render( + , + 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(
+ Unable to load config file: please refresh the page to try again. +
, 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( + , + document.getElementById('matrixchat'), + ); + }).catch(err => { + console.error(err); + + let errorMessage = err.translatedMessage + || _t("Unexpected error preparing the app. See console for details."); + errorMessage = {errorMessage}; + + // Like the compatibility page, AWOOOOOGA at the user + const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage"); + window.matrixChat = ReactDOM.render( + , + 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( + , + 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 = ( -
-

- {_t( - "Your Riot configuration contains invalid JSON. Please correct the problem " + - "and reload the page.", - )} -

-

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

-
- ); - - const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage"); - window.matrixChat = ReactDOM.render( - , - 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(
- Unable to load config file: please refresh the page to try again. -
, 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( - , - document.getElementById('matrixchat'), - ); - }).catch(err => { - console.error(err); - - let errorMessage = err.translatedMessage - || _t("Unexpected error preparing the app. See console for details."); - errorMessage = {errorMessage}; - - // Like the compatibility page, AWOOOOOGA at the user - const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage"); - window.matrixChat = ReactDOM.render( - , - 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( - , - 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(); From 6835f6054b8b331d6ee1e4f2b2cded5f94d03be9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 21 Jan 2020 10:16:32 +0000 Subject: [PATCH 07/13] path.resolve does joining too so path.join is redundant --- webpack.config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index ae92e815..40e902b1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,8 +24,8 @@ module.exports = (env, argv) => { // Resolve the directories for the react-sdk and js-sdk for later use. We resolve these early so we // don't have to call them over and over. We also resolve to the package.json instead of the src // directory so we don't have to rely on a index.js or similar file existing. - const reactSdkSrcDir = path.resolve(path.join(require.resolve("matrix-react-sdk/package.json"), '..', 'src')); - const jsSdkSrcDir = path.resolve(path.join(require.resolve("matrix-js-sdk/package.json"), '..', 'src')); + const reactSdkSrcDir = path.resolve(require.resolve("matrix-react-sdk/package.json"), '..', 'src'); + const jsSdkSrcDir = path.resolve(require.resolve("matrix-js-sdk/package.json"), '..', 'src'); return { ...development, From fe15d3b7c1f50c00276ff6e65d74055a81dc43c2 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 22 Jan 2020 14:18:26 +0000 Subject: [PATCH 08/13] Add docs for admin report content message Part of https://github.com/vector-im/riot-web/issues/11992 --- docs/config.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/config.md b/docs/config.md index 7fa7774e..5a252deb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -37,6 +37,10 @@ For a good example, see https://riot.im/develop/config.json. authentication flows 1. `authFooterLinks`: a list of links to show in the authentication page footer: `[{"text": "Link text", "url": "https://link.target"}, {"text": "Other link", ...}]` +1. `reportEvent`: Configures the dialog for reporting content to the homeserver + admin. + 1. `adminMessageMD`: An extra message to show on the reporting dialog to + mention homeserver-specific policies. Accepts Markdown. 1. `integrations_ui_url`: URL to the web interface for the integrations server. The integrations server is not Riot and normally not your homeserver either. The integration server settings may be left blank to disable integrations. From 5d95a3ef7e1ad99861afbbf9c8df26857b001bbb Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 15:04:31 +0000 Subject: [PATCH 09/13] Only deploy to /develop if everything else passed --- .buildkite/pipeline.yaml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 6f383d8b..573a2877 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -52,19 +52,6 @@ steps: - docker#v3.0.1: image: "node:10" - - label: ":hammer: Package" - command: - - "echo '--- Fetching Dependencies'" - - "./scripts/fetch-develop.deps.sh --depth 1" - - "yarn install" - - "echo '+++ Packaging'" - - "./scripts/ci_package.sh" - branches: "develop" - artifact_paths: "dist/riot-*.tar.gz" - plugins: - - docker#v3.0.1: - image: "node:10" - - label: "🌐 i18n" command: - "echo '--- Fetching Dependencies'" @@ -75,3 +62,19 @@ steps: plugins: - docker#v3.0.1: image: "node:10" + + - wait: ~ # this wait is to perform deploy to /develop only if all other steps passed + continue_on_failure: false + + - label: ":hammer: Package" + command: + - "echo '--- Fetching Dependencies'" + - "./scripts/fetch-develop.deps.sh --depth 1" + - "yarn install" + - "echo '+++ Packaging'" + - "./scripts/ci_package.sh" + branches: "develop" + artifact_paths: "dist/riot-*.tar.gz" + plugins: + - docker#v3.0.1: + image: "node:10" From 0798945109a6c54813a7823df17a418fea4abb8a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 22 Jan 2020 15:49:00 +0000 Subject: [PATCH 10/13] Update cookie policy urls on /app and /develop config.json --- riot.im/app/config.json | 2 +- riot.im/develop/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/riot.im/app/config.json b/riot.im/app/config.json index 3fbdbc99..7ba61fd1 100644 --- a/riot.im/app/config.json +++ b/riot.im/app/config.json @@ -18,7 +18,7 @@ "piwik": { "url": "https://piwik.riot.im/", "siteId": 1, - "policyUrl": "https://matrix.org/docs/guides/riot_im_cookie_policy" + "policyUrl": "https://matrix.org/legal/riot-im-cookie-policy" }, "roomDirectory": { "servers": [ diff --git a/riot.im/develop/config.json b/riot.im/develop/config.json index 98e85602..c3e77f5e 100644 --- a/riot.im/develop/config.json +++ b/riot.im/develop/config.json @@ -30,7 +30,7 @@ "piwik": { "url": "https://piwik.riot.im/", "siteId": 1, - "policyUrl": "https://matrix.org/docs/guides/riot_im_cookie_policy" + "policyUrl": "https://matrix.org/legal/riot-im-cookie-policy" }, "roomDirectory": { "servers": [ From 51a97571a002a0650c66e645c68ac39d0b946414 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 09:08:05 -0700 Subject: [PATCH 11/13] Use debian to build the Docker image Fixes not having certain OS capabilities to build some packages. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a180be10..8d83af0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Builder -FROM node:10-alpine as builder +FROM node:10 as builder # Support custom branches of the react-sdk and js-sdk. This also helps us build # images of riot-web develop. @@ -9,7 +9,7 @@ ARG REACT_SDK_BRANCH="master" ARG JS_SDK_REPO="https://github.com/matrix-org/matrix-js-sdk.git" ARG JS_SDK_BRANCH="master" -RUN apk add --no-cache git dos2unix +RUN apt-get update && apt-get install -y git dos2unix WORKDIR /src From b6963d0e5cdaed673345ac9c1ab196741cb23eb4 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 22 Jan 2020 17:44:18 +0000 Subject: [PATCH 12/13] Use bash in Docker scripts Our Docker scripts use Bash-style conditionals that aren't supported by the default Debian shell. --- Dockerfile | 4 ++-- scripts/docker-link-repos.sh | 2 +- scripts/docker-write-version.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8d83af0a..8f584b81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y git dos2unix WORKDIR /src COPY . /src -RUN dos2unix /src/scripts/docker-link-repos.sh && sh /src/scripts/docker-link-repos.sh +RUN dos2unix /src/scripts/docker-link-repos.sh && bash /src/scripts/docker-link-repos.sh RUN yarn --network-timeout=100000 install RUN yarn build @@ -22,7 +22,7 @@ RUN yarn build RUN cp /src/config.sample.json /src/webapp/config.json # Ensure we populate the version file -RUN dos2unix /src/scripts/docker-write-version.sh && sh /src/scripts/docker-write-version.sh +RUN dos2unix /src/scripts/docker-write-version.sh && bash /src/scripts/docker-write-version.sh # App diff --git a/scripts/docker-link-repos.sh b/scripts/docker-link-repos.sh index 66bd9842..b35ce89c 100644 --- a/scripts/docker-link-repos.sh +++ b/scripts/docker-link-repos.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -ex diff --git a/scripts/docker-write-version.sh b/scripts/docker-write-version.sh index 730f47af..774bb49c 100644 --- a/scripts/docker-write-version.sh +++ b/scripts/docker-write-version.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -ex From 47d88d6b792230ab77806b00414da45d591fcbd5 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 22 Jan 2020 11:55:09 -0700 Subject: [PATCH 13/13] Fix the remainder of the cookie links Fixes https://github.com/vector-im/riot-web/issues/10362 Per https://github.com/vector-im/riot-web/pull/11998#issuecomment-577329852 --- electron_app/riot.im/config.json | 2 +- riot.im/app/config.json | 2 +- riot.im/develop/config.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electron_app/riot.im/config.json b/electron_app/riot.im/config.json index cedb4ccd..9a98daec 100644 --- a/electron_app/riot.im/config.json +++ b/electron_app/riot.im/config.json @@ -22,7 +22,7 @@ "piwik": { "url": "https://piwik.riot.im/", "siteId": 1, - "policyUrl": "https://matrix.org/docs/guides/riot_im_cookie_policy" + "policyUrl": "https://matrix.org/legal/riot-im-cookie-policy" }, "phasedRollOut": { "feature_lazyloading": { diff --git a/riot.im/app/config.json b/riot.im/app/config.json index 7ba61fd1..bd03e237 100644 --- a/riot.im/app/config.json +++ b/riot.im/app/config.json @@ -35,7 +35,7 @@ "text": "Privacy Policy" }, { - "url": "https://matrix.org/docs/guides/riot_im_cookie_policy", + "url": "https://matrix.org/legal/riot-im-cookie-policy", "text": "Cookie Policy" } ] diff --git a/riot.im/develop/config.json b/riot.im/develop/config.json index c3e77f5e..3614243d 100644 --- a/riot.im/develop/config.json +++ b/riot.im/develop/config.json @@ -47,7 +47,7 @@ "text": "Privacy Policy" }, { - "url": "https://matrix.org/docs/guides/riot_im_cookie_policy", + "url": "https://matrix.org/legal/riot-im-cookie-policy", "text": "Cookie Policy" } ]