Add a new state between LOADING/SOFT_LOGOUT and LOGGED_IN

... so that we can transition into COMPLETE_SECURITY without going via
LOGGED_IN.
This commit is contained in:
Richard van der Hoff 2025-11-26 10:36:39 +00:00
parent 66dd507ea2
commit a525c53fd9
3 changed files with 104 additions and 65 deletions

View File

@ -27,6 +27,7 @@ const notLoggedInMap: Record<Exclude<Views, Views.LOGGED_IN>, ScreenName> = {
[Views.FORGOT_PASSWORD]: "ForgotPassword", [Views.FORGOT_PASSWORD]: "ForgotPassword",
[Views.COMPLETE_SECURITY]: "CompleteSecurity", [Views.COMPLETE_SECURITY]: "CompleteSecurity",
[Views.E2E_SETUP]: "E2ESetup", [Views.E2E_SETUP]: "E2ESetup",
[Views.PENDING_CLIENT_START]: "Loading",
[Views.SOFT_LOGOUT]: "SoftLogout", [Views.SOFT_LOGOUT]: "SoftLogout",
[Views.LOCK_STOLEN]: "SessionLockStolen", [Views.LOCK_STOLEN]: "SessionLockStolen",
}; };

View File

@ -20,52 +20,62 @@ Please see LICENSE files in the repository root for full details.
* LOADING CONFIRM_LOCK_ * LOADING CONFIRM_LOCK_
* THEFT * THEFT
* Lock theft confirmed * Lock theft confirmed
* Session recovered * Session recovered Token/OIDC login succeeded
* *
* No previous session *
* Token/OIDC login succeeded * No previous session
* *
* * (from all other states
* WELCOME (from all other states * except LOCK_STOLEN) WELCOME
* except LOCK_STOLEN)
*
* "Create Account" "Sign in" Client logged out
*
*
* *
* "Create an ▼ ▼ "Forgot * Client logged out
* account" ┌─────────────────┐ password" *
* REGISTER LOGIN FORGOT_PASSWORD * "Sign in" "Create account"
* *
* "Sign in here" Complete / *
* "Sign in instead" * "Forgot ▼ ▼ "Create an
* * password" ┌─────────────────┐ account"
* * FORGOT_PASSWORD LOGIN REGISTER
* *
* * Complete / "Sign in here"
* (postLoginSetup) * "Sign in instead"
* *
* * "Forgotten your
* E2EE not enabled * password?"
* Account has Account lacks *
* cross-signing cross-signing * Soft-logout error
* keys keys * SOFT_LOGOUT (from all other states
* Client started and * except LOCK_STOLEN)
* force_verification *
* pending COMPLETE_ E2E_SETUP * Re-authentication succeeded
* SECURITY *
* "Forgotten * (postLoginSetup)
* your *
* password?" * PENDING_CLIENT_ Account has Account lacks
* * START cross-signing cross-signing
* (from all other states * keys keys
* except LOCK_STOLEN) *
* *
* Soft logout error * Client started,
* * force_verification pending
* LOGGED_IN Re-authentication succeeded SOFT_LOGOUT *
* * Client started,
* * force_verification COMPLETE_
* not needed SECURITY
* E2E_SETUP
*
* E2EE not enabled
*
*
*
*
*
*
*
*
* LOGGED_IN
*
*
* *
* (from all other states) * (from all other states)
* *
@ -102,6 +112,12 @@ enum Views {
// flow to setup SSSS / cross-signing on this account // flow to setup SSSS / cross-signing on this account
E2E_SETUP, E2E_SETUP,
/**
* We have successfully recovered a session from localstorage, but the client
* has not yet been started.
*/
PENDING_CLIENT_START,
// we are logged in with an active matrix client. The logged_in state also // we are logged in with an active matrix client. The logged_in state also
// includes guests users as they too are logged in at the client level. // includes guests users as they too are logged in at the client level.
LOGGED_IN, LOGGED_IN,

View File

@ -1535,18 +1535,27 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.stores.client = MatrixClientPeg.safeGet(); this.stores.client = MatrixClientPeg.safeGet();
StorageManager.tryPersistStorage(); StorageManager.tryPersistStorage();
// If we're in the middle of a login/registration, we wait for it to complete before transitioning to the logged // If we're loading the app for the first time, we can now transition to a splash screen while we wait for the
// in view the login flow will call `postLoginSetup` when it's done, which will arrange for `onShowPostLoginScreen` // client to start. The exceptions are:
// to be called. //
// - If there is a token login in flight: in that case we wait for the login to complete (which hits
// `postLoginSetup`).
//
// - Lifecycle emits an `Action.OnLoggedIn` event during startup even if the localstorage flag indicating a
// previous soft logout is set. In that situation we actually want to wait for the `Action.ClientNotViable`
// event, which will transition us into Views.SOFT_LOGOUT. We therefore have to check for !isSoftLogout().
// There will be a subsequent `Action.OnLoggedIn` event once the reauthentication completes.
//
// XXX: fix this properly by having Lifecycle not emit OnLoggedIn when it knows it is about to emit a
// ClientNotViable.
//
// If we're already in the SOFT_LOGOUT view, that means that reauthentication has succeeded, and we can
// transition to the splash screen.
if ( if (
!this.tokenLogin && (this.state.view === Views.LOADING && !Lifecycle.isSoftLogout() && !this.tokenLogin) ||
!Lifecycle.isSoftLogout() && this.state.view === Views.SOFT_LOGOUT
this.state.view !== Views.LOGIN &&
this.state.view !== Views.REGISTER &&
this.state.view !== Views.COMPLETE_SECURITY &&
this.state.view !== Views.E2E_SETUP
) { ) {
this.onShowPostLoginScreen(); this.setStateForNewView({ view: Views.PENDING_CLIENT_START });
} }
} }
@ -1779,15 +1788,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const cli = MatrixClientPeg.safeGet(); const cli = MatrixClientPeg.safeGet();
const shouldForceVerification = await this.shouldForceVerification(); const shouldForceVerification = await this.shouldForceVerification();
// XXX: Don't replace the screen if it's already one of these: postLoginSetup
// changes to these screens in certain circumstances so we shouldn't clobber it.
// We should probably have one place where we decide what the next screen is after
// login.
if (![Views.COMPLETE_SECURITY, Views.E2E_SETUP].includes(this.state.view)) {
if (shouldForceVerification) {
this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
}
}
const crypto = cli.getCrypto(); const crypto = cli.getCrypto();
if (crypto) { if (crypto) {
@ -1804,6 +1804,19 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setState({ this.setState({
ready: true, ready: true,
}); });
// If the view is PENDING_CLIENT_START, that means we recovered the session from localstorage, or from
// soft-logout: we can now transition to the logged-in view.
//
// If the view is something else, that probably means it's a login or registration view; we handle that in
// `postLoginSetup`.
if (this.state.view === Views.PENDING_CLIENT_START) {
if (shouldForceVerification) {
this.setStateForNewView({ view: Views.COMPLETE_SECURITY });
} else {
await this.onShowPostLoginScreen();
}
}
} }
public showScreen(screen: string, params?: { [key: string]: any }): void { public showScreen(screen: string, params?: { [key: string]: any }): void {
@ -2162,6 +2175,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />; view = <CompleteSecurity onFinished={this.onCompleteSecurityE2eSetupFinished} />;
} else if (this.state.view === Views.E2E_SETUP) { } else if (this.state.view === Views.E2E_SETUP) {
view = <E2eSetup onCancelled={this.onCompleteSecurityE2eSetupFinished} />; view = <E2eSetup onCancelled={this.onCompleteSecurityE2eSetupFinished} />;
} else if (this.state.view === Views.PENDING_CLIENT_START) {
// we think we are logged in, but are still waiting for the /sync to complete
view = (
<LoginSplashView
matrixClient={MatrixClientPeg.safeGet()}
onLogoutClick={this.onLogoutClick}
syncError={this.state.syncError}
/>
);
} else if (this.state.view === Views.LOGGED_IN) { } else if (this.state.view === Views.LOGGED_IN) {
// `ready` and `view==LOGGED_IN` may be set before `page_type` (because the // `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`, // latter is set via the dispatcher). If we don't yet have a `page_type`,