From 66f93ee541ec5df8cd5f55bca1ba76d7821917b0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 25 Jun 2017 12:18:59 +0200 Subject: [PATCH 1/5] Added clientonly script Added clientonly script to have server and client run at different locations --- CHANGELOG.md | 1 + README.md | 5 +++ clientonly/index.js | 97 +++++++++++++++++++++++++++++++++++++++++ config/config.js.sample | 1 + js/defaults.js | 2 + js/electron.js | 14 +++--- js/server.js | 3 ++ package.json | 1 + 8 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 clientonly/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d25af7db..94d1ee24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Add ability to change the path of the `custom.css`. - Add translation Dutch to Alert module. - Added Romanian translation. +- Add `clientonly` script to start only the electron client for a remote server ### Updated - Added missing keys to Polish translation. diff --git a/README.md b/README.md index 8b0c66be..b7dea0fb 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ bash -c "$(curl -sL https://raw.githubusercontent.com/MichMich/MagicMirror/maste ### Server Only In some cases, you want to start the application without an actual app window. In this case, you can start MagicMirror² in server only mode by manually running `node serveronly` or using Docker. This will start the server, after which you can open the application in your browser of choice. Detailed description below. +### Client Only +When you have a server running remotely and want to connect a standalone client to this instance, you can manually run `node clientonly --address 192.168.1.5 --port 8080`. (Specify the ip address and port number of the server) + +**Important:** Make sure that you whitelist the interface/ip in the server config where you want the client to connect to, otherwise it will not be allowed to connect to the server + #### Docker MagicMirror² in server only mode can be deployed using [Docker](https://docker.com). After a successful [Docker installation](https://docs.docker.com/engine/installation/) you just need to execute the following command in the shell: diff --git a/clientonly/index.js b/clientonly/index.js new file mode 100644 index 00000000..1a8fa0a6 --- /dev/null +++ b/clientonly/index.js @@ -0,0 +1,97 @@ +/* jshint esversion: 6 */ + +"use strict"; + +// Use seperate scope to prevent global scope pollution +(function () { + const cookie = require("cookie"); + + var config = { }; + + // Parse command line arguments, if any + var addressIndex = process.argv.indexOf("--address"); + var portIndex = process.argv.indexOf("--port"); + + if (addressIndex > -1) { + config.address = process.argv[addressIndex + 1]; + } else { + fail(); + } + if (portIndex > -1) { + config.port = process.argv[portIndex + 1]; + } else { + fail(); + } + + function fail(message, code = 1) { + if (message !== undefined && typeof message === "string") { + console.log(message); + } else { + console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080'"); + } + process.exit(code); + } + + function getServerConfig(url) { + // Return new pending promise + return new Promise((resolve, reject) => { + // Select http or https module, depending on reqested url + const lib = url.startsWith("https") ? require("https") : require("http"); + const request = lib.get(url, (response) => { + // Handle http errors + if (response.statusCode < 200 || response.statusCode > 299) { + reject(new Error(`Failed to load page, status code: ${response.statusCode}`)); + } + if (response.headers["set-cookie"]) { + response.headers["set-cookie"].forEach( + function (cookiestr) { + if (cookiestr.startsWith("config")) { + var cookieString = JSON.parse(cookie.parse(cookiestr)["config"]); + resolve(cookieString); + } + } + ); + }; + reject(new Error(`Unable to read config cookie from server (${url}`)); + }); + // Handle connection errors of the request + request.on("error", (err) => reject(new Error(`Failed to load page, error message: ${err}`))); + }) + }; + + // Only start the client if a non-local server was provided + if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) { + getServerConfig(`http://${config.address}:${config.port}/`) + .then(function (cookieConfig) { + // Pass along the server config via an environment variable + var env = Object.create( process.env ); + var options = { env: env }; + cookieConfig.address = config.address; + cookieConfig.port = config.port; + env.config = JSON.stringify(cookieConfig); + + // Spawn electron application + const electron = require("electron"); + const child = require("child_process").spawn(electron, ["js/electron.js"], options ); + + // Pipe all child process output to current stdout + child.stdout.on("data", function (buf) { + process.stdout.write(`Client: ${buf}`); + }); + + // Pipe all child process errors to current stderr + child.stderr.on("data", function (buf) { + process.stderr.write(`Client: ${buf}`); + }); + + child.on("error", function (err) { + process.stdout.write(`Client: ${err}`); + }); + }) + .catch(function (reason) { + fail(`Unable to connect to server: (${reason})`); + }); + } else { + fail(); + } +}()); \ No newline at end of file diff --git a/config/config.js.sample b/config/config.js.sample index b2eeee8a..8294e319 100644 --- a/config/config.js.sample +++ b/config/config.js.sample @@ -9,6 +9,7 @@ */ var config = { + address: "localhost", port: 8080, ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"], // Set [] to allow all IP addresses // or add a specific IPv4 of 192.168.1.5 : diff --git a/js/defaults.js b/js/defaults.js index eada87a4..08c4d945 100644 --- a/js/defaults.js +++ b/js/defaults.js @@ -8,10 +8,12 @@ */ var port = 8080; +var address = "localhost"; if (typeof(mmPort) !== "undefined") { port = mmPort; } var defaults = { + address: address, port: port, kioskmode: false, electronOptions: {}, diff --git a/js/electron.js b/js/electron.js index 334a3593..d55f17a0 100644 --- a/js/electron.js +++ b/js/electron.js @@ -6,7 +6,7 @@ const electron = require("electron"); const core = require(__dirname + "/app.js"); // Config -var config = {}; +var config = process.env.config ? JSON.parse(process.env.config) : {}; // Module to control application life. const app = electron.app; // Module to create native browser window. @@ -47,7 +47,7 @@ function createWindow() { // and load the index.html of the app. //mainWindow.loadURL('file://' + __dirname + '../../index.html'); - mainWindow.loadURL("http://localhost:" + config.port); + mainWindow.loadURL(`http://${config.address}:${config.port}`); // Open the DevTools if run with "npm start dev" if (process.argv.includes("dev")) { @@ -96,8 +96,10 @@ app.on("activate", function() { } }); -// Start the core application. +// Start the core application if server is run on localhost // This starts all node helpers and starts the webserver. -core.start(function(c) { - config = c; -}); +if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) > -1) { + core.start(function (c) { + config = c; + }); +} \ No newline at end of file diff --git a/js/server.js b/js/server.js index 002c2031..6ac5fe04 100644 --- a/js/server.js +++ b/js/server.js @@ -62,6 +62,9 @@ var Server = function(config, callback) { } html = html.replace("#CONFIG_FILE#", configFile); + // Set a temporary cookie called "config" to the JSON encoded config object + res.cookie("config", JSON.stringify(config)); + res.send(html); }); diff --git a/package.json b/package.json index fe64cc41..64a01a6d 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "dependencies": { "body-parser": "^1.17.2", "colors": "^1.1.2", + "cookie": "^0.3.1", "electron": "^1.6.10", "express": "^4.15.3", "express-ipfilter": "latest", From 1590693547bf207090f1b451e5f23e6de9821660 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 29 Jun 2017 21:22:00 +0200 Subject: [PATCH 2/5] New server route to fetch config Added a new route to the Express server to supply client with config. Removed the original 'cookie' hack --- clientonly/index.js | 47 +++++++++++++++++++++------------------------ js/server.js | 7 ++++--- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/clientonly/index.js b/clientonly/index.js index 1a8fa0a6..7212fd7e 100644 --- a/clientonly/index.js +++ b/clientonly/index.js @@ -6,7 +6,7 @@ (function () { const cookie = require("cookie"); - var config = { }; + var config = {}; // Parse command line arguments, if any var addressIndex = process.argv.indexOf("--address"); @@ -38,41 +38,38 @@ // Select http or https module, depending on reqested url const lib = url.startsWith("https") ? require("https") : require("http"); const request = lib.get(url, (response) => { - // Handle http errors - if (response.statusCode < 200 || response.statusCode > 299) { - reject(new Error(`Failed to load page, status code: ${response.statusCode}`)); - } - if (response.headers["set-cookie"]) { - response.headers["set-cookie"].forEach( - function (cookiestr) { - if (cookiestr.startsWith("config")) { - var cookieString = JSON.parse(cookie.parse(cookiestr)["config"]); - resolve(cookieString); - } - } - ); - }; - reject(new Error(`Unable to read config cookie from server (${url}`)); + var configData = ""; + + // Gather incomming data + response.on("data", function(chunk) { + configData += chunk; + }); + // Resolve promise at the end of the HTTP/HTTPS stream + response.on("end", function() { + resolve(JSON.parse(configData)); + }); + }); + + request.on("error", function(error) { + reject(new Error(`Unable to read config from server (${url} (${error.message}`)); }); - // Handle connection errors of the request - request.on("error", (err) => reject(new Error(`Failed to load page, error message: ${err}`))); }) }; // Only start the client if a non-local server was provided if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) { - getServerConfig(`http://${config.address}:${config.port}/`) - .then(function (cookieConfig) { + getServerConfig(`http://${config.address}:${config.port}/config/`) + .then(function (config) { // Pass along the server config via an environment variable - var env = Object.create( process.env ); + var env = Object.create(process.env); var options = { env: env }; - cookieConfig.address = config.address; - cookieConfig.port = config.port; - env.config = JSON.stringify(cookieConfig); + config.address = config.address; + config.port = config.port; + env.config = JSON.stringify(config); // Spawn electron application const electron = require("electron"); - const child = require("child_process").spawn(electron, ["js/electron.js"], options ); + const child = require("child_process").spawn(electron, ["js/electron.js"], options); // Pipe all child process output to current stdout child.stdout.on("data", function (buf) { diff --git a/js/server.js b/js/server.js index 6ac5fe04..8520e392 100644 --- a/js/server.js +++ b/js/server.js @@ -52,6 +52,10 @@ var Server = function(config, callback) { res.send(global.version); }); + app.get("/config", function(req,res) { + res.send(config); + }); + app.get("/", function(req, res) { var html = fs.readFileSync(path.resolve(global.root_path + "/index.html"), {encoding: "utf8"}); html = html.replace("#VERSION#", global.version); @@ -62,9 +66,6 @@ var Server = function(config, callback) { } html = html.replace("#CONFIG_FILE#", configFile); - // Set a temporary cookie called "config" to the JSON encoded config object - res.cookie("config", JSON.stringify(config)); - res.send(html); }); From 8eb772d80b98ad13832430ce00d4a60b13bf942a Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 29 Jun 2017 21:32:48 +0200 Subject: [PATCH 3/5] Allow use of env variables Made some changes that allows the use of environment variables when starting the standalone client. --- clientonly/index.js | 48 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/clientonly/index.js b/clientonly/index.js index 7212fd7e..4eeabf42 100644 --- a/clientonly/index.js +++ b/clientonly/index.js @@ -5,31 +5,22 @@ // Use seperate scope to prevent global scope pollution (function () { const cookie = require("cookie"); - var config = {}; - // Parse command line arguments, if any - var addressIndex = process.argv.indexOf("--address"); - var portIndex = process.argv.indexOf("--port"); - - if (addressIndex > -1) { - config.address = process.argv[addressIndex + 1]; - } else { - fail(); - } - if (portIndex > -1) { - config.port = process.argv[portIndex + 1]; - } else { - fail(); - } - - function fail(message, code = 1) { - if (message !== undefined && typeof message === "string") { - console.log(message); - } else { - console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080'"); + // Helper function to get server address/hostname from either the commandline or env + function getServerAddress() { + // Helper function to get command line parameters + // Assumes that a cmdline parameter is defined with `--key [value]` + function getCommandLineParameter(key, defaultValue = undefined) { + var index = process.argv.indexOf(`--${key}`); + var value = index > -1 ? process.argv[index + 1] : undefined; + return value !== undefined ? String(value) : defaultValue; } - process.exit(code); + + // Prefer command line arguments over environment variables + ["address", "port"].forEach((key) => { + config[key] = getCommandLineParameter(key, process.env[key.toUpperCase()]); + }) } function getServerConfig(url) { @@ -56,6 +47,19 @@ }) }; + function fail(message, code = 1) { + if (message !== undefined && typeof message === "string") { + console.log(message); + } else { + console.log("Usage: 'node clientonly --address 192.168.1.10 --port 8080'"); + } + process.exit(code); + } + + getServerAddress(); + + (config.address && config.port) || fail(); + // Only start the client if a non-local server was provided if (["localhost", "127.0.0.1", "::1", "::ffff:127.0.0.1", undefined].indexOf(config.address) === -1) { getServerConfig(`http://${config.address}:${config.port}/config/`) From a05e69b85566c17cf9541048a6e425c827d02146 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 30 Jun 2017 08:20:42 +0200 Subject: [PATCH 4/5] Removed cookie dependencies Removed module import and dependency on the 'cookie' library; it's not used anymore --- clientonly/index.js | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/clientonly/index.js b/clientonly/index.js index 4eeabf42..750a98e6 100644 --- a/clientonly/index.js +++ b/clientonly/index.js @@ -4,7 +4,6 @@ // Use seperate scope to prevent global scope pollution (function () { - const cookie = require("cookie"); var config = {}; // Helper function to get server address/hostname from either the commandline or env diff --git a/package.json b/package.json index 64a01a6d..fe64cc41 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "dependencies": { "body-parser": "^1.17.2", "colors": "^1.1.2", - "cookie": "^0.3.1", "electron": "^1.6.10", "express": "^4.15.3", "express-ipfilter": "latest", From 04b550e435cbd6d11a525e2b0cbc359a06315609 Mon Sep 17 00:00:00 2001 From: Michael Teeuw Date: Wed, 12 Jul 2017 10:58:28 +0200 Subject: [PATCH 5/5] Update correct version. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94d1ee24..8c4e8836 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed ### Added +- Add `clientonly` script to start only the electron client for a remote server ### Updated ### Fixed @@ -33,7 +34,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Add ability to change the path of the `custom.css`. - Add translation Dutch to Alert module. - Added Romanian translation. -- Add `clientonly` script to start only the electron client for a remote server ### Updated - Added missing keys to Polish translation.