Skip to content
Advertisement

How do I wait to update an HTML header until promise is resolved?

I am building my first electron application and I am running into a JS error I am not sure how to fix. I have managed to setup the IPC for a basic password manager application. The use-case is simple:

  1. I click an HTML button and the frontend connects to the backend and delivers a keyword from which to build a password.
  2. Password is generated.
  3. Upon completion, the password is returned to the frontend to be displayed via HTML in a header tag.

Here is an example of the expected behavior with the input string dog:
keyword –> dog
generated password –> dog-jdls4ksls

The issue I am seeing is that instead of printing the generated password, I am seeing:

[object Object]-jdls4ksls

My best guess is that, since I am using async/await, I am printing the promise memory object instead of the returned value. I do not know, however, how I would block to wait for completion. The code in question providing this output is the last line of render.js, which was called from the HTML body.

Any help would be appreciated!

For context, I am primarily a backend developer, with plenty of python and C/C++/C# experience. My goal is to rebuild a C#/.NET GUI application into an electron app.

Here is all my code.

main.js

const {app, BrowserWindow, ipcMain} = require("electron")
const path = require("path")

function generatePassword(keyword) {
    console.log(keyword)
    return keyword + '-' + Math.random().toString(36).substring(2,12)
}

function createWindow () {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        resizable: false,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })

    win.loadFile('html/passwordGen.html')
}

app.whenReady().then(() => {
    ipcMain.handle("generatePassword", generatePassword)
    // console.log(generatePassword('test string')) // works
    createWindow()
}).catch(error => {
    console.log(error) // log error to console
    app.quit() // quit the app
})

preload.js

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

contextBridge.exposeInMainWorld('main', {
    genPW: (keyword) => ipcRenderer.invoke("geåneratePassword", keyword)
})

render.js

async function testClick () {
    const pw_root = document.getElementById("keyword")
    const pw_label = document.querySelector("#password")
    pw_label.innerText = await window.main.genPW(pw_root.value)
}

passwordGen.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Password Generator</title>
    <link rel="stylesheet" href="../css/style.css">
    <script src="../render.js"></script>
</head>
<body>
    
    <h1>Password Generator</h1>
    <input type="text" id="keyword" placeholder="Please enter a keyword...">
    <button id="btn" onclick="testClick()">Generate Password</button>

    <h1 id="password"></h1>
</body>
</html>

Edit:

Here is the code that worked. The accepted solution worked with the exception that I needed to either 1) keep the async/await on the generatePassword() or 2) convert it to .then() format as recommended in another solution.

main.js

const {app, BrowserWindow, ipcMain} = require("electron")
const path = require("path")

function generatePassword(keyword) {
    console.log(keyword)
    return keyword + '-' + Math.random().toString(36).substring(2,12)
}

function createWindow () {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        resizable: false,
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })

    win.loadFile('html/passwordGen.html')
}

app.whenReady().then(() => {
    // ipcMain.handle("generatePassword", generatePassword)
    // console.log(generatePassword('stink')) // works
    ipcMain.handle('generatePassword', (_event, keyword) => {
        console.log(keyword); // Testing
        return generatePassword(keyword);
    });
    createWindow()
}).catch(error => {
    console.log(error) // log error to console
    app.quit() // quit the app
})

preload.js

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

contextBridge.exposeInMainWorld('main', {
    genPW: (keyword) => {
        return ipcRenderer.invoke("generatePassword", keyword)
    }
})

render.js

async function testClick () {
    const pw_root = document.getElementById("keyword")
    const pw_label = document.querySelector("#password")
    pw_label.innerText = await window.main.genPW(pw_root.value)
    // window.main.genPW(pw_root.value).then(res => {pw_label.innerText = res})
    // ^^^ works as well if async/await removed
}

Advertisement

Answer

You were really close. Using Elctron’s invoke method is the correct approach.

Within your main.js file, Electron’s IPC handle signature contains the channel and listener arguments. In your code you are calling your generatePassword() function in place of the listener argument. Instead, it should be (event, ...args). In your specific case (event, keyword). See ipcMain.handle(channel, listener) for more information.

Additionally, within your preload.js script, all you need to do is add a return statement in front of your ipcRenderer.invoke method.

Finally, there is no need to use async on your testClick() function. Electron’s invoke handles all of this.


main.js (main process)

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

const nodePath = require('path');

// Prevent garbage collection
let window;

function createWindow() {
    window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        resizable: false,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

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

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

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

// ---

function generatePassword(keyword) {
    console.log(keyword)
    return keyword + '-' + Math.random().toString(36).substring(2,12)
}

electronIpcMain.handle('generatePassword', (event, keyword) => {
    console.log(keyword); // Testing
    return generatePassword(keyword);
});


preload.js (main process)

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

contextBridge.exposeInMainWorld(
    'main', {
        genPW: (keyword) => {
            return ipcRenderer.invoke('generatePassword', keyword);
        }
    });

For sake of simplicity, I have included your render.js script within <script> tags below the closing </body> tag.

index.html (render process)

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

    <body>
        <div>
            <label for="password">Password:</label>
            <input type="text" id="password">

            <input type="button" id="submit" value="Submit">
        </div>

        <div>
            <label for="generated-password">Generated Password:</label>
            <input type="text" id="generated-password" disabled>
        </div>
    </body>

    <script>
        document.getElementById('submit').addEventListener('click', () => {
            window.main.genPW(document.getElementById('password').value)
                .then((generatedPassword) => {
                    document.getElementById('generated-password').value = generatedPassword;
                })
        })
    </script>
</html>
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement