Remove boilerplate around dispatcher and settings watchers (#28338)

* Remove boilerplate around dispatcher and settings watchers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2024-11-01 15:15:04 +00:00 committed by GitHub
parent b8fd98ab3c
commit 2d9982f9f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 81 additions and 111 deletions

View file

@ -113,13 +113,9 @@ export default class DeviceListener {
this.client.removeListener(ClientEvent.Sync, this.onSync);
this.client.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
}
if (this.deviceClientInformationSettingWatcherRef) {
SettingsStore.unwatchSetting(this.deviceClientInformationSettingWatcherRef);
}
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
this.dispatcherRef = undefined;
}
this.dismissed.clear();
this.dismissedThisDeviceToast = false;
this.keyBackupInfo = null;

View file

@ -326,7 +326,7 @@ export class PosthogAnalytics {
if (this.enabled) {
this.posthog.reset();
}
if (this.watchSettingRef) SettingsStore.unwatchSetting(this.watchSettingRef);
SettingsStore.unwatchSetting(this.watchSettingRef);
this.setAnonymity(Anonymity.Disabled);
}

View file

@ -20,9 +20,9 @@ import { ActionPayload } from "./dispatcher/payloads";
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
class Presence {
private unavailableTimer: Timer | null = null;
private dispatcherRef: string | null = null;
private state: SetPresence | null = null;
private unavailableTimer?: Timer;
private dispatcherRef?: string;
private state?: SetPresence;
/**
* Start listening the user activity to evaluate his presence state.
@ -46,14 +46,10 @@ class Presence {
* Stop tracking user activity
*/
public stop(): void {
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
this.dispatcherRef = null;
}
if (this.unavailableTimer) {
this.unavailableTimer.abort();
this.unavailableTimer = null;
}
this.dispatcherRef = undefined;
this.unavailableTimer?.abort();
this.unavailableTimer = undefined;
}
/**
@ -61,7 +57,7 @@ class Presence {
* @returns {string} the presence state (see PRESENCE enum)
*/
public getState(): SetPresence | null {
return this.state;
return this.state ?? null;
}
private onAction = (payload: ActionPayload): void => {

View file

@ -38,7 +38,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
public static contextType = MatrixClientContext;
public declare context: React.ContextType<typeof MatrixClientContext>;
private unmounted = false;
private dispatcherRef: string | null = null;
private dispatcherRef?: string;
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
super(props, context);
@ -100,7 +100,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
public componentWillUnmount(): void {
this.unmounted = true;
if (this.dispatcherRef !== null) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
}
private onAction = (payload: ActionPayload): void => {

View file

@ -228,9 +228,9 @@ class LoggedInView extends React.Component<IProps, IState> {
this._matrixClient.removeListener(ClientEvent.Sync, this.onSync);
this._matrixClient.removeListener(RoomStateEvent.Events, this.onRoomStateEvents);
OwnProfileStore.instance.off(UPDATE_EVENT, this.refreshBackgroundImage);
if (this.layoutWatcherRef) SettingsStore.unwatchSetting(this.layoutWatcherRef);
if (this.compactLayoutWatcherRef) SettingsStore.unwatchSetting(this.compactLayoutWatcherRef);
if (this.backgroundImageWatcherRef) SettingsStore.unwatchSetting(this.backgroundImageWatcherRef);
SettingsStore.unwatchSetting(this.layoutWatcherRef);
SettingsStore.unwatchSetting(this.compactLayoutWatcherRef);
SettingsStore.unwatchSetting(this.backgroundImageWatcherRef);
this.timezoneProfileUpdateRef?.forEach((s) => SettingsStore.unwatchSetting(s));
this.resizer?.detach();
}

View file

@ -77,7 +77,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public declare context: React.ContextType<typeof RoomContext>;
private dispatcherRef: string | null = null;
private dispatcherRef?: string;
private readonly layoutWatcherRef: string;
private timelinePanel = createRef<TimelinePanel>();
private card = createRef<HTMLDivElement>();
@ -118,7 +118,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
const roomId = this.props.mxEvent.getRoomId();
SettingsStore.unwatchSetting(this.layoutWatcherRef);

View file

@ -121,9 +121,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef);
if (this.dndWatcherRef) SettingsStore.unwatchSetting(this.dndWatcherRef);
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
SettingsStore.unwatchSetting(this.themeWatcherRef);
SettingsStore.unwatchSetting(this.dndWatcherRef);
defaultDispatcher.unregister(this.dispatcherRef);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
this.context.voiceBroadcastRecordingsStore.off(

View file

@ -80,9 +80,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
}
MatrixClientPeg.get()?.removeListener(RoomEvent.Name, this.onRoomName);
MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onStateEvent);

View file

@ -340,13 +340,13 @@ export default class AppTile extends React.Component<IProps, IState> {
}
// Widget action listeners
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
if (this.props.room) {
this.context.off(RoomEvent.MyMembership, this.onMyMembership);
}
if (this.allowedWidgetsWatchRef) SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
SettingsStore.unwatchSetting(this.allowedWidgetsWatchRef);
OwnProfileStore.instance.removeListener(UPDATE_EVENT, this.onUserReady);
}

View file

@ -71,7 +71,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.settingWatcherRef) SettingsStore.unwatchSetting(this.settingWatcherRef);
SettingsStore.unwatchSetting(this.settingWatcherRef);
}
private onContextMenuOpenClick = (e: ButtonEvent): void => {

View file

@ -368,7 +368,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
this.unmounted = true;
MatrixClientPeg.get()?.off(ClientEvent.Sync, this.reconnectedListener);
this.clearBlurhashTimeout();
if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher);
SettingsStore.unwatchSetting(this.sizeWatcher);
if (this.state.isAnimated && this.state.thumbUrl) {
URL.revokeObjectURL(this.state.thumbUrl);
}

View file

@ -175,7 +175,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
}
public componentWillUnmount(): void {
if (this.sizeWatcher) SettingsStore.unwatchSetting(this.sizeWatcher);
SettingsStore.unwatchSetting(this.sizeWatcher);
}
private videoOnPlay = async (): Promise<void> => {

View file

@ -100,14 +100,10 @@ export default class TimelineCard extends React.Component<IProps, IState> {
public componentWillUnmount(): void {
SdkContextClass.instance.roomViewStore.removeListener(UPDATE_EVENT, this.onRoomViewStoreUpdate);
if (this.readReceiptsSettingWatcher) {
SettingsStore.unwatchSetting(this.readReceiptsSettingWatcher);
}
if (this.layoutWatcherRef) {
SettingsStore.unwatchSetting(this.layoutWatcherRef);
}
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
}
private onRoomViewStoreUpdate = async (_initial?: boolean): Promise<void> => {

View file

@ -82,7 +82,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
this.unmounted = true;
ScalarMessaging.stopListening();
WidgetLayoutStore.instance.off(WidgetLayoutStore.emissionForRoom(this.props.room), this.updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
if (this.resizeContainer) {
this.resizer.detach();
}

View file

@ -331,7 +331,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
public componentWillUnmount(): void {
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);

View file

@ -446,7 +446,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
public componentWillUnmount(): void {
SpaceStore.instance.off(UPDATE_SUGGESTED_ROOMS, this.updateSuggestedRooms);
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.updateLists);
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
defaultDispatcher.unregister(this.dispatcherRef);
SdkContextClass.instance.roomViewStore.off(UPDATE_EVENT, this.onRoomViewStoreUpdate);
}

View file

@ -248,7 +248,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
defaultDispatcher.unregister(this.dispatcherRef);
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onListsUpdated);
RoomListStore.instance.off(LISTS_LOADING_EVENT, this.onListsLoading);
this.tilesRef.current?.removeEventListener("scroll", this.onScrollPrevent);

View file

@ -175,7 +175,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
this.onRoomPreviewChanged,
);
this.props.room.off(RoomEvent.Name, this.onRoomNameUpdate);
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
defaultDispatcher.unregister(this.dispatcherRef);
this.notificationState.off(NotificationStateEvents.Update, this.onNotificationUpdate);
this.roomProps.off(PROPERTY_UPDATED, this.onRoomPropertyUpdate);
CallStore.instance.off(CallStoreEvent.Call, this.onCallChanged);

View file

@ -141,10 +141,8 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
if (client) client.removeListener(ClientEvent.AccountData, this.updateWidget);
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
window.removeEventListener("resize", this.onResize);
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
}
}
public componentDidUpdate(): void {
this.sendVisibilityToWidget(this.props.isStickerPickerOpen);

View file

@ -79,10 +79,8 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
public componentWillUnmount(): void {
this.unmounted = true;
if (this.layoutWatcherRef) {
SettingsStore.unwatchSetting(this.layoutWatcherRef);
}
}
/**
* Save the new font size

View file

@ -52,7 +52,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
}
public componentWillUnmount(): void {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
document.removeEventListener("keydown", this.onKeyDown);
}

View file

@ -101,7 +101,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
}
public componentWillUnmount(): void {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
}
private onAction = (payload: ActionPayload): void => {

View file

@ -129,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
}
public componentWillUnmount(): void {
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
MatrixClientPeg.safeGet().removeListener(RoomEvent.MyMembership, this.onMyMembership);
}

View file

@ -126,7 +126,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
document.removeEventListener("keydown", this.onNativeKeyDown);
this.updateCallListeners(this.props.call, null);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
dis.unregister(this.dispatcherRef);
}
public static getDerivedStateFromProps(props: IProps): Partial<IState> {

View file

@ -45,8 +45,11 @@ export class MatrixDispatcher {
/**
* Removes a callback based on its token.
* @param id The token that was returned by `register`.
* Can be undefined to avoid needing an if around every caller.
*/
public unregister(id: DispatchToken): void {
public unregister(id: DispatchToken | undefined): void {
if (!id) return;
invariant(this.callbacks.has(id), `Dispatcher.unregister(...): '${id}' does not map to a registered callback.`);
this.callbacks.delete(id);
}

View file

@ -22,12 +22,12 @@ import { Action } from "../dispatcher/actions";
// TODO: Move this and related files to the js-sdk or something once finalized.
export class Mjolnir {
private static instance: Mjolnir | null = null;
private static instance?: Mjolnir;
private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention
private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention
private mjolnirWatchRef: string | null = null;
private dispatcherRef: string | null = null;
private mjolnirWatchRef?: string;
private dispatcherRef?: string;
public get roomIds(): string[] {
return this._roomIds;
@ -61,15 +61,11 @@ export class Mjolnir {
}
public stop(): void {
if (this.mjolnirWatchRef) {
SettingsStore.unwatchSetting(this.mjolnirWatchRef);
this.mjolnirWatchRef = null;
}
this.mjolnirWatchRef = undefined;
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
this.dispatcherRef = null;
}
this.dispatcherRef = undefined;
MatrixClientPeg.get()?.removeListener(RoomStateEvent.Events, this.onEvent);
}

View file

@ -643,8 +643,8 @@ export class ElementCall extends Call {
public static readonly MEMBER_EVENT_TYPE = new NamespacedValue(null, EventType.GroupCallMemberPrefix);
public readonly STUCK_DEVICE_TIMEOUT_MS = 1000 * 60 * 60; // 1 hour
private settingsStoreCallEncryptionWatcher: string | null = null;
private terminationTimer: number | null = null;
private settingsStoreCallEncryptionWatcher?: string;
private terminationTimer?: number;
private _layout = Layout.Tile;
public get layout(): Layout {
return this._layout;
@ -938,13 +938,9 @@ export class ElementCall extends Call {
this.session.off(MatrixRTCSessionEvent.MembershipsChanged, this.onMembershipChanged);
this.client.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionEnded, this.onRTCSessionEnded);
if (this.settingsStoreCallEncryptionWatcher) {
SettingsStore.unwatchSetting(this.settingsStoreCallEncryptionWatcher);
}
if (this.terminationTimer !== null) {
clearTimeout(this.terminationTimer);
this.terminationTimer = null;
}
this.terminationTimer = undefined;
super.destroy();
}

View file

@ -192,10 +192,11 @@ export default class SettingsStore {
/**
* Stops the SettingsStore from watching a setting. This is a no-op if the watcher
* provided is not found.
* @param {string} watcherReference The watcher reference (received from #watchSetting)
* to cancel.
* @param watcherReference The watcher reference (received from #watchSetting) to cancel.
* Can be undefined to avoid needing an if around every caller.
*/
public static unwatchSetting(watcherReference: string): void {
public static unwatchSetting(watcherReference: string | undefined): void {
if (!watcherReference) return;
if (!SettingsStore.watchers.has(watcherReference)) {
logger.warn(`Ending non-existent watcher ID ${watcherReference}`);
return;

View file

@ -28,11 +28,7 @@ export class FontWatcher implements IWatcher {
*/
public static readonly DEFAULT_DELTA = 0;
private dispatcherRef: string | null;
public constructor() {
this.dispatcherRef = null;
}
private dispatcherRef?: string;
public async start(): Promise<void> {
this.updateFont();
@ -148,7 +144,6 @@ export class FontWatcher implements IWatcher {
}
public stop(): void {
if (!this.dispatcherRef) return;
dis.unregister(this.dispatcherRef);
}

View file

@ -18,9 +18,9 @@ import { ActionPayload } from "../../dispatcher/payloads";
import { SettingLevel } from "../SettingLevel";
export default class ThemeWatcher {
private themeWatchRef: string | null;
private systemThemeWatchRef: string | null;
private dispatcherRef: string | null;
private themeWatchRef?: string;
private systemThemeWatchRef?: string;
private dispatcherRef?: string;
private preferDark: MediaQueryList;
private preferLight: MediaQueryList;
@ -29,10 +29,6 @@ export default class ThemeWatcher {
private currentTheme: string;
public constructor() {
this.themeWatchRef = null;
this.systemThemeWatchRef = null;
this.dispatcherRef = null;
// we have both here as each may either match or not match, so by having both
// we can get the tristate of dark/light/unsupported
this.preferDark = (<any>global).matchMedia("(prefers-color-scheme: dark)");
@ -55,9 +51,9 @@ export default class ThemeWatcher {
this.preferDark.removeEventListener("change", this.onChange);
this.preferLight.removeEventListener("change", this.onChange);
this.preferHighContrast.removeEventListener("change", this.onChange);
if (this.systemThemeWatchRef) SettingsStore.unwatchSetting(this.systemThemeWatchRef);
if (this.themeWatchRef) SettingsStore.unwatchSetting(this.themeWatchRef);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
SettingsStore.unwatchSetting(this.systemThemeWatchRef);
SettingsStore.unwatchSetting(this.themeWatchRef);
dis.unregister(this.dispatcherRef);
}
private onChange = (): void => {

View file

@ -65,7 +65,7 @@ export abstract class AsyncStore<T extends object> extends EventEmitter {
* Stops the store's listening functions, such as the listener to the dispatcher.
*/
protected stop(): void {
if (this.dispatcherRef) this.dispatcher.unregister(this.dispatcherRef);
this.dispatcher.unregister(this.dispatcherRef);
}
/**

View file

@ -23,7 +23,7 @@ export abstract class AsyncStoreWithClient<T extends object> extends AsyncStore<
const asyncStore = this; // eslint-disable-line @typescript-eslint/no-this-alias
this.readyStore = new (class extends ReadyWatchingStore {
public get mxClient(): MatrixClient | null {
return this.matrixClient;
return this.matrixClient ?? null;
}
protected async onReady(): Promise<any> {

View file

@ -142,7 +142,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
this.matrixClient.removeListener(BeaconEvent.Destroy, this.onDestroyBeacon);
this.matrixClient.removeListener(RoomStateEvent.Members, this.onRoomStateMembers);
}
SettingsStore.unwatchSetting(this.dynamicWatcherRef ?? "");
SettingsStore.unwatchSetting(this.dynamicWatcherRef);
this.clearBeacons();
}

View file

@ -16,8 +16,8 @@ import { Action } from "../dispatcher/actions";
import { MatrixDispatcher } from "../dispatcher/dispatcher";
export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable {
protected matrixClient: MatrixClient | null = null;
private dispatcherRef: string | null = null;
protected matrixClient?: MatrixClient;
private dispatcherRef?: string;
public constructor(protected readonly dispatcher: MatrixDispatcher) {
super();
@ -35,7 +35,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
}
public get mxClient(): MatrixClient | null {
return this.matrixClient; // for external readonly access
return this.matrixClient ?? null; // for external readonly access
}
public useUnitTestClient(cli: MatrixClient): void {
@ -43,7 +43,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
}
public destroy(): void {
if (this.dispatcherRef !== null) this.dispatcher.unregister(this.dispatcherRef);
this.dispatcher.unregister(this.dispatcherRef);
}
protected async onReady(): Promise<void> {
@ -80,7 +80,7 @@ export abstract class ReadyWatchingStore extends EventEmitter implements IDestro
} else if (payload.action === "on_client_not_viable" || payload.action === Action.OnLoggedOut) {
if (this.matrixClient) {
await this.onNotReady();
this.matrixClient = null;
this.matrixClient = undefined;
}
}
};

View file

@ -91,9 +91,9 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
this.byRoom = new MapWithDefault(() => new Map());
this.matrixClient?.off(RoomStateEvent.Events, this.updateRoomFromState);
if (this.pinnedRef) SettingsStore.unwatchSetting(this.pinnedRef);
if (this.layoutRef) SettingsStore.unwatchSetting(this.layoutRef);
if (this.dynamicRef) SettingsStore.unwatchSetting(this.dynamicRef);
SettingsStore.unwatchSetting(this.pinnedRef);
SettingsStore.unwatchSetting(this.layoutRef);
SettingsStore.unwatchSetting(this.dynamicRef);
WidgetStore.instance.off(UPDATE_EVENT, this.updateFromWidgetStore);
}

View file

@ -39,6 +39,7 @@ jest.mock("matrix-js-sdk/src/logger");
jest.mock("../../src/dispatcher/dispatcher", () => ({
dispatch: jest.fn(),
register: jest.fn(),
unregister: jest.fn(),
}));
jest.mock("../../src/SecurityManager", () => ({