diff --git a/package.json b/package.json index 11437ca331..6d42bd99be 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,6 @@ "mobx-observable-history": "^1.0.3", "mobx-react": "^6.2.2", "mock-fs": "^4.12.0", - "moment": "^2.26.0", "node-pty": "^0.9.0", "npm": "^6.14.8", "openid-client": "^3.15.2", @@ -322,6 +321,7 @@ "jest-mock-extended": "^1.0.10", "make-plural": "^6.2.2", "mini-css-extract-plugin": "^0.9.0", + "moment": "^2.26.0", "node-loader": "^0.6.0", "node-sass": "^4.14.1", "nodemon": "^2.0.4", diff --git a/src/common/ipc/__tests__/type-enforced-ipc.test.ts b/src/common/ipc/__tests__/type-enforced-ipc.test.ts new file mode 100644 index 0000000000..4e20249392 --- /dev/null +++ b/src/common/ipc/__tests__/type-enforced-ipc.test.ts @@ -0,0 +1,126 @@ +import { EventEmitter } from "events"; +import { onCorrect, onceCorrect } from "../type-enforced-ipc"; + +describe("type enforced ipc tests", () => { + describe("onCorrect tests", () => { + it("should call the handler if the args are valid", () => { + let called = false; + const source = new EventEmitter(); + const listener = () => called = true; + const verifier = (args: unknown[]): args is [] => true; + const channel = "foobar"; + + onCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + expect(called).toBe(true); + }); + + it("should not call the handler if the args are not valid", () => { + let called = false; + const source = new EventEmitter(); + const listener = () => called = true; + const verifier = (args: unknown[]): args is [] => false; + const channel = "foobar"; + + onCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + expect(called).toBe(false); + }); + + it("should call the handler twice if the args are valid on two emits", () => { + let called = 0; + const source = new EventEmitter(); + const listener = () => called += 1; + const verifier = (args: unknown[]): args is [] => true; + const channel = "foobar"; + + onCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + source.emit(channel); + expect(called).toBe(2); + }); + + it("should call the handler twice if the args are [valid, invalid, valid]", () => { + let called = 0; + const source = new EventEmitter(); + const listener = () => called += 1; + const results = [true, false, true]; + const verifier = (args: unknown[]): args is [] => results.pop(); + const channel = "foobar"; + + onCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + source.emit(channel); + source.emit(channel); + expect(called).toBe(2); + }); + }); + + describe("onceCorrect tests", () => { + it("should call the handler if the args are valid", () => { + let called = false; + const source = new EventEmitter(); + const listener = () => called = true; + const verifier = (args: unknown[]): args is [] => true; + const channel = "foobar"; + + onceCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + expect(called).toBe(true); + }); + + it("should not call the handler if the args are not valid", () => { + let called = false; + const source = new EventEmitter(); + const listener = () => called = true; + const verifier = (args: unknown[]): args is [] => false; + const channel = "foobar"; + + onceCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + expect(called).toBe(false); + }); + + it("should call the handler only once even if args are valid multiple times", () => { + let called = 0; + const source = new EventEmitter(); + const listener = () => called += 1; + const verifier = (args: unknown[]): args is [] => true; + const channel = "foobar"; + + onceCorrect({ source, listener, verifier, channel }); + + source.emit(channel); + source.emit(channel); + expect(called).toBe(1); + }); + + it("should call the handler on only the first valid set of args", () => { + let called = ""; + let verifierCalled = 0; + const source = new EventEmitter(); + const listener = (info: any, arg: string) => called = arg; + const verifier = (args: unknown[]): args is [string] => (++verifierCalled) % 3 === 0; + const channel = "foobar"; + + onceCorrect({ source, listener, verifier, channel }); + + source.emit(channel, {}, "a"); + source.emit(channel, {}, "b"); + source.emit(channel, {}, "c"); + source.emit(channel, {}, "d"); + source.emit(channel, {}, "e"); + source.emit(channel, {}, "f"); + source.emit(channel, {}, "g"); + source.emit(channel, {}, "h"); + source.emit(channel, {}, "i"); + expect(called).toBe("c"); + }); + }); +}); diff --git a/src/common/ipc/index.ts b/src/common/ipc/index.ts index 20fb79d0b2..a34890472e 100644 --- a/src/common/ipc/index.ts +++ b/src/common/ipc/index.ts @@ -1,2 +1,3 @@ export * from "./ipc"; export * from "./update-available"; +export * from "./type-enforced-ipc"; diff --git a/src/common/ipc/ipc.ts b/src/common/ipc/ipc.ts index 3fb1bef672..48b0b89153 100644 --- a/src/common/ipc/ipc.ts +++ b/src/common/ipc/ipc.ts @@ -4,14 +4,11 @@ import { ipcMain, ipcRenderer, webContents, remote } from "electron"; import { toJS } from "mobx"; -import { EventEmitter } from "ws"; import logger from "../../main/logger"; import { ClusterFrameInfo, clusterFrameMap } from "../cluster-frames"; const subFramesChannel = "ipc:get-sub-frames"; -export type HandlerEvent = Parameters[1]>[0]; - export function handleRequest(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any) { ipcMain.handle(channel, listener); } @@ -20,73 +17,6 @@ export async function requestMain(channel: string, ...args: any[]) { return ipcRenderer.invoke(channel, ...args); } -/** - * Adds a listener to `source` that waits for the first IPC message with the correct - * argument data is sent. - * @param channel The channel to be listened on - * @param listener The function for the channel to be called if the args of the correct type - * @param verifier The function to be called to verify that the args are the correct type - */ -export function onceCorrect< - EM extends EventEmitter, - T extends any[], - L extends (event: HandlerEvent, ...args: T) => any ->({ - source, - channel, - listener, - verifier, -}: { - source: EM, - channel: string | symbol, - listener: L, - verifier: (args: unknown[]) => args is T, -}): void { - function handler(event: HandlerEvent, ...args: unknown[]): void { - if (verifier(args)) { - source.removeListener(channel, handler); // remove immediately - - (async () => (listener(event, ...args)))() // might return a promise, or throw, or reject - .catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error })); - } else { - logger.error("[IPC]: channel was sent to with invalid data", { channel, args }); - } - } - - source.on(channel, handler); -} - -/** - * Adds a listener to `source` that checks to verify the arguments before calling the handler. - * @param channel The channel to be listened on - * @param listener The function for the channel to be called if the args of the correct type - * @param verifier The function to be called to verify that the args are the correct type - */ -export function onCorrect< - EM extends EventEmitter, - T extends any[], - L extends (event: HandlerEvent, ...args: T) => any ->({ - source, - channel, - listener, - verifier, -}: { - source: EM, - channel: string | symbol, - listener: L, - verifier: (args: unknown[]) => args is T, -}): void { - source.on(channel, (event, ...args: unknown[]) => { - if (verifier(args)) { - (async () => (listener(event, ...args)))() // might return a promise, or throw, or reject - .catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error })); - } else { - logger.error("[IPC]: channel was sent to with invalid data", { channel, args }); - } - }); -} - function getSubFrames(): ClusterFrameInfo[] { return toJS(Array.from(clusterFrameMap.values()), { recurseEverything: true }); } diff --git a/src/common/ipc/type-enforced-ipc.ts b/src/common/ipc/type-enforced-ipc.ts new file mode 100644 index 0000000000..be54992008 --- /dev/null +++ b/src/common/ipc/type-enforced-ipc.ts @@ -0,0 +1,71 @@ +import { EventEmitter } from "events"; +import logger from "../../main/logger"; + +export type HandlerEvent = Parameters[1]>[0]; +export type ListVerifier = (args: unknown[]) => args is T; +export type Rest = T extends [any, ...infer R] ? R : []; + +/** + * Adds a listener to `source` that waits for the first IPC message with the correct + * argument data is sent. + * @param channel The channel to be listened on + * @param listener The function for the channel to be called if the args of the correct type + * @param verifier The function to be called to verify that the args are the correct type + */ +export function onceCorrect< + EM extends EventEmitter, + L extends (event: HandlerEvent, ...args: any[]) => any +>({ + source, + channel, + listener, + verifier, +}: { + source: EM, + channel: string | symbol, + listener: L, + verifier: ListVerifier>>, +}): void { + function handler(event: HandlerEvent, ...args: unknown[]): void { + if (verifier(args)) { + source.removeListener(channel, handler); // remove immediately + + (async () => (listener(event, ...args)))() // might return a promise, or throw, or reject + .catch((error: any) => logger.error("[IPC]: channel once handler threw error", { channel, error })); + } else { + logger.error("[IPC]: channel was emitted with invalid data", { channel, args }); + } + } + + source.on(channel, handler); +} + +/** + * Adds a listener to `source` that checks to verify the arguments before calling the handler. + * @param channel The channel to be listened on + * @param listener The function for the channel to be called if the args of the correct type + * @param verifier The function to be called to verify that the args are the correct type + */ +export function onCorrect< + EM extends EventEmitter, + L extends (event: HandlerEvent, ...args: any[]) => any +>({ + source, + channel, + listener, + verifier, +}: { + source: EM, + channel: string | symbol, + listener: L, + verifier: ListVerifier>>, +}): void { + source.on(channel, (event, ...args: unknown[]) => { + if (verifier(args)) { + (async () => (listener(event, ...args)))() // might return a promise, or throw, or reject + .catch(error => logger.error("[IPC]: channel on handler threw error", { channel, error })); + } else { + logger.error("[IPC]: channel was emitted with invalid data", { channel, args }); + } + }); +} diff --git a/src/renderer/components/+workloads-overview/overview-statuses.tsx b/src/renderer/components/+workloads-overview/overview-statuses.tsx index 33e5aa37c5..7458956d2a 100644 --- a/src/renderer/components/+workloads-overview/overview-statuses.tsx +++ b/src/renderer/components/+workloads-overview/overview-statuses.tsx @@ -9,7 +9,7 @@ import { namespaceStore } from "../+namespaces/namespace.store"; import { PageFiltersList } from "../item-object-list/page-filters-list"; import { NamespaceSelectFilter } from "../+namespaces/namespace-select"; import { isAllowedResource, KubeResource } from "../../../common/rbac"; -import { ResourceNames } from "../../../renderer/utils/rbac"; +import { ResourceNames } from "../../utils/rbac"; import { autobind } from "../../utils"; const resources: KubeResource[] = [