Merge pull request #377 from matrix-org/dbkr/use_logout_api

Call the logout API when we log out
This commit is contained in:
David Baker 2016-08-03 17:51:42 +01:00 committed by GitHub
commit cbfb0ad998
5 changed files with 226 additions and 78 deletions

View file

@ -181,11 +181,11 @@ function _onAction(payload) {
console.error("Unknown conf call type: %s", payload.type); console.error("Unknown conf call type: %s", payload.type);
} }
} }
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
switch (payload.action) { switch (payload.action) {
case 'place_call': case 'place_call':
if (module.exports.getAnyActiveCall()) { if (module.exports.getAnyActiveCall()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "Existing Call", title: "Existing Call",
description: "You are already in a 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 the runtime env doesn't do VoIP, whine.
if (!MatrixClientPeg.get().supportsVoip()) { if (!MatrixClientPeg.get().supportsVoip()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "VoIP is unsupported", title: "VoIP is unsupported",
description: "You cannot place VoIP calls in this browser." description: "You cannot place VoIP calls in this browser."
@ -210,7 +211,7 @@ function _onAction(payload) {
var members = room.getJoinedMembers(); var members = room.getJoinedMembers();
if (members.length <= 1) { if (members.length <= 1) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
description: "You cannot place a call with yourself." description: "You cannot place a call with yourself."
}); });
@ -236,11 +237,13 @@ function _onAction(payload) {
case 'place_conference_call': case 'place_conference_call':
console.log("Place conference call in %s", payload.room_id); console.log("Place conference call in %s", payload.room_id);
if (!ConferenceHandler) { if (!ConferenceHandler) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
description: "Conference calls are not supported in this client" description: "Conference calls are not supported in this client"
}); });
} }
else if (!MatrixClientPeg.get().supportsVoip()) { else if (!MatrixClientPeg.get().supportsVoip()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, { Modal.createDialog(ErrorDialog, {
title: "VoIP is unsupported", title: "VoIP is unsupported",
description: "You cannot place VoIP calls in this browser." description: "You cannot place VoIP calls in this browser."

110
src/Lifecycle.js Normal file
View file

@ -0,0 +1,110 @@
/*
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';
/**
* Transitions to a logged-in state using the given credentials
* @param {MatrixClientCreds} credentials The credentials to use
*/
function setLoggedIn(credentials) {
credentials.guest = Boolean(credentials.guest);
console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest);
MatrixClientPeg.replaceUsingCreds(credentials);
dis.dispatch({action: 'on_logged_in'});
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
// Also we sometimes want to re-log in a guest session
// if we abort the login
_onLoggedOut();
return;
}
return MatrixClientPeg.get().logout().then(_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();
}
);
}
/**
* 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
// 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.start();
}
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 = {
setLoggedIn, logout, startMatrixClient
};

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict'; 'use strict';
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import utils from 'matrix-js-sdk/lib/utils';
const localStorage = window.localStorage; const localStorage = window.localStorage;
@ -31,6 +32,14 @@ function deviceId() {
return id; 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 * Wrapper object for handling the js-sdk Matrix Client object in the react-sdk
* Handles the creation/initialisation of client objects. * Handles the creation/initialisation of client objects.
@ -40,6 +49,14 @@ function deviceId() {
class MatrixClientPeg { class MatrixClientPeg {
constructor() { constructor() {
this.matrixClient = null; this.matrixClient = null;
// These are the default options used when when the
// client is started in 'start'. These can be altered
// at any time up to after the 'will_start_client'
// event is finished processing.
this.opts = {
initialSyncLimit: 20,
};
} }
get(): MatrixClient { get(): MatrixClient {
@ -62,8 +79,21 @@ class MatrixClientPeg {
* Replace this MatrixClientPeg's client with a client instance that has * Replace this MatrixClientPeg's client with a client instance that has
* Home Server / Identity Server URLs and active credentials * Home Server / Identity Server URLs and active credentials
*/ */
replaceUsingAccessToken(hs_url, is_url, user_id, access_token, isGuest) { replaceUsingCreds(creds: MatrixClientCreds) {
this._replaceClient(hs_url, is_url, user_id, access_token, isGuest); this._replaceClient(
creds.homeserverUrl,
creds.identityServerUrl,
creds.userId,
creds.accessToken,
creds.guest,
);
}
start() {
const opts = utils.deepCopy(this.opts);
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";
this.get().startClient(opts);
} }
_replaceClient(hs_url, is_url, user_id, access_token, isGuest) { _replaceClient(hs_url, is_url, user_id, access_token, isGuest) {
@ -95,14 +125,14 @@ class MatrixClientPeg {
} }
} }
getCredentials() { getCredentials(): MatrixClientCreds {
return [ return {
this.matrixClient.baseUrl, homeserverUrl: this.matrixClient.baseUrl,
this.matrixClient.idBaseUrl, identityServerUrl: this.matrixClient.idBaseUrl,
this.matrixClient.credentials.userId, userId: this.matrixClient.credentials.userId,
this.matrixClient.getAccessToken(), accessToken: this.matrixClient.getAccessToken(),
this.matrixClient.isGuest(), guest: this.matrixClient.isGuest(),
]; };
} }
tryRestore() { tryRestore() {

View file

@ -36,6 +36,7 @@ var sdk = require('../../index');
var MatrixTools = require('../../MatrixTools'); var MatrixTools = require('../../MatrixTools');
var linkifyMatrix = require("../../linkify-matrix"); var linkifyMatrix = require("../../linkify-matrix");
var KeyCode = require('../../KeyCode'); var KeyCode = require('../../KeyCode');
var Lifecycle = require('../../Lifecycle');
var createRoom = require("../../createRoom"); var createRoom = require("../../createRoom");
@ -140,9 +141,20 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
this.favicon = new Favico({animation: 'none'}); 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;
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
}
}, },
componentDidMount: function() { componentDidMount: function() {
let clientStarted = false;
this._autoRegisterAsGuest = false; this._autoRegisterAsGuest = false;
if (this.props.enableGuest) { if (this.props.enableGuest) {
if (!this.getCurrentHsUrl()) { if (!this.getCurrentHsUrl()) {
@ -156,13 +168,14 @@ module.exports = React.createClass({
this.props.startingQueryParams.guest_access_token) this.props.startingQueryParams.guest_access_token)
{ {
this._autoRegisterAsGuest = false; this._autoRegisterAsGuest = false;
this.onLoggedIn({ Lifecycle.setLoggedIn({
userId: this.props.startingQueryParams.guest_user_id, userId: this.props.startingQueryParams.guest_user_id,
accessToken: this.props.startingQueryParams.guest_access_token, accessToken: this.props.startingQueryParams.guest_access_token,
homeserverUrl: this.getDefaultHsUrl(), homeserverUrl: this.getDefaultHsUrl(),
identityServerUrl: this.getDefaultIsUrl(), identityServerUrl: this.getDefaultIsUrl(),
guest: true guest: true
}); });
clientStarted = true;
} }
else { else {
this._autoRegisterAsGuest = true; this._autoRegisterAsGuest = true;
@ -174,7 +187,9 @@ module.exports = React.createClass({
// Don't auto-register as a guest. This applies if you refresh the page on a // 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. // logged in client THEN hit the Sign Out button.
this._autoRegisterAsGuest = false; this._autoRegisterAsGuest = false;
this.startMatrixClient(); if (!clientStarted) {
Lifecycle.startMatrixClient();
}
} }
this.focusComposer = false; this.focusComposer = false;
// scrollStateMap is a map from room id to the scroll state returned by // scrollStateMap is a map from room id to the scroll state returned by
@ -229,7 +244,7 @@ module.exports = React.createClass({
MatrixClientPeg.get().registerGuest().done(function(creds) { MatrixClientPeg.get().registerGuest().done(function(creds) {
console.log("Registered as guest: %s", creds.user_id); console.log("Registered as guest: %s", creds.user_id);
self._setAutoRegisterAsGuest(false); self._setAutoRegisterAsGuest(false);
self.onLoggedIn({ Lifecycle.setLoggedIn({
userId: creds.user_id, userId: creds.user_id,
accessToken: creds.access_token, accessToken: creds.access_token,
homeserverUrl: hsUrl, homeserverUrl: hsUrl,
@ -260,34 +275,10 @@ module.exports = React.createClass({
var self = this; var self = this;
switch (payload.action) { switch (payload.action) {
case 'logout': case 'logout':
var guestCreds;
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
guestCreds = { // stash our guest creds so we can backout if needed this.guestCreds = MatrixClientPeg.getCredentials();
userId: MatrixClientPeg.get().credentials.userId,
accessToken: MatrixClientPeg.get().getAccessToken(),
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(),
guest: true
} }
} Lifecycle.logout();
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,
});
break; break;
case 'start_registration': case 'start_registration':
var newState = payload.params || {}; var newState = payload.params || {};
@ -313,7 +304,6 @@ module.exports = React.createClass({
if (this.state.logged_in) return; if (this.state.logged_in) return;
this.replaceState({ this.replaceState({
screen: 'login', screen: 'login',
guestCreds: this.state.guestCreds,
}); });
this.notifyNewScreen('login'); this.notifyNewScreen('login');
break; break;
@ -323,17 +313,12 @@ module.exports = React.createClass({
}); });
break; break;
case 'start_upgrade_registration': case 'start_upgrade_registration':
// stash our guest creds so we can backout if needed
this.guestCreds = MatrixClientPeg.getCredentials();
this.replaceState({ this.replaceState({
screen: "register", screen: "register",
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
guestAccessToken: MatrixClientPeg.get().getAccessToken(), 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'); this.notifyNewScreen('register');
break; break;
@ -355,10 +340,13 @@ module.exports = React.createClass({
var client = MatrixClientPeg.get(); var client = MatrixClientPeg.get();
client.loginWithToken(payload.params.loginToken).done(function(data) { client.loginWithToken(payload.params.loginToken).done(function(data) {
MatrixClientPeg.replaceUsingAccessToken( MatrixClientPeg.replaceUsingCreds({
client.getHomeserverUrl(), client.getIdentityServerUrl(), homeserverUrl: client.getHomeserverUrl(),
data.user_id, data.access_token identityServerUrl: client.getIdentityServerUrl(),
); userId: data.user_id,
accessToken: data.access_token,
guest: false,
});
self.setState({ self.setState({
screen: undefined, screen: undefined,
logged_in: true logged_in: true
@ -482,6 +470,15 @@ module.exports = React.createClass({
middleOpacity: payload.middleOpacity, middleOpacity: payload.middleOpacity,
}); });
break; break;
case 'on_logged_in':
this._onLoggedIn();
break;
case 'on_logged_out':
this._onLoggedOut();
break;
case 'will_start_client':
this._onWillStartClient();
break;
} }
}, },
@ -592,23 +589,36 @@ module.exports = React.createClass({
this.scrollStateMap[roomId] = state; this.scrollStateMap[roomId] = state;
}, },
onLoggedIn: function(credentials) { /**
credentials.guest = Boolean(credentials.guest); * Called when a new logged in session has started
console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest); */
MatrixClientPeg.replaceUsingAccessToken( _onLoggedIn: function(credentials) {
credentials.homeserverUrl, credentials.identityServerUrl, this.guestCreds = null;
credentials.userId, credentials.accessToken, credentials.guest this.notifyNewScreen('');
);
this.setState({ this.setState({
screen: undefined, screen: undefined,
logged_in: true logged_in: true,
}); });
this.startMatrixClient();
this.notifyNewScreen('');
}, },
startMatrixClient: function() { /**
* Called when the session is logged out
*/
_onLoggedOut: function() {
this.notifyNewScreen('login');
this.replaceState({
logged_in: false,
ready: false,
});
},
/**
* Called just before the matrix client is started
* (useful for setting listeners)
*/
_onWillStartClient() {
var cli = MatrixClientPeg.get(); var cli = MatrixClientPeg.get();
var self = this; var self = this;
cli.on('sync', function(state, prevState) { cli.on('sync', function(state, prevState) {
self.updateFavicon(state, prevState); self.updateFavicon(state, prevState);
@ -675,13 +685,6 @@ module.exports = React.createClass({
action: 'logout' 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 // stop all the background processes related to the current client
@ -919,12 +922,14 @@ module.exports = React.createClass({
onReturnToGuestClick: function() { onReturnToGuestClick: function() {
// reanimate our guest login // reanimate our guest login
this.onLoggedIn(this.state.guestCreds); if (this.guestCreds) {
this.setState({ guestCreds: null }); Lifecycle.setLoggedIn(this.guestCreds);
this.guestCreds = null;
}
}, },
onRegistered: function(credentials) { onRegistered: function(credentials) {
this.onLoggedIn(credentials); Lifecycle.setLoggedIn(credentials);
// do post-registration stuff // do post-registration stuff
// This now goes straight to user settings // This now goes straight to user settings
// We use _setPage since if we wait for // We use _setPage since if we wait for
@ -1130,7 +1135,7 @@ module.exports = React.createClass({
onLoggedIn={this.onRegistered} onLoggedIn={this.onRegistered}
onLoginClick={this.onLoginClick} onLoginClick={this.onLoginClick}
onRegisterClick={this.onRegisterClick} onRegisterClick={this.onRegisterClick}
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
/> />
); );
} else if (this.state.screen == 'forgot_password') { } else if (this.state.screen == 'forgot_password') {
@ -1146,7 +1151,7 @@ module.exports = React.createClass({
} else { } else {
return ( return (
<Login <Login
onLoggedIn={this.onLoggedIn} onLoggedIn={Lifecycle.setLoggedIn}
onRegisterClick={this.onRegisterClick} onRegisterClick={this.onRegisterClick}
defaultHsUrl={this.getDefaultHsUrl()} defaultHsUrl={this.getDefaultHsUrl()}
defaultIsUrl={this.getDefaultIsUrl()} defaultIsUrl={this.getDefaultIsUrl()}
@ -1155,7 +1160,7 @@ module.exports = React.createClass({
fallbackHsUrl={this.getFallbackHsUrl()} fallbackHsUrl={this.getFallbackHsUrl()}
onForgotPasswordClick={this.onForgotPasswordClick} onForgotPasswordClick={this.onForgotPasswordClick}
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this._registerAsGuest.bind(this, true)} onLoginAsGuestClick={this.props.enableGuest && this.props.config && this._registerAsGuest.bind(this, true)}
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
/> />
); );
} }

View file

@ -51,7 +51,7 @@ module.exports.stubClient = function() {
// 'sandbox.restore()' doesn't work correctly on inherited methods, // 'sandbox.restore()' doesn't work correctly on inherited methods,
// so we do this for each method // so we do this for each method
var methods = ['get', 'unset', 'replaceUsingUrls', var methods = ['get', 'unset', 'replaceUsingUrls',
'replaceUsingAccessToken']; 'replaceUsingCreds'];
for (var i = 0; i < methods.length; i++) { for (var i = 0; i < methods.length; i++) {
sandbox.stub(peg, methods[i]); sandbox.stub(peg, methods[i]);
} }