Using remote.getGlobal variables in modern isolated renderer process Electron - javascript

I'm updating an entire app written a few years ago using Electron v1.8.8. As far as I know, Electron changed its paradigm and now the default expected way of communicating between the main and renderer process is using a module in the middle named preload.js.
To get/set global variables in the old way you would first require the remote module:
var { remote } = require('electron');
And then getting/setting it like so:
remote.getGlobal('sharedObj').get('user')
I've been trying to expose an api object in preload.js to achieve the same old functionality, but with no success.
How I would achieve this without setting nodeIntegration: true, contextIsolation: false?

To send messages between the main thread and a render thread requires the understanding of Inter-Process Communication and Context Isolation.
You are correct in that preload.js acts like a middle man. From my experience, using preload.js as a script whose sole purpose is to communicate data between the main and render threads via the use of defined channels allows for a simple, easy to read, separation of concern. IE: Don't place domain specific functions within your preload.js script. Only add functions that are used for the purpose of performing specific methods of communication.
Below is a typical preload.js file. I have defined a couple of channels to communicate between your main thread and your render thread. PS: You can use any name / naming convention to name your channels.
preload.js (main thread)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'renderThread:saysHi'
],
// From main to render.
'receive': [
'mainThread:saysHi'
],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Whilst your main.js file may look a little different than that shown below, the main points of interest are the reception and transmission of the defined channels renderThread:saysHi and mainThread:saysHi.
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
let appWindow;
function createAppWindow() {
const appWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
fullscreen: false,
resizable: true,
movable: true,
minimizable: true,
maximizable: true,
enableLargerThanScreen: true,
closable: true,
focusable: true,
fullscreenable: true,
frame: true,
hasShadow: true,
backgroundColor: '#fff',
show: false,
icon: nodePath.join(__dirname, 'icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
worldSafeExecuteJavaScript: true,
enableRemoteModule: false,
devTools: (! electronApp.isPackaged),
preload: nodePath.join(__dirname, 'preload.js')
}
});
appWindow.loadFile('index.html')
.then(() => {
// Main thread saying hi to the render thread.
appWindow.webContents.send('mainThread:saysHi', 'Hello from the main thread.'); })
.then(() => {
appWindow.show(); })
return appWindow;
}
// Listen for the render thread saying hi.
electronIpcMain.on('renderThread:saysHi', (event, message) => {
console.log(message); });
}
electronApp.on('ready', () => {
appWindow = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
Your index.html file will import your Javascript file which would contain the code to listen out and send messages on the specified channels.
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="main-thread-message"></div>
<input type="button" id="render-thread-button" value="Click to say hi to the main thread">
</body>
<script type="module" src="index.js"></script>
</html>
index.js (render thread)
let mainThreadMessage = document.getElementById('main-thread-message');
let renderThreadButton = document.getElementById('render-thread-button');
// IIFE - Immediately Invoke Function Expression
(function() {
window.ipcRender.receive('mainThread:saysHi', (message) => {
mainThreadMessage.textContent = message;
});
renderThreadButton.addEventLister('click', () => {
window.ipcRender.send('renderThread:saysHi', 'Hello from the render thread.');
});
})();
In the above code, the global object window contains ipcRender.send and ipcRender.receive which is the structure used in your preload.js script underneath the line contextBridge.exposeInMainWorld. You can rename ipcRender and receive / send / invoke to anything you like, though if you do, you would also use the same reference when using it 'html js' side.

Related

Can't use any methods of an istance of a third-party modules [chess.js] (ELECTRON - preload.js & render.js problem)

Today i faced this problem: i was trying to create a chessboard app (with Electron) using the chess.js modules and the problems showed quickly... I need to use in my render.js file all methods/features that chess.js offers, such as chess.move(), chess.fen() and so on... But after setting sandbox: false (in webPreferences) and writing this in my preload script (preload.js):
const { contextBridge, ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld(
'electron',
{
//other code
getChess : () => {
const {Chess} = require('chess.js');
return chess = new Chess;
}
}
)
i am unable to use any the methods (to make things more clearer, i am enable to use the "chess" istance of Chess in my render script, i can log this, and it will show the obj (as expected))
this is my render script (render.js):
//get Chess through preload.js
const chess = window.electron.getChess();
//✅ Working (showing the chess obj)
console.log(chess)
//❌ Not Working (example Uncaught TypeError: chess.move is not a function)
chess.move('e42')
chess.inCheck()
chess.isAttacked()
chess.load()
chess.clear()
chess.board()
chess.fen()
chess.png()
//. . .
Probably is something stupid to ask, but it seems to me hard to get through.
chess.js have a lot of methods .fen, .move, .load ... i need a way to include all to my render.
I don't wanna set nodeIntegration: true due to security reason.
idk if it will help but here is my main.js file :
const { app, BrowserWindow, Menu, ipcMain} = require('electron')
const path = require('path')
const ipc = ipcMain
Menu.setApplicationMenu(false)
const createWindow = () => {
const win = new BrowserWindow({
width : 1500,
height : 900,
resizable: false,
titleBarStyle: 'hidden',
frame: false,
webPreferences: {
nodeIntegration: false, // is default value after Electron v5
contextIsolation: true, // protect against prototype pollution
enableRemoteModule: false, // turn off remote
devTools: false,
sandbox: false,
preload: path.join(__dirname, 'preload.js'),
}
})
//win.webContents.openDevTools()
win.loadFile("src/index.html");
ipc.on('closeAPP', () => {
win.close()
})
ipc.on('minimizeAPP', () => {
win.minimize()
})
ipc.on('maximizeAPP', () => {
win.maximize()
})
}
app.whenReady().then( () => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
There is a way to use all the methods ?
This is by design. Electron does not support, supposedly for security reasons, sending "custom classes" over the contextBridge (as per the documentation) and drops all prototype modifications or prototypes of objects other than Object itself.
This is because you're expected to expose only certain functions of which you're sure they cannot do harm to the renderer process and instead do all processing either in preload.js (not recommended) or in the main process and pass values via IPC.
Mind that the latter will also not work for non-standard Objects, thus it would be best to store the Chess objects you'll need in the main process, manipulate them there, and only pass the values required for (ex.) display to the renderer process. How this is done best is explained in Electron's IPC tutorial.

Converting a React + Redux + MUI Menu to Electron

I am attempting to convert a web-based React + Redux + MUI app to Electron. The app has a main AppBar with multiple dropdown menus, with menu items which hook into the app's Redux store. So a fairly typical set-up for these technologies in partnership, but I'm struggling to understand how to translate this (if it's possible) to an Electron application menu.
So if I have a MUI MenuItem with a typical onClick handler, like this:
const [soneState, setSomeState] = useState();
const handleOnClick = (e) => {
const val = e.target.value;
console.log(`The value is ${val}`);
setSomeState(val);
}
What would be the equivalent for an Electron Menu? Assuming also that I am leveraging the Redux store, rather than local component state. A number of the handlers in the menu communicate with an Express server via fetch. I have been reading up on Electron inter-process communication via the contextBridge, but I'm not sure which side the Electron Menu comes in that equation. Can it leverage both the Redux store as well as talking to the Main process? I assume I can't make fetch calls from the Menu?
OK I think I figured it out...
I can use the pattern described in the official docs here to create an API in my preload.js, which the app menu can then call into to send messages to the render process. So in preload.js I have:
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
loadSomeFile: (callback) => ipcRenderer.on('load-some-file', callback),
})
Then I define my menu like this:
const { app, Menu, MenuItem, dialog, ipcMain } = require('electron');
module.exports = (window) => {
return Menu.buildFromTemplate([
{
label: 'File',
submenu: [
{
label: 'About',
},
{
label: 'Preferences',
},
{
type: 'separator'
},
{
label: 'Load Some File',
click() {
dialog.showOpenDialog({
properties: ['openFile']
})
.then((fileObj) => {
window.webContents.send('load-some-file', fileObj);
})
}
},
{
type: 'separator'
},
{
label: 'Exit',
click() {
app.quit()
}
}
]
},
])
}
And in my main.js I have:
const { app, BrowserWindow, Menu, } = require('electron');
const path = require("path");
const mainMenu = require("./app/main-menu");
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
});
win.loadURL("http://localhost:8080");
return win;
}
app.whenReady().then(() => {
const win = createWindow();
Menu.setApplicationMenu(mainMenu(win));
});
Most of what I already have in my React/Redux/MUI frontend code can remain unchanged. But I need to access the api from the window object on the frontend, and listen to events coming from the Main process (like from the app menu).
window.api.loadSomeFile((event, data) => {
console.log(data);
});
Tested it and it works well. Very little to actually change in my code.

getting multiple windows in electronJS which has the same browserwindow instance to display different results

I am trying to send data from a main window to multiple windows in electronJS.
How my app works is there is a main window with many selections. On clicking each selection, a new window will open, and the window will show data that is related to that selection.
The problem I am facing is that the data does not persist and keeps getting updated each time I click a new selection.
For example:
Main window: selection A,B,C,D
clicks on A
New window 1 pops up, displays data for A
clicks on B
New window 2 pops up, displays data for B. New window 1 displays data for B.
clicks on C
New window 3 pops up, displays data for C. New window 1 & 2 displays data for C.
I think the issue has got to do with the browserwindow instance, but I'm not quite sure how to fix it, because I won't know how many new windows would be opened. Is there a way to 'freeze' the data at the point in time for each instance?
I include my code for both my renderers and main.js. Data is passed from renderer(index) to main and then to renderer(instrument)
main.js
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain } = require('electron')
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1000,
height: 1000,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})
// 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.whenReady().then(() => {
createWindow()
ipcMain.on('open-instrument-window', (event, instrument) => {
openNewInstrumentWindow()
})
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()
})
// 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 require them here.
const openNewInstrumentWindow = () => {
const instrumentWindow = new BrowserWindow({
width: 1000,
height: 1000,
title: 'Instrument details',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nodeIntegrationInWorker: true
}
})
instrumentWindow.loadFile('instrument.html')
instrumentWindow.webContents.openDevTools()
// this is where the data is send over to the renderer instrument
ipcMain.on('a', (event, payload) => {
instrumentWindow.webContents.send('your-relayed-message', payload);
});
}
this is my renderer index code. I have extracted the relevant portion. ipcrenderer is used twice, first to open the new window, second is to send the data over to renderer instrument.
const searchResults = document.getElementsByClassName('search-result');
for (let i = 0; i < searchResults.length; i++) {
searchResults[i].onclick = () => {
ipcRenderer.send('open-instrument-window')
ipcRenderer.send('a', instruments[i]);
}
}
this is my renderer instrument code.
const getInstrumentDetail = (exchange) => {
axios.get(`http://url${exchange}`)
.then(response => {
let instrument = response.data;
document.getElementById('instrument-name').innerHTML = instrument.name;
})
}
ipcRenderer.on('your-relayed-message', (event, payload) => {
getInstrumentDetail(payload['exchange']);
});
Appreciate any tips or guidance to point me in the right direction.

How to access html DOM element through electron js

I'm making a text editor with electron js, and once the user presses ctrl + s, I want the file to be saved as a txt file. The thing is though, I can't seem to find a way to directly access the div that holds the text. I've tried using a preload, but that only works once the program is run. How can I hold the element as a variable?
Heres the main javascript code:
const { app, BrowserWindow, globalShortcut } = require('electron');
const path = require('path');
// Create the main window
const createWindow = () => {
// Adjust a few settings
const win = new BrowserWindow({
// What the height and width that you open up to
width: 500,
height: 600,
// Minimun width and height
minWidth: 400,
minHeight: 400,
icon: __dirname + '/icon.png',
// Change the window title
title: "text editor",
webPreferences: {
// Preload so that the javascript can access the text you write
preload: path.join(__dirname, 'preload.js'),
}
});
win.loadFile('index.html');
// Remove that ugly title bar and remove unnecessary keyboard shortcuts
win.removeMenu();
}
// Create window on ready so that no nasty errors happen
app.whenReady().then(() => {
createWindow();
});
app.whenReady().then(() => {
// Global shortcut so the user has the ablitiy to exit
globalShortcut.register('ctrl+e', () => {
console.log("exiting...");
app.exit();
});
globalShortcut.register('ctrl+s', () => {
console.log("saving...");
});
})
// when all windows close this app actually closes
app.on('window-all-closed', () => {
if (process !== 'darwin') app.quit();
})
To get the innerText (or equivalent) of the div element in your index.html window, you will need to send a message to your render thread requesting this information. Following this, you will then need your render thread to send the innerText back to your main thread for processing (saving).
Electron's Inter-Process Communication can be confusing at times but if implemented correctly it can be simple and safe.
To learn more about the processes involved you will want to read and try and understand the following links:
ipcMain.on()
webContents.send()
Context Isolation
contextBridge
Let's begin with building out your html document first. At a minimum it must include an editable <div> tag and a 'save' button.
index.html (render thread)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test Editor</title>
<style>
#editor {
width: 50vw;
height: 50vh;
}
<style>
</head>
<body>
<div id="content" contenteditable="true"></div>
<input type="button" id="save" value="Save">
</body>
<script src="script.js"></script>
</html>
See Example: A simple but complete rich text editor for some cool ideas.
Now let's add the 'save' button and IPC message functionality.
script.js (render thread)
// IIFE (Immediately Invoked Function Expression)
(function() => {
let content = document.getElemetById('content').innerText;
document.getElementById('save').addEventListener('click', saveContent(content));
window.ipcRender.receive('editor:getContent', () => { saveContent(content); });
});
function saveContent(content) {
window.ipcRender.send('editor:saveContent', content);
}
Here is your main.js file with the following updates.
Add Electron's ipcMain module.
Add the win object to the top scope so it is noit garbage collected.
Listen for message(s) from the render thread (using an IFFE).
Add the saveContent() function (to be fully fleshed out by you).
Remove const from the new BrowserWindow line.
Return win from the createWindow() function so it can be referenced later on.
Update the globalShortcut ctrl+s function.
main.js (main thread)
const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron');
const path = require('path');
let win = null;
// IIFE (Immediately Invoked Function Expression)
(function() => {
ipcMain.on('editor:saveContent', (event, content) => { saveContent(content); });
})();
function saveContent(content) {
console.log("saving...");
// Save content...
console.log("saved...");
}
// Create the main window
function createWindow() {
// Adjust a few settings
win = new BrowserWindow({
// What the height and width that you open up to
width: 500,
height: 600,
// Minimun width and height
minWidth: 400,
minHeight: 400,
icon: __dirname + '/icon.png',
// Change the window title
title: "text editor",
webPreferences: {
// Preload so that the javascript can access the text you write
preload: path.join(__dirname, 'preload.js'),
}
});
win.loadFile('index.html');
// Remove that ugly title bar and remove unnecessary keyboard shortcuts
win.removeMenu();
return win;
}
// Create window on ready so that no nasty errors happen
app.on('ready', () => {
// Create the window.
win = createWindow();
// Global shortcut so the user has the ability to exit
globalShortcut.register('ctrl+e', () => {
console.log("exiting...");
app.exit();
});
// Global shortcut to save editable content.
globalShortcut.register('ctrl+s', () => {
console.log('ctrl+s pressed.');
win.webContents.send('editor:getContent');
});
})
// when all windows close this app actually closes
app.on('window-all-closed', () => {
if (process !== 'darwin') app.quit();
})
Note that I have left the actual saving to the filesystem functionality to you. See Node.js: fs.writeFile() for more information.
Ok, the last piece of the puzzle is a working preload.js script. This is the script that grants the use of a list of whitelisted channels between the main and render threads.
In here we add the editor:saveContent and editor:getContent channel names.
preload.js (main thread)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'editor:saveContent'
],
// From main to render.
'receive': [
'editor:getContent'
],
// From render to main and back again.
'sendReceive': []
}
};
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Note that I do not perform any functions so-to-speak in the preload script. I only manage a list
of channel names and the transfer of any data associated with those channel names.

How to access native hwnd of window with electron on windows in preload.js?

Here I want show the hwnd of a BrowserWindow in a electron window:
// preload.js
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])
}
replaceText('hwnd-version', window.getNativeWindowHandle().readInt32LE())
})
console.log("xxxxxxx")
// index.html
<!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'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Native Editor</title>
</head>
<body>
<h1>World Editor running</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>,
based on hwnd <span id="hwnd-version"></span>.
<script src="./renderer.js"></script>
</body>
</html>
What I got is the hwnd-version will not be replaced by the hwnd. How to access it?
Issue:
The issue is you are using the globalThis and its window object, which is different from Electron's BrowserWindow object defined in main. Your preload script should use Electron's BrowserWindow object to access that getNativeWindowHandle() function. However, this is tricky, so read on.
Obstacle:
It is not secure to enable nodeIntegration nor enableRemoteModule. Both of those in your Electron window should be set to false when creating, as you have already done. I also enable contextIsolation and use something similar in the following Stack Overflow answer: How to use preload.js properly in Electron
Resolution:
Preload Script
Require contextBridge and ipcRenderer in your preload script and use as follows:
// preload.js
const { contextBridge, ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld("api", {
send: (channel, data) => {
// whitelist channels
let validChannels = ["getBrowserWindowFromMain"]
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["sendBrowserWindowToRenderer"]
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => {
func(...args)
})
}
}
})
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])
}
})
console.log("xxxxxxx")
Main Script
Require ipcMain from Electron and change your const win = new BrowserWindow(...) to win = new BrowserWindow(...) and be sure to define var win = null so that it is accessible anywhere in main.js, and use like so in the following code:
// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
var win = null;
function createWindow () {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: false, // false is default value after Electron v5
contextIsolation: true, // true is default value since Electron v12
preload: path.join(__dirname, 'preload.js'),
enableRemoteModule: false
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
ipcMain.on("getBrowserWindowFromMain", (event, args) => {
win.webContents.send("sendBrowserWindowToRenderer", win.getNativeWindowHandle().readInt32LE());
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
Index HTML File
This file is fine and can remain as is.
Renderer Script
Add the following code to your renderer.js file in addition to what you already have. I found that accessing the new api object from the globalThis and it's window object like window.api.receive or window.api.send as defined in the contextBridge in preload.js would fail because api would be undefined during the DomContentLoaded event.
Here is the code for renderer.js:
// renderer.js
window.api.receive("sendBrowserWindowToRenderer", (windowHandle) => {
console.log("Window object received.");
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) {
element.innerText = text
}
}
if (windowHandle) {
replaceText('hwnd-version', windowHandle)
} else {
replaceText('hwnd-version', '*** Could not retrieve window handle for window at index [0] ***')
}
})
window.api.send("getBrowserWindowFromMain", null)
I have tested this code using NodeJS version 18.12.1, NPM version 9.2.0 with Electron version ^22.0.0 in my own workspace in VS Code.
Notice:
In win.webContents.send() for the second parameter, I only send the integer returned from calling the win.getNativeWindowHandle().readInt32LE() function. The reason is because win.webContents.send() serializes the second parameter and the object win is not serializable. I would think it's for the best, too, to avoid sending large objects to and from the renderer and main processes.
I couldn't help but notice you have the text Native Editor for the title tag. Are you passing this window handle to, say, a C++ module imported in the renderer process to pass to some graphics API like DirectX or Vulkan? If so, let me know how that goes, cause I'm not sure if it will work or not due to the window needing to be created with certain capabilities to use with those APIs, but I wanted to try it. But it is generally not something that's supported, and it breaks cross-platform abilities of Electron becoming dependent on Windows, and then each windowing system (X11, MacOS X, Windows, etc...) would need to have their own way of accessing the electron window, which I guarantee will vary from OS to OS.

Categories