UNPKG

16.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const _ = require("lodash");
4const EventEmitter = require("eventemitter3");
5const express = require("express");
6const fs = require("fs");
7const http = require("http");
8const https = require("https");
9const jaul = require("jaul");
10const logger = require("anyhow");
11const path = require("path");
12const setmeup = require("setmeup");
13let settings;
14class App {
15 constructor() {
16 this.events = new EventEmitter();
17 this.on = (eventName, callback) => {
18 this.events.on(eventName, callback);
19 };
20 this.once = (eventName, callback) => {
21 this.events.on(eventName, callback);
22 };
23 this.off = (eventName, callback) => {
24 this.events.off(eventName, callback);
25 };
26 this.init = (middlewares) => {
27 let mw;
28 if (settings.general.debug && logger.levels.indexOf("debug") < 0) {
29 logger.levels.push("debug");
30 }
31 if (!logger.preprocessor) {
32 logger.preprocessor = require("./logger").clean;
33 }
34 logger.errorStack = settings.logger.errorStack;
35 this.expressApp = express();
36 middlewares = middlewares || { append: [], prepend: [] };
37 if (middlewares.prepend && !_.isArray(middlewares.prepend)) {
38 middlewares.prepend = [middlewares.prepend];
39 }
40 if (middlewares.append && !_.isArray(middlewares.append)) {
41 middlewares.append = [middlewares.append];
42 }
43 if (middlewares.prepend && middlewares.prepend.length > 0) {
44 for (mw of middlewares.prepend) {
45 if (mw) {
46 this.expressApp.use(mw);
47 logger.debug("App.init", "Prepended middleware");
48 }
49 }
50 }
51 this.expressApp.set("trust proxy", settings.app.trustProxy);
52 this.expressApp.set("views", settings.app.viewPath);
53 if (settings.app.viewEngine) {
54 this.expressApp.set("view engine", settings.app.viewEngine);
55 this.expressApp.set("view options", settings.app.viewOptions);
56 }
57 if (settings.app.bodyParser && settings.app.bodyParser.enabled) {
58 try {
59 const midBodyParser = require("body-parser");
60 this.expressApp.use(midBodyParser.json({ extended: settings.app.bodyParser.extended, limit: settings.app.bodyParser.limit }));
61 this.expressApp.use(midBodyParser.urlencoded({ extended: settings.app.bodyParser.extended, limit: settings.app.bodyParser.limit }));
62 }
63 catch (ex) {
64 logger.warn("App.init", "Can't load 'body-parser' module.");
65 }
66 }
67 if (settings.app.cookie && settings.app.cookie.enabled) {
68 try {
69 const midCookieParser = require("cookie-parser");
70 this.expressApp.use(midCookieParser(settings.app.secret));
71 }
72 catch (ex) {
73 ex.friendlyMessage = "Can't load 'cookie-parser' module.";
74 logger.error("App.init", ex);
75 }
76 }
77 if (settings.app.session && settings.app.session.enabled) {
78 try {
79 const midSession = require("express-session");
80 const memoryStore = require("memorystore")(midSession);
81 this.expressApp.use(midSession({
82 store: new memoryStore({ checkPeriod: settings.app.session.checkPeriod }),
83 proxy: settings.app.session.proxy,
84 resave: settings.app.session.resave,
85 saveUninitialized: settings.app.session.saveUninitialized,
86 secret: settings.app.secret,
87 ttl: settings.app.session.maxAge * 1000,
88 cookie: {
89 secure: settings.app.session.secure,
90 httpOnly: settings.app.session.httpOnly,
91 maxAge: settings.app.session.maxAge * 1000
92 }
93 }));
94 }
95 catch (ex) {
96 ex.friendlyMessage = "Can't load 'express-session' and 'memorystore' modules.";
97 logger.error("App.init", ex);
98 }
99 }
100 if (settings.app.compression && settings.app.compression.enabled) {
101 try {
102 const midCompression = require("compression");
103 this.expressApp.use(midCompression());
104 }
105 catch (ex) {
106 ex.friendlyMessage = "Can't load 'compression' module.";
107 logger.error("App.init", ex);
108 }
109 }
110 if (settings.app.publicPath) {
111 this.expressApp.use(express.static(settings.app.publicPath));
112 }
113 if (middlewares.append && middlewares.append.length > 0) {
114 for (mw of middlewares.append) {
115 if (mw) {
116 this.expressApp.use(mw);
117 logger.debug("App.init", "Appended middleware");
118 }
119 }
120 }
121 if (settings.general.debug) {
122 this.expressApp.use(function (req, res, next) {
123 const { method } = req;
124 const { url } = req;
125 const ip = jaul.network.getClientIP(req);
126 const msg = `Request from ${ip}`;
127 if (res) {
128 logger.debug("App", msg, method, url);
129 }
130 if (next) {
131 next();
132 }
133 return url;
134 });
135 }
136 this.events.emit("init");
137 this.start();
138 };
139 this.start = () => {
140 if (this.server) {
141 logger.warn("App.start", "Server is already running, abort start.");
142 return this.server;
143 }
144 let serverRef;
145 if (settings.app.ssl && settings.app.ssl.enabled && settings.app.ssl.keyFile && settings.app.ssl.certFile) {
146 const sslKeyFile = jaul.io.getFilePath(settings.app.ssl.keyFile);
147 const sslCertFile = jaul.io.getFilePath(settings.app.ssl.certFile);
148 if (sslKeyFile && sslCertFile) {
149 const sslKey = fs.readFileSync(sslKeyFile, { encoding: settings.general.encoding });
150 const sslCert = fs.readFileSync(sslCertFile, { encoding: settings.general.encoding });
151 const sslOptions = { key: sslKey, cert: sslCert };
152 serverRef = https.createServer(sslOptions, this.expressApp);
153 }
154 else {
155 throw new Error("Invalid certificate filename, please check paths defined on settings.app.ssl.");
156 }
157 }
158 else {
159 serverRef = http.createServer(this.expressApp);
160 }
161 this.server = serverRef;
162 let listenCb = () => {
163 if (settings.app.ip) {
164 logger.info("App.start", settings.app.title, `Listening on ${settings.app.ip} port ${settings.app.port}`, `URL ${settings.app.url}`);
165 }
166 else {
167 logger.info("App.start", settings.app.title, `Listening on port ${settings.app.port}`, `URL ${settings.app.url}`);
168 }
169 };
170 let listenError = (err) => {
171 logger.error("App.start", "Can't start", err);
172 };
173 if (settings.app.ip) {
174 serverRef.listen(settings.app.port, settings.app.ip, listenCb).on("error", listenError);
175 }
176 else {
177 serverRef.listen(settings.app.port, listenCb).on("error", listenError);
178 }
179 serverRef.timeout = settings.app.timeout;
180 this.events.emit("start");
181 return this.server;
182 };
183 this.kill = () => {
184 if (!this.server) {
185 logger.warn("App.kill", "Server was not running");
186 return;
187 }
188 try {
189 this.server.close();
190 this.server = null;
191 this.events.emit("kill");
192 }
193 catch (ex) {
194 logger.error("App.kill", ex);
195 }
196 };
197 this.all = (...args) => {
198 logger.debug("App.all", args[1], args[2]);
199 return this.expressApp.all.apply(this.expressApp, args);
200 };
201 this.get = (...args) => {
202 logger.debug("App.get", args[1], args[2]);
203 return this.expressApp.get.apply(this.expressApp, args);
204 };
205 this.post = (...args) => {
206 logger.debug("App.post", args[1], args[2]);
207 return this.expressApp.post.apply(this.expressApp, args);
208 };
209 this.put = (...args) => {
210 logger.debug("App.put", args[1], args[2]);
211 return this.expressApp.put.apply(this.expressApp, args);
212 };
213 this.patch = (...args) => {
214 logger.debug("App.patch", args[1], args[2]);
215 return this.expressApp.patch.apply(this.expressApp, args);
216 };
217 this.delete = (...args) => {
218 logger.debug("App.delete", args[1], args[2]);
219 return this.expressApp.delete.apply(this.expressApp, args);
220 };
221 this.use = (...args) => {
222 logger.debug("App.use", args[1], args[2]);
223 return this.expressApp.use.apply(this.expressApp, args);
224 };
225 this.set = (...args) => {
226 logger.debug("App.set", args[1], args[2]);
227 return this.expressApp.set.apply(this.expressApp, args);
228 };
229 this.route = (reqPath) => {
230 logger.debug("App.route", reqPath);
231 return this.expressApp.route.apply(this.expressApp, reqPath);
232 };
233 this.renderView = (req, res, view, options, status) => {
234 logger.debug("App.renderView", req.originalUrl, view, options);
235 try {
236 if (!options) {
237 options = {};
238 }
239 if (options.title == null) {
240 options.title = settings.app.title;
241 }
242 if (status) {
243 res.status(status);
244 }
245 res.render(view, options);
246 }
247 catch (ex) {
248 logger.error("App.renderView", view, ex);
249 this.renderError(req, res, ex);
250 }
251 if (settings.app.events.render) {
252 this.events.emit("renderView", req, res, view, options, status);
253 }
254 };
255 this.renderText = (req, res, text, status) => {
256 logger.debug("App.renderText", req.originalUrl, text);
257 try {
258 if (text == null) {
259 logger.debug("App.renderText", "Called with empty text parameter");
260 text = "";
261 }
262 else if (!_.isString(text)) {
263 text = text.toString();
264 }
265 if (status) {
266 res.status(status);
267 }
268 res.setHeader("content-type", "text/plain");
269 res.send(text);
270 }
271 catch (ex) {
272 logger.error("App.renderText", text, ex);
273 this.renderError(req, res, ex);
274 }
275 if (settings.app.events.render) {
276 this.events.emit("renderText", req, res, text, status);
277 }
278 };
279 this.renderJson = (req, res, data, status) => {
280 logger.debug("App.renderJson", req.originalUrl, data);
281 if (_.isString(data)) {
282 try {
283 data = JSON.parse(data);
284 }
285 catch (ex) {
286 logger.error("App.renderJson", ex);
287 return this.renderError(req, res, ex, 500);
288 }
289 }
290 var cleanJson = function (obj, depth) {
291 if (depth >= settings.logger.maxDepth) {
292 return;
293 }
294 if (_.isArray(obj)) {
295 return Array.from(obj).map((i) => cleanJson(i, depth + 1));
296 }
297 else if (_.isObject(obj)) {
298 return (() => {
299 const result = [];
300 for (let k in obj) {
301 const v = obj[k];
302 if (_.isFunction(v)) {
303 result.push(delete obj[k]);
304 }
305 else {
306 result.push(cleanJson(v, depth + 1));
307 }
308 }
309 return result;
310 })();
311 }
312 };
313 cleanJson(data, 0);
314 if (status) {
315 res.status(status);
316 }
317 if (settings.app.allowOriginHeader) {
318 res.setHeader("Access-Control-Allow-Origin", settings.app.allowOriginHeader);
319 }
320 res.json(data);
321 if (settings.app.events.render) {
322 this.events.emit("renderJson", req, res, data, status);
323 }
324 };
325 this.renderImage = (req, res, filename, options) => {
326 logger.debug("App.renderImage", req.originalUrl, filename, options);
327 let mimetype = options ? options.mimetype : null;
328 if (!mimetype) {
329 let extname = path.extname(filename).toLowerCase().replace(".", "");
330 if (extname == "jpg") {
331 extname = "jpeg";
332 }
333 mimetype = `image/${extname}`;
334 }
335 res.type(mimetype);
336 res.sendFile(filename);
337 if (settings.app.events.render) {
338 this.events.emit("renderImage", req, res, filename, options);
339 }
340 };
341 this.renderError = (req, res, error, status) => {
342 let message;
343 logger.debug("App.renderError", req.originalUrl, status, error);
344 if (typeof error == "undefined" || error == null) {
345 error = "Unknown error";
346 logger.warn("App.renderError", "Called with null error");
347 }
348 if (status == null) {
349 status = error.statusCode || error.status || error.code;
350 }
351 if (status == "ETIMEDOUT") {
352 status = 408;
353 }
354 if (!_.isNumber(status)) {
355 status = 500;
356 }
357 try {
358 if (error.error && !error.message && !error.error_description && !error.reason) {
359 error = error.error;
360 }
361 if (_.isString(error)) {
362 message = { message: error };
363 }
364 else {
365 message = {};
366 message.message = error.message || error.error_description || error.description;
367 if (!message.message) {
368 message.message = error.toString();
369 }
370 if (error.friendlyMessage) {
371 message.friendlyMessage = error.friendlyMessage;
372 }
373 if (error.reason) {
374 message.reason = error.reason;
375 }
376 if (error.code) {
377 message.code = error.code;
378 }
379 else if (error.status) {
380 message.code = error.status;
381 }
382 }
383 }
384 catch (ex) {
385 logger.error("App.renderError", ex);
386 }
387 res.status(status).json(message);
388 if (settings.app.events.render) {
389 this.events.emit("renderError", req, res, error, status);
390 }
391 };
392 if (!logger.isReady) {
393 logger.setup();
394 }
395 setmeup.load(__dirname + "/../settings.default.json", { overwrite: false });
396 settings = setmeup.settings;
397 }
398 static get Instance() {
399 return this._instance || (this._instance = new this());
400 }
401 newInstance() {
402 return new App();
403 }
404}
405exports.App = App;
406exports.default = App.Instance;