Saturday, February 18, 2023

Electron IPC Channel Pub/Sub

Problem: 

While working on an Electron program that has multiple windows, I knew I needed the windows to be able to talk to each other. This is usually done with each window establishing a connection with the main script, and then the main script facilitating the communication between each window. This way works fine, but seems very manual and then became a bit more convoluted when multiple windows need to get the same information. 


Solution:

To solve this, I decided to implement a very simple subscription style connection:

const ChannelSubscribeAction = 'channel-subscribe';
const ChannelUnsubscribeAction = 'channel-unsubscribe';
const ErrorDataChannel = 'error-data';

// handle channel subscriptions
const channelWindowMap = new Map();
ipcMain.on(ChannelSubscribeAction, (event, channel) => {
    if (!channelWindowMap.has(channel)){
        channelWindowMap.set(channel, new Set());
        ipcMain.on(channel, (event, data) => handleChannelEvent(channel, event, data));
    }
    channelWindowMap.set(channel, channelWindowMap.get(channel).add(event.sender));
});
ipcMain.on(ChannelUnsubscribeAction, (event, channel) => {
    if (!channelWindowMap.has(channel)) return;
    channelWindowMap.set(channel, channelWindowMap.get(channel).remove(event.sender));
});
function handleChannelEvent(channel, event, data){
    if (!channelWindowMap.has(channel)) return;
   
    channelWindowMap.get(channel).forEach((webContentWin) => {
        webContentWin.send(channel, data);
        // if we get an error, open dev tools for the window that had the error
        if (channel == ErrorDataChannel){
            event.sender.webContents.openDevTools();
        }
    });
}

This allows for any window to subscribe to any channel, and then later unsubscribe if desired. In my code here, I also am detecting when script errors occur and triggering the dev tools to open for that specific window to have a closer look. 

The channels are all defined in a single simple enum file and then when each window starts up, it calls in: 

ipcRenderer.send(ChannelSubscribeAction, Channels.MainChannel);

Note: There may be some vulnerabilities that come with this approach.


No comments: