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