• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

igoramadas / expresser / 10689632465

03 Sep 2024 07:49PM UTC coverage: 96.383% (+1.1%) from 95.292%
10689632465

push

github

igoramadas
Version 4.8.2 with possibility to set a path and handler for added middlewares.

161 of 170 branches covered (94.71%)

Branch coverage included in aggregate %.

9 of 9 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

372 of 383 relevant lines covered (97.13%)

22.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.33
/src/app.ts
1
// Expresser: app.ts
2

3
import {isArray, isFunction, isObject, isString} from "./utils"
4✔
4
import EventEmitter from "eventemitter3"
4✔
5
import express = require("express")
4✔
6
import fs = require("fs")
4✔
7
import http = require("http")
4✔
8
import https = require("https")
4✔
9
import http2 = require("http2")
4✔
10
import http2Express = require("http2-express-bridge")
4✔
11
import jaul = require("jaul")
4✔
12
import logger = require("anyhow")
4✔
13
import path = require("path")
4✔
14
import setmeup = require("setmeup")
4✔
15
let settings
16

17
/** Middleware definitions to be be passed on app [[init]]. */
18
export interface MiddlewareDefs {
19
    /** Single or array of middlewares to be prepended. */
20
    prepend: any | any[]
21
    /** Single or array of middlewares to be appended. */
22
    append: any | any[]
23
}
24

25
/** Main App class. */
26
export class App {
4✔
27
    private static _instance: App
28
    /** @hidden */
29
    static get Instance() {
30
        return this._instance || (this._instance = new this())
8✔
31
    }
32

33
    /** Returns a new fresh instance of the App module. */
34
    newInstance(): App {
35
        return new App()
24✔
36
    }
37

38
    /** Default App constructor. */
39
    constructor() {
40
        if (!logger.isReady) {
28!
41
            /* istanbul ignore next */
42
            logger.setup()
43
        }
44

45
        // Load default settings.
46
        setmeup.load(__dirname + "/../settings.default.json", {overwrite: false})
28✔
47

48
        settings = setmeup.settings
28✔
49
    }
50

51
    // PROPERTIES
52
    // --------------------------------------------------------------------------
53

54
    /** Express application. */
55
    expressApp: express.Application
56

57
    /** The underlying HTTP(S) server. */
58
    server: any
59

60
    /** Event emitter. */
61
    events: EventEmitter = new EventEmitter()
28✔
62

63
    // EVENTS
64
    // --------------------------------------------------------------------------
65

66
    /**
67
     * Bind callback to event. Shortcut to `events.on()`.
68
     * @param eventName The name of the event.
69
     * @param callback Callback function.
70
     */
71
    on = (eventName: string, callback: EventEmitter.ListenerFn): void => {
28✔
72
        this.events.on(eventName, callback)
4✔
73
    }
74

75
    /**
76
     * Bind callback to event that will be triggered only once. Shortcut to `events.once()`.
77
     * @param eventName The name of the event.
78
     * @param callback Callback function.
79
     */
80
    once = (eventName: string, callback: EventEmitter.ListenerFn): void => {
28✔
81
        this.events.on(eventName, callback)
8✔
82
    }
83

84
    /**
85
     * Unbind callback from event. Shortcut to `events.off()`.
86
     * @param eventName The name of the event.
87
     * @param callback Callback function.
88
     */
89
    off = (eventName: string, callback: EventEmitter.ListenerFn): void => {
28✔
90
        this.events.off(eventName, callback)
4✔
91
    }
92

93
    // MAIN METHODS
94
    // --------------------------------------------------------------------------
95

96
    /**
97
     * Init the app module and start the HTTP(S) server.
98
     * @param middlewares List of middlewares to be appended / prepended.
99
     * @event init
100
     */
101
    init = (middlewares?: MiddlewareDefs): void => {
28✔
102
        let mw
103

104
        // Debug enabled?
105
        if (settings.general.debug && logger.options.levels.indexOf("debug") < 0) {
28✔
106
            logger.setOptions({
4✔
107
                levels: logger.options.levels.concat(["debug"])
108
            })
109
        }
110

111
        logger.setOptions({
28✔
112
            preprocessors: ["cleanup", "friendlyErrors", "maskSecrets"],
113
            preprocessorOptions: {
114
                errorStack: settings.logger.errorStack
115
            }
116
        })
117

118
        // Create express v4 app.
119
        if (settings.app.http2) {
28✔
120
            this.expressApp = express()
24✔
121
        } else {
122
            this.expressApp = http2Express(express)
4✔
123
        }
124

125
        // Helper to add a middleware.
126
        const addMiddleware = (mw) => {
28✔
127
            if (!mw) return
12!
128
            if (mw.path && mw.handler) {
12✔
129
                this.expressApp.use(mw.path, mw.handler)
4✔
130
            } else {
131
                this.expressApp.use(mw)
8✔
132
            }
133
        }
134
        middlewares = middlewares || {append: [], prepend: []}
28✔
135

136
        // Make sure passed middlewares are array based.
137
        if (middlewares.prepend && !isArray(middlewares.prepend)) {
28✔
138
            middlewares.prepend = [middlewares.prepend]
4✔
139
        }
140
        if (middlewares.append && !isArray(middlewares.append)) {
28!
UNCOV
141
            middlewares.append = [middlewares.append]
×
142
        }
143

144
        // Prepend middlewares?
145
        if (middlewares?.prepend?.length > 0) {
28✔
146
            for (mw of middlewares.prepend) {
4✔
147
                addMiddleware(mw)
4✔
148
            }
149
        }
150

151
        // Trust proxy (mainly for secure cookies)?
152
        this.expressApp.set("trust proxy", settings.app.trustProxy)
28✔
153

154
        // Default view path.
155
        this.expressApp.set("views", settings.app.viewPath)
28✔
156

157
        // Set view options, use Pug for HTML templates.
158
        if (settings.app.viewEngine) {
28✔
159
            this.expressApp.set("view engine", settings.app.viewEngine)
4✔
160
            this.expressApp.set("view options", settings.app.viewOptions)
4✔
161
        }
162

163
        // Use body parser?
164
        if (settings.app.bodyParser?.enabled) {
28✔
165
            try {
8✔
166
                const midBodyParser = require("body-parser")
8✔
167
                if (settings.app.bodyParser.rawTypes) {
8✔
168
                    this.expressApp.use(midBodyParser.raw({limit: settings.app.bodyParser.rawLimit, type: settings.app.bodyParser.rawTypes}))
8✔
169
                }
170
                this.expressApp.use(midBodyParser.json({limit: settings.app.bodyParser.limit}))
8✔
171
                this.expressApp.use(midBodyParser.text({limit: settings.app.bodyParser.limit}))
8✔
172
                this.expressApp.use(midBodyParser.urlencoded({limit: settings.app.bodyParser.limit, extended: settings.app.bodyParser.extended}))
8✔
173
            } catch (ex) {
174
                /* istanbul ignore next */
175
                logger.warn("App.init", "Can't load 'body-parser' module")
176
            }
177

178
            try {
8✔
179
                const bodyParserErrorHandler = require("express-body-parser-error-handler")
8✔
180
                this.expressApp.use(bodyParserErrorHandler())
8✔
181
            } catch (ex) {
182
                /* istanbul ignore next */
183
                logger.debug("App.init", "Module 'express-body-parser-error-handler' not installed, body-parser might throw ugly exceptions")
184
            }
185
        }
186

187
        // Cookies enabled? Depends on `cookie-parser` being installed.
188
        if (settings.app.cookie?.enabled) {
28✔
189
            try {
4✔
190
                const midCookieParser = require("cookie-parser")
4✔
191
                this.expressApp.use(midCookieParser(settings.app.secret))
4✔
192
            } catch (ex) {
193
                /* istanbul ignore next */
194
                ex.friendlyMessage = "Can't load 'cookie-parser' module"
195
                /* istanbul ignore next */
196
                logger.error("App.init", ex)
197
            }
198
        }
199

200
        // Session enabled?
201
        if (settings.app.session?.enabled) {
28✔
202
            try {
4✔
203
                const midSession = require("express-session")
4✔
204
                const memoryStore = require("memorystore")(midSession)
4✔
205

206
                this.expressApp.use(
4✔
207
                    midSession({
208
                        store: new memoryStore({checkPeriod: settings.app.session.checkPeriod}),
209
                        proxy: settings.app.session.proxy,
210
                        resave: settings.app.session.resave,
211
                        saveUninitialized: settings.app.session.saveUninitialized,
212
                        secret: settings.app.secret,
213
                        ttl: settings.app.session.maxAge * 1000,
214
                        cookie: {
215
                            secure: settings.app.session.secure,
216
                            httpOnly: settings.app.session.httpOnly,
217
                            maxAge: settings.app.session.maxAge * 1000
218
                        }
219
                    })
220
                )
221
            } catch (ex) {
222
                /* istanbul ignore next */
223
                ex.friendlyMessage = "Can't load 'express-session' and 'memorystore' modules"
224
                /* istanbul ignore next */
225
                logger.error("App.init", ex)
226
            }
227
        }
228

229
        // Use HTTP compression only if enabled on settings.
230
        if (settings.app.compression && settings.app.compression.enabled) {
28✔
231
            try {
4✔
232
                const midCompression = require("compression")
4✔
233
                this.expressApp.use(midCompression())
4✔
234
            } catch (ex) {
235
                /* istanbul ignore next */
236
                ex.friendlyMessage = "Can't load 'compression' module"
237
                /* istanbul ignore next */
238
                logger.error("App.init", ex)
239
            }
240
        }
241

242
        // Use Express static routing.
243
        if (settings.app.publicPath) {
28✔
244
            this.expressApp.use(express.static(settings.app.publicPath))
28✔
245
        }
246

247
        // Append middlewares?
248
        if (middlewares?.append?.length > 0) {
28✔
249
            for (mw of middlewares.append) {
4✔
250
                addMiddleware(mw)
8✔
251
            }
252
        }
253

254
        // Log all requests if debug is true.
255
        if (settings.general.debug) {
28✔
256
            this.expressApp.use(function (req, res, next) {
24✔
257
                const {method, url} = req
112✔
258
                const ip = jaul.network.getClientIP(req)
112✔
259
                const msg = `Request from ${ip}`
112✔
260

261
                if (res) {
112✔
262
                    logger.debug("App", msg, method, url)
112✔
263
                }
264

265
                if (next) {
112✔
266
                    next()
112✔
267
                }
268

269
                return url
112✔
270
            })
271
        }
272

273
        // Error handler enabled?
274
        if (settings.logger.errorHandler) {
28✔
275
            this.expressApp.use((err, req, res, next) => {
12✔
276
                logger.error("App", req.method, req.url, res.headersSent ? "Headers sent" : "Headers not sent", err)
×
277

278
                if (err instanceof URIError) {
×
279
                    res.end()
×
280
                } else {
281
                    next(err)
×
282
                }
283
            })
284
        }
285

286
        // Disable the X-Powered-By header.
287
        this.expressApp.disable("x-powered-by")
28✔
288

289
        // Dispatch init event, and start the server.
290
        this.events.emit("init")
28✔
291
        this.start()
28✔
292
    }
293

294
    /**
295
     * Start the HTTP(S) server.
296
     * @returns The HTTP(S) server created by Express.
297
     * @event start
298
     */
299
    start = (): http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer => {
28✔
300
        if (this.server) {
40✔
301
            logger.warn("App.start", "Server is already running, abort start")
4✔
302
            return this.server
4✔
303
        }
304

305
        let serverRef: http.Server | https.Server | http2.Http2Server | http2.Http2SecureServer
306

307
        if (settings.app.ssl?.enabled && settings.app.ssl?.keyFile && settings.app.ssl?.certFile) {
36✔
308
            const sslKeyFile = jaul.io.getFilePath(settings.app.ssl.keyFile)
12✔
309
            const sslCertFile = jaul.io.getFilePath(settings.app.ssl.certFile)
12✔
310

311
            // Certificate files were found? Proceed, otherwise alert the user and throw an error.
312
            if (sslKeyFile && sslCertFile) {
12✔
313
                const sslKey = fs.readFileSync(sslKeyFile, {encoding: settings.general.encoding})
8✔
314
                const sslCert = fs.readFileSync(sslCertFile, {encoding: settings.general.encoding})
8✔
315
                const sslOptions: any = {key: sslKey, cert: sslCert}
8✔
316

317
                if (settings.app.http2) {
8!
318
                    sslOptions.allowHTTP1 = true
8✔
319
                    serverRef = http2.createSecureServer(sslOptions, this.expressApp as any)
8✔
320
                } else {
321
                    serverRef = https.createServer(sslOptions, this.expressApp)
×
322
                }
323
            } else {
324
                throw new Error("Invalid certificate filename, please check paths defined on settings.app.ssl")
4✔
325
            }
326
        } else {
327
            serverRef = settings.app.http2 ? http2.createServer(this.expressApp) : http.createServer(this.expressApp)
24✔
328
        }
329

330
        // Expose the web server.
331
        this.server = serverRef
32✔
332

333
        let listenCb = () => {
32✔
334
            if (settings.app.ip) {
32✔
335
                logger.info("App.start", settings.app.title, `Listening on ${settings.app.ip} port ${settings.app.port}`, `URL ${settings.app.url}`)
24✔
336
            } else {
337
                logger.info("App.start", settings.app.title, `Listening on port ${settings.app.port}`, `URL ${settings.app.url}`)
8✔
338
            }
339
        }
340

341
        /* istanbul ignore next */
342
        let listenError = (err) => {
343
            /* istanbul ignore next */
344
            logger.error("App.start", "Can't start", err)
345
        }
346

347
        // Start the app!
348
        if (settings.app.ip) {
32✔
349
            serverRef.listen(settings.app.port, settings.app.ip, listenCb).on("error", listenError)
24✔
350
        } else {
351
            serverRef.listen(settings.app.port, listenCb).on("error", listenError)
8✔
352
        }
353

354
        // Set default timeout.
355
        serverRef.setTimeout(settings.app.timeout)
32✔
356

357
        // Emit start event and return HTTP(S) server.
358
        this.events.emit("start")
32✔
359
        return this.server
32✔
360
    }
361

362
    /**
363
     * Kill the underlying HTTP(S) server(s).
364
     * @event kill
365
     */
366
    kill = (): void => {
28✔
367
        if (!this.server) {
40✔
368
            logger.warn("App.kill", "Server was not running")
8✔
369
            return
8✔
370
        }
371

372
        try {
32✔
373
            this.server.close()
32✔
374
            this.server = null
32✔
375
            this.events.emit("kill")
32✔
376
        } catch (ex) {
377
            /* istanbul ignore next */
378
            logger.error("App.kill", ex)
379
        }
380
    }
381

382
    // BRIDGED METHODS
383
    // --------------------------------------------------------------------------
384

385
    /**
386
     * Shortcut to express ".all()".
387
     * @param args Arguments passed to Express.
388
     */
389
    all = (...args: any[]) => {
28✔
390
        logger.debug("App.all", args[1], args[2])
×
391
        return this.expressApp.all.apply(this.expressApp, args)
×
392
    }
393

394
    /**
395
     * Shortcut to express ".get()".
396
     * @param args Arguments passed to Express.
397
     */
398
    get = (...args: any[]) => {
28✔
399
        logger.debug("App.get", args[1], args[2])
144✔
400
        return this.expressApp.get.apply(this.expressApp, args)
144✔
401
    }
402

403
    /**
404
     * Shortcut to express ".post()".
405
     * @param args Arguments passed to Express.
406
     */
407
    post = (...args: any[]) => {
28✔
408
        logger.debug("App.post", args[1], args[2])
20✔
409
        return this.expressApp.post.apply(this.expressApp, args)
20✔
410
    }
411

412
    /**
413
     * Shortcut to express ".put()".
414
     * @param args Arguments passed to Express.
415
     */
416
    put = (...args: any[]) => {
28✔
417
        logger.debug("App.put", args[1], args[2])
4✔
418
        return this.expressApp.put.apply(this.expressApp, args)
4✔
419
    }
420

421
    /**
422
     * Shortcut to express ".patch()".
423
     * @param args Arguments passed to Express.
424
     */
425
    patch = (...args: any[]) => {
28✔
426
        logger.debug("App.patch", args[1], args[2])
4✔
427
        return this.expressApp.patch.apply(this.expressApp, args)
4✔
428
    }
429

430
    /**
431
     * Shortcut to express ".delete()".
432
     * @param args Arguments passed to Express.
433
     */
434
    delete = (...args: any[]) => {
28✔
435
        logger.debug("App.delete", args[1], args[2])
4✔
436
        return this.expressApp.delete.apply(this.expressApp, args)
4✔
437
    }
438

439
    /**
440
     * Shortcut to express ".head()".
441
     * @param args Arguments passed to Express.
442
     */
443
    head = (...args: any[]) => {
28✔
444
        logger.debug("App.head", args[1], args[2])
×
445
        return this.expressApp.head.apply(this.expressApp, args)
×
446
    }
447

448
    /**
449
     * Shortcut to express ".use()".
450
     * @param args Arguments passed to Express.
451
     */
452
    use = (...args: any[]) => {
28✔
453
        logger.debug("App.use", args[1], args[2])
8✔
454
        return this.expressApp.use.apply(this.expressApp, args)
8✔
455
    }
456

457
    /**
458
     * Shortcut to express ".set()".
459
     * @param args Arguments passed to Express.
460
     */
461
    set = (...args: any[]) => {
28✔
462
        logger.debug("App.set", args[1], args[2])
4✔
463
        return this.expressApp.set.apply(this.expressApp, args)
4✔
464
    }
465

466
    /**
467
     * Shortcut to express ".route()".
468
     * @param reqPath The path of the desired route.
469
     * @returns An instance of a single route for the specified path.
470
     */
471
    route = (reqPath: string): express.IRoute => {
28✔
472
        logger.debug("App.route", reqPath)
4✔
473
        return this.expressApp.route.apply(this.expressApp, reqPath)
4✔
474
    }
475

476
    // RENDERING METHODS
477
    // --------------------------------------------------------------------------
478

479
    /**
480
     * Render a view and send to the client. The view engine depends on the settings defined.
481
     * @param req The Express request object.
482
     * @param res The Express response object.
483
     * @param view The view filename.
484
     * @param options Options passed to the view, optional.
485
     * @param status Optional status code, defaults to 200.
486
     * @event renderView
487
     */
488
    renderView = (req: express.Request, res: express.Response, view: string, options?: any, status?: number) => {
28✔
489
        logger.debug("App.renderView", req.originalUrl, view, options)
12✔
490

491
        try {
12✔
492
            if (!options) {
12✔
493
                options = {}
8✔
494
            }
495

496
            if (options.title == null) {
12✔
497
                options.title = settings.app.title
8✔
498
            }
499

500
            // A specific status code was passed?
501
            if (status) {
12✔
502
                res.status(parseInt(status as any))
4✔
503
            }
504

505
            // Send rendered view to client.
506
            res.render(view, options)
12✔
507
        } catch (ex) {
508
            /* istanbul ignore next */
509
            logger.error("App.renderView", view, ex)
510
            /* istanbul ignore next */
511
            this.renderError(req, res, ex)
512
        }
513

514
        /* istanbul ignore if */
515
        if (settings.app.events.render) {
12✔
516
            this.events.emit("renderView", req, res, view, options, status)
517
        }
518
    }
519

520
    /**
521
     * Sends pure text to the client.
522
     * @param req The Express request object.
523
     * @param res The Express response object.
524
     * @param text The text to be rendered, mandatory.
525
     * @param status Optional status code, defaults to 200.
526
     * @event renderText
527
     */
528
    renderText = (req: express.Request, res: express.Response, text: any, status?: number) => {
28✔
529
        logger.debug("App.renderText", req.originalUrl, text)
12✔
530

531
        try {
12✔
532
            if (text == null) {
12✔
533
                logger.debug("App.renderText", "Called with empty text parameter")
4✔
534
                text = ""
4✔
535
            } else if (!isString(text)) {
8✔
536
                text = text.toString()
4✔
537
            }
538

539
            // A specific status code was passed?
540
            if (status) {
12✔
541
                res.status(parseInt(status as any))
4✔
542
            }
543

544
            res.setHeader("content-type", "text/plain")
12✔
545
            res.send(text)
12✔
546
        } catch (ex) {
547
            /* istanbul ignore next */
548
            logger.error("App.renderText", text, ex)
549
            /* istanbul ignore next */
550
            this.renderError(req, res, ex)
551
        }
552

553
        /* istanbul ignore if */
554
        if (settings.app.events.render) {
12✔
555
            this.events.emit("renderText", req, res, text, status)
556
        }
557
    }
558

559
    /**
560
     * Render response as JSON data and send to the client.
561
     * @param req The Express request object.
562
     * @param res The Express response object.
563
     * @param data The JSON data to be sent.
564
     * @param status Optional status code, defaults to 200.
565
     * @event renderJson
566
     */
567
    renderJson = (req: express.Request, res: express.Response, data: any, status?: number) => {
28✔
568
        logger.debug("App.renderJson", req.originalUrl, data)
28✔
569

570
        if (isString(data)) {
28✔
571
            try {
4✔
572
                data = JSON.parse(data)
4✔
573
            } catch (ex) {
574
                logger.error("App.renderJson", ex)
4✔
575
                return this.renderError(req, res, ex, 500)
4✔
576
            }
577
        }
578

579
        // Remove methods from JSON before rendering.
580
        var cleanJson = function (obj, depth) {
24✔
581
            if (depth >= settings.logger.maxDepth) {
128!
582
                return
×
583
            }
584

585
            if (isArray(obj)) {
128✔
586
                return Array.from(obj).map((i) => cleanJson(i, depth + 1))
16✔
587
            } else if (isObject(obj)) {
120✔
588
                return (() => {
64✔
589
                    const result = []
64✔
590
                    for (let k in obj) {
64✔
591
                        const v = obj[k]
92✔
592
                        if (isFunction(v)) {
92✔
593
                            result.push(delete obj[k])
4✔
594
                        } else {
595
                            result.push(cleanJson(v, depth + 1))
88✔
596
                        }
597
                    }
598
                    return result
64✔
599
                })()
600
            }
601
        }
602

603
        cleanJson(data, 0)
24✔
604

605
        // A specific status code was passed?
606
        if (status) {
24✔
607
            res.status(parseInt(status as any))
4✔
608
        }
609

610
        // Add Access-Control-Allow-Origin if set.
611
        if (settings.app.allowOriginHeader) {
24✔
612
            res.setHeader("Access-Control-Allow-Origin", settings.app.allowOriginHeader)
4✔
613
        }
614

615
        // Send JSON response.
616
        res.json(data)
24✔
617

618
        /* istanbul ignore if */
619
        if (settings.app.events.render) {
24✔
620
            this.events.emit("renderJson", req, res, data, status)
621
        }
622
    }
623

624
    /**
625
     * Render an image from the speficied file, and send to the client.
626
     * @param req The Express request object.
627
     * @param res The Express response object.
628
     * @param filename The full path to the image file.
629
     * @param options Options passed to the image renderer, for example the "mimetype".
630
     * @event renderImage
631
     */
632
    renderImage = (req: express.Request, res: express.Response, filename: string, options?: any) => {
28✔
633
        logger.debug("App.renderImage", req.originalUrl, filename, options)
8✔
634

635
        let mimetype = options ? options.mimetype : null
8✔
636

637
        // Try to figure out the mime type in case it wasn't passed along the options.
638
        if (!mimetype) {
8✔
639
            let extname = path.extname(filename).toLowerCase().replace(".", "")
4✔
640

641
            if (extname == "jpg") {
4✔
642
                extname = "jpeg"
4✔
643
            }
644

645
            mimetype = `image/${extname}`
4✔
646
        }
647

648
        // Send image to client.
649
        res.type(mimetype)
8✔
650
        res.sendFile(filename)
8✔
651

652
        /* istanbul ignore if */
653
        if (settings.app.events.render) {
8✔
654
            this.events.emit("renderImage", req, res, filename, options)
655
        }
656
    }
657

658
    /**
659
     * Sends error response as JSON.
660
     * @param req The Express request object.
661
     * @param res The Express response object.
662
     * @param error The error object or message to be sent to the client.
663
     * @param status The response status code, optional, default is 500.
664
     * @event renderError
665
     */
666
    renderError = (req: express.Request, res: express.Response, error: any, status?: number | string) => {
28✔
667
        let message
668
        logger.debug("App.renderError", req.originalUrl, status, error)
28✔
669

670
        /* istanbul ignore next */
671
        if (typeof error == "undefined" || error == null) {
672
            error = "Unknown error"
673
            logger.warn("App.renderError", "Called with null error")
674
        }
675

676
        // Status default statuses.
677
        if (status == null) {
28✔
678
            status = error.statusCode || error.status || error.code
12✔
679
        }
680
        if (status == "ETIMEDOUT") {
28✔
681
            status = 408
4✔
682
        }
683

684
        // Error defaults to 500 if not a valid number.
685
        if (isNaN(status as number)) {
28✔
686
            status = 500
4✔
687
        }
688

689
        try {
28✔
690
            // Error inside another .error property?
691
            if (error.error && !error.message && !error.error_description && !error.reason) {
28✔
692
                error = error.error
4✔
693
            }
694

695
            if (isString(error)) {
28✔
696
                message = {message: error}
8✔
697
            } else {
698
                message = {}
20✔
699
                message.message = error.message || error.error_description || error.description
20✔
700

701
                // No message found? Just use the default .toString() then.
702
                /* istanbul ignore next */
703
                if (!message.message) {
704
                    message.message = error.toString()
705
                }
706

707
                if (error.friendlyMessage) {
20✔
708
                    message.friendlyMessage = error.friendlyMessage
4✔
709
                }
710
                if (error.reason) {
20✔
711
                    message.reason = error.reason
4✔
712
                }
713
                if (error.code) {
20✔
714
                    message.code = error.code
4✔
715
                } else if (error.status) {
16✔
716
                    message.code = error.status
4✔
717
                }
718
            }
719
        } catch (ex) {
720
            /* istanbul ignore next */
721
            logger.error("App.renderError", ex)
722
        }
723

724
        // Send error JSON to client.
725
        res.status(parseInt(status as any)).json(message)
28✔
726

727
        /* istanbul ignore if */
728
        if (settings.app.events.render) {
28✔
729
            this.events.emit("renderError", req, res, error, status)
730
        }
731
    }
732
}
733

734
// Exports...
735
export default App.Instance
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc