Skip to content
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) {
Comment thread
khassel marked this conversation as 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 () {
Comment thread
rejas marked this conversation as 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