Electron / NodeJS:如何从IpcMain向IpcRenderer发送异步消息

agxfikkp  于 2023-03-27  发布在  Electron
关注(0)|答案(1)|浏览(285)

正如标题所述,我想知道如何将来自Python进程的消息间歇地发送到我的渲染器进程。我已经尝试了很多技术,但我似乎错过了我认为的要点。
首先,我的js文件,我在其中对预加载中注册的API进行API调用(下一个片段)。我发起调用并等待多条消息返回。

console.log('Start installation!');
window.mainAPI.startInstallation('hello');

window.addEventListener("message", (event) => {
    console.log(event);
});

Preload. js/ IpcRenderer。它涉及最后一个“端点”或通道。

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

contextBridge.exposeInMainWorld('mainAPI', {

  openConnection: (data) => ipcRenderer.invoke('openConnection', data).then((result) => {
    window.postMessage(result)
  }),

  saveConfig: (data) => ipcRenderer.invoke('saveConfig', data).then((result) => {
    window.postMessage(result)
  }),

  saveModuleSelection: (data) => ipcRenderer.invoke('saveModuleSelection', data).then((result) => {
    window.postMessage(result)
  }),

  startInstallation: (data) => ipcRenderer.invoke('startInstallation', data).then((result) => {
    window.postMessage(result)
  })

});

Main.js(为清晰起见,粘贴了完整内容):

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const { PythonShell } = require('python-shell')
const fs = require('fs');

function startPython(event, data) {
    let pyshell = new PythonShell('install.py', {mode: 'text'});
    
    pyshell.send(JSON.stringify(data));

    pyshell.on('message', function(message) {
        // HOW TO SEND MESSAGES TO EVENT LISTENER FROM HERE
        console.log('Sending: ' + message);
        event.sender.send('startInstallation', message); // doesnt work
    });

    pyshell.end(function(err, code, signal) {
      var res = {
        'error': []
      };
      if (err) {
        res.error.push(err);
        throw err;
      }
      res.code = code;
      res.signal = signal;

      console.log('The exit code was: ' + code);
      console.log('The exit signal was: ' + signal);
      console.log('finished');

      return JSON.stringify(res)
    });
}

function write_env_vars(filename, string) {
  try {
    fs.writeFileSync(`./backend/${filename}.env`, string, 'utf-8');
    return true
  } catch(e) {
    console.log(e);
    return false
  }
}

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

const createWindow = () => {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
        preload: path.join(__dirname, 'preload.js'),
    },
  })

  win.loadFile('inethi/front/index.html')
}

var credentials, config, modules;

app.whenReady().then(() => {

  ipcMain.handle('openConnection', async (event, args) => {
    await sleep(200);
    // add call to test_server_connection.py here.
    credentials = JSON.parse(args);
    console.log(credentials);
    return write_env_vars('credentials', `CRED_IP_ADDRESS=${credentials.ip}\nCRED_USERNAME=${credentials.username}\nCRED_PASSWORD=${credentials.password}`);
  })

  ipcMain.handle('saveConfig', async (event, args) => {
    await sleep(200);
    config = JSON.parse(args);
    console.log(config);
    return write_env_vars('config', `CONF_STORAGE_PATH=${config.storagepath}\nCONF_DOMAIN_NAME=${config.domainname}\nCONF_HTTPS=${config.https}\nCONF_MASTER_PASSWORD=${config.master}\n`);
  })

  ipcMain.handle('saveModuleSelection', async (event, args) => {
    await sleep(200);
    modules = JSON.parse(args);
    console.log(modules);
    return write_env_vars('modules', `MODS_DOCKER=${modules.docker}\nMODS_TRAEFIK=${modules.traefik}\nMODS_NGINX=${modules.nginx}\nMODS_KEYCLOAK=${modules.keycloak}\nMODS_NEXTCLOUD=${modules.nextcloud}\nMODS_JELLYFIN=${modules.jellyfin}\nMODS_WORDPRESS=${modules.wordpress}\nMODS_PEERTUBE=${modules.peertube}\nMODS_PAUM=${modules.paum}\nMODS_RADIUSDESK=${modules.radiusdesk}\n`);
  })

  ipcMain.handle('startInstallation', async (event, args) => {
    console.log('Starting installation');
    const data = {
      'credentials': credentials,
      'config': config,
      'modules': modules
    }
    await sleep(3000);
    const res = startPython(event, data);
    console.log('Installation done');
    console.log(res);
    return true
  })

  createWindow()
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

所以问题是前三个端点或通道工作正常。我返回true,eventListener捕获消息。但是对于最后一个通道,'startInstallation'通道,它不工作,我怀疑这和(异步--从来没有真正理解过)python脚本。我的目标是1)发送间歇性的(打印)消息(在main.js文件的顶部)在pyshell.on(...)部分,并发送一个最终的消息,如果脚本运行成功。
任何帮助将不胜感激!

idfiyjo8

idfiyjo81#

在ContextBridge中,似乎没有公开接收事件或处理IPCRenderer事件的方法。
如果您在contextBridge代码中添加类似的内容

onPythonEvent: (channel, func) => {
            let validChannels = ["fromPython"];
            if (validChannels.includes(channel)) {
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }

然后在你的客户端代码中,你应该能够像这样使用它:

window.mainAPI.onPythonEvent("fromPython",(val)=>{console.log(val)});

然后在你的startPythonMethod中,你可以像这样触发它,将值直接发送到渲染器窗口:

pyshell.on('message', function(message) {
        console.log('Sending: ' + message);
        win.webContents.send('fromPython', data)
    });

您可能需要重构为首先创建browserWindow,然后注册ipc处理程序。您将窗口作为arg传递给您拥有的startPython方法。

const win = createWindow();
ipcMain.handle('startInstallation', async (event, args) => {
    //...
    const res = startPython(win);
   // ...
  })

相关问题