├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
└── workflows
│ └── nodejs.yml
├── .gitignore
├── .npmignore
├── .nvmrc
├── .prettierrc
├── AUTHORS
├── CHANGELOG.md
├── LICENSE
├── README.md
├── examples
├── .eslintrc
├── .gitignore
├── echo
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ └── server.js
├── express
│ ├── index.html
│ ├── package.json
│ └── server.js
├── hapi
│ ├── html
│ │ └── index.html
│ ├── package.json
│ └── server.js
├── haproxy.cfg
├── koa
│ ├── index.html
│ ├── package.json
│ └── server.js
└── multiplex
│ ├── README.md
│ ├── index.html
│ ├── package.json
│ └── server.js
├── index.js
├── lib
├── handlers.js
├── iframe.js
├── info.js
├── listener.js
├── middleware.js
├── server.js
├── session.js
├── sockjs-connection.js
├── transport
│ ├── base-receiver.js
│ ├── eventsource.js
│ ├── htmlfile.js
│ ├── jsonp-polling.js
│ ├── list.js
│ ├── response-receiver.js
│ ├── transport.js
│ ├── websocket-raw.js
│ ├── websocket.js
│ ├── xhr-polling.js
│ ├── xhr-streaming.js
│ └── xhr.js
├── utils.js
└── webjs.js
├── package-lock.json
├── package.json
├── scripts
└── test.sh
└── tests
├── .eslintrc
└── test_server
├── README.md
├── config.js
├── server.js
└── sockjs_app.js
/.eslintignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sockjs/sockjs-node/a43ed3bd25c9df16e18c681a678d5bc47b0758a1/.eslintignore
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true
5 | },
6 | "extends": ["eslint:recommended", "plugin:prettier/recommended"]
7 | }
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | tidelift: npm/sockjs
4 |
--------------------------------------------------------------------------------
/.github/workflows/nodejs.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [18.x, 20.x, 22.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - uses: actions/setup-python@v4
17 | with:
18 | python-version: '3.x'
19 | architecture: 'x64'
20 | - name: Install virtualenv
21 | run: |
22 | pip install virtualenv
23 | - name: Use Node.js ${{ matrix.node-version }}
24 | uses: actions/setup-node@v4
25 | with:
26 | node-version: ${{ matrix.node-version }}
27 | - name: npm install, build, and test
28 | run: |
29 | npm ci
30 | ./scripts/test.sh
31 | env:
32 | CI: true
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .pidfile.pid
2 | node_modules
3 | *~
4 | sockjs-protocol
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .gitignore
2 | .eslintrc
3 | .eslintignore
4 | .nvmrc
5 | node_modules
6 | examples
7 | *~
8 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/erbium
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "none"
5 | }
6 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | # This is the list of sockjs authors for copyright purposes.
2 | #
3 | # This does not necessarily list everyone who has contributed code, since in
4 | # some cases, their employer may be the copyright holder. To see the full list
5 | # of contributors, see the revision history in source control.
6 | Bryce Kahle
7 | Marek Majkowski
8 | VMWare
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Unreleased
2 |
3 | ## **BREAKING CHANGES**
4 | * `installHandlers(server, options)` renamed to `attach(server)` and only takes a single argument.
5 | This means you cannot use the same SockJS server installed at multiple prefixes.
6 | In practice this was confusing and not common.
7 | * `websocket` option is deprecated, but still respected. Please use the new `transports` option.
8 | * Node.js `>= 6.5.0` is required.
9 |
10 | ## Other Fixes/Changes
11 | * Convert from coffeescript to ES6.
12 | * Update minimum Node.js version to 6.X.
13 | * Update SockJSConnection implementation to be compatible with latest Node.js streams.
14 | * SockJSConnection properties `readable` and `writable` have been removed. These are used internally by Node.js streams.
15 | * Remove `console.log` logging by default.
16 | * Remove usage of exceptions for flow control.
17 | * Add `debug` logs for easier troubleshooting.
18 | * Added `transports` option to allow selection of specific transports.
19 | * Added `detach(server)` function to remove SockJS from a HTTP server instance.
20 | * Update dependencies.
21 | * Examples have been updated to use latest versions of libraries.
22 |
23 |
24 | 0.3.19
25 | ======
26 |
27 | * Update `node-uuid` version #224
28 | * Add `disable_cors` option to prevent CORS headers from being added to responses #218
29 | * Add `dnt` header to whitelist #212
30 | * Add `x-forwarded-host` and `x-forwarded-port` headers to whitelist #208
31 | * Update `sockjs_url` default to latest 1.x target #223
32 | * Updated hapi.js example #216
33 |
34 | 0.3.18
35 | ======
36 |
37 | * Change to using `res.statusCode` instead of manual parsing of `res._header` #213
38 | * Update sockjs-protocol filename in README #203
39 |
40 | 0.3.17
41 | ======
42 |
43 | * Fix usage of undefined `session` in `heartbeat_timeout` #179
44 |
45 | 0.3.16
46 | ======
47 |
48 | * Fix CORS response for null origin #177
49 | * Add websocket ping-pong and close if no response #129, #162, #169
50 | * Update sockjs-client version in examples #182
51 | * Add koa example #180
52 | * Disable raw websocket endpoint when websocket = false #183
53 | * Upgrade to faye-websocket 0.10.0 and use proper close code
54 | * When connection is aborted, don't delay the teardown
55 | * Forward additional headers #188
56 | * Add `no-transform` to Cache-Control headers #189
57 | * Update documentation about heartbeats #192
58 |
59 |
60 | 0.3.15
61 | ======
62 |
63 | * Remove usage of naked '@' function params to be compatible with coffeescript 1.9.0 #175
64 |
65 | 0.3.14
66 | ======
67 |
68 | * Re-publish to npm because of build issue in 0.3.13
69 |
70 | 0.3.13
71 | ======
72 |
73 | * Upgrade faye-websocket to 0.9.3 to fix #171
74 |
75 | 0.3.12
76 | ======
77 |
78 | * Allow Faye socket constructor options to be passed with
79 | faye_server_options option to createServer
80 | * Fix websocket bad json tests
81 | * Upgrade Faye to allow 0.9.*
82 |
83 | 0.3.11
84 | ======
85 |
86 | * #133 - only delay disconnect on non-websocket transports
87 | * Upgrade Faye to 0.8.0
88 |
89 | 0.3.10
90 | ======
91 |
92 | * #168 - Add CORS headers for eventsource
93 | * #158 - schedule heartbeat timer even if send_buffer is not empty
94 | * #96 - remove rbytes dependency
95 | * #83 - update documentation for prefix
96 | * #163 - add protection to JSON for SWF exploit
97 | * #104 - delete unused parameters in code
98 | * #106 - update CDN urls
99 | * #79 - Don't remove stream listeners until after end so 'close' event is heard
100 | * Get rid of need for _sockjs_onload global variable
101 | * Use Faye for websocket request validation
102 | * Upgrade Faye to 0.7.3
103 | * Upgrade node-uuid to 1.4.1
104 |
105 | 0.3.9
106 | =====
107 |
108 | * #130 - Set Vary: Origin on CORS requests
109 | * Upgrade Faye to 0.7.2 from 0.7.0
110 |
111 |
112 | 0.3.8
113 | =====
114 |
115 | * #118 - Allow servers to specify a base URL in /info
116 | * #131 - Don't look up session id undefined
117 | * #124 - Small grammar updates for ReadMe
118 | * Upgrade Faye to 0.7.0 from 0.4.0
119 |
120 | 0.3.7
121 | =====
122 |
123 | * Expose "protocol" on raw websocket connection instance, correctly
124 |
125 | 0.3.6
126 | =====
127 |
128 | * When the server closes a connection, make sure the send buffer still
129 | gets flushed.
130 | * Expose "protocol" on raw websocket connection instance
131 | * #105, #109, #113 - expose 'host', 'user-agent', and 'accept-language'
132 | headers
133 | * Serve SockJS over https CDN by default
134 | * Upgrade Faye to 0.4.4 from 0.4.0
135 |
136 | 0.3.5
137 | =====
138 |
139 | * #103 - connection.protocol might have been empty on some rare
140 | occasions.
141 | * #99 - faye-websocket was leaking sockets in "closed" state
142 | when dealing with rfc websockets
143 |
144 |
145 | 0.3.4
146 | =====
147 |
148 | * #73 - apparently 'package' is a reserved keyword (use 'pkg' instead)
149 | * #93 - Coffescript can leak a variable when the same name is used
150 | in catch statement. Let's always use 'x' as the variable in catch.
151 | * #76 - decorateConnection could throw an error if remote connection
152 | was closed before setup was complete
153 | * #90 - Fix "TypeError: 'addListener'" exception (via @pl).
154 | * remove 'optionalDependencies' section from package.json,
155 | 'rbytes' was always optional.
156 | * #91 - Fix rare null exception.
157 |
158 |
159 | 0.3.3
160 | =====
161 |
162 | * sockjs/sockjs-protocol#56, #88 Fix for iOS 6 caching POSTs
163 |
164 |
165 | 0.3.1
166 | =====
167 |
168 | * #58 - websocket transport emitted an array instead of a string
169 | during onmessage event.
170 | * Running under node.js 0.7 caused infinite recursion (Stephan Kochen)
171 | * #59 - restrict characters allowed in callback parameter
172 | * Updated readme - rbytes package is optional
173 | * Updated readme WRT deployments on heroku
174 | * Add minimalistic license block to every source file.
175 |
176 |
177 | 0.3.0
178 | =====
179 |
180 | * Sending JSESSIONID cookie is now *disabled* by default.
181 | * sockjs/sockjs-protocol#46 - introduce new service
182 | required for protocol tests "/cookie_needed_echo"
183 | * Initial work towards better integration with
184 | "connect" (Stephan Kochen). See discusion:
185 | https://github.com/senchalabs/connect/pull/506
186 | * More documentation about the Cookie and Origin headers.
187 | * #51 - expose "readyState" on connection instance
188 | * #53 - expose "protocol" on connection instance
189 | * #52 - Some protocols may not emit 'close' event with IE.
190 | * sockjs/sockjs-client#49 - Support 'null' origin - aka: allow SockJS
191 | client to be served from file:// paths.
192 |
193 |
194 | 0.2.1
195 | =====
196 |
197 | * Bumped "faye-websocket" dependency to 0.4. Updated
198 | code to take advantage of introduced changes.
199 | * Pinned "node-static" and bumped "node-uuid" dependencies.
200 | * Removed "Origin" header list of headers exposed to the user.
201 | This header is not really meaningful in sockjs context.
202 | * Header "Access-Control-Allow-Methods" was misspelled.
203 |
204 |
205 | 0.2.0
206 | =====
207 |
208 | * #36, #3 - Replace a custom WebSocket server implementation
209 | with faye-websocket-node.
210 | * Multiple changes to support SockJS-protocol 0.2.
211 | * The session is now closed on network errors immediately
212 | (instead of waiting 5 seconds)
213 | * Raw websocket interface available - to make it easier
214 | to write command line SockJS clients.
215 | * Support '/info' url.
216 | * The test server got moved from SockJS-client to SockJS-node.
217 | * Dropped deprecated Server API (use createServer method instead).
218 | * Option `websocket` is now used instead of `disabled_transports`.
219 |
220 |
221 | 0.1.2
222 | =====
223 |
224 | * #27 - Allow all unicode characters to be send over SockJS.
225 | * #14 - Make it possible to customize JSESSIONID cookie logic.
226 |
227 |
228 | 0.1.1
229 | =====
230 |
231 | * #32 Expose various request headers on connection.
232 | * #30 Expose request path on connection.
233 |
234 |
235 | 0.1.0
236 | =====
237 |
238 | * The API changed, there is now an idiomatic API, modelled on node.js
239 | Stream API. The old API is deprecated and there is a dummy wrapper
240 | that emulates it. Please do upgrade to the new idiomatic API.
241 | * #22 Initial support for hybi13 (stephank)
242 | * New options accepted by the `Server` constructor: `log`,
243 | `heartbeat_delay` and `disconnect_delay`.
244 | * SockJS is now not able to send rich data structures - all data
245 | passed to `write` is converted to a string.
246 | * #23 `Connection.remoteAddress` property introduced (Stéphan Kochen)
247 | * Loads of small changes in order to adhere to protocol spec.
248 |
249 |
250 | 0.0.5
251 | =====
252 |
253 | * #20: `npm submodule sockjs` didn't work due to outdated github
254 | path.
255 |
256 |
257 | 0.0.4
258 | =====
259 |
260 | * Support for htmlfile transport, used by IE in a deployment
261 | dependent on cookies.
262 | * Added /chunking_test API, used to detect support for HTTP chunking
263 | on client side.
264 | * Unified code logic for all the chunking transports - the same code
265 | is reused for polling versions.
266 | * All the chunking transports are closed by the server after 128K was
267 | send, in order to force client to GC and reconnect.
268 | * Don't distribute source coffeescript with npm.
269 | * Minor fixes in websocket code.
270 | * Dropped jQuery dependency.
271 | * Unicode encoding could been garbled during XHR upload.
272 | * Other minor fixes.
273 |
274 |
275 | 0.0.3
276 | ======
277 |
278 | * EventSource transport didn't emit 'close' event.
279 |
280 |
281 | 0.0.2
282 | =====
283 |
284 | * By default set JSESSIONID cookie, useful for load balancing.
285 |
286 |
287 | 0.0.1
288 | =====
289 |
290 | * Initial release.
291 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2019 The sockjs Authors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SockJS-node
2 |
3 | [](https://www.npmjs.com/package/sockjs)[](https://david-dm.org/sockjs/sockjs-node)
4 |
5 | # SockJS for enterprise
6 |
7 | Available as part of the Tidelift Subscription.
8 |
9 | The maintainers of SockJS and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-sockjs?utm_source=npm-sockjs&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
10 |
11 | # SockJS family
12 |
13 | * [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library
14 | * [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server
15 | * [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server
16 | * [SockJS-tornado](https://github.com/MrJoes/sockjs-tornado) Python/Tornado server
17 | * [vert.x](https://github.com/eclipse/vert.x) Java/vert.x server
18 |
19 | Work in progress:
20 |
21 | * [SockJS-ruby](https://github.com/nyarly/sockjs-ruby)
22 | * [SockJS-netty](https://github.com/cgbystrom/sockjs-netty)
23 | * [SockJS-gevent](https://github.com/sdiehl/sockjs-gevent) ([and a fork](https://github.com/njoyce/sockjs-gevent))
24 | * [pyramid-SockJS](https://github.com/fafhrd91/pyramid_sockjs)
25 | * [wildcloud-websockets](https://github.com/wildcloud/wildcloud-websockets)
26 | * [SockJS-cyclone](https://github.com/flaviogrossi/sockjs-cyclone)
27 | * [SockJS-twisted](https://github.com/Fugiman/sockjs-twisted/)
28 | * [wai-SockJS](https://github.com/Palmik/wai-sockjs)
29 | * [SockJS-perl](https://github.com/vti/sockjs-perl)
30 | * [SockJS-go](https://github.com/igm/sockjs-go/)
31 | * [actix/sockjs](https://github.com/actix/sockjs) for Rust
32 |
33 | ⚠️️ **ATTENTION** This is pre-release documentation. The documentation for the
34 | latest stable release is at: https://github.com/sockjs/sockjs-node/tree/v0.3.19 ️⚠️
35 |
36 | # What is SockJS?
37 |
38 | SockJS is a JavaScript library (for browsers) that provides a WebSocket-like
39 | object. SockJS gives you a coherent, cross-browser, Javascript API
40 | which creates a low latency, full duplex, cross-domain communication
41 | channel between the browser and the web server, with WebSockets or without.
42 | This necessitates the use of a server, which this is one version of, for Node.js.
43 |
44 |
45 | # SockJS-node server
46 |
47 | SockJS-node is a Node.js server side counterpart of
48 | [SockJS-client browser library](https://github.com/sockjs/sockjs-client).
49 |
50 | To install `sockjs-node` run:
51 |
52 | npm install sockjs
53 |
54 | A simplified echo SockJS server could look more or less like:
55 |
56 | ```javascript
57 | const http = require('http');
58 | const sockjs = require('sockjs');
59 |
60 | const echo = sockjs.createServer({ prefix:'/echo' });
61 | echo.on('connection', function(conn) {
62 | conn.on('data', function(message) {
63 | conn.write(message);
64 | });
65 | conn.on('close', function() {});
66 | });
67 |
68 | const server = http.createServer();
69 | echo.attach(server);
70 | server.listen(9999, '0.0.0.0');
71 | ```
72 |
73 | (Take look at
74 | [examples](https://github.com/sockjs/sockjs-node/tree/main/examples/echo)
75 | directory for a complete version.)
76 |
77 | Subscribe to
78 | [SockJS mailing list](https://groups.google.com/forum/#!forum/sockjs) for
79 | discussions and support.
80 |
81 |
82 | # SockJS-node API
83 |
84 | The API design is based on common Node APIs like the
85 | [Streams API](https://nodejs.org/api/stream.html) or the
86 | [Http.Server API](https://nodejs.org/api/http.html#http_class_http_server).
87 |
88 | ## Server class
89 |
90 | SockJS module is generating a `Server` class, similar to
91 | [Node.js http.createServer](https://nodejs.org/api/http.html#http_http_createserver_requestlistener)
92 | module.
93 |
94 | ```javascript
95 | const sockjs_server = sockjs.createServer(options);
96 | ```
97 |
98 | Where `options` is a hash which can contain:
99 |
100 |
101 | - sockjs_url (string, required)
102 | - Transports which don't support cross-domain communication natively
103 | ('eventsource' to name one) use an iframe trick. A simple page is
104 | served from the SockJS server (using its foreign domain) and is
105 | placed in an invisible iframe. Code run from this iframe doesn't
106 | need to worry about cross-domain issues, as it's being run from
107 | domain local to the SockJS server. This iframe also does need to
108 | load SockJS javascript client library, and this option lets you specify
109 | its url (if you're unsure, point it to
110 |
111 | the latest minified SockJS client release, this is the default).
112 | You must explicitly specify this url on the server side for security
113 | reasons - we don't want the possibility of running any foreign
114 | javascript within the SockJS domain (aka cross site scripting attack).
115 | Also, sockjs javascript library is probably already cached by the
116 | browser - it makes sense to reuse the sockjs url you're using in
117 | normally.
118 |
119 | - prefix (string regex)
120 | - A url prefix for the server. All http requests which paths begins
121 | with selected prefix will be handled by SockJS. All other requests
122 | will be passed through, to previously registered handlers.
123 |
124 | - response_limit (integer)
125 | - Most streaming transports save responses on the client side and
126 | don't free memory used by delivered messages. Such transports need
127 | to be garbage-collected once in a while. `response_limit` sets
128 | a minimum number of bytes that can be send over a single http streaming
129 | request before it will be closed. After that client needs to open
130 | new request. Setting this value to one effectively disables
131 | streaming and will make streaming transports to behave like polling
132 | transports. The default value is 128K.
133 |
134 | - transports (Array of strings)
135 | - List of transports to enable. Select from `eventsource`, `htmlfile`,
136 | `jsonp-polling`, `websocket`, `websocket-raw`, `xhr-polling`,
137 | and `xhr-streaming`.
138 |
139 | - jsessionid (boolean or function)
140 | - Some hosting providers enable sticky sessions only to requests that
141 | have JSESSIONID cookie set. This setting controls if the server should
142 | set this cookie to a dummy value. By default setting JSESSIONID cookie
143 | is disabled. More sophisticated behaviour can be achieved by supplying
144 | a function.
145 |
146 | - log (function(severity, message))
147 | - It's quite useful, especially for debugging, to see some messages
148 | printed by a SockJS-node library. This is done using this `log`
149 | function, which is by default set to nothing. If this
150 | behaviour annoys you for some reason, override `log` setting with a
151 | custom handler. The following `severities` are used: `debug`
152 | (miscellaneous logs), `info` (requests logs), `error` (serious
153 | errors, consider filing an issue).
154 |
155 | - heartbeat_delay (milliseconds)
156 | - In order to keep proxies and load balancers from closing long
157 | running http requests we need to pretend that the connection is
158 | active and send a heartbeat packet once in a while. This setting
159 | controls how often this is done. By default a heartbeat packet is
160 | sent every 25 seconds.
161 |
162 | - disconnect_delay (milliseconds)
163 | - The server sends a `close` event when a client receiving
164 | connection have not been seen for a while. This delay is configured
165 | by this setting. By default the `close` event will be emitted when a
166 | receiving connection wasn't seen for 5 seconds.
167 |
168 | - disable_cors (boolean)
169 | - Enabling this option will prevent
170 | CORS
171 | headers from being included in the HTTP response. Can be used when the
172 | sockjs client is known to be connecting from the same origin as the
173 | sockjs server. This also disables the iframe HTML endpoint.
174 |
175 |
176 |
177 | ## Server instance
178 |
179 | Once you have create `Server` instance you can hook it to the
180 | [http.Server instance](https://nodejs.org/api/http.html#http_class_http_server).
181 |
182 | ```javascript
183 | var http_server = http.createServer();
184 | sockjs_server.attach(http_server);
185 | http_server.listen(...);
186 | ```
187 |
188 | `Server` instance is an
189 | [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter),
190 | and emits following event:
191 |
192 |
193 | - Event: connection (connection)
194 | - A new connection has been successfully opened.
195 |
196 |
197 | All http requests that don't go under the path selected by `prefix`
198 | will remain unanswered and will be passed to previously registered
199 | handlers. You must install your custom http handlers before calling
200 | `attach`. You can remove the SockJS handler later with `detach`.
201 |
202 | ## Connection instance
203 |
204 | A `Connection` instance supports
205 | [Node Stream API](https://nodejs.org/api/stream.html) and
206 | has following methods and properties:
207 |
208 |
209 | - Property: remoteAddress (string)
210 | - Last known IP address of the client.
211 |
212 | - Property: remotePort (number)
213 | - Last known port number of the client.
214 |
215 | - Property: address (object)
216 | - Hash with 'address' and 'port' fields.
217 |
218 | - Property: headers (object)
219 | - Hash containing various headers copied from last receiving request
220 | on that connection. Exposed headers include: `origin`, `referer`
221 | and `x-forwarded-for` (and friends). We explicitly do not grant
222 | access to `cookie` header, as using it may easily lead to security
223 | issues (for details read the section "Authorisation").
224 |
225 | - Property: url (string)
226 | - Url
227 | property copied from last request.
228 |
229 | - Property: pathname (string)
230 | - `pathname` from parsed url, for convenience.
231 |
232 | - Property: prefix (string)
233 | - Prefix of the url on which the request was handled.
234 |
235 | - Property: protocol (string)
236 | - Protocol used by the connection. Keep in mind that some protocols
237 | are indistinguishable - for example "xhr-polling" and "xdr-polling".
238 |
239 | - Property: readyState (integer)
240 | - Current state of the connection:
241 | 0-connecting, 1-open, 2-closing, 3-closed.
242 |
243 | - write(message)
244 | - Sends a message over opened connection. A message must be a
245 | non-empty string. It's illegal to send a message after the connection was
246 | closed (either after 'close' or 'end' method or 'close' event).
247 |
248 | - close([code], [reason])
249 | - Asks the remote client to disconnect. 'code' and 'reason'
250 | parameters are optional and can be used to share the reason of
251 | disconnection.
252 |
253 | - end()
254 | - Asks the remote client to disconnect with default 'code' and
255 | 'reason' values.
256 |
257 |
258 |
259 | A `Connection` instance emits the following events:
260 |
261 |
262 | - Event: data (message)
263 | - A message arrived on the connection. Message is a unicode
264 | string.
265 |
266 | - Event: close ()
267 | - Connection was closed. This event is triggered exactly once for
268 | every connection.
269 |
270 |
271 | For example:
272 |
273 | ```javascript
274 | sockjs_server.on('connection', function(conn) {
275 | console.log('connection' + conn);
276 | conn.on('close', function() {
277 | console.log('close ' + conn);
278 | });
279 | conn.on('data', function(message) {
280 | console.log('message ' + conn, message);
281 | });
282 | });
283 | ```
284 |
285 | ## Footnote
286 |
287 | A fully working echo server does need a bit more boilerplate (to
288 | handle requests unanswered by SockJS), see the
289 | [`echo` example](https://github.com/sockjs/sockjs-node/tree/main/examples/echo)
290 | for a complete code.
291 |
292 | # Examples
293 |
294 | If you want to see samples of running code, take a look at:
295 |
296 | * [./examples/echo](https://github.com/sockjs/sockjs-node/tree/main/examples/echo)
297 | directory, which contains a full example of a echo server.
298 | * [./tests/test_server](https://github.com/sockjs/sockjs-node/tree/main/tests/test_server) a standard SockJS test server.
299 |
300 |
301 | # Connecting to SockJS-node without the client
302 |
303 | Although the main point of SockJS it to enable browser-to-server
304 | connectivity, it is possible to connect to SockJS from an external
305 | application. Any SockJS server complying with 0.3 protocol does
306 | support a raw WebSocket url. The raw WebSocket url for the test server
307 | looks like:
308 |
309 | * ws://localhost:8081/echo/websocket
310 |
311 | You can connect any WebSocket RFC 6455 compliant WebSocket client to
312 | this url. This can be a command line client, external application,
313 | third party code or even a browser (though I don't know why you would
314 | want to do so).
315 |
316 | Note: This endpoint will *not send any heartbeat packets*.
317 |
318 |
319 | # Deployment and load balancing
320 |
321 | There are two issues that need to be considered when planning a
322 | non-trivial SockJS-node deployment: WebSocket-compatible load balancer
323 | and sticky sessions (aka session affinity).
324 |
325 | ## WebSocket compatible load balancer
326 |
327 | Often WebSockets don't play nicely with proxies and load balancers.
328 | Deploying a SockJS server behind Nginx or Apache could be painful.
329 |
330 | Fortunately recent versions of an excellent load balancer
331 | [HAProxy](http://haproxy.1wt.eu/) are able to proxy WebSocket
332 | connections. We propose to put HAProxy as a front line load balancer
333 | and use it to split SockJS traffic from normal HTTP data. Take a look
334 | at the sample
335 | [SockJS HAProxy configuration](https://github.com/sockjs/sockjs-node/blob/main/examples/haproxy.cfg).
336 |
337 | The config also shows how to use HAproxy balancing to split traffic
338 | between multiple Node.js servers. You can also do balancing using dns
339 | names.
340 |
341 | ## Sticky sessions
342 |
343 | If you plan deploying more than one SockJS server, you must make sure
344 | that all HTTP requests for a single session will hit the same server.
345 | SockJS has two mechanisms that can be useful to achieve that:
346 |
347 | * Urls are prefixed with server and session id numbers, like:
348 | `/resource///transport`. This is
349 | useful for load balancers that support prefix-based affinity
350 | (HAProxy does).
351 | * `JSESSIONID` cookie is being set by SockJS-node. Many load
352 | balancers turn on sticky sessions if that cookie is set. This
353 | technique is derived from Java applications, where sticky sessions
354 | are often necessary. HAProxy does support this method, as well as
355 | some hosting providers, for example CloudFoundry. In order to
356 | enable this method on the client side, please supply a
357 | `cookie:true` option to SockJS constructor.
358 |
359 |
360 | # Development and testing
361 |
362 | If you want to work on SockJS-node source code, you need to clone the
363 | git repo and follow these steps. First you need to install
364 | dependencies:
365 |
366 | cd sockjs-node
367 | npm install
368 |
369 | If compilation succeeds you may want to test if your changes pass all
370 | the tests. Currently, there are two separate test suites.
371 |
372 | ## SockJS-protocol Python tests
373 |
374 | To run it run something like:
375 |
376 | ./scripts/test.sh
377 |
378 | For details see
379 | [SockJS-protocol README](https://github.com/sockjs/sockjs-protocol#readme).
380 |
381 | ## SockJS-client Karma tests
382 |
383 | To run it run something like:
384 |
385 | cd sockjs-client
386 | npm run test:browser_local
387 |
388 | For details see
389 | [SockJS-client README](https://github.com/sockjs/sockjs-client#readme).
390 |
391 | # Various issues and design considerations
392 |
393 | ## Authorisation
394 |
395 | SockJS-node does not expose cookies to the application. This is done
396 | deliberately as using cookie-based authorisation with SockJS simply
397 | doesn't make sense and will lead to security issues.
398 |
399 | Cookies are a contract between a browser and an http server, and are
400 | identified by a domain name. If a browser has a cookie set for
401 | particular domain, it will pass it as a part of all http requests to
402 | the host. But to get various transports working, SockJS uses a middleman
403 | - an iframe hosted from target SockJS domain. That means the server
404 | will receive requests from the iframe, and not from the real
405 | domain. The domain of an iframe is the same as the SockJS domain. The
406 | problem is that any website can embed the iframe and communicate with
407 | it - and request establishing SockJS connection. Using cookies for
408 | authorisation in this scenario will result in granting full access to
409 | SockJS communication with your website from any website. This is a
410 | classic CSRF attack.
411 |
412 | Basically - cookies are not suited for SockJS model. If you want to
413 | authorise a session - provide a unique token on a page, send it as a
414 | first thing over SockJS connection and validate it on the server
415 | side. In essence, this is how cookies work.
416 |
417 |
418 | ## Deploying SockJS on Heroku
419 |
420 | Long polling is known to cause problems on Heroku, but
421 | [workaround for SockJS is available](https://github.com/sockjs/sockjs-node/issues/57#issuecomment-5242187).
422 |
--------------------------------------------------------------------------------
/examples/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": 0
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | package-lock.json
2 |
--------------------------------------------------------------------------------
/examples/echo/README.md:
--------------------------------------------------------------------------------
1 | SockJS-node Echo example
2 | ========================
3 |
4 | To run this example, first install dependencies:
5 |
6 | npm install
7 |
8 | And run a server:
9 |
10 | node server.js
11 |
12 |
13 | That will spawn an http server at http://127.0.0.1:9999/ which will
14 | serve both html (served from the current directory) and also SockJS
15 | server (under the [/echo](http://127.0.0.1:9999/echo) path).
16 |
--------------------------------------------------------------------------------
/examples/echo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
35 |
36 | SockJS Echo example
37 |
38 |
42 |
43 |
71 |
72 |
--------------------------------------------------------------------------------
/examples/echo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs-echo",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "node-static": "^0.7.11",
6 | "sockjs": "^0.4.0"
7 | },
8 | "private": true
9 | }
10 |
--------------------------------------------------------------------------------
/examples/echo/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const http = require('http');
4 | const sockjs = require('sockjs');
5 | const node_static = require('node-static');
6 |
7 | // 1. Echo sockjs server
8 | const sockjs_opts = {
9 | prefix: '/echo'
10 | };
11 |
12 | const sockjs_echo = sockjs.createServer(sockjs_opts);
13 | sockjs_echo.on('connection', function (conn) {
14 | conn.on('data', function (message) {
15 | conn.write(message);
16 | });
17 | });
18 |
19 | // 2. Static files server
20 | const static_directory = new node_static.Server(__dirname);
21 |
22 | // 3. Usual http stuff
23 | const server = http.createServer();
24 | server.addListener('request', function (req, res) {
25 | static_directory.serve(req, res);
26 | });
27 | server.addListener('upgrade', function (req, res) {
28 | res.end();
29 | });
30 |
31 | sockjs_echo.attach(server);
32 |
33 | console.log(' [*] Listening on 0.0.0.0:9999');
34 | server.listen(9999, '0.0.0.0');
35 |
--------------------------------------------------------------------------------
/examples/express/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
35 |
36 | SockJS Express example
37 |
38 |
42 |
43 |
71 |
72 |
--------------------------------------------------------------------------------
/examples/express/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs-express",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "express": "^4.16.3",
6 | "sockjs": "^0.4.0"
7 | },
8 | "private": true
9 | }
10 |
--------------------------------------------------------------------------------
/examples/express/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const http = require('http');
4 | const express = require('express');
5 | const sockjs = require('sockjs');
6 |
7 | const sockjs_opts = {
8 | prefix: '/echo'
9 | };
10 |
11 | const sockjs_echo = sockjs.createServer(sockjs_opts);
12 | sockjs_echo.on('connection', (conn) => {
13 | conn.on('data', (msg) => conn.write(msg));
14 | });
15 |
16 | const app = express();
17 | app.get('/', (req, res) => res.sendFile(__dirname + '/index.html'));
18 |
19 | const server = http.createServer(app);
20 | sockjs_echo.attach(server);
21 |
22 | server.listen(9999, '0.0.0.0', () => {
23 | console.log(' [*] Listening on 0.0.0.0:9999');
24 | });
25 |
--------------------------------------------------------------------------------
/examples/hapi/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
35 |
36 | SockJS Echo example
37 |
38 |
42 |
43 |
71 |
72 |
--------------------------------------------------------------------------------
/examples/hapi/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs-hapi",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "hapi": "^17.6.0",
6 | "inert": "^5.1.0",
7 | "sockjs": "^0.4.0"
8 | },
9 | "private": true
10 | }
11 |
--------------------------------------------------------------------------------
/examples/hapi/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const sockjs = require('sockjs');
4 | const Hapi = require('hapi');
5 | const Inert = require('inert');
6 |
7 | // 1. Echo sockjs server
8 | const sockjs_opts = {
9 | prefix: '/echo'
10 | };
11 |
12 | const sockjs_echo = sockjs.createServer(sockjs_opts);
13 | sockjs_echo.on('connection', function (conn) {
14 | conn.on('data', function (message) {
15 | conn.write(message);
16 | });
17 | });
18 |
19 | // Create a server and set port (default host 0.0.0.0)
20 | const hapi_server = new Hapi.Server({
21 | port: 9999
22 | });
23 |
24 | hapi_server.register(Inert).then(() => {
25 | hapi_server.route({
26 | method: 'GET',
27 | path: '/{path*}',
28 | handler: {
29 | file: './html/index.html'
30 | }
31 | });
32 |
33 | //hapi_server.listener is the http listener hapi uses
34 | sockjs_echo.attach(hapi_server.listener);
35 | hapi_server.start().then(() => console.log(' [*] Listening on 0.0.0.0:9999'));
36 | });
37 |
--------------------------------------------------------------------------------
/examples/haproxy.cfg:
--------------------------------------------------------------------------------
1 | # Requires recent Haproxy to work with websockets (for example 1.4.16).
2 | defaults
3 | mode http
4 | # Set timeouts to your needs
5 | timeout client 5s
6 | timeout connect 5s
7 | timeout server 5s
8 |
9 | frontend all 0.0.0.0:8888
10 | mode http
11 | timeout client 120s
12 |
13 | option forwardfor
14 | # Fake connection:close, required in this setup.
15 | option http-server-close
16 | option http-pretend-keepalive
17 |
18 | acl is_sockjs path_beg /echo /broadcast /close
19 | acl is_stats path_beg /stats
20 |
21 | use_backend sockjs if is_sockjs
22 | use_backend stats if is_stats
23 | default_backend static
24 |
25 |
26 | backend sockjs
27 | # Load-balance according to hash created from first two
28 | # directories in url path. For example requests going to /1/
29 | # should be handled by single server (assuming resource prefix is
30 | # one-level deep, like "/echo").
31 | balance uri depth 2
32 | timeout server 120s
33 | server srv_sockjs1 127.0.0.1:9999
34 | # server srv_sockjs2 127.0.0.1:9998
35 |
36 | backend static
37 | balance roundrobin
38 | server srv_static 127.0.0.1:8000
39 |
40 | backend stats
41 | stats uri /stats
42 | stats enable
43 |
--------------------------------------------------------------------------------
/examples/koa/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
35 |
36 | SockJS Express example
37 |
38 |
42 |
43 |
71 |
72 |
--------------------------------------------------------------------------------
/examples/koa/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs-koa",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "koa": "^2.5.3",
6 | "sockjs": "^0.4.0"
7 | },
8 | "private": true
9 | }
10 |
--------------------------------------------------------------------------------
/examples/koa/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Koa = require('koa');
4 | const sockjs = require('sockjs');
5 | const http = require('http');
6 | const fs = require('fs');
7 | const path = require('path');
8 |
9 | // 1. Echo sockjs server
10 | const sockjs_opts = {
11 | prefix: '/echo'
12 | };
13 | const sockjs_echo = sockjs.createServer(sockjs_opts);
14 | sockjs_echo.on('connection', function (conn) {
15 | conn.on('data', function (message) {
16 | conn.write(message);
17 | });
18 | });
19 |
20 | // 2. koa server
21 | const app = new Koa();
22 |
23 | app.use(function (ctx, next) {
24 | return next().then(() => {
25 | const filePath = __dirname + '/index.html';
26 | ctx.response.type = path.extname(filePath);
27 | ctx.response.body = fs.createReadStream(filePath);
28 | });
29 | });
30 |
31 | const server = http.createServer(app.callback());
32 | sockjs_echo.attach(server);
33 |
34 | server.listen(9999, '0.0.0.0');
35 | console.log(' [*] Listening on 0.0.0.0:9999');
36 |
--------------------------------------------------------------------------------
/examples/multiplex/README.md:
--------------------------------------------------------------------------------
1 | WebSocket-multiplex SockJS example
2 | ==================================
3 |
4 | This example is a copy of example from
5 | [websocket-multiplex](https://github.com/sockjs/websocket-multiplex/)
6 | project:
7 |
8 | * https://github.com/sockjs/websocket-multiplex/
9 |
10 |
11 | To run this example, first install dependencies:
12 |
13 | npm install
14 |
15 | And run a server:
16 |
17 | node server.js
18 |
19 |
20 | That will spawn an http server at http://127.0.0.1:9999/ which will
21 | serve both html (served from the current directory) and also SockJS
22 | service (under the [/multiplex](http://127.0.0.1:9999/multiplex)
23 | path).
24 |
25 | With that set up, WebSocket-multiplex is able to push three virtual
26 | connections over a single SockJS connection. See the code for details.
27 |
--------------------------------------------------------------------------------
/examples/multiplex/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
36 |
37 | SockJS Multiplex example
38 |
39 |
43 |
44 |
48 |
49 |
53 |
54 |
96 |
97 |
--------------------------------------------------------------------------------
/examples/multiplex/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs-multiplex",
3 | "version": "0.0.1",
4 | "dependencies": {
5 | "express": "^4.16.3",
6 | "sockjs": "^0.4.0",
7 | "websocket-multiplex": "^0.1.0"
8 | },
9 | "private": true
10 | }
11 |
--------------------------------------------------------------------------------
/examples/multiplex/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const http = require('http');
4 | const express = require('express');
5 | const sockjs = require('sockjs');
6 | const websocket_multiplex = require('websocket-multiplex');
7 |
8 | // 1. Setup SockJS server
9 | const sockjs_opts = {
10 | prefix: '/multiplex'
11 | };
12 | const service = sockjs.createServer(sockjs_opts);
13 |
14 | // 2. Setup multiplexing
15 | const multiplexer = new websocket_multiplex.MultiplexServer(service);
16 |
17 | const ann = multiplexer.registerChannel('ann');
18 | ann.on('connection', function (conn) {
19 | conn.write('Ann says hi!');
20 | conn.on('data', function (data) {
21 | conn.write('Ann nods: ' + data);
22 | });
23 | });
24 |
25 | const bob = multiplexer.registerChannel('bob');
26 | bob.on('connection', function (conn) {
27 | conn.write("Bob doesn't agree.");
28 | conn.on('data', function (data) {
29 | conn.write('Bob says no to: ' + data);
30 | });
31 | });
32 |
33 | const carl = multiplexer.registerChannel('carl');
34 | carl.on('connection', function (conn) {
35 | conn.write('Carl says goodbye!');
36 | // Explicitly cancel connection
37 | conn.end();
38 | });
39 |
40 | // 3. Express server
41 | const app = express();
42 | app.get('/', function (req, res) {
43 | res.sendFile(__dirname + '/index.html');
44 | });
45 |
46 | const server = http.createServer(app);
47 | service.attach(server);
48 |
49 | server.listen(9999, '0.0.0.0', () => {
50 | console.log(' [*] Listening on 0.0.0.0:9999');
51 | });
52 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Server = require('./lib/server');
4 |
5 | module.exports.createServer = function createServer(options) {
6 | return new Server(options);
7 | };
8 |
9 | module.exports.listen = function listen(http_server, options) {
10 | const srv = exports.createServer(options);
11 | if (http_server) {
12 | srv.attach(http_server);
13 | }
14 | return srv;
15 | };
16 |
--------------------------------------------------------------------------------
/lib/handlers.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | welcome_screen(req, res) {
5 | res.setHeader('Content-Type', 'text/plain; charset=UTF-8');
6 | res.writeHead(200);
7 | res.end('Welcome to SockJS!\n');
8 | },
9 |
10 | handle_404(req, res) {
11 | res.setHeader('Content-Type', 'text/plain; charset=UTF-8');
12 | res.writeHead(404);
13 | res.end('404 Error: Page not found\n');
14 | },
15 |
16 | handle_405(req, res, methods) {
17 | res.writeHead(405, { Allow: methods.join(', ') });
18 | res.end();
19 | },
20 |
21 | handle_error(err, req, res) {
22 | if (res.finished) {
23 | return;
24 | }
25 | if (typeof err === 'object' && 'status' in err) {
26 | res.setHeader('Content-Type', 'text/plain; charset=UTF-8');
27 | res.writeHead(err.status);
28 | res.end(err.message || '');
29 | } else {
30 | try {
31 | res.writeHead(500);
32 | res.end('500 - Internal Server Error');
33 | } catch (ex) {
34 | this.options.log(
35 | 'error',
36 | `Exception on "${req.method} ${req.url}" in filter "${req.last_fun}":\n${ex.stack || ex}`
37 | );
38 | }
39 | }
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/lib/iframe.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 | const middleware = require('./middleware');
5 |
6 | const iframe_template = `
7 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 | Don't panic!
19 | This is a SockJS hidden iframe. It's used for cross domain magic.
20 |
21 | `;
22 |
23 | module.exports = {
24 | iframe(req, res, _head, next) {
25 | const context = {
26 | '{{ sockjs_url }}': this.options.sockjs_url
27 | };
28 |
29 | let content = iframe_template;
30 | for (const k in context) {
31 | content = content.replace(k, context[k]);
32 | }
33 |
34 | const quoted_md5 = `"${utils.md5_hex(content)}"`;
35 |
36 | if ('if-none-match' in req.headers && req.headers['if-none-match'] === quoted_md5) {
37 | res.statusCode = 304;
38 | res.end();
39 | return next();
40 | }
41 |
42 | middleware.cache_for(res);
43 | res.setHeader('Content-Type', 'text/html; charset=UTF-8');
44 | res.setHeader('ETag', quoted_md5);
45 | res.setHeader('Content-Length', Buffer.byteLength(content));
46 | res.end(content);
47 | next();
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/lib/info.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('./utils');
4 | const middleware = require('./middleware');
5 |
6 | module.exports = {
7 | info(req, res) {
8 | const info = {
9 | // deprecated option, but useful for old clients
10 | websocket: this.options.transports.includes('websocket'),
11 | transports: this.options.transports,
12 | origins: this.options.disable_cors ? undefined : ['*:*'],
13 | cookie_needed: !!this.options.jsessionid,
14 | entropy: utils.random32()
15 | };
16 | // Users can specify a new base URL which further requests will be made
17 | // against. For example, it may contain a randomized domain name to
18 | // avoid browser per-domain connection limits.
19 | if (typeof this.options.base_url === 'function') {
20 | info.base_url = this.options.base_url();
21 | } else if (this.options.base_url) {
22 | info.base_url = this.options.base_url;
23 | }
24 | res.setHeader('Content-Type', 'application/json; charset=UTF-8');
25 | res.writeHead(200);
26 | res.end(JSON.stringify(info));
27 | },
28 |
29 | info_options(req, res, _head, next) {
30 | res.statusCode = 204;
31 | middleware.cache_for(res);
32 | res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
33 | res.setHeader('Access-Control-Max-Age', res.cache_for);
34 | res.end();
35 | next();
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/lib/listener.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:listener');
4 | const transportList = require('./transport/list');
5 | const middleware = require('./middleware');
6 | const handlers = require('./handlers');
7 | const info = require('./info');
8 | const iframe = require('./iframe');
9 |
10 | module.exports.generateDispatcher = function generateDispatcher(options) {
11 | const p = (s) => new RegExp(`^${options.prefix}${s}[/]?$`);
12 | const t = (s) => [p(`/([^/.]+)/([^/.]+)${s}`), 'server', 'session'];
13 | const prefix_dispatcher = [
14 | ['GET', p(''), [handlers.welcome_screen]],
15 | ['OPTIONS', p('/info'), [middleware.h_sid, middleware.xhr_cors, info.info_options]],
16 | ['GET', p('/info'), [middleware.xhr_cors, middleware.h_no_cache, info.info]]
17 | ];
18 | if (!options.disable_cors) {
19 | prefix_dispatcher.push(['GET', p('/iframe[0-9-.a-z_]*.html'), [iframe.iframe]]);
20 | }
21 |
22 | const transport_dispatcher = [];
23 |
24 | for (const name of options.transports) {
25 | const tr = transportList[name];
26 | if (!tr) {
27 | throw new Error(`unknown transport ${name}`);
28 | }
29 | debug('enabling transport', name);
30 |
31 | for (const route of tr.routes) {
32 | const d = route.transport ? transport_dispatcher : prefix_dispatcher;
33 | const path = route.transport ? t(route.path) : p(route.path);
34 | const fullroute = [route.method, path, route.handlers];
35 | if (!d.some((x) => x[0] == route.method && x[1].toString() === path.toString())) {
36 | d.push(fullroute);
37 | }
38 | }
39 | }
40 | return prefix_dispatcher.concat(transport_dispatcher);
41 | };
42 |
--------------------------------------------------------------------------------
/lib/middleware.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const FayeWebsocket = require('faye-websocket');
4 | const utils = require('./utils');
5 | const querystring = require('querystring');
6 |
7 | module.exports = {
8 | h_no_cache(req, res, _head, next) {
9 | res.setHeader('Cache-Control', 'no-store, no-cache, no-transform, must-revalidate, max-age=0');
10 | next();
11 | },
12 |
13 | h_sid(req, res, _head, next) {
14 | // Some load balancers do sticky sessions, but only if there is
15 | // a JSESSIONID cookie. If this cookie isn't yet set, we shall
16 | // set it to a dummy value. It doesn't really matter what, as
17 | // session information is usually added by the load balancer.
18 | req.cookies = utils.parseCookie(req.headers.cookie);
19 | if (typeof this.options.jsessionid === 'function') {
20 | // Users can supply a function
21 | this.options.jsessionid(req, res);
22 | } else if (this.options.jsessionid) {
23 | // We need to set it every time, to give the loadbalancer
24 | // opportunity to attach its own cookies.
25 | const jsid = req.cookies['JSESSIONID'] || 'dummy';
26 | res.setHeader('Set-Cookie', `JSESSIONID=${jsid}; path=/`);
27 | }
28 | next();
29 | },
30 |
31 | cache_for(res, duration = 365 * 24 * 60 * 60) {
32 | res.cache_for = duration;
33 | const exp = new Date(Date.now() + duration * 1000);
34 | res.setHeader('Cache-Control', `public, max-age=${duration}`);
35 | res.setHeader('Expires', exp.toGMTString());
36 | },
37 |
38 | log_request(req, res, _head, next) {
39 | const td = Date.now() - req.start_date;
40 | this.options.log(
41 | 'info',
42 | `${req.method} ${req.url} ${td}ms ${res.finished ? res.statusCode : '(unfinished)'}`
43 | );
44 | next();
45 | },
46 |
47 | expect_form(req, res, _head, next) {
48 | utils.getBody(req, (err, body) => {
49 | if (err) {
50 | return next(err);
51 | }
52 |
53 | switch ((req.headers['content-type'] || '').split(';')[0]) {
54 | case 'application/x-www-form-urlencoded':
55 | req.body = querystring.parse(body);
56 | break;
57 | case 'text/plain':
58 | case '':
59 | req.body = body;
60 | break;
61 | default:
62 | this.options.log('error', `Unsupported content-type ${req.headers['content-type']}`);
63 | break;
64 | }
65 | next();
66 | });
67 | },
68 |
69 | expect_xhr(req, res, _head, next) {
70 | utils.getBody(req, (err, body) => {
71 | if (err) {
72 | return next(err);
73 | }
74 |
75 | switch ((req.headers['content-type'] || '').split(';')[0]) {
76 | case 'text/plain':
77 | case 'T':
78 | case 'application/json':
79 | case 'application/xml':
80 | case '':
81 | case 'text/xml':
82 | req.body = body;
83 | break;
84 | default:
85 | this.options.log('error', `Unsupported content-type ${req.headers['content-type']}`);
86 | break;
87 | }
88 | next();
89 | });
90 | },
91 |
92 | websocket_check(req, _socket, _head, next) {
93 | if (!FayeWebsocket.isWebSocket(req)) {
94 | return next({
95 | status: 400,
96 | message: 'Not a valid websocket request'
97 | });
98 | }
99 | next();
100 | },
101 |
102 | xhr_options(req, res, _head, next) {
103 | res.statusCode = 204; // No content
104 | module.exports.cache_for(res);
105 | res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, POST');
106 | res.setHeader('Access-Control-Max-Age', res.cache_for);
107 | res.end();
108 | next();
109 | },
110 |
111 | xhr_cors(req, res, _head, next) {
112 | if (this.options.disable_cors) {
113 | return next();
114 | }
115 |
116 | let origin;
117 | if (!req.headers['origin']) {
118 | origin = '*';
119 | } else {
120 | origin = req.headers['origin'];
121 | res.setHeader('Access-Control-Allow-Credentials', 'true');
122 | }
123 | res.setHeader('Access-Control-Allow-Origin', origin);
124 | res.setHeader('Vary', 'Origin');
125 | const headers = req.headers['access-control-request-headers'];
126 | if (headers) {
127 | res.setHeader('Access-Control-Allow-Headers', headers);
128 | }
129 | next();
130 | }
131 | };
132 |
--------------------------------------------------------------------------------
/lib/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const events = require('events');
4 | const url = require('url');
5 | const debug = require('debug')('sockjs:server');
6 | const listener = require('./listener');
7 | const webjs = require('./webjs');
8 | const pkg = require('../package.json');
9 |
10 | class Server extends events.EventEmitter {
11 | constructor(user_options) {
12 | super();
13 | this.options = Object.assign(
14 | {
15 | prefix: '',
16 | transports: [
17 | 'eventsource',
18 | 'htmlfile',
19 | 'jsonp-polling',
20 | 'websocket',
21 | 'websocket-raw',
22 | 'xhr-polling',
23 | 'xhr-streaming'
24 | ],
25 | response_limit: 128 * 1024,
26 | faye_server_options: null,
27 | jsessionid: false,
28 | heartbeat_delay: 25000,
29 | disconnect_delay: 5000,
30 | log() {},
31 | sockjs_url: 'https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js'
32 | },
33 | user_options
34 | );
35 |
36 | // support old options.websocket setting
37 | if (user_options.websocket === false) {
38 | const trs = new Set(this.options.transports);
39 | trs.delete('websocket');
40 | trs.delete('websocket-raw');
41 | this.options.transports = Array.from(trs.values());
42 | }
43 |
44 | this._prefixMatches = () => true;
45 | if (this.options.prefix) {
46 | // remove trailing slash, but not leading
47 | this.options.prefix = this.options.prefix.replace(/\/$/, '');
48 | this._prefixMatches = (requrl) => url.parse(requrl).pathname.startsWith(this.options.prefix);
49 | }
50 |
51 | this.options.log(
52 | 'debug',
53 | `SockJS v${pkg.version} bound to ${JSON.stringify(this.options.prefix)}`
54 | );
55 | this.handler = webjs.generateHandler(this, listener.generateDispatcher(this.options));
56 | }
57 |
58 | attach(server) {
59 | this._rlisteners = this._installListener(server, 'request');
60 | this._ulisteners = this._installListener(server, 'upgrade');
61 | }
62 |
63 | detach(server) {
64 | if (this._rlisteners) {
65 | this._removeListener(server, 'request', this._rlisteners);
66 | this._rlisteners = null;
67 | }
68 | if (this._ulisteners) {
69 | this._removeListener(server, 'upgrade', this._ulisteners);
70 | this._ulisteners = null;
71 | }
72 | }
73 |
74 | _removeListener(server, eventName, listeners) {
75 | server.removeListener(eventName, this.handler);
76 | listeners.forEach((l) => server.on(eventName, l));
77 | }
78 |
79 | _installListener(server, eventName) {
80 | const listeners = server.listeners(eventName).filter((x) => x !== this.handler);
81 | server.removeAllListeners(eventName);
82 | server.on(eventName, (req, res, head) => {
83 | if (this._prefixMatches(req.url)) {
84 | debug('prefix match', eventName, req.url, this.options.prefix);
85 | this.handler(req, res, head);
86 | } else {
87 | listeners.forEach((l) => l.call(server, req, res, head));
88 | }
89 | });
90 | return listeners;
91 | }
92 | }
93 |
94 | module.exports = Server;
95 |
--------------------------------------------------------------------------------
/lib/session.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:session');
4 | const Transport = require('./transport/transport');
5 | const SockJSConnection = require('./sockjs-connection');
6 |
7 | const MAP = new Map();
8 | function closeFrame(status, reason) {
9 | return `c${JSON.stringify([status, reason])}`;
10 | }
11 |
12 | class Session {
13 | static bySessionId(session_id) {
14 | if (!session_id) {
15 | return null;
16 | }
17 | return MAP.get(session_id) || null;
18 | }
19 |
20 | static _register(req, server, session_id, receiver) {
21 | let session = Session.bySessionId(session_id);
22 | if (!session) {
23 | debug('create new session', session_id);
24 | session = new Session(session_id, server);
25 | }
26 | session.register(req, receiver);
27 | return session;
28 | }
29 |
30 | static register(req, server, receiver) {
31 | debug('static register', req.session);
32 | return Session._register(req, server, req.session, receiver);
33 | }
34 |
35 | static registerNoSession(req, server, receiver) {
36 | debug('static registerNoSession');
37 | return Session._register(req, server, undefined, receiver);
38 | }
39 |
40 | constructor(session_id, server) {
41 | this.session_id = session_id;
42 | this.heartbeat_delay = server.options.heartbeat_delay;
43 | this.disconnect_delay = server.options.disconnect_delay;
44 | this.prefix = server.options.prefix;
45 | this.send_buffer = [];
46 | this.is_closing = false;
47 | this.readyState = Transport.CONNECTING;
48 | debug('readyState', 'CONNECTING', this.session_id);
49 | if (this.session_id) {
50 | MAP.set(this.session_id, this);
51 | }
52 | this.didTimeout = this.didTimeout.bind(this);
53 | this.to_tref = setTimeout(this.didTimeout, this.disconnect_delay);
54 | this.connection = new SockJSConnection(this);
55 | this.emit_open = () => {
56 | this.emit_open = null;
57 | server.emit('connection', this.connection);
58 | };
59 | }
60 |
61 | get id() {
62 | return this.session_id;
63 | }
64 |
65 | register(req, recv) {
66 | if (this.recv) {
67 | recv.sendFrame(closeFrame(2010, 'Another connection still open'));
68 | recv.close();
69 | return;
70 | }
71 | if (this.to_tref) {
72 | clearTimeout(this.to_tref);
73 | this.to_tref = null;
74 | }
75 | if (this.readyState === Transport.CLOSING) {
76 | this.flushToRecv(recv);
77 | recv.sendFrame(this.close_frame);
78 | recv.close();
79 | this.to_tref = setTimeout(this.didTimeout, this.disconnect_delay);
80 | return;
81 | }
82 | // Registering. From now on 'unregister' is responsible for
83 | // setting the timer.
84 | this.recv = recv;
85 | this.recv.session = this;
86 |
87 | // Save parameters from request
88 | this.decorateConnection(req);
89 |
90 | // first, send the open frame
91 | if (this.readyState === Transport.CONNECTING) {
92 | this.recv.sendFrame('o');
93 | this.readyState = Transport.OPEN;
94 | debug('readyState', 'OPEN', this.session_id);
95 | // Emit the open event, but not right now
96 | process.nextTick(this.emit_open);
97 | }
98 |
99 | // At this point the transport might have gotten away (jsonp).
100 | if (!this.recv) {
101 | return;
102 | }
103 | this.tryFlush();
104 | }
105 |
106 | decorateConnection(req) {
107 | Session.decorateConnection(req, this.connection, this.recv);
108 | }
109 |
110 | static decorateConnection(req, connection, recv) {
111 | let socket = recv.socket;
112 | if (!socket) {
113 | socket = recv.response.socket;
114 | }
115 | // Store the last known address.
116 | let remoteAddress, remotePort, address;
117 | try {
118 | remoteAddress = socket.remoteAddress;
119 | remotePort = socket.remotePort;
120 | address = socket.address();
121 | } catch (x) {
122 | // intentionally empty
123 | }
124 |
125 | if (remoteAddress) {
126 | // All-or-nothing
127 | connection.remoteAddress = remoteAddress;
128 | connection.remotePort = remotePort;
129 | connection.address = address;
130 | }
131 |
132 | connection.url = req.url;
133 | connection.pathname = req.pathname;
134 | connection.protocol = recv.protocol;
135 |
136 | const headers = {};
137 | const allowedHeaders = [
138 | 'referer',
139 | 'x-client-ip',
140 | 'x-forwarded-for',
141 | 'x-cluster-client-ip',
142 | 'via',
143 | 'x-real-ip',
144 | 'x-forwarded-proto',
145 | 'x-ssl',
146 | 'dnt',
147 | 'host',
148 | 'user-agent',
149 | 'accept-language'
150 | ];
151 | for (const key of allowedHeaders) {
152 | if (req.headers[key]) {
153 | headers[key] = req.headers[key];
154 | }
155 | }
156 |
157 | if (headers) {
158 | connection.headers = headers;
159 | }
160 | }
161 |
162 | unregister() {
163 | debug('unregister', this.session_id);
164 | const delay = this.recv.delay_disconnect;
165 | this.recv.session = null;
166 | this.recv = null;
167 | if (this.to_tref) {
168 | clearTimeout(this.to_tref);
169 | }
170 |
171 | if (delay) {
172 | debug('delay timeout', this.session_id);
173 | this.to_tref = setTimeout(this.didTimeout, this.disconnect_delay);
174 | } else {
175 | debug('immediate timeout', this.session_id);
176 | this.didTimeout();
177 | }
178 | }
179 |
180 | flushToRecv(recv) {
181 | if (this.send_buffer.length > 0) {
182 | const sb = this.send_buffer;
183 | this.send_buffer = [];
184 | recv.sendBulk(sb);
185 | return true;
186 | }
187 | return false;
188 | }
189 |
190 | tryFlush() {
191 | if (!this.flushToRecv(this.recv) || !this.to_tref) {
192 | if (this.to_tref) {
193 | clearTimeout(this.to_tref);
194 | }
195 | const x = () => {
196 | if (this.recv) {
197 | this.to_tref = setTimeout(x, this.heartbeat_delay);
198 | this.recv.heartbeat();
199 | }
200 | };
201 | this.to_tref = setTimeout(x, this.heartbeat_delay);
202 | }
203 | }
204 |
205 | didTimeout() {
206 | if (this.to_tref) {
207 | clearTimeout(this.to_tref);
208 | this.to_tref = null;
209 | }
210 | if (
211 | this.readyState !== Transport.CONNECTING &&
212 | this.readyState !== Transport.OPEN &&
213 | this.readyState !== Transport.CLOSING
214 | ) {
215 | throw new Error('INVALID_STATE_ERR');
216 | }
217 | if (this.recv) {
218 | throw new Error('RECV_STILL_THERE');
219 | }
220 | debug('readyState', 'CLOSED', this.session_id);
221 | this.readyState = Transport.CLOSED;
222 | this.connection.push(null);
223 | this.connection = null;
224 | if (this.session_id) {
225 | MAP.delete(this.session_id);
226 | debug('delete session', this.session_id, MAP.size);
227 | this.session_id = null;
228 | }
229 | }
230 |
231 | didMessage(payload) {
232 | if (this.readyState === Transport.OPEN) {
233 | this.connection.push(payload);
234 | }
235 | }
236 |
237 | send(payload) {
238 | if (this.readyState !== Transport.OPEN) {
239 | return false;
240 | }
241 | this.send_buffer.push(payload);
242 | if (this.recv) {
243 | this.tryFlush();
244 | }
245 | return true;
246 | }
247 |
248 | close(status = 1000, reason = 'Normal closure') {
249 | debug('close', status, reason);
250 | if (this.readyState !== Transport.OPEN) {
251 | return false;
252 | }
253 | this.readyState = Transport.CLOSING;
254 | debug('readyState', 'CLOSING', this.session_id);
255 | this.close_frame = closeFrame(status, reason);
256 | if (this.recv) {
257 | // Go away. sendFrame can trigger close which can
258 | // trigger unregister. Make sure this.recv is not null.
259 | this.recv.sendFrame(this.close_frame);
260 | if (this.recv) {
261 | this.recv.close();
262 | }
263 | if (this.recv) {
264 | this.unregister();
265 | }
266 | }
267 | return true;
268 | }
269 | }
270 |
271 | module.exports = Session;
272 |
--------------------------------------------------------------------------------
/lib/sockjs-connection.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:connection');
4 | const stream = require('stream');
5 | const { v4: uuid } = require('uuid');
6 |
7 | class SockJSConnection extends stream.Duplex {
8 | constructor(session) {
9 | super({ decodeStrings: false, encoding: 'utf8', readableObjectMode: true });
10 | this._session = session;
11 | this.id = uuid();
12 | this.headers = {};
13 | this.prefix = this._session.prefix;
14 | debug('new connection', this.id, this.prefix);
15 | }
16 |
17 | toString() {
18 | return ``;
19 | }
20 |
21 | _write(chunk, encoding, callback) {
22 | if (Buffer.isBuffer(chunk)) {
23 | chunk = chunk.toString();
24 | }
25 | this._session.send(chunk);
26 | callback();
27 | }
28 |
29 | _read() {}
30 |
31 | end(chunk, encoding, callback) {
32 | super.end(chunk, encoding, callback);
33 | this.close();
34 | }
35 |
36 | close(code, reason) {
37 | debug('close', code, reason);
38 | return this._session.close(code, reason);
39 | }
40 |
41 | get readyState() {
42 | return this._session.readyState;
43 | }
44 | }
45 |
46 | module.exports = SockJSConnection;
47 |
--------------------------------------------------------------------------------
/lib/transport/base-receiver.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const utils = require('../utils');
4 | const debug = require('debug')('sockjs:base-receiver');
5 |
6 | class BaseReceiver {
7 | constructor(socket) {
8 | this.abort = this.abort.bind(this);
9 | this.socket = socket;
10 | this.socket.on('close', this.abort);
11 | this.socket.on('end', this.abort);
12 | }
13 |
14 | tearDown() {
15 | if (!this.socket) {
16 | return;
17 | }
18 | debug('tearDown', this.session && this.session.id);
19 | this.socket.removeListener('close', this.abort);
20 | this.socket.removeListener('end', this.abort);
21 | this.socket = null;
22 | }
23 |
24 | abort() {
25 | debug('abort', this.session && this.session.id);
26 | this.delay_disconnect = false;
27 | this.close();
28 | }
29 |
30 | close() {
31 | debug('close', this.session && this.session.id);
32 | this.tearDown();
33 | if (this.session) {
34 | this.session.unregister();
35 | }
36 | }
37 |
38 | sendBulk(messages) {
39 | const q_msgs = messages.map((m) => utils.quote(m)).join(',');
40 | return this.sendFrame(`a[${q_msgs}]`);
41 | }
42 |
43 | heartbeat() {
44 | return this.sendFrame('h');
45 | }
46 | }
47 |
48 | module.exports = BaseReceiver;
49 |
--------------------------------------------------------------------------------
/lib/transport/eventsource.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const utils = require('../utils');
3 | const ResponseReceiver = require('./response-receiver');
4 | const Session = require('../session');
5 | const middleware = require('../middleware');
6 |
7 | class EventSourceReceiver extends ResponseReceiver {
8 | constructor(req, res, options) {
9 | super(req, res, options);
10 | this.protocol = 'eventsource';
11 | }
12 |
13 | sendFrame(payload) {
14 | // Beware of leading whitespace
15 | const data = `data: ${utils.escape_selected(payload, '\r\n\x00')}\r\n\r\n`;
16 | return super.sendFrame(data);
17 | }
18 | }
19 |
20 | function eventsource(req, res, _head, next) {
21 | let origin;
22 | if (!req.headers['origin'] || req.headers['origin'] === 'null') {
23 | origin = '*';
24 | } else {
25 | origin = req.headers['origin'];
26 | res.setHeader('Access-Control-Allow-Credentials', 'true');
27 | }
28 | res.setHeader('Content-Type', 'text/event-stream');
29 | res.setHeader('Access-Control-Allow-Origin', origin);
30 | res.setHeader('Vary', 'Origin');
31 | const headers = req.headers['access-control-request-headers'];
32 | if (headers) {
33 | res.setHeader('Access-Control-Allow-Headers', headers);
34 | }
35 |
36 | res.writeHead(200);
37 | // Opera needs one more new line at the start.
38 | res.write('\r\n');
39 |
40 | Session.register(req, this, new EventSourceReceiver(req, res, this.options));
41 | next();
42 | }
43 |
44 | module.exports = {
45 | routes: [
46 | {
47 | method: 'GET',
48 | path: '/eventsource',
49 | handlers: [middleware.h_sid, middleware.h_no_cache, eventsource],
50 | transport: true
51 | }
52 | ]
53 | };
54 |
--------------------------------------------------------------------------------
/lib/transport/htmlfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ResponseReceiver = require('./response-receiver');
4 | const Session = require('../session');
5 | const middleware = require('../middleware');
6 |
7 | // Browsers fail with "Uncaught exception: ReferenceError: Security
8 | // error: attempted to read protected variable: _jp". Set
9 | // document.domain in order to work around that.
10 | let iframe_template = `
11 |
12 |
13 |
14 |
15 | Don't panic!
16 |
23 | `;
24 | // Safari needs at least 1024 bytes to parse the website. Relevant:
25 | // http://code.google.com/p/browsersec/wiki/Part2#Survey_of_content_sniffing_behaviors
26 | iframe_template += Array(1024 - iframe_template.length + 14).join(' ');
27 | iframe_template += '\r\n\r\n';
28 |
29 | class HtmlFileReceiver extends ResponseReceiver {
30 | constructor(req, res, options) {
31 | super(req, res, options);
32 | this.protocol = 'htmlfile';
33 | }
34 |
35 | sendFrame(payload) {
36 | return super.sendFrame(`\r\n`);
37 | }
38 | }
39 |
40 | function htmlfile(req, res, _head, next) {
41 | if (!('c' in req.query || 'callback' in req.query)) {
42 | return next({
43 | status: 500,
44 | message: '"callback" parameter required'
45 | });
46 | }
47 | const callback = 'c' in req.query ? req.query['c'] : req.query['callback'];
48 | if (/[^a-zA-Z0-9-_.]/.test(callback)) {
49 | return next({
50 | status: 500,
51 | message: 'invalid "callback" parameter'
52 | });
53 | }
54 |
55 | res.setHeader('Content-Type', 'text/html; charset=UTF-8');
56 | res.writeHead(200);
57 | res.write(iframe_template.replace(/{{ callback }}/g, callback));
58 |
59 | Session.register(req, this, new HtmlFileReceiver(req, res, this.options));
60 | next();
61 | }
62 |
63 | module.exports = {
64 | routes: [
65 | {
66 | method: 'GET',
67 | path: '/htmlfile',
68 | handlers: [middleware.h_sid, middleware.h_no_cache, htmlfile],
69 | transport: true
70 | }
71 | ]
72 | };
73 |
--------------------------------------------------------------------------------
/lib/transport/jsonp-polling.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ResponseReceiver = require('./response-receiver');
4 | const Session = require('../session');
5 | const middleware = require('../middleware');
6 |
7 | class JsonpReceiver extends ResponseReceiver {
8 | constructor(req, res, options, callback) {
9 | super(req, res, options);
10 | this.protocol = 'jsonp-polling';
11 | this.max_response_size = 1;
12 | this.callback = callback;
13 | }
14 |
15 | sendFrame(payload) {
16 | // Yes, JSONed twice, there isn't a a better way, we must pass
17 | // a string back, and the script, will be evaled() by the
18 | // browser.
19 | // prepend comment to avoid SWF exploit #163
20 | return super.sendFrame(`/**/${this.callback}(${JSON.stringify(payload)});\r\n`);
21 | }
22 | }
23 |
24 | function jsonp(req, res, _head, next) {
25 | if (!('c' in req.query || 'callback' in req.query)) {
26 | return next({
27 | status: 500,
28 | message: '"callback" parameter required'
29 | });
30 | }
31 |
32 | const callback = 'c' in req.query ? req.query['c'] : req.query['callback'];
33 | if (/[^a-zA-Z0-9-_.]/.test(callback) || callback.length > 32) {
34 | return next({
35 | status: 500,
36 | message: 'invalid "callback" parameter'
37 | });
38 | }
39 |
40 | // protect against SWF JSONP exploit - #163
41 | res.setHeader('X-Content-Type-Options', 'nosniff');
42 | res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
43 | res.writeHead(200);
44 |
45 | Session.register(req, this, new JsonpReceiver(req, res, this.options, callback));
46 | next();
47 | }
48 |
49 | function jsonp_send(req, res, _head, next) {
50 | if (!req.body) {
51 | return next({
52 | status: 500,
53 | message: 'Payload expected.'
54 | });
55 | }
56 | let d;
57 | if (typeof req.body === 'string') {
58 | try {
59 | d = JSON.parse(req.body);
60 | } catch (x) {
61 | return next({
62 | status: 500,
63 | message: 'Broken JSON encoding.'
64 | });
65 | }
66 | } else {
67 | d = req.body.d;
68 | }
69 | if (typeof d === 'string' && d) {
70 | try {
71 | d = JSON.parse(d);
72 | } catch (x) {
73 | return next({
74 | status: 500,
75 | message: 'Broken JSON encoding.'
76 | });
77 | }
78 | }
79 |
80 | if (!d || d.__proto__.constructor !== Array) {
81 | return next({
82 | status: 500,
83 | message: 'Payload expected.'
84 | });
85 | }
86 | const jsonp = Session.bySessionId(req.session);
87 | if (jsonp === null) {
88 | return next({ status: 404, message: 'session not found' });
89 | }
90 | for (const message of d) {
91 | jsonp.didMessage(message);
92 | }
93 |
94 | res.setHeader('Content-Length', '2');
95 | res.setHeader('Content-Type', 'text/plain; charset=UTF-8');
96 | res.writeHead(200);
97 | res.end('ok');
98 | next();
99 | }
100 |
101 | module.exports = {
102 | routes: [
103 | {
104 | method: 'GET',
105 | path: '/jsonp',
106 | handlers: [middleware.h_sid, middleware.h_no_cache, jsonp],
107 | transport: true
108 | },
109 | {
110 | method: 'POST',
111 | path: '/jsonp_send',
112 | handlers: [middleware.h_sid, middleware.h_no_cache, middleware.expect_form, jsonp_send],
113 | transport: true
114 | }
115 | ]
116 | };
117 |
--------------------------------------------------------------------------------
/lib/transport/list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | eventsource: require('./eventsource'),
5 | htmlfile: require('./htmlfile'),
6 | 'jsonp-polling': require('./jsonp-polling'),
7 | websocket: require('./websocket'),
8 | 'websocket-raw': require('./websocket-raw'),
9 | 'xhr-polling': require('./xhr-polling'),
10 | 'xhr-streaming': require('./xhr-streaming')
11 | };
12 |
--------------------------------------------------------------------------------
/lib/transport/response-receiver.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const BaseReceiver = require('./base-receiver');
4 | const debug = require('debug')('sockjs:response-receiver');
5 |
6 | // Write stuff to response, using chunked encoding if possible.
7 | class ResponseReceiver extends BaseReceiver {
8 | constructor(request, response, options) {
9 | super(request.socket);
10 | this.max_response_size = options.response_limit;
11 | this.delay_disconnect = true;
12 | this.request = request;
13 | this.response = response;
14 | this.options = options;
15 | this.curr_response_size = 0;
16 | try {
17 | this.request.socket.setKeepAlive(true, 5000);
18 | } catch (x) {
19 | // intentionally empty
20 | }
21 | }
22 |
23 | sendFrame(payload) {
24 | debug('sendFrame');
25 | this.curr_response_size += payload.length;
26 | let r = false;
27 | try {
28 | this.response.write(payload);
29 | r = true;
30 | } catch (x) {
31 | // intentionally empty
32 | }
33 | if (this.max_response_size && this.curr_response_size >= this.max_response_size) {
34 | this.close();
35 | }
36 | return r;
37 | }
38 |
39 | close() {
40 | super.close(...arguments);
41 | try {
42 | this.response.end();
43 | } catch (x) {
44 | // intentionally empty
45 | }
46 | this.request = null;
47 | this.response = null;
48 | }
49 | }
50 |
51 | module.exports = ResponseReceiver;
52 |
--------------------------------------------------------------------------------
/lib/transport/transport.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | class Transport {}
4 |
5 | Transport.CONNECTING = 0;
6 | Transport.OPEN = 1;
7 | Transport.CLOSING = 2;
8 | Transport.CLOSED = 3;
9 |
10 | module.exports = Transport;
11 |
--------------------------------------------------------------------------------
/lib/transport/websocket-raw.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const FayeWebsocket = require('faye-websocket');
4 | const Session = require('../session');
5 | const Transport = require('./transport');
6 | const SockJSConnection = require('../sockjs-connection');
7 | const middleware = require('../middleware');
8 |
9 | class RawWebsocketSessionReceiver {
10 | constructor(req, conn, server, ws) {
11 | this.ws = ws;
12 | this.prefix = server.options.prefix;
13 | this.readyState = Transport.OPEN;
14 | this.recv = {
15 | socket: conn,
16 | protocol: 'websocket-raw'
17 | };
18 |
19 | this.connection = new SockJSConnection(this);
20 | Session.decorateConnection(req, this.connection, this.recv);
21 | server.emit('connection', this.connection);
22 |
23 | this._close = this._close.bind(this);
24 | this.ws.once('close', this._close);
25 |
26 | this.didMessage = this.didMessage.bind(this);
27 | this.ws.on('message', this.didMessage);
28 | }
29 |
30 | didMessage(m) {
31 | if (this.readyState === Transport.OPEN) {
32 | this.connection.emit('data', m.data);
33 | }
34 | }
35 |
36 | send(payload) {
37 | if (this.readyState !== Transport.OPEN) {
38 | return false;
39 | }
40 | this.ws.send(payload);
41 | return true;
42 | }
43 |
44 | close(status = 1000, reason = 'Normal closure') {
45 | if (this.readyState !== Transport.OPEN) {
46 | return false;
47 | }
48 | this.readyState = Transport.CLOSING;
49 | this.ws.close(status, reason, false);
50 | return true;
51 | }
52 |
53 | _close() {
54 | if (!this.ws) {
55 | return;
56 | }
57 | this.ws.removeEventListener('message', this.didMessage);
58 | this.ws.removeEventListener('close', this._close);
59 | try {
60 | this.ws.close(1000, 'Normal closure', false);
61 | } catch (x) {
62 | // intentionally empty
63 | }
64 | this.ws = null;
65 |
66 | this.readyState = Transport.CLOSED;
67 | this.connection.emit('end');
68 | this.connection.emit('close');
69 | this.connection = null;
70 | }
71 | }
72 |
73 | function raw_websocket(req, socket, head, next) {
74 | const ver = req.headers['sec-websocket-version'] || '';
75 | if (['8', '13'].indexOf(ver) === -1) {
76 | return next({
77 | status: 400,
78 | message: 'Only supported WebSocket protocol is RFC 6455.'
79 | });
80 | }
81 | const ws = new FayeWebsocket(req, socket, head, null, this.options.faye_server_options);
82 | ws.onopen = () => {
83 | new RawWebsocketSessionReceiver(req, socket, this, ws);
84 | };
85 | next();
86 | }
87 |
88 | module.exports = {
89 | routes: [
90 | {
91 | method: 'GET',
92 | path: '/websocket',
93 | handlers: [middleware.websocket_check, raw_websocket],
94 | transport: false
95 | }
96 | ]
97 | };
98 |
--------------------------------------------------------------------------------
/lib/transport/websocket.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:trans:websocket');
4 | const FayeWebsocket = require('faye-websocket');
5 | const BaseReceiver = require('./base-receiver');
6 | const Session = require('../session');
7 | const middleware = require('../middleware');
8 |
9 | class WebSocketReceiver extends BaseReceiver {
10 | constructor(ws, socket) {
11 | super(socket);
12 | debug('new connection');
13 | this.protocol = 'websocket';
14 | this.ws = ws;
15 | try {
16 | socket.setKeepAlive(true, 5000);
17 | } catch (x) {
18 | // intentionally empty
19 | }
20 | this.ws.once('close', this.abort);
21 | this.ws.on('message', (m) => this.didMessage(m.data));
22 | this.heartbeatTimeout = this.heartbeatTimeout.bind(this);
23 | }
24 |
25 | tearDown() {
26 | if (this.ws) {
27 | this.ws.removeEventListener('close', this.abort);
28 | }
29 | super.tearDown();
30 | }
31 |
32 | didMessage(payload) {
33 | debug('message');
34 | if (this.ws && this.session && payload.length > 0) {
35 | let message;
36 | try {
37 | message = JSON.parse(payload);
38 | } catch (x) {
39 | return this.close(3000, 'Broken framing.');
40 | }
41 | if (payload[0] === '[') {
42 | message.forEach((msg) => this.session.didMessage(msg));
43 | } else {
44 | this.session.didMessage(message);
45 | }
46 | }
47 | }
48 |
49 | sendFrame(payload) {
50 | debug('send');
51 | if (this.ws) {
52 | try {
53 | this.ws.send(payload);
54 | return true;
55 | } catch (x) {
56 | // intentionally empty
57 | }
58 | }
59 | return false;
60 | }
61 |
62 | close(status = 1000, reason = 'Normal closure') {
63 | super.close(status, reason);
64 | if (this.ws) {
65 | try {
66 | this.ws.close(status, reason, false);
67 | } catch (x) {
68 | // intentionally empty
69 | }
70 | }
71 | this.ws = null;
72 | }
73 |
74 | heartbeat() {
75 | const supportsHeartbeats = this.ws.ping(null, () => clearTimeout(this.hto_ref));
76 |
77 | if (supportsHeartbeats) {
78 | this.hto_ref = setTimeout(this.heartbeatTimeout, 10000);
79 | } else {
80 | super.heartbeat();
81 | }
82 | }
83 |
84 | heartbeatTimeout() {
85 | if (this.session) {
86 | this.session.close(3000, 'No response from heartbeat');
87 | }
88 | }
89 | }
90 |
91 | function sockjs_websocket(req, socket, head, next) {
92 | const ws = new FayeWebsocket(req, socket, head, null, this.options.faye_server_options);
93 | ws.once('open', () => {
94 | // websockets possess no session_id
95 | Session.registerNoSession(req, this, new WebSocketReceiver(ws, socket));
96 | });
97 | next();
98 | }
99 |
100 | module.exports = {
101 | routes: [
102 | {
103 | method: 'GET',
104 | path: '/websocket',
105 | handlers: [middleware.websocket_check, sockjs_websocket],
106 | transport: true
107 | }
108 | ]
109 | };
110 |
--------------------------------------------------------------------------------
/lib/transport/xhr-polling.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ResponseReceiver = require('./response-receiver');
4 | const Session = require('../session');
5 | const middleware = require('../middleware');
6 | const xhr = require('./xhr');
7 |
8 | class XhrPollingReceiver extends ResponseReceiver {
9 | constructor(req, res, options) {
10 | super(req, res, options);
11 | this.protocol = 'xhr-polling';
12 | this.max_response_size = 1;
13 | }
14 |
15 | sendFrame(payload) {
16 | return super.sendFrame(payload + '\n');
17 | }
18 | }
19 |
20 | function xhr_poll(req, res, _head, next) {
21 | res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
22 | res.writeHead(200);
23 |
24 | Session.register(req, this, new XhrPollingReceiver(req, res, this.options));
25 | next();
26 | }
27 |
28 | module.exports = {
29 | routes: [
30 | {
31 | method: 'POST',
32 | path: '/xhr',
33 | handlers: [middleware.h_sid, middleware.h_no_cache, middleware.xhr_cors, xhr_poll],
34 | transport: true
35 | },
36 | {
37 | method: 'OPTIONS',
38 | path: '/xhr',
39 | handlers: [middleware.h_sid, middleware.xhr_cors, middleware.xhr_options],
40 | transport: true
41 | },
42 | {
43 | method: 'POST',
44 | path: '/xhr_send',
45 | handlers: [
46 | middleware.h_sid,
47 | middleware.h_no_cache,
48 | middleware.xhr_cors,
49 | middleware.expect_xhr,
50 | xhr.xhr_send
51 | ],
52 | transport: true
53 | },
54 | {
55 | method: 'OPTIONS',
56 | path: '/xhr_send',
57 | handlers: [middleware.h_sid, middleware.xhr_cors, middleware.xhr_options],
58 | transport: true
59 | }
60 | ]
61 | };
62 |
--------------------------------------------------------------------------------
/lib/transport/xhr-streaming.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const ResponseReceiver = require('./response-receiver');
4 | const Session = require('../session');
5 | const middleware = require('../middleware');
6 | const xhr = require('./xhr');
7 |
8 | class XhrStreamingReceiver extends ResponseReceiver {
9 | constructor(req, res, options) {
10 | super(req, res, options);
11 | this.protocol = 'xhr-streaming';
12 | }
13 |
14 | sendFrame(payload) {
15 | return super.sendFrame(payload + '\n');
16 | }
17 | }
18 |
19 | function xhr_streaming(req, res, _head, next) {
20 | res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
21 | res.writeHead(200);
22 |
23 | // IE requires 2KB prefix:
24 | // http://blogs.msdn.com/b/ieinternals/archive/2010/04/06/comet-streaming-in-internet-explorer-with-xmlhttprequest-and-xdomainrequest.aspx
25 | res.write(Array(2049).join('h') + '\n');
26 |
27 | Session.register(req, this, new XhrStreamingReceiver(req, res, this.options));
28 | next();
29 | }
30 |
31 | module.exports = {
32 | routes: [
33 | {
34 | method: 'POST',
35 | path: '/xhr_streaming',
36 | handlers: [middleware.h_sid, middleware.h_no_cache, middleware.xhr_cors, xhr_streaming],
37 | transport: true
38 | },
39 | {
40 | method: 'OPTIONS',
41 | path: '/xhr_streaming',
42 | handlers: [middleware.h_sid, middleware.xhr_cors, middleware.xhr_options],
43 | transport: true
44 | },
45 | {
46 | method: 'POST',
47 | path: '/xhr_send',
48 | handlers: [
49 | middleware.h_sid,
50 | middleware.h_no_cache,
51 | middleware.xhr_cors,
52 | middleware.expect_xhr,
53 | xhr.xhr_send
54 | ],
55 | transport: true
56 | },
57 | {
58 | method: 'OPTIONS',
59 | path: '/xhr_send',
60 | handlers: [middleware.h_sid, middleware.xhr_cors, middleware.xhr_options],
61 | transport: true
62 | }
63 | ]
64 | };
65 |
--------------------------------------------------------------------------------
/lib/transport/xhr.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Session = require('../session');
4 |
5 | module.exports = {
6 | xhr_send(req, res, _head, next) {
7 | if (!req.body) {
8 | return next({
9 | status: 500,
10 | message: 'Payload expected.'
11 | });
12 | }
13 | let d;
14 | try {
15 | d = JSON.parse(req.body);
16 | } catch (x) {
17 | return next({
18 | status: 500,
19 | message: 'Broken JSON encoding.'
20 | });
21 | }
22 |
23 | if (!d || d.__proto__.constructor !== Array) {
24 | return next({
25 | status: 500,
26 | message: 'Payload expected.'
27 | });
28 | }
29 | const jsonp = Session.bySessionId(req.session);
30 | if (!jsonp) {
31 | return next({ status: 404 });
32 | }
33 | for (const message of d) {
34 | jsonp.didMessage(message);
35 | }
36 |
37 | // FF assumes that the response is XML.
38 | res.setHeader('Content-Type', 'text/plain; charset=UTF-8');
39 | res.writeHead(204);
40 | res.end();
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const crypto = require('crypto');
3 | const http = require('http');
4 |
5 | // used in case of 'upgrade' requests where res is
6 | // net.Socket instead of http.ServerResponse
7 | module.exports.fake_response = function fake_response(req, res) {
8 | // This is quite simplistic, don't expect much.
9 | const headers = { Connection: 'close' };
10 | res.writeHead = function (status, user_headers = {}) {
11 | let r = [];
12 | r.push(`HTTP/${req.httpVersion} ${status} ${http.STATUS_CODES[status]}`);
13 | Object.assign(headers, user_headers);
14 | for (const k in headers) {
15 | r.push(`${k}: ${headers[k]}`);
16 | }
17 | r.push('');
18 | r.push('');
19 | try {
20 | res.write(r.join('\r\n'));
21 | } catch (x) {
22 | // intentionally empty
23 | }
24 | };
25 | res.setHeader = (k, v) => (headers[k] = v);
26 | };
27 |
28 | module.exports.escape_selected = function escape_selected(str, chars) {
29 | const map = {};
30 | chars = `%${chars}`;
31 | Array.prototype.forEach.call(chars, (c) => (map[c] = escape(c)));
32 | const r = new RegExp(`([${chars}])`);
33 | const parts = str.split(r);
34 | parts.forEach((v, i) => {
35 | if (v.length === 1 && v in map) {
36 | parts[i] = map[v];
37 | }
38 | });
39 | return parts.join('');
40 | };
41 |
42 | module.exports.md5_hex = function md5_hex(data) {
43 | return crypto.createHash('md5').update(data).digest('hex');
44 | };
45 |
46 | // eslint-disable-next-line no-control-regex
47 | const escapable = /[\x00-\x1f\ud800-\udfff\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufff0-\uffff]/g;
48 |
49 | function unroll_lookup(escapable) {
50 | const unrolled = {};
51 | const c = Array.from(Array(65536).keys()).map((i) => String.fromCharCode(i));
52 | escapable.lastIndex = 0;
53 | c.join('').replace(escapable, (a) => {
54 | unrolled[a] = `\\u${`0000${a.charCodeAt(0).toString(16)}`.slice(-4)}`;
55 | });
56 | return unrolled;
57 | }
58 |
59 | const lookup = unroll_lookup(escapable);
60 |
61 | module.exports.quote = function quote(string) {
62 | const quoted = JSON.stringify(string);
63 |
64 | // In most cases normal json encoding fast and enough
65 | escapable.lastIndex = 0;
66 | if (!escapable.test(quoted)) {
67 | return quoted;
68 | }
69 |
70 | return quoted.replace(escapable, (a) => lookup[a]);
71 | };
72 |
73 | module.exports.parseCookie = function parseCookie(cookie_header) {
74 | const cookies = {};
75 | if (cookie_header) {
76 | cookie_header.split(';').forEach((cookie) => {
77 | const [name, value] = cookie.split('=');
78 | cookies[name.trim()] = (value || '').trim();
79 | });
80 | }
81 | return cookies;
82 | };
83 |
84 | module.exports.random32 = function random32() {
85 | return crypto.randomBytes(4).readUInt32LE(0);
86 | };
87 |
88 | module.exports.getBody = function getBody(req, cb) {
89 | let body = [];
90 | req.on('data', (d) => {
91 | body.push(d);
92 | });
93 | req.once('end', () => {
94 | cb(null, Buffer.concat(body).toString('utf8'));
95 | });
96 | req.once('error', cb);
97 | req.once('close', () => {
98 | body = null;
99 | });
100 | };
101 |
--------------------------------------------------------------------------------
/lib/webjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:webjs');
4 | const url = require('url');
5 | const utils = require('./utils');
6 | const handlers = require('./handlers');
7 | const middleware = require('./middleware');
8 |
9 | function execute_async_request(server, funs, req, res, head) {
10 | function next(err) {
11 | if (err) {
12 | if (err.status) {
13 | const handlerName = `handle_${err.status}`;
14 | if (handlers[handlerName]) {
15 | return handlers[handlerName].call(server, req, res, err);
16 | }
17 | }
18 | return handlers.handle_error.call(server, err, req, res);
19 | }
20 | if (!funs.length) {
21 | return;
22 | }
23 | const fun = funs.shift();
24 | debug('call', fun);
25 | fun.call(server, req, res, head, next);
26 | }
27 | next();
28 | }
29 |
30 | module.exports.generateHandler = function generateHandler(server, dispatcher) {
31 | return function (req, res, head) {
32 | if (res.writeHead === undefined) {
33 | utils.fake_response(req, res);
34 | }
35 | const parsedUrl = url.parse(req.url, true);
36 | req.pathname = parsedUrl.pathname || '';
37 | req.query = parsedUrl.query;
38 | req.start_date = Date.now();
39 |
40 | let found = false;
41 | const allowed_methods = [];
42 | for (const row of dispatcher) {
43 | let [method, path, funs] = row;
44 | if (!Array.isArray(path)) {
45 | path = [path];
46 | }
47 | // path[0] must be a regexp
48 | const m = req.pathname.match(path[0]);
49 | if (!m) {
50 | continue;
51 | }
52 | if (req.method !== method) {
53 | allowed_methods.push(method);
54 | continue;
55 | }
56 | for (let i = 1; i < path.length; i++) {
57 | req[path[i]] = m[i];
58 | }
59 | funs = funs.slice(0);
60 | funs.push(middleware.log_request);
61 | execute_async_request(server, funs, req, res, head);
62 | found = true;
63 | break;
64 | }
65 |
66 | if (!found) {
67 | if (allowed_methods.length !== 0) {
68 | handlers.handle_405.call(server, req, res, allowed_methods);
69 | } else {
70 | handlers.handle_404.call(server, req, res);
71 | }
72 | middleware.log_request.call(server, req, res, true, () => {});
73 | }
74 | };
75 | };
76 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs",
3 | "version": "0.4.0-rc.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.10.1",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
10 | "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.10.1"
14 | }
15 | },
16 | "@babel/helper-validator-identifier": {
17 | "version": "7.10.1",
18 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
19 | "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
20 | "dev": true
21 | },
22 | "@babel/highlight": {
23 | "version": "7.10.1",
24 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
25 | "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
26 | "dev": true,
27 | "requires": {
28 | "@babel/helper-validator-identifier": "^7.10.1",
29 | "chalk": "^2.0.0",
30 | "js-tokens": "^4.0.0"
31 | },
32 | "dependencies": {
33 | "chalk": {
34 | "version": "2.4.2",
35 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
36 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
37 | "dev": true,
38 | "requires": {
39 | "ansi-styles": "^3.2.1",
40 | "escape-string-regexp": "^1.0.5",
41 | "supports-color": "^5.3.0"
42 | }
43 | }
44 | }
45 | },
46 | "@types/color-name": {
47 | "version": "1.1.1",
48 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
49 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
50 | "dev": true
51 | },
52 | "acorn": {
53 | "version": "7.2.0",
54 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz",
55 | "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==",
56 | "dev": true
57 | },
58 | "acorn-jsx": {
59 | "version": "5.2.0",
60 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
61 | "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
62 | "dev": true
63 | },
64 | "ajv": {
65 | "version": "6.12.6",
66 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
67 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
68 | "dev": true,
69 | "requires": {
70 | "fast-deep-equal": "^3.1.1",
71 | "fast-json-stable-stringify": "^2.0.0",
72 | "json-schema-traverse": "^0.4.1",
73 | "uri-js": "^4.2.2"
74 | }
75 | },
76 | "ansi-escapes": {
77 | "version": "4.3.1",
78 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
79 | "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
80 | "dev": true,
81 | "requires": {
82 | "type-fest": "^0.11.0"
83 | },
84 | "dependencies": {
85 | "type-fest": {
86 | "version": "0.11.0",
87 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
88 | "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==",
89 | "dev": true
90 | }
91 | }
92 | },
93 | "ansi-regex": {
94 | "version": "5.0.1",
95 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
96 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
97 | "dev": true
98 | },
99 | "ansi-styles": {
100 | "version": "3.2.1",
101 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
102 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
103 | "dev": true,
104 | "requires": {
105 | "color-convert": "^1.9.0"
106 | }
107 | },
108 | "argparse": {
109 | "version": "1.0.10",
110 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
111 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
112 | "dev": true,
113 | "requires": {
114 | "sprintf-js": "~1.0.2"
115 | }
116 | },
117 | "astral-regex": {
118 | "version": "1.0.0",
119 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
120 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
121 | "dev": true
122 | },
123 | "balanced-match": {
124 | "version": "1.0.0",
125 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
126 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
127 | "dev": true
128 | },
129 | "brace-expansion": {
130 | "version": "1.1.11",
131 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
132 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
133 | "dev": true,
134 | "requires": {
135 | "balanced-match": "^1.0.0",
136 | "concat-map": "0.0.1"
137 | }
138 | },
139 | "callsites": {
140 | "version": "3.1.0",
141 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
142 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
143 | "dev": true
144 | },
145 | "chalk": {
146 | "version": "4.0.0",
147 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz",
148 | "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==",
149 | "dev": true,
150 | "requires": {
151 | "ansi-styles": "^4.1.0",
152 | "supports-color": "^7.1.0"
153 | },
154 | "dependencies": {
155 | "ansi-styles": {
156 | "version": "4.2.1",
157 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
158 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
159 | "dev": true,
160 | "requires": {
161 | "@types/color-name": "^1.1.1",
162 | "color-convert": "^2.0.1"
163 | }
164 | },
165 | "color-convert": {
166 | "version": "2.0.1",
167 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
168 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
169 | "dev": true,
170 | "requires": {
171 | "color-name": "~1.1.4"
172 | }
173 | },
174 | "color-name": {
175 | "version": "1.1.4",
176 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
177 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
178 | "dev": true
179 | },
180 | "has-flag": {
181 | "version": "4.0.0",
182 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
183 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
184 | "dev": true
185 | },
186 | "supports-color": {
187 | "version": "7.1.0",
188 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
189 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
190 | "dev": true,
191 | "requires": {
192 | "has-flag": "^4.0.0"
193 | }
194 | }
195 | }
196 | },
197 | "chardet": {
198 | "version": "0.7.0",
199 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
200 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
201 | "dev": true
202 | },
203 | "cli-cursor": {
204 | "version": "3.1.0",
205 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
206 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
207 | "dev": true,
208 | "requires": {
209 | "restore-cursor": "^3.1.0"
210 | }
211 | },
212 | "cli-width": {
213 | "version": "2.2.1",
214 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
215 | "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
216 | "dev": true
217 | },
218 | "color-convert": {
219 | "version": "1.9.3",
220 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
221 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
222 | "dev": true,
223 | "requires": {
224 | "color-name": "1.1.3"
225 | }
226 | },
227 | "color-name": {
228 | "version": "1.1.3",
229 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
230 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
231 | "dev": true
232 | },
233 | "concat-map": {
234 | "version": "0.0.1",
235 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
236 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
237 | "dev": true
238 | },
239 | "cross-spawn": {
240 | "version": "7.0.3",
241 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
242 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
243 | "dev": true,
244 | "requires": {
245 | "path-key": "^3.1.0",
246 | "shebang-command": "^2.0.0",
247 | "which": "^2.0.1"
248 | }
249 | },
250 | "debug": {
251 | "version": "4.3.1",
252 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
253 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
254 | "requires": {
255 | "ms": "2.1.2"
256 | }
257 | },
258 | "deep-is": {
259 | "version": "0.1.3",
260 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
261 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
262 | "dev": true
263 | },
264 | "doctrine": {
265 | "version": "3.0.0",
266 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
267 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
268 | "dev": true,
269 | "requires": {
270 | "esutils": "^2.0.2"
271 | }
272 | },
273 | "emoji-regex": {
274 | "version": "8.0.0",
275 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
276 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
277 | "dev": true
278 | },
279 | "escape-string-regexp": {
280 | "version": "1.0.5",
281 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
282 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
283 | "dev": true
284 | },
285 | "eslint": {
286 | "version": "7.2.0",
287 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.2.0.tgz",
288 | "integrity": "sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ==",
289 | "dev": true,
290 | "requires": {
291 | "@babel/code-frame": "^7.0.0",
292 | "ajv": "^6.10.0",
293 | "chalk": "^4.0.0",
294 | "cross-spawn": "^7.0.2",
295 | "debug": "^4.0.1",
296 | "doctrine": "^3.0.0",
297 | "eslint-scope": "^5.1.0",
298 | "eslint-utils": "^2.0.0",
299 | "eslint-visitor-keys": "^1.2.0",
300 | "espree": "^7.1.0",
301 | "esquery": "^1.2.0",
302 | "esutils": "^2.0.2",
303 | "file-entry-cache": "^5.0.1",
304 | "functional-red-black-tree": "^1.0.1",
305 | "glob-parent": "^5.0.0",
306 | "globals": "^12.1.0",
307 | "ignore": "^4.0.6",
308 | "import-fresh": "^3.0.0",
309 | "imurmurhash": "^0.1.4",
310 | "inquirer": "^7.0.0",
311 | "is-glob": "^4.0.0",
312 | "js-yaml": "^3.13.1",
313 | "json-stable-stringify-without-jsonify": "^1.0.1",
314 | "levn": "^0.4.1",
315 | "lodash": "^4.17.14",
316 | "minimatch": "^3.0.4",
317 | "natural-compare": "^1.4.0",
318 | "optionator": "^0.9.1",
319 | "progress": "^2.0.0",
320 | "regexpp": "^3.1.0",
321 | "semver": "^7.2.1",
322 | "strip-ansi": "^6.0.0",
323 | "strip-json-comments": "^3.1.0",
324 | "table": "^5.2.3",
325 | "text-table": "^0.2.0",
326 | "v8-compile-cache": "^2.0.3"
327 | }
328 | },
329 | "eslint-config-prettier": {
330 | "version": "6.11.0",
331 | "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz",
332 | "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==",
333 | "dev": true,
334 | "requires": {
335 | "get-stdin": "^6.0.0"
336 | }
337 | },
338 | "eslint-plugin-prettier": {
339 | "version": "3.1.3",
340 | "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz",
341 | "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==",
342 | "dev": true,
343 | "requires": {
344 | "prettier-linter-helpers": "^1.0.0"
345 | }
346 | },
347 | "eslint-scope": {
348 | "version": "5.1.0",
349 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz",
350 | "integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==",
351 | "dev": true,
352 | "requires": {
353 | "esrecurse": "^4.1.0",
354 | "estraverse": "^4.1.1"
355 | }
356 | },
357 | "eslint-utils": {
358 | "version": "2.0.0",
359 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
360 | "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
361 | "dev": true,
362 | "requires": {
363 | "eslint-visitor-keys": "^1.1.0"
364 | }
365 | },
366 | "eslint-visitor-keys": {
367 | "version": "1.2.0",
368 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz",
369 | "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==",
370 | "dev": true
371 | },
372 | "espree": {
373 | "version": "7.1.0",
374 | "resolved": "https://registry.npmjs.org/espree/-/espree-7.1.0.tgz",
375 | "integrity": "sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw==",
376 | "dev": true,
377 | "requires": {
378 | "acorn": "^7.2.0",
379 | "acorn-jsx": "^5.2.0",
380 | "eslint-visitor-keys": "^1.2.0"
381 | }
382 | },
383 | "esprima": {
384 | "version": "4.0.1",
385 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
386 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
387 | "dev": true
388 | },
389 | "esquery": {
390 | "version": "1.3.1",
391 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
392 | "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
393 | "dev": true,
394 | "requires": {
395 | "estraverse": "^5.1.0"
396 | },
397 | "dependencies": {
398 | "estraverse": {
399 | "version": "5.1.0",
400 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz",
401 | "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==",
402 | "dev": true
403 | }
404 | }
405 | },
406 | "esrecurse": {
407 | "version": "4.2.1",
408 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
409 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
410 | "dev": true,
411 | "requires": {
412 | "estraverse": "^4.1.0"
413 | }
414 | },
415 | "estraverse": {
416 | "version": "4.3.0",
417 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
418 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
419 | "dev": true
420 | },
421 | "esutils": {
422 | "version": "2.0.3",
423 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
424 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
425 | "dev": true
426 | },
427 | "external-editor": {
428 | "version": "3.1.0",
429 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
430 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
431 | "dev": true,
432 | "requires": {
433 | "chardet": "^0.7.0",
434 | "iconv-lite": "^0.4.24",
435 | "tmp": "^0.0.33"
436 | }
437 | },
438 | "fast-deep-equal": {
439 | "version": "3.1.1",
440 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
441 | "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
442 | "dev": true
443 | },
444 | "fast-diff": {
445 | "version": "1.2.0",
446 | "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
447 | "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
448 | "dev": true
449 | },
450 | "fast-json-stable-stringify": {
451 | "version": "2.1.0",
452 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
453 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
454 | "dev": true
455 | },
456 | "fast-levenshtein": {
457 | "version": "2.0.6",
458 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
459 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
460 | "dev": true
461 | },
462 | "faye-websocket": {
463 | "version": "0.11.3",
464 | "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz",
465 | "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==",
466 | "requires": {
467 | "websocket-driver": ">=0.5.1"
468 | }
469 | },
470 | "figures": {
471 | "version": "3.2.0",
472 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
473 | "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
474 | "dev": true,
475 | "requires": {
476 | "escape-string-regexp": "^1.0.5"
477 | }
478 | },
479 | "file-entry-cache": {
480 | "version": "5.0.1",
481 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
482 | "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
483 | "dev": true,
484 | "requires": {
485 | "flat-cache": "^2.0.1"
486 | }
487 | },
488 | "flat-cache": {
489 | "version": "2.0.1",
490 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
491 | "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
492 | "dev": true,
493 | "requires": {
494 | "flatted": "^2.0.0",
495 | "rimraf": "2.6.3",
496 | "write": "1.0.3"
497 | }
498 | },
499 | "flatted": {
500 | "version": "2.0.2",
501 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
502 | "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
503 | "dev": true
504 | },
505 | "fs.realpath": {
506 | "version": "1.0.0",
507 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
508 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
509 | "dev": true
510 | },
511 | "functional-red-black-tree": {
512 | "version": "1.0.1",
513 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
514 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
515 | "dev": true
516 | },
517 | "get-stdin": {
518 | "version": "6.0.0",
519 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
520 | "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
521 | "dev": true
522 | },
523 | "glob": {
524 | "version": "7.1.6",
525 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
526 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
527 | "dev": true,
528 | "requires": {
529 | "fs.realpath": "^1.0.0",
530 | "inflight": "^1.0.4",
531 | "inherits": "2",
532 | "minimatch": "^3.0.4",
533 | "once": "^1.3.0",
534 | "path-is-absolute": "^1.0.0"
535 | }
536 | },
537 | "glob-parent": {
538 | "version": "5.1.2",
539 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
540 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
541 | "dev": true,
542 | "requires": {
543 | "is-glob": "^4.0.1"
544 | }
545 | },
546 | "globals": {
547 | "version": "12.4.0",
548 | "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
549 | "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==",
550 | "dev": true,
551 | "requires": {
552 | "type-fest": "^0.8.1"
553 | }
554 | },
555 | "has-flag": {
556 | "version": "3.0.0",
557 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
558 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
559 | "dev": true
560 | },
561 | "http-parser-js": {
562 | "version": "0.5.2",
563 | "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.2.tgz",
564 | "integrity": "sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ=="
565 | },
566 | "iconv-lite": {
567 | "version": "0.4.24",
568 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
569 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
570 | "dev": true,
571 | "requires": {
572 | "safer-buffer": ">= 2.1.2 < 3"
573 | }
574 | },
575 | "ignore": {
576 | "version": "4.0.6",
577 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
578 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
579 | "dev": true
580 | },
581 | "import-fresh": {
582 | "version": "3.2.1",
583 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
584 | "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
585 | "dev": true,
586 | "requires": {
587 | "parent-module": "^1.0.0",
588 | "resolve-from": "^4.0.0"
589 | }
590 | },
591 | "imurmurhash": {
592 | "version": "0.1.4",
593 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
594 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
595 | "dev": true
596 | },
597 | "inflight": {
598 | "version": "1.0.6",
599 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
600 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
601 | "dev": true,
602 | "requires": {
603 | "once": "^1.3.0",
604 | "wrappy": "1"
605 | }
606 | },
607 | "inherits": {
608 | "version": "2.0.4",
609 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
610 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
611 | "dev": true
612 | },
613 | "inquirer": {
614 | "version": "7.1.0",
615 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
616 | "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
617 | "dev": true,
618 | "requires": {
619 | "ansi-escapes": "^4.2.1",
620 | "chalk": "^3.0.0",
621 | "cli-cursor": "^3.1.0",
622 | "cli-width": "^2.0.0",
623 | "external-editor": "^3.0.3",
624 | "figures": "^3.0.0",
625 | "lodash": "^4.17.15",
626 | "mute-stream": "0.0.8",
627 | "run-async": "^2.4.0",
628 | "rxjs": "^6.5.3",
629 | "string-width": "^4.1.0",
630 | "strip-ansi": "^6.0.0",
631 | "through": "^2.3.6"
632 | },
633 | "dependencies": {
634 | "ansi-styles": {
635 | "version": "4.2.1",
636 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
637 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
638 | "dev": true,
639 | "requires": {
640 | "@types/color-name": "^1.1.1",
641 | "color-convert": "^2.0.1"
642 | }
643 | },
644 | "chalk": {
645 | "version": "3.0.0",
646 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
647 | "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
648 | "dev": true,
649 | "requires": {
650 | "ansi-styles": "^4.1.0",
651 | "supports-color": "^7.1.0"
652 | }
653 | },
654 | "color-convert": {
655 | "version": "2.0.1",
656 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
657 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
658 | "dev": true,
659 | "requires": {
660 | "color-name": "~1.1.4"
661 | }
662 | },
663 | "color-name": {
664 | "version": "1.1.4",
665 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
666 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
667 | "dev": true
668 | },
669 | "has-flag": {
670 | "version": "4.0.0",
671 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
672 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
673 | "dev": true
674 | },
675 | "supports-color": {
676 | "version": "7.1.0",
677 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
678 | "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
679 | "dev": true,
680 | "requires": {
681 | "has-flag": "^4.0.0"
682 | }
683 | }
684 | }
685 | },
686 | "is-extglob": {
687 | "version": "2.1.1",
688 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
689 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
690 | "dev": true
691 | },
692 | "is-fullwidth-code-point": {
693 | "version": "3.0.0",
694 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
695 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
696 | "dev": true
697 | },
698 | "is-glob": {
699 | "version": "4.0.1",
700 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
701 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
702 | "dev": true,
703 | "requires": {
704 | "is-extglob": "^2.1.1"
705 | }
706 | },
707 | "isexe": {
708 | "version": "2.0.0",
709 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
710 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
711 | "dev": true
712 | },
713 | "js-tokens": {
714 | "version": "4.0.0",
715 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
716 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
717 | "dev": true
718 | },
719 | "js-yaml": {
720 | "version": "3.14.0",
721 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
722 | "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
723 | "dev": true,
724 | "requires": {
725 | "argparse": "^1.0.7",
726 | "esprima": "^4.0.0"
727 | }
728 | },
729 | "json-schema-traverse": {
730 | "version": "0.4.1",
731 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
732 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
733 | "dev": true
734 | },
735 | "json-stable-stringify-without-jsonify": {
736 | "version": "1.0.1",
737 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
738 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
739 | "dev": true
740 | },
741 | "levn": {
742 | "version": "0.4.1",
743 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
744 | "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
745 | "dev": true,
746 | "requires": {
747 | "prelude-ls": "^1.2.1",
748 | "type-check": "~0.4.0"
749 | }
750 | },
751 | "lodash": {
752 | "version": "4.17.21",
753 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
754 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
755 | "dev": true
756 | },
757 | "mimic-fn": {
758 | "version": "2.1.0",
759 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
760 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
761 | "dev": true
762 | },
763 | "minimatch": {
764 | "version": "3.0.4",
765 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
766 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
767 | "dev": true,
768 | "requires": {
769 | "brace-expansion": "^1.1.7"
770 | }
771 | },
772 | "minimist": {
773 | "version": "1.2.8",
774 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
775 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
776 | "dev": true
777 | },
778 | "mkdirp": {
779 | "version": "0.5.5",
780 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
781 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
782 | "dev": true,
783 | "requires": {
784 | "minimist": "^1.2.5"
785 | }
786 | },
787 | "ms": {
788 | "version": "2.1.2",
789 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
790 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
791 | },
792 | "mute-stream": {
793 | "version": "0.0.8",
794 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
795 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
796 | "dev": true
797 | },
798 | "natural-compare": {
799 | "version": "1.4.0",
800 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
801 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
802 | "dev": true
803 | },
804 | "once": {
805 | "version": "1.4.0",
806 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
807 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
808 | "dev": true,
809 | "requires": {
810 | "wrappy": "1"
811 | }
812 | },
813 | "onetime": {
814 | "version": "5.1.0",
815 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
816 | "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
817 | "dev": true,
818 | "requires": {
819 | "mimic-fn": "^2.1.0"
820 | }
821 | },
822 | "optionator": {
823 | "version": "0.9.1",
824 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
825 | "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
826 | "dev": true,
827 | "requires": {
828 | "deep-is": "^0.1.3",
829 | "fast-levenshtein": "^2.0.6",
830 | "levn": "^0.4.1",
831 | "prelude-ls": "^1.2.1",
832 | "type-check": "^0.4.0",
833 | "word-wrap": "^1.2.3"
834 | }
835 | },
836 | "os-tmpdir": {
837 | "version": "1.0.2",
838 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
839 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
840 | "dev": true
841 | },
842 | "parent-module": {
843 | "version": "1.0.1",
844 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
845 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
846 | "dev": true,
847 | "requires": {
848 | "callsites": "^3.0.0"
849 | }
850 | },
851 | "path-is-absolute": {
852 | "version": "1.0.1",
853 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
854 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
855 | "dev": true
856 | },
857 | "path-key": {
858 | "version": "3.1.1",
859 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
860 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
861 | "dev": true
862 | },
863 | "prelude-ls": {
864 | "version": "1.2.1",
865 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
866 | "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
867 | "dev": true
868 | },
869 | "prettier": {
870 | "version": "2.0.5",
871 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz",
872 | "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==",
873 | "dev": true
874 | },
875 | "prettier-linter-helpers": {
876 | "version": "1.0.0",
877 | "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
878 | "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
879 | "dev": true,
880 | "requires": {
881 | "fast-diff": "^1.1.2"
882 | }
883 | },
884 | "progress": {
885 | "version": "2.0.3",
886 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
887 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
888 | "dev": true
889 | },
890 | "punycode": {
891 | "version": "2.1.1",
892 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
893 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
894 | "dev": true
895 | },
896 | "regexpp": {
897 | "version": "3.1.0",
898 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
899 | "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
900 | "dev": true
901 | },
902 | "resolve-from": {
903 | "version": "4.0.0",
904 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
905 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
906 | "dev": true
907 | },
908 | "restore-cursor": {
909 | "version": "3.1.0",
910 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
911 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
912 | "dev": true,
913 | "requires": {
914 | "onetime": "^5.1.0",
915 | "signal-exit": "^3.0.2"
916 | }
917 | },
918 | "rimraf": {
919 | "version": "2.6.3",
920 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
921 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
922 | "dev": true,
923 | "requires": {
924 | "glob": "^7.1.3"
925 | }
926 | },
927 | "run-async": {
928 | "version": "2.4.1",
929 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
930 | "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
931 | "dev": true
932 | },
933 | "rxjs": {
934 | "version": "6.5.5",
935 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
936 | "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
937 | "dev": true,
938 | "requires": {
939 | "tslib": "^1.9.0"
940 | }
941 | },
942 | "safe-buffer": {
943 | "version": "5.2.1",
944 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
945 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
946 | },
947 | "safer-buffer": {
948 | "version": "2.1.2",
949 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
950 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
951 | "dev": true
952 | },
953 | "semver": {
954 | "version": "7.3.2",
955 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
956 | "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
957 | "dev": true
958 | },
959 | "shebang-command": {
960 | "version": "2.0.0",
961 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
962 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
963 | "dev": true,
964 | "requires": {
965 | "shebang-regex": "^3.0.0"
966 | }
967 | },
968 | "shebang-regex": {
969 | "version": "3.0.0",
970 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
971 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
972 | "dev": true
973 | },
974 | "signal-exit": {
975 | "version": "3.0.3",
976 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
977 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
978 | "dev": true
979 | },
980 | "slice-ansi": {
981 | "version": "2.1.0",
982 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
983 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
984 | "dev": true,
985 | "requires": {
986 | "ansi-styles": "^3.2.0",
987 | "astral-regex": "^1.0.0",
988 | "is-fullwidth-code-point": "^2.0.0"
989 | },
990 | "dependencies": {
991 | "is-fullwidth-code-point": {
992 | "version": "2.0.0",
993 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
994 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
995 | "dev": true
996 | }
997 | }
998 | },
999 | "sprintf-js": {
1000 | "version": "1.0.3",
1001 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1002 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1003 | "dev": true
1004 | },
1005 | "string-width": {
1006 | "version": "4.2.0",
1007 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
1008 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
1009 | "dev": true,
1010 | "requires": {
1011 | "emoji-regex": "^8.0.0",
1012 | "is-fullwidth-code-point": "^3.0.0",
1013 | "strip-ansi": "^6.0.0"
1014 | }
1015 | },
1016 | "strip-ansi": {
1017 | "version": "6.0.0",
1018 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
1019 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
1020 | "dev": true,
1021 | "requires": {
1022 | "ansi-regex": "^5.0.0"
1023 | }
1024 | },
1025 | "strip-json-comments": {
1026 | "version": "3.1.0",
1027 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz",
1028 | "integrity": "sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==",
1029 | "dev": true
1030 | },
1031 | "supports-color": {
1032 | "version": "5.5.0",
1033 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1034 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1035 | "dev": true,
1036 | "requires": {
1037 | "has-flag": "^3.0.0"
1038 | }
1039 | },
1040 | "table": {
1041 | "version": "5.4.6",
1042 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
1043 | "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
1044 | "dev": true,
1045 | "requires": {
1046 | "ajv": "^6.10.2",
1047 | "lodash": "^4.17.14",
1048 | "slice-ansi": "^2.1.0",
1049 | "string-width": "^3.0.0"
1050 | },
1051 | "dependencies": {
1052 | "ansi-regex": {
1053 | "version": "4.1.1",
1054 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
1055 | "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
1056 | "dev": true
1057 | },
1058 | "emoji-regex": {
1059 | "version": "7.0.3",
1060 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
1061 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
1062 | "dev": true
1063 | },
1064 | "is-fullwidth-code-point": {
1065 | "version": "2.0.0",
1066 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
1067 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
1068 | "dev": true
1069 | },
1070 | "string-width": {
1071 | "version": "3.1.0",
1072 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
1073 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
1074 | "dev": true,
1075 | "requires": {
1076 | "emoji-regex": "^7.0.1",
1077 | "is-fullwidth-code-point": "^2.0.0",
1078 | "strip-ansi": "^5.1.0"
1079 | }
1080 | },
1081 | "strip-ansi": {
1082 | "version": "5.2.0",
1083 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
1084 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
1085 | "dev": true,
1086 | "requires": {
1087 | "ansi-regex": "^4.1.0"
1088 | }
1089 | }
1090 | }
1091 | },
1092 | "text-table": {
1093 | "version": "0.2.0",
1094 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
1095 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
1096 | "dev": true
1097 | },
1098 | "through": {
1099 | "version": "2.3.8",
1100 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
1101 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
1102 | "dev": true
1103 | },
1104 | "tmp": {
1105 | "version": "0.0.33",
1106 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
1107 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
1108 | "dev": true,
1109 | "requires": {
1110 | "os-tmpdir": "~1.0.2"
1111 | }
1112 | },
1113 | "tslib": {
1114 | "version": "1.13.0",
1115 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
1116 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==",
1117 | "dev": true
1118 | },
1119 | "type-check": {
1120 | "version": "0.4.0",
1121 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
1122 | "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
1123 | "dev": true,
1124 | "requires": {
1125 | "prelude-ls": "^1.2.1"
1126 | }
1127 | },
1128 | "type-fest": {
1129 | "version": "0.8.1",
1130 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
1131 | "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
1132 | "dev": true
1133 | },
1134 | "uri-js": {
1135 | "version": "4.2.2",
1136 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
1137 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
1138 | "dev": true,
1139 | "requires": {
1140 | "punycode": "^2.1.0"
1141 | }
1142 | },
1143 | "uuid": {
1144 | "version": "8.1.0",
1145 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
1146 | "integrity": "sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg=="
1147 | },
1148 | "v8-compile-cache": {
1149 | "version": "2.1.1",
1150 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
1151 | "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
1152 | "dev": true
1153 | },
1154 | "websocket-driver": {
1155 | "version": "0.7.4",
1156 | "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
1157 | "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
1158 | "requires": {
1159 | "http-parser-js": ">=0.5.1",
1160 | "safe-buffer": ">=5.1.0",
1161 | "websocket-extensions": ">=0.1.1"
1162 | }
1163 | },
1164 | "websocket-extensions": {
1165 | "version": "0.1.4",
1166 | "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
1167 | "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
1168 | },
1169 | "which": {
1170 | "version": "2.0.2",
1171 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1172 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1173 | "dev": true,
1174 | "requires": {
1175 | "isexe": "^2.0.0"
1176 | }
1177 | },
1178 | "word-wrap": {
1179 | "version": "1.2.4",
1180 | "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
1181 | "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
1182 | "dev": true
1183 | },
1184 | "wrappy": {
1185 | "version": "1.0.2",
1186 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1187 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1188 | "dev": true
1189 | },
1190 | "write": {
1191 | "version": "1.0.3",
1192 | "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
1193 | "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
1194 | "dev": true,
1195 | "requires": {
1196 | "mkdirp": "^0.5.1"
1197 | }
1198 | }
1199 | }
1200 | }
1201 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sockjs",
3 | "description": "SockJS-node is a server counterpart of SockJS-client a JavaScript library that provides a WebSocket-like object in the browser. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server.",
4 | "version": "0.4.0-rc.1",
5 | "author": "Bryce Kahle",
6 | "bugs": {
7 | "url": "https://github.com/sockjs/sockjs-node/issues"
8 | },
9 | "contributors": [
10 | {
11 | "name": "Bryce Kahle",
12 | "email": "bkahle@gmail.com"
13 | },
14 | {
15 | "name": "Marek Majkowski",
16 | "email": "deadbeef@popcount.org"
17 | }
18 | ],
19 | "dependencies": {
20 | "debug": "^4.3.1",
21 | "faye-websocket": "^0.11.3",
22 | "uuid": "^8.1.0"
23 | },
24 | "devDependencies": {
25 | "eslint": "^7.2.0",
26 | "eslint-config-prettier": "^6.11.0",
27 | "eslint-plugin-prettier": "^3.1.3",
28 | "prettier": "^2.0.5"
29 | },
30 | "homepage": "https://github.com/sockjs/sockjs-node",
31 | "keywords": [
32 | "websockets",
33 | "websocket"
34 | ],
35 | "license": "MIT",
36 | "main": "index.js",
37 | "repository": {
38 | "type": "git",
39 | "url": "https://github.com/sockjs/sockjs-node.git"
40 | },
41 | "scripts": {
42 | "lint": "eslint .",
43 | "fmt": "prettier --write '{lib,examples,tests}/**/*.js'",
44 | "version": "git add CHANGELOG.MD",
45 | "postversion": "npm publish",
46 | "postpublish": "git push origin --all && git push origin --tags"
47 | },
48 | "engines": {
49 | "node": ">=6.5.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/scripts/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | rm -rf sockjs-protocol
6 | git clone --depth=1 https://github.com/sockjs/sockjs-protocol.git
7 | cd sockjs-protocol
8 | make test_deps pycco_deps
9 | cd ..
10 | node tests/test_server/server.js &
11 | SRVPID=$!
12 | sleep 1
13 |
14 | set +e
15 |
16 | cd sockjs-protocol
17 | ./venv/bin/python sockjs-protocol.py
18 | PASSED=$?
19 | kill $SRVPID
20 | exit $PASSED
21 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": 0
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/tests/test_server/README.md:
--------------------------------------------------------------------------------
1 | SockJS test server
2 | ==================
3 |
4 | In order to test sockjs server implementation the server needs to
5 | provide a standarized sockjs endpoint that will can used by various
6 | sockjs-related tests. For example by QUnit or
7 | [Sockjs-protocol](https://github.com/sockjs/sockjs-protocol) tests.
8 |
9 | This small code does exactly that - runs a simple server that supports
10 | the following SockJS services:
11 |
12 | * `/echo`
13 | * `/disabled_websocket_echo`
14 | * `/cookie_needed_echo`
15 | * `/close`
16 | * `/ticker`
17 | * `/amplify`
18 | * `/broadcast`
19 |
20 | If you just want to quickly run it:
21 |
22 | node server.js
23 |
--------------------------------------------------------------------------------
/tests/test_server/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const debug = require('debug')('sockjs:test-server:app');
4 |
5 | exports.config = {
6 | server_opts: {
7 | sockjs_url: 'http://localhost:8080/lib/sockjs.js',
8 | websocket: true,
9 | log: (x, ...rest) => debug(`[${x}]`, ...rest)
10 | },
11 |
12 | port: 8081
13 | };
14 |
--------------------------------------------------------------------------------
/tests/test_server/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const http = require('http');
3 | const config = require('./config').config;
4 | const sockjs_app = require('./sockjs_app');
5 |
6 | const server = http.createServer();
7 | server.addListener('request', function (req, res) {
8 | res.setHeader('Content-Type', 'text/plain');
9 | res.writeHead(404);
10 | res.end('404 - Nothing here (via sockjs-node test_server)');
11 | });
12 | server.addListener('upgrade', function (req, res) {
13 | res.end();
14 | });
15 |
16 | sockjs_app.install(config.server_opts, server);
17 |
18 | server.listen(config.port, config.host, () => {
19 | const addr = server.address();
20 | console.log(` [*] Listening on ${addr.address}:${addr.port}`);
21 | });
22 |
--------------------------------------------------------------------------------
/tests/test_server/sockjs_app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const sockjs = require('../../index');
3 | const debug = require('debug')('sockjs:test-server:app');
4 |
5 | exports.install = function (opts, server) {
6 | const echoHandler = function (conn) {
7 | debug(` [+] echo open ${conn}`);
8 | conn.on('close', function () {
9 | debug(` [-] echo close ${conn}`);
10 | });
11 | conn.on('data', function (m) {
12 | const d = JSON.stringify(m);
13 | debug(` [ ] echo message ${conn} ${d.slice(0, 64)}${d.length > 64 ? '...' : ''}`);
14 | conn.write(m);
15 | });
16 | };
17 |
18 | const sjs_echo = sockjs.createServer(
19 | Object.assign({}, opts, { prefix: '/echo', response_limit: 4096 })
20 | );
21 | sjs_echo.on('connection', echoHandler);
22 | sjs_echo.attach(server);
23 |
24 | const sjs_echo2 = sockjs.createServer(
25 | Object.assign({}, opts, {
26 | prefix: '/disabled_websocket_echo',
27 | websocket: false
28 | })
29 | );
30 | sjs_echo2.on('connection', echoHandler);
31 | sjs_echo2.attach(server);
32 |
33 | const sjs_echo3 = sockjs.createServer(
34 | Object.assign({}, opts, { prefix: '/cookie_needed_echo', jsessionid: true })
35 | );
36 | sjs_echo3.on('connection', echoHandler);
37 | sjs_echo3.attach(server);
38 |
39 | const sjs_close = sockjs.createServer(Object.assign({}, opts, { prefix: '/close' }));
40 | sjs_close.on('connection', function (conn) {
41 | debug(` [+] close open ${conn}`);
42 | conn.close(3000, 'Go away!');
43 | conn.on('close', function () {
44 | debug(` [-] close close ${conn}`);
45 | });
46 | });
47 | sjs_close.attach(server);
48 |
49 | const sjs_ticker = sockjs.createServer(Object.assign({}, opts, { prefix: '/ticker' }));
50 | sjs_ticker.on('connection', function (conn) {
51 | debug(` [+] ticker open ${conn}`);
52 | let tref;
53 | const schedule = function () {
54 | conn.write('tick!');
55 | tref = setTimeout(schedule, 1000);
56 | };
57 | tref = setTimeout(schedule, 1000);
58 | conn.on('close', function () {
59 | clearTimeout(tref);
60 | debug(` [-] ticker close ${conn}`);
61 | });
62 | });
63 | sjs_ticker.attach(server);
64 |
65 | const broadcast = {};
66 | const sjs_broadcast = sockjs.createServer(Object.assign({}, opts, { prefix: '/broadcast' }));
67 | sjs_broadcast.on('connection', function (conn) {
68 | debug(` [+] broadcast open ${conn}`);
69 | broadcast[conn.id] = conn;
70 | conn.on('close', function () {
71 | delete broadcast[conn.id];
72 | debug(` [-] broadcast close${conn}`);
73 | });
74 | conn.on('data', function (m) {
75 | debug(` [-] broadcast message ${m}`);
76 | for (const id in broadcast) {
77 | broadcast[id].write(m);
78 | }
79 | });
80 | });
81 | sjs_broadcast.attach(server);
82 |
83 | const sjs_amplify = sockjs.createServer(Object.assign({}, opts, { prefix: '/amplify' }));
84 | sjs_amplify.on('connection', function (conn) {
85 | debug(` [+] amp open ${conn}`);
86 | conn.on('close', function () {
87 | debug(` [-] amp close ${conn}`);
88 | });
89 | conn.on('data', function (m) {
90 | let n = Math.floor(Number(m));
91 | n = n > 0 && n < 19 ? n : 1;
92 | debug(` [ ] amp message: 2^${n}`);
93 | conn.write(new Array(Math.pow(2, n) + 1).join('x'));
94 | });
95 | });
96 | sjs_amplify.attach(server);
97 | };
98 |
--------------------------------------------------------------------------------