Electron require() is not defined
Asked Answered
S

11

247

I'm creating an Electron app for my own purpose. My problem is when I'm using node functions inside my HTML page it throws an error of:

'require()' is not defined.

Is there any way to use Node functionalities in all my HTML pages? If it is possible please give me an example of how to do this or provide a link. Here are the variables I'm trying to use in my HTML page:

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

and these are the values I'm using in all my HTML windows within Electron.

Samy answered 6/6, 2017 at 13:32 Comment(0)
D
521

As of version 5, the default for nodeIntegration changed from true to false. You can enable it when creating the Browser Window:

app.on('ready', () => {
    mainWindow = new BrowserWindow({
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false,
        }
    });
});
Donald answered 29/4, 2019 at 17:53 Comment(10)
Since Electron's recent version have nodeIntegration default to false due to security reasons, which is the recommended way to access node modules? Is there a way to communicate with the main process without nodeIntegration?Erl
@PauloHenrique nodeIntegration: true is a security risk only when you're executing some untrusted remote code on your application. For example, suppose your application opens up a third party webpage. That would be a security risk because the third party webpage will have access to node runtime and can run some malicious code on your user's filesystem. In that case it makes sense to set nodeIntegration: false. If your app is not displaying any remote content, or is displaying only trusted content, then setting nodeIntegration: true is okay.Hidie
This was driving me crazy. My app simply would show no error and didn't run my code. It was when i used a try catch block to intercept the error that finally brought me here.Sextuplicate
@PauloHenrique - If you want to follow and create a secure app (adhering to security best-practices), please follow my setup as I describe in this comment: github.com/electron/electron/issues/9920#issuecomment-575839738Terrenceterrene
not working on 10.1.15, still get security warning.Disquisition
according to the documentation on electronjs.org/docs/api/browser-window on version 10.1.15 still, the nodeIntergration option needs to be set to enable in the WebPrefernces.Donald
This change in defaults has resulted in breaking several of the electron app tutorials I've read. Thank you so much for this - now if we can get the authors to update their tutorials that would be great.Maimaia
I have checked the documentation for the release of electron 12.0 electronjs.org/docs/breaking-changes and to get the previous behavior the contextIssolation should be false thanksDonald
Without the line contextIsolation: false, you won't be able to access requireLampert
What if there is a dependency that uses require()? For me somehow somewhere there are 2 requires in final main.js (generated file without formatting that FE loads) require("events") and require("ringo/engine"), no idea how they got there.. Can we somehow allow them or override them?Kendry
T
147

Edit 2022


I've published a larger post on the history of Electron and it's security that provides additional context on the changes that affect how security was approached in different framework versions (and what's the best approach to take).

Original answer


I hope this answer gets some attention, because a large majority of answers here leave large security holes in your electron app. In fact this answer is essentially what you should be doing to use require() in your electron apps. (There is just a new electron API that makes it a little bit cleaner in v7).

I wrote a detailed explanation/solution in github using the most current electron apis of how you can require() something, but I'll explain briefly here why you should follow an approach using a preload script, contextBridge and ipc.

The problem

Electron apps are great because we get to use node, but this power is a double-edged sword. If we are not careful, we give someone access to node through our app, and with node a bad actor can corrupt your machine or delete your operating system files (among other things, I imagine).

As brought up by @raddevus in a comment, this is necessary when loading remote content. If your electron app is entirely offline/local, then you are probably okay simply turning on nodeIntegration:true. I still would, however, opt to keep nodeIntegration:false to act as a safeguard for accidental/malicious users using your app, and prevent any possible malware that might ever get installed on your machine from interacting with your electron app and using the nodeIntegration:true attack vector (incredibly rare, but could happen)!

What does the problem look like

This problem manifests when you (any one of the below):

  1. Have nodeIntegration:true enabled
  2. Use the remote module

All of these problems give uninterrupted access to node from your renderer process. If your renderer process is ever hijacked, you can consider all is lost.

What our solution is

The solution is to not give the renderer direct access to node (ie. require()), but to give our electron main process access to require, and anytime our renderer process needs to use require, marshal a request to the main process.

The way this works in the latest versions (7+) of Electron is on the renderer side we set up ipcRenderer bindings, and on the main side we set up ipcMain bindings. In the ipcMain bindings we set up listener methods that use modules we require(). This is fine and well because our main process can require all it wants.

We use the contextBridge to pass the ipcRenderer bindings to our app code (to use), and so when our app needs to use the required modules in main, it sends a message via IPC (inter-process-communication) and the main process runs some code, and we then send a message back with our result.

Roughly, here's what you want to do.

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>

Disclaimer

I'm the author of secure-electron-template, a secure template to build electron apps. I care about this topic, and have been working on this for a few weeks (at this point in time).

Terrenceterrene answered 24/1, 2020 at 0:7 Comment(8)
I'm new ElectronJS developer & was working thru a PluralSite tutorial that no longer runs bec node integration settings changed. Your post is very good & I'm also reading your associated Github post and the associated official Electron security docs. Getting the node integration set up exactly right (so apps will work properly and be secure) has lots of moving parts (especially for newbie). Following sentence from Electron docs "It is paramount that you do not enable Node.js integration in any renderer (BrowserWindow, BrowserView, or <webview>) that loads remote content." (My emphasis)Frazee
I'm assuming that the inverse is true ..."if BrowserWindow does not load remote content, then it is safe to include Node integration". If that sentence is true you may want to alter your posts a bit to emphasize this point because in my case I have two apps which fall into that category and do not need to remove node integration. Thanks for your help.Frazee
@Frazee Thank you, I hope the template helps you build secure electron apps (if you choose to use it)! Yes, you are correct on your emphasis. However, I will say disabling nodeIntegration prevents the user from accidentally or purposely causing harm to themselves while using the app, and is an extra safeguard in case some malware got attached to your electron process and was able to perform XSS knowing this vector was open (incredibly rare, but that's where my brain went)!Terrenceterrene
@Frazee Thank you, I am updating my posts to reflect your comment.Terrenceterrene
I'm probably a bit slow, but I found this answer confusing. In fact the page on context isolation in the electron docs explains it much better, and points out that the slightly simpler approach used in @Mateen Ulhaq's answer still isn't ideal and will not work by default in Electron 12.Obviate
Are there any resources that explain what the risks of contextIsolation:false, nodeIntegration:true . Particularly if you're only loading local HTML/JS/CSS files. I'm very interested in this topic.Cwm
It helped me to import module securely! Thanks! it was a great example to do it.Risorgimento
I don't want to be a complaining nanny or something, but the fact that you can go through the official quick start guide and end up with an app that "works" but is absolutely not ready to be expanded upon is absolute garbage. Given the movement forward and speed thereof, 99% of answers I've found have been wrong and dangerous and not working at all. A very frustrating experience. Thanks for this reply, at least.Apologue
S
65

For security reasons, you should keep nodeIntegration: false and use a preload script to expose just what you need from Node/Electron API to the renderer process (view) via window variable. From the Electron docs:

Preload scripts continue to have access to require and other Node.js features


Example

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();
});
Sonjasonnet answered 16/7, 2019 at 2:22 Comment(5)
If you are an electron newbie as me: the renderer file is usually included in the html in the classic way: <script src="./renderer.js"></script>Volatile
Why are the official docs using require() in renderers if require is not available?Demurrage
The documentation you have linked has been marked as "deprecated" since 2019. (They should use a bright red banner instead of this greyish quoted message.)Chau
In newer versions of electron you need to add enableRemoteModule: true, when creating the windo to enable the remote to not be undefinedChum
This topic is so weird to get started, it should be just talking about preload from the very beginning, what is all the security fuss about. We are only trying to follow tutorials, and if not require, so what then, just state what in the documentation. Btw, i like this answer.Hypaethral
G
14

First off, @Sathiraumesh solution leaves your electron application with huge security issue. Imagine that your app is adding some extra features to messenger.com, for example toolbar's icon will change or blink when you've have unread message. So in your main.js file, you create new BrowserWindow like so (notice I intentionally misspelled messenger.com):

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

What if messengre.com is a malicious website, that wants to harm your computer. If you set nodeIntegration: true this site has access to your local file system and can execute this:

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

And your home directory is gone.

Solution
Expose only what you need, instead of everything. This is achived by preloading javascript code with require statements.

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

Now awful messengre.com cannot delete your entire file system.

Garbage answered 29/10, 2019 at 22:18 Comment(0)
M
14

It looks like Electron's security evolved like this (source).

Electron 1 nodeIntegration defaults to true

Renderer has full access to Node API -- huge security risks if Renderer loads remote code.

Electron 5 nodeIntegration defaults to false

When set to false, a preload script is used to expose specific API to Renderer. (The preload script always has access to Node APIs regardless of the value of nodeIntegration)

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

Electron 5 contextIsolation defaults to true (actually still defaults to false in Electron 11)

This causes preload script to run in a separate context. You can no longer do window.api = .... You now have to do:

//preload.js
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => require('fs').unlink(f)
})

Electron 6 require()ing node builtins in sandboxed renderers no longer implicitly loads the remote version

If Renderer has sandbox set to true, you have to do:

//preload.js
const { contextBridge, remote } = require('electron')

contextBridge.exposeInMainWorld('api', {
    deleteFile: f => remote.require('fs').unlink(f)
})

Electron 10 enableRemoteModule default to false (remote module deprecated in Electron 12)

The remote module is used when you need to access Node APIs from a sandboxed Renderer (as in above example); or when you need to access Electron APIs that are available only to the Main process (such as dialog, menu). Without remote, you'll need to write explicit IPC handlers like follows.

//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 flag (removed in Electron 12)

Recommendation

Always set {nodeIntegration: false, contextIsolation: true, enableRemoteModule: false}.

For max security, set {sandbox: true}. Your preload script will have to use IPC to call the Main process to do everything.

If sandbox is false, your preload script can access Node API directly, as in require('fs').readFile. You're secure as long as you don't this:

//bad
contextBridge.exposeInMainWorld('api', {
    readFile: require('fs').readFile
})
Moorings answered 3/2, 2021 at 20:44 Comment(0)
M
10

Are you using nodeIntegration: false while BrowserWindow initialization? If so, set it to true (defaults value is true).

And include your external scripts in the HTML like this (not as <script> src="./index.js" </script>):

<script>
   require('./index.js')
</script>
Marniemaro answered 6/6, 2017 at 16:11 Comment(4)
I'm using pdf js offline with this.So when I'm using nodeIntegration: true then PDFJS.getDocument is not a function error will arrived.How to set nodeIntegration: true in my html page when pdfjs is completely loaded.Samy
Have you looked at this example? You may be able to just import the package via var pdfjsLib = require('pdfjs-dist') and use it this way.Marniemaro
Why do you recommend to use require instead of <script src="..."></script>? This also has an unanswered question here.Noam
@Noam Webpack answers this question: it's hard to tell what a script depends on, dependency order must be managed, and unnecessary code will still be downloaded and executed.Resentful
S
5

All I wanted to do was to require a js file in my html page because of the tutorial I was following. However, I intend to use remote modules so security was paramount. I modified Michael's answer up there so I'm posting, purely for those who spent hours looking for a secure alternative to 'require' like me. If the code is incorrect, feel free to point it out.

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

I am especially curious to know if this still presents a security risk. Thanks.

Satanism answered 20/10, 2020 at 10:35 Comment(1)
Thanks Joseph. This worked well for me and provided a way to bootstrap React with TypeScript (No Webpack) as i was running into issues referencing the renderer direct from a script tag. I did end up adding a whitelist of 'files' to further box what would be loaded. Ideally i will move to sandbox = true in the future.Cryptogenic
B
4

If you just don't care about any security issues and want to have require being interpreted correctly by JavaScript on the browser window, then have an extra flag on the main.js code:

webPreferences: {
            nodeIntegration: true,
            nodeIntegrationInWorker: true,
            nodeIntegrationInSubFrames: true,
            enableRemoteModule: true,
            contextIsolation: false //required flag
        }
        
//rest of the code...
Brassard answered 17/1, 2022 at 8:40 Comment(0)
B
2

You have to enable the nodeIntegration in webPreferences to use it. see below,

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

There was a breaking api changes in electron 5.0(Announcement on Repository). In recent versions nodeIntegration is by default set to false.

Docs Due to the Node.js integration of Electron, there are some extra symbols inserted into the DOM like module, exports, require. This causes problems for some libraries since they want to insert the symbols with the same names.To solve this, you can turn off node integration in Electron:

But if you want to keep the abilities to use Node.js and Electron APIs, you have to rename the symbols in the page before including other libraries:

<head>
    <script>
        window.nodeRequire = require;
        delete window.require;
        delete window.exports;
        delete window.module;
    </script>
    <script type="text/javascript" src="jquery.js"></script>
</head>
Buchenwald answered 6/11, 2019 at 10:14 Comment(0)
B
1

For sake of actuality and completeness I am adding my piece of cake. Here is what I find important about this topic. Please keep in mind the date of this post - October 2022, the version of electron is 21.1.1. There is an article in electron docs called Inter-Process Communication where this topic is described in a very clear way.

The following code is just a copy of the example code on that aforementioned site.

The main.js file:

const {app, BrowserWindow, ipcMain} = require('electron')
const path = require('path')

function createWindow () {
  const mainWindow = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })

  ipcMain.on('set-title', (event, title) => {
    const webContents = event.sender
    const win = BrowserWindow.fromWebContents(webContents)
    win.setTitle(title)
  })

  mainWindow.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()
  
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

The takeaway:

  • in webPreferences define only the preload script and leave all those nodeIntegration, nodeIntegrationInWorker, nodeIntegrationInSubFrames, enableRemoteModule, contextIsolation apply the defaults.

The next file is preload.js:

const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    setTitle: (title) => ipcRenderer.send('set-title', title)
})

Here the electronAPI object will be injected into the browsers context so there will be a window.electronAPI object which will have a member function called setTitle. Of course you can add whatever other properties there.

The setTitle function only calls ipcRenderer.send which is one end of the Inter-Process Communication brigde or tunnel if you like.

What you send in here falls out on the other end, which is in the main.js file, the ipcMain.on function. Here you register for the set-title event.

The example continues with the index.html file:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Hello World!</title>
  </head>
  <body>
    Title: <input id="title"/>
    <button id="btn" type="button">Set</button>
    <script src="./renderer.js"></script>
  </body>
</html>

which loads the renderer.js script:

const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
    const title = titleInput.value
    window.electronAPI.setTitle(title)
});

and there you can access the window.electronAPI.setTitle function, which you defined in preload.js where it sends the title into ipcRenderer and this title then falls out of ipcMain in main.js fireing an event and causing a function to run which in turn sets the application title.

So once again I want to emphasize to read the documentation. There is more about IPC with exanples. Also read the Context Isolation chapter, it is short and very clear.

Bevel answered 20/10, 2022 at 20:42 Comment(0)
S
-3

Finally, I made it work.Add this code to your HTML document Script Element.

Sorry for the late Reply.I use the below code to do this thing.

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

And use nodeRequire instead of using require.

It works Fine.

Samy answered 5/1, 2018 at 10:2 Comment(1)
Please share your HTML Page Code.Woolly

© 2022 - 2024 — McMap. All rights reserved.