1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

ipc-refactoring, fixes

Signed-off-by: Roman <ixrock@gmail.com>
Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Roman 2020-08-06 22:49:18 +03:00 committed by Lauri Nevala
parent a78d5a0fb9
commit 5452fdb32d
5 changed files with 112 additions and 72 deletions

View File

@ -265,7 +265,7 @@
"css-element-queries": "^1.2.3",
"css-loader": "^3.5.3",
"dompurify": "^2.0.11",
"electron": "^9.1.0",
"electron": "^9.1.2",
"electron-builder": "^22.7.0",
"electron-notarize": "^0.3.0",
"electron-rebuild": "^1.11.0",

View File

@ -2,20 +2,118 @@
// 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, IpcRendererEvent, WebContents, webContents } from "electron"
import logger from "../main/logger";
import { getRandId } from "./utils";
export type IpcChannel = string;
export interface IpcHandleOpts {
timeout?: number;
export enum IpcMode {
SYNC = "sync",
ASYNC = "async",
}
export interface IpcMessageHandler<T extends any[] = any> {
(...args: T): any;
export interface IpcChannelRequest<A extends any[] = any> {
msgId: string;
args: A;
}
export interface IpcMessageOpts<A extends any[] = any> {
export interface IpcChannelResponse<T extends any[] = any, E = any> {
msgId: string;
data?: T;
error?: E;
}
export interface IpcChannelInit {
channel: IpcChannel; // main <-> renderer communication channel name
mode?: IpcMode; // default: "async", use "sync" as last resort: https://www.electronjs.org/docs/api/ipc-renderer#ipcrenderersendsyncchannel-args
handle?: (...args: any[]) => any; // main-process message handler
autoBind?: boolean; // auto-bind message handler in main-process, default: false
timeout?: number; // timeout for waiting response from the sender
once?: boolean; // todo: add support
}
export function createIpcChannel({ autoBind = false, mode = IpcMode.ASYNC, timeout = 0, handle, channel }: IpcChannelInit) {
channel = `${mode}:${channel}`
const ipcChannel = {
channel: channel,
handleInMain: () => {
logger.info(`[IPC]: setup channel "${channel}"`);
ipcMain.on(channel, async (event, req: IpcChannelRequest) => {
let resolved = false;
let timerId: any;
function resolve(res: Partial<IpcChannelResponse>) {
if (resolved) return;
res.msgId = req.msgId; // return back to sender to be able to handle response
resolved = true
logger.info(`[IPC]: sending response to "${channel}"`, res);
if (mode === IpcMode.ASYNC) {
event.reply(channel, res);
}
if (mode === IpcMode.SYNC) {
event.returnValue = res;
}
}
if (timeout > 0) {
timerId = setTimeout(() => {
const timeoutError = new Error(`[IPC]: response timeout in ${timeout}ms`);
resolve({ error: timeoutError })
}, timeout);
}
try {
const data = await handle(...req.args); // todo: maybe exec in separate thread/worker
resolve({ data })
} catch (error) {
resolve({
error: String(error)
})
} finally {
clearTimeout(timerId);
}
})
},
invokeFromRenderer: async (...args: any[]) => {
const req: IpcChannelRequest = {
msgId: getRandId({ prefix: "ipc-msg-id" }),
args: args,
}
logger.info(`[IPC]: "${channel}" sending message to main`, req);
if (mode === IpcMode.ASYNC) {
ipcRenderer.send(channel, req)
}
if (mode === IpcMode.SYNC) {
ipcRenderer.sendSync(channel, req)
}
return new Promise(async (resolve, reject) => {
ipcRenderer.on(channel, function waitResponseHandler(event: IpcRendererEvent, res: IpcChannelResponse) {
if (req.msgId === res.msgId) {
const meta = { ...req, ...res };
if (res.data) {
logger.info(`[IPC]: "${channel}" resolve`, meta);
resolve(res.data);
}
if (res.error) {
logger.error(`[IPC]: "${channel}" reject`, meta);
reject(res.error);
}
ipcRenderer.off(channel, waitResponseHandler); // unsubscribe since handled
}
});
})
},
}
if (autoBind && ipcMain) {
ipcChannel.handleInMain();
}
return ipcChannel;
}
export interface IpcBroadcastParams<A extends any[] = any> {
channel: IpcChannel
webContentId?: number; // sends to single webContents view
filter?: (webContent: WebContents) => boolean
@ -23,7 +121,7 @@ export interface IpcMessageOpts<A extends any[] = any> {
args?: A;
}
export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcMessageOpts) {
export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcBroadcastParams) {
const singleView = webContentId ? webContents.fromId(webContentId) : null;
let views = singleView ? [singleView] : webContents.getAllWebContents();
if (filter) {
@ -35,61 +133,3 @@ export function broadcastIpc({ channel, webContentId, filter, args = [] }: IpcMe
webContent.send(channel, ...[args].flat());
})
}
// todo: support timeout + merge with sendMessage?
export async function invokeIpc<R = any>(channel: IpcChannel, ...args: any[]): Promise<R> {
logger.info(`[IPC]: invoke channel "${channel}"`, { args });
return ipcRenderer.invoke(channel, ...args);
}
// todo: make isomorphic api
export function handleIpc(channel: IpcChannel, handler: IpcMessageHandler, options: IpcHandleOpts = {}) {
const { timeout = 0 } = options;
logger.info(`[IPC]: setup to handle "${channel}"`);
ipcMain.handle(channel, async (event, ...args) => {
logger.info(`[IPC]: handle "${channel}"`, { args });
return new Promise(async (resolve, reject) => {
let timerId;
if (timeout) {
timerId = setTimeout(() => {
const timeoutError = new Error("[IPC]: response timeout");
reject(timeoutError);
}, timeout);
}
try {
const result = await handler(...args); // todo: maybe exec in separate thread/worker
resolve(result);
clearTimeout(timerId);
} catch (err) {
reject(err);
}
})
})
}
export interface IpcPairOptions {
channel: IpcChannel
handle?: IpcMessageHandler
autoBind?: boolean;
timeout?: number;
}
// todo: improve api
export function createIpcChannel({ channel, autoBind, ...initOpts }: IpcPairOptions) {
const bindHandler = (opts: { handler?: IpcMessageHandler, options?: IpcHandleOpts } = {}) => {
const handler = opts.handler || initOpts.handle || Function;
const options = opts.options || { timeout: initOpts.timeout };
handleIpc(channel, handler, options);
};
if (autoBind) {
bindHandler();
}
return {
channel: channel,
handleInMain: bindHandler,
invokeFromRenderer(...args: any[]) {
return invokeIpc(channel, ...args);
},
}
}

View File

@ -52,7 +52,7 @@ export class ClusterStatus extends React.Component<Props> {
}
async refreshClusterState() {
return clusterIpc.activate.invokeFromRenderer(this.clusterId);
await clusterIpc.activate.invokeFromRenderer(this.clusterId);
}
reconnect = async () => {

View File

@ -38,7 +38,7 @@ export class ClusterView extends React.Component {
view.setAttribute("enableremotemodule", "true")
view.addEventListener("did-finish-load", () => {
console.log('CLUSTER VIEW READY!', cluster)
view.openDevTools()
// view.openDevTools()
});
view.addEventListener("did-fail-load", event => {
// todo: handle

View File

@ -4647,10 +4647,10 @@ electron@*:
"@types/node" "^12.0.12"
extract-zip "^1.0.3"
electron@^9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.1.0.tgz#ca77600c9e4cd591298c340e013384114d3d8d05"
integrity sha512-VRAF8KX1m0py9I9sf0kw1kWfeC87mlscfFcbcRdLBsNJ44/GrJhi3+E8rKbpHUeZNQxsPaVA5Zu5Lxb6dV/scQ==
electron@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/electron/-/electron-9.1.2.tgz#bfa26d6b192ea13abb6f1461371fd731a8358988"
integrity sha512-xEYadr3XqIqJ4ktBPo0lhzPdovv4jLCpiUUGc2M1frUhFhwqXokwhPaTUcE+zfu5+uf/ONDnQApwjzznBsRrgQ==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"