suchen

Heim  >  Fragen und Antworten  >  Hauptteil

Preload.js in Electron richtig nutzen: Eine umfassende Anleitung

<p>Ich habe versucht, ein Knotenmodul (in diesem Fall <code>fs</code>) in meinem <code>renderer</code>-Prozess zu verwenden: </p> <pre class="brush:php;toolbar:false;">// main_window.js const fs = require('fs') Funktion action() { console.log(fs) }</pre> <p><sup>Hinweis: Wenn ich die Schaltfläche in <code>main_window</code> drücke, wird die Funktion <code>action</code> aufgerufen. </sup></p> <p>Aber dies erzeugt den Fehler: </p> <pre class="brush:php;toolbar:false;">Uncaught ReferenceError: require ist nicht definiert unter main_window.js:1</pre> <p>Ich konnte dieses Problem lösen, indem ich beim Initialisieren von <code>main_window</code> diese Zeilen zu meiner <code>main.js</code> hinzufügte. /p> <pre class="brush:php;toolbar:false;">// main.js main_window = neues BrowserWindow({ Breite: 650, Höhe: 550, webPreferences: { nodeIntegration: wahr } })</pre> Laut Dokumentation ist dies jedoch nicht die beste Vorgehensweise. Ich sollte eine Datei <code>preload.js</code> erstellen und diese Node-Module darin und dann in alle meine <code>renderer</ laden. Code> Verwenden Sie ihn dabei.So:<p><br /></p> <p><code>main.js</code>:</p> <pre class="brush:php;toolbar:false;">main_window = new BrowserWindow({ Breite: 650, Höhe: 550, webPreferences: { Vorladen: path.join(app.getAppPath(), 'preload.js') } })</pre> <p><code>preload.js</code>:</p> <pre class="brush:php;toolbar:false;">const fs = require('fs') window.test = function() { console.log(fs) }</pre> <p><code>main_window.js</code>:</p> <pre class="brush:php;toolbar:false;">function action() { window.test() }</pre> <p>Und es funktioniert! </p> <hr /> <p>Meine Frage ist nun, ob es nicht kontraintuitiv ist, dass ich den größten Teil des Codes für den <code>renderer</code>-Prozess in <code>preload.js</code> schreiben sollte Sie haben nur Zugriff auf das Node-Modul in <code>preload.js</code>) und rufen dann einfach die Funktionen in jeder <code>renderer.js</code>-Datei auf (wie hier, <code>main_window). js</code>)? Was verstehe ich hier nicht? </p>
P粉197639753P粉197639753456 Tage vor501

Antworte allen(2)Ich werde antworten

  • P粉615829742

    P粉6158297422023-08-28 19:14:20

    考虑这个例子

    并非官方文档中的所有内容都可以在代码中的任何位置直接实现。您必须需要对环境和流程有简明的了解。

    环境/流程 描述
    主要 API 更接近操作系统(低级)。其中包括文件系统、基于操作系统的通知弹出窗口、任务栏等。这些是通过 Electron 的核心 API 和 Node.js 的组合实现的
    预加载 最近的附录,以防止主环境中可用的强大 API 泄​​漏。有关更多详细信息,请参阅 Electron v12 变更日志< /a> 和问题#23506
    渲染器 现代 Web 浏览器的 API,例如 DOM 和前端 JavaScript(高级)。这是通过 Chromium 实现的。

    上下文隔离和节点集成

    场景 contextIsolation nodeIntegration 备注
    A 不需要预加载。 Node.js 在 Main 中可用,但在 Renderer 中不可用。
    B true 不需要预加载。 Node.js 在 Main 和 Renderer 中可用。
    C true 需要预加载。 Node.js 在主加载和预加载中可用,但在渲染器中不可用。 默认推荐
    D true true 需要预加载。 Node.js 在 Main、Preload 和 Renderer 中可用。

    如何正确使用预载?

    您必须使用 Electron 的进程间通信 (IPC) 才能使主进程和渲染进程进行通信。

    1. 流程中,使用:
    2. 预加载流程中,向渲染器流程公开用户定义端点
    3. 渲染器流程中,使用公开的用户定义端点来:
      • 向 Main 发送消息
      • 接收来自 Main 的消息

    实现示例

    主要

    /**
     * Sending messages to Renderer
     * `window` is an object which is an instance of `BrowserWindow`
     * `data` can be a boolean, number, string, object, or array
     */
    window.webContents.send( 'custom-endpoint', data );
    
    /**
     * Receiving messages from Renderer
     */
    ipcMain.handle( 'custom-endpoint', async ( event, data ) => {
        console.log( data )
    } )
    

    预加载

    const { contextBridge, ipcRenderer } = require('electron')
    
    contextBridge.exposeInMainWorld( 'api', {
        send: ( channel, data ) => ipcRenderer.invoke( channel, data ),
        handle: ( channel, callable, event, data ) => ipcRenderer.on( channel, callable( event, data ) )
    } )
    

    渲染器

    /**
     * Sending messages to Main
     * `data` can be a boolean, number, string, object, or array
     */
    api.send( 'custom-endpoint', data )
    
    /**
     * Receiving messages from Main
     */
    api.handle( 'custom-endpoint', ( event, data ) => function( event, data ) {
        console.log( data )
    }, event);
    

    使用 Promise 怎么样?

    尽可能遵守对相同流程/环境的承诺。您在 ma​​in 上的承诺应该保留在 main 上。您对渲染器的承诺也应该保留在渲染器上。不要做出从主程序跳转到预加载程序再到渲染器的承诺。

    文件系统

    您的大部分业务逻辑仍应位于主端或渲染器端,但绝不应位于预加载中。这是因为预载几乎只是作为一种媒介而存在。预载应该非常小。

    在OP的情况下 fs 应该在主端实现。

    Antwort
    0
  • P粉323374878

    P粉3233748782023-08-28 11:18:17

    编辑 2022 年


    我已经发表了一篇关于 Electron 历史的较大文章( Electron 版本中的安全性如何发生变化)以及 Electron 开发人员可以采取的其他安全注意事项,以确保在新应用程序中正确使用预加载文件。

    编辑 2020


    正如另一位用户所问,让我在下面解释我的答案。

    在 Electron 中使用 preload.js 的正确方法是在您的应用可能需要 require 的任何模块周围公开白名单包装器。

    从安全角度来看,公开 require 或通过 preload.js 中的 require 调用检索的任何内容都是危险的(请参阅< a href="https://github.com/electron/ Electron/issues/9920#issuecomment-575839738" rel="noreferrer">我的评论以获取更多解释原因)。如果您的应用程序加载远程内容(许多应用程序都会这样做),则尤其如此。

    为了正确执行操作,您需要在 < 上启用许多选项code>BrowserWindow 正如我在下面详细介绍的。设置这些选项会强制您的电子应用程序通过 IPC(进程间通信)进行通信,并将两个环境相互隔离。像这样设置您的应用程序可以让您验证后端中可能是 require 模块的任何内容,而客户端不会对其进行篡改。

    下面,您将找到一个简短的示例,介绍我所讨论的内容以及它在您的应用中的外观。如果您是新手,我可能建议使用 secure-electron-template< /a> (我是其作者)在构建电子应用程序时从一开始就融入了所有这些安全最佳实践。

    此页面也有很好的信息使用 preload.js 制作安全应用程序时所需的架构。


    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>
    

    Antwort
    0
  • StornierenAntwort