How IPC Communication Works Between Renderer and Main Process in Modly
Modly uses Electron's Inter-Process Communication (IPC) architecture with a preload-script security layer, exposing a trusted window.electron API that maps renderer calls to ipcRenderer.invoke/send channels handled by ipcMain in the main process.
Modly is an Electron-based application that requires secure communication between its web-based UI (renderer process) and the Node.js backend (main process). Understanding how this IPC system works is essential for extending the application or debugging communication issues. This article examines the complete request/response flow as implemented in the lightningpixel/modly repository.
The Three-Layer IPC Architecture
Modly's IPC system consists of three distinct layers working together to maintain security while enabling rich functionality.
Preload Script: The Security Boundary
The electron/preload/electron-api.ts file creates a trusted API surface that isolates the renderer from direct Node.js access. This approach follows Electron's security best practices by using contextBridge to expose only explicitly permitted functionality.
The preload script exposes methods that internally call:
ipcRenderer.send(channel, ...args)— fire-and-forget messages for actions that don't need a responseipcRenderer.invoke(channel, ...args)— request/response RPC-style calls that return promises
Main Process Handlers: Request Processing
All IPC handlers are registered in electron/main/ipc-handlers.ts using two registration methods:
| Method | Purpose | Example Use |
|---|---|---|
ipcMain.on(channel, handler) |
One-way message handling | Window controls (window:minimize) |
ipcMain.handle(channel, handler) |
Async request/response | File dialogs, data fetching |
Message Flow Visualization
Renderer (UI) ──► window.electron.<method>() ──► ipcRenderer.invoke/on/send('channel')
│
Preload (contextBridge)
▼
Main Process (ipcMain) ──► handler in ipc-handlers.ts
│
▼
Performs work (dialog, filesystem, Python bridge)
│
▼
Returns value (Promise) → resolves in renderer
One-Way vs. Request-Response Patterns
Modly uses both communication patterns strategically based on whether the renderer needs feedback.
Fire-and-Forget with ipcRenderer.send
Window controls demonstrate the simplest pattern — the renderer notifies the main process without waiting for a result.
// electron/preload/electron-api.ts
window.electron = {
window: {
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
}
}
Corresponding main-process handler:
// electron/main/ipc-handlers.ts
ipcMain.on('window:minimize', () => getWindow()?.minimize());
ipcMain.on('window:maximize', () => {
const win = getWindow();
if (win) win.isMaximized() ? win.restore() : win.maximize();
});
Request-Response with ipcRenderer.invoke
When the renderer needs data or confirmation, Modly uses the promise-based invoke/handle pattern.
// electron/preload/electron-api.ts
window.electron = {
window: {
isMaximized: () => ipcRenderer.invoke('window:isMaximized') as Promise<boolean>,
},
fs: {
selectImage: () => ipcRenderer.invoke('fs:selectImage') as Promise<string | null>,
}
}
Corresponding handler with async result:
// electron/main/ipc-handlers.ts
ipcMain.handle('window:isMaximized', () => getWindow()?.isMaximized() ?? false);
ipcMain.handle('fs:selectImage', async () => {
const win = getWindow();
if (!win) return null;
const result = await dialog.showOpenDialog(win, {
title: 'Select an image',
filters: [{ name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'webp'] }],
properties: ['openFile'],
});
return result.canceled ? null : result.filePaths[0];
});
Complete IPC Channel Categories
The ipc-handlers.ts file registers handlers across six functional domains:
-
Window controls —
window:minimize,window:maximize,window:close,window:isMaximized -
File system dialogs —
fs:selectImage,fs:selectMeshFile,fs:saveModel,fs:listDir,fs:listFiles -
Python bridge —
python:start,python:status(implementation inelectron/main/python-bridge.ts) -
Model management —
model:download,model:delete,model:export,model:activeDownloads -
Extension management —
extensions:list,extensions:installFromGitHub,extensions:uninstall -
App & system info —
app:info,system:memory
Practical Usage in the Renderer
The exposed API simplifies IPC calls into familiar method invocations:
// Minimize window without waiting
await window.electron.window.minimize();
// Get file path with full async handling
const imagePath = await window.electron.fs.selectImage();
if (imagePath) {
console.log('Selected:', imagePath);
}
// Start Python server and receive structured response
const { success, port } = await window.electron.python.start();
if (success) {
console.log(`Python server on port ${port}`);
}
Security Model and Channel Isolation
Modly's IPC design enforces explicit channel whitelisting — only channels registered in ipc-handlers.ts are accessible. The preload script acts as a controlled gateway, preventing the renderer from directly accessing ipcRenderer or Node.js APIs. This architecture mitigates risks from compromised renderer content by ensuring all main-process access flows through audited, type-safe wrapper functions.
The electron/main/extension-path-guard.ts file provides additional security validation for extension-related operations, ensuring path traversal attacks cannot exploit the file system handlers.
Key Source Files
| File | Responsibility |
|---|---|
electron/preload/electron-api.ts |
Defines the window.electron API surface |
electron/preload/index.ts |
Entry point loading the API via contextBridge |
electron/main/ipc-handlers.ts |
Registers all ipcMain handlers |
electron/main/extension-path-guard.ts |
Path validation for extension security |
electron/main/python-bridge.ts |
Python server lifecycle handlers |
Summary
- Modly IPC uses Electron's
contextBridge+ preload pattern to create a secure, auditable API boundary between renderer and main process - Two communication patterns:
send/onfor fire-and-forget,invoke/handlefor request-response - All handlers centralized in
electron/main/ipc-handlers.tswith consistent channel naming (domain:action) - Renderer access strictly mediated through
window.electronobject — no direct Node.js access - Type-safe wrappers in preload script ensure consistent promise types and error handling
Frequently Asked Questions
What prevents malicious code in the renderer from accessing the file system directly?
The renderer runs in a sandboxed context with no direct Node.js access. Only the preload script (loaded before renderer code executes) can access ipcRenderer, and it exposes specific methods rather than the raw IPC object. This means compromised renderer code can only invoke the predefined handlers in ipc-handlers.ts, not arbitrary file system operations.
Why does Modly use both send and invoke instead of just one pattern?
Performance and semantics differ. send is lighter for one-way notifications where no confirmation is needed (window minimize). invoke adds promise overhead but enables data return and error propagation (file dialogs, Python status). Using both appropriately keeps the UI responsive while supporting complex workflows.
How are IPC channels kept in sync between preload and main process?
Both sides reference string channel names that must match exactly. The centralized registration in ipc-handlers.ts and exposure in electron-api.ts creates a de facto contract. TypeScript interfaces (implied by the as Promise<T> type assertions in preload) help catch mismatches during development, though runtime validation would require additional tooling.
Can I add custom IPC channels to Modly?
Yes — add the handler in electron/main/ipc-handlers.ts using ipcMain.on or ipcMain.handle, then expose a wrapper method in electron/preload/electron-api.ts that calls the corresponding ipcRenderer method with your channel name. Follow the existing naming convention (domain:action) for consistency.
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 →