Electron "require is not defined" - javascript

I'm making an application which I need to give access to the file system (fs) module, however even with nodeIntegration enabled the renderer gives me this error:
Uncaught ReferenceError: require is not defined
All similar problems I could find had a solution that said they needed to turn nodeIntegration on, however I already have it enabled.
This is my main.js:
const electron = require('electron');
const {app, BrowserWindow} = electron;
let win;
app.on('ready', () => {
var { width, height } = electron.screen.getPrimaryDisplay().workAreaSize;
width = 1600;
height = 900;
win = new BrowserWindow({'minHeight': 850, 'minWidth': 1600, width, height, webPreferences: {
contextIsolation: true,
webSecurity: true,
nodeIntegration: true
}});
win.setMenu(null);
win.loadFile('index.html');
win.webContents.openDevTools()
});
My index.js, linked in index.html as <script src="index.js"></script> currently only has require("fs"); in it, I've commented out all the other stuff.
I don't know why require still doesn't work even though nodeIntegration is enabled.

When you have nodeIntegration disabled but aren't using contextIsolation, you could use a preload script to expose a safe version of it on the global object. (Note: you shouldn't expose the entire fs module to a remote page!)
Here's an example of using a preload script in this way:
// main process script
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: false,
nodeIntegration: false,
preload: './preload.js'
}
})
mainWindow.loadURL('my-safe-file.html')
// preload.js
const { readFileSync } = require('fs')
// the host page will have access to `window.readConfig`,
// but not direct access to `readFileSync`
window.readConfig = function () {
const data = readFileSync('./config.json')
return data
}
// renderer.js
const config = window.readConfig()
If you're only loading local pages, and those pages don't load or execute unsafe dynamic content then you might reconsider the use of contextIsolation for this strategy. If you want to keep contextIsolation on, however (and you definitely should if you have a chance of showing unsafe content), you can only communicate with the preload script with message passing via postMessage.
Here's an example of the same scenario above, but with contextIsolation on and using message passing.
// main process script
const mainWindow = new BrowserWindow({
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
preload: './preload.js'
}
})
mainWindow.loadURL('my-unsafe-file.html')
// preload.js
const { readFileSync } = require('fs')
const readConfig = function () {
const data = readFileSync('./config.json')
return data
}
window.addEventListener('message', (event) => {
if (event.source !== window) return
if (event.data.type === 'request') {
window.postMessage({ type: 'response', content: readConfig() })
}
})
// renderer.js
window.addEventListener('message', (event) => {
if (event.source !== window) return
if (event.data.type === 'response') {
const config = event.data.content
}
})
window.postMessage('request')
While this is definitely more verbose and difficult to deal with (and forces things to be async, because message passing is async), it's also much more secure. A pair of small JS wrappers around the postMessage API could make this easier to work with (e.g. via an RPC-like mechanism), but remember that the whole point of using contextIsolation is because you can't trust the renderer, so your preload script shouldn't trust just any message it gets via the postMessage API — you should always verify the event that you receive to ensure that you trust it.
This slide deck describers in detail why turning off Node integration without using context isolation is not always a good idea.

Related

Display full folder path in console using electron and Javascript

Im using electron with a python backend (for a stand alone desktop application) and I need to supply the python script with a directory. With the following code I can get a dialog to open however, it will not output the folder path to the console.
const OpenBtn = document.getElementById('OpenBtn')
OpenBtn.addEventListener('click', function(event) {
const { dialog } = require('electron').remote;
//Synchronous
let dir = dialog.showOpenDialog({properties:["openDirectory"]})
console.log(dir)
})
I am new to the frontend aspects of creating apps and I am trying to understand what is contained in dir. I see it produces a "promise" (I've tried various ways of accessing the filePaths string, but without success.
There is an HTML button with id=OpenBtn, and I have
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true
}
in my main.js file.
Either use the synchronous showOpenDialogSync:
let dirs = dialog.showOpenDialogSync({properties:["openDirectory"]})
if (typeof dirs !== "undefined") {
console.log("Selected paths:");
console.log(dirs);
}
Or the asynchronous showOpenDialog:
dialog.showOpenDialog({properties: ["openDirectory"]}).then(result => {
if (result.canceled === false) {
console.log("Selected paths:");
console.log(result.filePaths);
}
}).catch(err => {
console.log(err);
})

Using the electron ipcRenderer from a front-end javascript file

I'm in the process of learning to use Electron, and while trying to have my application communicate with the front end I am aware I need to use the ipcRenderer to gain a reference to the DOM elements and then pass that information to ipcMain.
I tried to follow much of the advice suggested here and here, but both of these examples use require('electron').ipcMain and whenever I try to include my script that will be interacting with the front-end into my HTML, nothing occurs since Uncaught ReferenceError: require is not defined. I've been searching for a few hours and haven't had any luck finding a solution - so clearly I'm doing something wrong.
My main.js is very simple, I just create my window and then I create an ipc listener as so:
const { app, BrowserWindow } = require("electron");
const ipc = require('electron').ipcMain;
function createWindow() {
const window = new BrowserWindow({
transparent: true,
frame: false,
resizable: false,
center: true,
width: 410,
height: 550,
});
window.loadFile("index.html");
}
app.whenReady().then(createWindow);
ipc.on('invokeAction', (event, data) => {
var result = "test result!";
event.sender.send('actionReply', result);
})
Within the file that I wish to manipulate the DOM with, I attempt to get the element ID and then add an event listener as seen here:
const ipc = require('electron').ipcRenderer;
const helper = require("./api");
var authenticate_button = ipcRenderer.getElementById("authenticate-button");
var authButton = document.getElementById("authenticate-button");
authButton.addEventListener("click", () => {
ipc.once('actionReply', (event, response) => {
console.log("Hello world!");
})
ipc.send('invokeAction');
});
function onAuthenticateClick() {
helper.authenticateLogin(api_public, api_secret, access_public, access_secret);
}
and finally, my HTML only consists of a button that I wish to attach my event listener to:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Project Test</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="main-container">
<button id="authenticate-button" type="submit" onclick="">Authenticate</button>
<p id="status-label">Not Authenticated</p>
</div>
<script src="script.js"></script>
</body>
</html>
If anyone could help point me in the right direction as to how to get this basic functionality to work, it would be very helpful!
As mentioned by AlekseyHoffman, the reason you can't access ipcRenderer in your frontend js file is because you have nodeIntegration set to false. That said, there's a reason it's set to false by default now; it makes your app far less secure.
Let me suggest an alternate approach: rather than trying to access ipcRenderer directly from your frontend js by setting nodeIntegration to true, access it from preload.js. In preload.js, you can selectively expose ipcMain functions (from your main.js file) you want to access on the frontend (including those that can send data back from main.js), and call them via ipcRenderer there. In your frontend js, you can access the preload.js object that exposes those functions; preload.js will then call those main.js functions via ipcRenderer and return the data back to the frontend js that called it.
Here's a simple, but fully working example (these files should be sufficient to build an electron app with two-way communication between main.js and frontend. In this example, all of the following files are in the same directory.):
main.js
// boilerplate code for electron..
const {
app,
BrowserWindow,
ipcMain,
contextBridge
} = require("electron");
const path = require("path");
let win;
/**
* make the electron window, and make preload.js accessible to the js
* running inside it (this will allow you to communicate with main.js
* from the frontend).
*/
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,
preload: path.join(__dirname, "./preload.js") // path to your preload.js file
}
});
// Load app
win.loadFile(path.join(__dirname, "index.html"));
}
app.on("ready", createWindow);
// end boilerplate code... now on to your stuff
/**
* FUNCTION YOU WANT ACCESS TO ON THE FRONTEND
*/
ipcMain.handle('myfunc', async (event, arg) => {
return new Promise(function(resolve, reject) {
// do stuff
if (true) {
resolve("this worked!");
} else {
reject("this didn't work!");
}
});
});
Note, I'm using an example of ipcMain.handle because it allows two-way communication and returns a Promise object - i.e., when you access this function from the frontend via preload.js, you can get that Promise back with the data inside it.
preload.js:
// boilerplate code for electron...
const {
contextBridge,
ipcRenderer
} = require("electron");
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
// end boilerplate code, on to your stuff..
/**
* HERE YOU WILL EXPOSE YOUR 'myfunc' FROM main.js
* TO THE FRONTEND.
* (remember in main.js, you're putting preload.js
* in the electron window? your frontend js will be able
* to access this stuff as a result.
*/
contextBridge.exposeInMainWorld(
"api", {
invoke: (channel, data) => {
let validChannels = ["myfunc"]; // list of ipcMain.handle channels you want access in frontend to
if (validChannels.includes(channel)) {
// ipcRenderer.invoke accesses ipcMain.handle channels like 'myfunc'
// make sure to include this return statement or you won't get your Promise back
return ipcRenderer.invoke(channel, data);
}
},
}
);
renderer process (i.e. your frontend js file - I'll call it frontend.js):
// call your main.js function here
console.log("I'm going to call main.js's 'myfunc'");
window.api.invoke('myfunc', [1,2,3])
.then(function(res) {
console.log(res); // will print "This worked!" to the browser console
})
.catch(function(err) {
console.error(err); // will print "This didn't work!" to the browser console.
});
index.html
<!DOCTYPE html>
<html>
<head>
<title>My Electron App</title>
</head>
<body>
<h1>Hello Beautiful World</h1>
<script src="frontend.js"></script> <!-- load your frontend script -->
</body>
</html>
package.json
{
"name": "myapp",
"main": "main.js",
"scripts": {
"start": "electron ."
}
}
The files above should be sufficient to have a fully working electron app with communication between main.js and the frontend js. Put them all in one directory with the names main.js, preload.js, frontend.js, and index.html, and package.json and launch your electron app using npm start. Note that in this example I am storing all the files in the same directory; make sure to change these paths to wherever they are stored on your system.
See these links for more info and examples:
Electron documentation on inter-process communication
An overview of why IPC is needed and the security issues of setting nodeintegration to true
The require is not defined because you didn't enable nodeIntegration on the window. Set it to true in your window config:
const window = new BrowserWindow({
transparent: true,
frame: false,
resizable: false,
center: true,
width: 410,
height: 550,
webPreferences: {
nodeIntegration: true
}
})

Would I be able to give access to specific electron APIs safely?

So I need to find a way to create custom window titleBar buttons that I can add functionality to safely without enabling nodeIntegration in electron. I was thinking the preload might be what I need but I'm not sure how this works or if it would work for this.
Since I'm creating custom window buttons with HTML, CSS and Javascript, I need these methods:
mainWindow.minimize();
mainWindow.close();
mainWindow.getBounds();
mainWindow.setBounds(...);
mainWindow.setResizable(...);
This is in the renderer process so nodeIntegration would need to be enabled and would need to use remote like this:
const { remote } = require('electron');
const mainWindow = remote.getCurrentWindow();
Would I be able to use the preload option with nodeIntegration disabled to access these methods to add functionality to my custom buttons? If so, how? Would it be safe this way?
You could add a preload script which provides some APIs, just like the following one:
const { remote } = require("electron");
function initialise () {
window.Controls = {
minimize: () => { remote.getCurrentWindow ().minimize (); },
close: () => { remote.getCurrentWindow ().close (); },
getBounds: () => { remote.getCurrentWindow ().getBounds (); },
setBounds: (bounds) => { remote.getCurrentWindow ().setBounds (bounds); },
setResizable: (resizable) => { remote.getCurrentWindow ().setResizable (resizeable); }
};
}
initialise ();
Then, you can use the functions defined like this in your renderer process:
document.getElementById ("close-button").addEventListener ("click", (e) => {
window.Controls.close ();
});
This reduces the risk of executing insecure code by just setting nodeIntegration: true on the BrowserWindow. However, all code which has access to window.Controls will be able to manipulate the window state.

How to access jquery from the render process inside the preload script on Electron

I configured the Node integration to false in my window because, as described in the documentation, disabling is a good practice.
I want to access the jQuery of my render process in my preload script, but I don't know How I can do this.
Is it possible to activate a modal (for example) after some event occurs in the preload script?
Link to my example code in Github: https://github.com/JhonatanRSantos/Sample
As Janith said, you don't need to use a preload script. Here is a working sample based on your Github repo :
Index.html - in HEADER part, modify like this :
<script>window.$ = window.jQuery = require('jquery');</script>
<script src="./node_modules/popper.js/dist/umd/popper.min.js"></script>
<script src="./node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
Note that you have to use the path ./node_modules/popper.js/dist/umd/popper.min.js or you will get an error in the console (see popper.js in bootstrap 4 gives SyntaxError Unexpected token export)
Index.html - FOOTER, add :
<script src="./index.js"></script>
Rename preload.js to index.js
index.js can be :
console.log('Preload.js');
setTimeout(() => {
console.log('Open Boostrap Modal');
$('#myModal').modal('show');
}, 5000);
and finally, main.js can be :
const { app, BrowserWindow } = require('electron');
const path = require('path')
const url = require('url')
let createWindow = () => {
let win = new BrowserWindow({
width: 800,
height: 500,
center: true,
resizable: false,
show: false
});
win.setMenu(null);
const mainUrl = url.format({ // https://electronjs.org/docs/api/browser-window#winloadurlurl-options
protocol: 'file',
slashes: true,
pathname: path.join(__dirname, 'index.html')
})
win.loadURL(mainUrl);
win.once('ready-to-show', () => {
win.show();
win.webContents.openDevTools();
});
win.on('closed', () => {
win = null;
});
};
app.on('ready', createWindow);
I made a pull request to your repo to get changes.

Opening a local pdf file using node.js and express

I am attempting to make a pdf viewer app using electron and electron-pdf-window
the code below works when i want to open from a URL file path, but when i tried to open a pdf from my local files using the file:/// the application download the pdf instead of viewing it on my window.
const { app } = require('electron')
const PDFWindow = require('electron-pdf-window')
app.on('ready', () => {
const win = new PDFWindow({
width: 800,
height: 600
})
win.loadURL('file://///C://username/desktop/myfile.pdf')
})
I tried also below code but below error displays.
TypeError: Cannot match against 'undefined' or 'null'.
const { BrowserWindow } = require('electron').remote
const PDFWindow = require('electron-pdf-window')
const win = new BrowserWindow({ width: 800, height: 600 })
PDFWindow.addSupport(win)
win.loadURL('file://///C://username/desktop/myfile.pdf')
Is there another way to open local pdf files from my PC directory?
Base on this readme, https://github.com/electron/electron/blob/master/docs/api/browser-window.md
you can do it something like
win.loadURL(`file://${__dirname}/app/index.html`)
but you have to put this inside app.on('ready', function() {} to avoid getting the Cannot create BrowserWindow before app is ready error.
Reason why that error appears
Because the app is not yet ready and is still loading
You need Modify electron-pdf-window package.
Open electron-pdf-window/index.js
Go to comment line 23 const->let fileUrl = url.replace(/^file:///i, '');
firstly change const via let,then add after this code
fileUrl = fileUrl.replace('/', '');
you can use normally ;
const w= new BrowserWindow({
width: 1200,
height: 920,
webPreferences: {
nodeIntegration: true,
contextIsolation: true
}
});
PDFWindow.addSupport(w)
w.loadURL(url.format ({
pathname: path.join(__dirname, '../pdf/123.pdf'),
protocol: 'file:',
slashes: true
}));

Categories

Resources