electron-protocol-ipc
💫 Experimental Inter-Process Communication for Electron via a custom protocol with
similar features to the Electron ipcMain
and ipcRenderer
APIs.
With Electron's recommended configuration of nodeIntegration: false
, a preload
script is required to expose IPC between the main and renderer precesses. If
you're also using the recommended contextIsolation: true
, you'll need to
expose the IPC in the preload via contextBridge
.
Although this is relatively painless when you've done it a few times, it is not trivial for newcomers.
If you maintain a JavaScript library that communicates between the Electron main and renderer processes, your users need to understand the process architecture and preload scripts to get your library working which creates a barrier to entry,
How it Works
- Sets up a privileged custom protocol
-
send
from the renderer to main is via JSONfetch
-
invoke
from renderer andhandle
in the main is via JSONfetch
with JSON response -
send
from main to renderer is handled by keeping a stream open (initiated from the renderer) and sending newline-delimited JSON from the main process - Forward messages to renderers when
destination
is set
👍
- Preload no longer required
- Renderer code is just regular browser code
- Messages can be seen in dev tools network tab
👎
- Messages can be seen in dev tools network tab
- JSON serialisation not great for binary data
-
IpcMain
needs to be created beforeapp
ready
event fires
Example
main.js
import { app } from 'electron';
import { IpcMain } from 'electron-protocol-ipc';
// If you're using a bundler, you might need to use:
import { IpcMain } from 'electron-protocol-ipc/main';
// IpcMain must be created before app 'ready' event so we can
// configure the protocol to be privileged and work with fetch
const ipc = new IpcMain();
// Subscribe to a channel
ipc.on('media-devices', (sender, devices) => console.log(sender, devices));
// Handle `invoke` from a renderer
ipc.handle('gpu-info', () => app.getGPUInfo('basic'));
// Regularly send metrics
setInterval(() => {
ipc.send('metrics', app.getAppMetrics());
}, 4000);
renderer.js
import { IpcRenderer } from 'electron-protocol-ipc';
// If you're using a bundler, you might need to use:
import { IpcRenderer } from 'electron-protocol-ipc/renderer';
// 'streamFromMain' enables the stream that handles send
// from the main process, this is only required if you plan to
// call ipcMain.send(...)
const ipc = new IpcRenderer({ streamFromMain: true });
// Subscribe to a channel
ipc.on('metrics', (sender, metrics) => console.log(sender, metrics));
// Regularly send media device list
setInterval(async () => {
ipc.send(
'media-devices',
await window.navigator.mediaDevices.enumerateDevices()
);
}, 5000);
const gupInfo = await ipc.invoke('gpu-info');
// Send random numbers to second renderer
// This goes via the main process
setInterval(() => {
ipc.sendTo('random-numbers', 'second', Math.random());
}, 200);
second-renderer.js
import { IpcRenderer } from 'electron-protocol-ipc';
// If we name a renderer, we can send to it from other
// renderers with sendTo
const ipc = new IpcRenderer({ name: 'second', streamFromMain: true });
// Subscribe to a channel
ipc.on('random-numbers', (_, num) => console.log('New number:', num));