OpenWhispr IPC Communication Pattern: Hybrid Request-Response and Pub-Sub Architecture

OpenWhispr implements a hybrid IPC communication pattern that combines request-response for asynchronous operations and publish-subscribe for event broadcasting between Electron's main and renderer processes.

OpenWhispr is an Electron-based speech-to-text application that relies on a sophisticated IPC communication pattern to coordinate between its React-based UI and the privileged main process. The architecture leverages Electron’s built-in IPC layer through a carefully structured approach defined in src/helpers/ipcHandlers.js and preload.js. This design ensures type-safe, bidirectional data flow while maintaining strict context isolation via the preload script bridge.

Core IPC Architecture

The IPC communication pattern in OpenWhispr follows a dual-model approach that separates stateful operations from event notifications. This hybrid design allows the renderer process to invoke privileged main-process actions while remaining reactive to system-wide state changes.

Request-Response for Stateful Operations

For operations requiring return values or error handling, OpenWhispr uses Electron’s ipcRenderer.invoke paired with ipcMain.handle. This pattern appears throughout src/helpers/ipcHandlers.js for database operations and state queries.

The main-process handler wraps async logic with serializeIpcError, which normalizes exceptions into structured objects containing error, code, and messageKey properties. This ensures the renderer receives predictable error shapes rather than raw exception objects.

// src/helpers/ipcHandlers.js
ipcMain.handle('db-save-transcription',
  serializeIpcError(async (event, text, rawText, options) => {
    // SQLite write operation
    return { id: newId };
  })
);

Fire-and-Forget Notifications

For one-way communication that requires no acknowledgment, the pattern shifts to ipcRenderer.send and ipcMain.on. This mechanism handles UI state notifications like mic-warm-hold-changed and dictation-lifecycle-state-changed where the renderer simply informs the main process of a state transition without awaiting a result.

Publish-Subscribe Event Broadcasting

The main process pushes events to the renderer using window.webContents.send or event.reply, while renderers subscribe via ipcRenderer.on listeners registered in preload.js. This pub-sub pattern enables real-time updates for global state changes such as dictionary-updated or agent-dictation-pill-state-changed.

Implementation Files

Four primary files define the IPC communication pattern in OpenWhispr:

  • src/helpers/ipcHandlers.js – Central hub registering ipcMain.handle and ipcMain.on handlers, plus the broadcastToWindows utility for multi-window messaging
  • preload.js – Exposes curated APIs to the renderer via contextBridge.exposeInMainWorld, wrapping ipcRenderer methods to maintain sandbox security
  • main.js – Bootstraps the Electron application, configures platform-specific IPC flags, and initializes IPC channels before window creation
  • src/helpers/windowBroadcast.js – Utility module providing broadcastToWindows to iterate over BrowserWindow.getAllWindows() for cross-window messaging

Request-Response Implementation

The request-response pattern enables the React frontend to execute privileged operations safely. In preload.js, the API surface exposes specific channels:

// preload.js
saveTranscription: (text, rawText, options) =>
  ipcRenderer.invoke('db-save-transcription', text, rawText, options),

React components consume this through the global window.electronAPI object:

// React component
await window.electronAPI.saveTranscription(text, rawText, { source: 'dictation' });

The main process handler in ipcHandlers.js processes the request and returns a serializable result, or throws an error captured by the serializeIpcError wrapper.

Publish-Subscribe Implementation

For events originating in the main process, OpenWhispr uses a pub-sub model that supports multiple renderer windows. The subscription setup occurs in preload.js:

// preload.js
onDictionaryUpdated: (callback) => {
  const listener = (_event, words) => callback?.(words);
  ipcRenderer.on('dictionary-updated', listener);
  return () => ipcRenderer.removeListener('dictionary-updated', listener);
},

React components register and clean up listeners using the returned unsubscribe function:

// Component lifecycle
const unsubscribe = window.electronAPI.onDictionaryUpdated(updatedWords => {
  console.log('Dictionary changed', updatedWords);
});
return unsubscribe; // Cleanup on unmount

When the main process needs to broadcast the event, it uses the broadcastToWindows helper:

// src/helpers/windowBroadcast.js
function broadcastToWindows(channel, ...args) {
  BrowserWindow.getAllWindows().forEach(w => w.webContents.send(channel, ...args));
}

// Usage in ipcHandlers.js
function broadcastDictionaryUpdate(words) {
  broadcastToWindows('dictionary-updated', words);
}

Multi-Window Coordination

OpenWhispr supports multiple renderer windows (such as control panels and overlays). The windowBroadcast.broadcastToWindows function in src/helpers/windowBroadcast.js ensures state changes reach all windows simultaneously by iterating over the BrowserWindow registry and dispatching via webContents.send.

Summary

  • OpenWhispr uses a hybrid IPC communication pattern combining invoke/handle for request-response and on/send for pub-sub messaging.
  • Error serialization via serializeIpcError in src/helpers/ipcHandlers.js ensures consistent error objects reach the renderer.
  • The preload script (preload.js) securely exposes IPC capabilities through contextBridge.exposeInMainWorld, preventing direct Node.js access in the renderer.
  • Multi-window broadcasting uses broadcastToWindows to push events to all BrowserWindow instances when global state changes occur.
  • Channel registration happens early in main.js before window creation, ensuring IPC readiness at application startup.

Frequently Asked Questions

How does OpenWhispr handle main-to-renderer communication?

OpenWhispr uses Electron’s webContents.send method from the main process paired with ipcRenderer.on listeners registered in preload.js. This pub-sub pattern allows the main process to broadcast events like toggle-dictation or dictionary-updated to React components, which subscribe via window.electronAPI methods that return cleanup functions for listener removal.

What error handling pattern does OpenWhispr use for IPC calls?

The application wraps ipcMain.handle registrations with serializeIpcError, a helper defined in src/helpers/ipcHandlers.js that catches exceptions and transforms them into structured objects with error, code, and messageKey properties. This ensures that ipcRenderer.invoke calls in the renderer always resolve to either valid data or a predictable error shape rather than raw exception objects.

Can OpenWhispr send messages between multiple renderer windows?

Yes. Through the broadcastToWindows function in src/helpers/windowBroadcast.js, the main process iterates over all BrowserWindow instances obtained via BrowserWindow.getAllWindows() and dispatches events using w.webContents.send(). This enables synchronized state updates across multiple windows such as the main control panel and floating overlay windows.

Where are IPC channels defined and secured in OpenWhispr?

IPC channel handlers are registered in src/helpers/ipcHandlers.js, while the allowed renderer-side API surface is defined in preload.js using contextBridge.exposeInMainWorld. This isolation pattern ensures that only explicitly whitelisted methods and channels are accessible to the React renderer, preventing arbitrary IPC access or Node.js API exposure in the frontend context.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →