From 04c36b4b5b955a7e613924e7e22c41445232aa54 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Fri, 19 Feb 2021 16:56:33 -0500 Subject: [PATCH] add TypedSender and TypedInvoker Signed-off-by: Sebastian Malton --- src/common/cluster-ipc.ts | 62 ------- src/common/ipc/cluster.ipc.ts | 91 +++++++++- src/common/ipc/type-enforced-ipc.ts | 166 +++++++++++++++++- src/common/ipc/update-available.ipc.ts | 68 ++++--- src/common/utils/type-narrowing.ts | 132 ++++++++++++++ src/extensions/cluster-feature.ts | 5 +- src/main/app-updater.ts | 8 +- src/main/cluster.ts | 4 +- .../+cluster-settings/cluster-settings.tsx | 7 +- src/renderer/components/app.tsx | 5 +- .../cluster-manager/cluster-status.tsx | 5 +- .../cluster-manager/clusters-menu.tsx | 5 +- src/renderer/ipc/index.tsx | 20 +-- 13 files changed, 446 insertions(+), 132 deletions(-) delete mode 100644 src/common/cluster-ipc.ts create mode 100644 src/common/utils/type-narrowing.ts diff --git a/src/common/cluster-ipc.ts b/src/common/cluster-ipc.ts deleted file mode 100644 index d44634407e..0000000000 --- a/src/common/cluster-ipc.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { handleRequest } from "./ipc"; -import { ClusterId, clusterStore } from "./cluster-store"; -import { appEventBus } from "./event-bus"; -import { ResourceApplier } from "../main/resource-applier"; -import { ipcMain, IpcMainInvokeEvent } from "electron"; -import { clusterFrameMap } from "./cluster-frames"; - -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"; - - -if (ipcMain) { - handleRequest(clusterActivateHandler, (event, clusterId: ClusterId, force = false) => { - const cluster = clusterStore.getById(clusterId); - - if (cluster) { - return cluster.activate(force); - } - }); - - handleRequest(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => { - const cluster = clusterStore.getById(clusterId); - - if (cluster) { - clusterFrameMap.set(cluster.id, { frameId: event.frameId, processId: event.processId }); - - 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`; - } - }); -} diff --git a/src/common/ipc/cluster.ipc.ts b/src/common/ipc/cluster.ipc.ts index 41b0337e30..b574e2a316 100644 --- a/src/common/ipc/cluster.ipc.ts +++ b/src/common/ipc/cluster.ipc.ts @@ -1,11 +1,92 @@ +import { IpcMainInvokeEvent } from "electron"; +import { ResourceApplier } from "../../main/resource-applier"; +import { clusterFrameMap } from "../cluster-frames"; +import { ClusterId, clusterStore } from "../cluster-store"; +import { appEventBus } from "../event-bus"; +import { hasOptionalProperty, hasTypedProperty, isString, isBoolean, bindPredicate, isTypedArray } from "../utils/type-narrowing"; +import { createTypedInvoker, createTypedSender } from "./type-enforced-ipc"; + +export type ClusterIdArgList = [clusterId: ClusterId]; + +function isClusterIdArgList(args: unknown[]): args is ClusterIdArgList { + return hasTypedProperty(args, 0, isString) + && args.length === 1; +} + /** * This channel is broadcast on whenever the cluster fails to list namespaces * during a refresh and no `accessibleNamespaces` have been set. */ -export const ClusterListNamespaceForbiddenChannel = "cluster:list-namespace-forbidden"; +export const clusterListNamespacesForbidden = createTypedSender({ + channel: "cluster:list-namespace-forbidden", + verifier: isClusterIdArgList, +}); -export type ListNamespaceForbiddenArgs = [clusterId: string]; +export const clusterActivate = createTypedInvoker({ + channel: "cluster:activate", + handler(event, clusterId: ClusterId, force?: boolean) { + return clusterStore.getById(clusterId)?.activate(force ?? false); + }, + verifier(args: unknown[]): args is [clusterId: ClusterId, force?: boolean] { + return hasTypedProperty(args, 0, isString) + && hasOptionalProperty(args, 1, isBoolean) + && args.length <= 2; + } +}); -export function argArgsListNamespaceFordiddenArgs(args: unknown[]): args is ListNamespaceForbiddenArgs { - return args.length === 1 && typeof args[0] === "string"; -} +export const clusterSetFrameId = createTypedInvoker({ + channel: "cluster:set-frame-id", + handler({ frameId, processId }: IpcMainInvokeEvent, clusterId: ClusterId) { + const cluster = clusterStore.getById(clusterId); + + if (cluster) { + clusterFrameMap.set(cluster.id, { frameId, processId }); + + return cluster.pushState(); + } + }, + verifier: isClusterIdArgList, +}); + +export const clusterRefresh = createTypedInvoker({ + channel: "cluster:refresh", + handler(event, clusterId: ClusterId) { + return clusterStore.getById(clusterId)?.refresh({ refreshMetadata: true }); + }, + verifier: isClusterIdArgList, +}); + +export const clusterDisconnect = createTypedInvoker({ + channel: "cluster:disconnect", + handler(event, clusterId: ClusterId) { + appEventBus.emit({ name: "cluster", action: "stop" }); + const cluster = clusterStore.getById(clusterId); + + if (cluster) { + cluster.disconnect(); + clusterFrameMap.delete(cluster.id); + } + }, + verifier: isClusterIdArgList, +}); + +export const clusterKubectlApplyAll = createTypedInvoker({ + channel: "cluster:kubectl-apply-all", + handler(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 new Error(`${clusterId} is not a valid cluster id`); + } + }, + verifier(args: unknown[]): args is [clusterId: ClusterId, resources: string[]] { + return hasTypedProperty(args, 0, isString) + && hasTypedProperty(args, 1, bindPredicate(isTypedArray, isString)) + && args.length === 2; + }, +}); diff --git a/src/common/ipc/type-enforced-ipc.ts b/src/common/ipc/type-enforced-ipc.ts index be54992008..c0d4ee0d65 100644 --- a/src/common/ipc/type-enforced-ipc.ts +++ b/src/common/ipc/type-enforced-ipc.ts @@ -1,9 +1,12 @@ +import { ipcMain, ipcRenderer } from "electron"; import { EventEmitter } from "events"; import logger from "../../main/logger"; +import { broadcastMessage } from "./ipc"; export type HandlerEvent = Parameters[1]>[0]; export type ListVerifier = (args: unknown[]) => args is T; export type Rest = T extends [any, ...infer R] ? R : []; +export type IpcListener = (e: E, ...args: Args) => void; /** * Adds a listener to `source` that waits for the first IPC message with the correct @@ -14,7 +17,7 @@ export type Rest = T extends [any, ...infer R] ? R : []; */ export function onceCorrect< EM extends EventEmitter, - L extends (event: HandlerEvent, ...args: any[]) => any + Listener extends IpcListener, >({ source, channel, @@ -23,8 +26,8 @@ export function onceCorrect< }: { source: EM, channel: string | symbol, - listener: L, - verifier: ListVerifier>>, + listener: Listener, + verifier: ListVerifier>>, }): void { function handler(event: HandlerEvent, ...args: unknown[]): void { if (verifier(args)) { @@ -48,7 +51,7 @@ export function onceCorrect< */ export function onCorrect< EM extends EventEmitter, - L extends (event: HandlerEvent, ...args: any[]) => any + Listener extends IpcListener, >({ source, channel, @@ -57,8 +60,8 @@ export function onCorrect< }: { source: EM, channel: string | symbol, - listener: L, - verifier: ListVerifier>>, + listener: Listener, + verifier: ListVerifier>>, }): void { source.on(channel, (event, ...args: unknown[]) => { if (verifier(args)) { @@ -69,3 +72,154 @@ export function onCorrect< } }); } + +interface IPCEncodedError { + name: string, + message: string, + extra: Record, +} + +function encodeError(e: Error): IPCEncodedError { + delete e.stack; + + return { + name: e.name, + message: e.message, + extra: { ...e }, + }; +} + +function decodeError({ extra, message, name }: IPCEncodedError): Error { + const e = new Error(message); + + e.name = name; + + Object.assign(e, extra); + + return e; +} + +export function handleCorrect< + Handler extends (event: Event, ...args: any[]) => any +>({ + channel, + handler, + verifier, +}: { + channel: string, + handler: Handler, + verifier: ListVerifier>>, +}): void { + ipcMain.handle(channel, async (event, ...args: unknown[]) => { + try { + if (verifier(args)) { + const result = await Promise.resolve(handler(event, ...args)); + + return { result }; + } else { + throw new TypeError("Arguments are wrong type"); + } + } catch (error) { + return { error: encodeError(error) }; + } + }); +} + +export async function invokeWithDecode< + L extends any[], +>({ + channel, + args +}: { + channel: string, + args: L +}): Promise { + const { error, result } = await ipcRenderer.invoke(channel, ...args); + + if (error) { + throw decodeError(error); + } + + return result; +} + +export interface TypedInvoker< + Handler extends (event: Event, ...args: any[]) => any +> { + invoke: (...args: Rest>) => Promise>, +} + +export function createTypedInvoker< + Handler extends (event: Event, ...args: any[]) => any +>({ + channel, + handler, + verifier, +}: { + channel: string, + handler: Handler, + verifier: ListVerifier>>, +}): TypedInvoker { + if (ipcMain) { + handleCorrect({ + channel, + handler, + verifier, + }); + } else if (ipcRenderer) { + return { + invoke(...args) { + return invokeWithDecode({ + channel, + args + }); + } + }; + } + + return { + invoke() { + throw new TypeError("invoke called in main"); + } + }; +} + +export interface TypedSender< + Args extends any[] +> { + broadcast: (...args: Args) => void, + on: (listener: IpcListener) => void, + once: (listener: IpcListener) => void, +} + +export function createTypedSender< + Args extends any[] +>({ + channel, + verifier, +}: { + channel: string, + verifier: ListVerifier, +}): TypedSender { + return { + broadcast(...args) { + broadcastMessage(channel, ...args); + }, + on(listener) { + onCorrect({ + source: ipcMain ?? ipcRenderer, + channel, + listener, + verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly + }); + }, + once(listener) { + onceCorrect({ + source: ipcMain ?? ipcRenderer, + channel, + listener, + verifier: verifier as any, // safety: this verifier is correct, TS just doesn't equate Rest<..> correctly + }); + } + }; +} diff --git a/src/common/ipc/update-available.ipc.ts b/src/common/ipc/update-available.ipc.ts index 8571c08512..28bdb3f8c2 100644 --- a/src/common/ipc/update-available.ipc.ts +++ b/src/common/ipc/update-available.ipc.ts @@ -1,25 +1,45 @@ import { UpdateInfo } from "electron-updater"; +import { UpdateFileInfo, ReleaseNoteInfo } from "builder-util-runtime"; +import { bindPredicate, bindPredicateOr, hasOptionalProperty, hasTypedProperty, isNull, isObject, isString, isTypedArray, isNumber, isBoolean } from "../utils/type-narrowing"; +import { createTypedSender } from "./type-enforced-ipc"; -export const UpdateAvailableChannel = "update-available"; export const AutoUpdateLogPrefix = "[UPDATE-CHECKER]"; +export const updateAvailale = createTypedSender({ + channel: "update-available", + verifier: isUpdateAvailableArgs, +}); -export type UpdateAvailableFromMain = [backChannel: string, updateInfo: UpdateInfo]; +export type UpdateAvailableArgs = [backChannel: string, updateInfo: UpdateInfo]; -export function areArgsUpdateAvailableFromMain(args: unknown[]): args is UpdateAvailableFromMain { - if (args.length !== 2) { - return false; - } +function isUpdateFileInfo(src: unknown): src is UpdateFileInfo { + return isObject(src) + && hasTypedProperty(src, "sha512", isString) + && hasTypedProperty(src, "url", isString) + && hasOptionalProperty(src, "size", isNumber) + && hasOptionalProperty(src, "blockMapSize", isNumber) + && hasOptionalProperty(src, "isAdminRightsRequired", isBoolean); +} - if (typeof args[0] !== "string") { - return false; - } +function isReleaseNoteInfo(src: unknown): src is ReleaseNoteInfo { + return isObject(src) + && hasTypedProperty(src, "version", isString) + && hasTypedProperty(src, "note", bindPredicateOr(isString, isNull)); +} - if (typeof args[1] !== "object" || args[1] === null) { - // TODO: improve this checking - return false; - } +function isUpdateInfo(src: unknown): src is UpdateInfo { + return isObject(src) + && hasTypedProperty(src, "version", isString) + && hasTypedProperty(src, "releaseDate", isString) + && hasTypedProperty(src, "files", bindPredicate(isTypedArray, isUpdateFileInfo)) + && hasOptionalProperty(src, "releaseName", bindPredicateOr(isString, isNull)) + && hasOptionalProperty(src, "stagingPercentage", isNumber) + && hasOptionalProperty(src, "releaseNotes", bindPredicateOr(isString, isReleaseNoteInfo, isNull)); +} - return true; +export function isUpdateAvailableArgs(args: unknown[]): args is UpdateAvailableArgs { + return hasTypedProperty(args, 0, isString) + && hasTypedProperty(args, 1, isUpdateInfo) + && args.length === 2; } export type BackchannelArg = { @@ -31,15 +51,19 @@ export type BackchannelArg = { export type UpdateAvailableToBackchannel = [updateDecision: BackchannelArg]; -export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel { - if (args.length !== 1) { +function isBackChannelArg(src: unknown): src is BackchannelArg { + if (!( + isObject(src) + && hasTypedProperty(src, "doUpdate", isBoolean) + )) { return false; } - if (typeof args[0] !== "object" || args[0] === null) { - // TODO: improve this checking - return false; - } - - return true; + return !src.doUpdate + || hasTypedProperty(src, "now", isBoolean); +} + +export function areArgsUpdateAvailableToBackchannel(args: unknown[]): args is UpdateAvailableToBackchannel { + return hasTypedProperty(args, 0, isBackChannelArg) + && args.length === 1; } diff --git a/src/common/utils/type-narrowing.ts b/src/common/utils/type-narrowing.ts new file mode 100644 index 0000000000..f92202f3cb --- /dev/null +++ b/src/common/utils/type-narrowing.ts @@ -0,0 +1,132 @@ +/** + * Narrows `val` to include the property `key` (if true is returned) + * @param val The object to be tested + * @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful) + */ +export function hasOwnProperty(val: S, key: K): val is (S & { [key in K]: unknown }) { + // this call syntax is for when `val` was created by `Object.create(null)` + return Object.prototype.hasOwnProperty.call(val, key); +} + +/** + * Narrows `val` to a static type that includes fields of names in `keys` + * @param val the value that we are trying to type narrow + * @param keys the key names (must be literals for tsc to do any type meaningful) + */ +export function hasOwnProperties(val: S, ...keys: K[]): val is (S & { [key in K]: unknown }) { + return keys.every(key => hasOwnProperty(val, key)); +} + +/** + * Narrows `val` to include the property `key` with type `V` + * @param val the value that we are trying to type narrow + * @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful) + * @param isValid a function to check if the field is valid + */ +export function hasTypedProperty(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]: V }) { + return hasOwnProperty(val, key) && isValid(val[key]); +} + +/** + * Narrows `val` to include the property `key` with type `V | undefined` or doesn't contain it + * @param val the value that we are trying to type narrow + * @param key The key to test if it is present on the object (must be a literal for tsc to do any type meaningful) + * @param isValid a function to check if the field (when present) is valid + */ +export function hasOptionalProperty(val: S, key: K, isValid: (value: unknown) => value is V): val is (S & { [key in K]?: V }) { + if (hasOwnProperty(val, key)) { + return typeof val[key] === "undefined" || isValid(val[key]); + } + + return true; +} + +/** + * isRecord checks if `val` matches the signature `Record` or `{ [label in T]: V }` + * @param val The value to be checked + * @param isKey a function for checking if the key is of the correct type + * @param isValue a function for checking if a value is of the correct type + */ +export function isRecord(val: unknown, isKey: (key: unknown) => key is T, isValue: (value: unknown) => value is V): val is Record { + return isObject(val) && Object.entries(val).every(([key, value]) => isKey(key) && isValue(value)); +} + +/** + * isTypedArray checks if `val` is an array and all of its entries are of type `T` + * @param val The value to be checked + * @param isEntry a function for checking if an entry is the correct type + */ +export function isTypedArray(val: unknown, isEntry: (entry: unknown) => entry is T): val is T[] { + return Array.isArray(val) && val.every(isEntry); +} + +/** + * checks if val is of type string + * @param val the value to be checked + */ +export function isString(val: unknown): val is string { + return typeof val === "string"; +} + +/** + * checks if val is of type boolean + * @param val the value to be checked + */ +export function isBoolean(val: unknown): val is boolean { + return typeof val === "boolean"; +} + +/** + * checks if val is of type number + * @param val the value to be checked + */ +export function isNumber(val: unknown): val is number { + return typeof val === "number"; +} + +/** + * checks if val is of type object and isn't null + * @param val the value to be checked + */ +export function isObject(val: unknown): val is object { + return typeof val === "object" && val !== null; +} + +/** + * checks if val is null + * @param val the value to be checked + */ +export function isNull(val: unknown): val is null { + return val === null; +} + +/** + * Creates a new predicate function (with the same predicate) from `fn`. Such + * that it can be called with just the value to be tested. + * + * This is useful for when using `hasOptionalProperty` and `hasTypedProperty` + * @param fn A typescript user predicate function to be bound + * @param boundArgs the set of arguments to be passed to `fn` in the new function + */ +export function bindPredicate(fn: (arg1: unknown, ...args: FnArgs) => arg1 is T, ...boundArgs: FnArgs): (arg1: unknown) => arg1 is T { + return (arg1: unknown): arg1 is T => fn(arg1, ...boundArgs); +} + +type Predicate = (arg: unknown) => arg is T; + +export function bindPredicateOr(p1: Predicate): Predicate; +export function bindPredicateOr(p1: Predicate, p2: Predicate): Predicate; +export function bindPredicateOr(p1: Predicate, p2: Predicate, p3: Predicate): Predicate; +export function bindPredicateOr(p1: Predicate, p2: Predicate, p3: Predicate, p4: Predicate): Predicate; + +export function bindPredicateOr(...predicates: T): Predicate { + return (arg: unknown): arg is any => { + for (const predicate of predicates) { + if (predicate) { + return true; + } + } + + return false; + }; +} diff --git a/src/extensions/cluster-feature.ts b/src/extensions/cluster-feature.ts index 36a2f0bfb8..6318e3465f 100644 --- a/src/extensions/cluster-feature.ts +++ b/src/extensions/cluster-feature.ts @@ -6,8 +6,7 @@ import { ResourceApplier } from "../main/resource-applier"; import { Cluster } from "../main/cluster"; import logger from "../main/logger"; import { app } from "electron"; -import { requestMain } from "../common/ipc"; -import { clusterKubectlApplyAllHandler } from "../common/cluster-ipc"; +import { clusterKubectlApplyAll } from "../common/ipc"; export interface ClusterFeatureStatus { /** feature's current version, as set by the implementation */ @@ -94,7 +93,7 @@ export abstract class ClusterFeature { if (app) { await new ResourceApplier(cluster).kubectlApplyAll(resources); } else { - await requestMain(clusterKubectlApplyAllHandler, cluster.id, resources); + await clusterKubectlApplyAll.invoke(cluster.id, resources); } } diff --git a/src/main/app-updater.ts b/src/main/app-updater.ts index 618f714b49..92a2fa5e32 100644 --- a/src/main/app-updater.ts +++ b/src/main/app-updater.ts @@ -37,9 +37,9 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void { autoUpdater.autoInstallOnAppQuit = false; autoUpdater - .on("update-available", (args: UpdateInfo) => { + .on("update-available", (updateInfo: UpdateInfo) => { try { - const backchannel = `auto-update:${args.version}`; + const backchannel = `auto-update:${updateInfo.version}`; ipcMain.removeAllListeners(backchannel); // only one handler should be present @@ -50,8 +50,8 @@ export function startUpdateChecking(interval = 1000 * 60 * 60 * 24): void { listener: handleAutoUpdateBackChannel, verifier: areArgsUpdateAvailableToBackchannel, }); - logger.info(`${AutoUpdateLogPrefix}: broadcasting update available`, { backchannel, version: args.version }); - broadcastMessage(UpdateAvailableChannel, backchannel, args); + logger.info(`${AutoUpdateLogPrefix}: broadcasting update available`, { backchannel, version: updateInfo.version }); + broadcastMessage(UpdateAvailableChannel, backchannel, updateInfo); } catch (error) { logger.error(`${AutoUpdateLogPrefix}: broadcasting failed`, { error }); } diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 00e3f5dff3..f4c4a06fda 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -4,7 +4,7 @@ import type { IMetricsReqParams } from "../renderer/api/endpoints/metrics.api"; import type { WorkspaceId } from "../common/workspace-store"; import { action, comparer, computed, observable, reaction, toJS, when } from "mobx"; import { apiKubePrefix } from "../common/vars"; -import { broadcastMessage, ClusterListNamespaceForbiddenChannel } from "../common/ipc"; +import { broadcastMessage, clusterListNamespacesForbidden } from "../common/ipc"; import { ContextHandler } from "./context-handler"; import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"; import { Kubectl } from "./kubectl"; @@ -685,7 +685,7 @@ export class Cluster implements ClusterModel, ClusterState { if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) { logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id }); - broadcastMessage(ClusterListNamespaceForbiddenChannel, this.id); + clusterListNamespacesForbidden.broadcast(this.id); } return namespaceList; diff --git a/src/renderer/components/+cluster-settings/cluster-settings.tsx b/src/renderer/components/+cluster-settings/cluster-settings.tsx index 07d275a274..2db8eb048a 100644 --- a/src/renderer/components/+cluster-settings/cluster-settings.tsx +++ b/src/renderer/components/+cluster-settings/cluster-settings.tsx @@ -13,8 +13,7 @@ import { ClusterIcon } from "../cluster-icon"; import { IClusterSettingsRouteParams } from "./cluster-settings.route"; import { clusterStore } from "../../../common/cluster-store"; import { PageLayout } from "../layout/page-layout"; -import { requestMain } from "../../../common/ipc"; -import { clusterActivateHandler, clusterRefreshHandler } from "../../../common/cluster-ipc"; +import { clusterActivate, clusterRefresh } from "../../../common/ipc"; import { navigation } from "../../navigation"; interface Props extends RouteComponentProps { @@ -47,8 +46,8 @@ export class ClusterSettings extends React.Component { refreshCluster = async () => { if (this.cluster) { - await requestMain(clusterActivateHandler, this.cluster.id); - await requestMain(clusterRefreshHandler, this.cluster.id); + await clusterActivate.invoke(this.cluster.id); + await clusterRefresh.invoke(this.cluster.id); } }; diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 8b7f8a527c..64c17319ec 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -36,9 +36,8 @@ import { webFrame } from "electron"; import { clusterPageRegistry, getExtensionPageUrl } from "../../extensions/registries/page-registry"; import { extensionLoader } from "../../extensions/extension-loader"; import { appEventBus } from "../../common/event-bus"; -import { broadcastMessage, requestMain } from "../../common/ipc"; +import { broadcastMessage, clusterSetFrameId } from "../../common/ipc"; import whatInput from "what-input"; -import { clusterSetFrameIdHandler } from "../../common/cluster-ipc"; import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries"; import { TabLayout, TabLayoutRoute } from "./layout/tab-layout"; import { StatefulSetScaleDialog } from "./+workloads-statefulsets/statefulset-scale-dialog"; @@ -60,7 +59,7 @@ export class App extends React.Component { logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`); await Terminal.preloadFonts(); - await requestMain(clusterSetFrameIdHandler, clusterId); + await clusterSetFrameId.invoke(clusterId); await getHostedCluster().whenReady; // cluster.activate() is done at this point extensionLoader.loadOnClusterRenderer(); setTimeout(() => { diff --git a/src/renderer/components/cluster-manager/cluster-status.tsx b/src/renderer/components/cluster-manager/cluster-status.tsx index 3e17eb3e4c..8ab4a0199f 100644 --- a/src/renderer/components/cluster-manager/cluster-status.tsx +++ b/src/renderer/components/cluster-manager/cluster-status.tsx @@ -5,14 +5,13 @@ import React from "react"; import { observer } from "mobx-react"; import { ipcRenderer } from "electron"; import { computed, observable } from "mobx"; -import { requestMain, subscribeToBroadcast } from "../../../common/ipc"; +import { clusterActivate, subscribeToBroadcast } from "../../../common/ipc"; import { Icon } from "../icon"; import { Button } from "../button"; import { cssNames, IClassName } from "../../utils"; import { Cluster } from "../../../main/cluster"; import { ClusterId, clusterStore } from "../../../common/cluster-store"; import { CubeSpinner } from "../spinner"; -import { clusterActivateHandler } from "../../../common/cluster-ipc"; interface Props { className?: IClassName; @@ -50,7 +49,7 @@ export class ClusterStatus extends React.Component { } activateCluster = async (force = false) => { - await requestMain(clusterActivateHandler, this.props.clusterId, force); + await clusterActivate.invoke(this.props.clusterId, force); }; reconnect = async () => { diff --git a/src/renderer/components/cluster-manager/clusters-menu.tsx b/src/renderer/components/cluster-manager/clusters-menu.tsx index d963438136..823ccc2a04 100644 --- a/src/renderer/components/cluster-manager/clusters-menu.tsx +++ b/src/renderer/components/cluster-manager/clusters-menu.tsx @@ -2,7 +2,7 @@ import "./clusters-menu.scss"; import React from "react"; import { remote } from "electron"; -import { requestMain } from "../../../common/ipc"; +import { clusterDisconnect } from "../../../common/ipc"; import type { Cluster } from "../../../main/cluster"; import { DragDropContext, Draggable, DraggableProvided, Droppable, DroppableProvided, DropResult } from "react-beautiful-dnd"; import { observer } from "mobx-react"; @@ -21,7 +21,6 @@ import { Tooltip } from "../tooltip"; import { ConfirmDialog } from "../confirm-dialog"; import { clusterViewURL } from "./cluster-view.route"; import { getExtensionPageUrl, globalPageMenuRegistry, globalPageRegistry } from "../../../extensions/registries"; -import { clusterDisconnectHandler } from "../../../common/cluster-ipc"; import { commandRegistry } from "../../../extensions/registries/command-registry"; import { CommandOverlay } from "../command-palette/command-container"; import { computed } from "mobx"; @@ -64,7 +63,7 @@ export class ClustersMenu extends React.Component { navigate(landingURL()); clusterStore.setActive(null); } - await requestMain(clusterDisconnectHandler, cluster.id); + await clusterDisconnect.invoke(cluster.id); } })); } diff --git a/src/renderer/ipc/index.tsx b/src/renderer/ipc/index.tsx index ba776c1d5e..68a5eb1bb1 100644 --- a/src/renderer/ipc/index.tsx +++ b/src/renderer/ipc/index.tsx @@ -1,6 +1,6 @@ import React from "react"; import { ipcRenderer, IpcRendererEvent } from "electron"; -import { areArgsUpdateAvailableFromMain, UpdateAvailableChannel, onCorrect, UpdateAvailableFromMain, BackchannelArg, ClusterListNamespaceForbiddenChannel, argArgsListNamespaceFordiddenArgs, ListNamespaceForbiddenArgs } from "../../common/ipc"; +import { UpdateAvailableArgs, BackchannelArg, ClusterIdArgList, updateAvailale, clusterListNamespacesForbidden } from "../../common/ipc"; import { Notifications, notificationsStore } from "../components/notifications"; import { Button } from "../components/button"; import { isMac } from "../../common/vars"; @@ -32,7 +32,7 @@ function RenderYesButtons(props: { backchannel: string, notificationId: string } ); } -function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableFromMain): void { +function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, updateInfo]: UpdateAvailableArgs): void { const notificationId = uuid.v4(); Notifications.info( @@ -58,7 +58,7 @@ function UpdateAvailableHandler(event: IpcRendererEvent, ...[backchannel, update const listNamespacesForbiddenHandlerDisplayedAt = new Map(); const intervalBetweenNotifications = 1000 * 60; // 60s -function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ListNamespaceForbiddenArgs): void { +function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: ClusterIdArgList): void { const lastDisplayedAt = listNamespacesForbiddenHandlerDisplayedAt.get(clusterId); const wasDisplayed = Boolean(lastDisplayedAt); const now = Date.now(); @@ -92,16 +92,6 @@ function ListNamespacesForbiddenHandler(event: IpcRendererEvent, ...[clusterId]: } export function registerIpcHandlers() { - onCorrect({ - source: ipcRenderer, - channel: UpdateAvailableChannel, - listener: UpdateAvailableHandler, - verifier: areArgsUpdateAvailableFromMain, - }); - onCorrect({ - source: ipcRenderer, - channel: ClusterListNamespaceForbiddenChannel, - listener: ListNamespacesForbiddenHandler, - verifier: argArgsListNamespaceFordiddenArgs, - }); + updateAvailale.on(UpdateAvailableHandler); + clusterListNamespacesForbidden.on(ListNamespacesForbiddenHandler); }