Electron: can't access main.js from another .js file - node.js

I have all my html handlers in a second file (b.js). They look like this:
window.onload = function () {
let btn = window.getElementById('btn');
button.addEventListener('click', fn);
}
This works fine, but I want to make a button to open another window, so I tried adding an exported method in main.js. My full main.js is below:
const {app, BrowserWindow} = require('electron');
let mainWindow;
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 500,
height: 300,
frame: false,
transparent: true,
resizable: false,
webPreferences: {
nodeIntegration: true,
}
});
mainWindow.setResizable(false);
mainWindow.loadFile('index.html');
mainWindow.webContents.openDevTools();
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow.quit();
})
}
app.on('ready', createWindow);
module.exports = {
openMainScreen: function () {
mainWindow.loadFile("mainScreen.html");
mainWindow.resizeTo(1200, 800);
}
};
If I try to require(main.js)in b.js as I thought I should: I get this error:
Uncaught TypeError: Cannot read property 'on' of undefined
Pointing to app.on('ready'.... Looking at this post: Cannot read property 'on' of undefined in electron javascript It says the app is starting twice. How can I resolve this?

The way I fixed this was to use electrons built in IPCmain to communicate between javascript that was being rendered with the html and the main javascript that ran the app.
https://electronjs.org/docs/api/ipc-main

Related

Preload script not loading in electron react

I'm building an app with React and Electron and trying to create a communication channel between the main (electron.js in my case) and renderer processes. By achieving this, I want to access the Store object created in my main process to save user preferences etc.
I have set up a preload script and provided the path in my main.js (electron.js). But when I try to access the method defined in the preload.js from the renderer like this window.electronAPI.sendData('user-data', this.state), it does not recognise the electronAPI (undefined variable electronAPI). Also the console.log in my preload.js is never shown when a window is loaded. Hence I assume the preload script never gets loaded and I don't know why. Any help is very much appreciated!
EDIT: The preload script seems to load now because the console.log gets printed. Maybe preload.js did load before too but I could not see it since I could only access the application by opening localhost:3000 in the browser. Now the app opens in a BrowserWindow. However the 'electronAPI' defined in the preload script cannot be accessed still.
Here is the code:
electron.js (main.js)
const electron = require('electron');
const path = require('path');
const {app, BrowserWindow, Menu, ipcMain} = electron;
let isDev = process.env.APP_DEV ? (process.env.APP_DEV.trim() === "true") : false;
function createWindow (){
const win = new BrowserWindow({
width: 970,
height: 600,
backgroundColor: '#0C0456',
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js')
}
});
//if in development mode, load window from localhost:3000 otherwise build from index.html
try {
win.webContents.loadURL(isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname,'../build/index.html')}`
)
} catch(e){
console.error(e)
}
win.webContents.openDevTools({"mode":"detach"});
//Remove menu
Menu.setApplicationMenu(null);
win.once('ready-to-show', () => win.show());
console.log("main window is shown");
console.log(typeof path.join(__dirname, 'preload.js'));
win.on('crashed',() => {
console.error(`The renderer process has crashed.`);
})
}
app.on( 'ready', () => {
createWindow(); // open default window
} );
ipcMain.on('user-data', (event, args)=>{
console.log(args);
});
preload.js
const {contextBridge, ipcRenderer} = require("electron");
const validChannels = ['user-data'];
console.log("this is the preload script");
contextBridge.exposeInMainWorld('electronAPI', {
sendData: (channel, data) => {
if(validChannels.includes(channel)){
ipcRenderer.send(channel, data);
}
}
})

Electron window.require is not a function even with nodeIntegration set to true

I've been experiencing some issues with an Electron + Create React App app I'm making. It's an offline app for cost calculation, I need to persist a couple user settings, for this I used https://github.com/sindresorhus/electron-store. Like with most electron's modules, I have to import it as:
const Store = window.require("electron-store");
To avoid webpack's conflicts. By searching I found that for most people setting nodeIntegration: true when creating electron's BrowserWindow would avoid the problem, but it's not my case, I keep getting the same error.
What I've already tried:
Using plain require: It results in TypeError: fs.existsSync is not a function, and in console: Can't resolve 'worker_threads' in '...\node_modules\write-file-atomic'
Use a module to override webpack config: I used craco to set target to electron-renderer. It results in a blank page when I launch the app, with an error in devtools telling ReferenceError: require is not defined
Additional info is that I'm not using typescript but plain js so using "declare global" and such won't work
My public/electron.js file:
const electron = require("electron");
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require("path");
const isDev = require("electron-is-dev");
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 680,
webPreferences: {
nodeIntegration: true
}
});
mainWindow.loadURL(
isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../build/index.html")}`
);
mainWindow.on("closed", () => (mainWindow = null));
if (!isDev) mainWindow.setMenu(null);
}
app.on("ready", createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
electron.app.allowRendererProcessReuse = true;
app.on("activate", () => {
if (mainWindow === null) {
createWindow();
}
});
You have tu create a preoload script on electron.
Create a file named preload.js on the directory where you have de electron main script, Put this code on it:
window.require = require;
Go to your electron main script and type this in the code where you create the window:
win = new BrowserWindow({
width: 1280,
height: 720,
webPreferences: {
nodeIntegration: false,
preload: __dirname + '/preload.js'
},
})
With this you will preload the script before all, this fixed the problem for me, I hope also for you.
Electron 12 changed the default setting of contextIsolation for webPreferences, as documented in their breaking changes for Electron 12. Setting it to false will allow you access to require() again, as per their notes:
In Electron 12, contextIsolation will be enabled by default. To restore the previous behavior, contextIsolation: false must be specified in WebPreferences.
We recommend having contextIsolation enabled for the security of your application.
Another implication is that require() cannot be used in the renderer process unless nodeIntegration is true and contextIsolation is false.
mainWindow = new BrowserWindow({
width: 900,
height: 680,
webPreferences: {
contextIsolation: false,
nodeIntegration: true
}
}

electron prevent multiple instance with middle click

I am tying to make single instance Electron application.
I am using app.makeSingleInstance , see my sample below.
SingleInstance issue with middle click :
Single Instance works if I click on app.exe 2nd time
It does not work if I middle click on a link inside my app
What I need:
Make electron app singleInstance and ensure it remaisn single instance even with middle click.
I dont want to compeltey disable middle click in my app as at some places, I have a use case for them on non-link items
How to reproduce:
use repo: https://github.com/electron/electron-quick-start
replace existing with my index.html and main.js , see below
npm install and then npm start
index.html:
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Hello World!</title></head>
<body>
<h1>app.makeSingleInstance()</h1>
Middle Click on it
</body>
</html>
main.js
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
let mainWindow
const isSecondInstance = app.makeSingleInstance((commandLine, workingDirectory) => {
if (myWindow) {
if (myWindow.isMinimized()) myWindow.restore()
myWindow.focus()
}
})
if (isSecondInstance) {
app.quit()
}
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
The middle click does not create a new instance of your application, but rather a new instance of a BrowserWindow. You can disable middle-clicks on a (actually all) elements using the auxclick event.
In your main window's HTML you could put the following JavaScript to disable middle-clicks on link elements if you do not want to redirect these events to your default browser:
// The following function will catch all non-left (middle and right) clicks
function handleNonLeftClick (e) {
// e.button will be 1 for the middle mouse button.
if (e.button === 1) {
// Check if it is a link (a) element; if so, prevent the execution.
if (e.target.tagName.toLowerCase() === "a") {
e.preventDefault();
}
}
}
window.onload = () => {
// Attach the listener to the whole document.
document.addEventListener("auxclick", handleNonLeftClick);
}
But you can also choose to redirect the middle-click events to your standard browser, namely via Electron's shell module:
// Require Electron's "shell" module
const { shell } = require("electron");
function handleNonLeftClick (e) {
// e.button will be 1 for the middle mouse button.
if (e.button === 1) {
// Check if it is a link (a) element; if so, prevent the execution.
if (e.target.tagName.toLowerCase() === "a") {
// Prevent the default action to fire...
e.preventDefault();
// ...and let the OS handle the URL.
shell.openExternal(e.target.href);
}
}
}
// Also attach the listener this time:
window.onload = () => { document.addEventListener("auxclick", handleNonLeftClick); }
You could remove the if (e.button === 1) if you also want to block right-clicks on a elements.

Electron loading animation

Could someone please help me to implement a loading animation for my Electron app ?
I am learning this new technology and it would be nice to understand the correct way to do that.
I am thinking about something like :
app.on('ready', () => {
// show main content
})
app.on('not-ready', () => {
// show loading animation
})
As far as I know there is no event emitted from app before ready (only exception is will-finish-launching available only on macOS).
Furthermore, you cannot open any BrowserWindow before app is ready, so you should really wait it.
However, if your main application window loading very slow, you can still open a "loading window" before that and switch them when your main window is ready.
const { app, BrowserWindow } = require('electron')
app.on('ready', () => {
let main = null
let loading = new BrowserWindow({show: false, frame: false})
loading.once('show', () => {
main = new BrowserWindow({show: false})
main.webContents.once('dom-ready', () => {
console.log('main loaded')
main.show()
loading.hide()
loading.close()
})
// long loading html
main.loadURL('http://spacecrafts3d.org')
})
loading.loadURL('loding.html')
loading.show()
})
You can use win.on('ready-to-show') instead of win.webContents.on('dom-ready') everywhere if you want to eliminate visual flash (but losing some speed)
window.open()
If you want to do the same for BrowserWindow opened in renderer process by window.open(), you can use new-window event of webContents if nativeWindowOpen is true
main = new BrowserWindow({
webPreferences: {
nativeWindowOpen: true
}
})
main.webContents.on('new-window', (event, url) => {
// there are more args not used here
event.preventDefault()
const win = new BrowserWindow({show: false})
win.webContents.once('dom-ready', () => {
win.show()
loading.hide() // don't destroy in this case
})
win.loadURL(url)
loading.show()
event.newGuest = win
})
It can be done by displaying a new BrowserWindow displaying the activity loader , until the main app fully loads .
Let's define a createWindow funtion (as given in docs) which is responsible for loading the main app for the user as :
var loadingwindow = null; // Responsible for creating activity loader
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
})
win.loadFile('index.html')
loadingwindow.hide() // Used to hide the loading screen when the contents in main app are loaded
}
Now , in order to display the loadingwindow screen , we need to place it in app.on('ready' , callback_fn) as shows here :
app.on("ready" , () => {
loadingwindow = new BrowserWindow({
frame : false,
movable : false,
})
loadingwindow.loadFile('activity.html') // To load the activity loader html file
loadingwindow.show();
})
That's it ! ,
To check if it is working properly , wrap the setTimeout function over the app.whenReady().then(createWindow)

Electron.remote is undefined

I have trouble with using Electron. As you can see the title, when i load remote module, it saids it is undefined. This is the code of entry js:
const electron = require('electron');
const { app, BrowserWindow, Tray, remote, ipcMain } = electron;
function initApp() { ... }
app.on('ready', () => {
initApp();
console.log(electron); // object, but no remote inside
console.log(electron.remote); // undefined
console.log(remote); // undefined
});
and i tried to follow official doc here: http://electron.atom.io/docs/api/remote/
with
const { remote } = electron;
const { BrowserWindow } = remote;
let win = new BrowserWindow({width: 800, height: 600}); // error! BrowserWindow is not a constructor blabla
...
remote.getCurrentWindow().focus();
i don't know what am i missing. any advice will very appreciate.
Update 2020, since this answer still appears at the top. For the original answer to work in current versions of Electron, you need to set enableRemoteModule when creating the window in your main process.
const myWindow = new BrowserWindow({
webPreferences: {
enableRemoteModule: true
}
});
Original answer:
remote is needed only to require other modules from inside a render process. In the main process you just get your modules directly from require('electron'). Which it looks like is done in the example just with remote unnecessarily added.
Render process:
const { remote } = require('electron');
const { BrowserWindow } = remote;
Main process:
const { BrowserWindow } = require('electron');
In electron 10.0.0, remoteModule is set false by default. So, if you want to use const {BrowserWindow, dialog } = require('electron').remote; in JavaScript file, then you must set enableRemoteModule as true in webPreferences.
const w = new BrowserWindow({
webPreferences: {
enableRemoteModule: true
}
});
link: https://github.com/electron/electron/blob/master/docs/breaking-changes.md#default-changed-enableremotemodule-defaults-to-false
The remote module was deprecated in Electron 12, and will be removed in Electron 14. It is replaced by the #electron/remote module.
// Deprecated in Electron 12:
const { BrowserWindow } = require('electron').remote
// Replace with:
const { BrowserWindow } = require('#electron/remote')
// In the main process:
require('#electron/remote/main').initialize()
remote becomes undefined sometimes in electron all you have to do is to go to your main.js and add the following object while creating a window under webPreference set enableRemoteModule: true as shown bellow then your problem will be solved
win = new BrowserWindow({
width: 700,
height: 600,
hasShadow: true,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
},
});
i enabled remote module, still getting
index.html:43 Uncaught TypeError: Cannot read properties of undefined (reading 'getCurrentWindow')
for
const remote = require('electron').remote;
(or)
const { remote } = require('electron');
while using
remote.getCurrentWindow().close();
i did add
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
}

Resources