Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add error handling to node_helper startup sequence #2945

Merged
merged 4 commits into from
Oct 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
33 changes: 22 additions & 11 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
khassel marked this conversation as resolved.
Show resolved Hide resolved
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);
}
});
});
});
Expand Down
189 changes: 95 additions & 94 deletions js/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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)
};
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);

this.open = function () {
rejas marked this conversation as resolved.
Show resolved Hide resolved
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
});
});

Log.log(`Starting server on port ${port} ... `);
server.on("connection", (socket) => {
serverSockets.add(socket);
socket.on("close", () => {
serverSockets.delete(socket);
});
});

server.listen(port, config.address || "localhost");
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"));
}
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. <br> Please check your config.js or config.js.sample to change this.");
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. <br> 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.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("/version", function (req, res) {
res.send(global.version);
});
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("/config", function (req, res) {
res.send(config);
});
app.get("/version", function (req, res) {
res.send(global.version);
});

app.get("/", function (req, res) {
let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version);
app.get("/config", function (req, res) {
res.send(config);
});

let configFile = "config/config.js";
if (typeof global.configuration_file !== "undefined") {
configFile = global.configuration_file;
}
html = html.replace("#CONFIG_FILE#", configFile);
app.get("/", function (req, res) {
let html = fs.readFileSync(path.resolve(`${global.root_path}/index.html`), { encoding: "utf8" });
html = html.replace("#VERSION#", global.version);

res.send(html);
});
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);
}
return {
app,
io
};
};

this.close = function () {
for (const socket of serverSockets.values()) {
Expand Down