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

> Explore OpenWhispr's hybrid IPC communication pattern. Discover how it blends request-response and publish-subscribe for efficient Electron process interaction.

- Repository: [OpenWhispr/openwhispr](https://github.com/OpenWhispr/openwhispr)
- Tags: architecture
- Published: 2026-09-06

---

**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`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/ipcHandlers.js) and [`preload.js`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/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.

```javascript
// 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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/ipcHandlers.js)** – Central hub registering `ipcMain.handle` and `ipcMain.on` handlers, plus the `broadcastToWindows` utility for multi-window messaging
- **[`preload.js`](https://github.com/OpenWhispr/openwhispr/blob/main/preload.js)** – Exposes curated APIs to the renderer via `contextBridge.exposeInMainWorld`, wrapping `ipcRenderer` methods to maintain sandbox security
- **[`main.js`](https://github.com/OpenWhispr/openwhispr/blob/main/main.js)** – Bootstraps the Electron application, configures platform-specific IPC flags, and initializes IPC channels before window creation
- **[`src/helpers/windowBroadcast.js`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/preload.js), the API surface exposes specific channels:

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

```

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

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

```

The main process handler in [`ipcHandlers.js`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/preload.js):

```javascript
// 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:

```javascript
// 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:

```javascript
// 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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/ipcHandlers.js) ensures consistent error objects reach the renderer.
- **The preload script** ([`preload.js`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/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`](https://github.com/OpenWhispr/openwhispr/blob/main/src/helpers/ipcHandlers.js), while the allowed renderer-side API surface is defined in [`preload.js`](https://github.com/OpenWhispr/openwhispr/blob/main/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.