1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const _ = require("lodash");
|
4 | const EventEmitter = require("eventemitter3");
|
5 | const express = require("express");
|
6 | const fs = require("fs");
|
7 | const http = require("http");
|
8 | const https = require("https");
|
9 | const jaul = require("jaul");
|
10 | const logger = require("anyhow");
|
11 | const path = require("path");
|
12 | const setmeup = require("setmeup");
|
13 | let settings;
|
14 | class 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 | }
|
405 | exports.App = App;
|
406 | exports.default = App.Instance;
|