-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Electron: Processes Communication
There are two methods for processes communication, IPC (Inter-Process Communication) and MessagePort
IPC
The
node:net
module supports IPC with named pipes on Windows, and Unix domain sockets on other operating systems.
In Electron, processes communicate by passing messages through developer-defined channels with the ipcMain
and ipcRenderer
modules.
Pattern 1: Renderer to main (one-way)
-
Use the
ipcRenderer.send
API to send messages in preload scripts.// preload.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { setTitle: (title) => ipcRenderer.send('set-title', title) })
// renderer.js const setButton = document.getElementById('btn') setButton.addEventListener('click', () => { window.electronAPI.setTitle('hello world') });
-
Use the
ipcMain.on
API to receive messages in main process.// main.js const {app, BrowserWindow, ipcMain} = require('electron') const path = require('path') function handleSetTitle (event, title) { const webContents = event.sender const win = BrowserWindow.fromWebContents(webContents) win.setTitle(title) } function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } app.whenReady().then(() => { ipcMain.on('set-title', handleSetTitle) createWindow() }
Pattern 2: Renderer to main (two-way)
Sending a message to the main process and waiting for a result.
-
Use the
ipcRenderer.invoke
API to send messages to the main process in preload scripts.// preload.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { openFile: () => ipcRenderer.invoke('dialog:openFile') })
// renderer.js const btn = document.getElementById('btn') const filePathElement = document.getElementById('filePath') btn.addEventListener('click', async () => { const filePath = await window.electronAPI.openFile() filePathElement.innerText = filePath })
-
Use the
ipcMain.handle API
to receive messages and return result, the return result will be wrapped in a Promise.const { BrowserWindow, dialog, ipcMain } = require('electron') const path = require('path') async function handleFileOpen() { const { canceled, filePaths } = await dialog.showOpenDialog() return canceled ? undefined : filePaths[0] } function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } app.whenReady(() => { ipcMain.handle('dialog:openFile', handleFileOpen) createWindow() })
There is a synchronously api called ipcRenderer.sendSync
, which will block the renderer process until a reply is received.
Pattern 3: Main to renderer
-
Use the
WebContents.send
API to send messages to the renderer process, like the way usingipcRenderer.send
.// main.js const {app, BrowserWindow, Menu, ipcMain} = require('electron') const path = require('path') function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) const menu = Menu.buildFromTemplate([ { label: app.name, submenu: [ { click: () => mainWindow.webContents.send('update-counter', 1), label: 'Increment', }, { click: () => mainWindow.webContents.send('update-counter', -1), label: 'Decrement', } ] } ]) Menu.setApplicationMenu(menu) mainWindow.loadFile('index.html') } ipcMain.on('counter-value', (_event, value) => { console.log(value) // will print value to Node console })
-
Use the
ipcRenderer.on
API to receive messages in preload scripts.// preload.js const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback) })
// renderer.js const counter = document.getElementById('counter') window.electronAPI.onUpdateCounter((event, value) => { const oldValue = Number(counter.innerText) const newValue = oldValue + value counter.innerText = newValue // return a replay event.sender.send('counter-value', newValue) })
Pattern 4: Renderer to renderer
There is no direct way to send messages between renderer processes using ipcMain
and ipcRenderer
modules.
- Use the main process as a message broker.
- Pass a
MessagePort
from the main process to both renderers.
MessagePort
- In the renderer, the
MessagePort
behaves exactly as it does on the web. - In the main, Electron adds two new classes:
MessagePortMain
andMessageChannelMain
. MessagePort
objects can be passed usingipcRenderer.postMessage
andWebContents.postMessage
.- The renderer page can communicate with the main process directly with
MessagePort
, don’t rely on preload scripts as a broker.
Example: Setting up a MessageChannel between two renderers
// main.js (Main process)
const { BrowserWindow, app, MessageChannelMain } = require('electron')
app.whenReady().then(async () => {
// create the windows.
const mainWindow = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadMain.js'
}
})
const secondaryWindow = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
preload: 'preloadSecondary.js'
}
})
// set up the channel.
const { port1, port2 } = new MessageChannelMain()
// once the webContents are ready, send a port to each webContents with postMessage.
mainWindow.once('ready-to-show', () => {
mainWindow.webContents.postMessage('port', null, [port1])
})
secondaryWindow.once('ready-to-show', () => {
secondaryWindow.webContents.postMessage('port', null, [port2])
})
})
// preloadMain.js and preloadSecondary.js (Preload scripts)
const { ipcRenderer } = require('electron')
ipcRenderer.on('port', e => {
// port received, make it globally available.
window.electronMessagePort = e.ports[0]
window.electronMessagePort.onmessage = messageEvent => {
// handle message
}
})