I've been practicing using Electron for a few months now, and I was wondering how to convert a Node.js webpage project that I made a while ago into an Electron app. In my Node.js webpage, I used functions like
app.get('/' function(req, res){
res.render('loginpage');
}
app.post('/loginCredentials', function(req, res){
/*Make sure req.body.username and req.body.password are valid*/
res.render('home');
}
Also in my Node.js application, whenever the user wants to see the database results, I query the database in the Node.js webpage and send the variables to the webpage when they're rendered.
But in Electron, I don't know how to handle such requests.If I wanted to make an Electron application, would I have to deal all of this in the webpage files themselves, or does Electron have some app.get/app.post methods?
Alternatively, is there a way to make my Node.js webpage load in a browser window like an Electron application?
Thank you in advance
UPDATE 1; ipcMain Module function:
In my electron main.js file, I have the following code:
const {ipcMain} = require('electron')
ipcMain.on('hello', (event, arg) => {
console.log(arg)
event.returnValue = 'pong'
})
And in my webpage, that is located in the './views' directory, I have the following code: (it is a pug file and it renders correctly)
input(type ='submit', value ='Submit', onclick = "handleClick()")
...
script.
const {ipcRenderer} = require('electron')
function handleClick(){
var x = ipcRenderer.sendSync('hello', 'ping')
console.log(x)
}
I kept getting an error (it is shown at the bottom of the question) before made the following changes. I changed:
const ipcMain = require('electron')
to
const {ipcMain} = require('electron')
Apparently, the brackets are required to be around the module for it to be imported correctly.
Error that existed but was fixed:
Uncaught TypeError: ipcRenderer.sendSync is not a function
at handleClick (loginpage.pug:64)
at HTMLInputElement.onclick
ipcMain is a useful aspect in Electron that mimics the app.get/app.post in Node.js
While it is technically possible to make Node.js your backend for an Electron app, it would be a heavy handed solution and is not recommended.
Instead of post/get requests, instances of Electron's EventEmitter class are typically used for communicating between the front and backend. Use the ipcMain module on the backend, and ipcRenderer on your frontend to exchange any sort of data. In fact, messages can be sent either synchronously or asynchronously.
For example, from Electron's documentation:
// In main process.
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.sender.send('asynchronous-reply', 'pong')
})
ipcMain.on('synchronous-message', (event, arg) => {
console.log(arg) // prints "ping"
event.returnValue = 'pong'
})
// In renderer process (web page).
const {ipcRenderer} = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')
Related
I have created windows service from nodeJs application using node-windows package. Below is my code.
Main.js
var Service = require('node-windows').Service;
// Create a new service object
var svc = new Service({
name:'SNMPCollector',
description: 'SNMP collector',
script: './app.js',
nodeOptions: [
'--harmony',
'--max_old_space_size=4096'
]
//, workingDirectory: '...'
});
// Listen for the "install" event, which indicates the
// process is available as a service.
svc.on('install',function(){
svc.start();
});
svc.install();
/* svc.uninstall(); */
App.js
const { workerData, parentPort, isMainThread, Worker } = require('worker_threads')
var NodesList = ["xxxxxxx", "xxxxxxx"]
module.exports.run = function (Nodes) {
if (isMainThread) {
while (Nodes.length > 0) {
// my logic
})
}
}
}
Now when I run main.js, it creates a windows service and I can see the service running in services.msc
But, how can I call this run() method which is inside the running service, from any outside application? I couldn't find any solution for this, any help would be great.
You might consider simply importing your run function where you need it and run it there, then there is no need for a windows service or main.js - this assumes that "any outside application" is a Node application.
In your other application you you do the folowing:
const app = require('<path to App.js>');
app.run(someNodes)
For broader usage or if you do need to run it as a service, you could be starting an express (or another webserver) in your App.js with an endpoint that invokes your run function. Then from anywhere else you'll need to make an http call to that endpoint.
App.js
const express = require('express')
const bodyParser = require('body-parser')
const { workerData, parentPort, isMainThread, Worker } = require('worker_threads')
const app = express()
const port = 3000
var NodesList = ["xxxxxxx", "xxxxxxx"]
const run = function (Nodes) {
if (isMainThread) {
while (Nodes.length > 0) {
// my logic
})
}
}
}
app.use(bodyParser.json())
app.post('/', (req, res) => res.send(run(req.body)))
app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))
(Based off of example for express - https://expressjs.com/en/starter/hello-world.html)
You'll need to install both express and body-parser: $ npm install --save express body-parser from the directory of App.js.
From your other applications you will need to call the endpoint http://localhost:3000 with a POST request and the Nodes as a JSON array.
You can expose it on a port like the other answer mentions, though you'll want to make sure you don't expose it more broadly depending on the environment you're running in. There's a good answer here on ensuring the port is locked down.
As an alternative to exposing it on a port you can simply call the function by running the command in any other application:
node -e 'require("/somePathToYourJS/app").run()'
One concern is that app.js will now run at whatever permissions the calling application has. Although that can be resolved by running runas prior. More details here. But an example is:
runas /user:domainname\username "node -e 'require(^"/somePathToYourJS/app^").run()'"
I am currently working on creating a web app that allows me to run Nightwatch tests from a graphic interface. Right now I can run all my tests with a post request from my web app I am using Nightwatch Programmatic API.
My question is can I chose the folder of tests I want to run from a post request, here is my code,
Thank you.
router.post('/exec', function (req, res) {
Nightwatch.cli(function (argv) {
argv.config = 'nightwatch.conf.js';
argv.source= 'folder of tests i want to run';
const runner = Nightwatch.CliRunner(argv);
runner
.setup()
.startWebDriver()
.then(() => {
return runner.runTests()
})
.then(() => {
return runner.stopWebDriver()
})
.catch(err => console.error(err));
});
})
As you know, the config file is exported as an object. If you are able to read the file contents and store it into a variable, you can try to override the property src_folders.
const red = require('../nightwatch.conf');
console.log(red.src_folders)
I try to start and stop a Tomcat instance on Windows using Electron.
I have modified the electron-quick-start project to stop my Tomcat instance with a batch file which calls Tomcat's shutdown.bat when all Electron windows are closed or before my application exits.
However, when I close my application, there is no output from the shutdownTomcat.on ("data", ...) and shutdownTomcat.on ("exit", ...) listeners. The only output is from a console.log ("Hello world") from my app.on ("before-quit", ...).
I chose this approach because I am new to Electron and want to test NodeJS' spawn's behaviour.
When I use my code outside of the app.on () listeners, the output is shown but my Tomcat instance is not being stopped. However, my startTomcat.bat file, which calls Tomcat's startup.bat, works without any problems.
I already read NodeJS' childProcess' documentation ("Spawning .bat and .cmd files on Windows"), but I cannot get it to work; which leads to my question, namely where the problem is and what I'm doing wrong.
My Main.js file I use for the main process:
const { app, BrowserWindow } = require('electron');
const { spawn } = require('child_process');
const path = require('path');
const start = path.resolve("./start.bat");
const startTomcat = spawn('cmd.exe', ['/c', start], {
cwd: process.cwd(),
detached: true,
});
// ...
app.on('before-quit',()=> {
const shutdownTomcat = spawn('cmd.exe', ['/c', stop], {
detached: true
// stdio: 'ignore'
});
shutdownTomcat.stdout.on('data', (data) => {
console.log("This data won't show up...", data);
});
shutdownTomcat.stderr.on('data', (data) => {
console.log(data.toString());
});
shutdownTomcat.on('exit', (code) => {
console.log(`Child Shutdown Tomcat exited with code ${code}`);
});
console.log("Hello World");
});
And finally, the batch file (stop.bat) I'm using to call Tomcat's shutdown.bat:
cd /D "%~dp0"
cd "..\\apache-tomcat\\bin"
call shutdown.bat
Most probably your electron application is already terminated by the time your events would have fired. Therefore there is no longer reference to your spawned process and listeners.
You can try event.preventDefault() to cancel the app quitting. Then you can explicitly app.quit() once you are done (but beware of infinitely looping through your before-quit listener; you may remove it or app.exit(0)).
I want to display the current git tag on my app's login page,
Its built using react.
Im trying to use the 'git-rev-sync' library to do this.
but it doesnt seem to work on the client side because I keep getting errors like
'cannot find module 'children process', it works on the server side where Im able to console.log and print the tag
anyone know how to achieve this? Open to any solutions with any library
import version from 'git-rev-sync'
...
class Login extends Component {
...
render ()
...
return (
<div> my version: {version.tag()} </div>
) }
Thanks
I decided to use git-revision-webpack-plugin which creates a VERSION file (among other files) in the dist folder, and then I read the file from my client side react app:
add this to your webpack.js:
const GitRevisionPlugin = require('git-revision-webpack-plugin')
module.exports = {
plugins: [
new GitRevisionPlugin({
lightweightTags: true //I added this to get the tags as well
})
]
}
then my client side looks like this:
const [revision, setRevision] = useState('')
const fetchRevision = async () => {
let result = await fetch('/dist/VERSION')
let txt = await result.text()
txt = txt.replace(/^(.*?)(?:\-.*)?$/, '$1') //I only care for the tag.
setRevision(txt)
}
useEffect(() => {
fetchRevision()
}, [])
and then you can render the revision
One thing to notice, depending on your server, you may need to tell it to serve this VERSION file as is, so for example in express, you might find you need this:
server.get('*', (req, res, next) => {
if (/^\/dist\/*/.test(req.originalUrl)) {
const relative = req.originalUrl.replace(/\/dist(\/.*)/, '$1')
const filename = path.join(compiler.outputPath, relative)
compiler.outputFileSystem.readFile(filename, (err, result) => {
if (err) {
return next(err)
}
res.send(result)
res.end()
})
}
...
})
Hope this helps for future use.
If you used create-react-app#0.2.3 > to generate your app.
create-react-app scripts use environment variables that start with the REACT_APP_ symbol in the root .env file. create-react-app - Adding custom environment variables is a good place to dig into the details.
or just include the following in your .env file.
.env
REACT_APP_VERSION=$npm_package_version
and access it on your react login component by referring to {process.env.REACT_APP_VERSION}
I've been trying to convert a small webapp into an electron application. It's working perfectly, except I need to load a bunch of files (.html) into the main DOM. In the webapp, I just used $.get, but how can I do it in electron? I try looking at the DOC but I cannot find an easy way to do that, beside an IPC pipe (and I don't quite get it).
Could anyone point me to the right direction?
Edit
I'll clarify here. I have a main process that start a BrowserWindow
mainWindow = new BrowserWindow({width: 800, height: 600})
and then, in a js file imported via a <script> tag, I want to get load and attach some file inside a dialog:
$('.dialog').load('pages/hello.html', {})
Kind regards
On the Electron side you will have this code starting with the electron library. With ES6 destructuring you can pull the app property off. The app object represents the overall running process on your machine.
const electron = require('electron');
const { app } = electron;
Then you want to add on an event handler:
app.on('ready', () => {
console.log('app is ready');
});
Once you have this running successfully. You have pulled the app property which is the underlying process. You have successfully added your event based function using the app object.
So you then want to create a new browser window to show content to the user, you pull off another property called BrowserWindow, pass it an empty object with your configuration.
const electron = require('electron');
const { app, BrowserWindow } = electron;
app.on('ready', () => {
const mainWindow = new BrowserWindow({width: 800, height: 600});
});
You can then load the HTML document by calling the method loadURL() passing ES6 template string:
const electron = require('electron');
const { app, BrowserWindow } = electron;
app.on('ready', () => {
const mainWindow = new BrowserWindow({width: 800, height: 600});
mainWindow.loadURL(`file://${__dirname}/index.html`);
});
In Electron you can do it with fs.readFile
So :
const fs = require('fs');
const { promisify } = require('util');
const path = require('path');
const readFile = promisify(fs.readFile);
async function loadHTML(html){
const render = await readFile(path.join(__dirname, html), 'utf-8');
const parser = new DOMParser();
const childrenArray = parser.parseFromString(render,'text/html').querySelector('body').childNodes;
const frag = document.createDocumentFragment();
childrenArray.forEach(item => {
frag.appendChild(item);
});
document.body.appendChild(frag);
};
loadHTML('/path/to/my/index.html');
If I don't have a Typo, it should work.
it reads the file as a string so you need to parse this String with the DOMParser.
Load like this:
`file://${path.join(__dirname,"/../../../../../../src/offline.html")}`
Let's try to understand each part:
Reason
file
we will be accessing file protocol, not HTTP directly
__dirname
comes with node integration in electron, everyone should set it to true
path.join
else navigating up won't happen actually, it will be simple string concatenation
why all those nav up?
maybe it's development dependant, for my case I had to log and find that src directory
About that navigating up, I tried on different OS and PC, most of the cases were the same. Except for one exception. Which also had a different config. And I didn't dare to change that, as the config is working.