UNPKG

7.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* This module does a lot of monkeypatching, but unfortunately that appears to be the only way to
8 * accomplish this kind of stuff in Express.
9 *
10 * Here be dragons. */
11
12exports.expressWs = expressWs;
13
14var _http = require('http');
15
16var _http2 = _interopRequireDefault(_http);
17
18var _express = require('express');
19
20var _express2 = _interopRequireDefault(_express);
21
22var _ws = require('ws');
23
24var _ws2 = _interopRequireDefault(_ws);
25
26var _trailingSlash = require('./trailing-slash');
27
28var _trailingSlash2 = _interopRequireDefault(_trailingSlash);
29
30function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
31
32/* The following fixes HenningM/express-ws#17, correctly. */
33function websocketUrl(url) {
34 if (url.indexOf('?') !== -1) {
35 var _url$split = url.split('?');
36
37 var _url$split2 = _slicedToArray(_url$split, 2);
38
39 var baseUrl = _url$split2[0];
40 var query = _url$split2[1];
41
42
43 return (0, _trailingSlash2.default)(baseUrl) + '.websocket?' + query;
44 }
45 return (0, _trailingSlash2.default)(url) + '.websocket';
46}
47
48function expressWs(app, httpServer) {
49 var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
50
51 var server = httpServer;
52
53 if (!server) {
54 /* No HTTP server was explicitly provided, create one for our Express application. */
55 server = _http2.default.createServer(app);
56
57 app.listen = function serverListen() {
58 server.listen.apply(server, arguments);
59 };
60 }
61
62 function wrapMiddleware(middleware) {
63 return function (req, res, next) {
64 if (req.ws !== null) {
65 req.wsHandled = true;
66 /* Unpack the `.ws` property and call the actual handler. */
67 middleware(req.ws, req, next);
68 } else {
69 /* This wasn't a WebSocket request, so skip this middleware. */
70 next();
71 }
72 };
73 }
74
75 function addWsMethod(target) {
76 if (!target.ws) {
77 /* This prevents conflict with other things setting `.ws`. */
78 target.ws = function addWsRoute(route) {
79 var middlewares = Array.prototype.slice.call(arguments, 1); // deopt!
80 var wrappedMiddlewares = middlewares.map(wrapMiddleware);
81
82 /* We append `/.websocket` to the route path here. Why? To prevent conflicts when
83 * a non-WebSocket request is made to the same GET route - after all, we are only
84 * interested in handling WebSocket requests.
85 *
86 * Whereas the original `express-ws` prefixed this path segment, we suffix it -
87 * this makes it possible to let requests propagate through Routers like normal,
88 * which allows us to specify WebSocket routes on Routers as well \o/! */
89 var wsRoute = websocketUrl(route);
90
91 /* Here we configure our new GET route. It will never get called by a client
92 * directly, it's just to let our request propagate internally, so that we can
93 * leave the regular middleware execution and error handling to Express. */
94 target.get.apply(target, [wsRoute].concat(wrappedMiddlewares));
95 };
96 }
97 }
98
99 /* Make our custom `.ws` method available directly on the Express application. You should
100 * really be using Routers, though. */
101 addWsMethod(app);
102
103 /* Monkeypatch our custom `.ws` method into Express' Router prototype. This makes it possible,
104 * when using the standard Express Router, to use the `.ws` method without any further calls
105 * to `makeRouter`. When using a custom router, the use of `makeRouter` may still be necessary.
106 *
107 * This approach works, because Express does a strange mixin hack - the Router factory
108 * function is simultaneously the prototype that gets assigned to the resulting Router
109 * object. */
110 if (!options.leaveRouterUntouched) {
111 addWsMethod(_express2.default.Router);
112 }
113
114 var wsServer = new _ws2.default.Server({ server: server });
115
116 wsServer.on('connection', function (socket) {
117 var request = socket.upgradeReq;
118
119 request.ws = socket;
120 request.wsHandled = false;
121
122 /* By setting this fake `.url` on the request, we ensure that it will end up in the fake
123 * `.get` handler that we defined above - where the wrapper will then unpack the `.ws`
124 * property, indicate that the WebSocket has been handled, and call the actual handler. */
125 request.url = websocketUrl(request.url);
126
127 var dummyResponse = new _http2.default.ServerResponse(request);
128
129 dummyResponse.writeHead = function (statusCode) {
130 if (statusCode > 200) {
131 /* Something in the middleware chain signalled an error. */
132 socket.close();
133 }
134 };
135
136 app.handle(request, dummyResponse, function () {
137 if (!request.wsHandled) {
138 /* There was no matching WebSocket-specific route for this request. We'll close
139 * the connection, as no endpoint was able to handle the request anyway... */
140 socket.close();
141 }
142 });
143 });
144
145 return {
146 app: app,
147 getWss: function getWss() {
148 return wsServer;
149 },
150 applyTo: function applyTo(router) {
151 return addWsMethod(router);
152 }
153 };
154};
\No newline at end of file