1 |
|
2 |
|
3 | const async = require('async')
|
4 |
|
5 | class Middleware {
|
6 | constructor (robot) {
|
7 | this.robot = robot
|
8 | this.stack = []
|
9 | }
|
10 |
|
11 | // Public: Execute all middleware in order and call 'next' with the latest
|
12 | // 'done' callback if last middleware calls through. If all middleware is
|
13 | // compliant, 'done' should be called with no arguments when the entire
|
14 | // round trip is complete.
|
15 | //
|
16 | // context - context object that is passed through the middleware stack.
|
17 | // When handling errors, this is assumed to have a `response` property.
|
18 | //
|
19 | // next(context, done) - Called when all middleware is complete (assuming
|
20 | // all continued by calling respective 'next' functions)
|
21 | //
|
22 | // done() - Initial (final) completion callback. May be wrapped by
|
23 | // executed middleware.
|
24 | //
|
25 | // Returns nothing
|
26 | // Returns before executing any middleware
|
27 | execute (context, next, done) {
|
28 | const self = this
|
29 |
|
30 | if (done == null) {
|
31 | done = function () {}
|
32 | }
|
33 |
|
34 | // Execute a single piece of middleware and update the completion callback
|
35 | // (each piece of middleware can wrap the 'done' callback with additional
|
36 | // logic).
|
37 | function executeSingleMiddleware (doneFunc, middlewareFunc, cb) {
|
38 | // Match the async.reduce interface
|
39 | function nextFunc (newDoneFunc) {
|
40 | cb(null, newDoneFunc || doneFunc)
|
41 | }
|
42 |
|
43 | // Catch errors in synchronous middleware
|
44 | try {
|
45 | middlewareFunc(context, nextFunc, doneFunc)
|
46 | } catch (err) {
|
47 | // Maintaining the existing error interface (Response object)
|
48 | self.robot.emit('error', err, context.response)
|
49 | // Forcibly fail the middleware and stop executing deeper
|
50 | doneFunc()
|
51 | }
|
52 | }
|
53 |
|
54 | // Executed when the middleware stack is finished
|
55 | function allDone (_, finalDoneFunc) {
|
56 | next(context, finalDoneFunc)
|
57 | }
|
58 |
|
59 | // Execute each piece of middleware, collecting the latest 'done' callback
|
60 | // at each step.
|
61 | process.nextTick(async.reduce.bind(null, this.stack, done, executeSingleMiddleware, allDone))
|
62 | }
|
63 |
|
64 | // Public: Registers new middleware
|
65 | //
|
66 | // middleware - A generic pipeline component function that can either
|
67 | // continue the pipeline or interrupt it. The function is called
|
68 | // with (robot, context, next, done). If execution should
|
69 | // continue (next middleware, final callback), the middleware
|
70 | // should call the 'next' function with 'done' as an optional
|
71 | // argument.
|
72 | // If not, the middleware should call the 'done' function with
|
73 | // no arguments. Middleware may wrap the 'done' function in
|
74 | // order to execute logic after the final callback has been
|
75 | // executed.
|
76 | //
|
77 | // Returns nothing.
|
78 | register (middleware) {
|
79 | if (middleware.length !== 3) {
|
80 | throw new Error(`Incorrect number of arguments for middleware callback (expected 3, got ${middleware.length})`)
|
81 | }
|
82 | this.stack.push(middleware)
|
83 | }
|
84 | }
|
85 |
|
86 | module.exports = Middleware
|