mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Refactor ipc module (#1225)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
c4b98534dc
commit
5aaacb21f9
20 files changed
+224
-242
No files matched your search
+18
-44
@@ -6,7 +6,7 @@ import { action, IReactionOptions, observable, reaction, runInAction, toJS, when
|
||||
import Singleton from "./utils/singleton";
|
||||
import { getAppVersion } from "./utils/app-version";
|
||||
import logger from "../main/logger";
|
||||
import { broadcastIpc, IpcBroadcastParams } from "./ipc";
|
||||
import { broadcastMessage, subscribeToBroadcast, unsubscribeFromBroadcast } from "./ipc";
|
||||
import isEqual from "lodash/isEqual";
|
||||
|
||||
export interface BaseStoreParams<T = any> extends ConfOptions<T> {
|
||||
@@ -37,12 +37,16 @@ export class BaseStore<T = any> extends Singleton {
|
||||
return path.basename(this.storeConfig.path);
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.storeConfig.path;
|
||||
protected get syncRendererChannel() {
|
||||
return `store-sync-renderer:${this.path}`
|
||||
}
|
||||
|
||||
get syncChannel() {
|
||||
return `STORE-SYNC:${this.path}`
|
||||
protected get syncMainChannel() {
|
||||
return `store-sync-main:${this.path}`
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.storeConfig.path;
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
@@ -89,16 +93,16 @@ export class BaseStore<T = any> extends Singleton {
|
||||
logger.silly(`[STORE]: SYNC ${this.name} from renderer`, { model });
|
||||
this.onSync(model);
|
||||
};
|
||||
ipcMain.on(this.syncChannel, callback);
|
||||
this.syncDisposers.push(() => ipcMain.off(this.syncChannel, callback));
|
||||
subscribeToBroadcast(this.syncMainChannel, callback)
|
||||
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncMainChannel, callback));
|
||||
}
|
||||
if (ipcRenderer) {
|
||||
const callback = (event: IpcRendererEvent, model: T) => {
|
||||
logger.silly(`[STORE]: SYNC ${this.name} from main`, { model });
|
||||
this.onSyncFromMain(model);
|
||||
};
|
||||
ipcRenderer.on(this.syncChannel, callback);
|
||||
this.syncDisposers.push(() => ipcRenderer.off(this.syncChannel, callback));
|
||||
subscribeToBroadcast(this.syncRendererChannel, callback)
|
||||
this.syncDisposers.push(() => unsubscribeFromBroadcast(this.syncRendererChannel, callback));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +113,8 @@ export class BaseStore<T = any> extends Singleton {
|
||||
}
|
||||
|
||||
unregisterIpcListener() {
|
||||
ipcRenderer.removeAllListeners(this.syncChannel)
|
||||
ipcRenderer.removeAllListeners(this.syncMainChannel)
|
||||
ipcRenderer.removeAllListeners(this.syncRendererChannel)
|
||||
}
|
||||
|
||||
disableSync() {
|
||||
@@ -135,41 +140,10 @@ export class BaseStore<T = any> extends Singleton {
|
||||
protected async onModelChange(model: T) {
|
||||
if (ipcMain) {
|
||||
this.saveToFile(model); // save config file
|
||||
this.syncToWebViews(model); // send update to renderer views
|
||||
broadcastMessage(this.syncRendererChannel, model)
|
||||
} else {
|
||||
broadcastMessage(this.syncMainChannel, model)
|
||||
}
|
||||
// send "update-request" to main-process
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(this.syncChannel, model);
|
||||
}
|
||||
}
|
||||
|
||||
protected async syncToWebViews(model: T) {
|
||||
const msg: IpcBroadcastParams = {
|
||||
channel: this.syncChannel,
|
||||
args: [model],
|
||||
}
|
||||
broadcastIpc(msg); // send to all windows (BrowserWindow, webContents)
|
||||
const frames = await this.getSubFrames();
|
||||
frames.forEach(frameId => {
|
||||
// send to all sub-frames (e.g. cluster-view managed in iframe)
|
||||
broadcastIpc({
|
||||
...msg,
|
||||
frameId: frameId,
|
||||
frameOnly: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// todo: refactor?
|
||||
protected async getSubFrames(): Promise<number[]> {
|
||||
const subFrames: number[] = [];
|
||||
const { clusterStore } = await import("./cluster-store");
|
||||
clusterStore.clustersList.forEach(cluster => {
|
||||
if (cluster.frameId) {
|
||||
subFrames.push(cluster.frameId)
|
||||
}
|
||||
});
|
||||
return subFrames;
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { observable } from "mobx"
|
||||
|
||||
export const clusterFrameMap = observable.map<string, number>();
|
||||
+47
-51
@@ -1,59 +1,55 @@
|
||||
import { createIpcChannel } from "./ipc";
|
||||
import { handleRequest } from "./ipc";
|
||||
import { ClusterId, clusterStore } from "./cluster-store";
|
||||
import { extensionLoader } from "../extensions/extension-loader"
|
||||
import { appEventBus } from "./event-bus"
|
||||
import { ResourceApplier } from "../main/resource-applier";
|
||||
import { ipcMain } from "electron";
|
||||
import { clusterFrameMap } from "./cluster-frames"
|
||||
|
||||
export const clusterIpc = {
|
||||
activate: createIpcChannel({
|
||||
channel: "cluster:activate",
|
||||
handle: (clusterId: ClusterId, force = false) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
return cluster.activate(force);
|
||||
}
|
||||
},
|
||||
}),
|
||||
export const clusterActivateHandler = "cluster:activate"
|
||||
export const clusterSetFrameIdHandler = "cluster:set-frame-id"
|
||||
export const clusterRefreshHandler = "cluster:refresh"
|
||||
export const clusterDisconnectHandler = "cluster:disconnect"
|
||||
export const clusterKubectlApplyAllHandler = "cluster:kubectl-apply-all"
|
||||
|
||||
setFrameId: createIpcChannel({
|
||||
channel: "cluster:set-frame-id",
|
||||
handle: (clusterId: ClusterId, frameId?: number) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
if (frameId) cluster.frameId = frameId; // save cluster's webFrame.routingId to be able to send push-updates
|
||||
extensionLoader.broadcastExtensions(frameId)
|
||||
return cluster.pushState();
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
refresh: createIpcChannel({
|
||||
channel: "cluster:refresh",
|
||||
handle: (clusterId: ClusterId) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) return cluster.refresh({ refreshMetadata: true })
|
||||
},
|
||||
}),
|
||||
|
||||
disconnect: createIpcChannel({
|
||||
channel: "cluster:disconnect",
|
||||
handle: (clusterId: ClusterId) => {
|
||||
appEventBus.emit({name: "cluster", action: "stop"});
|
||||
return clusterStore.getById(clusterId)?.disconnect();
|
||||
},
|
||||
}),
|
||||
|
||||
kubectlApplyAll: createIpcChannel({
|
||||
channel: "cluster:kubectl-apply-all",
|
||||
handle: (clusterId: ClusterId, resources: string[]) => {
|
||||
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"})
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
const applier = new ResourceApplier(cluster)
|
||||
applier.kubectlApplyAll(resources)
|
||||
} else {
|
||||
throw `${clusterId} is not a valid cluster id`;
|
||||
}
|
||||
if (ipcMain) {
|
||||
handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
return cluster.activate(force);
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
handleRequest(clusterSetFrameIdHandler, (event, clusterId: ClusterId, frameId: number) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
clusterFrameMap.set(cluster.id, frameId)
|
||||
return cluster.pushState();
|
||||
}
|
||||
})
|
||||
|
||||
handleRequest(clusterRefreshHandler, (event, clusterId: ClusterId) => {
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) return cluster.refresh({ refreshMetadata: true })
|
||||
})
|
||||
|
||||
handleRequest(clusterDisconnectHandler, (event, clusterId: ClusterId) => {
|
||||
appEventBus.emit({name: "cluster", action: "stop"});
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
cluster.disconnect();
|
||||
clusterFrameMap.delete(cluster.id)
|
||||
}
|
||||
})
|
||||
|
||||
handleRequest(clusterKubectlApplyAllHandler, (event, clusterId: ClusterId, resources: string[]) => {
|
||||
appEventBus.emit({name: "cluster", action: "kubectl-apply-all"})
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
if (cluster) {
|
||||
const applier = new ResourceApplier(cluster)
|
||||
applier.kubectlApplyAll(resources)
|
||||
} else {
|
||||
throw `${clusterId} is not a valid cluster id`;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { WorkspaceId } from "./workspace-store";
|
||||
import path from "path";
|
||||
import { app, ipcRenderer, remote, webFrame } from "electron";
|
||||
import { unlink } from "fs-extra";
|
||||
import { action, computed, observable, toJS } from "mobx";
|
||||
import { action, computed, observable, reaction, toJS } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { Cluster, ClusterState } from "../main/cluster";
|
||||
import migrations from "../migrations/cluster-store"
|
||||
@@ -13,6 +13,7 @@ import { saveToAppFiles } from "./utils/saveToAppFiles";
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import _ from "lodash";
|
||||
import move from "array-move";
|
||||
import { subscribeToBroadcast, unsubscribeAllFromBroadcast } from "./ipc";
|
||||
|
||||
export interface ClusterIconUpload {
|
||||
clusterId: string;
|
||||
@@ -85,21 +86,20 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
migrations: migrations,
|
||||
});
|
||||
|
||||
this.pushStateToViewsPeriodically()
|
||||
this.pushStateToViewsAutomatically()
|
||||
}
|
||||
|
||||
protected pushStateToViewsPeriodically() {
|
||||
protected pushStateToViewsAutomatically() {
|
||||
if (!ipcRenderer) {
|
||||
// This is a bit of a hack, we need to do this because we might loose messages that are sent before a view is ready
|
||||
setInterval(() => {
|
||||
reaction(() => this.connectedClustersList, () => {
|
||||
this.pushState()
|
||||
}, 5000)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
registerIpcListener() {
|
||||
logger.info(`[CLUSTER-STORE] start to listen (${webFrame.routingId})`)
|
||||
ipcRenderer.on("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
||||
subscribeToBroadcast("cluster:state", (event, clusterId: string, state: ClusterState) => {
|
||||
logger.silly(`[CLUSTER-STORE]: received push-state at ${location.host} (${webFrame.routingId})`, clusterId, state);
|
||||
this.getById(clusterId)?.setState(state)
|
||||
})
|
||||
@@ -107,7 +107,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
|
||||
unregisterIpcListener() {
|
||||
super.unregisterIpcListener()
|
||||
ipcRenderer.removeAllListeners("cluster:state")
|
||||
unsubscribeAllFromBroadcast("cluster:state")
|
||||
}
|
||||
|
||||
pushState() {
|
||||
@@ -132,6 +132,10 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
return this.getById(this.activeCluster);
|
||||
}
|
||||
|
||||
@computed get connectedClustersList(): Cluster[] {
|
||||
return this.clustersList.filter((c) => !c.disconnected)
|
||||
}
|
||||
|
||||
isActive(id: ClusterId) {
|
||||
return this.activeCluster === id;
|
||||
}
|
||||
|
||||
+54
-63
@@ -1,79 +1,70 @@
|
||||
// Inter-protocol communications (main <-> renderer)
|
||||
// Inter-process communications (main <-> renderer)
|
||||
// https://www.electronjs.org/docs/api/ipc-main
|
||||
// https://www.electronjs.org/docs/api/ipc-renderer
|
||||
|
||||
import { ipcMain, ipcRenderer, WebContents, webContents } from "electron"
|
||||
import { ipcMain, ipcRenderer, webContents, remote } from "electron";
|
||||
import logger from "../main/logger";
|
||||
import { clusterFrameMap } from "./cluster-frames";
|
||||
|
||||
export type IpcChannel = string;
|
||||
|
||||
export interface IpcChannelOptions {
|
||||
channel: IpcChannel; // main <-> renderer communication channel name
|
||||
handle?: (...args: any[]) => Promise<any> | any; // message handler
|
||||
autoBind?: boolean; // auto-bind message handler in main-process, default: true
|
||||
timeout?: number; // timeout for waiting response from the sender
|
||||
once?: boolean; // one-time event
|
||||
export function handleRequest(channel: string, listener: (...args: any[]) => any) {
|
||||
ipcMain.handle(channel, listener)
|
||||
}
|
||||
|
||||
export function createIpcChannel({ autoBind = true, once, timeout = 0, handle, channel }: IpcChannelOptions) {
|
||||
const ipcChannel = {
|
||||
channel: channel,
|
||||
handleInMain: () => {
|
||||
logger.info(`[IPC]: setup channel "${channel}"`);
|
||||
const ipcHandler = once ? ipcMain.handleOnce : ipcMain.handle;
|
||||
ipcHandler(channel, async (event, ...args) => {
|
||||
let timerId: any;
|
||||
try {
|
||||
if (timeout > 0) {
|
||||
timerId = setTimeout(() => {
|
||||
throw new Error(`[IPC]: response timeout in ${timeout}ms`)
|
||||
}, timeout);
|
||||
}
|
||||
return await handle(...args); // todo: maybe exec in separate thread/worker
|
||||
} catch (error) {
|
||||
throw error
|
||||
} finally {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
})
|
||||
},
|
||||
removeHandler() {
|
||||
ipcMain.removeHandler(channel);
|
||||
},
|
||||
invokeFromRenderer: async <T>(...args: any[]): Promise<T> => {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
},
|
||||
}
|
||||
if (autoBind && ipcMain) {
|
||||
ipcChannel.handleInMain();
|
||||
}
|
||||
return ipcChannel;
|
||||
export async function requestMain(channel: string, ...args: any[]) {
|
||||
return ipcRenderer.invoke(channel, ...args)
|
||||
}
|
||||
|
||||
export interface IpcBroadcastParams<A extends any[] = any> {
|
||||
channel: IpcChannel
|
||||
webContentId?: number; // send to single webContents view
|
||||
frameId?: number; // send to inner frame of webContents
|
||||
frameOnly?: boolean; // send message only to view with provided `frameId`
|
||||
filter?: (webContent: WebContents) => boolean
|
||||
timeout?: number; // todo: add support
|
||||
args?: A;
|
||||
async function getSubFrames(): Promise<number[]> {
|
||||
const subFrames: number[] = [];
|
||||
clusterFrameMap.forEach((frameId, _) => {
|
||||
subFrames.push(frameId)
|
||||
});
|
||||
return subFrames;
|
||||
}
|
||||
|
||||
export function broadcastIpc({ channel, frameId, frameOnly, webContentId, filter, args = [] }: IpcBroadcastParams) {
|
||||
const singleView = webContentId ? webContents.fromId(webContentId) : null;
|
||||
let views = singleView ? [singleView] : webContents.getAllWebContents();
|
||||
if (filter) {
|
||||
views = views.filter(filter);
|
||||
}
|
||||
export function broadcastMessage(channel: string, ...args: any[]) {
|
||||
const views = (webContents || remote?.webContents)?.getAllWebContents();
|
||||
if (!views) return
|
||||
|
||||
views.forEach(webContent => {
|
||||
const type = webContent.getType();
|
||||
logger.silly(`[IPC]: broadcasting "${channel}" to ${type}=${webContent.id}`, { args });
|
||||
if (!frameOnly) {
|
||||
webContent.send(channel, ...args);
|
||||
}
|
||||
if (frameId) {
|
||||
webContent.sendToFrame(frameId, channel, ...args)
|
||||
}
|
||||
webContent.send(channel, ...args);
|
||||
getSubFrames().then((frames) => {
|
||||
frames.map((frameId) => {
|
||||
webContent.sendToFrame(frameId, channel, ...args)
|
||||
})
|
||||
}).catch((e) => e)
|
||||
})
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.send(channel, ...args)
|
||||
} else {
|
||||
ipcMain.emit(channel, ...args)
|
||||
}
|
||||
}
|
||||
|
||||
export function subscribeToBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.on(channel, listener)
|
||||
} else {
|
||||
ipcMain.on(channel, listener)
|
||||
}
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
export function unsubscribeFromBroadcast(channel: string, listener: (...args: any[]) => any) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.off(channel, listener)
|
||||
} else {
|
||||
ipcMain.off(channel, listener)
|
||||
}
|
||||
}
|
||||
|
||||
export function unsubscribeAllFromBroadcast(channel: string) {
|
||||
if (ipcRenderer) {
|
||||
ipcRenderer.removeAllListeners(channel)
|
||||
} else {
|
||||
ipcMain.removeAllListeners(channel)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { action, computed, observable, toJS, reaction } from "mobx";
|
||||
import { BaseStore } from "./base-store";
|
||||
import { clusterStore } from "./cluster-store"
|
||||
import { appEventBus } from "./event-bus";
|
||||
import { broadcastIpc } from "../common/ipc";
|
||||
import { broadcastMessage } from "../common/ipc";
|
||||
import logger from "../main/logger";
|
||||
|
||||
export type WorkspaceId = string;
|
||||
@@ -53,10 +53,7 @@ export class Workspace implements WorkspaceModel, WorkspaceState {
|
||||
|
||||
pushState(state = this.getState()) {
|
||||
logger.silly("[WORKSPACE] pushing state", {...state, id: this.id})
|
||||
broadcastIpc({
|
||||
channel: "workspace:state",
|
||||
args: [this.id, toJS(state)],
|
||||
});
|
||||
broadcastMessage("workspace:state", this.id, toJS(state))
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
Reference in new issue
Block a user