# How escrcpy Implements Function-Based IPC Communication with @escrcpy/electron-ipcx

> Discover how escrcpy uses @escrcpy/electron-ipcx for seamless function-based IPC. Enable type-safe, promise-based communication between Electron processes effortlessly.

- Repository: [viarotel-org/escrcpy](https://github.com/viarotel-org/escrcpy)
- Tags: internals
- Published: 2026-09-10

---

**escrcpy uses the @escrcpy/electron-ipcx package to expose main-process functionality as standard async JavaScript functions, enabling type-safe, promise-based communication between Electron's main and renderer processes without manual message serialization.**

escrcpy is an open-source Electron application that provides a GUI for scrcpy, the Android screen mirroring tool. To maintain clean separation between the main Node.js environment and the Chromium renderer while avoiding callback hell, the project implements **function-based IPC communication** through its dedicated `@escrcpy/electron-ipcx` package. This abstraction layer converts low-level IPC channels into callable async functions that support automatic error propagation and resource disposal.

## Registering Handlers in the Main Process

The main process registers callable endpoints using `ipcxMain.handle()`, which accepts a channel name and an async handler function. This pattern is implemented in [`desktop/electron/modules/terminal/service.js`](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/modules/terminal/service.js), where terminal session management functions are exposed to renderers.

When a handler is registered, it receives the original `ipcMain` event object for context and a payload containing arguments from the renderer:

```javascript
// desktop/electron/modules/terminal/service.js
import { ipcxMain } from '@escrcpy/electron-ipcx/main';

// Create a new terminal session
ipcxMain.handle('terminal:create-session', async (_event, config) => {
  const session = await createTerminalSession(config);
  return { sessionId: session.id };
});

// Write data to an existing session
ipcxMain.handle('terminal:write-session', async (_event, { sessionId, data }) => {
  const session = getSessionById(sessionId);
  await session.write(data);
});

// Resize a session's pseudo-tty
ipcxMain.handle('terminal:resize-session', async (_event, { sessionId, cols, rows }) => {
  const session = getSessionById(sessionId);
  await session.resize(cols, rows);
});

// Clean up a session
ipcxMain.handle('terminal:destroy-session', async (_event, { sessionId }) => {
  const session = getSessionById(sessionId);
  await session.close();
  // Optionally remove handlers if they are per-session
  ipcxMain.removeHandler('terminal:create-session');
  ipcxMain.removeHandler('terminal:write-session');
  ipcxMain.removeHandler('terminal:resize-session');
  ipcxMain.removeHandler('terminal:destroy-session');
});

```

Handlers can be removed dynamically using `ipcxMain.removeHandler(channel)`, which is essential for cleaning up per-session resources when connections terminate.

## Invoking Functions from the Renderer

On the renderer side, [`desktop/electron/middleware/terminal/index.js`](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/middleware/terminal/index.js) consumes these IPC functions through `ipcxRenderer.invoke()` for one-shot operations. This method returns a standard Promise that resolves with the handler's return value or rejects if the main process throws an error.

For long-lived resources such as terminal sessions, escrcpy uses **`ipcxRenderer.invokeRetained()`**, which returns both a promise and a disposal function:

```javascript
// desktop/electron/middleware/terminal/index.js
import { ipcxRenderer } from '@escrcpy/electron-ipcx/renderer';

// Open a terminal session (retained = long-living)
export const createTerminal = async (config) => {
  const { promise, dispose } = ipcxRenderer.invokeRetained('terminal:create-session', config);
  const { sessionId } = await promise;
  return { sessionId, dispose };
};

// Write to the terminal
export const writeTerminal = (sessionId, data) => {
  return ipcxRenderer.invoke('terminal:write-session', { sessionId, data });
};

// Resize the terminal
export const resizeTerminal = (sessionId, cols, rows) => {
  return ipcxRenderer.invoke('terminal:resize-session', { sessionId, cols, rows });
};

// Close the terminal
export const destroyTerminal = (sessionId) => {
  return ipcxRenderer.invoke('terminal:destroy-session', { sessionId });
};

```

The `invokeRetained` pattern is critical for resources that persist beyond a single request, allowing the renderer to explicitly release remote references when components unmount or sessions end.

## Error Handling and Type Safety

Because `@escrcpy/electron-ipcx` wraps native IPC in promise-based interfaces, **error propagation** works naturally with async/await semantics. Any exception thrown inside a main-process handler automatically rejects the renderer's promise, preserving stack traces and error types across the process boundary.

The package also ships with **TypeScript definitions** that enable IDE autocompletion for channel names and payload types. Developers can import shared type signatures to ensure compile-time safety for both handler registration and renderer invocation, eliminating the string-typing issues common in raw Electron IPC.

## Summary

- escrcpy implements **function-based IPC communication** by wrapping Electron's native IPC in the `@escrcpy/electron-ipcx` package, exposing main-process logic as standard async functions.
- **Main process** handlers are registered via `ipcxMain.handle()` in files like [`desktop/electron/modules/terminal/service.js`](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/modules/terminal/service.js), with cleanup managed through `ipcxMain.removeHandler()`.
- **Renderer processes** invoke these functions using `ipcxRenderer.invoke()` for stateless calls and `ipcxRenderer.invokeRetained()` for long-lived resources that require explicit disposal.
- Errors thrown in the main process automatically propagate as rejected promises to the renderer, maintaining standard JavaScript error-handling patterns.
- The architecture provides full **TypeScript support**, enabling type-safe channel definitions across the main-renderer boundary.

## Frequently Asked Questions

### What is the difference between `invoke` and `invokeRetained` in @escrcpy/electron-ipcx?

**`ipcxRenderer.invoke()`** is designed for stateless, one-shot IPC calls that return a single value and immediately release resources. **`ipcxRenderer.invokeRetained()`** is used for long-lived resources like terminal sessions or file streams; it returns an object containing both a `promise` for the initial result and a `dispose` function that the renderer must call to clean up the remote resource when finished.

### How does escrcpy clean up IPC handlers for terminated sessions?

When a terminal session ends, the renderer calls `ipcxRenderer.invoke('terminal:destroy-session')`, which triggers the main process to execute `ipcxMain.removeHandler()` for all related channel endpoints. This explicit cleanup prevents memory leaks from accumulating orphaned handlers in the main process when sessions close.

### Is the IPC communication in escrcpy type-safe?

Yes, the `@escrcpy/electron-ipcx` package includes TypeScript definitions that allow developers to define channel signatures as typed interfaces. Both the main and renderer processes can import these definitions, providing compile-time validation of payload shapes and return types while enabling IDE features like autocomplete and refactoring support.

### Why use function-based IPC instead of standard Electron ipcRenderer/ipcMain?

Function-based IPC eliminates boilerplate message serialization, manual event listener management, and callback coordination. By treating remote capabilities as standard JavaScript functions, escrcpy developers write cleaner async/await code with automatic error propagation and built-in support for resource disposal, significantly reducing the complexity of main-renderer communication compared to raw IPC message passing.