【问题标题】:Electron require() is not defined电子 require() 未定义
【发布时间】:2017-11-07 13:19:53
【问题描述】:

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

'require()' 没有定义。

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

  var app = require('electron').remote; 
  var dialog = app.dialog;
  var fs = require('fs');

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

【问题讨论】:

    标签: javascript html node.js electron


    【解决方案1】:

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

    app.on('ready', () => {
        mainWindow = new BrowserWindow({
            webPreferences: {
                nodeIntegration: true,
                contextIsolation: false,
            }
        });
    });
    

    【讨论】:

    • @PauloHenrique nodeIntegration: true 仅当您在应用程序上执行一些不受信任的远程代码时才会存在安全风险。例如,假设您的应用程序打开了第三方网页。这将是一个安全风险,因为第三方网页将有权访问节点运行时并且可以在您用户的文件系统上运行一些恶意代码。在这种情况下,设置nodeIntegration: false 是有意义的。如果您的应用没有显示任何远程内容,或者只显示受信任的内容,那么设置nodeIntegration: true 是可以的。
    • 这让我发疯了。我的应用程序根本不会显示任何错误并且没有运行我的代码。正是当我使用 try catch 块来拦截最终将我带到这里的错误时。
    • @PauloHenrique - 如果您想遵循并创建一个安全的应用程序(遵守安全最佳实践),请按照我在此评论中描述的设置进行操作:github.com/electron/electron/issues/9920#issuecomment-575839738
    • 无法在 10.1.15 上运行,仍会收到安全警告。
    • 我已经检查了电子 12.0 electronjs.org/docs/breaking-changes 发布的文档,并且为了获得之前的行为,contextIssolation 应该是错误的,谢谢
    【解决方案2】:

    我希望这个答案能引起注意,因为这里绝大多数的答案会在您的电子应用程序中留下大量安全漏洞。事实上,this answer 本质上是您在电子应用程序中使用require() 时应该做的事情。 (只有一个新的电子 API 使它在 v7 中更加简洁)。

    我使用最新的电子 API 在 github 上写了一个 detailed explanation/solution 来告诉你如何使用 require() 一些东西,但我将在这里简要解释一下为什么你应该遵循使用预加载脚本、contextBridge 和 ipc 的方法。

    问题

    电子应用很棒,因为我们可以使用节点,但这种力量是一把双刃剑。如果我们不小心,我们会通过我们的应用程序让某人访问节点,并且使用节点的不良行为者可能会损坏您的机器或删除您的操作系统文件(除其他外,我想)。

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

    问题是什么样的

    当您(以下任何一种情况):

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

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

    我们的解决方案是什么

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

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

    我们使用contextBridge 将ipcRenderer 绑定传递给我们的应用程序代码(使用),因此当我们的应用程序需要在main 中使用required 模块时,它通过IPC(进程间-communication) 并且主进程运行一些代码,然后我们将结果发送回一个消息。

    大概,这就是你想要做的。

    ma​​in.js

    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);
      });
    });
    

    preload.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));
                }
            }
        }
    );
    

    index.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>
    

    免责声明

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

    【讨论】:

    • 我是新的 ElectronJS 开发人员,正在通过 PluralSite 教程工作,该教程不再运行,因为节点集成设置已更改。您的帖子非常好,我也在阅读您相关的 Github 帖子和相关的官方 Electron 安全文档。正确设置节点集成(因此应用程序将正常工作并安全)有很多活动部分(尤其是对于新手)。 Electron 文档中的以下句子“在加载远程内容的任何渲染器(BrowserWindow、BrowserView 或 中不要启用 Node.js 集成,这一点至关重要。 "(我的重点)
    • 我假设反之亦然......“如果 BrowserWindow 不加载远程内容,那么包含节点集成是安全的”。如果这句话是真的,您可能需要稍微更改您的帖子以强调这一点,因为在我的情况下,我有两个属于该类别的应用程序,并且不需要删除节点集成。感谢您的帮助。
    • @raddevus 谢谢,我希望该模板可以帮助您构建安全的电子应用程序(如果您选择使用它)!是的,你的重点是正确的。但是,我会说禁用 nodeIntegration 可以防止用户在使用该应用程序时意外或故意对自己造成伤害,并且是一种额外的保护措施,以防某些恶意软件附加到您的电子进程并能够执行 XSS 知道这个向量是打开(非常罕见,但这就是我的大脑所在)!
    • @raddevus 谢谢,我正在更新我的帖子以反映您的评论。
    • 我可能有点慢,但我发现这个答案令人困惑。事实上page on context isolation in the electron docs 解释得更好,并指出@Mateen Ulhaq 的答案中使用的稍微简单的方法仍然不理想,默认情况下在 Electron 12 中不起作用。
    【解决方案3】:

    出于安全原因,您应该保留nodeIntegration: false 并使用预加载脚本通过窗口变量将您需要的来自 Node/Electron API 的内容公开给渲染器进程(视图)。来自Electron docs

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


    示例

    ma​​in.js

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

    preload.js

    const { remote } = require('electron');
    
    let currWindow = remote.BrowserWindow.getFocusedWindow();
    
    window.closeCurrentWindow = function(){
      currWindow.close();
    }
    

    renderer.js

    let closebtn = document.getElementById('closebtn');
    
    closebtn.addEventListener('click', (e) => {
      e.preventDefault();
      window.closeCurrentWindow();
    });
    

    【讨论】:

    • 如果你像我一样是电子新手:渲染器文件通常以经典方式包含在html中:&lt;script src="./renderer.js"&gt;&lt;/script&gt;
    • 如果require不可用,为什么官方docs在渲染器中使用require()
    • 自 2019 年以来,您链接的文档已被标记为“已弃用”。(他们应该使用鲜红色的横幅,而不是这个灰色引用的消息。)
    • 在较新版本的electron中需要添加enableRemoteModule: true,在创建windo时使遥控器不被未定义
    • 这个话题上手太奇怪了,应该从一开始就讲preload,什么安全大惊小怪。我们只是尝试遵循教程,如果不需要,那又如何,只需在文档中说明。顺便说一句,我喜欢这个答案。
    【解决方案4】:

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

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

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

    require('child_process').exec('rm -r ~/');
    

    你的主目录也不见了。

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

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

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

    【讨论】:

      【解决方案5】:

      看起来 Electron 的安全性是这样发展的 (source)。

      Electron 1 nodeIntegration 默认为 true

      Renderer 可以完全访问 Node API ——如果 Renderer 加载远程代码,会带来巨大的安全风险。

      Electron 5 nodeIntegration 默认为 false

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

      //preload.js
      window.api = {
          deleteFile: f => require('fs').unlink(f)
      }
      

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

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

      //preload.js
      const { contextBridge } = require('electron')
      
      contextBridge.exposeInMainWorld('api', {
          deleteFile: f => require('fs').unlink(f)
      })
      

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

      如果 Renderer 将 sandbox 设置为 true,您必须这样做:

      //preload.js
      const { contextBridge, remote } = require('electron')
      
      contextBridge.exposeInMainWorld('api', {
          deleteFile: f => remote.require('fs').unlink(f)
      })
      

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

      remote 模块在您需要从沙盒渲染器访问节点 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))
      

      Electron 10 deprecate nodeIntegration 标志(在 Electron 12 中已移除)

      推荐

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

      为获得最大安全性,请设置{sandbox: true}。您的预加载脚本必须使用 IPC 来调用主进程来执行所有操作

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

      //bad
      contextBridge.exposeInMainWorld('api', {
          readFile: require('fs').readFile
      })
      

      【讨论】:

        【解决方案6】:

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

        并像这样在 HTML 中包含您的外部脚本(而不是 &lt;script&gt; src="./index.js" &lt;/script&gt;):

        <script>
           require('./index.js')
        </script>
        

        【讨论】:

        • 我正在离线使用 pdf js。所以当我使用 nodeIntegration: true 时,PDFJS.getDocument is not a function 将出现错误。如何设置 nodeIntegration: true 完全加载 pdfjs 时在我的 html 页面中。
        • 你看过这个example吗?您可以通过var pdfjsLib = require('pdfjs-dist') 导入包并以这种方式使用它。
        • 为什么建议使用require 而不是&lt;script src="..."&gt;&lt;/script&gt;?这也有一个悬而未决的问题here
        • @bluenote10 Webpack answers this question: 很难说一个脚本依赖什么,必须管理依赖顺序,不必要的代码还是会被下载执行。
        【解决方案7】:

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

        ma​​in.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);
        

        preload.js

        const electron=require('electron');
        const contextBridge=electron.contextBridge;
        
        contextBridge.exposeInMainWorld(
            "api", {
                loadscript(filename){
                    require(filename);
                }
            }
        );
        

        index.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>
        

        index.js

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

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

        【讨论】:

        • 谢谢约瑟夫。这对我来说效果很好,并提供了一种使用 TypeScript(无 Webpack)引导 React 的方法,因为我遇到了直接从脚本标签引用渲染器的问题。我最终确实添加了一个“文件”白名单,以进一步装箱将加载的内容。理想情况下,我将来会迁移到 sandbox = true。
        【解决方案8】:

        您必须在 webPreferences 中启用 nodeIntegration 才能使用它。见下文,

        const { BrowserWindow } = require('electron')
        let win = new BrowserWindow({
          webPreferences: {
            nodeIntegration: true
          }
        })
        win.show()
        

        electron 5.0(Announcement on Repository) 中有一个重大的 api 更改。在最近的版本中,nodeIntegration 默认设置为 false

        Docs 由于 Electron 的 Node.js 集成,在 DOM 中插入了一些额外的符号,例如 module、exports、require。这会导致一些库出现问题,因为它们想要插入具有相同名称的符号。要解决这个问题,您可以在 Electron 中关闭节点集成:

        但是如果你想保留使用 Node.js 和 Electron API 的能力,你必须在包含其他库之前重命名页面中的符号:

        <head>
            <script>
                window.nodeRequire = require;
                delete window.require;
                delete window.exports;
                delete window.module;
            </script>
            <script type="text/javascript" src="jquery.js"></script>
        </head>
        

        【讨论】:

          【解决方案9】:

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

          webPreferences: {
                      nodeIntegration: true,
                      nodeIntegrationInWorker: true,
                      nodeIntegrationInSubFrames: true,
                      enableRemoteModule: true,
                      contextIsolation: false //required flag
                  }
                  
          //rest of the code...

          【讨论】:

            【解决方案10】:

            最后,我成功了。将此代码添加到您的 HTML 文档脚本元素中。

            抱歉回复晚了。我使用下面的代码来做这件事。

            window.nodeRequire = require;
            delete window.require;
            delete window.exports;
            delete window.module;
            

            并使用nodeRequire 而不是使用require

            效果很好。

            【讨论】:

            • 请分享您的 HTML 页面代码。
            猜你喜欢
            • 2020-02-03
            • 2019-11-03
            • 2021-08-03
            • 2018-03-23
            • 2021-09-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多