Data Flow

Lets get some terms squared away:

  1. Backend - the nodejs process that is running in the background

  2. Frontend - the Html/Javascript/React Window

  3. Context Bridge - the stitching between the Frontend and the Backend

This article is how to push messages from the backend, to the frontend

Sending Frontend Messages To Backend

Option 1: Using the Router

Since we are using React, we can use React’s Router components to create some new application behaviors. For example, if we made a Pomodoro application, we would create a timer, and wait on the backend until that timer was complete.

In this example, we will change the main window’s location to be some page saying that the timer is up.

// main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');

let mainWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
        },
    });

    mainWindow.loadFile('index.html');

    // Start a 5-saecond timer (5000 ms)
    setTimeout(() => {
        // Send a message to the renderer to change the URL
        mainWindow.loadUrl(someUrl);
    }, 5000); // Timer set to 5 seconds
}

app.whenReady().then(createWindow);

Here you see we create the window, set the window to be a default file. We set a timer, and when that timer is finished, we change the URL.

Because we are changing the URL, that means we can also set query parameters. Be careful, if your query parameters are too long, they may become truncated.

Option 2: Using ContextBridge

Consider the same example, however, we will change a few lines and you will see that we can use that trusty old context bridge.

If we setup our context bridge with a method:

// preload.js
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
    onTimerFinish: (callback) => ipcRenderer.on('timer-finished', callback)
});

We can create a onTimerFinish, with a callback. This means we can create a subscriber in our Frontend React

window.electronAPI.onTimerFinish((event, timerName) => {
    // React State
    setExpiringTimerName(timerName)
});

And Change our main typescript process to send that message after waiting out the timer.

// main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');

let mainWindow;

function createWindow() {
    mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js'),
        },
    });

    mainWindow.loadFile('index.html');

    // Start a 5-second timer (5000 ms)
    setTimeout(() => {
        // Send a message to the renderer
        mainWindow.webContents.send('timer-finished', 'MyTimer');
    }, 5000); // Timer set to 5 seconds
}

You can see here we’ve changed from setting our frontend’s url, to sending a message to the context bridge.

Keep reading