ipcMain
Main-process IPC router for receiving messages and invoke requests from renderer processes in Bunmaska.
The ipcMain module receives messages sent from renderer processes. It registers fire-and-forget channel listeners (on/once) and request/response handlers (handle) that respond to ipcRenderer.invoke. In Bunmaska the transport is WebKit-backed (a WKScriptMessageHandler inbound, evaluateJavaScript outbound) rather than Chromium’s IPC, but the router itself is transport-agnostic and the API mirrors Electron’s.
Unlike Electron, Bunmaska’s ipcMain is not a Node.js EventEmitter - it is a small purpose-built router. That distinction matters in a few places, called out below. It is a singleton, imported from the main entry point:
import { ipcMain } from 'bunmaska';
To push messages the other way (main to renderer), use webContents.send; ipcMain only receives.
Methods
ipcMain.on(channel, listener)
channelstringlistenerFunctioneventIpcMainEvent...argsunknown[]
Listens on channel. When a renderer calls ipcRenderer.send(channel, ...args), listener is called with listener(event, ...args). Returns this, so calls chain. The event object exposes sender (the originating WebContents) - and only that; see Not in Bunmaska (yet).
import { ipcMain } from 'bunmaska';
ipcMain.on('counter:increment', (event, by: number) => {
console.log('increment by', by, 'from', event.sender);
});
// Renderer Process
import { ipcRenderer } from 'bunmaska/renderer';
ipcRenderer.send('counter:increment', 1);
ipcMain.once(channel, listener)
channelstringlistenerFunctioneventIpcMainEvent...argsunknown[]
Adds a one-time listener. It fires the next time a message arrives on channel, then removes itself. Returns this.
import { ipcMain } from 'bunmaska';
ipcMain.once('app:ready-handshake', (event) => {
console.log('renderer handshook once', event.sender);
});
ipcMain.removeListener(channel, listener)
channelstringlistenerFunction
Removes a specific listener previously added with on or once for channel. Returns this. Pass the same function reference you registered.
import { ipcMain } from 'bunmaska';
const onPing = (event: unknown) => console.log('ping', event);
ipcMain.on('net:ping', onPing);
ipcMain.removeListener('net:ping', onPing);
ipcMain.removeAllListeners([channel])
channelstring (optional)
Removes every listener registered on channel. With no argument, removes all listeners on all channels. Returns this. Note this only clears on/once listeners - it does not touch handle handlers (use removeHandler for those).
import { ipcMain } from 'bunmaska';
ipcMain.removeAllListeners('net:ping'); // one channel
ipcMain.removeAllListeners(); // everything
ipcMain.handle(channel, listener)
channelstringlistenerFunction<Promise<unknown> | unknown>eventIpcMainInvokeEvent...argsunknown[]
Registers a handler for an invokable IPC. It is called whenever a renderer runs ipcRenderer.invoke(channel, ...args). If listener returns a Promise, its resolved value is sent back as the reply; otherwise the plain return value is used. There is exactly one handler per channel - calling handle again on the same channel replaces the previous handler.
If the handler throws (or rejects), the error is caught and only its message string is serialized back to the renderer, where the invoke Promise rejects. The original error object, stack, and custom properties do not cross the boundary.
import { ipcMain } from 'bunmaska';
ipcMain.handle('fs:read-config', async (event, name: string) => {
const file = Bun.file(`./config/${name}.json`);
return await file.json();
});
// Renderer Process
import { ipcRenderer } from 'bunmaska/renderer';
const config = await ipcRenderer.invoke('fs:read-config', 'app');
ipcMain.handleOnce(channel, listener)
channelstringlistenerFunction<Promise<unknown> | unknown>eventIpcMainInvokeEvent...argsunknown[]
Like handle, but the handler is removed after it responds to the first invoke. Subsequent invokes on the channel get the “no handler registered” rejection until you register again.
import { ipcMain } from 'bunmaska';
ipcMain.handleOnce('license:activate', async (event, key: string) => {
return activate(key); // only honored once
});
ipcMain.removeHandler(channel)
channelstring
Removes the handler registered for channel, if any. After this, an invoke on the channel rejects with No handler registered for '<channel>'.
import { ipcMain } from 'bunmaska';
ipcMain.removeHandler('fs:read-config');
Events
ipcMain is a plain router in Bunmaska, not an EventEmitter, so it has no module-level lifecycle events of its own. All “events” are the user-defined channels you subscribe to via on/once.
Properties
ipcMain exposes no public properties - it is the bare router singleton.
The event argument passed to your listeners and handlers carries a single field:
event.sender- theWebContentsthat sent the message. This is the only field on bothIpcMainEventandIpcMainInvokeEventtoday. It is enough to identify and reply to a source viaevent.sender.send(...), but the richer Electron event shape is not present (see below).
Not in Bunmaska (yet)
The router covers the everyday on/once/handle flow, but several Electron members are absent. Document-worthy gaps:
ipcMain.off,ipcMain.addListener- these Electron aliases forremoveListener/ondo not exist. UseremoveListenerandondirectly.- Synchronous IPC (
event.returnValue) - there is no synchronousipcRenderer.sendSyncpath, so listeners cannot setevent.returnValueto reply inline. Usehandle/invokefor request/response instead. event.reply(...)- the convenience reply helper is not on the event. To send back to a renderer, callevent.sender.send(channel, ...)yourself.event.frameId/event.processId/event.senderFrame- frame and process routing metadata is not exposed;event.senderis all you get, and iframe-level addressing is not modeled.event.portsandMessagePorttransfer -MessagePortMain/postMessagechannels are not implemented; payloads cross as JSON, so functions, symbols, andbigintare rejected by the serializer.EventEmittersurface - becauseipcMainis not anEventEmitter, methods likeeventNames(),listenerCount(),setMaxListeners(), andprependListener()are unavailable.- Full-fidelity error propagation -
handleerrors are flattened to themessagestring only; stack traces and custom error properties are lost across the boundary (the same limitation Electron documents, noted here for parity).
Everything in the Methods section above is genuinely wired and exercised without FFI, so the core renderer-to-main messaging story works on both macOS and Linux.