Skip to content

janhapke/eimerjs

Repository files navigation

eimer logo

eimer.js

eimer.js (Electron Interprocess MEssage Router) is an inter-process communication (IPC) library for electron.js applications, which greatly reduces the amount of boilerplate code needed to interact between different processes.

It is written in TypeScript and comes with minimal dependencies. It has been developed for modern versions of Electron.js (>= 28).

⚠️ This library is in pre-beta stage and not yet production ready ⚠️

Table of Contents

What problem(s) does it solve?

Even simple Electron applications are naturally split into two different processes (main and renderer), which have to be linked through a "preload script". Exchanging data and calling functions across these process boundaries is already non-trivial and the interfaces provided by Electron make it difficult to keep a growing code base maintainable.

As applications grow in functionality and complexity, even more processes become involved - multiple windows on the rendering side, each potentially with web workers or iframes and the main process can start worker threads or spawn utility processes which themselves can spawn more processes and threads.

All of the different process-like concepts in Electron have slightly different APIs for inter-process communication and while MessagePorts can be used to unify these concepts, even these are limited to point-to-point communication between at most two processes at a time.

Features

  • Transports unify different IPC APIs under a single, common interface
    • ipcMain, ipcRenderer, MessagePort, window.postMessage, node:worker_threads
  • Message Bus transparently forwards IPC messages across all connected processes
  • Publish/Subscribe pattern to broadcast and listen to events across processes
  • Remote Procedure Call (RPC) pattern to invoke methods in different processes with a promise-based interface

Getting Started

We'll assume that you already have an Electron.js project that is written in TypeScript and it can be compiled / started.

If not, check out the examples directory! It also contains examples that show how to implement a basic counter with RPC and publish/subscribe and how to extend that to multiple windows or worker threads.

Install eimer.js

npm install eimer

Note that the following examples have been stripped down for brevity. Add best practices and coding style on your own. Also, you can find full source code, including build configurations, in the examples directory.

Add eimer.js to the Main Process

The Main Process sets up an instance of eimer.js which uses Electron's ipcMain to communicate with the renderer process. It also registers a simple "Hello World" RPC Handler. Every instance of Eimer has a unique "node id", which is passed to its constructor.

import { app, BrowserWindow, ipcMain } from 'electron';
import { Eimer, EimerIpcMainTransport } from 'eimer';

const eimer = new Eimer('main');
eimer.registerRpcHandler('hello', async () => 'world');

app.whenReady().then(() => {
    const mainWindow = new BrowserWindow({
        width: 800, height: 600, webPreferences: { preload: __dirname + '/preload.js' }
    });

    eimer.connect(new EimerIpcMainTransport(ipcMain, mainWindow.webContents));

    mainWindow.loadFile('index.html');
    mainWindow.webContents.openDevTools();

    app.on('window-all-closed', () => { app.quit() });
});

Add eimer.js to the Preload Script

The purpose of the Preload Script is to set up communication between the main process and the "browser" window, without exposing too many APIs of the main process in the browser context.

We set up another instance of eimer.js in the preload script, which simply forwards messages. It uses Electron's ipcRenderer to communicate with the main process and window.postMessage to communicate with the browser window.

import { ipcRenderer } from 'electron';
import { Eimer, EimerIpcRendererTransport, EimerBrowserTransport } from 'eimer';

const eimer = new Eimer('preload');
eimer.connect(new EimerIpcRendererTransport(ipcRenderer));
eimer.connect(new EimerBrowserTransport(window, { targetOrigin: '*' }));

Add eimer.js to the Browser Window

The Browser Window sets up an instance of eimer.js, which uses window.postMessage to communicate with the preload script.

import { Eimer, EimerBrowserTransport } from 'eimer';

const eimer = new Eimer('renderer');
eimer.connect(new EimerBrowserTransport(window));

eimer.callRpc<string>('hello').then(value => { console.log('hello ' + value) });

Build Process Considerations

  • The Preload Script as well as the Renderer / Browser Code run inside separate browser environments and need to be separately compiled and bundled with the eimer.js library.
    • This is not specific to eimer.js but a general pitfall of Electron Applications
  • You can add extra build scripts to the package.json that rely on esbuild (npm install esbuild)
    "scripts": {
        "build": "tsc && npm run build:preload && npm run build:renderer",
        "build:preload": "esbuild src/preload.ts --bundle --outfile=dist/preload.js --external:electron",
        "build:renderer": "esbuild src/renderer.ts --bundle --outfile=dist/renderer.js",
    }
  • Be sure to call the npm run build:preload and the npm run build:renderer scripts in your build process!
  • Additional options for esbuild (build:preload and build:renderer) that could be useful
    • --platform=browser
    • --target=es2020
    • --format=cjs
  • Check out the examples directory for complete projects including build configurations.

Limitations

This library is in pre-beta stage.

Existing functionality mostly works and is well tested. However, performance is not optimized at all (for example, all messages are broadcast to all connected processes, the ids of all messages ever sent is kept in memory indefinitely) and features are limited (security and extensibility need improvement).

Also the documentation is lacking.

Therefore, this library cannot be considered ready for production use just yet.

Authors

License

MIT