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