From db9750a7e322757e5bc9318bc33e756e12827a64 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 14:04:20 +0100 Subject: [PATCH 01/17] Call the logout API when we log out Also try to refactor some of the login/logout code out of MatrixChat and into a separate Lifecycle.js. This still isn't great, but it at least gets some code out of MatrixClient. --- src/CallHandler.js | 7 +- src/Lifecycle.js | 98 +++++++++++++++++++ src/MatrixClientPeg.js | 22 +++-- src/components/structures/MatrixChat.js | 121 ++++++++++++------------ 4 files changed, 176 insertions(+), 72 deletions(-) create mode 100644 src/Lifecycle.js diff --git a/src/CallHandler.js b/src/CallHandler.js index c459d12e31..5bd2d20ae8 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -181,11 +181,11 @@ function _onAction(payload) { console.error("Unknown conf call type: %s", payload.type); } } - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); switch (payload.action) { case 'place_call': if (module.exports.getAnyActiveCall()) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Existing Call", description: "You are already in a call." @@ -195,6 +195,7 @@ function _onAction(payload) { // if the runtime env doesn't do VoIP, whine. if (!MatrixClientPeg.get().supportsVoip()) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "VoIP is unsupported", description: "You cannot place VoIP calls in this browser." @@ -210,7 +211,7 @@ function _onAction(payload) { var members = room.getJoinedMembers(); if (members.length <= 1) { - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { description: "You cannot place a call with yourself." }); @@ -236,11 +237,13 @@ function _onAction(payload) { case 'place_conference_call': console.log("Place conference call in %s", payload.room_id); if (!ConferenceHandler) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { description: "Conference calls are not supported in this client" }); } else if (!MatrixClientPeg.get().supportsVoip()) { + const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "VoIP is unsupported", description: "You cannot place VoIP calls in this browser." diff --git a/src/Lifecycle.js b/src/Lifecycle.js new file mode 100644 index 0000000000..86fa39cb51 --- /dev/null +++ b/src/Lifecycle.js @@ -0,0 +1,98 @@ +/* +Copyright 2015, 2016 OpenMarket Ltd + +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 MatrixClientPeg from './MatrixClientPeg'; +import Notifier from './Notifier' +import UserActivity from './UserActivity'; +import Presence from './Presence'; +import dis from './dispatcher'; + +function login(credentials, options) { + credentials.guest = Boolean(credentials.guest); + console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); + MatrixClientPeg.replaceUsingAccessToken( + credentials.homeserverUrl, credentials.identityServerUrl, + credentials.userId, credentials.accessToken, credentials.guest + ); + + dis.dispatch({action: 'on_logged_in'}); + + startMatrixClient(options); +} + +function logout() { + if (MatrixClientPeg.get().isGuest()) { + // logout doesn't work for guest sessions + // Also we sometimes want to re-log in a guest session + // if we abort the login + _onLoggedOut(); + return; + } + + return MatrixClientPeg.get().logout().then(_onLoggedOut, + // Just throwing an error here is going to be very unhelpful + // if you're trying to log out because your server's down and + // you want to log into a different server, so just forget the + // access token. It's annoying that this will leave the access + // token still valid, but we should fix this by having access + // tokens expire (and if you really think you've been compromised, + // change your password). + _onLoggedOut + ); +} + +function startMatrixClient(options) { + // dispatch this before starting the matrix client: it's used + // to add listeners for the 'sync' event so otherwise we'd have + // a race condition (and we need to dispatch synchronously for this + // to work). + dis.dispatch({action: 'will_start_client'}, true); + + Notifier.start(); + UserActivity.start(); + Presence.start(); + MatrixClientPeg.get().startClient(MatrixClientPeg.opts); +} + +function _onLoggedOut() { + if (window.localStorage) { + const hsUrl = window.localStorage.getItem("mx_hs_url"); + const isUrl = window.localStorage.getItem("mx_is_url"); + window.localStorage.clear(); + // preserve our HS & IS URLs for convenience + // N.B. we cache them in hsUrl/isUrl and can't really inline them + // as getCurrentHsUrl() may call through to localStorage. + if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl); + if (isUrl) window.localStorage.setItem("mx_is_url", isUrl); + } + _stopMatrixClient(); + + dis.dispatch({action: 'on_logged_out'}); +} + +// stop all the background processes related to the current client +function _stopMatrixClient() { + Notifier.stop(); + UserActivity.stop(); + Presence.stop(); + MatrixClientPeg.get().stopClient(); + MatrixClientPeg.get().removeAllListeners(); + MatrixClientPeg.unset(); +} + +module.exports = { + login, logout, startMatrixClient +}; diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index ce4b5ba743..96eb95de64 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -40,6 +40,14 @@ function deviceId() { class MatrixClientPeg { constructor() { this.matrixClient = null; + + // These are the default options used when Lifecycle.js + // starts the client. These can be altered when the + // 'will_start_client' event is dispatched. + this.opts = { + pendingEventOrdering: "detached", + initialSyncLimit: 20, + }; } get(): MatrixClient { @@ -96,13 +104,13 @@ class MatrixClientPeg { } getCredentials() { - return [ - this.matrixClient.baseUrl, - this.matrixClient.idBaseUrl, - this.matrixClient.credentials.userId, - this.matrixClient.getAccessToken(), - this.matrixClient.isGuest(), - ]; + return { + homeserverUrl: this.matrixClient.baseUrl, + identityServerUrl: this.matrixClient.idBaseUrl, + userId: this.matrixClient.credentials.userId, + accessToken: this.matrixClient.getAccessToken(), + guest: this.matrixClient.isGuest(), + }; } tryRestore() { diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index dc9ca08e94..a4f024efef 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -36,6 +36,7 @@ var sdk = require('../../index'); var MatrixTools = require('../../MatrixTools'); var linkifyMatrix = require("../../linkify-matrix"); var KeyCode = require('../../KeyCode'); +var Lifecycle = require('../../Lifecycle'); var createRoom = require("../../createRoom"); @@ -140,6 +141,7 @@ module.exports = React.createClass({ componentWillMount: function() { this.favicon = new Favico({animation: 'none'}); + this.guestCreds = null; }, componentDidMount: function() { @@ -156,7 +158,7 @@ module.exports = React.createClass({ this.props.startingQueryParams.guest_access_token) { this._autoRegisterAsGuest = false; - this.onLoggedIn({ + this._onHaveCredentials({ userId: this.props.startingQueryParams.guest_user_id, accessToken: this.props.startingQueryParams.guest_access_token, homeserverUrl: this.getDefaultHsUrl(), @@ -174,7 +176,7 @@ module.exports = React.createClass({ // Don't auto-register as a guest. This applies if you refresh the page on a // logged in client THEN hit the Sign Out button. this._autoRegisterAsGuest = false; - this.startMatrixClient(); + Lifecycle.startMatrixClient(); } this.focusComposer = false; // scrollStateMap is a map from room id to the scroll state returned by @@ -229,7 +231,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().registerGuest().done(function(creds) { console.log("Registered as guest: %s", creds.user_id); self._setAutoRegisterAsGuest(false); - self.onLoggedIn({ + self._onHaveCredentials({ userId: creds.user_id, accessToken: creds.access_token, homeserverUrl: hsUrl, @@ -260,34 +262,10 @@ module.exports = React.createClass({ var self = this; switch (payload.action) { case 'logout': - var guestCreds; if (MatrixClientPeg.get().isGuest()) { - guestCreds = { // stash our guest creds so we can backout if needed - userId: MatrixClientPeg.get().credentials.userId, - accessToken: MatrixClientPeg.get().getAccessToken(), - homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(), - identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(), - guest: true - } + this.guestCreds = MatrixClientPeg.getCredentials(); } - - if (window.localStorage) { - var hsUrl = this.getCurrentHsUrl(); - var isUrl = this.getCurrentIsUrl(); - window.localStorage.clear(); - // preserve our HS & IS URLs for convenience - // N.B. we cache them in hsUrl/isUrl and can't really inline them - // as getCurrentHsUrl() may call through to localStorage. - window.localStorage.setItem("mx_hs_url", hsUrl); - window.localStorage.setItem("mx_is_url", isUrl); - } - this._stopMatrixClient(); - this.notifyNewScreen('login'); - this.replaceState({ - logged_in: false, - ready: false, - guestCreds: guestCreds, - }); + Lifecycle.logout(); break; case 'start_registration': var newState = payload.params || {}; @@ -313,7 +291,6 @@ module.exports = React.createClass({ if (this.state.logged_in) return; this.replaceState({ screen: 'login', - guestCreds: this.state.guestCreds, }); this.notifyNewScreen('login'); break; @@ -323,17 +300,14 @@ module.exports = React.createClass({ }); break; case 'start_upgrade_registration': + // stash our guest creds so we can backout if needed + if (MatrixClientPeg.get().isGuest()) { + this.guestCreds = MatrixClientPeg.getCredentials(); + } this.replaceState({ screen: "register", upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), guestAccessToken: MatrixClientPeg.get().getAccessToken(), - guestCreds: { // stash our guest creds so we can backout if needed - userId: MatrixClientPeg.get().credentials.userId, - accessToken: MatrixClientPeg.get().getAccessToken(), - homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(), - identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(), - guest: true - } }); this.notifyNewScreen('register'); break; @@ -482,6 +456,15 @@ module.exports = React.createClass({ middleOpacity: payload.middleOpacity, }); break; + case 'on_logged_in': + this._onLoggedIn(); + break; + case 'on_logged_out': + this._onLoggedOut(); + break; + case 'will_start_client': + this._onWillStartClient(); + break; } }, @@ -592,23 +575,40 @@ module.exports = React.createClass({ this.scrollStateMap[roomId] = state; }, - onLoggedIn: function(credentials) { - credentials.guest = Boolean(credentials.guest); - console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); - MatrixClientPeg.replaceUsingAccessToken( - credentials.homeserverUrl, credentials.identityServerUrl, - credentials.userId, credentials.accessToken, credentials.guest - ); - this.setState({ - screen: undefined, - logged_in: true + _doLogin(creds) { + Lifecycle.login(creds, { + syncTimelineLimit: this.props.config.sync_timeline_limit, }); - this.startMatrixClient(); - this.notifyNewScreen(''); }, - startMatrixClient: function() { + _onHaveCredentials: function(credentials) { + credentials.guest = Boolean(credentials.guest); + Lifecycle.login(credentials); + }, + + _onLoggedIn: function(credentials) { + this.guestCreds = null; + this.setState({ + screen: undefined, + logged_in: true, + }); + }, + + _onLoggedOut: function() { + this.notifyNewScreen('login'); + this.replaceState({ + logged_in: false, + ready: false, + }); + }, + + _onWillStartClient() { var cli = MatrixClientPeg.get(); + + if (this.props.config.sync_timeline_limit) { + MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit; + } + var self = this; cli.on('sync', function(state, prevState) { self.updateFavicon(state, prevState); @@ -675,13 +675,6 @@ module.exports = React.createClass({ action: 'logout' }); }); - Notifier.start(); - UserActivity.start(); - Presence.start(); - cli.startClient({ - pendingEventOrdering: "detached", - initialSyncLimit: this.props.config.sync_timeline_limit || 20, - }); }, // stop all the background processes related to the current client @@ -919,12 +912,14 @@ module.exports = React.createClass({ onReturnToGuestClick: function() { // reanimate our guest login - this.onLoggedIn(this.state.guestCreds); - this.setState({ guestCreds: null }); + if (this.guestCreds) { + this._onHaveCredentials(this.guestCreds); + this.guestCreds = null; + } }, onRegistered: function(credentials) { - this.onLoggedIn(credentials); + this._onHaveCredentials(credentials); // do post-registration stuff // This now goes straight to user settings // We use _setPage since if we wait for @@ -1130,7 +1125,7 @@ module.exports = React.createClass({ onLoggedIn={this.onRegistered} onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} - onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } + onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null} /> ); } else if (this.state.screen == 'forgot_password') { @@ -1146,7 +1141,7 @@ module.exports = React.createClass({ } else { return ( ); } From 3818a89ca3a0e9308dd23eaca8bbc94c85e606cb Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:46:43 +0100 Subject: [PATCH 02/17] Comment guestCreds --- src/components/structures/MatrixChat.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index a4f024efef..d0ff91fa3b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -141,6 +141,10 @@ module.exports = React.createClass({ componentWillMount: function() { this.favicon = new Favico({animation: 'none'}); + + // Stashed guest credentials if the user logs out + // whilst logged in as a guest user (so they can change + // their mind & log back in) this.guestCreds = null; }, From 58bbb3509618e951e39ce0e6343b6e3097e9f53b Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:48:27 +0100 Subject: [PATCH 03/17] s/login/setLoggedIn/ --- src/Lifecycle.js | 4 ++-- src/components/structures/MatrixChat.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 86fa39cb51..53180b24dd 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -20,7 +20,7 @@ import UserActivity from './UserActivity'; import Presence from './Presence'; import dis from './dispatcher'; -function login(credentials, options) { +function setLoggedIn(credentials, options) { credentials.guest = Boolean(credentials.guest); console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); MatrixClientPeg.replaceUsingAccessToken( @@ -94,5 +94,5 @@ function _stopMatrixClient() { } module.exports = { - login, logout, startMatrixClient + setLoggedIn, logout, startMatrixClient }; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d0ff91fa3b..2783f2ee82 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -580,14 +580,14 @@ module.exports = React.createClass({ }, _doLogin(creds) { - Lifecycle.login(creds, { + Lifecycle.setLoggedIn(creds, { syncTimelineLimit: this.props.config.sync_timeline_limit, }); }, _onHaveCredentials: function(credentials) { credentials.guest = Boolean(credentials.guest); - Lifecycle.login(credentials); + Lifecycle.setLoggedIn(credentials); }, _onLoggedIn: function(credentials) { From b9a5f7902b0d1c23f2cbe699e06efd5fa1cbb0a2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:52:56 +0100 Subject: [PATCH 04/17] Doc setLoggedIn & remove redundant opts --- src/Lifecycle.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 53180b24dd..821cbd189d 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -20,7 +20,15 @@ import UserActivity from './UserActivity'; import Presence from './Presence'; import dis from './dispatcher'; -function setLoggedIn(credentials, options) { +/** + * Transitions to a logged-in state using the given credentials + * @param {string} credentials.homeserverUrl The base HS URL + * @param {string} credentials.identityServerUrl The base IS URL + * @param {string} credentials.userId The full Matrix User ID + * @param {string} credentials.accessToken The session access token + * @param {boolean} credentials.guest True if the session is a guest session + */ +function setLoggedIn(credentials) { credentials.guest = Boolean(credentials.guest); console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); MatrixClientPeg.replaceUsingAccessToken( @@ -30,7 +38,7 @@ function setLoggedIn(credentials, options) { dis.dispatch({action: 'on_logged_in'}); - startMatrixClient(options); + startMatrixClient(); } function logout() { @@ -54,7 +62,7 @@ function logout() { ); } -function startMatrixClient(options) { +function startMatrixClient() { // dispatch this before starting the matrix client: it's used // to add listeners for the 'sync' event so otherwise we'd have // a race condition (and we need to dispatch synchronously for this From c2c548ef5af57a63b86de10539b39a2844934b23 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:55:13 +0100 Subject: [PATCH 05/17] Comment logout --- src/Lifecycle.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 821cbd189d..9c31f1a20e 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -41,6 +41,9 @@ function setLoggedIn(credentials) { startMatrixClient(); } +/** + * Logs the current session out and transitions to the logged-out state + */ function logout() { if (MatrixClientPeg.get().isGuest()) { // logout doesn't work for guest sessions From 77a5384bf8419af01d183811cb3917d1c3c92d12 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:56:12 +0100 Subject: [PATCH 06/17] Comment startMatrixClient --- src/Lifecycle.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 9c31f1a20e..157af4aa4c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -65,6 +65,10 @@ function logout() { ); } +/** + * Starts the matrix client and all other react-sdk services that + * listen for events while a session is logged in. + */ function startMatrixClient() { // dispatch this before starting the matrix client: it's used // to add listeners for the 'sync' event so otherwise we'd have From 40834d188e86fd795aaf687abefd8adc8ee30300 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:58:18 +0100 Subject: [PATCH 07/17] Don't let pendingEventOrdering be changed --- src/Lifecycle.js | 5 +++++ src/MatrixClientPeg.js | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 157af4aa4c..be09551c88 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -79,6 +79,11 @@ function startMatrixClient() { Notifier.start(); UserActivity.start(); Presence.start(); + + // the react sdk doesn't work without this, so don't allow + // it to be overridden (and modify the global object so at + // at least the app can see we've changed it) + MatrixClientPeg.opts.pendingEventOrdering = "detached"; MatrixClientPeg.get().startClient(MatrixClientPeg.opts); } diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 96eb95de64..49326cc22f 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -45,7 +45,6 @@ class MatrixClientPeg { // starts the client. These can be altered when the // 'will_start_client' event is dispatched. this.opts = { - pendingEventOrdering: "detached", initialSyncLimit: 20, }; } From 65865f879ffcec54574444b75c158dfad5de2e63 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 18:59:09 +0100 Subject: [PATCH 08/17] We already know we're a guest here --- src/components/structures/MatrixChat.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 2783f2ee82..81a2924fb7 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -305,9 +305,7 @@ module.exports = React.createClass({ break; case 'start_upgrade_registration': // stash our guest creds so we can backout if needed - if (MatrixClientPeg.get().isGuest()) { - this.guestCreds = MatrixClientPeg.getCredentials(); - } + this.guestCreds = MatrixClientPeg.getCredentials(); this.replaceState({ screen: "register", upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), From d74a8e405d1b040a42597d2d8d1615b66ea1d682 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 19:00:01 +0100 Subject: [PATCH 09/17] Remove unused function --- src/components/structures/MatrixChat.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 81a2924fb7..d0442ae235 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -577,12 +577,6 @@ module.exports = React.createClass({ this.scrollStateMap[roomId] = state; }, - _doLogin(creds) { - Lifecycle.setLoggedIn(creds, { - syncTimelineLimit: this.props.config.sync_timeline_limit, - }); - }, - _onHaveCredentials: function(credentials) { credentials.guest = Boolean(credentials.guest); Lifecycle.setLoggedIn(credentials); From 0c61c52480331410bbb8adf8fd284e00e0954015 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 2 Aug 2016 19:02:07 +0100 Subject: [PATCH 10/17] Just use Lifecycle.setLoggedIn --- src/components/structures/MatrixChat.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d0442ae235..1861366a96 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -162,7 +162,7 @@ module.exports = React.createClass({ this.props.startingQueryParams.guest_access_token) { this._autoRegisterAsGuest = false; - this._onHaveCredentials({ + Lifecycle.setLoggedIn({ userId: this.props.startingQueryParams.guest_user_id, accessToken: this.props.startingQueryParams.guest_access_token, homeserverUrl: this.getDefaultHsUrl(), @@ -235,7 +235,7 @@ module.exports = React.createClass({ MatrixClientPeg.get().registerGuest().done(function(creds) { console.log("Registered as guest: %s", creds.user_id); self._setAutoRegisterAsGuest(false); - self._onHaveCredentials({ + Lifecycle.setLoggedIn({ userId: creds.user_id, accessToken: creds.access_token, homeserverUrl: hsUrl, @@ -577,11 +577,6 @@ module.exports = React.createClass({ this.scrollStateMap[roomId] = state; }, - _onHaveCredentials: function(credentials) { - credentials.guest = Boolean(credentials.guest); - Lifecycle.setLoggedIn(credentials); - }, - _onLoggedIn: function(credentials) { this.guestCreds = null; this.setState({ @@ -909,13 +904,13 @@ module.exports = React.createClass({ onReturnToGuestClick: function() { // reanimate our guest login if (this.guestCreds) { - this._onHaveCredentials(this.guestCreds); + Lifecycle.setLoggedIn(this.guestCreds); this.guestCreds = null; } }, onRegistered: function(credentials) { - this._onHaveCredentials(credentials); + Lifecycle.setLoggedIn(credentials); // do post-registration stuff // This now goes straight to user settings // We use _setPage since if we wait for @@ -1137,7 +1132,7 @@ module.exports = React.createClass({ } else { return ( Date: Tue, 2 Aug 2016 19:05:05 +0100 Subject: [PATCH 11/17] Comment functions --- src/components/structures/MatrixChat.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 1861366a96..ca7437754b 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -577,6 +577,9 @@ module.exports = React.createClass({ this.scrollStateMap[roomId] = state; }, + /** + * Called when a new logged in session has started + */ _onLoggedIn: function(credentials) { this.guestCreds = null; this.setState({ @@ -585,6 +588,9 @@ module.exports = React.createClass({ }); }, + /** + * Called when the session is logged out + */ _onLoggedOut: function() { this.notifyNewScreen('login'); this.replaceState({ @@ -593,6 +599,10 @@ module.exports = React.createClass({ }); }, + /** + * Called just before the matrix client is started + * (useful for setting options and listeners) + */ _onWillStartClient() { var cli = MatrixClientPeg.get(); From 4825ab8fe78b3ddab42f73250932833d5ae61c3b Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 09:53:02 +0100 Subject: [PATCH 12/17] No need to set options in WillStartClient We can set them any time up to that point --- src/MatrixClientPeg.js | 5 +++-- src/components/structures/MatrixChat.js | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 49326cc22f..54e4cbb646 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -42,8 +42,9 @@ class MatrixClientPeg { this.matrixClient = null; // These are the default options used when Lifecycle.js - // starts the client. These can be altered when the - // 'will_start_client' event is dispatched. + // starts the client. These can be altered at any + // time up to after the 'will_start_client' event is + // finished processing. this.opts = { initialSyncLimit: 20, }; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index ca7437754b..47cbde9af5 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -146,6 +146,10 @@ module.exports = React.createClass({ // whilst logged in as a guest user (so they can change // their mind & log back in) this.guestCreds = null; + + if (this.props.config.sync_timeline_limit) { + MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit; + } }, componentDidMount: function() { @@ -601,15 +605,11 @@ module.exports = React.createClass({ /** * Called just before the matrix client is started - * (useful for setting options and listeners) + * (useful for setting listeners) */ _onWillStartClient() { var cli = MatrixClientPeg.get(); - if (this.props.config.sync_timeline_limit) { - MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit; - } - var self = this; cli.on('sync', function(state, prevState) { self.updateFavicon(state, prevState); From e3a5776eaed5f46ab56e6abe5e023a79c42b7ff2 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 09:57:12 +0100 Subject: [PATCH 13/17] We should clear /login off the URL after login --- src/components/structures/MatrixChat.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 47cbde9af5..d7fd90b537 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -586,6 +586,7 @@ module.exports = React.createClass({ */ _onLoggedIn: function(credentials) { this.guestCreds = null; + this.notifyNewScreen(''); this.setState({ screen: undefined, logged_in: true, From 1f17b78371048451856c33a35f8b9030e42514e6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 10:01:23 +0100 Subject: [PATCH 14/17] log if we can't log out --- src/Lifecycle.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index be09551c88..24073fe7a6 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -54,14 +54,17 @@ function logout() { } return MatrixClientPeg.get().logout().then(_onLoggedOut, - // Just throwing an error here is going to be very unhelpful - // if you're trying to log out because your server's down and - // you want to log into a different server, so just forget the - // access token. It's annoying that this will leave the access - // token still valid, but we should fix this by having access - // tokens expire (and if you really think you've been compromised, - // change your password). - _onLoggedOut + (err) => { + // Just throwing an error here is going to be very unhelpful + // if you're trying to log out because your server's down and + // you want to log into a different server, so just forget the + // access token. It's annoying that this will leave the access + // token still valid, but we should fix this by having access + // tokens expire (and if you really think you've been compromised, + // change your password). + console.log("Failed to call logout API: token will not be invalidated"); + _onLoggedOut(); + } ); } From cf7e7d65c8e7122f07e32eb7b7d8e9679d5f1e25 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 10:15:50 +0100 Subject: [PATCH 15/17] Don't start the client twice if we have a guest_access_token --- src/components/structures/MatrixChat.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d7fd90b537..415c736707 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -153,6 +153,8 @@ module.exports = React.createClass({ }, componentDidMount: function() { + let clientStarted = false; + this._autoRegisterAsGuest = false; if (this.props.enableGuest) { if (!this.getCurrentHsUrl()) { @@ -173,6 +175,7 @@ module.exports = React.createClass({ identityServerUrl: this.getDefaultIsUrl(), guest: true }); + clientStarted = true; } else { this._autoRegisterAsGuest = true; @@ -184,7 +187,9 @@ module.exports = React.createClass({ // Don't auto-register as a guest. This applies if you refresh the page on a // logged in client THEN hit the Sign Out button. this._autoRegisterAsGuest = false; - Lifecycle.startMatrixClient(); + if (!clientStarted) { + Lifecycle.startMatrixClient(); + } } this.focusComposer = false; // scrollStateMap is a map from room id to the scroll state returned by From d9a7d50a03167d6fbf820796ab2296bc468aab69 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 10:46:42 +0100 Subject: [PATCH 16/17] Add an interface for MatrixClientCreds and make MatrixClientPeg functions use it consistently --- src/Lifecycle.js | 5 +---- src/MatrixClientPeg.js | 20 +++++++++++++++++--- src/components/structures/MatrixChat.js | 11 +++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 24073fe7a6..163e6e9463 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -31,10 +31,7 @@ import dis from './dispatcher'; function setLoggedIn(credentials) { credentials.guest = Boolean(credentials.guest); console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); - MatrixClientPeg.replaceUsingAccessToken( - credentials.homeserverUrl, credentials.identityServerUrl, - credentials.userId, credentials.accessToken, credentials.guest - ); + MatrixClientPeg.replaceUsingCreds(credentials); dis.dispatch({action: 'on_logged_in'}); diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js index 54e4cbb646..c8b015f99f 100644 --- a/src/MatrixClientPeg.js +++ b/src/MatrixClientPeg.js @@ -31,6 +31,14 @@ function deviceId() { return id; } +interface MatrixClientCreds { + homeserverUrl: string, + identityServerUrl: string, + userId: string, + accessToken: string, + guest: boolean, +} + /** * Wrapper object for handling the js-sdk Matrix Client object in the react-sdk * Handles the creation/initialisation of client objects. @@ -70,8 +78,14 @@ class MatrixClientPeg { * Replace this MatrixClientPeg's client with a client instance that has * Home Server / Identity Server URLs and active credentials */ - replaceUsingAccessToken(hs_url, is_url, user_id, access_token, isGuest) { - this._replaceClient(hs_url, is_url, user_id, access_token, isGuest); + replaceUsingCreds(creds: MatrixClientCreds) { + this._replaceClient( + creds.homeserverUrl, + creds.identityServerUrl, + creds.userId, + creds.accessToken, + creds.guest, + ); } _replaceClient(hs_url, is_url, user_id, access_token, isGuest) { @@ -103,7 +117,7 @@ class MatrixClientPeg { } } - getCredentials() { + getCredentials(): MatrixClientCreds { return { homeserverUrl: this.matrixClient.baseUrl, identityServerUrl: this.matrixClient.idBaseUrl, diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 415c736707..b712445dc2 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -340,10 +340,13 @@ module.exports = React.createClass({ var client = MatrixClientPeg.get(); client.loginWithToken(payload.params.loginToken).done(function(data) { - MatrixClientPeg.replaceUsingAccessToken( - client.getHomeserverUrl(), client.getIdentityServerUrl(), - data.user_id, data.access_token - ); + MatrixClientPeg.replaceUsingCreds({ + homeserverUrl: client.getHomeserverUrl(), + identityServerUrl: client.getIdentityServerUrl(), + userId: data.user_id, + accessToken: data.access_token, + guest: false, + }); self.setState({ screen: undefined, logged_in: true From da03af6c1cd415471417d82463545cb510f27c94 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 3 Aug 2016 10:51:58 +0100 Subject: [PATCH 17/17] Fix tests --- test/test-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-utils.js b/test/test-utils.js index fc3aaace9f..e2ff5e8c10 100644 --- a/test/test-utils.js +++ b/test/test-utils.js @@ -51,7 +51,7 @@ module.exports.stubClient = function() { // 'sandbox.restore()' doesn't work correctly on inherited methods, // so we do this for each method var methods = ['get', 'unset', 'replaceUsingUrls', - 'replaceUsingAccessToken']; + 'replaceUsingCreds']; for (var i = 0; i < methods.length; i++) { sandbox.stub(peg, methods[i]); }