Created
August 18, 2025 12:04
-
-
Save Gergling/e9e9b2406ab5942a46a57260b4dcca48 to your computer and use it in GitHub Desktop.
Some utility functions and types for setting up a consistent electron app IPC layer. `core.ts` is the abstracted functions and types, and the other files illustrate the implementation examples.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This is the property indicating where the functions will be exposed. E.g. window.ipc.testIPC('test'); | |
| export const IPC_EXPOSURE_PROPERTY_NAME = 'ipc'; | |
| // Minimalist configuration type for the renderer side. | |
| export type IpcInvocationConfig = IpcInvocationConfigBase<{ | |
| testIPC: (message: string) => string; | |
| runQuery: (query: string) => { success: boolean; error?: string }; | |
| }>; | |
| // The handler configuration will run functions from the backend. | |
| // This example includes a (custom) database handler object, which | |
| // simply allows access to static functions. | |
| export const ipcHandlerConfig: IpcHandlerConfig< | |
| IpcInvocationConfig, | |
| { database: IpcHandlerDatabase; } | |
| > = { | |
| testIPC: async ({ args: [message] }) => { | |
| console.log('IPC test received:', message); | |
| return `Received: ${message}`; | |
| }, | |
| runQuery: ({ | |
| args: [query], | |
| database: { runQuery }, | |
| }) => runQuery(query), | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // These types are used a couple of times, so they're shortened. | |
| type ParamsBase = Record<string, unknown>; | |
| type IpcInvocationConfigTemplate<T = unknown, U = unknown> = { | |
| [K: string]: (...args: T[]) => Promise<U>; | |
| }; | |
| // A base type for simplifying the invocation config type. | |
| export type IpcInvocationConfigBase<V extends ({ | |
| [K: string]: (...args: unknown[]) => unknown; | |
| })> = { | |
| [K in keyof V]: (...args: Parameters<V[K]>) => Promise<ReturnType<V[K]>>; | |
| }; | |
| // The handler configuration type based on the invocation config type. | |
| export type IpcHandlerConfig< | |
| IpcInvocationConfig extends IpcInvocationConfigTemplate, | |
| Params extends ParamsBase | |
| > = { | |
| [K in keyof IpcInvocationConfig]: ( | |
| params: ( | |
| & { | |
| event: Electron.IpcMainInvokeEvent; | |
| } | |
| & Params | |
| & { args: Parameters<IpcInvocationConfig[K]>; } | |
| ) | |
| ) => ReturnType<IpcInvocationConfig[K]>; | |
| }; | |
| // A function for setting up the IPC handlers based on the config. | |
| export const handleIpc = < | |
| IpcInvocationConfig extends IpcInvocationConfigTemplate, | |
| Params extends ParamsBase | |
| >( | |
| config: IpcHandlerConfig<IpcInvocationConfig, Params>, | |
| ipcMain: Electron.IpcMain, | |
| additionalParameters?: Params | |
| ) => Object.entries(config).forEach(([ channelName, func ]) => { | |
| console.log(`Setting up IPC channel handler for ${channelName}.`); | |
| ipcMain.handle(channelName, (event, ...args) => func({ | |
| event, | |
| args, | |
| ...additionalParameters | |
| })); | |
| }); | |
| // A function for exposing IPC invocations to the renderer process. | |
| export const preloadIpc = < | |
| IpcInvocationConfig extends IpcInvocationConfigTemplate, | |
| Params extends ParamsBase | |
| >( | |
| ipcExposurePropertyName: string, | |
| config: IpcHandlerConfig<IpcInvocationConfig, Params>, | |
| contextBridge: Electron.ContextBridge, | |
| ipcRenderer: Electron.IpcRenderer, | |
| ) => { | |
| const invocations = Object | |
| .entries(config) | |
| .reduce(( | |
| invocations, | |
| [invocationName] | |
| ) => ({ | |
| ...invocations, | |
| [invocationName]: ( | |
| ...args: unknown[] // The exposed type will expect whatever the | |
| // invocation type has defined once we've cast it. | |
| ) => ipcRenderer.invoke(invocationName, ...args), | |
| }), {} as IpcInvocationConfig); | |
| contextBridge.exposeInMainWorld( | |
| ipcExposurePropertyName, | |
| invocations | |
| ); | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This is called from the main process to set up the IPC handlers. | |
| handleIpc(ipcHandlerConfig, ipcMain, { database }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This is called from the renderer preload. | |
| preloadIpc(IPC_EXPOSURE_PROPERTY_NAME, ipcHandlerConfig, contextBridge, ipcRenderer); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Somewhere on the renderer side you will want to augment your Window object type. | |
| declare global { | |
| interface Window { | |
| [IPC_EXPOSURE_PROPERTY_NAME]: IpcInvocationConfig; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment