electron-typescript-ipc
Version:
Library for type-safe use of contextBridge in Electron
132 lines (121 loc) • 3.57 kB
text/typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
contextBridge as originalContextBridge,
ipcMain as originalIpcMain,
ipcRenderer as originalIpcRenderer,
IpcRendererEvent,
IpcMainInvokeEvent,
BrowserWindow,
} from 'electron';
type IpcRenderer<T extends Api> = {
invoke<K extends keyof T['invoke']>(
channel: K,
...args: Parameters<T['invoke'][K]>
): Promise<ReturnType<T['invoke'][K]>>;
on<T extends Api, K extends keyof T['on']>(
channel: K,
listener: (...args: Parameters<Parameters<T['on'][K]>['0']>) => void,
): void;
addEventListener<T extends Api, K extends keyof T['on']>(
channel: K,
listener: (...args: Parameters<Parameters<T['on'][K]>['0']>) => void,
): void;
removeListener<T extends Api>(
channel: keyof T['on'],
listener: (...args: unknown[]) => void,
): void;
off<T extends Api>(
channel: keyof T['on'],
listener: (...args: unknown[]) => void,
): void;
};
export const createIpcRenderer = <T extends Api>() =>
originalIpcRenderer as unknown as IpcRenderer<T> & typeof originalIpcRenderer;
type IpcMain<T extends Api> = {
handle<K extends keyof T['invoke']>(
channel: K,
listener: (
event: IpcMainInvokeEvent,
...args: Parameters<T['invoke'][K]>
) => ReturnType<T['invoke'][K]>,
): void;
handleOnce<K extends keyof T['invoke']>(
channel: K,
listener: (
event: IpcMainInvokeEvent,
...args: Parameters<T['invoke'][K]>
) => ReturnType<T['invoke'][K]>,
): void;
send<K extends keyof T['on']>(
window: BrowserWindow,
channel: K,
...args: Tail<Parameters<Parameters<T['on'][K]>['0']>>
): void;
removeHandler<T extends Api>(channel: keyof T['invoke']): void;
};
export const createIpcMain = <T extends Api>() =>
({
...originalIpcMain,
send<T extends Api, K extends keyof T['on']>(
window: BrowserWindow,
channel: K,
...args: Tail<Parameters<Parameters<T['on'][K]>['0']>>
): void {
window.webContents.send(channel as string, ...args);
},
} as unknown as IpcMain<T> & typeof originalIpcMain);
type Tail<T extends unknown[]> = T extends [any, ...infer Rest] ? Rest : [];
type UnknownFunction = (...args: any) => unknown;
type IpcRendererListener<Args extends UnknownFunction> = (
event: IpcRendererEvent,
...args: Parameters<Args>
) => void;
export type GetApiType<T, S extends Record<string, UnknownFunction>> = {
invoke: T;
on: { [K in keyof S]: (listener: IpcRendererListener<S[K]>) => void };
};
type Api = GetApiType<Record<string, any>, Record<string, any>>;
/**
* @example
* import { contextBridge, createIpcRenderer, GetApiType } from 'electron-typescript-ipc';
*
* const ipcRenderer = createIpcRenderer<Api>();
*
* export type Api = GetApiType<
* {
* getDataFromStore: (str: string) => Promise<string>;
* },
* {
* showAlert: (text: string, num: number) => Promise<void>;
* }
* >;
*
* const api: Api = {
* invoke: {
* getDataFromStore: async (key: string) => {
* return await ipcRenderer.invoke('getDataFromStore', key);
* },
* },
* on: {
* showAlert: (listener) => {
* ipcRenderer.on('showAlert', listener);
* },
* },
* };
*
* contextBridge.exposeInMainWorld('myAPI', api);
*
* declare global {
* interface Window {
* myAPI: Api;
* }
* }
*/
export const contextBridge = {
exposeInMainWorld: <T extends GetApiType<any, any>>(
apiKey: string,
api: T,
) => {
originalContextBridge.exposeInMainWorld(apiKey, api);
},
};