2

In Electron, I'm trying to get files that would normally have a
file:// url origin
to be served instead from
localhost.

In order to do this I've set up an internal http server in the electron app to handle serving pages (full code is quark.js below).

server = spawn("node", [path.join(opts.dir, "../../../app.asar.unpacked/http/page-server.js")], {
               env: {
                 QUARK_PORT: opts.port || 21048,
                 QUARK_DIR: opts.dir || __dirname,
                 QUARK_DESC: opts.description || "default",
                 QUARK_LOG: opts.log || false
               }});

Problem - The above works on my machine with Node installed but NodeJS is not available on the production machines.

Question - This you can fork nodejs file inside electron process would suggest you can do this without having NodeJS on the production machines. Is this correct? I can't seem to get this working with error no 'on' method of undefined when I try to following instead of spawn.

const { fork } = require("child_process");

server = fork(
  path.join(opts.dir, "../../../app.asar.unpacked/http/page-server.js"),
  [],
  {
    env: {
      QUARK_PORT: opts.port || 21048,
      QUARK_DIR: opts.dir || __dirname,
      QUARK_DESC: opts.description || "default",
      QUARK_LOG: opts.log || false
    }
  }
);

Raw files and folder structure

Summary

  • main.ts - The usual electron main process but calling quark.js to set up http server
  • quark.js - Sets up http server
  • page-server.js - Serves pages

main.ts (electron)

import { app, BrowserWindow } from "electron";
import * as path from "path";
const os = require("os");
const quark = require("../../http/quark");

quark({
  port: 3000,
  dir: path.join(__dirname, "../../../app.asar.unpacked/dist/procurement-app"),
  description: "HTTP Server"
});

let win: BrowserWindow;

function createWindow() {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadURL(quark.url(`index.html`));

  win.on("closed", () => {
    win = null;
  });
}

app.on("ready", createWindow);
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") {
    app.quit();
  }
});
app.on("activate", () => {
  if (win === null) {
    createWindow();
  }
});

quark.js

/**
 *  To get Auth0 to work by using an internal express server in my electron app to handle serving pages.
 *  Otherwise the lock fails with a 403 error and responds with "Origin file:// is not allowed".
 *
 *  https://stackoverflow.com/questions/48465130/electron-auth0lock-origin-file-not-allowed/48466642#48466642
 *  https://github.com/lawrencezahner/quark
 */
const { spawn } = require("child_process");
const path = require("path");
const url = require("url");

let server;

var quark = function(opts) {
  if (server == null) {
    server = spawn(
      "node",
      [path.join(opts.dir, "../../../app.asar.unpacked/http/page-server.js")],
      {
        env: {
          QUARK_PORT: opts.port || 21048,
          QUARK_DIR: opts.dir || __dirname,
          QUARK_DESC: opts.description || "default",
          QUARK_LOG: opts.log || false
        }
      }
    ).on("error", err => {
      console.log("spawn error");
      throw err;
    });

    server.opts = opts;

    /** Logging */
    server.stdout.on("data", data => {
      console.log(data.toString());
    });

    server.stderr.on("data", data => {
      console.log(data.toString());
    });
  }
};

quark.url = function(path) {
  return url.format({
    hostname: "localhost",
    port: server.opts.port,
    pathname: path,
    protocol: "http"
  });
};

quark.html = function(path) {
  return quark.url(`${path}.html`);
};

quark.js = function(path) {
  return quark.url(`${path}.js`);
};

quark.scss = function(path) {
  return quark.url(`${path}.scss`);
};

module.exports = quark;

page-server.js

const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs");
const mime = require("mime-types");

const server = http.createServer(function(req, res) {
  /** Logging */
  console.log(`${req.method} ${req.url}`);

  var purl = url.parse(req.url);

  var pathname = path.join(
    process.env.QUARK_DIR,
    "../../../app.asar.unpacked/dist/procurement-app",
    purl.pathname
  );
  var ext = path.parse(pathname).ext;

  fs.exists(pathname, exists => {
    if (!exists) {
      res.statusCode = 404;
      res.end(`Could not find ${pathname} :(`);
      return;
    } else {
      fs.readFile(pathname, (err, data) => {
        if (err) {
          res.statusCode = 500;
          res.end(`Had trouble getting ${pathname}`);
        } else {
          res.setHeader("Content-type", mime.contentType(ext) || "text/plain");
          res.setHeader(
            "Access-Control-Allow-Origin",
            url.format({
              hostname: "localhost",
              port: process.env.QUARK_PORT,
              protocol: "http"
            })
          );

          res.end(data);
          return;
        }
      });
    }
  });
});

server.listen(process.env.QUARK_PORT);

/** Logging */
console.log(`Quark Server started on ${process.env.QUARK_PORT}`);

folder structure

├───app-builds
│   ├───.icon-ico
│   └───win-unpacked
│       ├───locales
│       ├───resources
│       │   └───app.asar.unpacked
│       │       ├───dist
│       │       │   └───procurement-app
│       │       ├───http
│       │       └───node_modules
│       │           ├───mime-db
│       │           └───mime-types
│       └───swiftshader
├───dist
│   └───procurement-app
│       └───assets
│           ├───icons
│           └───images
├───electron
│   └───[main.ts]
│   └───dist
│       └───[main.js]
├───http
│   └───[page-server.js, quark.js]
├───node_modules
└───src
    └───app

Note I'm following the answer to this question Electron Auth0Lock "Origin file:// not allowed" (the question is the same reason I'm trying to do this)

Andrew Allen
  • 6,512
  • 5
  • 30
  • 73
  • 1
    The documentation for `fork` says *"By default, `child_process.fork()` will spawn new Node.js instances using the `process.execPath` of the parent process. The `execPath` property in the `options` object allows for an alternative execution path to be used."* The documentation for `process.execPath` shows that it's (typically) referring to the `node` executable. So it's still using Node.js. That raises a question: What is the value of `process.execPath` in the Electron process (I assume it's the main process, not a renderer process) where you're doing this? – T.J. Crowder Dec 11 '19 at 09:36
  • Thanks for helping. `process.execPath` is `C:\Users\andrewa\AppData\Local\Temp\1Upf0SQvE7eIpLTlhxe9gNxtJH6\ProcurementApp.exe` in `main.ts` and just before the spawn in `quark.ts` which I guess explains why fork doesn't work. At the top of `page-server.ts` I get `C:\Program Files\nodejs\node.exe`. I'm not doing any rendering myself just loading via win.loadURL the js files produced from the app (not much experience of Electron other than using it to create the exe that displays the app's index.html). Is it possible to use Electron's Node API to set up http server? – Andrew Allen Dec 11 '19 at 10:27
  • 1
    I haven't done anything with Electron (yet!). But my understanding is you shouldn't need an HTTP server, the main process and the renderer process for the page you're loading can [talk to each other](https://electronjs.org/docs/tutorial/application-architecture#aside-communication-between-processes). So your main process code should be able to read the file (via the Node.js `fs` module) and send the data to your renderer process in the page. But again, sadly, I haven't actually done it, so... Good luck! – T.J. Crowder Dec 11 '19 at 10:33
  • In case this doen't get an answer:- a workaround I've found is to put `node.exe` and `node.lib` in the root folder (downloadable via https://nodejs.org/dist/v13.3.0/win-x64/ eg using node https://stackoverflow.com/questions/11944932), use `"extraResources": ["node.exe", "node.lib"],` in `electron-builder.json` so that these are in the resources folder (e.g `C:\Users\andrewa\AppData\Local\Temp\1Upf0SQvE7eIpLTlhxe9gNxtJH6\resources\` or `path.join(__dirname, "./node.exe")`) then you can use `spawn(path.join(__dirname, "./node.exe"), ...)` – Andrew Allen Dec 11 '19 at 15:29

0 Answers0