Inter Process Communication(IPC)
Electron - Inter-Process Communication (IPC)
Section titled “Electron - Inter-Process Communication (IPC)”Electron applications have at least two types of processes: one main process and one or more renderer processes. Inter-Process Communication (IPC) is essential for these processes to exchange information.
Electron provides modules for IPC: ipcMain (for the main process) and ipcRenderer (for renderer processes). These allow asynchronous and, in some cases, synchronous communication.
The ipcMain module, used in the main process, handles messages sent from renderer processes. It can reply to these messages or initiate communication with renderers.
The ipcRenderer module, used in preload scripts or renderer processes (when Node integration is enabled, though not recommended for security), sends messages to the main process and handles replies.
Modern Electron development emphasizes security. This means nodeIntegration is typically disabled, and contextIsolation is enabled in renderer processes. Communication with the main process is facilitated securely through a preload script using contextBridge.
Let’s create an example demonstrating asynchronous, synchronous (with a caution), and invoke/handle (request/response) IPC patterns.
First, create your main process file, main.js:
const { app, BrowserWindow, ipcMain } = require('electron');const path = require('path');
let mainWindow;
function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, // Protect against prototype pollution nodeIntegration: false, // Keep Node.js integration disabled in renderer } });
mainWindow.loadFile(path.join(__dirname, 'index.html')); // mainWindow.webContents.openDevTools(); // Optional: Open DevTools}
// Asynchronous message handler from rendereripMain.on('asynchronous-message', (event, arg) => { console.log('Main process received async:', arg); // Logs 'async ping' // Reply to the sender (renderer process) event.reply('asynchronous-reply', 'async pong from main');});
// Synchronous message handler from renderer// Note: Avoid synchronous IPC for performance reasons unless absolutely necessary.ipcMain.on('synchronous-message', (event, arg) => { console.log('Main process received sync:', arg); // Logs 'sync ping' event.returnValue = 'sync pong from main';});
// Handle for invoke/handle pattern (request/response)ipMain.handle('invoke-message', async (event, arg) => { console.log('Main process received invoke:', arg); // Logs 'invoke ping' // Perform some async operation if needed // const result = await somePromise(); return 'pong from handle in main';});
app.whenReady().then(() => { createWindow();
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } });});
app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); }});Next, create a preload.js file in the same directory. This script runs before the web page in the renderer process and has access to Node.js APIs. It securely exposes IPC functionality to the renderer:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', { send: (channel, data) => ipcRenderer.send(channel, data), sendSync: (channel, data) => ipcRenderer.sendSync(channel, data), on: (channel, func) => { // Deliberately strip event as it includes `sender` ipcRenderer.on(channel, (event, ...args) => func(...args)); }, invoke: (channel, data) => ipcRenderer.invoke(channel, data), // It's good practice to be specific about which channels are exposed // e.g., openFile: () => ipcRenderer.invoke('dialog:openFile')});Now, create your index.html file:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Electron IPC Demo</title></head><body> <h1>Electron IPC Communication</h1> <p>Check the console in both this window (Renderer) and your terminal (Main).</p>
<script> // 1. Asynchronous IPC: send and listen for reply console.log('Renderer: Sending async message...'); window.electronAPI.send('asynchronous-message', 'async ping from renderer');
window.electronAPI.on('asynchronous-reply', (reply) => { console.log('Renderer received async reply:', reply); // 'async pong from main' });
// 2. Synchronous IPC: send and get immediate result // WARNING: Synchronous IPC can block the renderer process. Use sparingly. console.log('Renderer: Sending sync message...'); const syncReply = window.electronAPI.sendSync('synchronous-message', 'sync ping from renderer'); console.log('Renderer received sync reply:', syncReply); // 'sync pong from main'
// 3. Invoke/Handle IPC: request/response pattern (Promise-based) async function testInvoke() { console.log('Renderer: Sending invoke message...'); const invokeReply = await window.electronAPI.invoke('invoke-message', 'invoke ping from renderer'); console.log('Renderer received invoke reply:', invokeReply); // 'pong from handle in main' } testInvoke(); </script></body></html>To run the app, ensure your package.json has "main": "main.js" and a start script like "start": "electron .". Then run:
npm startOutput in your application’s DevTools console (Renderer):
Renderer: Sending async message...Renderer: Sending sync message...Renderer received sync reply: sync pong from mainRenderer: Sending invoke message...Renderer received async reply: async pong from mainRenderer received invoke reply: pong from handle in mainOutput in your terminal console (Main):
Main process received async: async ping from rendererMain process received sync: sync ping from rendererMain process received invoke: invoke ping from rendererKey Takeaways:
- Always prioritize asynchronous IPC (
send/onorinvoke/handle) to avoid blocking processes and keep your UI responsive. - Use
preloadscripts withcontextBridgeto securely expose IPC channels and Node.js functionality to renderer processes whencontextIsolationis enabled (which is the default and recommended). - The
invoke/handlepattern is excellent for request-response type interactions, returning a Promise. - Avoid using the
remotemodule as it’s deprecated and has security implications.
For further details, consult the official Electron documentation on Inter-Process Communication and Preload Scripts.