diff --git a/README.md b/README.md index 3fea98150f..29c9d84ef6 100644 --- a/README.md +++ b/README.md @@ -109,25 +109,31 @@ You can configure the app by copying `config.sample.json` to For a good example, see https://riot.im/develop/config.json. -1. `default_server_name` sets the default server name to use for authentication. - This will trigger Riot to ask - `https:///.well-known/matrix/client` for the homeserver and - identity server URLs to use. This is the recommended approach for setting a - default server. However, it is also possible to use the following to directly - configure each of the URLs: - * `default_hs_url` sets the default homeserver URL. - * `default_is_url` sets the default identity server URL (this is the server used - for verifying third party identifiers like email addresses). If this is blank, - registering with an email address, adding an email address to your account, - or inviting users via email address will not work. Matrix identity servers are - very simple web services which map third party identifiers (currently only email - addresses) to matrix IDs: see http://matrix.org/docs/spec/identity_service/unstable.html - for more details. Currently the only public matrix identity servers are https://matrix.org - and https://vector.im. In the future, identity servers will be decentralised. - * Riot will report an error if you accidentally configure both `default_server_name` _and_ `default_hs_url` since it's unclear which should take priority. +1. `default_server_config` sets the default homeserver and identity server URL for + Riot to use. The object is the same as returned by [https:///.well-known/matrix/client](https://matrix.org/docs/spec/client_server/latest.html#get-well-known-matrix-client), + with added support for a `server_name` under the `m.homeserver` section to display + a custom homeserver name. Alternatively, the config can contain a `default_server_name` + instead which is where Riot will go to get that same object - see the `.well-known` + link above for more information. Note that the `default_server_name` is used to get + a complete server configuration whereas the `server_name` in the `default_server_config` + is for display purposes only. + * *Note*: The URLs can also be individually specified as `default_hs_url` and + `default_is_url`, however these are deprecated. They are maintained for backwards + compatibility with older configurations. `default_is_url` is respected only + if `default_hs_url` is used. + * The identity server is used for verifying third party identifiers like emails + and phone numbers. It is not used to store your password or account information. + If not provided, the identity server defaults to vector.im unless `disable_identity_server` + is set to true in the config. Currently the only two public identity servers + are https://matrix.org and https://vector.im, however in future identity servers + will be decentralised. + * Riot will fail to load if a mix of `default_server_config`, `default_server_name`, or + `default_hs_url` is specified. When multiple sources are specified, it is unclear + which should take priority and therefore the application cannot continue. 1. `features`: Lookup of optional features that may be `enable`d, `disable`d, or exposed to the user in the `labs` section of settings. The available optional experimental features vary from - release to release. + release to release. Some of the available features are described in the Labs Feature section + of this README. 1. `brand`: String to pass to your homeserver when configuring email notifications, to let the homeserver know what email template to use when talking to you. 1. `branding`: Configures various branding and logo details, such as: diff --git a/config.sample.json b/config.sample.json index c1aedee0b1..d4eb8df052 100644 --- a/config.sample.json +++ b/config.sample.json @@ -1,6 +1,14 @@ { - "default_hs_url": "https://matrix.org", - "default_is_url": "https://vector.im", + "default_server_config": { + "m.homeserver": { + "base_url": "https://matrix.org", + "server_name": "matrix.org" + }, + "m.identity_server": { + "base_url": "https://vector.im" + } + }, + "disable_identity_server": false, "disable_custom_urls": false, "disable_guests": false, "disable_login_language_selector": false, diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9c467cfc2f..10e57592b7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1,4 +1,7 @@ { + "Unexpected error preparing the app. See console for details.": "Unexpected error preparing the app. See console for details.", + "Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.": "Invalid configuration: can only specify one of default_server_config, default_server_name, or default_hs_url.", + "Invalid configuration: no default server specified.": "Invalid configuration: no default server specified.", "Riot Desktop on %(platformName)s": "Riot Desktop on %(platformName)s", "Unknown device": "Unknown device", "%(appName)s via %(browserName)s on %(osName)s": "%(appName)s via %(browserName)s on %(osName)s", @@ -15,7 +18,5 @@ "Need help?": "Need help?", "Chat with Riot Bot": "Chat with Riot Bot", "Explore rooms": "Explore rooms", - "Room Directory": "Room Directory", - "Search the room directory": "Search the room directory", - "Get started with some tips from Riot Bot!": "Get started with some tips from Riot Bot!" + "Room Directory": "Room Directory" } diff --git a/src/vector/index.js b/src/vector/index.js index 9d5c1dd422..9c5eb061ac 100644 --- a/src/vector/index.js +++ b/src/vector/index.js @@ -45,6 +45,9 @@ import VectorConferenceHandler from 'matrix-react-sdk/lib/VectorConferenceHandle import Promise from 'bluebird'; import request from 'browser-request'; import * as languageHandler from 'matrix-react-sdk/lib/languageHandler'; +import {_t, _td, newTranslatableError} from 'matrix-react-sdk/lib/languageHandler'; +import AutoDiscoveryUtils from 'matrix-react-sdk/lib/utils/AutoDiscoveryUtils'; +import {AutoDiscovery} from "matrix-js-sdk/lib/autodiscovery"; import url from 'url'; @@ -341,22 +344,37 @@ async function loadApp() { const platform = PlatformPeg.get(); platform.startUpdater(); - const MatrixChat = sdk.getComponent('structures.MatrixChat'); - window.matrixChat = ReactDOM.render( - , - document.getElementById('matrixchat'), - ); + // 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); + + const errorMessage = err.translatedMessage + || _t("Unexpected error preparing the app. See console for details."); + + // 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 @@ -428,4 +446,66 @@ async function loadLanguage() { } } +async function verifyServerConfig() { + console.log("Verifying homeserver configuration"); + + // TODO: TravisR - Handle query string arguments for hs_url and is_url + // We probably don't want to handle them unless the user is logged out though? + + 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) { + throw newTranslatableError(_td( + "Invalid configuration: can only specify one of default_server_config, default_server_name, " + + "or default_hs_url.", + )); + } + if (incompatibleOptions.length < 1) { + 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"); + + wkConfig = { + "m.homeserver": { + "base_url": hsUrl, + }, + }; + if (isUrl) { + wkConfig["m.identity_server"] = { + "base_url": isUrl, + }; + } + } + + let result = null; + + if (wkConfig) { + console.log("Config uses a default_server_config - validating object"); + result = await AutoDiscovery.fromDiscoveryConfig(wkConfig); + } + + if (serverName) { + console.log("Config uses a default_server_name - doing .well-known lookup"); + result = await AutoDiscovery.findClientConfig(serverName); + } + + const validatedConfig = AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result); + + // 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(); diff --git a/test/app-tests/joining.js b/test/app-tests/joining.js index 16b29fcb84..f348e8989f 100644 --- a/test/app-tests/joining.js +++ b/test/app-tests/joining.js @@ -37,6 +37,8 @@ const ReactDOM = require('react-dom'); const ReactTestUtils = require('react-addons-test-utils'); const expect = require('expect'); import Promise from 'bluebird'; +import {makeType} from "matrix-react-sdk/lib/utils/TypeUtils"; +import {ValidatedServerConfig} from "matrix-react-sdk/lib/utils/AutoDiscoveryUtils"; const test_utils = require('../test-utils'); const MockHttpBackend = require('matrix-mock-request'); @@ -96,8 +98,20 @@ describe('joining a room', function() { PlatformPeg.set(new WebPlatform()); + const config = { + validated_server_config: makeType(ValidatedServerConfig, { + hsUrl: HS_URL, + hsName: "TEST_ENVIRONMENT", + hsNameIsDifferent: false, // yes, we lie + isUrl: IS_URL, + identityEnabled: true, + }), + }; + const mc = ( - {throw new Error("unimplemented");}} initialScreenAfterLogin={{ screen: 'directory', diff --git a/test/app-tests/loading.js b/test/app-tests/loading.js index b2df82e6e4..8e7a7807e8 100644 --- a/test/app-tests/loading.js +++ b/test/app-tests/loading.js @@ -39,6 +39,8 @@ import dis from 'matrix-react-sdk/lib/dispatcher'; import * as test_utils from '../test-utils'; import MockHttpBackend from 'matrix-mock-request'; import {parseQs, parseQsFromFragment} from '../../src/vector/url_utils'; +import {makeType} from "matrix-react-sdk/lib/utils/TypeUtils"; +import {ValidatedServerConfig} from "matrix-react-sdk/lib/utils/AutoDiscoveryUtils"; const DEFAULT_HS_URL='http://my_server'; const DEFAULT_IS_URL='http://my_is'; @@ -146,6 +148,13 @@ describe('loading:', function() { const config = Object.assign({ default_hs_url: DEFAULT_HS_URL, default_is_url: DEFAULT_IS_URL, + validated_server_config: makeType(ValidatedServerConfig, { + hsUrl: DEFAULT_HS_URL, + hsName: "TEST_ENVIRONMENT", + hsNameIsDifferent: false, // yes, we lie + isUrl: DEFAULT_IS_URL, + identityEnabled: true, + }), embeddedPages: { homeUrl: 'data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==', }, @@ -160,6 +169,7 @@ describe('loading:', function() { component const login = ReactTestUtils.findRenderedComponentWithType( matrixChat, sdk.getComponent('structures.auth.Login')); + // When we switch to the login component, it'll hit the login endpoint + // for proof of life and to get flows. We'll only give it one option. + httpBackend.when('GET', '/login') + .respond(200, {"flows": [{"type": "m.login.password"}]}); + httpBackend.flush(); // We already would have tried the GET /login request + + // Give the component some time to finish processing the login flows before + // continuing. + await Promise.delay(100); + httpBackend.when('POST', '/login').check(function(req) { expect(req.data.type).toEqual('m.login.password'); expect(req.data.identifier.type).toEqual('m.id.user');