开发者问题收集

从前端 JavaScript 文件中使用 electron ipcRenderer

2020-06-17
18032

我正在学习使用 Electron,在尝试让我的应用程序与前端通信时,我意识到我需要使用 ipcRenderer 来获取对 DOM 元素的引用,然后将该信息传递给 ipcMain

我尝试遵循 此处 此处 建议的大部分建议,但这两个示例都使用了 require('electron').ipcMain ,每当我尝试将与前端交互的脚本包含到我的 HTML 中时,由于 Uncaught ReferenceError: require is not defined ,什么也不会发生。我已经搜索了几个小时,但一直没有找到解决方案 - 所以显然我做错了什么。

我的 main.js 非常简单,我只需创建我的窗口,然后创建一个 ipc 侦听器,如下所示:

const { app, BrowserWindow } = require("electron");
const ipc = require('electron').ipcMain;

function createWindow() {
    const window = new BrowserWindow({
        transparent: true,
        frame: false,
        resizable: false,
        center: true,
        width: 410,
        height: 550,
    });
    window.loadFile("index.html");
}

app.whenReady().then(createWindow);

ipc.on('invokeAction', (event, data) => {
    var result = "test result!";
    event.sender.send('actionReply', result);
})

在我希望用来操作 DOM 的文件中,我尝试获取元素 ID,然后添加事件侦听器,如下所示:

const ipc = require('electron').ipcRenderer;
const helper = require("./api");


var authenticate_button = ipcRenderer.getElementById("authenticate-button");

var authButton = document.getElementById("authenticate-button");
authButton.addEventListener("click", () => {
    ipc.once('actionReply', (event, response) => {
        console.log("Hello world!");
    })
    ipc.send('invokeAction');
});

function onAuthenticateClick() {
    helper.authenticateLogin(api_public, api_secret, access_public, access_secret);
}

最后,我的 HTML 仅包含一个我希望将事件侦听器附加到的按钮:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Project Test</title>
    <link rel="stylesheet" href="style.css" />
</head>

<body>
    <div class="main-container">
        <button id="authenticate-button" type="submit" onclick="">Authenticate</button>
        <p id="status-label">Not Authenticated</p>
    </div>

    <script src="script.js"></script>
</body>
</html>

如果有人能帮助我指出如何使这个基本功能正常工作的正确方向,那将非常有帮助!

2个回答

正如 AlekseyHoffman 所提到的,您无法在前端 js 文件中访问 ipcRenderer 的原因是因为您将 nodeIntegration 设置为 false。也就是说,现在默认将其设置为 false 是有原因的; 这会让您的应用的安全性大大降低。

我建议一种替代方法:不要尝试通过将 nodeIntegration 设置为 true 直接从前端 js 访问 ipcRenderer ,而是从 preload.js 访问它。在 preload.js 中,您可以有选择地公开您想要在前端访问的 ipcMain 函数(来自您的 main.js 文件)(包括那些可以从 main.js 发回数据的函数),然后在那里通过 ipcRenderer 调用它们。在您的前端 js 中,您可以访问公开这些函数的 preload.js 对象;然后,preload.js 将通过 ipcRenderer 调用那些 main.js 函数,并将数据返回给调用它的前端 js。

这是一个简单但 完全可以正常工作的示例 (这些文件足以构建一个在 main.js 和前端之间具有双向通信的电子应用程序。在此示例中,以下所有文件都在同一个目录中。):

ma​​in.js

// boilerplate code for electron..
const {
    app,
    BrowserWindow,
    ipcMain,
    contextBridge
} = require("electron");
const path = require("path");  
let win;

/**
 * make the electron window, and make preload.js accessible to the js
 * running inside it (this will allow you to communicate with main.js
 * from the frontend).
 */
async function createWindow() {

    // Create the browser window.
    win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: false, // is default value after Electron v5
            contextIsolation: true, // protect against prototype pollution
            enableRemoteModule: false,
            preload: path.join(__dirname, "./preload.js") // path to your preload.js file
        }
    });

    // Load app
    win.loadFile(path.join(__dirname, "index.html"));
}
app.on("ready", createWindow);

// end boilerplate code... now on to your stuff

/** 
 * FUNCTION YOU WANT ACCESS TO ON THE FRONTEND
 */
ipcMain.handle('myfunc', async (event, arg) => {
  return new Promise(function(resolve, reject) {
    // do stuff
    if (true) {
        resolve("this worked!");
    } else {
        reject("this didn't work!");
    }
  });  
});

注意 ,我使用的是 ipcMain.handle 的示例,因为它允许双向通信并返回一个 Promise 对象 - 即,当您通过 preload.js 从前端访问此函数时,您可以使用里面的数据取回该 Promise

preload.js:

// boilerplate code for electron...
const {
    contextBridge,
    ipcRenderer
} = require("electron");

// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
        const element = document.getElementById(selector)
        if (element) element.innerText = text
    }

    for (const type of ['chrome', 'node', 'electron']) {
        replaceText(`${type}-version`, process.versions[type])
    }
})

// end boilerplate code, on to your stuff..

/**
 * HERE YOU WILL EXPOSE YOUR 'myfunc' FROM main.js
 * TO THE FRONTEND.
 * (remember in main.js, you're putting preload.js
 * in the electron window? your frontend js will be able
 * to access this stuff as a result.
 */
contextBridge.exposeInMainWorld(
    "api", {
        invoke: (channel, data) => {
            let validChannels = ["myfunc"]; // list of ipcMain.handle channels you want access in frontend to
            if (validChannels.includes(channel)) {
                // ipcRenderer.invoke accesses ipcMain.handle channels like 'myfunc'
                // make sure to include this return statement or you won't get your Promise back
                return ipcRenderer.invoke(channel, data); 
            }
        },
    }
);

渲染器进程(即您的前端 js 文件 - 我将其称为 frontend.js):

// call your main.js function here
console.log("I'm going to call main.js's 'myfunc'");
window.api.invoke('myfunc', [1,2,3])
    .then(function(res) {
        console.log(res); // will print "This worked!" to the browser console
    })
    .catch(function(err) {
        console.error(err); // will print "This didn't work!" to the browser console.
    });

index.html

<!DOCTYPE html>
<html>

<head>
    <title>My Electron App</title>
</head>

<body>
    <h1>Hello Beautiful World</h1>
    <script src="frontend.js"></script> <!-- load your frontend script -->
</body>
</html>

package.json

{
  "name": "myapp",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  }
}

上述文件足以使 electron 应用程序完全正常运行,并实现 main.js 和前端 js 之间的通信。将它们全部放在一个目录中,名称分别为 main.jspreload.jsfrontend.jsindex.htmlpackage.json ,然后使用 npm start 启动您的 electron 应用程序。请注意,在此示例中,我将所有文件存储在同一目录中;请确保将这些路径更改为它们存储在系统上的任何位置。

有关更多信息和示例,请参阅以下链接:

Electron 关于进程间通信的文档

关于需要 IPC 的原因概述以及将 nodeintegration 设置为 true 的安全问题

bikz
2022-09-20

由于您未在窗口上启用 nodeIntegration ,因此未定义 require 。请在窗口配置中将其设置为 true:

const window = new BrowserWindow({
  transparent: true,
  frame: false,
  resizable: false,
  center: true,
  width: 410,
  height: 550,
  webPreferences: {
    nodeIntegration: true
  }
})
AlekseyHoffman
2020-06-17