|
@@ -0,0 +1,571 @@
|
|
|
+---
|
|
|
+title: Inter-Process Communication
|
|
|
+description: Use the ipcMain and ipcRenderer modules to communicate between Electron processes
|
|
|
+slug: ipc
|
|
|
+hide_title: false
|
|
|
+---
|
|
|
+
|
|
|
+# Inter-Process Communication
|
|
|
+
|
|
|
+Inter-process communication (IPC) is a key part of building feature-rich desktop applications
|
|
|
+in Electron. Because the main and renderer processes have different responsibilities in
|
|
|
+Electron's process model, IPC is the only way to perform many common tasks, such as calling a
|
|
|
+native API from your UI or triggering changes in your web contents from native menus.
|
|
|
+
|
|
|
+## IPC channels
|
|
|
+
|
|
|
+In Electron, processes communicate by passing messages through developer-defined "channels"
|
|
|
+with the [`ipcMain`] and [`ipcRenderer`] modules. These channels are
|
|
|
+**arbitrary** (you can name them anything you want) and **bidirectional** (you can use the
|
|
|
+same channel name for both modules).
|
|
|
+
|
|
|
+In this guide, we'll be going over some fundamental IPC patterns with concrete examples that
|
|
|
+you can use as a reference for your app code.
|
|
|
+
|
|
|
+## Understanding context-isolated processes
|
|
|
+
|
|
|
+Before proceeding to implementation details, you should be familiar with the idea of using a
|
|
|
+[preload script] to import Node.js and Electron modules in a context-isolated renderer process.
|
|
|
+
|
|
|
+* For a full overview of Electron's process model, you can read the [process model docs].
|
|
|
+* For a primer into exposing APIs from your preload script using the `contextBridge` module, check
|
|
|
+out the [context isolation tutorial].
|
|
|
+
|
|
|
+## Pattern 1: Renderer to main (one-way)
|
|
|
+
|
|
|
+To fire a one-way IPC message from a renderer process to the main process, you can use the
|
|
|
+[`ipcRenderer.send`] API to send a message that is then received by the [`ipcMain.on`] API.
|
|
|
+
|
|
|
+You usually use this pattern to call a main process API from your web contents. We'll demonstrate
|
|
|
+this pattern by creating a simple app that can programmatically change its window title.
|
|
|
+
|
|
|
+For this demo, you'll need to add code to your main process, your renderer process, and a preload
|
|
|
+script. The full code is below, but we'll be explaining each file individually in the following
|
|
|
+sections.
|
|
|
+
|
|
|
+```fiddle docs/fiddles/ipc/pattern-1
|
|
|
+```
|
|
|
+
|
|
|
+### 1. Listen for events with `ipcMain.on`
|
|
|
+
|
|
|
+In the main process, set an IPC listener on the `set-title` channel with the `ipcMain.on` API:
|
|
|
+
|
|
|
+```javascript {6-10,22} title='main.js (Main Process)'
|
|
|
+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()
|
|
|
+}
|
|
|
+//...
|
|
|
+```
|
|
|
+
|
|
|
+The above `handleSetTitle` callback has two parameters: an [IpcMainEvent] structure and a
|
|
|
+`title` string. Whenever a message comes through the `set-title` channel, this function will
|
|
|
+find the BrowserWindow instance attached to the message sender and use the `win.setTitle`
|
|
|
+API on it.
|
|
|
+
|
|
|
+:::info
|
|
|
+Make sure you're loading the `index.html` and `preload.js` entry points for the following steps!
|
|
|
+:::
|
|
|
+
|
|
|
+### 2. Expose `ipcRenderer.send` via preload
|
|
|
+
|
|
|
+To send messages to the listener created above, you can use the `ipcRenderer.send` API.
|
|
|
+By default, the renderer process has no Node.js or Electron module access. As an app developer,
|
|
|
+you need to choose which APIs to expose from your preload script using the `contextBridge` API.
|
|
|
+
|
|
|
+In your preload script, add the following code, which will expose a global `window.electronAPI`
|
|
|
+variable to your renderer process.
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+const { contextBridge, ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
+ setTitle: (title) => ipcRenderer.send('set-title', title)
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+At this point, you'll be able to use the `window.electronAPI.setTitle()` function in the renderer
|
|
|
+process.
|
|
|
+
|
|
|
+:::caution Security warning
|
|
|
+We don't directly expose the whole `ipcRenderer.send` API for [security reasons]. Make sure to
|
|
|
+limit the renderer's access to Electron APIs as much as possible.
|
|
|
+:::
|
|
|
+
|
|
|
+### 3. Build the renderer process UI
|
|
|
+
|
|
|
+In our BrowserWindow's loaded HTML file, add a basic user interface consisting of a text input
|
|
|
+and a button:
|
|
|
+
|
|
|
+```html {11-12} title='index.html'
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
|
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
|
|
+ <title>Hello World!</title>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ Title: <input id="title"/>
|
|
|
+ <button id="btn" type="button">Set</button>
|
|
|
+ <script src="./renderer.js"></script>
|
|
|
+ </body>
|
|
|
+</html>
|
|
|
+```
|
|
|
+
|
|
|
+To make these elements interactive, we'll be adding a few lines of code in the imported
|
|
|
+`renderer.js` file that leverages the `window.electronAPI` functionality exposed from the preload
|
|
|
+script:
|
|
|
+
|
|
|
+```javascript title='renderer.js (Renderer Process)'
|
|
|
+const setButton = document.getElementById('btn')
|
|
|
+const titleInput = document.getElementById('title')
|
|
|
+setButton.addEventListener('click', () => {
|
|
|
+ const title = titleInput.value
|
|
|
+ window.electronAPI.setTitle(title)
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+At this point, your demo should be fully functional. Try using the input field and see what happens
|
|
|
+to your BrowserWindow title!
|
|
|
+
|
|
|
+## Pattern 2: Renderer to main (two-way)
|
|
|
+
|
|
|
+A common application for two-way IPC is calling a main process module from your renderer process
|
|
|
+code and waiting for a result. This can be done by using [`ipcRenderer.invoke`] paired with
|
|
|
+[`ipcMain.handle`].
|
|
|
+
|
|
|
+In the following example, we'll be opening a native file dialog from the renderer process and
|
|
|
+returning the selected file's path.
|
|
|
+
|
|
|
+For this demo, you'll need to add code to your main process, your renderer process, and a preload
|
|
|
+script. The full code is below, but we'll be explaining each file individually in the following
|
|
|
+sections.
|
|
|
+
|
|
|
+```fiddle docs/fiddles/ipc/pattern-2
|
|
|
+```
|
|
|
+
|
|
|
+### 1. Listen for events with `ipcMain.handle`
|
|
|
+
|
|
|
+In the main process, we'll be creating a `handleFileOpen()` function that calls
|
|
|
+`dialog.showOpenDialog` and returns the value of the file path selected by the user. This function
|
|
|
+is used as a callback whenever an `ipcRender.invoke` message is sent through the `dialog:openFile`
|
|
|
+channel from the renderer process. The return value is then returned as a Promise to the original
|
|
|
+`invoke` call.
|
|
|
+
|
|
|
+:::caution A word on error handling
|
|
|
+Errors thrown through `handle` in the main process are not transparent as they
|
|
|
+are serialized and only the `message` property from the original error is
|
|
|
+provided to the renderer process. Please refer to
|
|
|
+[#24427](https://github.com/electron/electron/issues/24427) for details.
|
|
|
+:::
|
|
|
+
|
|
|
+```javascript {6-13,25} title='main.js (Main Process)'
|
|
|
+const { BrowserWindow, dialog, ipcMain } = require('electron')
|
|
|
+const path = require('path')
|
|
|
+
|
|
|
+//...
|
|
|
+
|
|
|
+async function handleFileOpen() {
|
|
|
+ const { canceled, filePaths } = await dialog.showOpenDialog()
|
|
|
+ if (canceled) {
|
|
|
+ return
|
|
|
+ } else {
|
|
|
+ return 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()
|
|
|
+})
|
|
|
+//...
|
|
|
+```
|
|
|
+
|
|
|
+:::tip on channel names
|
|
|
+The `dialog:` prefix on the IPC channel name has no effect on the code. It only serves
|
|
|
+as a namespace that helps with code readability.
|
|
|
+:::
|
|
|
+
|
|
|
+:::info
|
|
|
+Make sure you're loading the `index.html` and `preload.js` entry points for the following steps!
|
|
|
+:::
|
|
|
+
|
|
|
+### 2. Expose `ipcRenderer.invoke` via preload
|
|
|
+
|
|
|
+In the preload script, we expose a one-line `openFile` function that calls and returns the value of
|
|
|
+`ipcRenderer.invoke('dialog:openFile')`. We'll be using this API in the next step to call the
|
|
|
+native dialog from our renderer's user interface.
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+const { contextBridge, ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
+ openFile: () => ipcRenderer.invoke('dialog:openFile')
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+:::caution Security warning
|
|
|
+We don't directly expose the whole `ipcRenderer.invoke` API for [security reasons]. Make sure to
|
|
|
+limit the renderer's access to Electron APIs as much as possible.
|
|
|
+:::
|
|
|
+
|
|
|
+### 3. Build the renderer process UI
|
|
|
+
|
|
|
+Finally, let's build the HTML file that we load into our BrowserWindow.
|
|
|
+
|
|
|
+```html {10-11} title='index.html'
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
|
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
|
|
+ <title>Dialog</title>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <button type="button" id="btn">Open a File</button>
|
|
|
+ File path: <strong id="filePath"></strong>
|
|
|
+ <script src='./renderer.js'></script>
|
|
|
+ </body>
|
|
|
+</html>
|
|
|
+```
|
|
|
+
|
|
|
+The UI consists of a single `#btn` button element that will be used to trigger our preload API, and
|
|
|
+a `#filePath` element that will be used to display the path of the selected file. Making these
|
|
|
+pieces work will take a few lines of code in the renderer process script:
|
|
|
+
|
|
|
+```javascript title='renderer.js (Renderer Process)'
|
|
|
+const btn = document.getElementById('btn')
|
|
|
+const filePathElement = document.getElementById('filePath')
|
|
|
+
|
|
|
+btn.addEventListener('click', async () => {
|
|
|
+ const filePath = await window.electronAPI.openFile()
|
|
|
+ filePathElement.innerText = filePath
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+In the above snippet, we listen for clicks on the `#btn` button, and call our
|
|
|
+`window.electronAPI.openFile()` API to activate the native Open File dialog. We then display the
|
|
|
+selected file path in the `#filePath` element.
|
|
|
+
|
|
|
+### Note: legacy approaches
|
|
|
+
|
|
|
+The `ipcRenderer.invoke` API was added in Electron 7 as a developer-friendly way to tackle two-way
|
|
|
+IPC from the renderer process. However, there exist a couple alternative approaches to this IPC
|
|
|
+pattern.
|
|
|
+
|
|
|
+:::warning Avoid legacy approaches if possible
|
|
|
+We recommend using `ipcRenderer.invoke` whenever possible. The following two-way renderer-to-main
|
|
|
+patterns are documented for historical purposes.
|
|
|
+:::
|
|
|
+
|
|
|
+:::info
|
|
|
+For the following examples, we're calling `ipcRenderer` directly from the preload script to keep
|
|
|
+the code samples small.
|
|
|
+:::
|
|
|
+
|
|
|
+#### Using `ipcRenderer.send`
|
|
|
+
|
|
|
+The `ipcRenderer.send` API that we used for single-way communication can also be leveraged to
|
|
|
+perform two-way communication. This was the recommended way for asynchronous two-way communication
|
|
|
+via IPC prior to Electron 7.
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+// You can also put expose this code to the renderer
|
|
|
+// process with the `contextBridge` API
|
|
|
+const { ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+ipcRenderer.on('asynchronous-reply', (_event, arg) => {
|
|
|
+ console.log(arg) // prints "pong" in the DevTools console
|
|
|
+})
|
|
|
+ipcRenderer.send('asynchronous-message', 'ping')
|
|
|
+```
|
|
|
+
|
|
|
+```javascript title='main.js (Main Process)'
|
|
|
+ipcMain.on('asynchronous-message', (event, arg) => {
|
|
|
+ console.log(arg) // prints "ping" in the Node console
|
|
|
+ // works like `send`, but returning a message back
|
|
|
+ // to the renderer that sent the original message
|
|
|
+ event.reply('asynchronous-reply', 'pong')
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+There are a couple downsides to this approach:
|
|
|
+
|
|
|
+* You need to set up a second `ipcRenderer.on` listener to handle the response in the renderer
|
|
|
+process. With `invoke`, you get the response value returned as a Promise to the original API call.
|
|
|
+* There's no obvious way to pair the `asynchronous-reply` message to the original
|
|
|
+`asynchronous-message` one. If you have very frequent messages going back and forth through these
|
|
|
+channels, you would need to add additional app code to track each call and response individually.
|
|
|
+
|
|
|
+#### Using `ipcRenderer.sendSync`
|
|
|
+
|
|
|
+The `ipcRenderer.sendSync` API sends a message to the main process and waits _synchronously_ for a
|
|
|
+response.
|
|
|
+
|
|
|
+```javascript title='main.js (Main Process)'
|
|
|
+const { ipcMain } = require('electron')
|
|
|
+ipcMain.on('synchronous-message', (event, arg) => {
|
|
|
+ console.log(arg) // prints "ping" in the Node console
|
|
|
+ event.returnValue = 'pong'
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+// You can also put expose this code to the renderer
|
|
|
+// process with the `contextBridge` API
|
|
|
+const { ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+const result = ipcRenderer.sendSync('synchronous-message', 'ping')
|
|
|
+console.log(result) // prints "pong" in the DevTools console
|
|
|
+```
|
|
|
+
|
|
|
+The structure of this code is very similar to the `invoke` model, but we recommend
|
|
|
+**avoiding this API** for performance reasons. Its synchronous nature means that it'll block the
|
|
|
+renderer process until a reply is received.
|
|
|
+
|
|
|
+## Pattern 3: Main to renderer
|
|
|
+
|
|
|
+When sending a message from the main process to a renderer process, you need to specify which
|
|
|
+renderer is receiving the message. Messages need to be sent to a renderer process
|
|
|
+via its [`WebContents`] instance. This WebContents instance contains a [`send`][webcontents-send] method
|
|
|
+that can be used in the same way as `ipcRenderer.send`.
|
|
|
+
|
|
|
+To demonstrate this pattern, we'll be building a number counter controlled by the native operating
|
|
|
+system menu.
|
|
|
+
|
|
|
+For this demo, you'll need to add code to your main process, your renderer process, and a preload
|
|
|
+script. The full code is below, but we'll be explaining each file individually in the following
|
|
|
+sections.
|
|
|
+
|
|
|
+```fiddle docs/fiddles/ipc/pattern-3
|
|
|
+```
|
|
|
+
|
|
|
+### 1. Send messages with the `webContents` module
|
|
|
+
|
|
|
+For this demo, we'll need to first build a custom menu in the main process using Electron's `Menu`
|
|
|
+module that uses the `webContents.send` API to send an IPC message from the main process to the
|
|
|
+target renderer.
|
|
|
+
|
|
|
+```javascript {11-26} title='main.js (Main Process)'
|
|
|
+const {app, BrowserWindow, Menu} = 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')
|
|
|
+}
|
|
|
+//...
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+For the purposes of the tutorial, it's important to note that the `click` handler
|
|
|
+sends a message (either `1` or `-1`) to the renderer process through the `counter` channel.
|
|
|
+
|
|
|
+```javascript
|
|
|
+click: () => mainWindow.webContents.send('update-counter', -1)
|
|
|
+```
|
|
|
+
|
|
|
+:::info
|
|
|
+Make sure you're loading the `index.html` and `preload.js` entry points for the following steps!
|
|
|
+:::
|
|
|
+
|
|
|
+### 2. Expose `ipcRenderer.on` via preload
|
|
|
+
|
|
|
+Like in the previous renderer-to-main example, we use the `contextBridge` and `ipcRenderer`
|
|
|
+modules in the preload script to expose IPC functionality to the renderer process:
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+const { contextBridge, ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
+ onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback)
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+After loading the preload script, your renderer process should have access to the
|
|
|
+`window.electronAPI.onUpdateCounter()` listener function.
|
|
|
+
|
|
|
+:::caution Security warning
|
|
|
+We don't directly expose the whole `ipcRenderer.on` API for [security reasons]. Make sure to
|
|
|
+limit the renderer's access to Electron APIs as much as possible.
|
|
|
+:::
|
|
|
+
|
|
|
+:::info
|
|
|
+In the case of this minimal example, you can call `ipcRenderer.on` directly in the preload script
|
|
|
+rather than exposing it over the context bridge.
|
|
|
+
|
|
|
+```javascript title='preload.js (Preload Script)'
|
|
|
+const { ipcRenderer } = require('electron')
|
|
|
+
|
|
|
+window.addEventListener('DOMContentLoaded', () => {
|
|
|
+ const counter = document.getElementById('counter')
|
|
|
+ ipcRenderer.on('update-counter', (_event, value) => {
|
|
|
+ const oldValue = Number(counter.innerText)
|
|
|
+ const newValue = oldValue + value
|
|
|
+ counter.innerText = newValue
|
|
|
+ })
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+However, this approach has limited flexibility compared to exposing your preload APIs
|
|
|
+over the context bridge, since your listener can't directly interact with your renderer code.
|
|
|
+:::
|
|
|
+
|
|
|
+### 3. Build the renderer process UI
|
|
|
+
|
|
|
+To tie it all together, we'll create an interface in the loaded HTML file that contains a
|
|
|
+`#counter` element that we'll use to display the values:
|
|
|
+
|
|
|
+```html {10} title='index.html'
|
|
|
+<!DOCTYPE html>
|
|
|
+<html>
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
|
|
|
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
|
|
+ <title>Menu Counter</title>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ Current value: <strong id="counter">0</strong>
|
|
|
+ <script src="./renderer.js"></script>
|
|
|
+ </body>
|
|
|
+</html>
|
|
|
+```
|
|
|
+
|
|
|
+Finally, to make the values update in the HTML document, we'll add a few lines of DOM manipulation
|
|
|
+so that the value of the `#counter` element is updated whenever we fire an `update-counter` event.
|
|
|
+
|
|
|
+```javascript title='renderer.js (Renderer Process)'
|
|
|
+const counter = document.getElementById('counter')
|
|
|
+
|
|
|
+window.electronAPI.onUpdateCounter((_event, value) => {
|
|
|
+ const oldValue = Number(counter.innerText)
|
|
|
+ const newValue = oldValue + value
|
|
|
+ counter.innerText = newValue
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+In the above code, we're passing in a callback to the `window.electronAPI.onUpdateCounter` function
|
|
|
+exposed from our preload script. The second `value` parameter corresponds to the `1` or `-1` we
|
|
|
+were passing in from the `webContents.send` call from the native menu.
|
|
|
+
|
|
|
+### Optional: returning a reply
|
|
|
+
|
|
|
+There's no equivalent for `ipcRenderer.invoke` for main-to-renderer IPC. Instead, you can
|
|
|
+send a reply back to the main process from within the `ipcRenderer.on` callback.
|
|
|
+
|
|
|
+We can demonstrate this with slight modifications to the code from the previous example. In the
|
|
|
+renderer process, use the `event` parameter to send a reply back to the main process through the
|
|
|
+`counter-value` channel.
|
|
|
+
|
|
|
+```javascript title='renderer.js (Renderer Process)'
|
|
|
+const counter = document.getElementById('counter')
|
|
|
+
|
|
|
+window.electronAPI.onUpdateCounter((event, value) => {
|
|
|
+ const oldValue = Number(counter.innerText)
|
|
|
+ const newValue = oldValue + value
|
|
|
+ counter.innerText = newValue
|
|
|
+ event.reply('counter-value', newValue)
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+In the main process, listen for `counter-value` events and handle them appropriately.
|
|
|
+
|
|
|
+```javascript title='main.js (Main Process)'
|
|
|
+//...
|
|
|
+ipcMain.on('counter-value', (_event, value) => {
|
|
|
+ console.log(value) // will print value to Node console
|
|
|
+})
|
|
|
+//...
|
|
|
+```
|
|
|
+
|
|
|
+## Pattern 4: Renderer to renderer
|
|
|
+
|
|
|
+There's no direct way to send messages between renderer processes in Electron using the `ipcMain`
|
|
|
+and `ipcRenderer` modules. To achieve this, you have two options:
|
|
|
+
|
|
|
+* Use the main process as a message broker between renderers. This would involve sending a message
|
|
|
+from one renderer to the main process, which would forward the message to the other renderer.
|
|
|
+* Pass a [MessagePort] from the main process to both renderers. This will allow direct communication
|
|
|
+between renderers after the initial setup.
|
|
|
+
|
|
|
+## Object serialization
|
|
|
+
|
|
|
+Electron's IPC implementation uses the HTML standard
|
|
|
+[Structured Clone Algorithm][sca] to serialize objects passed between processes, meaning that
|
|
|
+only certain types of objects can be passed through IPC channels.
|
|
|
+
|
|
|
+In particular, DOM objects (e.g. `Element`, `Location` and `DOMMatrix`), Node.js objects
|
|
|
+backed by C++ classes (e.g. `process.env`, some members of `Stream`), and Electron objects
|
|
|
+backed by C++ classes (e.g. `WebContents`, `BrowserWindow` and `WebFrame`) are not serializable
|
|
|
+with Structured Clone.
|
|
|
+
|
|
|
+[context isolation tutorial]: context-isolation.md
|
|
|
+[security reasons]: ./context-isolation.md#security-considerations
|
|
|
+[`ipcMain`]: ../api/ipc-main.md
|
|
|
+[`ipcMain.handle`]: ../api/ipc-main.md#ipcmainhandlechannel-listener
|
|
|
+[`ipcMain.on`]: ../api/ipc-main.md
|
|
|
+[IpcMainEvent]: ../api/structures/ipc-main-event.md
|
|
|
+[`ipcRenderer`]: ../api/ipc-renderer.md
|
|
|
+[`ipcRenderer.invoke`]: ../api/ipc-renderer.md#ipcrendererinvokechannel-args
|
|
|
+[`ipcRenderer.send`]: ../api/ipc-renderer.md
|
|
|
+[MessagePort]: ./message-ports.md
|
|
|
+[preload script]: process-model.md#preload-scripts
|
|
|
+[process model docs]: process-model.md
|
|
|
+[sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
|
|
|
+[`WebContents`]: ../api/web-contents.md
|
|
|
+[webcontents-send]: ../api/web-contents.md#contentssendchannel-args
|