javascript - Node.js, vs. Electron:: Run external command and show the stdout on interface - Stack Overflow

I'm new to node.js and Electron and trying to run an external mand in Electron and show the result

I'm new to node.js and Electron and trying to run an external mand 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

  • Node.js spawn child process and get terminal output live
  • Piping data from child to parent in nodejs

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 mand 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 mand" 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 mand 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?

I'm new to node.js and Electron and trying to run an external mand 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

  • Node.js spawn child process and get terminal output live
  • Piping data from child to parent in nodejs

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 mand 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 mand" 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 mand 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?

Share Improve this question edited Jan 9, 2020 at 2:44 kakyo asked Jan 8, 2020 at 12:57 kakyokakyo 11.7k14 gold badges89 silver badges167 bronze badges 3
  • Well, since you also console.log the result, could you share what exactly is inside this object ([object Object]) that's displayed in your HTML? – Alexander Leithner Commented Jan 8, 2020 at 16:27
  • @AlexanderLeithner it's supposed to be var out = stdout.toString(); so the stdout from the spawned child process. – kakyo Commented Jan 9, 2020 at 2:29
  • Not sure what you get when doing require('child_process') in the renderer process... – ghybs Commented Jan 9, 2020 at 3:56
Add a ment  | 

2 Answers 2

Reset to default 4
// 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 newLinkForm = document.querySelector('.new-cmd-form');
const clearStorageButton = document.querySelector('.clear-results');

newLinkForm.addEventListener('submit', (event) => {
    event.preventDefault()
    const cmd = newCmd.value;
    console.log(event)
    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();
            const outArray = out.split('\n');

            let result = '<div class="text"'
            outArray.forEach(e => {
                result += `<h3>${e}</h3>`
            });
            result += '</div>';
            resultSection.innerHTML = result;
            console.log(result)
        });
}

const renderResults = () => {
    resultSection.innerHTML = '';
};

renderResults();

Try to use this. I added some changes at your renderer.js This will be working well.

But I'd like to remend to do these kind of operation at main process. You know we can use IPC api to municate between Renderer and Main process.

Have a look at this https://www.christianengvall.se/main-and-renderer-process-in-electron/

But personally, I don't usually run exec at Renderer process. For this operation, I rather to use this such exec or spawn at main process. We can use IPC to municate between Main and Renderer process.

IPCMain is used for listening the events from the Renderer and it's available at only Main process. And IPCRenderer is used for sending the events from Renderer to Main process. So that.

Using IPCRenderer, You can send the event like this at Renderer process.

const { ipcRenderer } = require('electron');

async function runCommand(cmd) {
  const res = await ipcRenderer.sendSync('runCommand', cmd);
  return res;
}

Then at Main process. (main.js)

// Listen runCommand event from the Renderer
// And return the result to Renderer.
ipcMain.on('runCommand', async (event, arg) => {
  event.returnValue = await runCommand(arg);
});

(you can declare the runCommand(arg) function yourself as your needs) Feel free to use whatever you'd like.

  1. Use the node approach
  2. Use IPCRenderer and IPCMain to poll/respond as to whether the mand has pleted
  3. When IPCRenderer is told the content is ready, trigger an AJAX exchange to populate the renderer/website

PS: The documentation on how to use IPC in Electron seemed unclear to me -- and some of it seems not to work correctly! -- so if you need a simple & working example, here's my app's startup splash and here's the app code that responds (see, mainly, lines 96-109 of the latter link).

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745335416a4623078.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信