Unable to use Node.js APIs in renderer process
Asked Answered
A

3

9

Unable to use any electron or node related operations in electron . Getting error process not defined. I Checked at various places they guide to add node Support but that is already Done so stucked here My Main Application code is

const electron = require("electron");
const { app, BrowserWindow } = electron;

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: { nodeIntegration: true },
  });

  win.loadFile("index.html");
}

app.whenReady().then(createWindow);

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});

app.on("activate", () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

And Index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World!</title>
  </head>
  <body style="background: white">
    <h1>Hello World!</h1>
    <p>
      We are using node
      <script>
        document.write(process.versions.node);
      </script>
      , Chrome
      <script>
        document.write(process.versions.chrome);
      </script>
      , and Electron
      <script>
        document.write(process.versions.electron);
      </script>
      .
    </p>
  </body>
</html>
Alla answered 3/3, 2021 at 10:35 Comment(1)
Error From Index.html FileAlla
P
17

Update: the answer below is a workaround. You should not disable contextIsolation and you should not enable nodeIntegration. Instead you should use a preload script and the contextBridge API.

In Electron 12, contextIsolation is now by default true

If you set it to false, you will have access to Node.js APIs in the renderer process

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: { 
     contextIsolation: false,
     nodeIntegration: true
    },
  });

  win.loadFile("index.html");
}

⚠️ It's important to note that this is not recommended!

There's a good reason why Electron maintainers changed the default value. See this discussion

Without contextIsolation any code running in a renderer process can quite easily reach into Electron internals or your preload script and perform privileged actions that you don't want arbitrary websites to be doing.

Popp answered 3/3, 2021 at 10:52 Comment(3)
@vikrantverma Just adding here that you probably do want to keep contextIsolation enabled for security reasons. See the answer hereSetscrew
OK, but I'm not running an "arbitrary website" in my renderer. I'm running MY APP! I don't get how this is a "security problem"?Noel
@SevenSystems A typical web app can't access the user file system even if it tried. What that quote is saying is that if you embed that app into Electron that becomes a reality! I guess you'll be using libraries either via <script> or NPM? If you accidentally mistyped the name of dependency (i.e. a typosquatting attack) you could be shipping an app to your users bundled with password-stealing malwares. Check out this example of mine to see this in action.Starobin
S
8

There is no reason to elevate the privileges of your renderer. Any third-party scripts on that page would run with the same privileges and that's definitely not what you want.

Instead you should use a preload script which has that privilege (i.e. can use Node.js APIs by default) but keep contextIsolation=true (which is the default value anyway). If you need to share data between your preload script and your renderer script use contextBridge.

In my example I have exposed data from the preload script to the renderer script under a rather silly namespace (window.BURRITO) to make it obvious that you're in charge:

main.js

const {app, BrowserWindow} = require('electron'); //<- v13.1.7
const path = require('path');

app.whenReady().then(() => {
  const preload = path.join(__dirname, 'preload.js');
  const mainWindow = new BrowserWindow({ webPreferences: { preload }});
  mainWindow.loadFile('index.html');
});

preload.js

const {contextBridge} = require('electron');

contextBridge.exposeInMainWorld('BURRITO', {
  getNodeVer: () => process.versions.node,
  getChromeVer: () => process.versions.chrome,
  getElectronVer: () => process.versions.electron
});

renderer.js

const onClick = (sel, fn) => document.querySelector(sel).addEventListener('click', fn);
onClick('#btn1', () => alert(BURRITO.getNodeVer()));
onClick('#btn2', () => alert(BURRITO.getChromeVer()));
onClick('#btn3', () => alert(BURRITO.getElectronVer()));

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <button id="btn1">Node?</button>
    <button id="btn2">Chrome?</button>
    <button id="btn3">Electron?</button>
    <script src="./renderer.js"></script>
  </body>
</html>

enter image description here

Starobin answered 26/7, 2021 at 21:27 Comment(0)
K
1

Stop enabling contextIsolation or nodeIntegration needlessly.

I had a problem of Unable to load module util and util is not defined, but this was happening because process wasn't defined, and that was causing issues. Sometimes I'd see process is not defined.

Everywhere I looked, I was seeing the same answer, fiddling with contextIsolation or nodeIntegration, but these should be avoided. Nearly every time, someone was saying that's bad practice in the comments. They raise security issues that may be made worse by using node-modules that contain malware and/or bad code. In environments like Node, a significant portion/majority of "our code" is not "our code."


I started with https://github.com/electron-react-boilerplate/electron-react-boilerplate#readme.

But I was getting the same errors that brought Electron people to this question.

Finally, out of desperation, I DID enable the parameters, and my app worked fine. That helped me nail down that the problem wasn't a module that "just didn't work" with Electron.

So I did this

contextBridge.exposeInMainWorld('process', process)

preload.js does not operate in the window context. There is, far as I can tell, no way to access it (which is the point). So this makes it available.

In order to expose other variables, you can do

contextBridge.exposeInMainWorld('My_Magic_Variable', {
    foo: "bar", 
    hello: "world"
})

If you're working in typescript, you also need to declare this somewhere like global.d.ts

declare global {
    // eslint-disable-next-line no-unused-vars
    interface Window {
        electron: ElectronHandler;
    }
    var My_Magic_Variable: {
        foo: string,
        hello: string
    }
}

export {} // This line forces the environment to recognize it as a module

And then, in your code-base, you can access My_Magic_Variable and its properties

Here's a type-aware version of exposeInMainWorld

type GlobalThis = typeof globalThis;
function makeGlobal<TProp extends keyof GlobalThis = keyof GlobalThis,
  TValue extends GlobalThis[TProp] = GlobalThis[TProp]>(prop: TProp, value: TValue) {
    contextBridge.exposeInMainWorld(prop as string, value);
}

makeGlobal('process', process); // works
makeGlobal('process', window); // raises exception
Karlakarlan answered 24/2 at 23:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.