diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 23bf5f60a5..43c5c3aa8c 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -26,7 +26,9 @@ import dis from './dispatcher'; */ function setLoggedIn(credentials) { credentials.guest = Boolean(credentials.guest); - console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); + console.log("onLoggedIn => %s (guest=%s) hs=%s", + credentials.userId, credentials.guest, + credentials.homeserverUrl); MatrixClientPeg.replaceUsingCreds(credentials); dis.dispatch({action: 'on_logged_in'}); diff --git a/src/component-index.js b/src/component-index.js index 97f8882b82..2bde711c16 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -39,6 +39,7 @@ module.exports.components['structures.login.ForgotPassword'] = require('./compon module.exports.components['structures.login.Login'] = require('./components/structures/login/Login'); module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration'); module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration'); +module.exports.components['structures.login.SessionLoader'] = require('./components/structures/login/SessionLoader'); module.exports.components['views.avatars.BaseAvatar'] = require('./components/views/avatars/BaseAvatar'); module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar'); module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar'); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 33e6499e21..b74a92bbf7 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -26,6 +26,7 @@ var UserActivity = require("../../UserActivity"); var Presence = require("../../Presence"); var dis = require("../../dispatcher"); +var SessionLoader = require("./login/SessionLoader"); var Login = require("./login/Login"); var Registration = require("./login/Registration"); var PostRegistration = require("./login/PostRegistration"); @@ -65,6 +66,9 @@ module.exports = React.createClass({ getInitialState: function() { var s = { + loading: true, + screen: undefined, + // If we are viewing a room by alias, this contains the alias currentRoomAlias: null, @@ -72,7 +76,7 @@ module.exports = React.createClass({ // in the case where we view a room by ID or by RoomView when it resolves // what ID an alias points at. currentRoomId: null, - logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials), + logged_in: false, collapse_lhs: false, collapse_rhs: false, ready: false, @@ -80,15 +84,6 @@ module.exports = React.createClass({ sideOpacity: 1.0, middleOpacity: 1.0, }; - if (s.logged_in) { - if (MatrixClientPeg.get().getRooms().length) { - s.page_type = this.PageTypes.RoomView; - } else { - // we don't need to default to the directoy here - // as we'll go there anyway after syncing - // s.page_type = this.PageTypes.RoomDirectory; - } - } return s; }, @@ -150,47 +145,14 @@ module.exports = React.createClass({ if (this.props.config.sync_timeline_limit) { MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit; } + + // register our dispatcher listener here rather than in + // componentDidMount so that we hear about any actions raised during + // the loading process. + this.dispatcherRef = dis.register(this.onAction); }, componentDidMount: function() { - let clientStarted = false; - - this._autoRegisterAsGuest = false; - if (this.props.enableGuest) { - 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."); - this._autoRegisterAsGuest = false; - } - else if (this.props.startingQueryParams.guest_user_id && - this.props.startingQueryParams.guest_access_token) - { - this._autoRegisterAsGuest = false; - Lifecycle.setLoggedIn({ - userId: this.props.startingQueryParams.guest_user_id, - accessToken: this.props.startingQueryParams.guest_access_token, - homeserverUrl: this.getDefaultHsUrl(), - identityServerUrl: this.getDefaultIsUrl(), - guest: true - }); - clientStarted = true; - } - else { - this._autoRegisterAsGuest = true; - } - } - - this.dispatcherRef = dis.register(this.onAction); - if (this.state.logged_in) { - // 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; - if (!clientStarted) { - Lifecycle.startMatrixClient(); - } - } this.focusComposer = false; // scrollStateMap is a map from room id to the scroll state returned by // RoomView.getScrollState() @@ -198,14 +160,6 @@ module.exports = React.createClass({ document.addEventListener("keydown", this.onKeyDown); window.addEventListener("focus", this.onFocus); - if (this.state.logged_in) { - this.notifyNewScreen(''); - } else if (this._autoRegisterAsGuest) { - this._registerAsGuest(); - } else { - this.notifyNewScreen('login'); - } - // this can technically be done anywhere but doing this here keeps all // the routing url path logic together. if (this.onAliasClick) { @@ -243,7 +197,6 @@ module.exports = React.createClass({ MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl); MatrixClientPeg.get().registerGuest().done(function(creds) { console.log("Registered as guest: %s", creds.user_id); - self._setAutoRegisterAsGuest(false); Lifecycle.setLoggedIn({ userId: creds.user_id, accessToken: creds.access_token, @@ -260,15 +213,9 @@ module.exports = React.createClass({ }); } console.error("Failed to register as guest: " + err + " " + err.data); - self._setAutoRegisterAsGuest(false); }); }, - _setAutoRegisterAsGuest: function(shouldAutoRegister) { - this._autoRegisterAsGuest = shouldAutoRegister; - this.forceUpdate(); - }, - onAction: function(payload) { var roomIndexDelta = 1; @@ -479,6 +426,9 @@ module.exports = React.createClass({ case 'will_start_client': this._onWillStartClient(); break; + case 'load_completed': + this._onLoadCompleted(); + break; } }, @@ -589,6 +539,13 @@ module.exports = React.createClass({ this.scrollStateMap[roomId] = state; }, + /** + * Called when the sessionloader has finished + */ + _onLoadCompleted: function() { + this.setState({loading: false}); + }, + /** * Called when a new logged in session has started */ @@ -1029,8 +986,26 @@ module.exports = React.createClass({ // work out the HS URL prompts we should show for + // console.log("rendering; loading="+this.state.loading+"; screen="+this.state.screen + + // "; logged_in="+this.state.logged_in+"; ready="+this.state.ready); + + if (this.state.loading) { + return ( + {dis.dispatch({action: 'load_completed'});}} + /> + ); + } // needs to be before normal PageTypes as you are logged in technically - if (this.state.screen == 'post_registration') { + else if (this.state.screen == 'post_registration') { return ( @@ -1101,20 +1076,15 @@ module.exports = React.createClass({ ); - } else if (this.state.logged_in || (!this.state.logged_in && this._autoRegisterAsGuest)) { + } else if (this.state.logged_in) { + // we think we are logged in, but are still waiting for the /sync to complete var Spinner = sdk.getComponent('elements.Spinner'); - var logoutLink; - if (this.state.logged_in) { - logoutLink = ( - - Logout - - ); - } return (
- {logoutLink} + + Logout +
); } else if (this.state.screen == 'register') { diff --git a/src/components/structures/login/SessionLoader.js b/src/components/structures/login/SessionLoader.js new file mode 100644 index 0000000000..229f10d27a --- /dev/null +++ b/src/components/structures/login/SessionLoader.js @@ -0,0 +1,152 @@ +/* +Copyright 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 React from 'react'; +import q from 'q'; + +import dis from '../../../dispatcher'; +import sdk from '../../../index'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import Lifecycle from '../../../Lifecycle'; + +/** + * A react component which is only used when the application first starts. + * + * Its job is to attempt to build a logged-in Matrix session. It tries a number + * of things: + * + * 0. if it looks like we are in the middle of a registration process, it does + * nothing. + * + * 1. if we have a guest access token in the query params, it uses that. + * + * 2. if an access token is stored in local storage (from a previous session), + * it uses that. + * + * 3. it attempts to auto-register as a guest user. + * + * If any of steps 1-3 are successful, it will call onLoggedIn (which is + * typically Lifecycle.setLoggedIn, which in turn will raise on_logged_in and + * will_start_client events). + * + * Finally, it calls onComplete, which makes MatrixChat move into its normal processing. + */ +export default class SessionLoader extends React.Component { + constructor(props, context) { + super(props, context); + } + + componentDidMount() { + this._loadSession().done(() => { + this.props.onComplete(); + }); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.hsUrl != this.props.hsUrl || + nextProps.isUrl != this.props.isUrl + ) { + throw new Error("changing servers on a SessionLoader is not supported"); + }; + } + + _loadSession() { + if (this.props.queryParams.client_secret && this.props.queryParams.sid) { + // this happens during email validation: the email contains a link to the + // IS, which in turn redirects back to vector. We let MatrixChat create a + // Registration component which completes the next stage of registration. + console.log("Not registering as guest: registration already in progress."); + return q(); + } + + let enableGuest = false; + if (this.props.enableGuest) { + if (!this.props.hsUrl) { + console.warn("Cannot enable guest access: can't determine HS URL to use"); + } + else { + enableGuest = true; + } + } + + if (enableGuest && + this.props.queryParams.guest_user_id && + this.props.queryParams.guest_access_token + ) { + console.log("Using guest access credentials"); + this.props.onLoggedIn({ + userId: this.props.queryParams.guest_user_id, + accessToken: this.props.queryParams.guest_access_token, + homeserverUrl: this.props.hsUrl, + identityServerUrl: this.props.isUrl, + guest: true, + }); + return q(); + } + + if (MatrixClientPeg.get() && MatrixClientPeg.get().credentials) { + console.log("Using existing credentials"); + this.props.onLoggedIn(MatrixClientPeg.getCredentials()); + return q(); + } + + if (enableGuest) { + return this._registerAsGuest(); + } + + // fall back to login screen + return q(); + } + + _registerAsGuest() { + var hsUrl = this.props.hsUrl; + var isUrl = this.props.isUrl; + console.log("Doing guest login on %s", hsUrl); + + MatrixClientPeg.replaceUsingUrls(hsUrl, isUrl); + return MatrixClientPeg.get().registerGuest().then((creds) => { + console.log("Registered as guest: %s", creds.user_id); + this.props.onLoggedIn({ + userId: creds.user_id, + accessToken: creds.access_token, + homeserverUrl: hsUrl, + identityServerUrl: isUrl, + guest: true, + }); + }, (err) => { + console.error("Failed to register as guest: " + err + " " + err.data); + }); + } + + render() { + const Spinner = sdk.getComponent('elements.Spinner'); + return ( +
+ +
+ ); + } +} + + +SessionLoader.propTypes = { + queryParams: React.PropTypes.object.isRequired, + enableGuest: React.PropTypes.bool, + hsUrl: React.PropTypes.string, + isUrl: React.PropTypes.string, + onLoggedIn: React.PropTypes.func.isRequired, + onComplete: React.PropTypes.func.isRequired, +}; diff --git a/test/components/structures/MatrixChat-test.js b/test/components/structures/MatrixChat-test.js deleted file mode 100644 index 68bc85dd27..0000000000 --- a/test/components/structures/MatrixChat-test.js +++ /dev/null @@ -1,34 +0,0 @@ -var React = require('react'); -var TestUtils = require('react-addons-test-utils'); -var expect = require('expect'); - -var sdk = require('matrix-react-sdk'); -var MatrixChat = sdk.getComponent('structures.MatrixChat'); -var peg = require('../../../src/MatrixClientPeg'); - -var test_utils = require('../../test-utils'); -var q = require('q'); - -describe('MatrixChat', function () { - var sandbox; - - beforeEach(function() { - sandbox = test_utils.stubClient(); - }); - - afterEach(function() { - sandbox.restore(); - }); - - it('gives a login panel by default', function () { - peg.get().loginFlows.returns(q({flows:[]})); - - var res = TestUtils.renderIntoDocument( - - ); - - // we expect a single component - TestUtils.findRenderedComponentWithType( - res, sdk.getComponent('structures.login.Login')); - }); -});