# How the Renderer Communicates with the Main Process in Munder Difflin: Complete IPC Guide

> Learn how the Munder Difflin renderer communicates with the main process using contextBridge and ipcRenderer.invoke. Explore this complete IPC guide for secure sandbox isolation.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: deep-dive
- Published: 2026-08-28

---

**Munder Difflin uses a secure preload bridge with Electron's `contextBridge` and `ipcRenderer.invoke`/`ipcMain.handle` pattern to let the renderer process call main-process operations while maintaining strict sandbox isolation.**

This architecture follows Electron's security best practices by exposing a typed, minimal API surface to the UI. The renderer process never accesses Node.js or system APIs directly—instead, all privileged operations flow through an explicitly defined bridge in [`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts).

## The Preload Bridge: Controlled API Exposure

The **preload script** ([`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts)) runs in a privileged context with access to both Electron APIs and the DOM. It uses `contextBridge.exposeInMainWorld` to inject a safe, typed API that the renderer can access via `window.api`.

The bridge provides two communication patterns:

- **`ipcRenderer.invoke`** – for request/response calls that return promises
- **`ipcRenderer.on`/`ipcRenderer.send`** – for event-driven, bidirectional streaming

### Key Bridge Methods

| Method | IPC Channel | Purpose |
|--------|-------------|---------|
| `hiveBoard()` | `hive:board` | Fetches current Hive board state |
| `onHiveMessage(listener)` | `hive:message` | Subscribes to real-time Hive messages |
| `ptySpawn(opts)` | `pty:spawn` | Spawns a new pseudo-terminal |
| `gitStatus()` | `git:status` | Retrieves Git repository status |

**Source:** [[`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts)

```typescript
// src/preload/index.ts
export const api = {
  // Request/response pattern
  hiveBoard: () => ipcRenderer.invoke('hive:board'),
  ptySpawn: (opts: PtyOptions) => ipcRenderer.invoke('pty:spawn', opts),

  // Event subscription pattern with cleanup
  onHiveMessage: (listener: (msg: HiveMessage) => void) => {
    ipcRenderer.on('hive:message', listener);
    return () => ipcRenderer.removeListener('hive:message', listener);
  },
};

contextBridge.exposeInMainWorld('api', api);

```

## Main-Process Handlers: Executing Privileged Operations

The **main process** ([`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts)) registers handlers using `ipcMain.handle` for async invoke calls and `ipcMain.on` for incoming events. These handlers perform system-level work that the sandboxed renderer cannot access directly—including file system operations, PTY management, and Hive state updates.

**Source:** [[`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts)

```typescript
// src/main/index.ts
import { ipcMain, BrowserWindow } from 'electron';
import { hive } from './hive';

// Handle renderer requests
ipcMain.handle('hive:board', () => hive.board());

ipcMain.handle('pty:spawn', async (event, opts: PtyOptions) => {
  // Spawn PTY with system privileges unavailable to renderer
  const pty = new PTY(opts);
  return pty.id;
});

// Broadcast events to all renderer windows
ipcMain.on('hive:broadcast', (event, msg) => {
  BrowserWindow.getAllWindows().forEach(win => {
    win.webContents.send('hive:message', msg);
  });
});

```

The main process acts as a **central authority** for all privileged operations, enforcing security boundaries while enabling rich desktop functionality.

## Renderer Usage: Clean Async API Surface

In the UI layer (React/Vue components), the renderer accesses the bridge through the globally exposed `window.api` object. This abstraction hides all IPC complexity—methods appear as standard async functions with TypeScript support.

**Source:** [[`src/renderer/src/store/store.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts)

```typescript
// src/renderer/src/store/store.ts
async function loadHiveBoard() {
  // Automatically becomes ipcRenderer.invoke('hive:board')
  const board = await window.api.hiveBoard();
  store.set({ board });
}

// Subscribe with automatic cleanup
function subscribeToMessages() {
  const unsubscribe = window.api.onHiveMessage(message => {
    store.updateMessages(message);
  });
  
  // Cleanup on unmount
  return unsubscribe;
}

```

The renderer code remains **pure frontend logic**—no Electron imports, no direct IPC knowledge, and fully testable outside the Electron environment.

## Communication Patterns Compared

Munder Difflin implements **two distinct IPC patterns** based on use case requirements:

**Request/Response (`invoke`/`handle`)**
- Use: Single-shot data fetching, command execution
- Example: `hiveBoard()`, `ptySpawn()`, `gitStatus()`
- Guarantees: Promise-based, automatic error propagation

**Event-Driven (`on`/`send`)**
- Use: Streaming data, real-time updates, broadcasts
- Example: `onHiveMessage()`, terminal output streams
- Guarantees: Manual subscription cleanup, multicast to windows

Both patterns are fully typed through TypeScript interfaces defined in the preload script, ensuring compile-time safety across process boundaries.

## Summary

- **Preload bridge** ([`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts)) exposes a minimal, typed API via `contextBridge` to maintain renderer sandbox security
- **Main handlers** ([`src/main/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/index.ts)) register `ipcMain.handle` and `ipcMain.on` callbacks for all privileged operations
- **Renderer code** calls `window.api` methods as standard async functions without direct Electron dependencies
- **Two patterns**: `invoke`/`handle` for request/response, `on`/`send` for streaming events
- **Security model**: Renderer has zero Node.js or system access; all operations flow through explicit, auditable bridge definitions

## Frequently Asked Questions

### How does Munder Difflin prevent the renderer from accessing unsafe APIs?

The preload script uses Electron's **`contextBridge.exposeInMainWorld`** to whitelist only specific methods. The renderer runs with `contextIsolation: true` (Electron's default), meaning it cannot access `require`, Node.js modules, or Electron APIs directly. Every system interaction must pass through the explicitly defined bridge in [`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts).

### What's the difference between `ipcRenderer.invoke` and `ipcRenderer.send`?

`ipcRenderer.invoke` is used with `ipcMain.handle` for **request/response patterns**—it returns a Promise with the main process result. `ipcRenderer.send` is used with `ipcMain.on` for **fire-and-forget or event streaming**—it has no return value and is typically paired with `ipcRenderer.on` for bidirectional communication. Munder Difflin uses `invoke` for Hive board fetches and `on`/`send` for real-time message streams.

### Can renderer-to-main calls timeout or fail?

Yes. Since `ipcRenderer.invoke` returns a Promise, it can reject if the main handler throws or if Electron's internal IPC mechanism fails. The preload bridge in [`src/preload/index.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/preload/index.ts) does not add automatic retry logic—renderer code should handle errors using standard `try/catch` or `.catch()` patterns on the returned Promise.

### Where is the API type definition for `window.api`?

Type definitions are co-located in the preload script or a shared types file imported by both preload and renderer. This ensures **TypeScript intellisense** in [`src/renderer/src/store/store.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/store.ts) while maintaining runtime safety through `contextBridge`. The global `window.api` type is typically declared via a TypeScript interface augmentation in the renderer's type declarations.