From 4ee29d4e638b6dc5af66582bc44e243237d12655 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 18 Jan 2021 17:41:17 -0700 Subject: [PATCH] Split out ready states of stores to its own store --- src/stores/AsyncStoreWithClient.ts | 54 ++++++++------------ src/stores/ReadyWatchingStore.ts | 81 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 src/stores/ReadyWatchingStore.ts diff --git a/src/stores/AsyncStoreWithClient.ts b/src/stores/AsyncStoreWithClient.ts index 1ed7c6a547..3050555ad4 100644 --- a/src/stores/AsyncStoreWithClient.ts +++ b/src/stores/AsyncStoreWithClient.ts @@ -18,22 +18,33 @@ import { MatrixClient } from "matrix-js-sdk/src/client"; import { AsyncStore } from "./AsyncStore"; import { ActionPayload } from "../dispatcher/payloads"; import { Dispatcher } from "flux"; -import { MatrixClientPeg } from "../MatrixClientPeg"; +import { ReadyWatchingStore } from "./ReadyWatchingStore"; export abstract class AsyncStoreWithClient extends AsyncStore { - protected matrixClient: MatrixClient; - - protected abstract async onAction(payload: ActionPayload); + private readyStore: ReadyWatchingStore; protected constructor(dispatcher: Dispatcher, initialState: T = {}) { super(dispatcher, initialState); - if (MatrixClientPeg.get()) { - this.matrixClient = MatrixClientPeg.get(); + // Create an anonymous class to avoid code duplication + const asyncStore = this; + this.readyStore = new (class extends ReadyWatchingStore { + public get mxClient(): MatrixClient { + return this.matrixClient; + } - // noinspection JSIgnoredPromiseFromCall - this.onReady(); - } + protected async onReady(): Promise { + return asyncStore.onReady(); + } + + protected async onNotReady(): Promise { + return asyncStore.onNotReady(); + } + })(dispatcher); + } + + protected get matrixClient(): MatrixClient { + return this.readyStore.mxClient; } protected async onReady() { @@ -44,30 +55,9 @@ export abstract class AsyncStoreWithClient extends AsyncStore< // Default implementation is to do nothing. } + protected abstract async onAction(payload: ActionPayload); + protected async onDispatch(payload: ActionPayload) { await this.onAction(payload); - - if (payload.action === 'MatrixActions.sync') { - // Only set the client on the transition into the PREPARED state. - // Everything after this is unnecessary (we only need to know once we have a client) - // and we intentionally don't set the client before this point to avoid stores - // updating for every event emitted during the cached sync. - if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) { - return; - } - - if (this.matrixClient !== payload.matrixClient) { - if (this.matrixClient) { - await this.onNotReady(); - } - this.matrixClient = payload.matrixClient; - await this.onReady(); - } - } else if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') { - if (this.matrixClient) { - await this.onNotReady(); - this.matrixClient = null; - } - } } } diff --git a/src/stores/ReadyWatchingStore.ts b/src/stores/ReadyWatchingStore.ts new file mode 100644 index 0000000000..9a28fe75fa --- /dev/null +++ b/src/stores/ReadyWatchingStore.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MatrixClient } from "matrix-js-sdk/src/client"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { ActionPayload } from "../dispatcher/payloads"; +import { Dispatcher } from "flux"; +import { IDestroyable } from "../utils/IDestroyable"; +import { EventEmitter } from "events"; + +export abstract class ReadyWatchingStore extends EventEmitter implements IDestroyable { + protected matrixClient: MatrixClient; + private readonly dispatcherRef: string; + + constructor(protected readonly dispatcher: Dispatcher) { + super(); + + this.dispatcherRef = this.dispatcher.register(this.onAction); + + if (MatrixClientPeg.get()) { + this.matrixClient = MatrixClientPeg.get(); + + // noinspection JSIgnoredPromiseFromCall + this.onReady(); + } + } + + public get mxClient(): MatrixClient { + return this.matrixClient; // for external readonly access + } + + public destroy() { + this.dispatcher.unregister(this.dispatcherRef); + } + + protected async onReady() { + // Default implementation is to do nothing. + } + + protected async onNotReady() { + // Default implementation is to do nothing. + } + + private onAction = async (payload: ActionPayload) => { + if (payload.action === 'MatrixActions.sync') { + // Only set the client on the transition into the PREPARED state. + // Everything after this is unnecessary (we only need to know once we have a client) + // and we intentionally don't set the client before this point to avoid stores + // updating for every event emitted during the cached sync. + if (!(payload.prevState === 'PREPARED' && payload.state !== 'PREPARED')) { + return; + } + + if (this.matrixClient !== payload.matrixClient) { + if (this.matrixClient) { + await this.onNotReady(); + } + this.matrixClient = payload.matrixClient; + await this.onReady(); + } + } else if (payload.action === 'on_client_not_viable' || payload.action === 'on_logged_out') { + if (this.matrixClient) { + await this.onNotReady(); + this.matrixClient = null; + } + } + }; +}