diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92864bc4..e5fbff27 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,6 +34,7 @@ Special thanks to: @rejas, @sdetweil
- Correctly show apparent temperature in SMHI weather provider
- Ensure updatenotification module isn't shown when local is _ahead_ of remote
+- Handle node_helper errors during startup (#2944)
- Possibility to change FontAwesome class in calendar, so icons like `fab fa-facebook-square` works.
- Fix cors problems with newsfeed articles (as far as possible), allow disabling cors per feed with option `useCorsProxy: false` (#2840)
diff --git a/js/app.js b/js/app.js
index bd3f81d2..2be62198 100644
--- a/js/app.js
+++ b/js/app.js
@@ -223,24 +223,35 @@ function App() {
}
loadModules(modules, function () {
- httpServer = new Server(config, function (app, io) {
- Log.log("Server started ...");
+ httpServer = new Server(config);
+ const { app, io } = httpServer.open();
+ Log.log("Server started ...");
- const nodePromises = [];
+ const nodePromises = [];
+ for (let nodeHelper of nodeHelpers) {
+ nodeHelper.setExpressApp(app);
+ nodeHelper.setSocketIO(io);
- for (let nodeHelper of nodeHelpers) {
- nodeHelper.setExpressApp(app);
- nodeHelper.setSocketIO(io);
+ try {
nodePromises.push(nodeHelper.start());
+ } catch (error) {
+ Log.error(`Error when starting node_helper for module ${nodeHelper.name}:`);
+ Log.error(error);
}
+ }
- Promise.allSettled(nodePromises).then(() => {
- Log.log("Sockets connected & modules started ...");
-
- if (typeof callback === "function") {
- callback(config);
+ Promise.allSettled(nodePromises).then((results) => {
+ // Log errors that happened during async node_helper startup
+ results.forEach((result) => {
+ if (result.status === "rejected") {
+ Log.error(result.reason);
}
});
+
+ Log.log("Sockets connected & modules started ...");
+ if (typeof callback === "function") {
+ callback(config);
+ }
});
});
});
diff --git a/js/server.js b/js/server.js
index f7b918d3..bd86219f 100644
--- a/js/server.js
+++ b/js/server.js
@@ -5,7 +5,6 @@
* MIT Licensed.
*/
const express = require("express");
-const app = require("express")();
const path = require("path");
const ipfilter = require("express-ipfilter").IpFilter;
const fs = require("fs");
@@ -19,117 +18,119 @@ const Utils = require("./utils.js");
* Server
*
* @param {object} config The MM config
- * @param {Function} callback Function called when done.
* @class
*/
-function Server(config, callback) {
+function Server(config) {
+ const app = express();
const port = process.env.MM_PORT || config.port;
const serverSockets = new Set();
-
let server = null;
- if (config.useHttps) {
- const options = {
- key: fs.readFileSync(config.httpsPrivateKey),
- cert: fs.readFileSync(config.httpsCertificate)
+
+ this.open = function () {
+ if (config.useHttps) {
+ const options = {
+ key: fs.readFileSync(config.httpsPrivateKey),
+ cert: fs.readFileSync(config.httpsCertificate)
+ };
+ server = require("https").Server(options, app);
+ } else {
+ server = require("http").Server(app);
+ }
+ const io = require("socket.io")(server, {
+ cors: {
+ origin: /.*$/,
+ credentials: true
+ },
+ allowEIO3: true
+ });
+
+ server.on("connection", (socket) => {
+ serverSockets.add(socket);
+ socket.on("close", () => {
+ serverSockets.delete(socket);
+ });
+ });
+
+ Log.log(`Starting server on port ${port} ... `);
+ server.listen(port, config.address || "localhost");
+
+ if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
+ Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
+ }
+
+ app.use(function (req, res, next) {
+ ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
+ if (err === undefined) {
+ res.header("Access-Control-Allow-Origin", "*");
+ return next();
+ }
+ Log.log(err.message);
+ res.status(403).send("This device is not allowed to access your mirror.
Please check your config.js or config.js.sample to change this.");
+ });
+ });
+
+ app.use(helmet(config.httpHeaders));
+ app.use("/js", express.static(__dirname));
+
+ // TODO add tests directory only when running tests?
+ const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs", "/tests/mocks"];
+ for (const directory of directories) {
+ app.use(directory, express.static(path.resolve(global.root_path + directory)));
+ }
+
+ app.get("/cors", async function (req, res) {
+ // example: http://localhost:8080/cors?url=https://google.de
+
+ try {
+ const reg = "^/cors.+url=(.*)";
+ let url = "";
+
+ let match = new RegExp(reg, "g").exec(req.url);
+ if (!match) {
+ url = "invalid url: " + req.url;
+ Log.error(url);
+ res.send(url);
+ } else {
+ url = match[1];
+ Log.log("cors url: " + url);
+ const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 MagicMirror/" + global.version } });
+ const header = response.headers.get("Content-Type");
+ const data = await response.text();
+ if (header) res.set("Content-Type", header);
+ res.send(data);
+ }
+ } catch (error) {
+ Log.error(error);
+ res.send(error);
+ }
+ });
+
+ app.get("/version", function (req, res) {
+ res.send(global.version);
+ });
+
+ app.get("/config", function (req, res) {
+ res.send(config);
+ });
+
+ app.get("/", function (req, res) {
+ let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
+ html = html.replace("#VERSION#", global.version);
+
+ let configFile = "config/config.js";
+ if (typeof global.configuration_file !== "undefined") {
+ configFile = global.configuration_file;
+ }
+ html = html.replace("#CONFIG_FILE#", configFile);
+
+ res.send(html);
+ });
+
+ return {
+ app,
+ io
};
- server = require("https").Server(options, app);
- } else {
- server = require("http").Server(app);
- }
- const io = require("socket.io")(server, {
- cors: {
- origin: /.*$/,
- credentials: true
- },
- allowEIO3: true
- });
-
- server.on("connection", (socket) => {
- serverSockets.add(socket);
- socket.on("close", () => {
- serverSockets.delete(socket);
- });
- });
-
- Log.log(`Starting server on port ${port} ... `);
-
- server.listen(port, config.address || "localhost");
-
- if (config.ipWhitelist instanceof Array && config.ipWhitelist.length === 0) {
- Log.warn(Utils.colors.warn("You're using a full whitelist configuration to allow for all IPs"));
- }
-
- app.use(function (req, res, next) {
- ipfilter(config.ipWhitelist, { mode: config.ipWhitelist.length === 0 ? "deny" : "allow", log: false })(req, res, function (err) {
- if (err === undefined) {
- res.header("Access-Control-Allow-Origin", "*");
- return next();
- }
- Log.log(err.message);
- res.status(403).send("This device is not allowed to access your mirror.
Please check your config.js or config.js.sample to change this.");
- });
- });
- app.use(helmet(config.httpHeaders));
-
- app.use("/js", express.static(__dirname));
-
- // TODO add tests directory only when running tests?
- const directories = ["/config", "/css", "/fonts", "/modules", "/vendor", "/translations", "/tests/configs", "/tests/mocks"];
- for (const directory of directories) {
- app.use(directory, express.static(path.resolve(global.root_path + directory)));
- }
-
- app.get("/cors", async function (req, res) {
- // example: http://localhost:8080/cors?url=https://google.de
-
- try {
- const reg = "^/cors.+url=(.*)";
- let url = "";
-
- let match = new RegExp(reg, "g").exec(req.url);
- if (!match) {
- url = "invalid url: " + req.url;
- Log.error(url);
- res.send(url);
- } else {
- url = match[1];
- Log.log("cors url: " + url);
- const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 MagicMirror/" + global.version } });
- const header = response.headers.get("Content-Type");
- const data = await response.text();
- if (header) res.set("Content-Type", header);
- res.send(data);
- }
- } catch (error) {
- Log.error(error);
- res.send(error);
- }
- });
-
- app.get("/version", function (req, res) {
- res.send(global.version);
- });
-
- app.get("/config", function (req, res) {
- res.send(config);
- });
-
- app.get("/", function (req, res) {
- let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
- html = html.replace("#VERSION#", global.version);
-
- let configFile = "config/config.js";
- if (typeof global.configuration_file !== "undefined") {
- configFile = global.configuration_file;
- }
- html = html.replace("#CONFIG_FILE#", configFile);
-
- res.send(html);
- });
-
- if (typeof callback === "function") {
- callback(app, io);
- }
+ };
this.close = function () {
for (const socket of serverSockets.values()) {