Merge pull request #1102 from matrix-org/rav/refactor_matrixclient_states

Refactor the state machine in MatrixChat
This commit is contained in:
Richard van der Hoff 2017-06-19 08:53:50 +01:00 committed by GitHub
commit 115a3deed9
2 changed files with 122 additions and 80 deletions

View File

@ -62,6 +62,8 @@ import { _t } from './languageHandler';
* true; defines the IS to use.
*
* @returns {Promise} a promise which resolves when the above process completes.
* Resolves to `true` if we ended up starting a session, or `false` if we
* failed.
*/
export function loadSession(opts) {
const fragmentQueryParams = opts.fragmentQueryParams || {};
@ -86,12 +88,12 @@ export function loadSession(opts) {
homeserverUrl: guestHsUrl,
identityServerUrl: guestIsUrl,
guest: true,
}, true);
}, true).then(() => true);
}
return _restoreFromLocalStorage().then((success) => {
if (success) {
return;
return true;
}
if (enableGuest) {
@ -99,6 +101,7 @@ export function loadSession(opts) {
}
// fall back to login screen
return false;
});
}
@ -176,9 +179,10 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
homeserverUrl: hsUrl,
identityServerUrl: isUrl,
guest: true,
}, true);
}, true).then(() => true);
}, (err) => {
console.error("Failed to register as guest: " + err + " " + err.data);
return false;
});
}

View File

@ -43,7 +43,41 @@ import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
import { _t, getCurrentLanguage } from '../../languageHandler';
/** constants for MatrixChat.state.view */
const VIEWS = {
// a special initial state which is only used at startup, while we are
// trying to re-animate a matrix client or register as a guest.
LOADING: 0,
// we are showing the login view
LOGIN: 1,
// we are showing the registration view
REGISTER: 2,
// completeing the registration flow
POST_REGISTRATION: 3,
// showing the 'forgot password' view
FORGOT_PASSWORD: 4,
// we have valid matrix credentials (either via an explicit login, via the
// initial re-animation/guest registration, or via a registration), and are
// now setting up a matrixclient to talk to it. This isn't an instant
// process because (a) we need to clear out indexeddb, and (b) we need to
// talk to the team server; while it is going on we show a big spinner.
LOGGING_IN: 5,
// we are logged in with an active matrix client.
LOGGED_IN: 6,
};
module.exports = React.createClass({
// we export this so that the integration tests can use it :-S
statics: {
VIEWS: VIEWS,
},
displayName: 'MatrixChat',
propTypes: {
@ -93,8 +127,10 @@ module.exports = React.createClass({
getInitialState: function() {
const s = {
loading: true,
screen: undefined,
// the master view we are showing.
view: VIEWS.LOADING,
// a thing to call showScreen with once login completes.
screenAfterLogin: this.props.initialScreenAfterLogin,
// Stashed guest credentials if the user logs out
@ -113,8 +149,6 @@ module.exports = React.createClass({
// If we're trying to just view a user ID (i.e. /user URL), this is it
viewUserId: null,
loggedIn: false,
loggingIn: false,
collapse_lhs: false,
collapse_rhs: false,
ready: false,
@ -301,10 +335,12 @@ module.exports = React.createClass({
});
}).catch((e) => {
console.error("Unable to load session", e);
}).then(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
dis.dispatch({action: 'load_completed'});
return false;
}).then((loadedSession) => {
if (!loadedSession) {
// fall back to showing the login screen
dis.dispatch({action: "start_login"});
}
});
}).done();
},
@ -325,18 +361,19 @@ module.exports = React.createClass({
}
},
setStateForNewScreen: function(state) {
setStateForNewView: function(state) {
if (state.view === undefined) {
throw new Error("setStateForNewView with no view!");
}
const newState = {
screen: undefined,
viewUserId: null,
loggedIn: false,
ready: false,
};
Object.assign(newState, state);
this.setState(newState);
},
onAction: function(payload) {
// console.log(`MatrixClientPeg.onAction: ${payload.action}`);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
@ -355,19 +392,19 @@ module.exports = React.createClass({
guestCreds: MatrixClientPeg.getCredentials(),
});
}
this.setStateForNewScreen({
screen: 'login',
this.setStateForNewView({
view: VIEWS.LOGIN,
});
this.notifyNewScreen('login');
break;
case 'start_post_registration':
this.setState({ // don't clobber loggedIn status
screen: 'post_registration',
this.setState({
view: VIEWS.POST_REGISTRATION,
});
break;
case 'start_password_recovery':
this.setStateForNewScreen({
screen: 'forgot_password',
this.setStateForNewView({
view: VIEWS.FORGOT_PASSWORD,
});
this.notifyNewScreen('forgot_password');
break;
@ -511,7 +548,10 @@ module.exports = React.createClass({
// and also that we're not ready (we'll be marked as logged
// in once the login completes, then ready once the sync
// completes).
this.setState({loggingIn: true, ready: false});
this.setStateForNewView({
view: VIEWS.LOGGING_IN,
ready: false,
});
break;
case 'on_logged_in':
this._onLoggedIn(payload.teamToken);
@ -522,9 +562,6 @@ module.exports = React.createClass({
case 'will_start_client':
this._onWillStartClient();
break;
case 'load_completed':
this._onLoadCompleted();
break;
case 'new_version':
this.onVersion(
payload.currentVersion, payload.newVersion,
@ -548,8 +585,8 @@ module.exports = React.createClass({
},
_startRegistration: function(params) {
this.setStateForNewScreen({
screen: 'register',
this.setStateForNewView({
view: VIEWS.REGISTER,
// these params may be undefined, but if they are,
// unset them from our state: we don't want to
// resume a previous registration session if the
@ -857,13 +894,6 @@ module.exports = React.createClass({
});
},
/**
* Called when the sessionloader has finished
*/
_onLoadCompleted: function() {
this.setState({loading: false});
},
/**
* Called whenever someone changes the theme
*
@ -916,9 +946,8 @@ module.exports = React.createClass({
*/
_onLoggedIn: function(teamToken) {
this.setState({
view: VIEWS.LOGGED_IN,
guestCreds: null,
loggedIn: true,
loggingIn: false,
});
if (teamToken) {
@ -979,8 +1008,8 @@ module.exports = React.createClass({
*/
_onLoggedOut: function() {
this.notifyNewScreen('login');
this.setStateForNewScreen({
loggedIn: false,
this.setStateForNewView({
view: VIEWS.LOGIN,
ready: false,
collapse_lhs: false,
collapse_rhs: false,
@ -1143,7 +1172,7 @@ module.exports = React.createClass({
// we can't view a room unless we're logged in
// (a guest account is fine)
if (this.state.loggedIn) {
if (this.state.view === VIEWS.LOGGED_IN) {
dis.dispatch(payload);
}
} else if (screen.indexOf('user/') == 0) {
@ -1263,7 +1292,7 @@ module.exports = React.createClass({
onFinishPostRegistration: function() {
// Don't confuse this with "PageType" which is the middle window to show
this.setState({
screen: undefined,
view: VIEWS.LOGGED_IN,
});
this.showScreen("settings");
},
@ -1352,11 +1381,9 @@ module.exports = React.createClass({
},
render: function() {
// `loading` might be set to false before `loggedIn = true`, causing the default
// (`<Login>`) to be visible for a few MS (say, whilst a request is in-flight to
// the RTS). So in the meantime, use `loggingIn`, which is true between
// actions `on_logging_in` and `on_logged_in`.
if (this.state.loading || this.state.loggingIn) {
// console.log(`Rendering MatrixChat with view ${this.state.view}`);
if (this.state.view === VIEWS.LOADING || this.state.view === VIEWS.LOGGING_IN) {
const Spinner = sdk.getComponent('elements.Spinner');
return (
<div className="mx_MatrixChat_splash">
@ -1366,7 +1393,7 @@ module.exports = React.createClass({
}
// needs to be before normal PageTypes as you are logged in technically
if (this.state.screen == 'post_registration') {
if (this.state.view === VIEWS.POST_REGISTRATION) {
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
return (
<PostRegistration
@ -1374,10 +1401,11 @@ module.exports = React.createClass({
);
}
// `ready` and `loggedIn` may be set before `page_type` (because the
if (this.state.view === VIEWS.LOGGED_IN) {
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the
// latter is set via the dispatcher). If we don't yet have a `page_type`,
// keep showing the spinner for now.
if (this.state.loggedIn && this.state.ready && this.state.page_type) {
if (this.state.ready && this.state.page_type) {
/* for now, we stuff the entirety of our props and state into the LoggedInView.
* we should go through and figure out what we actually need to pass down, as well
* as using something like redux to avoid having a billion bits of state kicking around.
@ -1394,7 +1422,7 @@ module.exports = React.createClass({
{...this.state}
/>
);
} else if (this.state.loggedIn) {
} else {
// we think we are logged in, but are still waiting for the /sync to complete
const Spinner = sdk.getComponent('elements.Spinner');
return (
@ -1405,7 +1433,10 @@ module.exports = React.createClass({
</a>
</div>
);
} else if (this.state.screen == 'register') {
}
}
if (this.state.view === VIEWS.REGISTER) {
const Registration = sdk.getComponent('structures.login.Registration');
return (
<Registration
@ -1428,7 +1459,10 @@ module.exports = React.createClass({
onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null}
/>
);
} else if (this.state.screen == 'forgot_password') {
}
if (this.state.view === VIEWS.FORGOT_PASSWORD) {
const ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
return (
<ForgotPassword
@ -1440,7 +1474,9 @@ module.exports = React.createClass({
onRegisterClick={this.onRegisterClick}
onLoginClick={this.onLoginClick} />
);
} else {
}
if (this.state.view === VIEWS.LOGIN) {
const Login = sdk.getComponent('structures.login.Login');
return (
<Login
@ -1458,5 +1494,7 @@ module.exports = React.createClass({
/>
);
}
console.error(`Unknown view ${this.state.view}`);
},
});