mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
add TypedSender and TypedInvoker
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
4cfdb60ce0
commit
04c36b4b5b
@ -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`;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -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;
|
||||
},
|
||||
});
|
||||
|
||||
@ -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<EM extends EventEmitter> = Parameters<Parameters<EM["on"]>[1]>[0];
|
||||
export type ListVerifier<T extends any[]> = (args: unknown[]) => args is T;
|
||||
export type Rest<T> = T extends [any, ...infer R] ? R : [];
|
||||
export type IpcListener<E extends Event, Args extends any[]> = (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> = T extends [any, ...infer R] ? R : [];
|
||||
*/
|
||||
export function onceCorrect<
|
||||
EM extends EventEmitter,
|
||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
||||
Listener extends IpcListener<Event, any[]>,
|
||||
>({
|
||||
source,
|
||||
channel,
|
||||
@ -23,8 +26,8 @@ export function onceCorrect<
|
||||
}: {
|
||||
source: EM,
|
||||
channel: string | symbol,
|
||||
listener: L,
|
||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
||||
listener: Listener,
|
||||
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||
}): void {
|
||||
function handler(event: HandlerEvent<EM>, ...args: unknown[]): void {
|
||||
if (verifier(args)) {
|
||||
@ -48,7 +51,7 @@ export function onceCorrect<
|
||||
*/
|
||||
export function onCorrect<
|
||||
EM extends EventEmitter,
|
||||
L extends (event: HandlerEvent<EM>, ...args: any[]) => any
|
||||
Listener extends IpcListener<Event, any[]>,
|
||||
>({
|
||||
source,
|
||||
channel,
|
||||
@ -57,8 +60,8 @@ export function onCorrect<
|
||||
}: {
|
||||
source: EM,
|
||||
channel: string | symbol,
|
||||
listener: L,
|
||||
verifier: ListVerifier<Rest<Parameters<L>>>,
|
||||
listener: Listener,
|
||||
verifier: ListVerifier<Rest<Parameters<Listener>>>,
|
||||
}): 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<string, any>,
|
||||
}
|
||||
|
||||
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<Rest<Parameters<Handler>>>,
|
||||
}): 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<any> {
|
||||
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<Parameters<Handler>>) => Promise<ReturnType<Handler>>,
|
||||
}
|
||||
|
||||
export function createTypedInvoker<
|
||||
Handler extends (event: Event, ...args: any[]) => any
|
||||
>({
|
||||
channel,
|
||||
handler,
|
||||
verifier,
|
||||
}: {
|
||||
channel: string,
|
||||
handler: Handler,
|
||||
verifier: ListVerifier<Rest<Parameters<Handler>>>,
|
||||
}): TypedInvoker<Handler> {
|
||||
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<Event, Args>) => void,
|
||||
once: (listener: IpcListener<Event, Args>) => void,
|
||||
}
|
||||
|
||||
export function createTypedSender<
|
||||
Args extends any[]
|
||||
>({
|
||||
channel,
|
||||
verifier,
|
||||
}: {
|
||||
channel: string,
|
||||
verifier: ListVerifier<Args>,
|
||||
}): TypedSender<Args> {
|
||||
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
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
132
src/common/utils/type-narrowing.ts
Normal file
132
src/common/utils/type-narrowing.ts
Normal file
@ -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<S extends object, K extends PropertyKey>(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<S extends object, K extends PropertyKey>(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<S extends object, K extends PropertyKey, V>(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<S extends object, K extends PropertyKey, V>(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<T, V>` 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<T extends PropertyKey, V>(val: unknown, isKey: (key: unknown) => key is T, isValue: (value: unknown) => value is V): val is Record<T, V> {
|
||||
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<T>(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<FnArgs extends any[], T>(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<T> = (arg: unknown) => arg is T;
|
||||
|
||||
export function bindPredicateOr<T1>(p1: Predicate<T1>): Predicate<T1>;
|
||||
export function bindPredicateOr<T1, T2>(p1: Predicate<T1>, p2: Predicate<T2>): Predicate<T1 | T2>;
|
||||
export function bindPredicateOr<T1, T2, T3>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>): Predicate<T1 | T2 | T3>;
|
||||
export function bindPredicateOr<T1, T2, T3, T4>(p1: Predicate<T1>, p2: Predicate<T2>, p3: Predicate<T3>, p4: Predicate<T4>): Predicate<T1 | T2 | T3 | T4>;
|
||||
|
||||
export function bindPredicateOr<T extends any[]>(...predicates: T): Predicate<T> {
|
||||
return (arg: unknown): arg is any => {
|
||||
for (const predicate of predicates) {
|
||||
if (predicate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<IClusterSettingsRouteParams> {
|
||||
@ -47,8 +46,8 @@ export class ClusterSettings extends React.Component<Props> {
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
@ -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<Props> {
|
||||
}
|
||||
|
||||
activateCluster = async (force = false) => {
|
||||
await requestMain(clusterActivateHandler, this.props.clusterId, force);
|
||||
await clusterActivate.invoke(this.props.clusterId, force);
|
||||
};
|
||||
|
||||
reconnect = async () => {
|
||||
|
||||
@ -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<Props> {
|
||||
navigate(landingURL());
|
||||
clusterStore.setActive(null);
|
||||
}
|
||||
await requestMain(clusterDisconnectHandler, cluster.id);
|
||||
await clusterDisconnect.invoke(cluster.id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@ -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<string, number>();
|
||||
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);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user