Skip to content

Instantly share code, notes, and snippets.

@WilliamStam
Created January 14, 2026 07:14
Show Gist options
  • Select an option

  • Save WilliamStam/2f392fb1620ca00e827ea8ae70f22f88 to your computer and use it in GitHub Desktop.

Select an option

Save WilliamStam/2f392fb1620ca00e827ea8ae70f22f88 to your computer and use it in GitHub Desktop.
Electron auto registering IPC services
import {IpcHandler} from "../utilities/ipc-registry";
export class FishService {
@IpcHandler()
async cake(): Promise<string | null> {
return "Cake is a lie";
}
}
// Export a singleton instance
export const fishService = new FishService();
export default fishService;
// add typescript aware stuff to window.fish.xx
declare global {
interface Window {
fish: {
cake: () => Promise<string | null>;
};
}
}
import {ipcMain, contextBridge, ipcRenderer} from 'electron';
interface HandlerMetadata {
service: string;
method: string;
channel: string;
}
const handlersRegistry = new Map<any, HandlerMetadata[]>();
/**
* Decorator to mark a method as an IPC handler
*/
export function IpcHandler(channel?: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const serviceName = target.constructor.name.replace('Service', '').toLowerCase();
const channelName = channel || `${serviceName}:${propertyKey}`;
if (!handlersRegistry.has(target.constructor)) {
handlersRegistry.set(target.constructor, []);
}
handlersRegistry.get(target.constructor)!.push({
service: serviceName,
method: propertyKey,
channel: channelName,
});
return descriptor;
};
}
/**
* Register all IPC handlers for a service instance (MAIN PROCESS ONLY)
*/
export function registerServiceHandlers(serviceInstance: any) {
const handlers = handlersRegistry.get(serviceInstance.constructor);
console.log("main handlers", handlers);
if (!handlers) {
console.warn(`No handlers registered for ${serviceInstance.constructor.name}`);
return;
}
handlers.forEach(({service, method, channel}) => {
ipcMain.handle(channel, async (event, ...args) => {
try {
return await serviceInstance[method](...args);
} catch (error) {
console.error(`Error in handler ${channel}:`, error);
throw error;
}
});
console.log(`Registered IPC handler: ${channel} -> ${serviceInstance.constructor.name}.${method}`);
});
}
import {registerServiceHandlers} from "./utilities/ipc-registry";
import fishService from "./datasets/fish";
// Normal main file stuff. im not including it to keep this easy to follow
// This is all thats needed for the main service. just import the
registerServiceHandlers(fishService)
// unfortunately we cant use the auto registering thing because preload is only able to laod a subset of imports.
// the registry above can actualy build this SOOO easily but because of the security stuff we cant do so.. so sadly we need to duplicate
import {contextBridge, ipcRenderer} from 'electron';
contextBridge.exposeInMainWorld('fish', {
cake: () => ipcRenderer.invoke('fish:cake'),
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment