Skip to content

Instantly share code, notes, and snippets.

@Gergling
Created August 18, 2025 12:04
Show Gist options
  • Save Gergling/e9e9b2406ab5942a46a57260b4dcca48 to your computer and use it in GitHub Desktop.
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 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),
};
// 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 is called from the main process to set up the IPC handlers.
handleIpc(ipcHandlerConfig, ipcMain, { database });
// This is called from the renderer preload.
preloadIpc(IPC_EXPOSURE_PROPERTY_NAME, ipcHandlerConfig, contextBridge, ipcRenderer);
// 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