diff --git a/src/common/base-store.ts b/src/common/base-store.ts index f7ad946bfd..3c481d8bdc 100644 --- a/src/common/base-store.ts +++ b/src/common/base-store.ts @@ -2,12 +2,13 @@ import path from "path"; import Config from "conf"; import { Options as ConfOptions } from "conf/dist/source/types"; import { app, ipcMain, IpcMainEvent, ipcRenderer, IpcRendererEvent, remote } from "electron"; -import { IReactionOptions, observable, reaction, runInAction, when } from "mobx"; +import { IReactionOptions, observable, reaction, when } from "mobx"; import Singleton from "./utils/singleton"; import { getAppVersion } from "./utils/app-version"; import logger from "../main/logger"; -import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "./ipc"; +import { createTypedSender } from "./ipc"; import isEqual from "lodash/isEqual"; +import { autobind } from "./utils"; export interface BaseStoreParams extends ConfOptions { autoLoad?: boolean; @@ -39,13 +40,15 @@ export abstract class BaseStore extends Singleton { return path.basename(this.storeConfig.path); } - protected get syncRendererChannel() { - return `store-sync-renderer:${this.path}`; - } + protected readonly syncRenderer = createTypedSender({ + channel: `store-sync-renderer:${this.path}`, + verifier: (src: unknown): src is any => true, + }); - protected get syncMainChannel() { - return `store-sync-main:${this.path}`; - } + protected readonly syncMain = createTypedSender({ + channel: `store-sync-main:${this.path}`, + verifier: (src: unknown): src is any => true, + }); get path() { return this.storeConfig.path; @@ -90,55 +93,34 @@ export abstract class BaseStore extends Singleton { enableSync() { this.syncDisposers.push( - reaction(() => this.toJSON(), model => this.onModelChange(model), this.params.syncOptions), + reaction(() => this.toJSON(), this.onModelChange, this.params.syncOptions), ); if (ipcMain) { - const callback = (event: IpcMainEvent, model: T) => { + this.syncDisposers.push(this.syncMain.on((event: IpcMainEvent, model: T) => { logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model }); this.onSync(model); - }; - - subscribeToBroadcast(this.syncMainChannel, callback); - this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback)); + })); } if (ipcRenderer) { - const callback = (event: IpcRendererEvent, model: T) => { + this.syncDisposers.push(this.syncRenderer.on((event: IpcRendererEvent, model: T) => { logger.silly(`[STORE]: SYNC ${this.name} from main`, { model }); - this.onSyncFromMain(model); - }; + this.disableSync(); + this.onSync(model); - subscribeToBroadcast(this.syncRendererChannel, callback); - this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback)); + if (this.params.syncEnabled) { + this.enableSync(); + } + })); } } - protected onSyncFromMain(model: T) { - this.applyWithoutSync(() => { - this.onSync(model); - }); - } - - unregisterIpcListener() { - ipcRenderer.removeAllListeners(this.syncMainChannel); - ipcRenderer.removeAllListeners(this.syncRendererChannel); - } - disableSync() { this.syncDisposers.forEach(dispose => dispose()); this.syncDisposers.length = 0; } - protected applyWithoutSync(callback: () => void) { - this.disableSync(); - runInAction(callback); - - if (this.params.syncEnabled) { - this.enableSync(); - } - } - protected onSync(model: T) { // todo: use "resourceVersion" if merge required (to avoid equality checks => better performance) if (!isEqual(this.toJSON(), model)) { @@ -146,12 +128,13 @@ export abstract class BaseStore extends Singleton { } } + @autobind() protected async onModelChange(model: T) { if (ipcMain) { this.saveToFile(model); // save config file - broadcastMessage(this.syncRendererChannel, model); + this.syncRenderer.broadcast(model); } else { - broadcastMessage(this.syncMainChannel, model); + this.syncMain.broadcast(model); } } diff --git a/src/common/cluster-store.ts b/src/common/cluster-store.ts index 241247f60c..43d7b6aae0 100644 --- a/src/common/cluster-store.ts +++ b/src/common/cluster-store.ts @@ -171,7 +171,6 @@ export class ClusterStore extends BaseStore { } unregisterIpcListener() { - super.unregisterIpcListener(); unsubscribeAllFromBroadcast("cluster:state"); } diff --git a/src/common/ipc/type-enforced-ipc.ts b/src/common/ipc/type-enforced-ipc.ts index 7f4458204d..f7208a4cb8 100644 --- a/src/common/ipc/type-enforced-ipc.ts +++ b/src/common/ipc/type-enforced-ipc.ts @@ -188,12 +188,14 @@ export function createTypedInvoker< }; } +type Disposer = () => void; + export interface TypedSender< Args extends any[] > { broadcast: (...args: Args) => void, - on: (listener: IpcListener) => void, - once: (listener: IpcListener) => void, + on: (listener: IpcListener) => Disposer, + once: (listener: IpcListener) => Disposer, } export function createTypedSender< @@ -205,25 +207,31 @@ export function createTypedSender< channel: string, verifier: ListVerifier, }): TypedSender { + const source = ipcMain ?? ipcRenderer; + return { broadcast(...args) { broadcastMessage(channel, ...args); }, on(listener) { onCorrect({ - source: ipcMain ?? ipcRenderer, + source, channel, listener, verifier: verifier as ListVerifier>, }); + + return () => source.removeListener(channel, listener); }, once(listener) { onceCorrect({ - source: ipcMain ?? ipcRenderer, + source, channel, listener, verifier: verifier as ListVerifier>, }); + + return () => source.removeListener(channel, listener); } }; } diff --git a/src/common/utils/type-narrowing.ts b/src/common/utils/type-narrowing.ts index 7458586f77..896a05183b 100644 --- a/src/common/utils/type-narrowing.ts +++ b/src/common/utils/type-narrowing.ts @@ -1,4 +1,4 @@ -type TypeGuard = (arg: unknown) => arg is T; +export type TypeGuard = (arg: unknown) => arg is T; type Rest = T extends [any, ...infer R] ? R : any; type First = T extends [infer R, ...any[]] ? R : any; type TypeGuardReturnType src is any> = T extends (src: unknown) => src is infer R ? R : any;