Search code examples
electronipcwindows-hello

Does Electron IPC send messaged while app is in background?


I am working on an Electron Desktop version for Windows for our Angular-WebApp. I implemented biometric authentication via Windows Hello which works nicely. Actually I open the Hello dialog (with nodeRT) when an event gets triggered via Inter-Process Communication (IPC), which tells me, that the page is ready for a login. The page is loaded with window.loadURL(url), so there is no local webcontent.

After a while, without doing anything in the app, the users get logged out automatically. After this, the event "login ready" should get triggered on the login page. Now there are two possibilies:

  1. The app is still in foreground and has focus. Then the Windows Hello window will open like expected and has focus. The event was triggered as expected.
  2. If the app is in background, without focus, the Windows Hello window will not open. The event was not triggered.

Is this the expected behavior? Is it possible that there is no IPC when the app is in background? How could I handle this? Maybe I could focus the app again. But I guess this could be disturbing. Any ideas?

I wrote logs which showed that the expected event was not triggered while the app is in background.


Solution

  • I have tested what happens when the main window is minimised and an event calls for the creation and display of a second (child) window.

    As expected, the second window will be created and shown.

    Therefore, Electron IPC messages can be sent and received by both the main process and render process(es) when the window(s) are minimised. IE: When the application is running in the background.

    In the below minimum reproducible example, you can set the timeout delay between when the main window is minimised and the second (login.html) window is created and shown.


    main.js (main process)

    // Import required electron modules
    const electronApp = require('electron').app;
    const electronBrowserWindow = require('electron').BrowserWindow;
    const electronIpcMain = require('electron').ipcMain;
    
    // Import required Node modules
    const nodePath = require('path');
    
    // Prevent garbage collection
    let mainWindow;
    let loginWindow;
    
    function createMainWindow() {
        const mainWindow = new electronBrowserWindow({
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                sandbox: true,
                preload: nodePath.join(__dirname, 'preload.js')
            }
        });
    
        mainWindow.loadFile(nodePath.join(__dirname, 'index.html'))
            .then(() => { mainWindow.show(); });
    
        return mainWindow;
    }
    
    function createLoginWindow(parentWindow) {
        const loginWindow = new electronBrowserWindow({
            x: 200,
            y: 150,
            width: 400,
            height: 300,
            parent: parentWindow,
            modal: true,
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                sandbox: true,
                preload: nodePath.join(__dirname, 'preload.js')
            }
        });
    
        loginWindow.loadFile(nodePath.join(__dirname, 'login.html'))
            .then(() => { loginWindow.show(); });
    
        return loginWindow;
    }
    
    electronApp.on('ready', () => {
        mainWindow = createMainWindow();
    });
    
    electronApp.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
            electronApp.quit();
        }
    });
    
    electronApp.on('activate', () => {
        if (electronBrowserWindow.getAllWindows().length === 0) {
            createMainWindow();
        }
    });
    
    // ---
    
    electronIpcMain.on('minimiseMainWindow', () => {
        mainWindow.minimize();
    })
    
    electronIpcMain.on('openLoginWindow', (event, delay) => {
        setTimeout(() => {
            loginWindow = createLoginWindow(mainWindow);
        }, delay * 1000)
    })
    

    preload.js (main process)

    As you can see below, I have requested the main window get minimised first just to make sure IPC continues to work when a second IPC message is sent.

    // Import the necessary Electron modules
    const contextBridge = require('electron').contextBridge;
    const ipcRenderer = require('electron').ipcRenderer;
    
    // Exposed protected methods in the render process
    contextBridge.exposeInMainWorld(
        // Allowed 'ipcRenderer' methods
        'ipcRenderer', {
            // From render to main
            delayOpenLoginWindow: (delay) => {
                ipcRenderer.send('minimiseMainWindow');
                ipcRenderer.send('openLoginWindow', delay);
            }
        }
    );
    

    And how it works...

    /**
     *
     * Main --> Render
     * ---------------
     * Main:    window.webContents.send('channel', data); // Data is optional.
     * Preload: apiName: (listener) => { ipcRenderer.on('channel', listener); }
     * Render:  window.ipcRenderer.apiName((event, data) => { methodName(data); });
     *
     * Main --> Render (Once)
     * ----------------------
     * Main:    window.webContents.send('channel', data); // Data is optional.
     * Preload: apiName: (listener) => { ipcRenderer.once('channel', listener); }
     * Render:  window.ipcRenderer.apiName((event, data) => { methodName(data); });
     *
     * Render --> Main
     * ---------------
     * Render:  window.ipcRenderer.apiName(data); // Data is optional.
     * Preload: apiName: (data) => { ipcRenderer.send('channel', data) }
     * Main:    electronIpcMain.on('channel', (event, data) => { methodName(data); });
     *
     * Render --> Main (Once)
     * ----------------------
     * Render:  window.ipcRenderer.apiName(data); // Data is optional.
     * Preload: apiName: (data) => { ipcRenderer.send('channel', data); }
     * Main:    electronIpcMain.once('channel', (event, data) => { methodName(data); });
     *
     * Render --> Main (Value) --> Render
     * ----------------------------------
     * Render:  window.ipcRenderer.apiName(data).then((result) => { methodName(result); });
     * Preload: apiName: (data) => { return ipcRenderer.invoke('channel', data); }
     * Main:    electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
     *
     * Render --> Main (Promise) --> Render
     * ------------------------------------
     * Render:  window.ipcRenderer.apiName(data).then((result) => { methodName(result); });
     * Preload: apiName: (data) => { return ipcRenderer.invoke('channel', data); }
     * Main:    electronIpcMain.handle('channel', async (event, data) => {
     *              return await myPromise(data).then((result) => { return result; })
     *          });
     *
     * Main:    function myPromise(data) { return new Promise((resolve, reject) => { ... }; )};
     *
     */
    

    index.html (render process)

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Main Window</title>
            <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
        </head>
    
        <body>
            <h1>Main Window</h1>
    
            <p>
                <span>Open the login window <input type="number" id="delay" min="5" max="30" step="5" value="5"> seconds after clicking the below Minimise Window button.</span>
            </p>
    
            <hr>
    
            <input type="button" id="button" value="Minimise Window">
        </body>
    
        <script>
            document.getElementById('button').addEventListener('click', () => {
                window.ipcRenderer.delayOpenLoginWindow(document.getElementById('delay').valueAsNumber);
            })
        </script>
    </html>
    

    login.html (render process)

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Login Window</title>
            <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
        </head>
    
        <body>
            <h1>Login window</h1>
        </body>
    </html>