Correlating Data with IPC in Electron

Words
682
Reading
4 min
Listen
Play
9y

13409222.png

Electron allows you to easily build cross platform applications using web technologies. It is powered by the Chromium web browser and Node.js. There are some drawbacks but I believe the benefits outweigh the cons. It powers apps such as Atom and Discord. This very article was written with Visual Studio Code which is also powered by Electron!

What is IPC?

IPC is inter-process communication that allows you to communicate between different processes. The power of IPC allows you to manage, synchronize, and share state throughout your app. There are numerous approaches to achieving IPC that vary depending on the system used, however that is out of the scope of this article.

For example, to load a webpage, a bidirectional communication channel via sockets are opened to the server, the client sends the request and the server sends back the webpage.

Why is this needed?

Sometimes you need data that can only be retrieved by another process. Maybe you want to offload your computationally-intensive work into another worker and retrieve the computed results when it's needed.

If you use sandboxed renderer processes, then IPC is a must if you need to access features outside of the sandboxes. Care must be taken if untrusted code is running in a sandboxed BrowserWindow! It is very easy to create security holes in your application if you use IPC to simply bypass the sandbox without regard for security.

Getting started

All the code written here uses Typescript. You should definitely check it out if you're not familiar with Typescript.

It is important to note that Electron internally serializes everything sent over IPC to JSON. Functions can't be sent through these IPC channels.

Sending and receiving data

Fortunately, it is very simple to get started sending data back and forth. There is no hassle with these APIs. However, there are some added debugging complexities.

ipcRenderer.send(channel: string, ...args: any[])

Great! Now that you know how to send data, let's listen for it in the main process.

ipcMain.on(channel: string, listener: Function)

The listener callback is invoked with the first parameter being the event. Following the event parameter, any args you originally sent will also be invoked in the event. This contains useful fields such as the webContents sender. This is exactly what we need to respond back to the designated renderer process.

Setting up our internal event system

Let's start with listening for actions and responses on both sides.

ipcMain.on('IPC_ACTION', (e, id: number, event: string, data: any) => {
  const webContents = e.sender as Electron.WebContents;
  if (event === 'ping') {
    webContents.send('IPC_RESPONSE', id, 'pong');
  }
});

The main process will respond to our ping event with a pong. Of course this can be anything. The ID is our event response identifier to which we must respond to, while the event identifies the action that needs to be taken. The renderer process can also send data, although in this case we don't need any data to complete our task.

We then use the webContents field from the first parameter, which is obtained through the sender. From here we send back the correlating id with a little message to respond with.

ipcRenderer.on('IPC_RESPONSE', (_, id: number, data: any) => {
  if (id === 1) {
    console.log(data);
  }
});

This handles our event ID of 1. We will see a pong message from our main process be logged into our console.

const id = 1;
const event = 'ping';
ipcRenderer.send('IPC_ACTION', id, event);

Here we actually send the action we want to perform. The ID and event gets sent off to the main process to do work. In our devtools we can then see our pong data returned!

Links

Summary

We have successfully correlated the data between the renderer and main process! It is recommended to do as much work as possible in the renderer process. You can have as many renderer processes as you need but only one main process. Of course, this is only one solution of many to dealing with IPC. I find this approach to be the simplest to use and easiest to debug.

To make full use of this approach a simple tracker is needed with callback or Promise APIs to ensure ease of use. The underlying event system should be fully transparent to the end user.


I hope you enjoyed reading this article and learned something from it. Feel free to leave suggestions or ask questions down below.