Search code examples
javascriptelectronipcrendereripcmain

Issue while sending message from preload.js to main.js using ipcRenderer and ipcmain


i am beginner in desktop apps development with electron, when i try to send message from preload script to main.js using ipcRenderer and ipcMain in main.js below details shows up in VScode terminal and nothing happens(no event occurs) not even console.log works

{
  preventDefault: [Function: preventDefault],
  sender: EventEmitter {
    isDestroyed: [Function: isDestroyed],
    destroy: [Function: destroy],
    getBackgroundThrottling: [Function: getBackgroundThrottling],
    setBackgroundThrottling: [Function: setBackgroundThrottling],
    getProcessId: [Function: getProcessId],
    getOSProcessId: [Function: getOSProcessId],
    equal: [Function: equal],
    _loadURL: [Function: _loadURL],
    reload: [Function: reload],
    reloadIgnoringCache: [Function: reloadIgnoringCache],
    downloadURL: [Function: downloadURL],
    getURL: [Function: getURL],
    getTitle: [Function: getTitle],
    isLoading: [Function: isLoading],
    isLoadingMainFrame: [Function: isLoadingMainFrame],
    isWaitingForResponse: [Function: isWaitingForResponse],
    stop: [Function: stop],
    canGoBack: [Function: canGoBack],
    goBack: [Function: goBack],
    canGoForward: [Function: canGoForward],
    goForward: [Function: goForward],
    canGoToOffset: [Function: canGoToOffset],
    goToOffset: [Function: goToOffset],
    canGoToIndex: [Function: canGoToIndex],
    goToIndex: [Function: goToIndex],
    getActiveIndex: [Function: getActiveIndex],
    clearHistory: [Function: clearHistory],
    length: [Function: length],
    isCrashed: [Function: isCrashed],
    forcefullyCrashRenderer: [Function: forcefullyCrashRenderer],
    setUserAgent: [Function: setUserAgent],
    getUserAgent: [Function: getUserAgent],
    savePage: [Function: savePage],
    openDevTools: [Function: openDevTools],
    closeDevTools: [Function: closeDevTools],
    isDevToolsOpened: [Function: isDevToolsOpened],
    isDevToolsFocused: [Function: isDevToolsFocused],
    enableDeviceEmulation: [Function: enableDeviceEmulation],
    disableDeviceEmulation: [Function: disableDeviceEmulation],
    toggleDevTools: [Function: toggleDevTools],
    inspectElement: [Function: inspectElement],
    setIgnoreMenuShortcuts: [Function: setIgnoreMenuShortcuts],
    setAudioMuted: [Function: setAudioMuted],
    isAudioMuted: [Function: isAudioMuted],
    isCurrentlyAudible: [Function: isCurrentlyAudible],
    undo: [Function: undo],
    redo: [Function: redo],
    cut: [Function: cut],
    copy: [Function: copy],
    paste: [Function: paste],
    pasteAndMatchStyle: [Function: pasteAndMatchStyle],
    delete: [Function: delete],
    selectAll: [Function: selectAll],
    unselect: [Function: unselect],
    replace: [Function: replace],
    replaceMisspelling: [Function: replaceMisspelling],
    findInPage: [Function: findInPage],
    stopFindInPage: [Function: stopFindInPage],
    focus: [Function: focus],
    isFocused: [Function: isFocused],
    sendInputEvent: [Function: sendInputEvent],
    beginFrameSubscription: [Function: beginFrameSubscription],
    endFrameSubscription: [Function: endFrameSubscription],
    startDrag: [Function: startDrag],
    attachToIframe: [Function: attachToIframe],
    detachFromOuterFrame: [Function: detachFromOuterFrame],
    isOffscreen: [Function: isOffscreen],
    startPainting: [Function: startPainting],
    stopPainting: [Function: stopPainting],
    isPainting: [Function: isPainting],
    setFrameRate: [Function: setFrameRate],
    getFrameRate: [Function: getFrameRate],
    invalidate: [Function: invalidate],
    setZoomLevel: [Function: setZoomLevel],
    getZoomLevel: [Function: getZoomLevel],
    setZoomFactor: [Function: setZoomFactor],
    getZoomFactor: [Function: getZoomFactor],
    getType: [Function: getType],
    _getPreloadPaths: [Function: _getPreloadPaths],
    getLastWebPreferences: [Function: getLastWebPreferences],
    getOwnerBrowserWindow: [Function: getOwnerBrowserWindow],
    inspectServiceWorker: [Function: inspectServiceWorker],
    inspectSharedWorker: [Function: inspectSharedWorker],
    inspectSharedWorkerById: [Function: inspectSharedWorkerById],
    getAllSharedWorkers: [Function: getAllSharedWorkers],
    _print: [Function: _print],
    _printToPDF: [Function: _printToPDF],
    _setNextChildWebPreferences: [Function: _setNextChildWebPreferences],
    addWorkSpace: [Function: addWorkSpace],
    removeWorkSpace: [Function: removeWorkSpace],
    showDefinitionForSelection: [Function: showDefinitionForSelection],
    copyImageAt: [Function: copyImageAt],
    capturePage: [Function: capturePage],
    setEmbedder: [Function: setEmbedder],
    setDevToolsWebContents: [Function: setDevToolsWebContents],
    getNativeView: [Function: getNativeView],
    incrementCapturerCount: [Function: incrementCapturerCount],
    decrementCapturerCount: [Function: decrementCapturerCount],
    isBeingCaptured: [Function: isBeingCaptured],
    setWebRTCIPHandlingPolicy: [Function: setWebRTCIPHandlingPolicy],
    getWebRTCIPHandlingPolicy: [Function: getWebRTCIPHandlingPolicy],
    takeHeapSnapshot: [Function: takeHeapSnapshot],
    setImageAnimationPolicy: [Function: setImageAnimationPolicy],
    _getProcessMemoryInfo: [Function: _getProcessMemoryInfo],
    id: 1,
    session: [Getter],
    hostWebContents: [Getter],
    devToolsWebContents: [Getter],
    debugger: [Getter],
    mainFrame: [Getter],
    _windowOpenHandler: null,
    _events: [Object: null prototype] {
      '-ipc-message': [Function (anonymous)],
      '-ipc-invoke': [Function (anonymous)],
      '-ipc-message-sync': [Function (anonymous)],
      '-ipc-ports': [Function (anonymous)],
      crashed: [Function (anonymous)],
      'render-process-gone': [Function (anonymous)],
      'devtools-reload-page': [Function (anonymous)],
      '-new-window': [Function (anonymous)],
      '-will-add-new-contents': [Function (anonymous)],
      '-add-new-contents': [Function (anonymous)],
      login: [Function (anonymous)],
      'ready-to-show': [Function (anonymous)],
      'select-bluetooth-device': [Function (anonymous)]
    },
    _eventsCount: 13
  },
  frameId: 1,
  processId: 4,
  reply: [Function (anonymous)]
}

on button click a function from preload will be called which will send message to main processs

renderer.js

button.addEventListener('click', function() {
    window.LoadProductWindow.load();
});

preload.js

const {
    contextBridge,
    electron,
    ipcRenderer
} = require('electron')

contextBridge.exposeInMainWorld('LoadProductWindow', {
    load: () => ipcRenderer.send('load_product_window')
})

main.js

ipcMain.on('load_product_window', () => {
    console.log('received')
});

please solve my issue and also i want to add a new window on button click to add new items how can i do that?

Thanks


Solution

  • There is no doubt that many examples showing the use of Electron's Inter-Process Communication and preload.js are complicated and difficult to understand. Once you have a firm understanding of Context-Isolation then things will begin to fall into place.

    The main issue I have been finding recently is that many people are breaking the Separation of Concerns design principle with their preload.js script. Whilst this is not a hard and fast rule, simplifying your preload.js script by moving all logic out of it apart from pure communication logic makes for really simple, readable code.


    First off, let's define a simple, easy to read preload.js script.

    In this script you will find an ipc object containing 3 arrays (send, receive and sendReceive) where you can define your white-listed channels names. When called from either the main thread or render thread, they will pass without be obstructed. Any and all other channel names used which are not defined within these 3 arrays will be blocked.

    Here, I have added the channel name productWindow:load to the send (from render to main thread) array.

    preload.js (main thread)

    'use strict';
    
    // Import the necessary Electron components.
    const contextBridge = require('electron').contextBridge;
    const ipcRenderer = require('electron').ipcRenderer;
    
    // White-listed channels.
    const ipc = {
        'render': {
            // From render to main.
            'send': [
                'productWindow:load'
            ],
            // From main to render.
            'receive': [],
            // 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);
                }
            }
        }
    );
    

    Next, we will quickly define your index.html file which will include Javascript functionality to send an IPC message to the main thread upon the push of a button.

    index.html (render thread)

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Main Page</title>
        </head>
    
        <body>
            <label for="button">Product Window: </label>
            <input type="button" id="button" value="Load Now">
        </body>
    
        <script>
            document.getElementById('button').addEventListener('click', () => {
                window.ipcRender.send('productWindow:load');
            })
        </script>
    </html>
    

    product.html (render thread)

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Product Page</title>
        </head>
    
        <body>
            <h1>It Works..!</h1>
        </body>
    </html>
    

    Finally, in your main.js script (being the entry point of your application) we listen for a message over the productWindow:load channel using Electron's ipcMain module, specifically the ipcMain.on() method.

    main.js (main thread)

    const electronApp = require('electron').app;
    const electronBrowserWindow = require('electron').BrowserWindow;
    const electronIpcMain = require('electron').ipcMain;
    
    const nodePath = require("path");
    
    let mainWindow;
    let productWindow;
    
    function createMainWindow() {
        const mainWindow = new electronBrowserWindow({
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                preload: nodePath.join(__dirname, 'preload.js')
            }
        });
    
        mainWindow.loadFile('index.html')
            .then(() => { mainWindow.show(); });
    
        return mainWindow;
    }
    
    function createProductWindow(parentWindow) {
        const productWindow = new electronBrowserWindow({
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            parent: parentWindow,
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                preload: nodePath.join(__dirname, 'preload.js')
            }
        });
    
        productWindow.loadFile('product.html')
            .then(() => { productWindow.show(); });
    
        return productWindow;
    }
    
    electronApp.on('ready', () => {
        mainWindow = createMainWindow();
    });
    
    // Let's listen for the 'productWindow:load' signal.
    electronIpcMain.on('productWindow:load', () => {
        productWindow = createProductWindow(mainWindow);
    });
    
    electronApp.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
            electronApp.quit();
        }
    });
    
    electronApp.on('activate', () => {
        if (electronBrowserWindow.getAllWindows().length === 0) {
            createMainWindow();
        }
    });
    

    As your Electron application begins to grow you will want to begin splitting your main.js file into smaller files, to 'separate your concerns'. IE: Moving your different window creation code segments into their own files, including their associated IPC code.

    This will improve your code structure, simplify readability and make for a maintainable code base.