I’ve been working with Electron to create installed desktop applications with JavaScript/TypeScript and I found myself writing up notes about how to have the Browser code communicate with the “main” process. I decided to share my notes here in case they’ll be useful for others.
In Electron there are the following processes to keep in mind:
The first three are mostly there to mitigate security risks by separating the rendering code running in the browser for more privileged APIs on the user’s local machine.
This part is explained well in Electron’s process model documentation topic, save the note on the Preload script’s purpose and the detail on the sandbox privileges for the Preload script.
A visual of the interprocess communication is more or less like this:
The terminology thoroughly confused me around inter-process communication (IPC).
contextBridge.exposeInMainWorld
: In this context, “main world” is the renderer (not the “main” process). 1ipcRenderer
is a module that is available only to the renderer process. It allows sending asynchronous messages from a renderer process to the main process.ipcMain
is a module that is available only to the main process. It is used to communicate asynchronously from the main process to renderer processes.So putting this together, lets go through a few trivia questions to make sure we’ve got it:
ipcRenderer.send(...)
sending the message to?const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
It is sending to the main process from the renderer process.
ipcMain.on(...)
listening to messages for?const { BrowserWindow, ipcMain } = require('electron/main')
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
In this case ipcMain.on
will receive messages sent to the main process. So the message sent by the ipcRenderer.send('set-title', title)
code from the previous example could be received with the above ipcMain.on('set-title'...)
line.
Using ipcRender.invoke(...)
, to receive a response, or ipcRender.send(...)
to send a message and not receive a response.
The code that you load into a BrowserWindow
, cannot require modules such as ipcRenderer
that we used above to send a message from a Preload script. However, The preload script can use contextBridge
to expose an API to the browser code. The renderer can then use that exposed API to send a message to the preload script and the preload script can forward the messages on to the main process.
As shown in the Main to render Pattern the main process can access a BrowserWindow.webContents to post a message to the browser similar to the DOM’s window.postMessage method used in browser JavaScript.
However, that message is only accessible via the preload script since the message is received by ipcRenderer
and the “code” that you load into the BrowserWindow
cannot “require” the ipcRenderer
module.
So the preload script requires ipcRenderer
to listen for the message sent from the main process, and provides that a method with a callback to the renderer process via contextBridge
as shown above and in the linked pattern so that the renderer can indirectly subscribe to the message.
You usually don’t? The preload can provide services to the renderer with contextBridge
as shown above, but usually it just forwards those up to the main process to handle. However, note that the Main process can send messages to the Renderer as noted above. expensive
Hopefully this will help someone else understand Electron and the process model a bit quicker. If so, share it or leave me a note!
I’ve been experimenting with some ways working with message passing a bit easier. If there is interest I’ll write up some thoughts on that too.
Thanks to Jim Willeke and Imran Khawaja for proofreading and feedback on this article.
Technically it is just the default renderer process, because you can have other “isolated renderers” (called “worlds”), but they key point is that it is a renderer process, not a main process.↩︎