Electron | TypeError: "x" is not a constructor - node.js

I have a simple electron app that opens a browser window to a url of a webpage.
I import my stuff using
const { BrowserWindow, app, MessageChannelMain } = require('electron');
I create my window on app.on("ready") using window = new BrowserWindow({ ... }); and everything works fine.
When I try to create a MessageChannelMain using const {port1, port2} = new MessageChannelMain();, I get the error:
TypeError: MessageChannelMain is not a constructor.
Full code:
function init_window(){
if ( mainWindow == null ) {
mainWindow = create_window();
load_window(); // calls mainWindow.loadURL(myURL);
const {port1, port2} = new MessageChannelMain();
port2.postMessage({ test: 21 })
port2.on('message', (event) => {
console.log('from renderer main world:', event.data)
});
port2.start();
mainWindow.webContents.postMessage('main-world-port', null, [port1])
mainWindow.on('closed', function(event) {
mainWindow = null;
});
mainWindow.webContents.on("did-fail-load", function(event) {
})
}
}
function create_window() {
return new BrowserWindow({
width: 1200,
height: 800,
darkTheme: true,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
},
});
}
When I console.log(MessageChannelMain); I get undefined.
At the same time, I get other errors when I try to use some electron stuff on the preload script:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
doThing: () => ipcRenderer.send('do-a-thing')
}
)
Here I am getting TypeError: Cannot read property 'exposeInMainWorld' of undefined

Related

Kaspersky marks my Electron app as a Trojan

I recently made an Electron app that is essentially a web browser and it has 3 windows and they all get destroyed when the user closes them, leaving no excess process behind this is the only complex part of the app but Kaspersky flags it as a Trojan.
my index.js
const { app, BrowserWindow, ipcMain, desktopCapturer } = require('electron');
const path = require('path');
app.commandLine.appendSwitch("disable-http-cache");
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
// eslint-disable-next-line global-require
if (require('electron-squirrel-startup')) {
app.quit();
}
function createMacroWindow(macroUrl) {
let macroWindow = new BrowserWindow({
width:800,
height:500,
frame:false,
show:false,
icon: path.join(__dirname, "./assets/icons/icon.png"),
webPreferences:{
devTools: false,
webviewTag: true,
nodeIntegration: true,
contextIsolation: false
}
})
macroWindow.loadFile(path.join(__dirname, './windows/macro-window.html'));
macroWindow.webContents.on('did-finish-load', () => {
macroWindow.webContents.send("loadURL", macroUrl)
})
macroWindow.center();
macroWindow.openDevTools();
macroWindow.show();
macroWindow.on("close", () => {
macroWindow = null;
})
require("#electron/remote/main").enable(macroWindow.webContents);
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1000,
height: 700,
frame:false,
show:false,
icon: path.join(__dirname, "./assets/icons/icon.png"),
webPreferences: {
webviewTag: true,
nodeIntegration: true,
devTools: true, // Geliştirici konsolunu kökten kısıtlayan kod (bu olmazsa CTRL+SHIFT+I yapınca yine açılır)
contextIsolation: false,
},
});
let splashScreen = new BrowserWindow({
width:1000,
height:700,
frame:false,
icon: path.join(__dirname, "./assets/icons/icon.png"),
webPreferences: {
devTools:false
}
})
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
splashScreen.loadFile(path.join(__dirname, './windows/splash-window.html'));
splashScreen.center();
function destroySplashScreen() {
splashScreen.close();
splashScreen = null;
}
mainWindow.webContents.on('did-finish-load', function() {
mainWindow.show();
mainWindow.maximize();
splashScreen.isDestroyed() ? console.log("splash screen already destroyed") : destroySplashScreen();
});
require('#electron/remote/main').initialize()
require("#electron/remote/main").enable(mainWindow.webContents)
ipcMain.handle(
'DESKTOP_CAPTURER_GET_SOURCES',
(event, opts) => desktopCapturer.getSources(opts)
)
let macroUrl;
ipcMain.on("openMacroWindow", (e, urlToLoad) => {
macroUrl = urlToLoad;
createMacroWindow(macroUrl);
})
ipcMain.on("closeApp", () => {
BrowserWindow.getAllWindows().forEach(window => window.close());
})
ipcMain.on('clearCache', () => {
mainWindow.webContents.session.clearStorageData([], function (data) {
console.log(data);
})
mainWindow.webContents.session.clearCache();
})
mainWindow.on("close", () => {
app.quit();
})
// Open the DevTools.
// mainWindow.webContents.openDevTools(); // Geliştirici konsolunu kapatmak için bu satırı silebilirsiniz
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// app.disableHardwareAcceleration()
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
splashScreen is the window that opens while the mainWindow loads, it closes when the main loads and is set to null afterwards just like the macroWindow.
macroWindow is opened when the ipcMain is told to openMacroWindow
Fix
I downgraded to electron-forge version 5.2.4 from 6.x version, this fixed my issue but this is not an ideal solution.

NodeJS contextBridge receiving results from index.js

I've been able to use the preload.js for sending message to API to get things done. I'm able to get responses just fine from iPC, but I'm not able to relay the responses from iPC back to the renderer and I don't understand what I'm missing.
index.js (main)
// Modules to control application life and create native browser window
const { app, BrowserWindow, remote, ipcMain } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
// and load the index.html of the app.
mainWindow.loadFile('index.html');
// Open the DevTools.
// mainWindow.webContents.openDevTools()
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready',() => {
createWindow();
require('./request.js')(mainWindow);
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
request.js - this is used in index/main thread above to process API requests, in this case requests to winax which I use with a separate 32 bit nodeJs interpreter because I couldn't get it to build for use with the same version of Node I can use for electron
const { ipcMain, ipcRenderer } = require('electron');
const { spawn } = require('child_process');
module.exports = function(mainWindow){
let winax;
ipcMain.on('winax', (event,arguments) => {
console.log('winax=');
console.log(arguments);
if(winax === undefined) {
console.log('spawning winax');
winax = spawn(
'C:\\Program Files\\nvm\\v14.20.0\\node.exe',
[ 'winax_microamp/index.js' ], {
shell: false,
stdio: ['inherit', 'inherit', 'inherit', 'ipc' ],
windowsHide: true
}
);
}
console.log('sending winax arguments');
/*
arguments = {
method: 'functionToRun',
owner: 'requestingProcess',
params: { required_params ... }
}
*/
winax.send(arguments);
winax.once('message', (message) => {
console.log('winax response=')
console.log(message);
mainWindow.webContents.postMessage('response', message);
});
});
}
preload.js
'use strict';
// preload.js
// All the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
// MAS: This function executes when the DOM is loaded so we should be able to add button interactions here
const { contextBridge, ipcRenderer } = require('electron');
let testAccDb = 'C:\\Users\\PZYVC7\\OneDrive - Ally Financial\\Access\\electron_microamp.accdb';
//const spawn = require('child_process').spawn
contextBridge.exposeInMainWorld(
'request', {
send: (func) => {
console.log('request.send.func=');
console.log(func);
if(func == 'openStagingTest'){
let args = {
method: 'requestStagingDatabase',
owner: 'ui',
params: {
path: testAccDb
}
};
ipcRenderer.send('winax',args);
} else if(func == 'closeStagingTest') {
let args = {
method: 'closeStagingDatabase',
owner: 'ui'
};
ipcRenderer.send('winax',args);
}
},
response: (message) => {
ipcRenderer.on('message', (message) => {
console.log('winax reply=');
console.log(message);
});
}
}
);
window.addEventListener('DOMContentLoaded', () => {
/*const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency]);
}*/
});
render.js
'use strict';
onLoad();
function onLoad(){
/*document.querySelector('.one').addEventListener('click',() => {
writeLog()
});*/
/*var testButton = document.querySelector('button[class=test]');
testButton.addEventListener('click', (e) => {
//C:\\Program Files\\nvm\\v14.20.0\\node.exe
window.main.asyncProc( '"C:\\Program Files\\nvm\\v14.20.0\\node.exe" winax_microamp/index.js' );
});*/
var buttons = document.querySelectorAll('button');
buttons.forEach((button) => {
//console.log('button=');
//console.log(button.className);
button.addEventListener('click', (event) => {
window.request.send(button.className);
});
});
}
Ugh... ok, so a few things I figured out eventually, I took hours to figure this out, and I'm not 100% sure if I'm understanding it properly so I appreciate any correction.
I think one issue that threw me off for longer than it should have is that the console.log for the preload.js goes into the developer tools rather than the system console like the main thread areas do.
Another thing throwing me off was that I was writing the implementation inside of preload.js, rather than in render.js. I confirmed it does work in both places.
If I want it in preload.js, I leave the implementation like this, and I confirmed it was firing here based on changing the log message a little
preload.js
response: (message) => {
ipcRenderer.on('response', (message) => {
console.log('expose reply=');
console.log(message);
});
}
}
If I want it in render instead, I need my preload to be more basic
response: (message) => {
ipcRenderer.on('response', message);
}
And then I need render to have this to process it there instead.
window.request.response((event,message) => {
console.log('winax reply=');
console.log(message);
});

Electron.js | Remove new window frame

I'm using an index.html including an iframe. When a page is opened in a new tab with CTRL + Click keys, the properties I defined for BrowserWindow are not applied.
I want to remove the frame of the opened new tab. How can I do it?
const path = require('path')
const {app, BrowserWindow, Menu} = require('electron')
function createWindow() {
const win = new BrowserWindow({
width: 1000,
height: 700,
frame: false,
show: false,
})
win.loadFile('index.html')
win.once('ready-to-show', () => { win.show() });
}
const menu = Menu.buildFromTemplate([])
Menu.setApplicationMenu(menu)
app.whenReady().then(() => { createWindow() })
Windows opened via links don't inherit their parents options, instead, you have to register a window open handler and set the options manually:
win.webContents.setWindowOpenHandler(({ url }) => {
return {
action: 'allow',
overrideBrowserWindowOptions: { // These options will be applied to the new BrowserWindow
frame: false,
// other BrowserWindow settings
}
}
}
On the documentation: https://www.electronjs.org/docs/latest/api/window-open#native-window-example

Vue/Electron IPC between main and renderer

I'm trying to setup communication between my Vue browser app and the electron main process, but it is not working.
Before startBot() is even called, I get an error message that __Dirname is unknown. But this constant is nowhere in to be found in the code.
What am I doing wrong?
https://gist.github.com/Quenos/7d6dbe4f5410739499ea9e3b0b4f961a.js
This is the background.js where I open the browser window with a preload. This has the purpose of making window available to the browser process
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1300,
height: 1100,
title: "Hedgehog TRDR Bot",
icon: path.join(__static, "hedgehog.jpg"),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
// __static is set by webpack and will point to the public directory
preload: path.resolve(__static, "preload.js"),
},
});
This is said preload.js
const { contextBridge, ipcRenderer } = require("electron");
const validChannels = ["READ_FILE", "WRITE_FILE"];
contextBridge.exposeInMainWorld("ipc", {
send: (channel, data) => {
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
on: (channel, func) => {
if (validChannels.includes(channel)) {
// Strip event as it includes `sender` and is a security risk
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
});
The main process which contains listeners that then will do file handling
const { ipcMain } = require("electron");
const fs = require("fs");
var file;
ipcMain.on("OPEN_FILE", (event, payload) => {
console.log("daaro");
file = fs.createWriteStream(payload.path);
event.reply("OPEN_FILE", { content: "roger" });
});
ipcMain.on("TEST_FILE", (event, payload) => {
console.log("daaro");
file.write(payload.path);
event.reply("OPEN_FILE", { content: "roger" });
});
And the browser process which send requests to do file handling:
async startBot() {
window.ipc.send("OPEN_FILE", { path: "./HH_trdr_bot.csv" });
}
In the meantime I've found this article that perfectly answers my question
https://medium.com/swlh/how-to-safely-set-up-an-electron-app-with-vue-and-webpack-556fb491b83
Docs of Vue CLI Plugin Electron Builder contain description of how to do it, but it's a little scattered.
First, see how to configure a preload script, i.e.:
vue.config.js
module.exports = {
// ...
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js',
}
}
}
and then, repeat the path in BrowserWindow's webPreferences.preload constructor option.
preload: path.join(__dirname, 'preload.js')
Finally, in src/preload.js expose the IPC Renderer as described in IPC Without Node Integration.
src/preload.js
import { contextBridge, ipcRenderer } from 'electron'
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('ipcRenderer', {
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))
}
}
})
It might be also a good idea to unsubscribe the event listener when the component that subscribed is about to be destroyed.
I made mine working by putting the preload.js file inside public folder, in the https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/guide.html#preload-files docs, there is a little diagram under Folder Structure section that says ├── public/ # Files placed here will be available through __static or process.env.BASE_URL, so what I did is simply, use the __static variable as described in docs, and append it with \preload.js
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
enableRemoteModule: false,
preload: __static + '/preload.js', // <-- simple solution
}
})

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