diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index ef0bedad27..cc96503316 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -25,6 +25,7 @@ var matrixClient = null; var localStorage = window.localStorage; function deviceId() { + // XXX: is Math.random()'s deterministicity a problem here? var id = Math.floor(Math.random()*16777215).toString(16); id = "W" + "000000".substring(id.length) + id; if (localStorage) { @@ -34,7 +35,7 @@ function deviceId() { return id; } -function createClient(hs_url, is_url, user_id, access_token, guestAccess) { +function createClientForPeg(hs_url, is_url, user_id, access_token, guestAccess) { var opts = { baseUrl: hs_url, idBaseUrl: is_url, @@ -68,7 +69,7 @@ if (localStorage) { var guestAccess = new GuestAccess(localStorage); if (access_token && user_id && hs_url) { console.log("Restoring session for %s", user_id); - createClient(hs_url, is_url, user_id, access_token, guestAccess); + createClientForPeg(hs_url, is_url, user_id, access_token, guestAccess); } else { console.log("Session not found."); @@ -91,7 +92,7 @@ class MatrixClient { // FIXME, XXX: this all seems very convoluted :( // - // if we replace the singleton using URLs we bypass our createClient() + // if we replace the singleton using URLs we bypass our createClientForPeg() // global helper function... but if we replace it using // an access_token we don't? // @@ -105,6 +106,7 @@ class MatrixClient { baseUrl: hs_url, idBaseUrl: is_url }); + // XXX: factor this out with the localStorage setting in replaceUsingAccessToken if (localStorage) { try { @@ -123,11 +125,11 @@ class MatrixClient { try { localStorage.clear(); } catch (e) { - console.warn("Error using local storage"); + console.warn("Error clearing local storage", e); } } this.guestAccess.markAsGuest(Boolean(isGuest)); - createClient(hs_url, is_url, user_id, access_token, this.guestAccess); + createClientForPeg(hs_url, is_url, user_id, access_token, this.guestAccess); if (localStorage) { try { localStorage.setItem("mx_hs_url", hs_url); @@ -136,7 +138,7 @@ class MatrixClient { localStorage.setItem("mx_access_token", access_token); console.log("Session persisted for %s", user_id); } catch (e) { - console.warn("Error using local storage: can't persist session!"); + console.warn("Error using local storage: can't persist session!", e); } } else { console.warn("No local storage available: can't persist session!"); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9b61e4b94e..df27d38172 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -85,6 +85,32 @@ module.exports = React.createClass({ }; }, + getCurrentHsUrl: function() { + if (MatrixClientPeg.get()) { + return MatrixClientPeg.get().getHomeserverUrl(); + } + else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) { + return window.localStorage.getItem("mx_hs_url"); + } + else if (this.props.config) { + return this.props.config.default_hs_url + } + return "https://matrix.org"; + }, + + getCurrentIsUrl: function() { + if (MatrixClientPeg.get()) { + return MatrixClientPeg.get().getIdentityServerUrl(); + } + else if (window.localStorage && window.localStorage.getItem("mx_is_url")) { + return window.localStorage.getItem("mx_is_url"); + } + else if (this.props.config) { + return this.props.config.default_is_url + } + return "https://matrix.org"; + }, + componentWillMount: function() { this.favicon = new Favico({animation: 'none'}); }, @@ -92,8 +118,8 @@ module.exports = React.createClass({ componentDidMount: function() { this._autoRegisterAsGuest = false; if (this.props.enableGuest) { - if (!this.props.config || !this.props.config.default_hs_url) { - console.error("Cannot enable guest access: No supplied config prop for HS/IS URLs"); + if (!this.getCurrentHsUrl()) { + console.error("Cannot enable guest access: can't determine HS URL to use"); } else if (this.props.startingQueryParams.client_secret && this.props.startingQueryParams.sid) { console.log("Not registering as guest; registration."); @@ -167,19 +193,19 @@ module.exports = React.createClass({ _registerAsGuest: function() { var self = this; - var config = this.props.config; - console.log("Doing guest login on %s", config.default_hs_url); - MatrixClientPeg.replaceUsingUrls( - config.default_hs_url, config.default_is_url - ); + console.log("Doing guest login on %s", this.getCurrentHsUrl()); + var hsUrl = this.getCurrentHsUrl(); + var isUrl = this.getCurrentIsUrl(); + + MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl); MatrixClientPeg.get().registerGuest().done(function(creds) { console.log("Registered as guest: %s", creds.user_id); self._setAutoRegisterAsGuest(false); self.onLoggedIn({ userId: creds.user_id, accessToken: creds.access_token, - homeserverUrl: config.default_hs_url, - identityServerUrl: config.default_is_url, + homeserverUrl: hsUrl, + identityServerUrl: isUrl, guest: true }); }, function(err) { @@ -201,6 +227,9 @@ module.exports = React.createClass({ case 'logout': if (window.localStorage) { window.localStorage.clear(); + // preserve our HS & IS URLs for convenience + window.localStorage.setItem("mx_hs_url", this.getCurrentHsUrl()); + window.localStorage.setItem("mx_is_url", this.getCurrentIsUrl()); } Notifier.stop(); UserActivity.stop(); @@ -744,7 +773,7 @@ module.exports = React.createClass({ } } else { - console.error("Unknown screen : %s", screen); + console.info("Ignoring showScreen for '%s'", screen); } }, @@ -908,6 +937,8 @@ module.exports = React.createClass({ var NewVersionBar = sdk.getComponent('globals.NewVersionBar'); var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword'); + // work out the HS URL prompts we should show for + // needs to be before normal PageTypes as you are logged in technically if (this.state.screen == 'post_registration') { return ( @@ -1004,26 +1035,34 @@ module.exports = React.createClass({ username={this.state.upgradeUsername} disableUsernameChanges={Boolean(this.state.upgradeUsername)} guestAccessToken={this.state.guestAccessToken} - hsUrl={this.props.config.default_hs_url} - isUrl={this.props.config.default_is_url} + defaultHsUrl={this.props.config.default_hs_url} + defaultIsUrl={this.props.config.default_is_url} + customHsUrl={this.getCurrentHsUrl()} + customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} onLoggedIn={this.onRegistered} - onLoginClick={this.onLoginClick} /> + onLoginClick={this.onLoginClick} + onRegisterClick={this.onRegisterClick} /> ); } else if (this.state.screen == 'forgot_password') { return ( + defaultHsUrl={this.props.config.default_hs_url} + defaultIsUrl={this.props.config.default_is_url} + customHsUrl={this.getCurrentHsUrl()} + customIsUrl={this.getCurrentIsUrl()} + onComplete={this.onLoginClick} + onLoginClick={this.onLoginClick} /> ); } else { return ( diff --git a/src/components/structures/login/ForgotPassword.js b/src/components/structures/login/ForgotPassword.js index dcf6a7c28e..c457ce1ead 100644 --- a/src/components/structures/login/ForgotPassword.js +++ b/src/components/structures/login/ForgotPassword.js @@ -27,8 +27,12 @@ module.exports = React.createClass({ displayName: 'ForgotPassword', propTypes: { - homeserverUrl: React.PropTypes.string, - identityServerUrl: React.PropTypes.string, + defaultHsUrl: React.PropTypes.string, + defaultIsUrl: React.PropTypes.string, + customHsUrl: React.PropTypes.string, + customIsUrl: React.PropTypes.string, + onLoginClick: React.PropTypes.func, + onRegisterClick: React.PropTypes.func, onComplete: React.PropTypes.func.isRequired }, @@ -152,8 +156,9 @@ module.exports = React.createClass({ else { resetPasswordJsx = (
- To reset your password, enter the email address linked to your account: -
+
+ To reset your password, enter the email address linked to your account: +
+
+
+ + Return to login + + + Create a new account +
diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index ef6b095da0..1b881187f8 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -30,28 +30,29 @@ var ServerConfig = require("../../views/login/ServerConfig"); module.exports = React.createClass({displayName: 'Login', propTypes: { onLoggedIn: React.PropTypes.func.isRequired, - homeserverUrl: React.PropTypes.string, - identityServerUrl: React.PropTypes.string, + + customHsUrl: React.PropTypes.string, + customIsUrl: React.PropTypes.string, + defaultHsUrl: React.PropTypes.string, + defaultIsUrl: React.PropTypes.string, + // login shouldn't know or care how registration is done. onRegisterClick: React.PropTypes.func.isRequired, + // login shouldn't care how password recovery is done. onForgotPasswordClick: React.PropTypes.func, onLoginAsGuestClick: React.PropTypes.func, }, - getDefaultProps: function() { - return { - homeserverUrl: 'https://matrix.org/', - identityServerUrl: 'https://matrix.org' - }; - }, - getInitialState: function() { return { busy: false, errorText: null, - enteredHomeserverUrl: this.props.homeserverUrl, - enteredIdentityServerUrl: this.props.identityServerUrl + enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl, + enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl, + + // used for preserving username when changing homeserver + username: "", }; }, @@ -76,12 +77,26 @@ module.exports = React.createClass({displayName: 'Login', }); }, + onUsernameChanged: function(username) { + this.setState({ username: username }); + }, + onHsUrlChanged: function(newHsUrl) { - this._initLoginLogic(newHsUrl); + var self = this; + this.setState({ + enteredHomeserverUrl: newHsUrl + }, function() { + self._initLoginLogic(newHsUrl); + }); }, onIsUrlChanged: function(newIsUrl) { - this._initLoginLogic(null, newIsUrl); + var self = this; + this.setState({ + enteredIdentityServerUrl: newIsUrl + }, function() { + self._initLoginLogic(null, newIsUrl); + }); }, _initLoginLogic: function(hsUrl, isUrl) { @@ -162,6 +177,8 @@ module.exports = React.createClass({displayName: 'Login', return ( ); case 'm.login.cas': @@ -203,8 +220,10 @@ module.exports = React.createClass({displayName: 'Login', { this.componentForStep(this._getCurrentFlowStep()) } @@ -216,7 +235,6 @@ module.exports = React.createClass({displayName: 'Login', Create a new account { loginAsGuestJsx } -
diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 9ec379a814..cf9adee1bb 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -36,8 +36,10 @@ module.exports = React.createClass({ sessionId: React.PropTypes.string, registrationUrl: React.PropTypes.string, idSid: React.PropTypes.string, - hsUrl: React.PropTypes.string, - isUrl: React.PropTypes.string, + customHsUrl: React.PropTypes.string, + customIsUrl: React.PropTypes.string, + defaultHsUrl: React.PropTypes.string, + defaultIsUrl: React.PropTypes.string, email: React.PropTypes.string, username: React.PropTypes.string, guestAccessToken: React.PropTypes.string, @@ -50,8 +52,6 @@ module.exports = React.createClass({ return { busy: false, errorText: null, - enteredHomeserverUrl: this.props.hsUrl, - enteredIdentityServerUrl: this.props.isUrl }; }, @@ -59,7 +59,7 @@ module.exports = React.createClass({ this.dispatcherRef = dis.register(this.onAction); // attach this to the instance rather than this.state since it isn't UI this.registerLogic = new Signup.Register( - this.props.hsUrl, this.props.isUrl + this.props.customHsUrl, this.props.customIsUrl ); this.registerLogic.setClientSecret(this.props.clientSecret); this.registerLogic.setSessionId(this.props.sessionId); @@ -242,11 +242,15 @@ module.exports = React.createClass({ {busySpinner} +
+
I already have an account @@ -256,11 +260,13 @@ module.exports = React.createClass({ render: function() { var LoginHeader = sdk.getComponent('login.LoginHeader'); + var LoginFooter = sdk.getComponent('login.LoginFooter'); return (
{this._getRegisterContentJsx()} +
); diff --git a/src/components/views/login/CustomServerDialog.js b/src/components/views/login/CustomServerDialog.js index dc6a49abd6..92b6c54ac1 100644 --- a/src/components/views/login/CustomServerDialog.js +++ b/src/components/views/login/CustomServerDialog.js @@ -31,12 +31,11 @@ module.exports = React.createClass({ servers by specifying a different Home server URL.
This allows you to use this app with an existing Matrix account on - a different Home server. + a different home server.

- You can also set a custom Identity server but this will affect - people's ability to find you if you use a server in a group other - than the main Matrix.org group. + You can also set a custom identity server but this will typically prevent + interaction with users based on email address.
diff --git a/src/components/views/login/PasswordLogin.js b/src/components/views/login/PasswordLogin.js index a8751da1a7..c5474f60c1 100644 --- a/src/components/views/login/PasswordLogin.js +++ b/src/components/views/login/PasswordLogin.js @@ -23,13 +23,26 @@ var ReactDOM = require('react-dom'); module.exports = React.createClass({displayName: 'PasswordLogin', propTypes: { onSubmit: React.PropTypes.func.isRequired, // fn(username, password) - onForgotPasswordClick: React.PropTypes.func // fn() + onForgotPasswordClick: React.PropTypes.func, // fn() + initialUsername: React.PropTypes.string, + initialPassword: React.PropTypes.string, + onUsernameChanged: React.PropTypes.func, + onPasswordChanged: React.PropTypes.func, + }, + + getDefaultProps: function() { + return { + onUsernameChanged: function() {}, + onPasswordChanged: function() {}, + initialUsername: "", + initialPassword: "", + }; }, getInitialState: function() { return { - username: "", - password: "" + username: this.props.initialUsername, + password: this.props.initialPassword, }; }, @@ -40,10 +53,12 @@ module.exports = React.createClass({displayName: 'PasswordLogin', onUsernameChanged: function(ev) { this.setState({username: ev.target.value}); + this.props.onUsernameChanged(ev.target.value); }, onPasswordChanged: function(ev) { this.setState({password: ev.target.value}); + this.props.onPasswordChanged(ev.target.value); }, render: function() { diff --git a/src/components/views/login/ServerConfig.js b/src/components/views/login/ServerConfig.js index fe80a0d61b..634bb4aac2 100644 --- a/src/components/views/login/ServerConfig.js +++ b/src/components/views/login/ServerConfig.js @@ -29,8 +29,21 @@ module.exports = React.createClass({ propTypes: { onHsUrlChanged: React.PropTypes.func, onIsUrlChanged: React.PropTypes.func, - defaultHsUrl: React.PropTypes.string, - defaultIsUrl: React.PropTypes.string, + + // default URLs are defined in config.json (or the hardcoded defaults) + // they are used if the user has not overridden them with a custom URL. + // In other words, if the custom URL is blank, the default is used. + defaultHsUrl: React.PropTypes.string, // e.g. https://matrix.org + defaultIsUrl: React.PropTypes.string, // e.g. https://vector.im + + // custom URLs are explicitly provided by the user and override the + // default URLs. The user enters them via the component's input fields, + // which is reflected on these properties whenever on..UrlChanged fires. + // They are persisted in localStorage by MatrixClientPeg, and so can + // override the default URLs when the component initially loads. + customHsUrl: React.PropTypes.string, + customIsUrl: React.PropTypes.string, + withToggleButton: React.PropTypes.bool, delayTimeMs: React.PropTypes.number // time to wait before invoking onChanged }, @@ -39,6 +52,8 @@ module.exports = React.createClass({ return { onHsUrlChanged: function() {}, onIsUrlChanged: function() {}, + customHsUrl: "", + customIsUrl: "", withToggleButton: false, delayTimeMs: 0 }; @@ -46,19 +61,21 @@ module.exports = React.createClass({ getInitialState: function() { return { - hs_url: this.props.defaultHsUrl, - is_url: this.props.defaultIsUrl, - original_hs_url: this.props.defaultHsUrl, - original_is_url: this.props.defaultIsUrl, - // no toggle button = show, toggle button = hide - configVisible: !this.props.withToggleButton + hs_url: this.props.customHsUrl, + is_url: this.props.customIsUrl, + // if withToggleButton is false, then show the config all the time given we have no way otherwise of making it visible + configVisible: !this.props.withToggleButton || + (this.props.customHsUrl !== this.props.defaultHsUrl) || + (this.props.customIsUrl !== this.props.defaultIsUrl) } }, onHomeserverChanged: function(ev) { this.setState({hs_url: ev.target.value}, function() { this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() { - this.props.onHsUrlChanged(this.state.hs_url.replace(/\/$/, "")); + var hsUrl = this.state.hs_url.trim().replace(/\/$/, ""); + if (hsUrl === "") hsUrl = this.props.defaultHsUrl; + this.props.onHsUrlChanged(hsUrl); }); }); }, @@ -66,7 +83,9 @@ module.exports = React.createClass({ onIdentityServerChanged: function(ev) { this.setState({is_url: ev.target.value}, function() { this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() { - this.props.onIsUrlChanged(this.state.is_url.replace(/\/$/, "")); + var isUrl = this.state.is_url.trim().replace(/\/$/, ""); + if (isUrl === "") isUrl = this.props.defaultIsUrl; + this.props.onIsUrlChanged(isUrl); }); }); }, @@ -78,18 +97,18 @@ module.exports = React.createClass({ return setTimeout(fn.bind(this), this.props.delayTimeMs); }, - getHsUrl: function() { - return this.state.hs_url; - }, - - getIsUrl: function() { - return this.state.is_url; - }, - - onServerConfigVisibleChange: function(ev) { + onServerConfigVisibleChange: function(visible, ev) { this.setState({ - configVisible: ev.target.checked + configVisible: visible }); + if (!visible) { + this.props.onHsUrlChanged(this.props.defaultHsUrl); + this.props.onIsUrlChanged(this.props.defaultIsUrl); + } + else { + this.props.onHsUrlChanged(this.state.hs_url); + this.props.onIsUrlChanged(this.state.is_url); + } }, showHelpPopup: function() { @@ -104,12 +123,19 @@ module.exports = React.createClass({ var toggleButton; if (this.props.withToggleButton) { toggleButton = ( -
- + + +    + + onChange={this.onServerConfigVisibleChange.bind(this, true)} />
); @@ -124,14 +150,14 @@ module.exports = React.createClass({ Home server URL