# IPC Communication Pattern Between Electron Main and Renderer Processes in Lepton

> Explore Lepton's asynchronous IPC communication between Electron main and renderer processes. Learn how lifecycle commands and system events are managed.

- Repository: [CosmoX/Lepton](https://github.com/hackjutsu/lepton)
- Tags: internals
- Published: 2026-02-23

---

**Lepton implements an asynchronous bidirectional IPC pattern using `ipcMain` and `ipcRenderer`, where the React-based renderer process emits lifecycle commands to the main process, and the main process broadcasts menu actions and system events back to the UI via specific channel names like `'session-ready'` and `'new-gist'`.**

Lepton is an open-source GitHub gist desktop client built with Electron and React. The **IPC communication pattern between Electron main and renderer processes** follows a clean request-response and event-broadcast architecture that keeps OS-level operations in the main process while maintaining UI state in the renderer. This separation leverages specific channel names registered across critical source files to handle authentication, menu commands, and application lifecycle events.

## Renderer to Main Process Communication

The renderer process sends small command strings to the main process to signal UI readiness and session state changes. These messages originate from React components and are handled by `ipcMain.on()` listeners in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js).

### Login Page Initialization

When the login UI mounts, it notifies the main process to prepare for auto-login handling. In [`app/containers/loginPage/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/loginPage/index.js), the component emits:

```javascript
ipcRenderer.send('login-page-ready')

```

The main process receives this in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) (lines 88-94) and responds by sending the auto-login signal back, then removes the listener to prevent duplicate handling:

```javascript
ipcMain.on('login-page-ready', () => {
  logger.info('[signal] sending auto-login signal')
  mainWindow.webContents.send('auto-login')
  ipcMain.removeAllListeners('login-page-ready')
})

```

### Session Lifecycle Management

After successful GitHub authentication, the renderer signals session initialization. In [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js) (lines 403-404):

```javascript
ipcRenderer.send('session-ready')

```

The main process configures the macOS Touch Bar in response (lines 97-99 in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)):

```javascript
ipcMain.on('session-ready', () => {
  setUpTouchBar()
})

```

When the user logs out, [`app/containers/userPanel/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/userPanel/index.js) (line 271) sends:

```javascript
ipcRenderer.send('session-destroyed')

```

This triggers cleanup in the main process (lines 101-103 in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)):

```javascript
ipcMain.on('session-destroyed', () => {
  mainWindow.setTouchBar(null)
})

```

## Main to Renderer Process Communication

The main process pushes events to the renderer using `mainWindow.webContents.send()` or `mainWindow.send()`, typically triggered by native menu actions or auto-update checks.

### Auto-Login Signals

Following the `'login-page-ready'` handshake, the main process initiates the authentication flow by sending:

```javascript
mainWindow.webContents.send('auto-login')

```

This pattern demonstrates the request-response nature of the IPC implementation, where the renderer's initial request prompts a main process response.

### Menu Command Broadcasting

Application menu items defined in [`app/utilities/menu/mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/menu/mainMenu.js) forward user actions to the renderer. For example, creating a new gist:

```javascript
click: (item, mainWindow) => mainWindow && mainWindow.send('new-gist')

```

The renderer registers handlers in [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js) (lines 62-86), which validate state before dispatching Redux actions:

```javascript
ipcRenderer.on('new-gist', () => {
  if (allDialogsClosed(dialogs)) {
    ipcRenderer.emit('new-gist-renderer')
  }
})

```

### Update Notifications

When a new version is available, the main process notifies the UI (lines 124-128 in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)):

```javascript
mainWindow.webContents.send('update-available')

```

The renderer handler in [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js) (lines 83-89) updates the Redux store:

```javascript
ipcRenderer.on('update-available', payload => {
  const newVersionInfo = remote.getGlobal('newVersionInfo')
  reduxStore.dispatch(updateNewVersionInfo(newVersionInfo))
  reduxStore.dispatch(updateUpdateAvailableBarStatus('ON'))
})

```

## One-Off Commands vs Broadcast Events

Lepton distinguishes between single-lifecycle signals and reusable broadcast commands. **One-off commands** like `'session-ready'` and `'auto-login'` fire once per application state change. **Broadcast commands** such as `'new-gist'`, `'search-gist'`, and `'immersive-mode'` trigger repeatedly via menu shortcuts.

Broadcast handlers in [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js) implement guards like `allDialogsClosed(dialogs)` to prevent action dispatch when modal dialogs are open. This safety check ensures that keyboard shortcuts do not interfere with active user workflows.

## Key Implementation Files

Understanding the **IPC communication pattern between Electron main and renderer processes** requires examining these specific source files:

- **[`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js)**: Registers `ipcMain` listeners for lifecycle commands and sends UI events via `mainWindow.webContents.send()`.
- **[`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js)**: Central hub for all `ipcRenderer.on()` handlers, bridging IPC events with the Redux store and React components.
- **[`app/utilities/menu/mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/menu/mainMenu.js)**: Defines native application menu items that trigger IPC events to the renderer.
- **[`app/containers/loginPage/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/loginPage/index.js)**: Initiates the login handshake with `'login-page-ready'`.
- **[`app/containers/userPanel/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/userPanel/index.js)**: Emits `'session-destroyed'` during logout.

## Summary

- Lepton uses **asynchronous `ipcMain`/`ipcRenderer` channels** for all cross-process communication, avoiding blocking operations.
- The pattern follows a **request-response model** for initialization (login page ready → auto-login signal) and an **event-broadcast model** for menu commands.
- **Renderer to main** channels include `'login-page-ready'`, `'session-ready'`, and `'session-destroyed'`, handled in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js).
- **Main to renderer** channels include `'auto-login'`, `'new-gist'`, and `'update-available'`, consumed in [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js).
- The implementation separates concerns by delegating OS-level features (Touch Bar, menus, updates) to the main process while React manages UI state.

## Frequently Asked Questions

### How does Lepton handle the initial login handshake between processes?

The renderer process emits `'login-page-ready'` via `ipcRenderer.send()` when the login component mounts. The main process listens via `ipcMain.on()` in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) and responds by sending `'auto-login'` back to the renderer using `mainWindow.webContents.send()`. The main process then removes the listener to ensure the handshake occurs only once.

### What prevents menu shortcuts from interfering with modal dialogs in Lepton?

In [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js), broadcast handlers like `'new-gist'` and `'search-gist'` check the `allDialogsClosed(dialogs)` utility before dispatching actions. This guard ensures that keyboard shortcuts from the main process only trigger when no modal dialogs are currently open, preventing state conflicts.

### Where are IPC channels defined in the Lepton source code?

Channel names are defined as string literals at the call sites rather than centralized constants. Renderer-side sends appear in files like [`app/containers/loginPage/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/containers/loginPage/index.js) and [`app/index.js`](https://github.com/hackjutsu/Lepton/blob/main/app/index.js), while main-side handlers reside in [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js). Menu-related channels originate in [`app/utilities/menu/mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/app/utilities/menu/mainMenu.js) using `mainWindow.send()`.

### Why does Lepton use both `mainWindow.send()` and `mainWindow.webContents.send()`?

Both methods achieve the same result of sending messages to the renderer process. `mainWindow.send()` is a convenience method available on the `BrowserWindow` instance, while `mainWindow.webContents.send()` accesses the underlying `webContents` object directly. Lepton uses both interchangeably across [`main.js`](https://github.com/hackjutsu/Lepton/blob/main/main.js) and [`mainMenu.js`](https://github.com/hackjutsu/Lepton/blob/main/mainMenu.js) to broadcast events from the main process.