coreybutler / node-windows

Windows support for Node.JS scripts (daemons, eventlog, UAC, etc).
Other
2.8k stars 356 forks source link

Using Electron with Node-Windows #173

Closed DrYSG closed 4 years ago

DrYSG commented 7 years ago

I am using the util.promisfy() feature of node V8.1 (which is the only version of node on my machine).

But I am getting an error when I try to use it in the service. Ideas?

(no issues if I just run my app from the command line:

$ node app.js

TypeError: util.promisify is not a function
    at Object.<anonymous> (d:\wwwroot\librarian2017\Watson\api\FilePacker.js:7:22)
    at Object.<anonymous> (d:\wwwroot\librarian2017\Watson\api\FilePacker.js:49:3)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (d:\wwwroot\librarian2017\Watson\api\falconzip.js:2:24)
d:\wwwroot\librarian2017\Watson\api\FilePacker.js:7
const dbQuery = util.promisify(pgComm.pgQuery)
DrYSG commented 7 years ago

Can you confirm or deny if this might be the problem?

I am using node-windows in a electron application. The Electron app installs a nodejs app that lives in a different folder.

When I install the service, I see that electron is the child process for the egplwatsonservice.exe

const watsonApp = path.join(__dirname, "..", "..", "..", "Watson", "app.js")
let svc = new Service({
    name: 'EGPL Watson Service',
    description: 'The EGPL GeoLibrarian Watson Web Service',
    script: watsonApp,
    env: {
        name: "NODE_ENV",
        value: process.env["NODE_ENV"]
    }
});

And since electron is still at node version 7.4 https://electron.atom.io/

it is not using node V8.1

Does this sound correct?

(If however, I had used a nodejs (without the electron interface, to create the service and to start it, then the child process for the EXE would have been NODE V8, and there would be no problem.)

coreybutler commented 7 years ago

I don't think this is a node-windows problem. Please see My script runs fine on it's own, but not with node-windows. Why?, specifically in regard to __dirname. Node-windows will use whatever version of Node it first finds on the PATH.

I'm closing this since I'm confident this is an implementation issue, not a problem with node-windows.

DrYSG commented 7 years ago

I don't debate that it is not directly a node-windows issue. But it seems that if invoke the node-windows from inside electron.exe it seems to use electron rather than node as the child process for the EXE service.

Does that sound correct?

DrYSG commented 7 years ago

@coreybutler I really appreciate this software. I also feel bad, since it seems that I did not communicate this issue clearly, and for that I take the blame. Partially it is due to the fact that I did not understand it correctly at first.

Let me try again.

I am using Electron which is and EXE built out of Node 7.4 and Chromium V56. It allows one to make Windows Desktop applications. We are using it to create a desktop GUI with buttons to start and stop and install and remove Windows services.

What I have observed and confirmed, is that if node-windows is used from within Electron, The service is created and run (with a new top level deamon EXE image) and two child processes running Electron.EXE

(if the invoker of the node-windows was a naked node process, then it would be two NODE.EXE processes).

So it does not appear that Node-Windows users whatever version of Node it finds first on the path, it seems to be connected to who created the service.

Now, it is up to to decide if that really is a case that needs to be dealt with. I have a work around by using a util.promisify polyfill which is allowing my V8.1 code to work as a service.

Perhaps as a minimum some documentation should be written to describe this effect.

AlexanderFeijoo commented 6 years ago

Did you ever get an answer to this? @DrYSG I am experiencing the same problem. In Development, my electron GUI app launches a node-windows node.js script just fine, but in production the actual electron.exe itself is being launched. Funnily enough it actually works, but it is logging all of electron's outputs, not the node.js script.

coreybutler commented 6 years ago

This fell off my radar.

node-windows identifies the executable path based on the current running process (https://github.com/coreybutler/node-windows/blob/master/lib/winsw.js#L72). Electron overrides this. There are some PR's to fix this, but I haven't had a chance to review. Same goes for the user account.

I haven't had much contact from any maintainers, so I'll try to look into this as soon as I can. In the meantime, I'll reopen this issue.

Sai1919 commented 6 years ago

@coreybutler , I am also facing some issue when node-windows is used in context of electron. Basically when I run node-windows alone it is creating service and running the supplied script properly. But when it is running in electron context then the service is getting created but the script is not running. Any of you have some details on this?

@DrYSG Hi, Did you succeed in creating and running the service from within electron?

DrYSG commented 6 years ago

@Sai1919 @AlexanderFeijoo

I am using node 9.2.1 and the following dependencies and it is working for me. I have not updated for a while.

"dependencies": {
    "electron-devtools-installer": "^2.1.0",
    "get-folder-size": "^1.0.0",
    "immutable": "^3.8.1",
    "lodash": "^4.17.4",
    "node-windows": "^0.1.14",
    "react": "^15.4.2",
    "react-bootstrap": "^0.31.0",
    "react-dom": "^15.4.2",
    "react-icons": "^2.2.5",
    "react-redux": "^5.0.5",
    "redux": "^3.6.0",
    "redux-devtools": "^3.3.2",
    "redux-devtools-extension": "^2.13.1",
    "redux-form": "^6.7.0",
    "redux-thunk": "^2.2.0",
    "rmdir": "^1.2.0",
    "rxjs": "^5.4.0",
    "shallow-equal": "^1.0.0"
  },
  "devDependencies": {
    "babel-cli": "^6.22.2",
    "babel-preset-env": "^1.5.1",
    "babel-preset-node7": "^1.5.0",
    "babel-preset-react": "^6.22.0",
    "devtron": "^1.4.0",
    "electron": "^1.6.7",
    "electron-builder": "^17.10.0",
    "redux-devtools-extension": "^2.13.2"
AlexanderFeijoo commented 6 years ago

@DrYSG You mentioned you had to use a util.promisify polyfill to resolve the initial issues with the service trying to run electron as the executable instead of the .js file. Is that still true or does node windows work out of the box with the above dependencies?

Would you mind terribly uploading the function that you are initializing and calling your service from within electron? The relative file pathing with a webpack / electron output has been confounding me for days now

Here is a simplified version of the function I'm calling to try to start the service. Do you have any advice for correctly setting the path?

This launches the service perfectly from within electron in development, and even if I run an 'unpacked' production build, but not from a NSIS installer production build packaged as an asar archive

export function startService(a, bunch, passed, params) {

        var Service = window.require('node-windows').Service;
        var a = JSON.stringify(a);
        var bunch = JSON.stringify(bunch);
        var passed = JSON.stringify(passed);
        var params = JSON.stringify(params);

          var svc = new Service({
            name:'Monin Order Sync',
            description: 'The Monin OrderSync Schedule Sync Job Service',
            nodeOptions: [
              '--harmony',
              '--experimental-modules'
            ],
            env: [
              {
                name: "a",
                value: a
              },
              {
                name: "bunch",
                value: bunch
              },
              {
                name: "passed",
                value: "passed"
              },
              {
                name: "params",
                value: params
              }
          ],
            script: 'C:\\Absolute\\Path\\To\\Script.js', //If I try to make this a relative path it always defaults to "C:\\" in production

          });

          // Listen for the "install" event, which indicates the 
          // process is available as a service. 

          svc.on('install',function(){
          svc.start();
          });

          svc.on('alreadyinstalled',function(){
            console.log('This service is already installed.');
          });

          svc.on('start',function(){
            console.log(svc.name+' started!');
          })

          svc.install();

        } 

@coreybutler Thankyou for re-opening this corey. Your package has been a lifesaver so far and I'm so close to making a fully functioning gui app with react and electron that runs a node.js script as a service. In the meantime I'm trying to exclude the service.js file from the asar package with "extraResources" and then run it from appData using 'nearly' absolute paths from within electron. ill let you know how this works out.

DrYSG commented 6 years ago

I am not using it now. But util.promisfy() is part of the Node V8:

https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original

DrYSG commented 6 years ago

Here is how I start my service. It is located in a folder three levels up from where I call the start/stop, that is ../../../watson/app.js , it is a express node applications that fields web calls.

node-windows code i am using is:

const Service = require('node-windows').Service
const path = require('path')

const watsonApp = path.join(__dirname, "..", "..", "..", "Watson", "app.js")
let svc = new Service({
    name: 'EGPL Watson Service',
    description: 'The EGPL GeoLibrarian Watson Web Service',
    script: watsonApp,
    env: {
        name: "NODE_ENV",
        value: process.env["NODE_ENV"]
    }
});

exports.setup = function () {
    global.DASH.stats.watsonInstalled = svc.exists
};

exports.install = function (cb) {
    if (svc.exists) return;
    svc.on('install', () => {
        global.DASH.stats.watsonInstalled = svc.exists;
        cb(null)
        return
    })
    svc.on('error', () => {
        err = new Error('svc install error')
        err.description = 'svc install error' + "\n" + err.message
        cb(err)
        return
    })
    svc.on('invalidinstallation ', () => {
        err = new Error('svc invalidinstallation')
        err.description = 'svc iinvalidinstallation' + "\n" + err.message
        cb(err)
        return
    })
    svc.install()
}

exports.uninstall = function (cb) {
    if (!svc.exists) return;
    svc.on('uninstall', () => {
        global.DASH.stats.watsonInstalled = svc.exists;
        cb(null)
        return
    })
    svc.uninstall()
}

exports.start = function (cb) {
    if (!svc.exists) return;
    if (global.DASH.stats.watsonRunning) return
    svc.on('start', () => {
        global.DASH.stats.watsonRunning = true
        cb(null)
        return
    })
    svc.start()
}

exports.stop = function (cb) {
    if (!global.DASH.stats.watsonRunning) return;
    svc.on('stop', () => {
        global.DASH.stats.watsonRunning = false
        cb(null)
        return
    })
    svc.stop()
}
AlexanderFeijoo commented 6 years ago

@DrYSG Sorry to be a pain, I want you to know how much I appreciate you taking your time to help me out with this problem. It's because of people like you working to improve the community that web-development is even possible.

Would you mind showing me how you call that function you just described above, and letting me know a little bit more about the app setup? If it is an open project I'd be more than happy to look through the code myself.

So I'm using a two package JSON structure with electron and webpack, bundling my app as an asar archive and targeting NSIS windows installation. My service and the function that calls it both live in two seperate files but in the same directory, i.e.

-root
     -service
          -exportStartService.js
          -theActualNodeScript.js

When webpack has finished its magic, both of these files are packed into a main.js bundle in the production directory (at least I believe this is what's happening) i.e.

-app
     -browser-app
          -dist
               main.js //all of my javascript is getting bundled into here
-root
     -service
          -exportStartService.js
          -theActualNodeScript.js

Are you using webpack to bundle your code? I would think that using the relative paths would create an accurate map so that my function could find my nodeJs script but it seems like once it goes in the bundle the pathing breaks. Not sure what's up

DrYSG commented 6 years ago

The project is not open. I use rollup when I want to package web apps, see: https://medium.com/webpack/webpack-and-rollup-the-same-but-different-a41ad427058c. You are not being a pain, this is how we collaborate, today I am in the giving role, which is nice for a change. I am not even doing rollup here, since it is electron and a deskside app, I don't need to worry about a small bundle. It is a react/redux project, so I use babel.

The code I showed you was from the main electron process. it is part of a file called watson.js

from the other thread, I am using the remote.require to pull it in:

const watson = remote.require('../main/watson')

and then I invoke from the react gui operations via:

export const serverAction = (action, target) => (dispatch, getState) => {
    switch (action) {
        case 'install':
            watson.install((err) => {
                if (err) {
                    utils.errorLog("watston install", err.description)
                }
            })
            break
        case 'uninstall':
            watson.uninstall((err) => {
                if (err) {
                    utils.errorLog("watston un-install", err.description)
                }
            })
            break
        case 'run':
            watson.start((err) => {
                if (err) {
                    utils.errorLog("watston run error", err.description)
                }
            })
            break
        case 'stop':
            watson.stop((err) => {
                if (err) {
                    utils.errorLog("watston stop error", err.description)
                }
            })
            break
        case 'stopFME':
            utils.killFME((err) => {
                if (err) {
                    utils.errorLog("FME kill error", err.description)
                }
            })
            break
        case 'info':
            utils.infoFME((err) => {
                if (err) {
                    utils.errorLog("FME Error", err.description)
                }
            })
            break

        default:
            alert(action)
    }
}
jdziat commented 6 years ago

Has this been resolved? This seems to be more implementation specific than an actual problem with the package itself.

coreybutler commented 4 years ago

Closing again since it does appear to be implementation-based rather than a problem. There are now notes in the wiki about implementations with Electron.