I'm new to node.js and Electron and trying to run an external command in Electron and show the result with HTML.
I made an HTTP server-client version with node.js, which works fine. But failed to make it to work with the Electron, even after following many answers here, such as
None of them worked for me for Electron.
Node.js version works.
I'm showing my working node.js code as follows. This code, after running node index.js
, and open localhost:8888/start
in the browser, will show the output of ls -al
on the webpage:
// index.js
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");
var handle = {};
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
server.start(router.route, handle);
The HTTP server:
// server.js
var http = require("http");
var url = require("url");
function start(route, handle) {
http.createServer(function(request, response) {
var pathname = url.parse(request.url).pathname;
console.log("Request for " + pathname + " received.");
route(handle, pathname, response);
}).listen(8888);
console.log("Server has started.");
}
exports.start = start;
The router that handles two requests, i.e., start/upload
, differently:
//router.js
function route(handle, pathname, response) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] == 'function') {
handle[pathname](response);
} else {
console.log("No request handler found for " + pathname);
// send an HTTP status and content-type in the HTTP response *header*
// back to the browser that requested your server).
response.writeHead(404, {"Content-Type": "text/plain"});
// send text in the HTTP response *body*.
response.write("404 Not found");
// finish the response.
response.end();
}
}
exports.route=route;
The actual request handlers:
// requestHandlers.js
var exec = require("child_process").exec;
function start(response) {
console.log("Request handler 'start' was called.");
var content = "empty";
exec("ls -al",
{timeout: 10000, maxBuffer: 20000*1024},
function(error, stdout, stderr) {
// send an HTTP status 200 and content-type in the HTTP response *header*
// back to the browser that requested your server).
response.writeHead(200, {"Content-Type": "text/plain"});
// send text in the HTTP response *body*.
response.write(stdout);
// finish the response.
response.end();
});
}
function upload(response) {
console.log("Request handler 'upload' was called.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("You've uploaded data");
response.end();
}
exports.start = start;
exports.upload = upload;
The above code worked for me in Safari.
Electron version fails
Now I wanted to do the similar thing with Electron: Enter a command in an Entry box, run it through a submit button, and show the result on the same page below the controls. Here is my main process:
// main.js
const {app, BrowserWindow} = require('electron');
let mainWindow = null;
app.on('ready', () => {
console.log('Hello from Electron');
mainWindow = new BrowserWindow({
webPreferences: {
nodeIntegration: true
}
});
mainWindow.webContents.openDevTools()
mainWindow.webContents.loadFile('./app/index.html');
// mainWindow events, within app lifecycle
mainWindow.webContents.on('did-fail-load', function() {
console.log("Failed to load index.html");
})
})
Now the renderer process:
// renderer.js
const { shell } = require('electron');
const parser = new DOMParser();
const resultSection = document.querySelector('.results');
const errorMessage = document.querySelector('.error-message');
const newCmdForm = document.querySelector('.new-cmd-form');
const newCmd = document.querySelector('.new-external-cmd');
const newCmdSubmit = document.querySelector('.new-cmd-run');
const clearStorageButton = document.querySelector('.clear-results');
newLinkForm.addEventListener('submit', (event) => {
const cmd = newCmd.value;
processCmd(cmd);
});
const processCmd = (cmd) => {
var exec = require('child_process').exec;
exec("ls -al", {timeout: 10000, maxBuffer: 20000*1024},
function(error, stdout, stderr) {
var out = stdout.toString();
var result =
'<div class="text"' +
`<h3>${out}</h3>` +
'</div>';
resultSection.innerHTML = result;
console.log(result)
});
}
const renderResults = () => {
resultSection.innerHTML = '';
};
renderResults();
Here is the page:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self' 'unsafe-inline';
connect-src *">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Cmdlet</title>
<link rel="stylesheet" href="style.css" type="text/css">
</head>
<h1>Cmdlet</h1>
<div class="error-message"></div>
<section class="input-new-cmd">
<form class="new-cmd-form">
<input type="text" class="new-external-cmd" placeholder="default command" size="100" required>
<input type="submit" class="new-cmd-run" value="Run">
</form>
</section>
<section class="results"></section>
<section class="controls">
<button class="clear-results">Clear</button>
</section>
<script>
require('./renderer');
</script>
</html>
Knowing that the calling of external command is async, I put the renderer update code in the callback. However, this code shows [object Object]
in the target area, instead of the output of ls -al
.
Where was I wrong?