Secure communication system between main and renderer processes, including contextBridge for secure API exposure.
Handle messages from renderer processes in the main process.
/**
* Communicate asynchronously from the main process to renderer processes
*/
interface IpcMain extends EventEmitter {
/** Listens to channel, when a new message arrives listener would be called with listener(event, args...) */
on(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this;
/** Adds a one time listener function for the event */
once(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this;
/** Removes the specified listener from the listener array for the specified channel */
removeListener(channel: string, listener: (...args: any[]) => void): this;
/** Removes all listeners, or those of the specified channel */
removeAllListeners(channel?: string): this;
/** Adds a handler for an invoke-able IPC */
handle(channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any> | any): void;
/** Adds a handler for an invoke-able IPC */
handleOnce(channel: string, listener: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any> | any): void;
/** Removes any handler for channel, if present */
removeHandler(channel: string): void;
/** Returns a list of the names of all channels that have handlers */
listenerCount(eventName: string | symbol): number;
}
declare const ipcMain: IpcMain;
interface IpcMainEvent extends Event {
/** Set this to the value to be returned in a synchronous message */
returnValue: any;
/** Returns the webContents that sent the message */
readonly sender: WebContents;
/** Returns the ID of the renderer frame that sent this message */
readonly senderFrame: WebFrameMain;
/** A list of MessagePorts that were transferred with this message */
readonly ports: MessagePortMain[];
/** A function that will send an IPC message to the renderer frame that sent the original message */
reply(channel: string, ...args: any[]): void;
}
interface IpcMainInvokeEvent extends Event {
/** Returns the webContents that sent the message */
readonly sender: WebContents;
/** Returns the ID of the renderer frame that sent this message */
readonly senderFrame: WebFrameMain;
}Usage Examples:
const { ipcMain, BrowserWindow } = require('electron');
// Handle async messages
ipcMain.on('async-message', (event, arg) => {
console.log('Received from renderer:', arg);
// Reply back to renderer
event.reply('async-reply', 'Response from main');
});
// Handle invoke (request-response pattern)
ipcMain.handle('get-app-version', async (event) => {
return process.env.npm_package_version;
});
ipcMain.handle('read-file', async (event, filePath) => {
try {
const fs = require('fs').promises;
const content = await fs.readFile(filePath, 'utf8');
return content;
} catch (error) {
throw error;
}
});
// Handle with validation
ipcMain.handle('database-query', async (event, query, params) => {
// Validate sender
if (!isValidSender(event.sender)) {
throw new Error('Unauthorized');
}
// Execute query
return await executeQuery(query, params);
});
// Broadcast to all windows
function broadcastToAll(channel, data) {
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send(channel, data);
});
}
// Send to specific window
function sendToWindow(windowId, channel, data) {
const window = BrowserWindow.fromId(windowId);
if (window && !window.isDestroyed()) {
window.webContents.send(channel, data);
}
}Send messages to the main process from renderer processes.
/**
* Communicate asynchronously from a renderer process to the main process
*/
interface IpcRenderer extends EventEmitter {
/** Listens to channel, when a new message arrives listener would be called with listener(event, args...) */
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
/** Adds a one time listener function for the event */
once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
/** Removes the specified listener from the listener array for the specified channel */
removeListener(channel: string, listener: (...args: any[]) => void): this;
/** Removes all listeners, or those of the specified channel */
removeAllListeners(channel?: string): this;
/** Send an asynchronous message to the main process via channel */
send(channel: string, ...args: any[]): void;
/** Send a message to the main process via channel and expect a result asynchronously */
invoke(channel: string, ...args: any[]): Promise<any>;
/** Send a message to the main process via channel and expect a result synchronously */
sendSync(channel: string, ...args: any[]): any;
/** Sends a message to a window with webContentsId via channel */
sendTo(webContentsId: number, channel: string, ...args: any[]): void;
/** Send a message to the host page in a <webview> via channel */
sendToHost(channel: string, ...args: any[]): void;
/** Like ipcRenderer.send but the event will be sent to the <webview> element in the host page instead of the main process */
postMessage(channel: string, message: any, transfer?: MessagePort[]): void;
}
declare const ipcRenderer: IpcRenderer;
interface IpcRendererEvent extends Event {
/** A list of MessagePorts that were transferred with this message */
readonly ports: MessagePort[];
/** Returns the webContents that sent the message */
readonly sender: WebContents;
/** The ID of the renderer frame that sent this message */
readonly senderId: number;
/** The ID of the frame that sent this message */
readonly senderFrame: WebFrameMain;
}Usage Examples:
// In renderer process (with nodeIntegration enabled)
const { ipcRenderer } = require('electron');
// Send async message
ipcRenderer.send('async-message', 'Hello from renderer');
// Listen for reply
ipcRenderer.on('async-reply', (event, message) => {
console.log('Reply from main:', message);
});
// Use invoke for request-response
async function getAppVersion() {
try {
const version = await ipcRenderer.invoke('get-app-version');
console.log('App version:', version);
return version;
} catch (error) {
console.error('Failed to get version:', error);
}
}
// File operations
async function loadFile(filePath) {
try {
const content = await ipcRenderer.invoke('read-file', filePath);
document.getElementById('content').textContent = content;
} catch (error) {
console.error('Failed to read file:', error);
}
}
// Form submission
document.getElementById('myForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
const result = await ipcRenderer.invoke('submit-form', data);
console.log('Form submitted successfully:', result);
} catch (error) {
console.error('Form submission failed:', error);
}
});
// Real-time updates
ipcRenderer.on('data-update', (event, data) => {
updateUI(data);
});
// Clean up listeners
window.addEventListener('beforeunload', () => {
ipcRenderer.removeAllListeners();
});Secure API exposure between main and renderer processes.
/**
* Create a safe, bi-directional, synchronous bridge across isolated contexts
*/
interface ContextBridge {
/** Exposes an API object to the main world context */
exposeInMainWorld(apiKey: string, api: any): void;
/** Exposes an API object to an isolated world context */
exposeInIsolatedWorld(worldId: number, apiKey: string, api: any): void;
}
declare const contextBridge: ContextBridge;Usage Examples:
Preload script (secure bridge):
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('electronAPI', {
// App control
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
// File operations
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content),
// Database operations
query: (sql, params) => ipcRenderer.invoke('db-query', sql, params),
// Window control
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window'),
// Event listeners
onUpdateAvailable: (callback) => {
ipcRenderer.on('update-available', (event, ...args) => callback(...args));
},
onDataChanged: (callback) => {
ipcRenderer.on('data-changed', (event, ...args) => callback(...args));
},
// Remove listeners
removeAllListeners: (channel) => {
ipcRenderer.removeAllListeners(channel);
}
});
// Expose specific utilities
contextBridge.exposeInMainWorld('fileUtils', {
basename: require('path').basename,
dirname: require('path').dirname,
extname: require('path').extname,
join: require('path').join
});Renderer usage (secure):
// renderer.js (in web page)
// No direct require() needed - use exposed APIs
async function initApp() {
// Use exposed API
const version = await window.electronAPI.getAppVersion();
document.getElementById('version').textContent = version;
// Set up event listeners
window.electronAPI.onUpdateAvailable((info) => {
showUpdateNotification(info);
});
window.electronAPI.onDataChanged((data) => {
refreshUI(data);
});
}
// File operations
async function openFile() {
try {
const content = await window.electronAPI.readFile('/path/to/file.txt');
document.getElementById('content').value = content;
} catch (error) {
console.error('Failed to open file:', error);
}
}
// Database queries
async function loadUsers() {
try {
const users = await window.electronAPI.query('SELECT * FROM users WHERE active = ?', [true]);
displayUsers(users);
} catch (error) {
console.error('Failed to load users:', error);
}
}
// Window controls
document.getElementById('minimize').addEventListener('click', () => {
window.electronAPI.minimizeWindow();
});
// Clean up on page unload
window.addEventListener('beforeunload', () => {
window.electronAPI.removeAllListeners('data-changed');
window.electronAPI.removeAllListeners('update-available');
});Advanced IPC using MessageChannel API for structured communication.
/**
* MessageChannelMain creates MessageChannel-like pair of ports to allow passing messages between main and renderer
*/
class MessageChannelMain {
/** Create a new MessageChannelMain */
constructor();
/** The port1 property */
readonly port1: MessagePortMain;
/** The port2 property */
readonly port2: MessagePortMain;
}
/**
* MessagePortMain is the main-process-side message port of a MessageChannel
*/
class MessagePortMain extends EventEmitter {
/** Sends a message from the port */
postMessage(message: any): void;
/** Starts the sending of messages queued on the port */
start(): void;
/** Disconnects the port */
close(): void;
}Usage Examples:
// In main process
const { MessageChannelMain, BrowserWindow } = require('electron');
// Create message channel
const { port1, port2 } = new MessageChannelMain();
// Send port to renderer
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
mainWindow.webContents.postMessage('port-message', null, [port2]);
// Listen on main side
port1.on('message', (event) => {
console.log('Received message from renderer:', event.data);
// Send response
port1.postMessage('Response from main');
});
port1.start();Preload script:
// preload.js
const { ipcRenderer } = require('electron');
ipcRenderer.on('port-message', (event) => {
const port = event.ports[0];
// Expose port to renderer
window.messagePort = {
send: (message) => port.postMessage(message),
onMessage: (callback) => {
port.on('message', (event) => callback(event.data));
}
};
port.start();
});interface WebFrameMain extends EventEmitter {
/** A integer representing the unique ID of the frame */
readonly frameId: number;
/** A string representing the name of the frame */
readonly name: string;
/** A integer representing the operating system pid of the process which owns this frame */
readonly processId: number;
/** A integer representing the unique ID of the frame's internal routing */
readonly routingId: number;
/** A WebContents instance representing the top level frame */
readonly top: WebFrameMain;
/** A WebContents instance representing the parent of this frame */
readonly parent: WebFrameMain | null;
/** A WebFrameMain[] collection containing the direct children of this frame */
readonly frames: WebFrameMain[];
/** A WebFrameMain[] collection containing every frame in the subtree of this frame */
readonly framesInSubtree: WebFrameMain[];
/** A string representing the current URL of this frame */
readonly url: string;
/** A string representing the origin of this frame */
readonly origin: string;
/** Executes JavaScript in the frame */
executeJavaScript(code: string, userGesture?: boolean): Promise<unknown>;
/** Returns a promise that resolves to the result of the executed code or is rejected if execution throws or results in a rejected promise */
reload(): boolean;
}
interface PreloadScriptOptions {
filePath: string;
worldId?: number;
}
interface IsolatedWorldInfo {
worldId: number;
name: string;
}