diff --git a/app/plugins.ts b/app/plugins.ts index 2e2d257b..733e10c1 100644 --- a/app/plugins.ts +++ b/app/plugins.ts @@ -1,7 +1,7 @@ /* eslint-disable eslint-comments/disable-enable-pair */ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/no-unsafe-call */ -import {app, dialog, BrowserWindow, App, ipcMain} from 'electron'; +import {app, dialog, BrowserWindow, App, ipcMain as _ipcMain} from 'electron'; import {resolve, basename} from 'path'; import {writeFileSync} from 'fs'; import Config from 'electron-store'; @@ -17,6 +17,7 @@ import mapKeys from './utils/map-keys'; import {configOptions} from '../lib/config'; import {promisify} from 'util'; import {exec, execFile} from 'child_process'; +import {IpcMainWithCommands} from '../common'; // local storage const cache = new Config(); @@ -456,12 +457,12 @@ export const decorateSessionClass = (Session: T): T => { export {toDependencies as _toDependencies}; -ipcMain.handle('child_process.exec', (event, args) => { - const {command, options} = args; +const ipcMain = _ipcMain as IpcMainWithCommands; + +ipcMain.handle('child_process.exec', (event, command, options) => { return promisify(exec)(command, options); }); -ipcMain.handle('child_process.execFile', (event, _args) => { - const {file, args, options} = _args; +ipcMain.handle('child_process.execFile', (event, file, args, options) => { return promisify(execFile)(file, args, options); }); diff --git a/app/rpc.ts b/app/rpc.ts index c6831c33..656fcca7 100644 --- a/app/rpc.ts +++ b/app/rpc.ts @@ -1,5 +1,5 @@ import {EventEmitter} from 'events'; -import {ipcMain, BrowserWindow} from 'electron'; +import {ipcMain, BrowserWindow, IpcMainEvent} from 'electron'; import {v4 as uuidv4} from 'uuid'; import {TypedEmitter, MainEvents, RendererEvents, FilterNever} from '../common'; @@ -35,7 +35,8 @@ export class Server { return this.win.webContents; } - ipcListener = (event: any, {ev, data}: {ev: keyof MainEvents; data: any}) => this.emitter.emit(ev, data); + ipcListener = (event: IpcMainEvent, {ev, data}: {ev: U; data: MainEvents[U]}) => + this.emitter.emit(ev, data); on = (ev: U, fn: (arg0: MainEvents[U]) => void) => { this.emitter.on(ev, fn); diff --git a/common.d.ts b/common.d.ts index 5f69d9ba..5995d92f 100644 --- a/common.d.ts +++ b/common.d.ts @@ -1,4 +1,6 @@ import type parseUrl from 'parse-url'; +import type {IpcMain, IpcRenderer} from 'electron'; +import type {ExecFileOptions, ExecOptions} from 'child_process'; export type Session = { uid: string; @@ -94,3 +96,34 @@ export interface TypedEmitter { removeListener(event: E, listener: (args: Events[E]) => void): this; removeAllListeners(event?: E): this; } + +type OptionalPromise = T | Promise; + +export type IpcCommands = { + 'child_process.exec': (command: string, options: ExecOptions) => {stdout: string; stderr: string}; + 'child_process.execFile': ( + file: string, + args: string[], + options: ExecFileOptions + ) => { + stdout: string; + stderr: string; + }; +}; + +export interface IpcMainWithCommands extends IpcMain { + handle( + channel: E, + listener: ( + event: Electron.IpcMainInvokeEvent, + ...args: Parameters + ) => OptionalPromise> + ): void; +} + +export interface IpcRendererWithCommands extends IpcRenderer { + invoke( + channel: E, + ...args: Parameters + ): Promise>; +} diff --git a/lib/utils/ipc-child-process.ts b/lib/utils/ipc-child-process.ts index 6f1dae22..b963032c 100644 --- a/lib/utils/ipc-child-process.ts +++ b/lib/utils/ipc-child-process.ts @@ -1,11 +1,13 @@ -import {ipcRenderer} from 'electron'; +import electron from 'electron'; +import type {IpcRendererWithCommands} from '../../common'; +const ipcRenderer = electron.ipcRenderer as IpcRendererWithCommands; export function exec(command: string, options?: any, callback?: (..._args: any) => void) { if (typeof options === 'function') { callback = options; options = {}; } - ipcRenderer.invoke('child_process.exec', {command, options}).then( + ipcRenderer.invoke('child_process.exec', command, options).then( ({stdout, stderr}) => callback?.(null, stdout, stderr), (error) => callback?.(error, '', '') ); @@ -25,7 +27,7 @@ export function execFile(file: string, args?: any, options?: any, callback?: (.. args = null; options = null; } - ipcRenderer.invoke('child_process.execFile', {file, args, options}).then( + ipcRenderer.invoke('child_process.execFile', file, args, options).then( ({stdout, stderr}) => callback?.(null, stdout, stderr), (error) => callback?.(error, '', '') ); diff --git a/lib/utils/rpc.ts b/lib/utils/rpc.ts index 40de6958..e7891897 100644 --- a/lib/utils/rpc.ts +++ b/lib/utils/rpc.ts @@ -46,8 +46,8 @@ export default class Client { return this; }; - emit>>(ch: U): boolean; - emit>(ch: U, data: MainEvents[U]): boolean; + emit>>(ev: U): boolean; + emit>(ev: U, data: MainEvents[U]): boolean; emit(ev: U, data?: MainEvents[U]) { if (!this.id) { throw new Error('Not ready');