How @escrcpy/electron-setup Works: Architecture and Usage Guide
@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 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) 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), 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), 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). This system provides:
- Singleton enforcement via the
singleton: trueoption - Main window designation using
mainWindow: truefor 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 currentBrowserWindowinstance 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) 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) 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/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) 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). 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:
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:
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:
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:
// 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 inmain/window.ts. - Plugins extend functionality through a standard interface registered via
app.use(), keeping features like IPC and theming isolated inpackages/electron-setup/plugins/. - Storage abstraction via
createDefaultStorageandElectronStoreAdapterdecouples persistence logic from business code, wrappingelectron-storebehind a typed interface. - The public API is consolidated in
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) 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/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/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/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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →