Merge pull request #397 from matrix-org/rav/factor_out_sessionloader

Start to factor out session-loading magic
pull/21833/head
David Baker 2016-08-10 11:40:58 +01:00 committed by GitHub
commit e0f71977b4
3 changed files with 151 additions and 116 deletions

View File

@ -14,19 +14,122 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import q from 'q';
import MatrixClientPeg from './MatrixClientPeg';
import Notifier from './Notifier'
import UserActivity from './UserActivity';
import Presence from './Presence';
import dis from './dispatcher';
/**
* Called at startup, 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 {setLoggedIn}, which in
* turn will raise on_logged_in and will_start_client events.
*
* It returns a promise which resolves when the above process completes.
*
* @param {object} opts.queryParams: string->string map of the query-parameters
* extracted from the #-fragment of the starting URI.
*
* @param {boolean} opts.enableGuest: set to true to enable guest access tokens
* and auto-guest registrations.
*
* @params {string} opts.hsUrl: homeserver URL. Only used if enableGuest is
* true; defines the HS to register against.
*
* @params {string} opts.isUrl: homeserver URL. Only used if enableGuest is
* true; defines the IS to use.
*
*/
export function loadSession(opts) {
const queryParams = opts.queryParams || {};
let enableGuest = opts.enableGuest || false;
const hsUrl = opts.hsUrl;
const isUrl = opts.isUrl;
if (queryParams.client_secret && 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();
}
if (!hsUrl) {
console.warn("Cannot enable guest access: can't determine HS URL to use");
enableGuest = false;
}
if (enableGuest &&
queryParams.guest_user_id &&
queryParams.guest_access_token
) {
console.log("Using guest access credentials");
setLoggedIn({
userId: queryParams.guest_user_id,
accessToken: queryParams.guest_access_token,
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
guest: true,
});
return q();
}
if (MatrixClientPeg.get() && MatrixClientPeg.get().credentials) {
console.log("Using existing credentials");
setLoggedIn(MatrixClientPeg.getCredentials());
return q();
}
if (enableGuest) {
return _registerAsGuest(hsUrl, isUrl);
}
// fall back to login screen
return q();
}
function _registerAsGuest(hsUrl, 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);
setLoggedIn({
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);
});
}
/**
* Transitions to a logged-in state using the given credentials
* @param {MatrixClientCreds} credentials The credentials to use
*/
function setLoggedIn(credentials) {
export function setLoggedIn(credentials) {
credentials.guest = Boolean(credentials.guest);
console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest);
console.log("setLoggedIn => %s (guest=%s) hs=%s",
credentials.userId, credentials.guest,
credentials.homeserverUrl);
MatrixClientPeg.replaceUsingCreds(credentials);
dis.dispatch({action: 'on_logged_in'});
@ -37,7 +140,7 @@ function setLoggedIn(credentials) {
/**
* Logs the current session out and transitions to the logged-out state
*/
function logout() {
export function logout() {
if (MatrixClientPeg.get().isGuest()) {
// logout doesn't work for guest sessions
// Also we sometimes want to re-log in a guest session
@ -65,7 +168,7 @@ function logout() {
* Starts the matrix client and all other react-sdk services that
* listen for events while a session is logged in.
*/
function startMatrixClient() {
export 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
@ -83,7 +186,7 @@ function startMatrixClient() {
* Stops a running client and all related services, used after
* a session has been logged out / ended.
*/
function onLoggedOut() {
export function onLoggedOut() {
if (window.localStorage) {
const hsUrl = window.localStorage.getItem("mx_hs_url");
const isUrl = window.localStorage.getItem("mx_is_url");
@ -110,7 +213,3 @@ function _stopMatrixClient() {
MatrixClientPeg.get().removeAllListeners();
MatrixClientPeg.unset();
}
module.exports = {
setLoggedIn, logout, startMatrixClient, onLoggedOut
};

View File

@ -66,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,
@ -73,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,
@ -81,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;
},
@ -155,44 +149,8 @@ module.exports = React.createClass({
},
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()
@ -200,14 +158,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) {
@ -219,6 +169,17 @@ module.exports = React.createClass({
window.addEventListener('resize', this.handleResize);
this.handleResize();
Lifecycle.loadSession({
queryParams: this.props.startingQueryParams,
enableGuest: this.props.enableGuest,
hsUrl: this.getDefaultHsUrl(),
isUrl: this.getDefaultIsUrl(),
}).done(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
dis.dispatch({action: 'load_completed'});
});
},
componentWillUnmount: function() {
@ -245,7 +206,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,
@ -262,15 +222,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;
@ -481,6 +435,9 @@ module.exports = React.createClass({
case 'will_start_client':
this._onWillStartClient();
break;
case 'load_completed':
this._onLoadCompleted();
break;
}
},
@ -591,6 +548,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
*/
@ -1031,8 +995,19 @@ 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) {
var Spinner = sdk.getComponent('elements.Spinner');
return (
<div className="mx_MatrixChat_splash">
<Spinner />
</div>
);
}
// 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 (
<PostRegistration
onComplete={this.onFinishPostRegistration} />
@ -1108,20 +1083,15 @@ module.exports = React.createClass({
</div>
</div>
);
} 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 = (
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
Logout
</a>
);
}
return (
<div className="mx_MatrixChat_splash">
<Spinner />
{logoutLink}
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
Logout
</a>
</div>
);
} else if (this.state.screen == 'register') {

View File

@ -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(
<MatrixChat config={{}}/>
);
// we expect a single <Login> component
TestUtils.findRenderedComponentWithType(
res, sdk.getComponent('structures.login.Login'));
});
});