搜尋

首頁  >  問答  >  主體

在 Electron 中正確利用 preload.js:綜合指南

<p>我嘗試在我的 <code>renderer</code> 進程中使用節點模組(在本例中為 <code>fs</code>),如下所示:</p> <pre class="brush:php;toolbar:false;">// main_window.js const fs = require('fs') function action() { console.log(fs) }</pre> <p><sup>注意:當我按 <code>main_window</code> 中的按鈕時,會呼叫 <code>action</code> 函數。 </sup></p> <p>但這會產生錯誤:</p> <pre class="brush:php;toolbar:false;">Uncaught ReferenceError: require is not defined at main_window.js:1</pre> <p>我可以解決這個問題,正如這個接受的答案所建議的,通過在初始化<code>main_window</code> 時將這些行添加到我的<code>main.js</code>中:</p> <pre class="brush:php;toolbar:false;">// main.js main_window = new BrowserWindow({ width: 650, height: 550, webPreferences: { nodeIntegration: true } })</pre> 但是,根據文檔,這不是最好的做法,我應該創建一個<code>preload.js</code> 文件並在其中加載這些Node 模組,然後在我的所有<code>renderer</code>中使用它流程。像這樣:<p><br /></p> <p><code>main.js</code>:</p> <pre class="brush:php;toolbar:false;">main_window = new BrowserWindow({ width: 650, height: 550, webPreferences: { preload: 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>而且它有效! </p> <hr /> <p>現在我的問題是,我應該在<code>preload.js</code> 中寫<code>renderer</code> 進程的大部分程式碼,這不是違反直覺的嗎(因為只有在<code>preload.js</code> 我才能訪問Node 模組)然後只呼叫每個<code>renderer.js</code> 檔案中的函數(例如此處,<code>main_window. js</code>)?我在這裡不明白什麼? </p>
P粉197639753P粉197639753443 天前490

全部回覆(2)我來回復

  • 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 怎麼樣?

    盡可能遵守對相同流程/環境的承諾。您在 main 上的承諾應該保留在 main 上。您對渲染器的承諾也應該保留在渲染器上。不要做出從主程式跳到預先載入程式再到渲染器的承諾。

    檔案系統

    您的大部分業務邏輯仍應位於主端或渲染器端,但絕不應位於預先載入中。這是因為預載幾乎只是作為一種媒介而存在。預載應該非常小。

    在OP的情況下 fs 應該在主端實作。

    回覆
    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 製作安全應用程式時所需的架構。


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

    回覆
    0
  • 取消回覆