How to pass ipcRenderer.invoke handler answer to electron renderer process using preload.js

7.3k views Asked by At

I'm writing a CRA + Electron app and I need to use ipcRenderer.invoke for inter-processes communication. I was able to make ipcRenderer.send and ipcRender.on work in contextIsolation mode, but no luck with ipcRender.invoke. For some reason I don't understand, I can't get the handler response back to renderer in any form (promise or value), all I get is "undefined" value.

preload.js

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

 // whitelist channels
const validChannels = ["toMain", "fromMain", "invokeMain"]
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "apiKey", {
        sendApi: (channel, ...args) => {
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, ...args);
            }
        },
        onApi: (channel, func) => {
            if (validChannels.includes(channel)) {
                // Deliberately strip event in func as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        },
        invokeApi: (channel, ...args) => {
            if ( validChannels.includes(channel) ) {
                ipcRenderer.invoke(channel, ...args).then( (value) =>{
                    console.log('apiKey.invokeApi:', value);
                    return value;
                });
            };
        },
    }
);

main.js

ipcMain.handle('invokeMain', async (event, ...arg) => {
  console.log ('invokeMain received arg:', ...arg);
  let response = 'pong';  
  //return new Promise( (resolve) => setTimeout(resolve(response), 2000), (reject) => reject() );
  let prom = new Promise( (resolve) => setTimeout(resolve(response), 2000), (reject) => reject() );
  let result = await prom;
  console.log ('invokeMain response to Renderer:', prom);
  return prom;
//return result;  
})

Main.js output

Renderer.js

//Invoke Main
let invokeMessage = "ping";
async function send (invokeMessage) {
    console.log('ipcMain.handle response:', window.apiKey.invokeApi('invokeMain', invokeMessage));
//    let response = await window.apiKey.invokeApi('invokeMain', invokeMessage)
//    console.log('ipcMain.handle response:', response);
//    response.then((value) =>{
//        console.log('ipcMain.handle response:', value);
//    });
};
send(invokeMessage);

Renderer.js output

Can anybody point me what I'm doing wrong please ? thanks in advance

1

There are 1 answers

0
groCervo On

I got it working by returning the promise of the invoke method from the preload instead of the value.

In my preload:

invoke: (channel: string, data: any) => {
    // whitelist channels
    let validChannels = ["myValidChannel"];
    if (validChannels.includes(channel)) {
        return ipcRenderer.invoke(channel, data);
    }
},

And handle it directly in the renderer :

try {
    const result = await window.myIpcRenderer.invoke("myValidChannel", data)
} catch (err) {
    console.log(err)
}