电子require()未定义

Mar*_*van 64 html javascript node.js electron

我正在为自己的目的创建Electron应用程序。我的问题是,当我在HTML页面中使用节点函数时,它引发以下错误:

未定义'require()'。

有没有办法在我所有的HTML页面中使用Node功能?如果可能的话,请给我举个例子,或者提供一个链接。以下是我要在HTML页面中尝试使用的变量:

  var app = require('electron').remote; 
  var dialog = app.dialog;
  var fs = require('fs');
Run Code Online (Sandbox Code Playgroud)

这些是我在Electron的所有HTML窗口中使用的值。

Sat*_*esh 164

从版本5开始,默认值nodeIntegration从true更改为false。您可以在创建浏览器窗口时启用它:

app.on('ready', () => {
    mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });
});
Run Code Online (Sandbox Code Playgroud)

  • @PauloHenrique`nodeIntegration:true`仅在您在应用程序上执行一些不受信任的远程代码时才有安全风险。例如,假设您的应用程序打开了第三方网页。这将带来安全风险,因为第三方网页将有权访问节点运行时,并且可以在用户的​​文件系统上运行某些恶意代码。在这种情况下,设置`nodeIntegration:false`是有意义的。如果您的应用程序不显示任何远程内容,或仅显示受信任的内容,则可以设置“ nodeIntegration:true”。 (16认同)
  • @PauloHenrique - 如果您想遵循并创建一个安全的应用程序(遵守安全最佳实践),请按照我在此评论中描述的设置进行操作:https://github.com/electron/electron/issues/9920#issuecomment -575839738 (5认同)
  • 我已经检查了电子 12.0 版本的文档 https://www.electronjs.org/docs/writing-changes 并获取以前的行为 contextIssolation 应该是 false 谢谢 (5认同)
  • 如果没有 `contextIsolation: false` 行,您将无法访问 `require` (5认同)
  • 非常感谢...我的require()不起作用,直到我添加了这个 (2认同)
  • 在 10.1.15 上不工作,仍然收到安全警告。 (2认同)

Zac*_*Zac 76

我希望这个答案得到一些关注,因为这里的大部分答案都会在您的电子应用程序中留下很大的安全漏洞。事实上,这个答案本质上就是你应该require()在你的电子应用程序中使用的东西。(只有一个新的电子 API 使它在 v7 中更简洁一些)。

我使用最新的电子 API 在 github 中写了一个详细的解释/解决方案require(),但我将在这里简要解释为什么你应该遵循使用预加载脚本、contextBridge 和 ipc 的方法。

问题

Electron 应用程序很棒,因为我们可以使用 node,但这种能力是一把双刃剑。如果我们不小心,我们会通过我们的应用程序授予某人访问节点的权限,而对于节点,坏人可能会损坏您的机器或删除您的操作系统文件(除其他外,我想)。

正如@raddevus 在评论中提出的,这在加载远程内容时是必要的。如果您的电子应用程序完全离线/本地,那么您可能只需打开即可。但是,我仍然会选择继续为使用您的应用程序的意外/恶意用户提供保护,并防止任何可能安装在您机器上的恶意软件与您的电子应用程序交互并使用攻击向量(非常罕见) ,但可能会发生)!nodeIntegration:truenodeIntegration:falsenodeIntegration:true

问题是什么

当您(以下任何一项)时,就会出现此问题:

  1. nodeIntegration:true启用
  2. 使用remote模块

所有这些问题都使您可以从渲染器进程不间断地访问节点。如果您的渲染器进程被劫持,您可以认为一切都已丢失。

我们的解决方案是什么

解决方案是不让渲染器直接访问节点(即require()),而是让我们的电子主进程访问require,并且在我们的渲染器进程需要使用的任何时候require,将请求编组到主进程。

在最新版本(7+)Electron 中的工作方式是在渲染器端设置ipcRenderer绑定,在主端设置ipcMain绑定。在 ipcMain 绑定中,我们设置了使用模块的侦听器方法require()。这很好,因为我们的主进程可以require随心所欲。

我们使用contextBridge将 ipcRenderer 绑定传递给我们的应用程序代码(使用),因此当我们的应用程序需要使用requiremain 中的d 模块时,它通过 IPC(进程间通信)发送消息并运行主进程一些代码,然后我们发送一条带有结果的消息。

粗略地说,这就是你想要做的。

主文件

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

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

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, // turn off remote
      preload: path.join(__dirname, "preload.js") // use a preload script
    }
  });

  // Load app
  win.loadFile(path.join(__dirname, "dist/index.html"));

  // rest of code..
}

app.on("ready", createWindow);

ipcMain.on("toMain", (event, args) => {
  fs.readFile("path/to/file", (error, data) => {
    // Do something with file contents

    // Send result back to renderer process
    win.webContents.send("fromMain", responseObj);
  });
});
Run Code Online (Sandbox Code Playgroud)

预加载.js

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

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld(
    "api", {
        send: (channel, data) => {
            // whitelist channels
            let validChannels = ["toMain"];
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, data);
            }
        },
        receive: (channel, func) => {
            let validChannels = ["fromMain"];
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender` 
                ipcRenderer.on(channel, (event, ...args) => func(...args));
            }
        }
    }
);
Run Code Online (Sandbox Code Playgroud)

索引.html

<!doctype html>
<html lang="en-US">
<head>
    <meta charset="utf-8"/>
    <title>Title</title>
</head>
<body>
    <script>
        window.api.receive("fromMain", (data) => {
            console.log(`Received ${data} from main process`);
        });
        window.api.send("toMain", "some data");
    </script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

免责声明

我是secure-electron-template构建电子应用程序的安全模板的作者。我关心这个话题,并且已经为此工作了几个星期(此时此刻)。

  • 我不想成为一个抱怨的保姆什么的,但事实上,你可以浏览官方的快速入门指南,最终得到一个“可以工作”但绝对没有准备好扩展的应用程序,这绝对是垃圾。考虑到进展及其速度,我发现的 99% 的答案都是**错误**、**危险**和**根本不起作用**。一次非常令人沮丧的经历。至少感谢您的回复。 (4认同)
  • @raddevus 谢谢您,我正在更新我的帖子以反映您的评论。 (2认同)
  • 我可能有点慢,但我发现这个答案令人困惑。事实上,[电子文档中有关上下文隔离的页面](https://www. Electronjs.org/docs/tutorial/context-isolation) 对此进行了更好的解释,并指出 @Mateen Ulhaq 中使用的稍微简单的方法答案仍然不理想,并且默认情况下在 Electron 12 中不起作用。 (2认同)

小智 11

出于安全原因,您应该保留nodeIntegration: false并使用预加载脚本,以通过window变量将需要的Node / Electron API暴露给渲染器进程(视图)。从电子文档

预加载脚本仍然可以访问require和其他Node.js功能


main.js

const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: path.join(app.getAppPath(), 'preload.js')
  }
})
Run Code Online (Sandbox Code Playgroud)

preload.js

const { remote } = require('electron');

let currWindow = remote.BrowserWindow.getFocusedWindow();

window.closeCurrentWindow = function(){
  currWindow.close();
}
Run Code Online (Sandbox Code Playgroud)

renderer.js

let closebtn = document.getElementById('closebtn');

closebtn.addEventListener('click', (e) => {
  e.preventDefault();
  window.closeCurrentWindow();
});
Run Code Online (Sandbox Code Playgroud)

  • 如果“require”不可用,为什么官方[文档](https://www.electronjs.org/docs/api/remote)在渲染器中使用“require()”? (23认同)
  • 如果你和我一样是电子新手:渲染器文件通常以经典方式包含在 html 中:`&lt;script src="./renderer.js"&gt;&lt;/script&gt;` (5认同)
  • 这个话题说起来就很奇怪,应该只是从头开始讲预加载,安全性有什么好大惊小怪的。我们只是尝试遵循教程,如果不需要,那么怎么办,只需在文档中说明即可。顺便说一句,我喜欢这个答案。 (4认同)
  • 自 2019 年以来,您链接的文档已被标记为“已弃用”。(他们应该使用亮红色横幅,而不是这条灰色的引用消息。) (2认同)
  • 在较新版本的Electron中,您需要在创建windo时添加enableRemoteModule: true,以使远程不被定义 (2认同)

Leg*_*ndo 9

首先,@Sathiraumesh 解决方案让您的电子应用程序面临巨大的安全问题。想象一下,您的应用程序正在向 中添加一些额外的功能messenger.com,例如,当您有未读消息时,工具栏的图标会发生变化或闪烁。所以在你的main.js文件中,你像这样创建新的 BrowserWindow(注意我故意拼错了 messenger.com):

app.on('ready', () => {
    const mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true
        }
    });
    mainWindow.loadURL(`https://messengre.com`);
});
Run Code Online (Sandbox Code Playgroud)

如果messengre.com是一个恶意网站,想要损害您的计算机怎么办。如果您设置nodeIntegration: true此站点可以访问您的本地文件系统并可以执行以下操作:

require('child_process').exec('rm -r ~/');
Run Code Online (Sandbox Code Playgroud)

你的主目录不见了。

解决方案
只公开你需要的东西,而不是一切。这是通过使用require语句预加载 javascript 代码来实现的。

// main.js
app.on('ready', () => {
    const mainWindow = new BrowserWindow({
        webPreferences: {
            preload: `${__dirname}/preload.js`
        }
    });
    mainWindow.loadURL(`https://messengre.com`);
});
Run Code Online (Sandbox Code Playgroud)
// preload.js
window.ipcRenderer = require('electron').ipcRenderer;
Run Code Online (Sandbox Code Playgroud)
// index.html
<script>
    window.ipcRenderer.send('channel', data);
</script>
Run Code Online (Sandbox Code Playgroud)

现在可怕的messengre.com无法删除您的整个文件系统。


Roy*_*ong 8

您正在使用nodeIntegration: falseBrowserWindow初始化时吗?如果是这样,请将其设置为true(默认值为true)。

并在HTML中包含您的外部脚本,而不是这样<script> src="./index.js" </script>

<script>
   require('./index.js')
</script>
Run Code Online (Sandbox Code Playgroud)


Hai*_*han 7

看起来 Electron 的安全性是这样演变的(来源)。

Electron 1 nodeIntegration默认为 true

Renderer 拥有对 Node API 的完全访问权——如果 Renderer 加载远程代码,则存在巨大的安全风险。

Electron 5 nodeIntegration默认为 false

当设置为 false 时,预加载脚本用于向渲染器公开特定的 API。(无论nodeIntegration的值如何,预加载脚本始终可以访问节点 API )

//preload.js
window.api = {
    deleteFile: f => require('fs').unlink(f)
}
Run Code Online (Sandbox Code Playgroud)

Electron 5 contextIsolation默认为 true(实际上在 Electron 11 中仍然默认为 false)

这会导致预加载脚本在单独的上下文中运行。你不能再这样做了window.api = ...。你现在必须做:

//preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => require('fs').unlink(f)
})
Run Code Online (Sandbox Code Playgroud)

require()沙盒渲染器中的Electron 6 ing 节点内置程序不再隐式加载远程版本

如果 Renderer 已sandbox设置为 true,则必须执行以下操作:

//preload.js
const { contextBridge, remote } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => remote.require('fs').unlink(f)
})
Run Code Online (Sandbox Code Playgroud)

Electron 10 enableRemoteModule默认为 false(远程模块在 Electron 12 中已弃用)

remote当您需要从沙盒渲染器访问 Node API 时使用该模块(如上例所示);或者当您需要访问仅适用于主进程(例如对话框、菜单)的 Electron API 时。如果没有remote,您将需要编写如下所示的显式 IPC 处理程序。

//preload.js
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('api', {
    displayMessage: text => ipcRenderer.invoke("displayMessage", text)
})

//main.js
const { ipcMain, dialog } = require('electron')

ipcMain.handle("displayMessage", text => dialog.showMessageBox(text))
Run Code Online (Sandbox Code Playgroud)

Electron 10 弃用 nodeIntegration标志(在 Electron 12 中删除)

推荐

始终设置{nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}

为了最大的安全性,设置{sandbox: true}. 您的预加载脚本将不得不使用 IPC 调用 Main 进程来完成所有工作

如果sandbox为 false,则您的预加载脚本可以直接访问 Node API,如require('fs').readFile. 只要你不这样做,你就是安全的:

//bad
contextBridge.exposeInMainWorld('api', {
    readFile: require('fs').readFile
})
Run Code Online (Sandbox Code Playgroud)


Jos*_*ang 5

由于我正在遵循的教程,我想做的就是在我的 html 页面中需要一个 js 文件。但是,我打算使用远程模块,因此安全性至关重要。我修改了迈克尔的答案,所以我发帖,纯粹是为了那些像我一样花了几个小时寻找“要求”的安全替代方案的人。如果代码不正确,请随时指出。

main.js

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

const path=require('path');
const url=require('url');

let win;

function createWindow(){
    win=new BrowserWindow({
        webPreferences:{
            contextIsolation: true,
            preload: path.join(__dirname, "preload.js")
        }
    });
    win.loadURL(url.format({
        pathname: path.join(__dirname, 'index.html'),
        protocol: 'file',
        slashes: true
    }));

    win.on('close', function(){
        win=null
    });
}

app.on('ready', createWindow);
Run Code Online (Sandbox Code Playgroud)

预加载.js

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

contextBridge.exposeInMainWorld(
    "api", {
        loadscript(filename){
            require(filename);
        }
    }
);
Run Code Online (Sandbox Code Playgroud)

索引.html

<!DOCTYPE html>
<html>
    <head>
        <title>Hello World App</title>
    </head>
    <body>
        <h1>Hello World</h1>
        <button id="btn">Click</button>
    </body>
    <script>
        window.api.loadscript('./index.js');
    </script>
</html>
Run Code Online (Sandbox Code Playgroud)

索引.js

const btn = document.getElementById('btn');
btn.addEventListener('click', function(){
    console.log('button clicked');
});
Run Code Online (Sandbox Code Playgroud)

我特别想知道这是否仍然存在安全风险。谢谢。


小智 5

如果您只是不关心任何安全问题,并且希望JavaScript 在浏览器窗口上正确解释 require,那么可以在 main.js 代码上添加一个额外的标志

webPreferences: {
            nodeIntegration: true,
            nodeIntegrationInWorker: true,
            nodeIntegrationInSubFrames: true,
            enableRemoteModule: true,
            contextIsolation: false //required flag
        }
        
//rest of the code...
Run Code Online (Sandbox Code Playgroud)