Emit events from DeviceListenerCurrentDevice instead of delegating to DeviceListener (#32451)

* Emit events from DeviceListenerCurrentDevice instead of delegating to DeviceListener

* Make CurrentDeviceEvents a const enum

* Break out event emitting into its own class so the instance can be persistent
This commit is contained in:
Andy Balaam
2026-02-19 16:43:42 +00:00
committed by GitHub
parent 0b0ac65149
commit 88c7d1ea66
5 changed files with 75 additions and 21 deletions
@@ -24,7 +24,7 @@ import { RecoveryPanelOutOfSync } from "../../encryption/RecoveryPanelOutOfSync"
import { useTypedEventEmitterState } from "../../../../../hooks/useEventEmitter"; import { useTypedEventEmitterState } from "../../../../../hooks/useEventEmitter";
import { KeyStoragePanel } from "../../encryption/KeyStoragePanel"; import { KeyStoragePanel } from "../../encryption/KeyStoragePanel";
import { DeleteKeyStoragePanel } from "../../encryption/DeleteKeyStoragePanel"; import { DeleteKeyStoragePanel } from "../../encryption/DeleteKeyStoragePanel";
import { DeviceListener, DeviceListenerEvents, type DeviceState } from "../../../../../device-listener"; import { DeviceListener, CurrentDeviceEvents, type DeviceState } from "../../../../../device-listener";
import { useKeyStoragePanelViewModel } from "../../../../viewmodels/settings/encryption/KeyStoragePanelViewModel"; import { useKeyStoragePanelViewModel } from "../../../../viewmodels/settings/encryption/KeyStoragePanelViewModel";
/** /**
@@ -64,8 +64,8 @@ export function EncryptionUserSettingsTab({ initialState = "main" }: Readonly<Pr
const [state, setState] = useState<State>(initialState); const [state, setState] = useState<State>(initialState);
const deviceState = useTypedEventEmitterState( const deviceState = useTypedEventEmitterState(
DeviceListener.sharedInstance(), DeviceListener.sharedInstance().currentDeviceChangedEmitter,
DeviceListenerEvents.DeviceState, CurrentDeviceEvents.DeviceStateChanged,
(state?: DeviceState): DeviceState => { (state?: DeviceState): DeviceState => {
return state ?? DeviceListener.sharedInstance().getDeviceState(); return state ?? DeviceListener.sharedInstance().getDeviceState();
}, },
@@ -0,0 +1,36 @@
/*
Copyright 2026 Element Creations Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
import { type DeviceState } from ".";
/**
* The events emitted when the {@link DeviceState} of the current device
* changes.
*/
export const enum CurrentDeviceEvents {
DeviceStateChanged = "device_state",
}
/**
* You must provide one of these if you listen to {@link CurrentDeviceEvents}
* emitted by {@link DeviceListenerCurrentDevice}. It specifies how to handle
* each type of event.
*/
type EventHandlerMap = {
[CurrentDeviceEvents.DeviceStateChanged]: (state: DeviceState) => void;
};
/**
* Emits events when the current device changes state.
*/
export class CurrentDeviceChangedEmitter extends TypedEventEmitter<CurrentDeviceEvents, EventHandlerMap> {
public onStateChanged(newState: DeviceState): void {
this.emit(CurrentDeviceEvents.DeviceStateChanged, newState);
}
}
+33 -16
View File
@@ -7,7 +7,7 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
import { type MatrixClient, ClientStoppedError, TypedEventEmitter } from "matrix-js-sdk/src/matrix"; import { type MatrixClient, ClientStoppedError } from "matrix-js-sdk/src/matrix";
import { logger as baseLogger, LogSpan } from "matrix-js-sdk/src/logger"; import { logger as baseLogger, LogSpan } from "matrix-js-sdk/src/logger";
import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange"; import { type CryptoSessionStateChange } from "@matrix-org/analytics-events/types/typescript/CryptoSessionStateChange";
import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { secureRandomString } from "matrix-js-sdk/src/randomstring";
@@ -20,22 +20,16 @@ import SdkConfig from "../SdkConfig";
import PlatformPeg from "../PlatformPeg"; import PlatformPeg from "../PlatformPeg";
import { recordClientInformation, removeClientInformation } from "../utils/device/clientInformation"; import { recordClientInformation, removeClientInformation } from "../utils/device/clientInformation";
import SettingsStore, { type CallbackFn } from "../settings/SettingsStore"; import SettingsStore, { type CallbackFn } from "../settings/SettingsStore";
import { DeviceListenerOtherDevices, DeviceListenerCurrentDevice, type DeviceState } from "."; import {
DeviceListenerOtherDevices,
DeviceListenerCurrentDevice,
type DeviceState,
CurrentDeviceChangedEmitter,
} from ".";
const logger = baseLogger.getChild("DeviceListener:"); const logger = baseLogger.getChild("DeviceListener:");
/** export class DeviceListener {
* The events emitted by {@link DeviceListener}
*/
export enum DeviceListenerEvents {
DeviceState = "device_state",
}
type EventHandlerMap = {
[DeviceListenerEvents.DeviceState]: (state: DeviceState) => void;
};
export class DeviceListener extends TypedEventEmitter<DeviceListenerEvents, EventHandlerMap> {
private dispatcherRef?: string; private dispatcherRef?: string;
/** /**
@@ -44,11 +38,34 @@ export class DeviceListener extends TypedEventEmitter<DeviceListenerEvents, Even
*/ */
public otherDevices?: DeviceListenerOtherDevices; public otherDevices?: DeviceListenerOtherDevices;
/** All the information about whether this device's encrypytion is OK. Only /**
* set if `running` is true, otherwise undefined. * All the information about whether this device's encrypytion is OK.
*
* Defined after {@link DeviceListener.start} has been called, before {@link
* DeviceListener.stop} is called.
*/ */
public currentDevice?: DeviceListenerCurrentDevice; public currentDevice?: DeviceListenerCurrentDevice;
/**
* Emits `CurrentDeviceEvents` events when the state
* of the current device changes.
*
* @example
* ```ts
* const deviceState = useTypedEventEmitterState(
* DeviceListener.sharedInstance().currentDeviceChangedEmitter,
* CurrentDeviceEvents.DeviceStateChanged,
* (state?: DeviceState): DeviceState => {
* return state ?? DeviceListener.sharedInstance().getDeviceState();
* },
* );
* ```
*
* Now `deviceState` will reflect the state of the current device and
* trigger an update when it changes.
*/
public currentDeviceChangedEmitter = new CurrentDeviceChangedEmitter();
private running = false; private running = false;
// The client with which the instance is running. Only set if `running` is true, otherwise undefined. // The client with which the instance is running. Only set if `running` is true, otherwise undefined.
private client?: MatrixClient; private client?: MatrixClient;
@@ -18,7 +18,7 @@ import {
ClientEvent, ClientEvent,
} from "matrix-js-sdk/src/matrix"; } from "matrix-js-sdk/src/matrix";
import { type DeviceListener, type DeviceState, DeviceListenerEvents } from "."; import { type DeviceListener, type DeviceState } from ".";
import { import {
hideToast as hideSetupEncryptionToast, hideToast as hideSetupEncryptionToast,
showToast as showSetupEncryptionToast, showToast as showSetupEncryptionToast,
@@ -263,7 +263,7 @@ export class DeviceListenerCurrentDevice {
private async setDeviceState(newState: DeviceState, logSpan: LogSpan): Promise<void> { private async setDeviceState(newState: DeviceState, logSpan: LogSpan): Promise<void> {
this.deviceState = newState; this.deviceState = newState;
this.deviceListener.emit(DeviceListenerEvents.DeviceState, newState); this.deviceListener.currentDeviceChangedEmitter.onStateChanged(newState);
if (newState === "ok" || this.dismissedThisDeviceToast) { if (newState === "ok" || this.dismissedThisDeviceToast) {
hideSetupEncryptionToast(); hideSetupEncryptionToast();
+1
View File
@@ -5,6 +5,7 @@ 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. Please see LICENSE files in the repository root for full details.
*/ */
export * from "./CurrentDeviceChangedEmitter";
export * from "./DeviceListener"; export * from "./DeviceListener";
export * from "./DeviceListenerCurrentDevice"; export * from "./DeviceListenerCurrentDevice";
export * from "./DeviceListenerOtherDevices"; export * from "./DeviceListenerOtherDevices";