Home > database >  Using remote.getGlobal variables in modern isolated renderer process Electron
Using remote.getGlobal variables in modern isolated renderer process Electron

Time:03-07

I'm updating an entire app written a few years ago using Electron v1.8.8. As far as I know, Electron changed its paradigm and now the default expected way of communicating between the main and renderer process is using a module in the middle named preload.js.
To get/set global variables in the old way you would first require the remote module:

var { remote } = require('electron');

And then getting/setting it like so:

remote.getGlobal('sharedObj').get('user')

I've been trying to expose an api object in preload.js to achieve the same old functionality, but with no success.
How I would achieve this without setting nodeIntegration: true, contextIsolation: false?

CodePudding user response:

To send messages between the main thread and a render thread requires the understanding of Inter-Process Communication and Context Isolation.

You are correct in that preload.js acts like a middle man. From my experience, using preload.js as a script whose sole purpose is to communicate data between the main and render threads via the use of defined channels allows for a simple, easy to read, separation of concern. IE: Don't place domain specific functions within your preload.js script. Only add functions that are used for the purpose of performing specific methods of communication.

Below is a typical preload.js file. I have defined a couple of channels to communicate between your main thread and your render thread. PS: You can use any name / naming convention to name your channels.

preload.js (main thread)

const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
    'render': {
        // From render to main.
        'send': [
            'renderThread:saysHi'
        ],
        // From main to render.
        'receive': [
            'mainThread:saysHi'
        ],
        // From render to main and back again.
        'sendReceive': []
    }
};

// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
    // Allowed 'ipcRenderer' methods.
    'ipcRender', {
        // From render to main.
        send: (channel, args) => {
            let validChannels = ipc.render.send;
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, args);
            }
        },
        // From main to render.
        receive: (channel, listener) => {
            let validChannels = ipc.render.receive;
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender`.
                ipcRenderer.on(channel, (event, ...args) => listener(...args));
            }
        },
        // From render to main and back again.
        invoke: (channel, args) => {
            let validChannels = ipc.render.sendReceive;
            if (validChannels.includes(channel)) {
                return ipcRenderer.invoke(channel, args);
            }
        }
    }
);

Whilst your main.js file may look a little different than that shown below, the main points of interest are the reception and transmission of the defined channels renderThread:saysHi and mainThread:saysHi.

main.js (main thread)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;

const nodePath = require("path");

let appWindow;

function createAppWindow() {
    const appWindow = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        fullscreen: false,
        resizable: true,
        movable: true,
        minimizable: true,
        maximizable: true,
        enableLargerThanScreen: true,
        closable: true,
        focusable: true,
        fullscreenable: true,
        frame: true,
        hasShadow: true,
        backgroundColor: '#fff',
        show: false,
        icon: nodePath.join(__dirname, 'icon.png'),
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            worldSafeExecuteJavaScript: true,
            enableRemoteModule: false,
            devTools: (! electronApp.isPackaged),
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    appWindow.loadFile('index.html')
        .then(() => {
            // Main thread saying hi to the render thread.
            appWindow.webContents.send('mainThread:saysHi', 'Hello from the main thread.'); })
        .then(() => { 
            appWindow.show(); })

    return appWindow;
}

// Listen for the render thread saying hi.
electronIpcMain.on('renderThread:saysHi', (event, message) => {
    console.log(message); });
}

electronApp.on('ready', () => {
    appWindow = createWindow();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

Your index.html file will import your Javascript file which would contain the code to listen out and send messages on the specified channels.

index.html (render thread)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>

    <body>
        <div id="main-thread-message"></div>

        <input type="button" id="render-thread-button" value="Click to say hi to the main thread">
    </body>

    <script type="module" src="index.js"></script>
</html>

index.js (render thread)

let mainThreadMessage = document.getElementById('main-thread-message');
let renderThreadButton = document.getElementById('render-thread-button');

// IIFE - Immediately Invoke Function Expression
(function() {
    window.ipcRender.receive('mainThread:saysHi', (message) => {
        mainThreadMessage.textContent = message;
    });

    renderThreadButton.addEventLister('click', () => {
        window.ipcRender.send('renderThread:saysHi', 'Hello from the render thread.');
    });
})();

In the above code, the global object window contains ipcRender.send and ipcRender.receive which is the structure used in your preload.js script underneath the line contextBridge.exposeInMainWorld. You can rename ipcRender and receive / send / invoke to anything you like, though if you do, you would also use the same reference when using it 'html js' side.

  • Related