ไม่ได้กำหนดอิเล็กตรอนต้องการ ()


116

ฉันกำลังสร้างแอพ Electron เพื่อจุดประสงค์ของตัวเอง ปัญหาของฉันคือเมื่อฉันใช้ฟังก์ชันโหนดในหน้า HTML ของฉันมันทำให้เกิดข้อผิดพลาด:

ไม่ได้กำหนด 'require ()'

มีวิธีใดบ้างในการใช้ฟังก์ชันโหนดในหน้า HTML ทั้งหมดของฉัน หากเป็นไปได้โปรดยกตัวอย่างวิธีการทำสิ่งนี้ให้ฉันหรือให้ลิงก์ นี่คือตัวแปรที่ฉันพยายามใช้ในหน้า HTML ของฉัน:

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

และนี่คือค่าที่ฉันใช้ในหน้าต่าง HTML ทั้งหมดภายใน Electron


คำตอบ:


311

ในเวอร์ชัน 5 ค่าเริ่มต้นสำหรับnodeIntegrationเปลี่ยนจากจริงเป็นเท็จ คุณสามารถเปิดใช้งานได้เมื่อสร้างหน้าต่างเบราว์เซอร์:

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

เนื่องจากเวอร์ชันล่าสุดของ Electron มีค่าเริ่มต้น nodeIntegration เป็นเท็จเนื่องจากเหตุผลด้านความปลอดภัยวิธีใดเป็นวิธีที่แนะนำในการเข้าถึงโมดูลโหนด มีวิธีสื่อสารกับกระบวนการหลักโดยไม่ใช้ nodeIntegration หรือไม่?
Paulo Henrique

32
@PauloHenrique nodeIntegration: trueเป็นความเสี่ยงด้านความปลอดภัยเฉพาะเมื่อคุณเรียกใช้รหัสระยะไกลที่ไม่น่าเชื่อถือในแอปพลิเคชันของคุณ ตัวอย่างเช่นสมมติว่าแอปพลิเคชันของคุณเปิดหน้าเว็บของบุคคลที่สาม นั่นจะเป็นความเสี่ยงด้านความปลอดภัยเนื่องจากหน้าเว็บของบุคคลที่สามจะสามารถเข้าถึงรันไทม์ของโหนดและสามารถเรียกใช้โค้ดที่เป็นอันตรายบนระบบไฟล์ของผู้ใช้ nodeIntegration: falseในกรณีที่ว่ามันทำให้รู้สึกถึงชุด หากแอปของคุณไม่แสดงเนื้อหาระยะไกลหรือแสดงเฉพาะเนื้อหาที่เชื่อถือได้การตั้งค่าnodeIntegration: trueก็ใช้ได้
xyres

1
นี่ทำให้ฉันแทบคลั่ง แอปของฉันจะไม่แสดงข้อผิดพลาดและไม่เรียกใช้รหัสของฉัน เป็นตอนที่ฉันใช้ try catch block เพื่อสกัดกั้นข้อผิดพลาดที่ทำให้ฉันมาที่นี่
Heriberto Juarez

3
@PauloHenrique - หากคุณต้องการติดตามและสร้างแอปที่ปลอดภัย (ปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดด้านความปลอดภัย) โปรดทำตามการตั้งค่าของฉันตามที่ฉันอธิบายไว้ในความคิดเห็นนี้: github.com/electron/electron/issues/9920#issuecomment-575839738
Zac

ไม่ทำงานบน 10.1.15 ยังคงได้รับคำเตือนด้านความปลอดภัย
Wilson Chen

38

ด้วยเหตุผลด้านความปลอดภัยคุณควรเก็บnodeIntegration: falseและใช้สคริปต์พรีโหลดเพื่อแสดงสิ่งที่คุณต้องการจาก Node / Electron API ไปยังกระบวนการ renderer (มุมมอง) ผ่านตัวแปรหน้าต่าง จากเอกสารอิเล็กตรอน :

สคริปต์ที่โหลดล่วงหน้ายังคงสามารถเข้าถึงrequireและคุณลักษณะอื่น ๆ ของ Node.js ได้


ตัวอย่าง

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
หากคุณเป็นมือใหม่อิเล็กตรอนเหมือนฉันไฟล์ renderer มักจะรวมอยู่ใน html ในรูปแบบคลาสสิก:<script src="./renderer.js"></script>
MrAn3

26

ฉันหวังว่าคำตอบนี้จะได้รับความสนใจเนื่องจากคำตอบส่วนใหญ่ที่นี่ทำให้เกิดช่องโหว่ด้านความปลอดภัยขนาดใหญ่ในแอปอิเล็กตรอนของคุณ อันที่จริงคำตอบนี้คือสิ่งที่คุณควรทำเพื่อใช้require()ในแอปอิเล็กตรอนของคุณ (มีเพียงอิเล็กตรอน API ใหม่ที่ทำให้สะอาดขึ้นเล็กน้อยใน v7)

ฉันเขียนคำอธิบายโดยละเอียด / วิธีแก้ปัญหาใน github โดยใช้อิเลคตรอนที่เป็นปัจจุบันที่สุดว่าคุณสามารถทำrequire()อะไรได้บ้าง แต่ฉันจะอธิบายสั้น ๆ ที่นี่ว่าทำไมคุณควรทำตามวิธีการโดยใช้สคริปต์พรีโหลดบริบทแบริดจ์และ ipc

ปัญหา

แอพ Electron นั้นยอดเยี่ยมเพราะเราใช้งาน node ได้ แต่พลังนี้เป็นดาบสองคม หากเราไม่ระมัดระวังเราจะให้ใครบางคนเข้าถึงโหนดผ่านแอพของเราและด้วยโหนดนักแสดงที่ไม่ดีสามารถทำให้เครื่องของคุณเสียหายหรือลบไฟล์ระบบปฏิบัติการของคุณได้ (ฉันคิดว่า)

ตามที่ @raddevus นำเสนอในความคิดเห็นสิ่งนี้จำเป็นเมื่อโหลดเนื้อหาระยะไกล หากแอปอิเล็กตรอนของคุณมีทั้งแบบออฟไลน์ / ท้องถิ่นnodeIntegration:trueแล้วคุณอาจจะไม่เป็นไรเพียงแค่เปิด อย่างไรก็ตามฉันยังคงเลือกที่nodeIntegration:falseจะรักษาการป้องกันผู้ใช้ที่ไม่ได้ตั้งใจ / เป็นอันตรายที่ใช้แอปของคุณและป้องกันมัลแวร์ที่อาจติดตั้งในเครื่องของคุณจากการโต้ตอบกับแอปอิเล็กตรอนของคุณและใช้nodeIntegration:trueเวกเตอร์การโจมตี (หายากอย่างไม่น่าเชื่อ แต่อาจเกิดขึ้นได้)!

ปัญหามีลักษณะอย่างไร

ปัญหานี้ปรากฏขึ้นเมื่อคุณ (ข้อใดข้อหนึ่งด้านล่าง):

  1. ได้nodeIntegration:trueเปิดใช้งาน
  2. ใช้remoteโมดูล

ปัญหาทั้งหมดนี้ทำให้สามารถเข้าถึงโหนดจากกระบวนการแสดงผลของคุณได้อย่างต่อเนื่อง หากกระบวนการแสดงภาพของคุณถูกแย่งชิงคุณสามารถพิจารณาว่าทั้งหมดสูญหาย

ทางออกของเราคืออะไร

วิธีแก้ปัญหาคืออย่าให้ตัวแสดงการเข้าถึงโหนดโดยตรง (กล่าวคือrequire()) แต่เพื่อให้กระบวนการหลักของอิเล็กตรอนของเราสามารถเข้าถึงได้requireและเมื่อใดก็ตามที่กระบวนการเรนเดอร์ของเราจำเป็นต้องใช้requireให้ยื่นคำร้องขอไปยังกระบวนการหลัก

วิธีนี้ทำงานใน Electron เวอร์ชันล่าสุด (7+) อยู่ที่ด้าน renderer ที่เราตั้งค่าการเชื่อมipcRendererและที่ด้านหลักเราตั้งค่าการเชื่อมipcMain ในการผูก ipcMain require()เราตั้งค่าวิธีการฟังที่ใช้โมดูลเรา นี่เป็นเรื่องปกติและดีเพราะกระบวนการหลักของเราสามารถทำได้requireทุกอย่าง

เราใช้contextBridgeเพื่อส่งการเชื่อมโยง ipcRenderer ไปยังรหัสแอปของเรา (เพื่อใช้งาน) ดังนั้นเมื่อแอปของเราจำเป็นต้องใช้requireโมดูล d เป็นหลักแอปจะส่งข้อความผ่าน IPC (การสื่อสารระหว่างกระบวนการ) และกระบวนการหลักจะทำงาน รหัสบางส่วนจากนั้นเราจะส่งข้อความกลับไปพร้อมกับผลลัพธ์ของเรา

คร่าวๆนี่คือสิ่งที่คุณต้องการทำ

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>

ข้อจำกัดความรับผิดชอบ

ฉันเป็นผู้secure-electron-templateสร้างเทมเพลตที่ปลอดภัยในการสร้างแอปอิเล็กตรอน ฉันสนใจหัวข้อนี้และได้ดำเนินการเรื่องนี้มาสองสามสัปดาห์แล้ว (ณ เวลานี้)


ฉันเป็นนักพัฒนา ElectronJS คนใหม่และกำลังทำงานผ่านบทช่วยสอน PluralSite ที่ไม่เรียกใช้การตั้งค่าการรวมโหนดเนื่องจากมีการเปลี่ยนแปลงอีกต่อไป โพสต์ของคุณดีมากและฉันยังอ่านโพสต์ Github ที่เกี่ยวข้องและเอกสารความปลอดภัยของ Electron ที่เกี่ยวข้อง การตั้งค่าการรวมโหนดอย่างถูกต้อง (เพื่อให้แอปทำงานได้อย่างถูกต้องและปลอดภัย) มีชิ้นส่วนที่เคลื่อนไหวได้มากมาย (โดยเฉพาะสำหรับมือใหม่) ประโยคต่อจาก Electron docs "เป็นสิ่งสำคัญยิ่งที่คุณไม่ได้เปิดใช้งานการรวม Node.js ในตัวแสดงผลใด ๆ (BrowserWindow, BrowserView หรือ <webview>) ที่โหลดเนื้อหาระยะไกล " (การเน้นของฉัน)
raddevus

ฉันสมมติว่าผกผันเป็นจริง ... "ถ้า BrowserWindow ไม่โหลดเนื้อหาระยะไกลก็ปลอดภัยที่จะรวมการรวมโหนด" หากประโยคนั้นเป็นจริงคุณอาจต้องการแก้ไขโพสต์ของคุณเล็กน้อยเพื่อเน้นประเด็นนี้เพราะในกรณีของฉันฉันมีสองแอพที่อยู่ในหมวดหมู่นั้นและไม่จำเป็นต้องลบการรวมโหนด ขอบคุณสำหรับความช่วยเหลือของคุณ.
raddevus

1
@raddevus ขอบคุณฉันหวังว่าเทมเพลตจะช่วยคุณสร้างแอปอิเล็กตรอนที่ปลอดภัย (หากคุณเลือกใช้)! ใช่คุณถูกต้องในการเน้นของคุณ อย่างไรก็ตามฉันจะบอกว่าการปิดใช้งานnodeIntegrationจะป้องกันไม่ให้ผู้ใช้เกิดอันตรายโดยไม่ได้ตั้งใจหรือโดยเจตนาในขณะที่ใช้แอพและเป็นการป้องกันเพิ่มเติมในกรณีที่มัลแวร์บางตัวติดอยู่กับกระบวนการอิเล็กตรอนของคุณและสามารถดำเนินการ XSS ได้โดยรู้ว่าเวกเตอร์นี้เปิดอยู่ (เหลือเชื่อ หายาก แต่นั่นคือสิ่งที่สมองของฉันไป)!
Zac

1
@raddevus ขอบคุณฉันกำลังอัปเดตโพสต์เพื่อแสดงความคิดเห็นของคุณ
Zac

9

คุณใช้nodeIntegration: falseในขณะเริ่มต้น BrowserWindow หรือไม่? หากเป็นเช่นนั้นให้ตั้งค่าเป็นtrue(ค่าเริ่มต้นคือtrue)

และรวมสคริปต์ภายนอกของคุณใน HTML เช่นนี้ (ไม่ใช่<script> src="./index.js" </script>):

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

ฉันใช้ pdf js ออฟไลน์กับสิ่งนี้ดังนั้นเมื่อฉันใช้nodeIntegration: trueดังนั้น PDFJS.getDocument จะไม่เกิดข้อผิดพลาดของฟังก์ชันวิธีตั้งค่าnodeIntegration: trueในหน้า html ของฉันเมื่อโหลด pdfjs เสร็จสมบูรณ์
Mari Selvan

คุณดูตัวอย่างนี้แล้วหรือยัง? คุณอาจสามารถนำเข้าแพ็กเกจผ่านทางvar pdfjsLib = require('pdfjs-dist')และใช้วิธีนี้ได้
RoyalBingBong

ทำไมคุณถึงแนะนำให้ใช้requireแทน<script src="..."></script>? นี้ยังมีคำถามที่ยังไม่ได้ตอบที่นี่
ลูโนเทต 10

@ bluenote10 Webpack ตอบคำถามนี้ยากที่จะบอกได้ว่าสคริปต์ขึ้นอยู่กับอะไรต้องจัดการลำดับการอ้างอิงและโค้ดที่ไม่จำเป็นจะยังคงถูกดาวน์โหลดและเรียกใช้งาน
haykam

8

ก่อนอื่นโซลูชัน @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คำสั่ง

// 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ไม่สามารถลบระบบไฟล์ทั้งหมดของคุณได้


1

สิ่งที่ฉันต้องการทำคือต้องการไฟล์ js ในหน้า html ของฉันเนื่องจากการสอนที่ฉันติดตาม อย่างไรก็ตามฉันตั้งใจจะใช้โมดูลระยะไกลดังนั้นความปลอดภัยจึงเป็นสิ่งสำคัญยิ่ง ฉันแก้ไขคำตอบของ Michael ที่นั่นดังนั้นฉันจึงโพสต์สำหรับผู้ที่ใช้เวลาหลายชั่วโมงในการมองหาทางเลือกที่ปลอดภัยเพื่อ 'ต้องการ' เช่นฉัน หากรหัสไม่ถูกต้องอย่าลังเลที่จะชี้ให้เห็น

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

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

ฉันอยากรู้เป็นพิเศษว่าสิ่งนี้ยังคงมีความเสี่ยงด้านความปลอดภัย ขอบคุณ.


-1

ในที่สุดฉันก็ทำให้มันใช้งานได้เพิ่มรหัสนี้ในองค์ประกอบสคริปต์เอกสาร HTML ของคุณ

ขออภัยสำหรับการตอบกลับล่าช้าฉันใช้รหัสด้านล่างเพื่อทำสิ่งนี้

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

และใช้nodeRequireแทนการใช้require.

มันใช้งานได้ดี


โปรดแบ่งปันรหัสหน้า HTML ของคุณ
Vijay

-1

คุณต้องเปิดใช้งานnodeIntegrationในwebPreferencesเพื่อใช้งาน ดูด้านล่าง

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

มีการเปลี่ยนแปลง api อย่างรุนแรงในอิเล็กตรอน 5.0 ( ประกาศบน Repository ) ในรุ่นล่าสุดnodeIntegrationคือโดยชุดเริ่มต้นที่ผิดพลาด

เอกสารเนื่องจากการรวม Node.js ของ Electron จึงมีสัญลักษณ์พิเศษบางอย่างที่แทรกเข้าไปใน DOM เช่นโมดูลการเอ็กซ์พอร์ตต้องการ สิ่งนี้ทำให้เกิดปัญหากับบางไลบรารีเนื่องจากต้องการแทรกสัญลักษณ์ที่มีชื่อเดียวกันในการแก้ปัญหานี้คุณสามารถปิดการรวมโหนดใน 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>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.