Yêu cầu điện tử () không được xác định


107

Tôi đang tạo một ứng dụng Electron cho mục đích của riêng mình. Vấn đề của tôi là khi tôi đang sử dụng các hàm nút bên trong trang HTML của mình, nó sẽ phát ra lỗi:

'request ()' không được xác định.

Có cách nào để sử dụng các chức năng Node trong tất cả các trang HTML của tôi không? Nếu có thể, vui lòng cho tôi một ví dụ về cách thực hiện việc này hoặc cung cấp liên kết. Đây là các biến tôi đang cố gắng sử dụng trong trang HTML của mình:

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

và đây là những giá trị tôi đang sử dụng trong tất cả các cửa sổ HTML của mình trong Electron.


Câu trả lời:


287

Kể từ phiên bản 5, mặc định cho nodeIntegrationđã thay đổi từ đúng thành sai. Bạn có thể bật nó khi tạo Cửa sổ trình duyệt:

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

Vì phiên bản gần đây của Electron có mặc định nodeIntegration là false vì lý do bảo mật, nên cách nào được khuyến nghị để truy cập các mô-đun nút? Có cách nào để giao tiếp với tiến trình chính mà không cần tích hợp nodeIntegration không?
Paulo Henrique,

28
@PauloHenrique chỉ nodeIntegration: truelà một rủi ro bảo mật khi bạn đang thực thi một số mã từ xa không đáng tin cậy trên ứng dụng của mình. Ví dụ: giả sử ứng dụng của bạn mở ra một trang web của bên thứ ba. Đó sẽ là một rủi ro bảo mật vì trang web của bên thứ ba sẽ có quyền truy cập vào thời gian chạy của nút và có thể chạy một số mã độc hại trên hệ thống tệp của người dùng của bạn. Trong trường hợp đó, nó có ý nghĩa để thiết lập nodeIntegration: false. Nếu ứng dụng của bạn không hiển thị bất kỳ nội dung từ xa nào hoặc chỉ hiển thị nội dung đáng tin cậy, thì bạn có thể cài đặt nodeIntegration: true.
xyres

1
Điều này làm tôi phát điên. Ứng dụng của tôi chỉ đơn giản là sẽ không có lỗi và không chạy mã của tôi. Đó là khi tôi sử dụng khối try catch để chặn lỗi cuối cùng đã đưa tôi đến đây.
Heriberto Juarez,

2
@PauloHenrique - Nếu bạn muốn theo dõi và tạo một ứng dụng an toàn (tuân theo các phương pháp hay nhất về bảo mật), hãy làm theo thiết lập của tôi như tôi mô tả trong nhận xét này: github.com/electron/electron/issues/9920#issuecomment-575839738
Zac

33

Vì lý do bảo mật, bạn nên giữ nodeIntegration: falsevà sử dụng tập lệnh tải trước để chỉ hiển thị những gì bạn cần từ API Node / Electron cho quy trình kết xuất (xem) thông qua biến cửa sổ. Từ tài liệu Electron :

Các tập lệnh tải trước tiếp tục có quyền truy cập requirevà các tính năng khác của Node.js


Thí dụ

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

1
Nếu bạn là một newbie điện tử như tôi: tệp kết xuất thường được bao gồm trong html theo cách cổ điển:<script src="./renderer.js"></script>
MrAn3

22

Tôi hy vọng câu trả lời này sẽ thu hút được sự chú ý, bởi vì phần lớn câu trả lời ở đây để lại những lỗ hổng bảo mật lớn trong ứng dụng điện tử của bạn. Trên thực tế , câu trả lời này về cơ bản là những gì bạn nên làm để sử dụng require()trong các ứng dụng điện tử của mình. (Chỉ có một API điện tử mới giúp nó sạch hơn một chút trong phiên bản v7).

Tôi đã viết lời giải thích / giải pháp chi tiết trong github bằng cách sử dụng ứng dụng điện tử mới nhất về cách bạn có thể làm require()gì đó, nhưng tôi sẽ giải thích ngắn gọn ở đây tại sao bạn nên làm theo cách tiếp cận bằng cách sử dụng tập lệnh tải trước, contextBridge và ipc.

Vấn đề

Các ứng dụng điện tử rất tuyệt vời vì chúng ta có thể sử dụng nút, nhưng sức mạnh này là một con dao hai lưỡi. Nếu chúng tôi không cẩn thận, chúng tôi sẽ cấp cho ai đó quyền truy cập vào nút thông qua ứng dụng của chúng tôi và với nút, một tác nhân xấu có thể làm hỏng máy của bạn hoặc xóa các tệp hệ điều hành của bạn (tôi tưởng tượng trong số những thứ khác).

Như được đưa ra bởi @raddevus trong một nhận xét, điều này là cần thiết khi tải nội dung từ xa . Nếu ứng dụng điện tử của bạn hoàn toàn ngoại tuyến / cục bộ , thì bạn có thể không sao chỉ cần bật nodeIntegration:true. Tuy nhiên, tôi vẫn sẽ chọn giữ lại nodeIntegration:falseđể hoạt động như một biện pháp bảo vệ cho những người dùng vô tình / độc hại sử dụng ứng dụng của bạn và ngăn chặn bất kỳ phần mềm độc hại nào có thể được cài đặt trên máy của bạn tương tác với ứng dụng electron của bạn và sử dụng nodeIntegration:truevectơ tấn công (cực kỳ hiếm , nhưng có thể xảy ra)!

Vấn đề trông như thế nào

Sự cố này xuất hiện khi bạn (bất kỳ trường hợp nào dưới đây):

  1. Đã nodeIntegration:truekích hoạt
  2. Sử dụng remotemô-đun

Tất cả những vấn đề này cho phép bạn truy cập không bị gián đoạn vào nút từ quá trình kết xuất của bạn. Nếu quá trình kết xuất của bạn từng bị tấn công, bạn có thể coi như tất cả đã bị mất.

Giải pháp của chúng tôi là gì

Giải pháp là không cung cấp cho trình kết xuất quyền truy cập trực tiếp vào nút (tức là. require()), Mà cung cấp cho quy trình chính electron của chúng tôi quyền truy cập requirevà bất kỳ lúc nào quy trình kết xuất của chúng tôi cần sử dụng require, điều chỉnh một yêu cầu tới quy trình chính.

Cách hoạt động của điều này trong các phiên bản mới nhất (7+) của Electron là ở phía trình kết xuất, chúng tôi thiết lập các liên kết ipcRenderer và ở phía chính chúng tôi thiết lập các liên kết ipcMain . Trong các ràng buộc ipcMain, chúng tôi thiết lập các phương thức lắng nghe sử dụng các mô-đun chúng tôi require(). Điều này là tốt và tốt vì quy trình chính của chúng tôi có thể làm requiretất cả những gì nó muốn.

Chúng tôi sử dụng contextBridge để chuyển các ràng buộc ipcRenderer vào mã ứng dụng của chúng tôi (để sử dụng) và vì vậy khi ứng dụng của chúng tôi cần sử dụng các requiremô-đun d trong chính, nó sẽ gửi một thông báo qua IPC (giao tiếp giữa các quá trình) và quá trình chính sẽ chạy một số mã, và sau đó chúng tôi gửi lại một tin nhắn với kết quả của chúng tôi.

Xấp xỉ , đây là những gì bạn muốn làm.

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>

Khước từ

Tôi là tác giả của secure-electron-template, một mẫu an toàn để xây dựng các ứng dụng điện tử. Tôi quan tâm đến chủ đề này và đã làm việc với chủ đề này trong vài tuần (tại thời điểm này).


Tôi là nhà phát triển ElectronJS mới và đang làm việc thông qua hướng dẫn PluralSite không chạy nữa, đã thay đổi cài đặt tích hợp nút bec. Bài đăng của bạn rất hay và tôi cũng đang đọc bài đăng trên Github được liên kết của bạn và các tài liệu bảo mật Electron chính thức liên quan. Việc thiết lập tích hợp nút chính xác (để các ứng dụng sẽ hoạt động bình thường và an toàn) có rất nhiều phần chuyển động (đặc biệt là đối với người mới). Câu sau từ tài liệu Electron "Điều tối quan trọng là bạn không bật tích hợp Node.js trong bất kỳ trình kết xuất nào (BrowserWindow, BrowserView hoặc <webview>) tải nội dung từ xa. " (Nhấn mạnh của tôi)
raddevus

Tôi giả định rằng điều ngược lại là đúng ... "nếu BrowserWindow không tải nội dung từ xa, thì có thể an toàn để bao gồm tích hợp Node". Nếu câu đó đúng, bạn có thể muốn thay đổi bài viết của mình một chút để nhấn mạnh điểm này vì trong trường hợp của tôi, tôi có hai ứng dụng thuộc danh mục đó và không cần phải xóa tích hợp nút. Cảm ơn bạn đã giúp đỡ.
raddevus

1
@raddevus Cảm ơn bạn, tôi hy vọng mẫu giúp bạn xây dựng các ứng dụng điện tử an toàn (nếu bạn chọn sử dụng nó)! Có, bạn đã chính xác về sự nhấn mạnh của bạn. Tuy nhiên, tôi sẽ nói rằng việc vô hiệu hóa nodeIntegrationngăn người dùng vô tình hoặc cố ý gây hại cho chính họ trong khi sử dụng ứng dụng và là một biện pháp bảo vệ bổ sung trong trường hợp một số phần mềm độc hại được gắn vào quy trình điện tử của bạn và có thể thực hiện XSS khi biết vectơ này đang mở (cực kỳ hiếm, nhưng đó là nơi não của tôi đã đi)!
Zac

1
@raddevus Cảm ơn bạn, tôi đang cập nhật bài viết của mình để phản ánh nhận xét của bạn.
Zac

9

Bạn có đang sử dụng nodeIntegration: falsekhi khởi chạy BrowserWindow không? Nếu vậy, hãy đặt nó thành true(giá trị mặc định là true).

Và bao gồm các tập lệnh bên ngoài của bạn trong HTML như thế này (không phải như <script> src="./index.js" </script>):

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

Tôi đang sử dụng pdf js ngoại tuyến với điều này. Vì vậy, khi tôi đang sử dụng nodeIntegration: true thì PDFJS.getDocument không phải là lỗi chức năng sẽ đến. Cách đặt nodeIntegration: true trong trang html của tôi khi pdfjs được tải xong.
Mari Selvan

Bạn đã xem ví dụ này chưa? Bạn có thể chỉ nhập gói qua var pdfjsLib = require('pdfjs-dist')và sử dụng nó theo cách này.
RoyalBingBong

Tại sao bạn khuyên bạn nên sử dụng requirethay vì <script src="..."></script>? Điều này cũng có một câu hỏi chưa được trả lời ở đây .
bluenote10

@ bluenote10 Webpack trả lời câu hỏi này : thật khó để biết tập lệnh phụ thuộc vào cái gì, thứ tự phụ thuộc phải được quản lý và mã không cần thiết sẽ vẫn được tải xuống và thực thi.
haykam

7

Trước hết, giải pháp @Sathiraumesh khiến ứng dụng điện tử của bạn gặp vấn đề lớn về bảo mật. Hãy tưởng tượng rằng ứng dụng của bạn đang thêm một số tính năng bổ sung messenger.com, chẳng hạn như biểu tượng của thanh công cụ sẽ thay đổi hoặc nhấp nháy khi bạn có tin nhắn chưa đọc. Vì vậy, trong main.jstệp của bạn , bạn tạo BrowserWindow mới như vậy (lưu ý rằng tôi cố tình viết sai chính tả messenger.com):

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

Điều gì xảy ra nếu messengre.comlà một trang web độc hại, muốn làm hại máy tính của bạn. Nếu bạn đặt nodeIntegration: true, trang web này có quyền truy cập vào hệ thống tệp cục bộ của bạn và có thể thực thi điều này:

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

Và thư mục chính của bạn đã biến mất.

Giải pháp
Chỉ phơi bày những gì bạn cần thay vì tất cả mọi thứ. Điều này được giải quyết bằng cách tải trước mã javascript với các requirecâu lệnh.

// 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>

Bây giờ thật tồi tệ messengre.comkhông thể xóa toàn bộ hệ thống tệp của bạn.


-1

Cuối cùng, tôi đã làm cho nó hoạt động. Thêm mã này vào Phần tử Script tài liệu HTML của bạn.

Xin lỗi vì trả lời muộn, tôi sử dụng đoạn mã dưới đây để thực hiện việc này.

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

Và sử dụng nodeRequirethay vì sử dụng require.

Nó hoạt động tốt.


Vui lòng chia sẻ mã trang HTML của bạn.
Vijay

-1

Bạn phải bật tích hợp nodeIntegration trong webPreferences để sử dụng nó. xem bên dưới,

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

Đã có một sự thay đổi api đột phá trong electron 5.0 ( Thông báo trên Kho lưu trữ ). Trong các phiên bản gần đây, nodeIntegration được đặt theo mặc định là false .

Tài liệu Do sự tích hợp Node.js của Electron, có một số ký hiệu bổ sung được chèn vào DOM như mô-đun, xuất khẩu, yêu cầu. Điều này gây ra sự cố cho một số thư viện vì họ muốn chèn các ký hiệu có cùng tên. Để giải quyết vấn đề này, bạn có thể tắt tích hợp nút trong Electron:

Nhưng nếu bạn muốn duy trì khả năng sử dụng Node.js và Electron API, bạn phải đổi tên các ký hiệu trong trang trước khi đưa vào các thư viện khác:

<head>
    <script>
        window.nodeRequire = require;
        delete window.require;
        delete window.exports;
        delete window.module;
    </script>
    <script type="text/javascript" src="jquery.js"></script>
</head>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.