Documentation and symbolic constants for dispatcher actions (#31278)

* Remove unreachable code

`view_last_screen` is never used.

* Remove unused action `user_activity_started`

Nothing listens to this, so it's pointless.

* Symbolic constant for `Action.UserActivity`

* Define symbolic constants for more `Action`s

Constants for some actions that are emitted by `Lifecycle`
This commit is contained in:
Richard van der Hoff 2025-11-20 18:18:04 +00:00 committed by GitHub
parent c203f02731
commit 1285b73be6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 60 additions and 40 deletions

View File

@ -88,7 +88,7 @@ export default abstract class BasePlatform {
protected onAction(payload: ActionPayload): void { protected onAction(payload: ActionPayload): void {
switch (payload.action) { switch (payload.action) {
case "on_client_not_viable": case Action.ClientNotViable:
case Action.OnLoggedOut: case Action.OnLoggedOut:
this.setNotificationCount(0); this.setNotificationCount(0);
break; break;

View File

@ -1001,7 +1001,7 @@ export function softLogout(): void {
// Ensure that we dispatch a view change **before** stopping the client so // Ensure that we dispatch a view change **before** stopping the client so
// so that React components unmount first. This avoids React soft crashes // so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client. // that can occur when components try to use a null client.
dis.dispatch({ action: "on_client_not_viable" }); // generic version of on_logged_out dis.dispatch({ action: Action.ClientNotViable }); // generic version of on_logged_out
stopMatrixClient(/*unsetClient=*/ false); stopMatrixClient(/*unsetClient=*/ false);
// DO NOT CALL LOGOUT. A soft logout preserves data, logout does not. // DO NOT CALL LOGOUT. A soft logout preserves data, logout does not.
@ -1034,7 +1034,7 @@ async function startMatrixClient(
// to add listeners for the 'sync' event so otherwise we'd have // to add listeners for the 'sync' event so otherwise we'd have
// a race condition (and we need to dispatch synchronously for this // a race condition (and we need to dispatch synchronously for this
// to work). // to work).
dis.dispatch({ action: "will_start_client" }, true); dis.dispatch({ action: Action.WillStartClient }, true);
// reset things first just in case // reset things first just in case
SdkContextClass.instance.typingStore.reset(); SdkContextClass.instance.typingStore.reset();
@ -1080,7 +1080,7 @@ async function startMatrixClient(
// dispatch that we finished starting up to wire up any other bits // dispatch that we finished starting up to wire up any other bits
// of the matrix client that cannot be set prior to starting up. // of the matrix client that cannot be set prior to starting up.
dis.dispatch({ action: "client_started" }); dis.dispatch({ action: Action.ClientStarted });
if (isSoftLogout()) { if (isSoftLogout()) {
softLogout(); softLogout();

View File

@ -15,6 +15,7 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
import dis from "./dispatcher/dispatcher"; import dis from "./dispatcher/dispatcher";
import Timer from "./utils/Timer"; import Timer from "./utils/Timer";
import { type ActionPayload } from "./dispatcher/payloads"; import { type ActionPayload } from "./dispatcher/payloads";
import { Action } from "./dispatcher/actions.ts";
// Time in ms after that a user is considered as unavailable/away // Time in ms after that a user is considered as unavailable/away
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
@ -61,7 +62,7 @@ class Presence {
} }
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
if (payload.action === "user_activity") { if (payload.action === Action.UserActivity) {
this.setState(SetPresence.Online); this.setState(SetPresence.Online);
this.unavailableTimer?.restart(); this.unavailableTimer?.restart();
} }

View File

@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import dis from "./dispatcher/dispatcher"; import dis from "./dispatcher/dispatcher";
import Timer from "./utils/Timer"; import Timer from "./utils/Timer";
import { Action } from "./dispatcher/actions.ts";
// important these are larger than the timeouts of timers // important these are larger than the timeouts of timers
// used with UserActivity.timeWhileActive*, // used with UserActivity.timeWhileActive*,
@ -190,11 +191,9 @@ export default class UserActivity {
this.lastScreenY = event.screenY; this.lastScreenY = event.screenY;
} }
dis.dispatch({ action: "user_activity" }); dis.dispatch({ action: Action.UserActivity });
if (!this.activeNowTimeout.isRunning()) { if (!this.activeNowTimeout.isRunning()) {
this.activeNowTimeout.start(); this.activeNowTimeout.start();
dis.dispatch({ action: "user_activity_start" });
UserActivity.runTimersUntilTimeout(this.attachedActiveNowTimers, this.activeNowTimeout); UserActivity.runTimersUntilTimeout(this.attachedActiveNowTimers, this.activeNowTimeout);
} else { } else {
this.activeNowTimeout.restart(); this.activeNowTimeout.restart();

View File

@ -18,6 +18,7 @@ import { MatrixClientPeg } from "../../MatrixClientPeg";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import { type ActionPayload } from "../../dispatcher/payloads"; import { type ActionPayload } from "../../dispatcher/payloads";
import { Action } from "../../dispatcher/actions.ts";
interface IProps { interface IProps {
// URL to request embedded page content from // URL to request embedded page content from
@ -109,7 +110,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
private onAction = (payload: ActionPayload): void => { private onAction = (payload: ActionPayload): void => {
// HACK: Workaround for the context's MatrixClient not being set up at render time. // HACK: Workaround for the context's MatrixClient not being set up at render time.
if (payload.action === "client_started") { if (payload.action === Action.ClientStarted) {
this.forceUpdate(); this.forceUpdate();
} }
}; };

View File

@ -812,13 +812,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
} }
break; break;
} }
case "view_last_screen":
// This function does what we want, despite the name. The idea is that it shows
// the last room we were looking at or some reasonable default/guess. We don't
// have to worry about email invites or similar being re-triggered because the
// function will have cleared that state and not execute that path.
this.showScreenAfterLogin();
break;
case "hide_left_panel": case "hide_left_panel":
this.setState( this.setState(
{ {
@ -856,13 +849,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.onLoggedIn(); this.onLoggedIn();
} }
break; break;
case "on_client_not_viable": case Action.ClientNotViable:
this.onSoftLogout(); this.onSoftLogout();
break; break;
case Action.OnLoggedOut: case Action.OnLoggedOut:
this.onLoggedOut(); this.onLoggedOut();
break; break;
case "will_start_client": case Action.WillStartClient:
this.setState({ ready: false }, () => { this.setState({ ready: false }, () => {
// if the client is about to start, we are, by definition, not ready. // if the client is about to start, we are, by definition, not ready.
// Set ready to false now, then it'll be set to true when the sync // Set ready to false now, then it'll be set to true when the sync
@ -870,7 +863,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.onWillStartClient(); this.onWillStartClient();
}); });
break; break;
case "client_started": case Action.ClientStarted:
// No need to make this handler async to wait for the result of this // No need to make this handler async to wait for the result of this
this.onClientStarted().catch((e) => { this.onClientStarted().catch((e) => {
logger.error("Exception in onClientStarted", e); logger.error("Exception in onClientStarted", e);

View File

@ -316,16 +316,39 @@ export enum Action {
*/ */
ShowRoomTopic = "show_room_topic", ShowRoomTopic = "show_room_topic",
/**
* Fired when the client is no longer viable to use: specifically, that we have been "soft-logged out".
*/
ClientNotViable = "client_not_viable",
/** /**
* Fired when the client was logged out. No additional payload information required. * Fired when the client was logged out. No additional payload information required.
*/ */
OnLoggedOut = "on_logged_out", OnLoggedOut = "on_logged_out",
/** /**
* Fired when the client was logged in. No additional payload information required. * Fired when the client was logged in, or has otherwise been set up with authentication data (e.g., by loading the
* access token from local storage). Note that this does not necessarily mean that a login action has happened,
* just that authentication creds have been set up.
*
* No additional payload information required.
*/ */
OnLoggedIn = "on_logged_in", OnLoggedIn = "on_logged_in",
/**
* Fired when the client is about to be started, shortly after {@link OnLoggedIn}.
*
* No additional payload information required.
*/
WillStartClient = "will_start_client",
/**
* Fired when the client has started, shortly after {@link WillStartClient}.
*
* No additional payload information required.
*/
ClientStarted = "client_started",
/** /**
* Overwrites the existing login with fresh session credentials. Use with a OverwriteLoginPayload. * Overwrites the existing login with fresh session credentials. Use with a OverwriteLoginPayload.
*/ */
@ -380,4 +403,10 @@ export enum Action {
* Open the create room dialog * Open the create room dialog
*/ */
CreateRoom = "view_create_room", CreateRoom = "view_create_room",
/**
* The `UserActivity` tracker determined that there was some activity from the user (typically a mouse movement
* or keyboard event).
*/
UserActivity = "user_activity",
} }

View File

@ -69,7 +69,7 @@ class LifecycleStore extends AsyncStore<IState> {
dis.dispatch(deferredAction); dis.dispatch(deferredAction);
break; break;
} }
case "on_client_not_viable": case Action.ClientNotViable:
case Action.OnLoggedOut: case Action.OnLoggedOut:
this.reset(); this.reset();
break; break;

View File

@ -77,7 +77,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
this.matrixClient = payload.matrixClient; this.matrixClient = payload.matrixClient;
await this.onReady(); await this.onReady();
} }
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) { } else if (payload.action === Action.ClientNotViable || payload.action === Action.OnLoggedOut) {
if (this.matrixClient) { if (this.matrixClient) {
await this.onNotReady(); await this.onNotReady();
this.matrixClient = undefined; this.matrixClient = undefined;

View File

@ -285,7 +285,7 @@ export class RoomViewStore extends EventEmitter {
break; break;
} }
case "on_client_not_viable": case Action.ClientNotViable:
case Action.OnLoggedOut: case Action.OnLoggedOut:
this.reset(); this.reset();
break; break;

View File

@ -218,7 +218,7 @@ export default class ElectronPlatform extends BasePlatform {
this.electron.send("app_onAction", payload); this.electron.send("app_onAction", payload);
} }
if (payload.action === "client_started") { if (payload.action === Action.ClientStarted) {
this.clientStartedPromiseWithResolvers.resolve(); this.clientStartedPromiseWithResolvers.resolve();
} }
} }

View File

@ -53,7 +53,7 @@ export default class WebPlatform extends BasePlatform {
super.onAction(payload); super.onAction(payload);
switch (payload.action) { switch (payload.action) {
case "client_started": case Action.ClientStarted:
// Defer drawing the toast until the client is started as the lifecycle methods reset the ToastStore right before // Defer drawing the toast until the client is started as the lifecycle methods reset the ToastStore right before
this.registerServiceWorkerPromise.catch(this.handleServiceWorkerRegistrationError); this.registerServiceWorkerPromise.catch(this.handleServiceWorkerRegistrationError);
break; break;

View File

@ -547,11 +547,11 @@ describe("<MatrixChat />", () => {
getComponent({ realQueryParams }); getComponent({ realQueryParams });
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "will_start_client", action: Action.WillStartClient,
}); });
// client successfully started // client successfully started
await waitFor(() => await waitFor(() =>
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }), expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ClientStarted }),
); );
// set up keys screen is rendered // set up keys screen is rendered
@ -1172,10 +1172,10 @@ describe("<MatrixChat />", () => {
getComponent({ realQueryParams }); getComponent({ realQueryParams });
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "will_start_client", action: Action.WillStartClient,
}); });
await waitFor(() => await waitFor(() =>
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "client_started" }), expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.ClientStarted }),
); );
// Then we are not allowed in - we are being asked to verify // Then we are not allowed in - we are being asked to verify
@ -1568,7 +1568,7 @@ describe("<MatrixChat />", () => {
it("should continue to post login setup when no session is found in local storage", async () => { it("should continue to post login setup when no session is found in local storage", async () => {
getComponent({ realQueryParams }); getComponent({ realQueryParams });
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "will_start_client", action: Action.WillStartClient,
}); });
// set up keys screen is rendered // set up keys screen is rendered
@ -1828,7 +1828,7 @@ describe("<MatrixChat />", () => {
getComponent({}); getComponent({});
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "will_start_client", action: Action.WillStartClient,
}); });
await flushPromises(); await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code"); mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");
@ -1851,7 +1851,7 @@ describe("<MatrixChat />", () => {
getComponent({}); getComponent({});
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
action: "will_start_client", action: Action.WillStartClient,
}); });
await flushPromises(); await flushPromises();
mockClient.emit(CryptoEvent.KeyBackupFailed, "error code"); mockClient.emit(CryptoEvent.KeyBackupFailed, "error code");

View File

@ -14,6 +14,7 @@ import { Container, WidgetLayoutStore } from "../../../src/stores/widgets/Widget
import { stubClient } from "../../test-utils"; import { stubClient } from "../../test-utils";
import defaultDispatcher from "../../../src/dispatcher/dispatcher"; import defaultDispatcher from "../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../src/settings/SettingsStore"; import SettingsStore from "../../../src/settings/SettingsStore";
import { Action } from "../../../src/dispatcher/actions.ts";
// setup test env values // setup test env values
const roomId = "!room:server"; const roomId = "!room:server";
@ -196,12 +197,7 @@ describe("WidgetLayoutStore", () => {
it("should clear the layout if the client is not viable", () => { it("should clear the layout if the client is not viable", () => {
store.recalculateRoom(mockRoom); store.recalculateRoom(mockRoom);
defaultDispatcher.dispatch( defaultDispatcher.dispatch({ action: Action.ClientNotViable }, true);
{
action: "on_client_not_viable",
},
true,
);
expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]); expect(store.getContainerWidgets(mockRoom, Container.Top)).toEqual([]);
expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]); expect(store.getContainerWidgets(mockRoom, Container.Center)).toEqual([]);

View File

@ -132,7 +132,7 @@ describe("ElectronPlatform", () => {
new ElectronPlatform(); new ElectronPlatform();
dispatcher.dispatch( dispatcher.dispatch(
{ {
action: "client_started", action: Action.ClientStarted,
}, },
true, true,
); );

View File

@ -15,6 +15,7 @@ import { setupLanguageMock } from "../../../setup/setupLanguage";
import ToastStore from "../../../../src/stores/ToastStore.ts"; import ToastStore from "../../../../src/stores/ToastStore.ts";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher.ts"; import defaultDispatcher from "../../../../src/dispatcher/dispatcher.ts";
import { emitPromise } from "../../../test-utils"; import { emitPromise } from "../../../test-utils";
import { Action } from "../../../../src/dispatcher/actions.ts";
fetchMock.config.overwriteRoutes = true; fetchMock.config.overwriteRoutes = true;
@ -49,7 +50,7 @@ describe("WebPlatform", () => {
}; };
new WebPlatform(); new WebPlatform();
defaultDispatcher.dispatch({ action: "client_started" }); defaultDispatcher.dispatch({ action: Action.ClientStarted });
await emitPromise(ToastStore.sharedInstance(), "update"); await emitPromise(ToastStore.sharedInstance(), "update");
const toasts = ToastStore.sharedInstance().getToasts(); const toasts = ToastStore.sharedInstance().getToasts();
expect(toasts).toHaveLength(1); expect(toasts).toHaveLength(1);