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 ( ); }