README
electron-ipcb
A small library to help handle IPC with electron and allow a better architecture. I'm using it with React but feel free to try anything else.
Why ?
I love code. Unless I have to repeat myself for hours, which has been the case with electron and its IPC.
The way to communicate between the main and renderer processes is always the same : we have listeners on both side to receive and send requests, everything barely change on every screen but we have to re-write everything.
The purpose of this library is simple :
- Write the least code possible
- Help to work with a good architecture
- Easy maintainability / readability
- Still allow some freedom
And now ... How ?
The library is centered around 3 main modules :
- IpcWindow
- IpcChannel
- IpcChannelBinding
The aim of IpcWindow is to allow a class to extends it, feed it with Objects build by the two others class so it create and bind every channel without writing anything more. Here you can see an example of a MainWindow :
/windows/main/MainWindow.ts
import * as path from 'path';
import * as url from 'url';
import { BrowserWindow, BrowserWindowConstructorOptions } from 'electron';
import IpcWindow from '@mrtd/electron-ipcb';
import MainBindings from './MainBindings';
const options: BrowserWindowConstructorOptions = { width: 1100, height: 700, backgroundColor: '#191622', webPreferences: { nodeIntegration: true } };
class MainWindow extends IpcWindow {
window: BrowserWindow | undefined;
constructor() {
super(options, MainBindings);
}
create() {
super.create();
const window = this.window as BrowserWindow;
if (process.env.NODE_ENV === 'development') window.loadURL('http://localhost:4000');
else window.loadURL(url.format({ pathname: path.join(__dirname, 'renderer/index.html'), protocol: 'file:', slashes: true }));
window.on('closed', () => {
this.window = undefined;
});
}
}
export default MainWindow;
The window (BrowserWindow) is not instanciated before the create
function is called. You might load some content differently but otherwise this is classic Electron. The only missing part here is MainBindings.
MainBindings is built on top of IpcChannel and IpcChannelBinding.
The aim of IpcChannel is to define the structure of the channel : its types and action. In my following examples i'll use systeminformation, a really nice library to get data about your computer.
If i was to create a definition channel with IpcChannel, to query the CPU data (for my MainWindow) :
/windows/main/MainChannels.ts
import { Systeminformation } from 'systeminformation';
import IpcChannel from '@mrtd/electron-ipcb';
export const MainCpuChannel = new IpcChannel<[], [Systeminformation.CpuData]>('system:cpu');
An IpcChannel expect you to define the types of arguments (for the request made by a renderer), the type of the response, sent by the main process and also received by the renderer. Otherwise it only take one parameter, the action name used to dispatch events.
From this object build with IpcChannel, we can create our IpcChannelBinding that will be fed to our IpcWindow and everything will be configured. Lets dive into it !
/windows/main/MainBindings.ts
import { cpu, Systeminformation } from 'systeminformation';
import IpcChannelBinding from '@mrtd/electron-ipcb';
import { MainCpuChannel, MainTimeChannel } from './MainChannels';
const MainCpuBinding = new IpcChannelBinding<[], [Systeminformation.CpuData]>(
MainCpuChannel,
(window, e, ...args) =>
new Promise<[Systeminformation.CpuData]>((resolve) => {
cpu((data) => resolve([data]));
})
);
export default [MainCpuBinding];
Here we create a IpcChannnelBinding by definining its arguments / response type the same way as IpcChannel. It expect as argument :
- The corresponding channel
- A handler that will be called every time a renderer process request it.
The expected handler receive the given window, the dispatched event and every arguments that was sent by the renderer. This handler is a function that must return any value or a Promise.
I then choose to export a list containing all ChannelBindings of my main windows here. Now, everything comes together and all the electron part is working as expected, feel free to dive into the source code or documentation if need be.
Renderer process
In the renderer process, we need to use the "Channel" we built (MainCpuChannel). We only have two methods :
onRenderer
allows to define the callback when we receive a response or error from the main process.request
to start a request to the main process on the chosen channel.
MainCpuChannel.onRenderer(
(event, cpu /* , others response parameters if needed */) => {
console.log(cpu);
},
(event, error) => { // This is the way to handle error thrown in the ChannelBinding handler
console.error(error);
}
);
MainCpuChannel.request(/* ...args if needed */);
As I work with React I define those in the componentDidMount method.