# How @escrcpy/electron-setup Works: Architecture and Usage Guide

> Discover how @escrcpy/electron-setup simplifies Escrcpy development with its plugin-based architecture. Learn about its unified API for app initialization and window management.

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

---

**@escrcpy/electron-setup provides a modular, plugin-based foundation for the Escrcpy Electron application, exposing a unified API for app initialization, window management, and dependency injection across main and renderer processes.**

The package serves as the runtime backbone for the [viarotel-org/escrcpy](https://github.com/viarotel-org/escrcpy) project, abstracting Electron's complexity into a composable system. It re-exports a cohesive API from [[`packages/electron-setup/main/index.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/index.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/index.ts) that handles everything from single-instance window management to persistent storage adapters. Developers interact with the framework through typed functions like `createElectronApp` and `createWindowManager`, while the plugin architecture allows features to be added as isolated, context-aware modules.

## Core Architecture

The library is organized into four distinct layers that separate concerns between the Electron main process, window lifecycle, and cross-process communication.

### App Bootstrap and Context

The entry point for any Escrcpy desktop build is [[`desktop/electron/main.js`](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/main.js)](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/main.js), which imports `createElectronApp` from the setup package. This factory function initializes the Electron `app` instance, configures preload and renderer directory paths, and prepares the dependency injection container.

In [[`packages/electron-setup/main/app.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/app.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/app.ts), the `createElectronApp` function returns an application context that maintains references to storage adapters, window managers, and utility helpers. The companion `useElectronApp` hook provides runtime access to this context for downstream plugins.

### Window Management System

Window creation is handled by the `createWindowManager` factory exported from [[`packages/electron-setup/main/window.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/window.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/window.ts). This system provides:

- **Singleton enforcement** via the `singleton: true` option
- **Main window designation** using `mainWindow: true` for the primary entry point
- **Lifecycle hooks** (`created`, `ready`, `closed`) for custom logic during window transitions
- **Context injection** via `useWindowContext`, allowing the renderer to access the current `BrowserWindow` instance through the preload bridge

The window manager interacts with [[`packages/electron-setup/shared/template.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/template.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/template.ts) to instantiate `TemplateBrowserWindow` instances with consistent defaults.

### Plugin Architecture

Plugins in [[`packages/electron-setup/plugins/index.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/plugins/index.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/plugins/index.ts) follow a standard interface that receives the app context during registration. The `app.use(plugin)` method mounts plugins such as the window IPC bridge ([[`plugins/window-ipc/index.ts`](https://github.com/viarotel-org/escrcpy/blob/main/plugins/window-ipc/index.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/plugins/window-ipc/index.ts)), which exposes type-safe invocation channels to the renderer without violating Electron's context isolation rules.

### Storage and Helpers

Utility functions in [[`packages/electron-setup/shared/helpers.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/helpers.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/helpers.ts) provide payload encoding/decoding (`encodePayload`, `decodePayload`), path resolution (`resolveMainWindow`), and page loading utilities (`loadPage`). These helpers are automatically available to both the main process and renderer via the preload script.

Persistent state management is abstracted through the `IStorage` interface implemented by `ElectronStoreAdapter` in [[`packages/electron-setup/shared/adapters/storage-adapter.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/adapters/storage-adapter.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/adapters/storage-adapter.ts). The `createDefaultStorage` factory wraps `electron-store` with a pluggable adapter pattern, allowing configuration to persist across application restarts.

## Implementation Examples

### Initializing the Electron Application

The following pattern demonstrates bootstrapping the app with typed configuration:

```typescript
import { createElectronApp, createWindowManager } from '@escrcpy/electron-setup';
import path from 'node:path';

const app = createElectronApp({
  name: 'Escrcpy',
  preloadDir: __dirname,
  rendererDir: path.join(__dirname, '../dist'),
});

const mainWindow = createWindowManager('main', {
  singleton: true,
  mainWindow: true,
  browserWindow: {
    width: 1280,
    height: 720,
    webPreferences: {
      contextIsolation: true,
    },
  },
  hooks: {
    created: (win) => console.log(`Window ${win.id} created`),
    ready: (win) => win.webContents.send('app-ready'),
  },
});

app.use(mainWindow);
app.start();

```

### Creating a Custom Plugin

Plugins receive the application context and can hook into lifecycle events:

```typescript
import type { Plugin } from '@escrcpy/electron-setup';

export const telemetryPlugin: Plugin = {
  name: 'telemetry',
  async setup(app) {
    const storage = app.context.storage;
    const launchCount = (await storage.get('launchCount') as number) || 0;
    await storage.set('launchCount', launchCount + 1);

    app.on('window-created', (win) => {
      console.log(`Telemetry: tracking window ${win.id}`);
    });
  },
};

// Registration
import { telemetryPlugin } from './plugins/telemetry';
app.use(telemetryPlugin);

```

### Accessing Storage Directly

For configuration persistence outside of plugins:

```typescript
import { createDefaultStorage, ElectronStoreAdapter } from '@escrcpy/electron-setup';

const storage = createDefaultStorage({ 
  name: 'user-preferences',
  defaults: { theme: 'light' }
});

await storage.set('theme', 'dark');
const currentTheme = await storage.get('theme'); // Returns 'dark'

```

### Renderer-Side Context Usage

The preload bridge exposes window context to the renderer:

```typescript
// Inside a Vue/React component (renderer process)
const { win } = window.$preload.useWindowContext();
win.webContents.send('renderer-event', { payload: data });

```

## Summary

- **@escrcpy/electron-setup** acts as a modular runtime layer in the Escrcpy monorepo, consolidating Electron boilerplate into reusable factories.
- The **window manager** (`createWindowManager`) provides typed, singleton-aware window creation with lifecycle hooks defined in [`main/window.ts`](https://github.com/viarotel-org/escrcpy/blob/main/main/window.ts).
- **Plugins** extend functionality through a standard interface registered via `app.use()`, keeping features like IPC and theming isolated in `packages/electron-setup/plugins/`.
- **Storage abstraction** via `createDefaultStorage` and `ElectronStoreAdapter` decouples persistence logic from business code, wrapping `electron-store` behind a typed interface.
- The **public API** is consolidated in [`packages/electron-setup/main/index.ts`](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/index.ts), ensuring consistent imports across the desktop entry point and plugin modules.

## Frequently Asked Questions

### What is the entry point for @escrcpy/electron-setup in the Escrcpy project?

The desktop entry point at [[`desktop/electron/main.js`](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/main.js)](https://github.com/viarotel-org/escrcpy/blob/main/desktop/electron/main.js) imports `createElectronApp` from `@escrcpy/electron-setup` and calls `app.start()` after registering required plugins and window managers. This file bridges the package's internal architecture with the final Electron executable.

### How does the plugin system handle communication between the main and renderer processes?

Plugins like the window IPC bridge ([[`plugins/window-ipc/index.ts`](https://github.com/viarotel-org/escrcpy/blob/main/plugins/window-ipc/index.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/plugins/window-ipc/index.ts)) register IPC handlers during the `setup` phase, receiving the app context that contains the window manager and storage. The preload script (`window.$preload`) then exposes a safe subset of these capabilities to the renderer, maintaining context isolation while enabling type-safe method invocation.

### How does the window manager enforce singleton patterns?

When `singleton: true` is passed to `createWindowManager` in [[`main/window.ts`](https://github.com/viarotel-org/escrcpy/blob/main/main/window.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/main/window.ts), the manager checks for existing window instances by ID before creating new `BrowserWindow` objects. If a window of that type already exists, the manager returns the existing instance and optionally focuses it, preventing duplicate windows for critical UI elements like the main application window.

### Can I replace the default storage implementation with a custom adapter?

Yes. While `createDefaultStorage` returns an `ElectronStoreAdapter` wrapping `electron-store`, any object implementing the `IStorage` interface can be injected into the app context. The storage adapter pattern in [[`shared/adapters/storage-adapter.ts`](https://github.com/viarotel-org/escrcpy/blob/main/shared/adapters/storage-adapter.ts)](https://github.com/viarotel-org/escrcpy/blob/main/packages/electron-setup/shared/adapters/storage-adapter.ts) defines the contract (get/set/clear methods), allowing you to substitute Redis, SQLite, or memory-based storage for testing or specific deployment environments.