From a525c53fd9b52b40d4d2de477dfdf30520398a3e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 26 Nov 2025 10:36:39 +0000 Subject: [PATCH] Add a new state between LOADING/SOFT_LOGOUT and LOGGED_IN ... so that we can transition into COMPLETE_SECURITY without going via LOGGED_IN. --- src/PosthogTrackers.ts | 1 + src/Views.ts | 108 +++++++++++++---------- src/components/structures/MatrixChat.tsx | 60 +++++++++---- 3 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/PosthogTrackers.ts b/src/PosthogTrackers.ts index ae67b5f378..2650403d3b 100644 --- a/src/PosthogTrackers.ts +++ b/src/PosthogTrackers.ts @@ -27,6 +27,7 @@ const notLoggedInMap: Record, ScreenName> = { [Views.FORGOT_PASSWORD]: "ForgotPassword", [Views.COMPLETE_SECURITY]: "CompleteSecurity", [Views.E2E_SETUP]: "E2ESetup", + [Views.PENDING_CLIENT_START]: "Loading", [Views.SOFT_LOGOUT]: "SoftLogout", [Views.LOCK_STOLEN]: "SessionLockStolen", }; diff --git a/src/Views.ts b/src/Views.ts index 0757b8c9c8..ccb0d93e05 100644 --- a/src/Views.ts +++ b/src/Views.ts @@ -20,52 +20,62 @@ Please see LICENSE files in the repository root for full details. * │ LOADING │─────────────────────────────►│ CONFIRM_LOCK_ │ * │ │◄─────────────────────────────│ THEFT │ * └─────────────────┘ Lock theft confirmed └─────────────────┘ - * Session recovered │ │ │ - * ┌──────────────┘ │ └────────────────┐ - * │ ┌─────────────┘ │ No previous session - * │ │ Token/OIDC login succeeded │ - * │ │ ▼ - * │ │ ┌─────────────────┐ - * │ │ │ WELCOME │ (from all other states - * │ │ │ │ except LOCK_STOLEN) - * │ │ └─────────────────┘ │ - * │ │ "Create Account" │ │ "Sign in" │ Client logged out - * │ │ ┌────────────────────────┘ │ │ - * │ │ │ │ ┌────────────────────┘ - * │ │ │ │ │ - * │ │ ▼ "Create an ▼ ▼ "Forgot - * │ │ ┌─────────────────┐ account" ┌─────────────────┐ password" ┌─────────────────┐ - * │ │ │ REGISTER │◄───────────────│ LOGIN │───────────────►│ FORGOT_PASSWORD │ - * │ │ │ │───────────────►│ │◄───────────────│ │ - * │ │ └─────────────────┘ "Sign in here" └─────────────────┘ Complete / └─────────────────┘ - * │ │ │ │ "Sign in instead" ▲ - * │ │ └────────────────────────────────┐ │ │ - * │ └────────────────────────────────────────┐ │ │ │ - * │ ▼ ▼ ▼ │ - * │ ┌──────────────────┐ │ - * │ │ (postLoginSetup) │ │ - * │ └──────────────────┘ │ - * │ ┌────────────────────────────────────┘ │ │ │ - * │ │ E2EE not enabled ┌─────────────┘ └──────┐ │ - * │ │ │ Account has │ Account lacks │ - * │ │ │ cross-signing │ cross-signing │ - * │ │ │ keys │ keys │ - * │ │ Client started and ▼ ▼ │ - * │ │ force_verification ┌─────────────────┐ ┌─────────────────┐ │ - * │ │ pending │ COMPLETE_ │ │ E2E_SETUP │ │ - * │ │ ┌─────────────────►│ SECURITY │ │ │ │ - * │ │ │ └─────────────────┘ └─────────────────┘ │ "Forgotten - * │ │ │ ┌───────────────────────┘ │ │ your - * │ │ │ │ ┌───────────────────────────────────────────────┘ │ password?" - * │ │ │ │ │ │ - * │ │ │ │ │ (from all other states │ - * │ │ │ │ │ except LOCK_STOLEN) │ - * │ │ │ │ │ └──────────────┐ │ - * ▼ ▼ │ ▼ ▼ Soft logout error ▼ │ - * ┌─────────────────┐ ┌─────────────────┐ - * │ LOGGED_IN │ Re-authentication succeeded │ SOFT_LOGOUT │ - * │ │◄────────────────────────────────────────────────────────│ │ - * └─────────────────┘ └─────────────────┘ + * Session recovered │ │ │ Token/OIDC login succeeded + * ┌──────────────┘ │ └──────────────────────────────────────────────────────────────────┐ + * │ └───────────────────────────────────────────┐ │ + * │ │ No previous session │ + * │ ▼ │ + * │ (from all other states ┌─────────────────┐ │ + * │ except LOCK_STOLEN) │ WELCOME │ │ + * │ │ │ │ │ + * │ │ Client logged out └─────────────────┘ │ + * │ │ │ │ │ + * │ └──────────────────────────┐ "Sign in" │ │ "Create account" │ + * │ │ ┌────────────┘ └──────────────┐ │ + * │ │ │ │ │ + * │ "Forgot ▼ ▼ "Create an ▼ │ + * │ ┌─────────────────┐ password" ┌─────────────────┐ account" ┌─────────────────┐ │ + * │ │ FORGOT_PASSWORD │◄───────────────│ LOGIN │───────────────►│ REGISTER │ │ + * │ │ │───────────────►│ │◄───────────────│ │ │ + * │ └─────────────────┘ Complete / └─────────────────┘ "Sign in here" └─────────────────┘ │ + * │ ▲ "Sign in instead" │ │ │ + * │ │ └──────────────────────┐ ┌─────────┘ │ + * │ │"Forgotten your │ │ ┌──────────────────┘ + * │ │ password?" │ │ │ + * │ │ │ │ │ + * │ ┌─────────────────┐ Soft-logout error │ │ │ + * │ │ SOFT_LOGOUT │◄───────────── (from all other states │ │ │ + * │ │ │ except LOCK_STOLEN) │ │ │ + * │ └─────────────────┘ │ │ │ + * │ │ Re-authentication succeeded ▼ ▼ ▼ + * │ │ ┌──────────────────┐ + * ▼ ▼ │ (postLoginSetup) │ + * ┌─────────────────┐ └──────────────────┘ + * │ PENDING_CLIENT_ │ Account has │ │ │ Account lacks + * │ START │ cross-signing │ │ │ cross-signing + * └─────────────────┘ keys │ │ │ keys + * │ │ │ │ │ + * │ └───────────────────────────────┐ │ │ │ + * │ Client started, │ │ │ └──────┐ + * │ force_verification pending │ │ │ │ + * │ ▼ │ │ │ + * │ Client started, ┌─────────────────┐ │ │ │ + * │ force_verification │ COMPLETE_ │◄────────────┘ │ ▼ + * │ not needed │ SECURITY │ │ ┌─────────────────┐ + * │ └─────────────────┘ │ │ E2E_SETUP │ + * │ │ │ │ │ + * │ ┌─────────────────────────────────────┘ E2EE not enabled │ └─────────────────┘ + * │ │ ┌─────────────────────────────────────────────────────────────┘ │ + * │ │ │ ┌──────────────────────────────────────────────────────────────────────┘ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * ▼ ▼ ▼ ▼ + * ┌─────────────────┐ + * │ LOGGED_IN │ + * │ │ + * └─────────────────┘ * * (from all other states) * │ @@ -102,6 +112,12 @@ enum Views { // flow to setup SSSS / cross-signing on this account 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 // includes guests users as they too are logged in at the client level. LOGGED_IN, diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 0a423fd3aa..255878447d 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1535,18 +1535,27 @@ export default class MatrixChat extends React.PureComponent { this.stores.client = MatrixClientPeg.safeGet(); StorageManager.tryPersistStorage(); - // If we're in the middle of a login/registration, we wait for it to complete before transitioning to the logged - // in view the login flow will call `postLoginSetup` when it's done, which will arrange for `onShowPostLoginScreen` - // to be called. + // If we're loading the app for the first time, we can now transition to a splash screen while we wait for the + // client to start. The exceptions are: + // + // - 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 ( - !this.tokenLogin && - !Lifecycle.isSoftLogout() && - this.state.view !== Views.LOGIN && - this.state.view !== Views.REGISTER && - this.state.view !== Views.COMPLETE_SECURITY && - this.state.view !== Views.E2E_SETUP + (this.state.view === Views.LOADING && !Lifecycle.isSoftLogout() && !this.tokenLogin) || + this.state.view === Views.SOFT_LOGOUT ) { - this.onShowPostLoginScreen(); + this.setStateForNewView({ view: Views.PENDING_CLIENT_START }); } } @@ -1779,15 +1788,6 @@ export default class MatrixChat extends React.PureComponent { const cli = MatrixClientPeg.safeGet(); 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(); if (crypto) { @@ -1804,6 +1804,19 @@ export default class MatrixChat extends React.PureComponent { this.setState({ 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 { @@ -2162,6 +2175,15 @@ export default class MatrixChat extends React.PureComponent { view = ; } else if (this.state.view === Views.E2E_SETUP) { view = ; + } 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 = ( + + ); } else 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`,