From b679693702018fbd28256319c5edde42032dab07 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:58:03 +0000 Subject: [PATCH] Documentation for initialisation/login code (#31297) * Documentation in MatrixChat and Lifecycle * State transition diagram for `View` --- src/Lifecycle.ts | 11 ++++ src/Views.ts | 71 +++++++++++++++++++++++- src/components/structures/MatrixChat.tsx | 51 ++++++++++++++++- 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts index 30638454c2..997119a103 100644 --- a/src/Lifecycle.ts +++ b/src/Lifecycle.ts @@ -599,6 +599,9 @@ async function abortLogin(): Promise { } /** Attempt to restore the session from localStorage or indexeddb. + * + * If the credentials are found, and the session is successfully restored, + * emits {@link Action.OnLoggedIn}, {@link Action.WillStartClient} and {@link Action.StartedClient}. * * @returns true if a session was found; false if no existing session was found. * @@ -787,6 +790,8 @@ async function createOidcTokenRefresher(credentials: IMatrixClientCreds): Promis * optionally clears localstorage, persists new credentials * to localstorage, starts the new client. * + * Emits {@link Action.OnLoggedIn}, {@link Action.WillStartClient} and {@link Action.StartedClient}. + * * @param {IMatrixClientCreds} credentials The credentials to use * @param {Boolean} clearStorageEnabled True to clear storage before starting the new client * @param {Boolean} isFreshLogin True if this is a fresh login, false if it is previous session being restored @@ -1019,6 +1024,12 @@ export function isLoggingOut(): boolean { * Starts the matrix client and all other react-sdk services that * listen for events while a session is logged in. * + * By the time this method is called, we have successfully logged in if necessary, and the client has been set up with + * the access token. + * + * Emits {@link Acction.WillStartClient} before starting the client, and {@link Action.ClientStarted} when the client has + * been started. + * * @param client the matrix client to start * @param startSyncing - `true` to actually start syncing the client. * @param clientPegOpts - Options to pass through to {@link MatrixClientPeg.start}. diff --git a/src/Views.ts b/src/Views.ts index 6c0df53a66..0757b8c9c8 100644 --- a/src/Views.ts +++ b/src/Views.ts @@ -6,7 +6,76 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -/** constants for MatrixChat.state.view */ +/** + * Constants for MatrixChat.state.view. + * + * The `View` is the primary state machine of the application: it has different states for the various setup flows + * that the user may find themselves in. Once we have a functioning client, we can transition to the `LOGGED_IN` state + * which is the "normal" state of the application. + * + * An incomplete state transition diagram follows. + * + * (initial state) + * ┌─────────────────┐ Lock held by other instance ┌─────────────────┐ + * │ 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 │ + * │ │◄────────────────────────────────────────────────────────│ │ + * └─────────────────┘ └─────────────────┘ + * + * (from all other states) + * │ + * │ Session lock stolen + * ▼ + * ┌─────────────────┐ + * │ LOCK_STOLEN │ + * │ │ + * └─────────────────┘ + */ enum 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. diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 95214e2e38..51c5dbeb3f 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -202,7 +202,10 @@ interface IState { hideToSRUsers: boolean; syncError: Error | null; serverConfig?: ValidatedServerConfig; + + /** Has our MatrixClient started? */ ready: boolean; + threepidInvite?: IThreepidInvite; roomOobData?: object; pendingInitialSync?: boolean; @@ -225,7 +228,13 @@ export default class MatrixChat extends React.PureComponent { private firstSyncPromise: PromiseWithResolvers; private screenAfterLogin?: IScreen; + + /** True if we have successfully completed an OIDC or token login. + * + * XXX it's unclear if this is ever cleared, so what happens if the user logs out and then logs back in? + */ private tokenLogin?: boolean; + // What to focus on next component update, if anything private focusNext: FocusNextType; private subTitleStatus: string; @@ -386,6 +395,26 @@ export default class MatrixChat extends React.PureComponent { await Lifecycle.onSessionLockStolen(); } + /** + * Perform actions that are specific to a user that has just logged in (compare {@link onLoggedIn}, which, despite + * its name, is called when an already-logged-in client is restored at session startup). + * + * Called when: + * + * - We successfully completed an OIDC or token login, via {@link initSession}. + * - The {@link Login} or {@link Register} components notify us that we successfully completed a non-OIDC login or + * registration. + * + * In both cases, {@link Action.OnLoggedIn} will already have been emitted, but the call to {@link onLoggedIn} will + * have been suppressed (by either {@link tokenLogin} being set, or the view being set to {@link Views.LOGIN} or + * {@link Views.REGISTER}). + * + * {@link onWillStartClient} and {@link onClientStarted} will already have been called (but not necessarily + * completed). + * + * This method either calls {@link onLiggedIn} directly, or switches to {@link Views.E2E_SETUP} or + * {@link Views.COMPLETE_SECURITY}, which will later call {@link onCompleteSecurityE2eSetupFinished}. + */ private async postLoginSetup(): Promise { const cli = MatrixClientPeg.safeGet(); const cryptoEnabled = Boolean(cli.getCrypto()); @@ -1369,7 +1398,15 @@ export default class MatrixChat extends React.PureComponent { } /** - * Called when a new logged in session has started + * Called when a new logged in session has started. + * + * Called: + * + * - on {@link Action.OnLoggedIn}, but only when we don't expect a separate call to {@link postLoginSetup}. + * - from {@link postLoginSetup}, when we don't have crypto setup tasks to perform after the login. + * + * It's never actually called if we have crypto setup tasks to perform after login (which we normally do, unless + * crypto is disabled.) XXX: is this a bug or a feature? */ private async onLoggedIn(): Promise { ThemeController.isLogin = false; @@ -1379,6 +1416,16 @@ export default class MatrixChat extends React.PureComponent { await this.onShowPostLoginScreen(); } + /** + * Show the first screen after the application is successfully loaded in a logged-in state. + * + * Called: + * + * - by {@link onLoggedIn} + * - by {@link onCompleteSecurityE2eSetupFinished} + * + * In other words, whenever we think we have completed the login and E2E setup tasks. + */ private async onShowPostLoginScreen(): Promise { this.setStateForNewView({ view: Views.LOGGED_IN }); // If a specific screen is set to be shown after login, show that above @@ -2043,7 +2090,7 @@ export default class MatrixChat extends React.PureComponent { PerformanceMonitor.instance.stop(PerformanceEntryNames.REGISTER); }; - // complete security / e2e setup has finished + /** Called when {@link Views.E2E_SETUP} or {@link Views.COMPLETE_SECURITY} have completed. */ private onCompleteSecurityE2eSetupFinished = async (): Promise => { const forceVerify = await this.shouldForceVerification(); if (forceVerify) {