diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9d0b1cd927..03303efc53 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,9 @@ -*Note*: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com) +**Note**: for support questions, please use one of these channels: [stackoverflow](http://stackoverflow.com/questions/tagged/socket.io) or [slack](https://socketio.slack.com) + +For bug reports and feature requests for the **Swift client**, please open an issue [there](https://github.com/socketio/socket.io-client-swift). + +For bug reports and feature requests for the **Java client**, please open an issue [there](https://github.com/socketio/socket.io-client-java). ### You want to: @@ -8,13 +12,15 @@ ### Current behaviour +*What is actually happening?* ### Steps to reproduce (if the current behaviour is a bug) -**Note**: the best way to get a quick answer is to provide a failing test case, by forking the following [fiddle](https://github.com/darrachequesne/socket.io-fiddle) for example. +**Note**: the best way (and by that we mean **the only way**) to get a quick answer is to provide a failing test case by forking the following [fiddle](https://github.com/socketio/socket.io-fiddle). ### Expected behaviour +*What is expected?* ### Setup - OS: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e225759ce4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test-node: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x, 14.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test + env: + CI: true diff --git a/.gitignore b/.gitignore index 32d7d3bbd4..7e78cb2147 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules coverage .idea dist +.nyc_output \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 44ab05c343..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -sudo: false -language: node_js -node_js: - - "4" - - "6" - - "7" - -git: - depth: 1 - -notifications: - irc: "irc.freenode.org#socket.io" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..29267b4e50 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,27 @@ +# [2.5.0](https://github.com/socketio/socket.io/compare/2.4.1...2.5.0) (2022-06-26) + + +### Bug Fixes + +* fix race condition in dynamic namespaces ([05e1278](https://github.com/socketio/socket.io/commit/05e1278cfa99f3ecf3f8f0531ffe57d850e9a05b)) +* ignore packet received after disconnection ([22d4bdf](https://github.com/socketio/socket.io/commit/22d4bdf00d1a03885dc0171125faddfaef730066)) +* only set 'connected' to true after middleware execution ([226cc16](https://github.com/socketio/socket.io/commit/226cc16165f9fe60f16ff4d295fb91c8971cde35)) +* prevent the socket from joining a room after disconnection ([f223178](https://github.com/socketio/socket.io/commit/f223178eb655a7713303b21a78f9ef9e161d6458)) + + + +## [2.4.1](https://github.com/socketio/socket.io/compare/2.4.0...2.4.1) (2021-01-07) + + +### Reverts + +* fix(security): do not allow all origins by default ([a169050](https://github.com/socketio/socket.io/commit/a1690509470e9dd5559cec4e60908ca6c23e9ba0)) + + +# [2.4.0](https://github.com/socketio/socket.io/compare/2.3.0...2.4.0) (2021-01-04) + + +### Bug Fixes + +* **security:** do not allow all origins by default ([f78a575](https://github.com/socketio/socket.io/commit/f78a575f66ab693c3ea96ea88429ddb1a44c86c7)) +* properly overwrite the query sent in the handshake ([d33a619](https://github.com/socketio/socket.io/commit/d33a619905a4905c153d4fec337c74da5b533a9e)) diff --git a/History.md b/History.md deleted file mode 100644 index e750230464..0000000000 --- a/History.md +++ /dev/null @@ -1,720 +0,0 @@ - -2.0.3 / 2017-06-12 -=================== - - * [fix] Reset rooms object before broadcasting (#2970) - * [fix] Fix middleware initialization (#2969) - * [docs] Update slack badge (#2961) - * [docs] Update webpack example (#2960) - -2.0.2 / 2017-06-01 -=================== - - * [fix] Fix timing issues with middleware (#2948) - -2.0.1 / 2017-05-09 -=================== - - * [fix] Update path of client file (#2934) - -2.0.0 / 2017-05-09 -=================== - - * [feat] Move binary detection to the parser (#2923) - * [feat] Allow to join several rooms at once (#2879) - * [feat] Merge Engine.IO and Socket.IO handshake packets (#2833) - * [feat] Allow the use of custom parsers (#2829) - - * [fix] Use path.resolve by default and require.resolve as a fallback (#2797) - * [fix] Properly close the connection on error (#2681) - * [fix] Prevent null from being accepted as argument (#2606) - - * [perf] Use shared instance of the encoder (#2825) - * [perf] Reset properties instead of deleting them (#2826) - * [perf] micro-optimisations (#2793) - - * [chore] Merge history of 1.7.x and 0.9.x branches (#2930) - * [chore] Added backers and sponsors on the README (#2933) - * [chore] Bump dependencies (#2926) - * [chore] Bump socket.io-adapter to version 1.0.0 (#2867) - * [chore] Bump engine.io to version 2.0.2 (#2864) - * [chore] Bump engine.io to version 2.0.0 (#2832) - * [chore] Update issue template with fiddle (#2811) - * [chore] Update copyright year LICENSE to 2017 (#2803) - - * [docs] Add an example of custom parser (#2929) - * [docs] Replace non-breaking space with proper whitespace (#2913) - * [docs] Update emit cheatsheet (#2906) - * [docs] Explicitly document that Server extends EventEmitter (#2874) - * [docs] Add server.engine.generateId attribute (#2880) - * [docs] Fix wrong space character in README (#2900) - * [docs] Fix documentation for 'connect' event (#2898) - * [docs] Add webpack build example (#2828) - * [docs] Update the wording to match the code example (#2853) - * [docs] Small addition to the Express Readme Part (#2846) - * [docs] Add a 'Features' section in the README (#2824) - * [docs] Add httpd cluster example (#2819) - * [docs] Add haproxy cluster example (#2818) - * [docs] Add nginx cluster example (#2817) - * [docs] Implement whiteboard example (#2810) - * [docs] Fix documentation for `local` flag (#2816) - * [docs] Add emit cheatsheet (#2815) - * [docs] Add pingInterval/pingTimeout/transports options in the API documentation (#2814) - * [docs] Add an example for socket.join() method (#2813) - * [docs] Fix a typo on `clients` method in the API documentation (#2812) - * [docs] Fix wrong argument name in API.md (#2802) - * [docs] Add install script on Readme.md (#2780) - * [docs] API documentation (#2784) - -1.7.4 / 2017-05-07 -=================== - - * [chore] Bump engine.io to version 1.8.4 - -0.9.18 / 2017-05-07 -=================== - - * Remove process.EventEmitter usage for Node 7.x - -1.7.3 / 2017-02-17 -=================== - - * [chore] Bump engine.io to version 1.8.3 - -1.7.2 / 2016-12-11 -=================== - - * [chore] Bump engine.io to version 1.8.2 (#2782) - * [fix] Fixes socket.use error packet (#2772) - -1.7.1 / 2016-11-28 -=================== - -1.7.0 / 2016-11-27 -=================== - - * [docs] Comment connected socket availability for adapters (#2081) - * [docs] Fixed grammar issues in the README.md (#2159) - * [feature] serve sourcemap for socket.io-client (#2482) - * [feature] Add a `local` flag (#2628) - * [chore] Bump engine.io to version 1.8.1 (#2765) - * [chore] Update client location and serve minified file (#2766) - -1.6.0 / 2016-11-20 -================== - - * [fix] Make ETag header comply with standard. (#2603) - * [feature] Loading client script on demand. (#2567) - * [test] Fix leaking clientSocket (#2721) - * [feature] Add support for all event emitter methods (#2601) - * [chore] Update year to 2016 (#2456) - * [feature] Add support for socket middleware (#2306) - * [feature] add support for Server#close(callback) (#2748) - * [fix] Don't drop query variables on handshake (#2745) - * [example] Add disconnection/reconnection logs to the chat example (#2675) - * [perf] Minor code optimizations (#2219) - * [chore] Bump debug to version 2.3.3 (#2754) - * [chore] Bump engine.io to version 1.8.0 (#2755) - * [chore] Bump socket.io-adapter to version 0.5.0 (#2756) - -1.5.1 / 2016-10-24 -================== - - * [fix] Avoid swallowing exceptions thrown by user event handlers (#2682) - * [test] Use client function to unify `client` in test script (#2731) - * [docs] Add link to LICENSE (#2221) - * [docs] Fix JSDoc of optional parameters (#2465) - * [docs] Fix typo (#2724) - * [docs] Link readme npm package badge to npm registry page (#2612) - * [docs] Minor fixes (#2526) - * [chore] Bump socket.io-parser to 2.3.0 (#2730) - * [chore] Add Github issue and PR templates (#2733) - * [chore] Bump engine.io to 1.7.2 (#2729) - * [chore] Bump socket.io-parser to 2.3.1 (#2734) - -1.5.0 / 2016-10-06 -================== - - * [feature] stop append /# before id when no namespace (#2508) - * [feature] Add a 'disconnecting' event to access to socket.rooms upon disconnection (#2332) - * [fix] Fix query string management (#2422) - * [fix] add quote to exec paths, prevent error when spaces in path (#2508) - * [docs] Prevent mixup for new programmers (#2599) - * [example] Fix chat display in Firefox (#2477) - * [chore] Add gulp & babel in the build process (#2471) - * [chore] Bump engine.io to 1.7.0 (#2707) - * [chore] Remove unused zuul-ngrok dependency (#2708) - * [chore] Point towards current master of socket.io-client (#2710) - * [chore] Restrict files included in npm package (#2709) - * [chore] Link build badge to master branch (#2549) - -1.4.8 / 2016-06-23 -================== - - * package: bump `engine.io` - -1.4.7 / 2016-06-23 -================== - - * package: bump `engine.io` - -1.4.6 / 2016-05-02 -================== - - * package: bump engine.io - -1.4.5 / 2016-01-26 -================== - - * fix closing the underlying `http.Server` - -1.4.4 / 2016-01-10 -================== - - * package: bump `engine.io` - -1.4.3 / 2016-01-08 -================== - - * bump `socket.io-client` - -1.4.2 / 2016-01-07 -================== - - * bump `engine.io` - -1.4.1 / 2016-01-07 -================== - - * version bump - -1.4.0 / 2015-11-28 -================== - - * socket.io: increase large binary data test timeout - * package: bump `engine.io` for release - * trigger callback even when joining an already joined room - * package: bump parser - * namespace: clear rooms flag after a clients call (fixes #1978) - * package: bump `socket.io-parser` - * fixed tests with large data - * fixed a typo in the example code - * package: bump mocha - * package: bump `has-binary` and `zuul-ngrok` - * package: bump `engine.io` and `socket.io-client` - * README: clarified documentation of Socket.in - * README: fixed up legacy repo links - * test: better timeout for stress test - * socket: don't set request property which has a getter - * removed proxy index file - * support flags on namespace - * improve Socket#packet and Client#packet - * socket: warn node_redis-style about missing `error` - * test: added failing test - * test: increase timeout for large binary data test - * package: bump `has-binary` to work with all objects (fixes #1955) - * fix origin verification default https port [evanlucas] - * support compression [nkzawa] - * changed type of `Client#sockets`, `Namespace#sockets` and `Socket#rooms` to maps (instead of arrays) - -1.3.7 / 2015-09-21 -================== - - * package: bump `socket.io-client` for node4 compatibility - * package: bump `engine.io` for node4 compatibility - -1.3.6 / 2015-07-14 -================== - - * package: bump `engine.io` to fix build on windows - -1.3.5 / 2015-03-03 -================== - - * package: bump `socket.io-parser` - -1.3.4 / 2015-02-14 -================== - - * package: bump `socket.io-client` - -1.3.3 / 2015-02-03 -================== - - * socket: warn node_redis-style about missing `error` - * package: bump parser to better handle bad binary packets - -1.3.2 / 2015-01-19 -================== - - * no change on this release - -1.3.1 / 2015-01-19 -================== - - * no change on this release - * package: bump `engine.io` - -1.3.0 / 2015-01-19 -================== - - * package: bump `engine.io` - * add test for reconnection after server restarts [rase-] - * update license with up-to-date year range [fay-jai] - * fix leaving unknown rooms [defunctzombie] - * allow null origins when allowed origins is a function [drewblaisdell] - * fix tests on node 0.11 - * package: fix `npm test` to run on windows - * package: bump `debug` v2.1.0 [coderaiser] - * added tests for volatile [rase-] - -1.2.1 / 2014-11-21 -================== - - * fix protocol violations and improve error handling (GH-1880) - * package: bump `engine.io` for websocket leak fix [3rd-Eden] - * style tweaks - -1.2.0 / 2014-10-27 -================== - - * package: bump `engine.io` - * downloads badge - * add test to check that empty rooms are autopruned - * added Server#origins(v:Function) description for dynamic CORS - * added test coverage for Server#origins(function) for dynamic CORS - * added optional Server#origins(function) for dynamic CORS - * fix usage example for Server#close - * package: fix main file for example application 'chat' - * package: bump `socket.io-parser` - * update README http ctor to createServer() - * bump adapter with a lot of fixes for room bookkeeping - -1.1.0 / 2014-09-04 -================== - - * examples: minor fix of escaping - * testing for equivalence of namespaces starting with / or without - * update index.js - * added relevant tests - * take "" and "/" as equivalent namespaces on server - * use svg instead of png to get better image quality in readme - * make CI build faster - * fix splice arguments and `socket.rooms` value update in `socket.leaveAll`. - * client cannot connect to non-existing namespaces - * bump engine.io version to get the cached IP address - * fixed handshake object address property and made the test case more strict. - * package: bump `engine.io` - * fixed the failing test where server crashes on disconnect involving connectBuffer - * npmignore: ignore `.gitignore` (fixes #1607) - * test: added failing case for `socket.disconnect` and nsps - * fix repo in package.json - * improve Close documentation - * use ephemeral ports - * fix: We should use the standard http protocol to handler the etag header. - * override default browser font-family for inputs - * update has-binary-data to 1.0.3 - * add close specs - * add ability to stop the http server even if not created inside socket.io - * make sure server gets close - * Add test case for checking that reconnect_failed is fired only once upon failure - * package: bump `socket.io-parser` for `component-emitter` dep fix - -1.0.6 / 2014-06-19 -================== - - * package: bump `socket.io-client` - -1.0.5 / 2014-06-16 -================== - - * package: bump `engine.io` to fix jsonp `\n` bug and CORS warnings - * index: fix typo [yanatan16] - * add `removeListener` to blacklisted events - * examples: clearer instructions to install chat example - * index: fix namespace `connectBuffer` issue - -1.0.4 / 2014-06-02 -================== - - * package: bump socket.io-client - -1.0.3 / 2014-05-31 -================== - - * package: bump `socket.io-client` - * package: bump `socket.io-parser` for binary ACK fix - * package: bump `engine.io` for binary UTF8 fix - * example: fix XSS in chat example - -1.0.2 / 2014-05-28 -================== - - * package: bump `socket.io-parser` for windows fix - -1.0.1 / 2014-05-28 -================== - - * bump due to bad npm tag - -1.0.0 / 2014-05-28 -================== - - * stable release - -1.0.0-pre5 / 2014-05-22 -======================= - - * package: bump `socket.io-client` for parser fixes - * package: bump `engine.io` - -1.0.0-pre4 / 2014-05-19 -======================= - - * package: bump client - -1.0.0-pre3 / 2014-05-17 -======================= - - * package: bump parser - * package: bump engine.io - -1.0.0-pre2 / 2014-04-27 -======================= - - * package: bump `engine.io` - * added backwards compatible of engine.io maxHttpBufferSize - * added test that server and client using same protocol - * added support for setting allowed origins - * added information about logging - * the set function in server can be used to set some attributes for BC - * fix error in callback call 'done' instead of 'next' in docs - * package: bump `socket.io-parser` - * package: bump `expect.js` - * added some new tests, including binary with acks - -1.0.0-pre / 2014-03-14 -====================== - - * implemented `engine.io` - * implemented `socket.io-adapter` - * implemented `socket.io-protocol` - * implemented `debug` and improved instrumentation - * added binary support - * added new `require('io')(srv)` signature - * simplified `socket.io-client` serving - -0.9.14 / 2013-03-29 -=================== - - * manager: fix memory leak with SSL [jpallen] - -0.9.13 / 2012-12-13 -=================== - - * package: fixed `base64id` requirement - -0.9.12 / 2012-12-13 -=================== - - * manager: fix for latest node which is returning a clone with `listeners` [viirya] - -0.9.11 / 2012-11-02 -=================== - - * package: move redis to optionalDependenices [3rd-Eden] - * bumped client - -0.9.10 / 2012-08-10 -=================== - - * Don't lowercase log messages - * Always set the HTTP response in case an error should be returned to the client - * Create or destroy the flash policy server on configuration change - * Honour configuration to disable flash policy server - * Add express 3.0 instructions on Readme.md - * Bump client - -0.9.9 / 2012-08-01 -================== - - * Fixed sync disconnect xhrs handling - * Put license text in its own file (#965) - * Add warning to .listen() to ease the migration to Express 3.x - * Restored compatibility with node 0.4.x - -0.9.8 / 2012-07-24 -================== - - * Bumped client. - -0.9.7 / 2012-07-24 -================== - - * Prevent crash when socket leaves a room twice. - * Corrects unsafe usage of for..in - * Fix for node 0.8 with `gzip compression` [vadimi] - * Update redis to support Node 0.8.x - * Made ID generation securely random - * Fix Redis Store race condition in manager onOpen unsubscribe callback - * Fix for EventEmitters always reusing the same Array instance for listeners - -0.9.6 / 2012-04-17 -================== - - * Fixed XSS in jsonp-polling. - -0.9.5 / 2012-04-05 -================== - - * Added test for polling and socket close. - * Ensure close upon request close. - * Fix disconnection reason being lost for polling transports. - * Ensure that polling transports work with Connection: close. - * Log disconnection reason. - -0.9.4 / 2012-04-01 -================== - - * Disconnecting from namespace improvement (#795) [DanielBaulig] - * Bumped client with polling reconnection loop (#438) - -0.9.3 / 2012-03-28 -================== - - * Fix "Syntax error" on FF Web Console with XHR Polling [mikito] - -0.9.2 / 2012-03-13 -================== - - * More sensible close `timeout default` (fixes disconnect issue) - -0.9.1-1 / 2012-03-02 -==================== - - * Bumped client with NPM dependency fix. - -0.9.1 / 2012-03-02 -================== - - * Changed heartbeat timeout and interval defaults (60 and 25 seconds) - * Make tests work both on 0.4 and 0.6 - * Updated client (improvements + bug fixes). - -0.9.0 / 2012-02-26 -================== - - * Make it possible to use a regexp to match the socket.io resource URL. - We need this because we have to prefix the socket.io URL with a variable ID. - * Supplemental fix to gavinuhma/authfix, it looks like the same Access-Control-Origin logic is needed in the http and xhr-polling transports - * Updated express dep for windows compatibility. - * Combine two substr calls into one in decodePayload to improve performance - * Minor documentation fix - * Minor. Conform to style of other files. - * Switching setting to 'match origin protocol' - * Revert "Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect()." - * Revert "Handle leaked dispatch:[id] subscription." - * Merge pull request #667 from dshaw/patch/redis-disconnect - * Handle leaked dispatch:[id] subscription. - * Fixes leaking Redis subscriptions for #663. The local flag was not getting passed through onClientDisconnect(). - * Prevent memory leaking on uncompleted requests & add max post size limitation - * Fix for testcase - * Set Access-Control-Allow-Credentials true, regardless of cookie - * Remove assertvarnish from package as it breaks on 0.6 - * Correct irc channel - * Added proper return after reserved field error - * Fixes manager.js failure to close connection after transport error has happened - * Added implicit port 80 for origin checks. fixes #638 - * Fixed bug #432 in 0.8.7 - * Set Access-Control-Allow-Origin header to origin to enable withCredentials - * Adding configuration variable matchOriginProtocol - * Fixes location mismatch error in Safari. - * Use tty to detect if we should add colors or not by default. - * Updated the package location. - -0.8.7 / 2011-11-05 -================== - - * Fixed memory leaks in closed clients. - * Fixed memory leaks in namespaces. - * Fixed websocket handling for malformed requests from proxies. [einaros] - * Node 0.6 compatibility. [einaros] [3rd-Eden] - * Adapted tests and examples. - -0.8.6 / 2011-10-27 -================== - - * Added JSON decoding on jsonp-polling transport. - * Fixed README example. - * Major speed optimizations [3rd-Eden] [einaros] [visionmedia] - * Added decode/encode benchmarks [visionmedia] - * Added support for black-listing client sent events. - * Fixed logging options, closes #540 [3rd-Eden] - * Added vary header for gzip [3rd-Eden] - * Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client - * Patched to properly shut down when a finishClose call is made during connection establishment - * Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify] - * Began IE10 compatibility [einaros] [tbranyen] - * Misc WebSocket fixes [einaros] - * Added UTF8 to respone headers for htmlfile [3rd-Eden] - -0.8.5 / 2011-10-07 -================== - - * Added websocket draft HyBi-16 support. [einaros] - * Fixed websocket continuation bugs. [einaros] - * Fixed flashsocket transport name. - * Fixed websocket tests. - * Ensured `parser#decodePayload` doesn't choke. - * Added http referrer verification to manager verifyOrigin. - * Added access control for cross domain xhr handshakes [3rd-Eden] - * Added support for automatic generation of socket.io files [3rd-Eden] - * Added websocket binary support [einaros] - * Added gzip support for socket.io.js [3rd-Eden] - * Expose socket.transport [3rd-Eden] - * Updated client. - -0.8.4 / 2011-09-06 -================== - - * Client build - -0.8.3 / 2011-09-03 -================== - - * Fixed `\n` parsing for non-JSON packets (fixes #479). - * Fixed parsing of certain unicode characters (fixes #451). - * Fixed transport message packet logging. - * Fixed emission of `error` event resulting in an uncaught exception if unhandled (fixes #476). - * Fixed; allow for falsy values as the configuration value of `log level` (fixes #491). - * Fixed repository URI in `package.json`. Fixes #504. - * Added text/plain content-type to handshake responses [einaros] - * Improved single byte writes [einaros] - * Updated socket.io-flashsocket default port from 843 to 10843 [3rd-Eden] - * Updated client. - -0.8.2 / 2011-08-29 -================== - - * Updated client. - -0.8.1 / 2011-08-29 -================== - - * Fixed utf8 bug in send framing in websocket [einaros] - * Fixed typo in docs [Znarkus] - * Fixed bug in send framing for over 64kB of data in websocket [einaros] - * Corrected ping handling in websocket transport [einaros] - -0.8.0 / 2011-08-28 -================== - - * Updated to work with two-level websocket versioning. [einaros] - * Added hybi07 support. [einaros] - * Added hybi10 support. [einaros] - * Added http referrer verification to manager.js verifyOrigin. [einaors] - -0.7.11 / 2011-08-27 -=================== - - * Updated socket.io-client. - -0.7.10 / 2011-08-27 -=================== - - * Updated socket.io-client. - -0.7.9 / 2011-08-12 -================== - - * Updated socket.io-client. - * Make sure we only do garbage collection when the server we receive is actually run. - -0.7.8 / 2011-08-08 -================== - - * Changed; make sure sio#listen passes options to both HTTP server and socket.io manager. - * Added docs for sio#listen. - * Added options parameter support for Manager constructor. - * Added memory leaks tests and test-leaks Makefile task. - * Removed auto npm-linking from make test. - * Make sure that you can disable heartbeats. [3rd-Eden] - * Fixed rooms memory leak [3rd-Eden] - * Send response once we got all POST data, not immediately [Pita] - * Fixed onLeave behavior with missing clientsk [3rd-Eden] - * Prevent duplicate references in rooms. - * Added alias for `to` to `in` and `in` to `to`. - * Fixed roomClients definition. - * Removed dependency on redis for installation without npm [3rd-Eden] - * Expose path and querystring in handshakeData [3rd-Eden] - -0.7.7 / 2011-07-12 -================== - - * Fixed double dispatch handling with emit to closed clients. - * Added test for emitting to closed clients to prevent regression. - * Fixed race condition in redis test. - * Changed Transport#end instrumentation. - * Leveraged $emit instead of emit internally. - * Made tests faster. - * Fixed double disconnect events. - * Fixed disconnect logic - * Simplified remote events handling in Socket. - * Increased testcase timeout. - * Fixed unknown room emitting (GH-291). [3rd-Eden] - * Fixed `address` in handshakeData. [3rd-Eden] - * Removed transports definition in chat example. - * Fixed room cleanup - * Fixed; make sure the client is cleaned up after booting. - * Make sure to mark the client as non-open if the connection is closed. - * Removed unneeded `buffer` declarations. - * Fixed; make sure to clear socket handlers and subscriptions upon transport close. - -0.7.6 / 2011-06-30 -================== - - * Fixed general dispatching when a client has closed. - -0.7.5 / 2011-06-30 -================== - - * Fixed dispatching to clients that are disconnected. - -0.7.4 / 2011-06-30 -================== - - * Fixed; only clear handlers if they were set. [level09] - -0.7.3 / 2011-06-30 -================== - - * Exposed handshake data to clients. - * Refactored dispatcher interface. - * Changed; Moved id generation method into the manager. - * Added sub-namespace authorization. [3rd-Eden] - * Changed; normalized SocketNamespace local eventing [dvv] - * Changed; Use packet.reason or default to 'packet' [3rd-Eden] - * Changed console.error to console.log. - * Fixed; bind both servers at the same time do that the test never times out. - * Added 304 support. - * Removed `Transport#name` for abstract interface. - * Changed; lazily require http and https module only when needed. [3rd-Eden] - -0.7.2 / 2011-06-22 -================== - - * Make sure to write a packet (of type `noop`) when closing a poll. - This solves a problem with cross-domain requests being flagged as aborted and - reconnection being triggered. - * Added `noop` message type. - -0.7.1 / 2011-06-21 -================== - - * Fixed cross-domain XHR. - * Added CORS test to xhr-polling suite. - -0.7.0 / 2010-06-21 -================== - - * http://socket.io/announcement.html diff --git a/LICENSE b/LICENSE index aea53a7090..6ce8c5ca1a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2014-2017 Automattic +Copyright (c) 2014-2018 Automattic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/Makefile b/Makefile deleted file mode 100644 index 6e494e024f..0000000000 --- a/Makefile +++ /dev/null @@ -1,8 +0,0 @@ - -test: - @./node_modules/.bin/gulp test - -test-cov: - @./node_modules/.bin/gulp test-cov - -.PHONY: test diff --git a/Readme.md b/Readme.md index 5df48abac8..92fc1c53bf 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,7 @@ # socket.io [![Backers on Open Collective](https://opencollective.com/socketio/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/socketio/sponsors/badge.svg)](#sponsors) -[![Build Status](https://secure.travis-ci.org/socketio/socket.io.svg?branch=master)](https://travis-ci.org/socketio/socket.io) +[![Build Status](https://github.com/socketio/socket.io/workflows/CI/badge.svg)](https://github.com/socketio/socket.io/actions) [![Dependency Status](https://david-dm.org/socketio/socket.io.svg)](https://david-dm.org/socketio/socket.io) [![devDependency Status](https://david-dm.org/socketio/socket.io/dev-status.svg)](https://david-dm.org/socketio/socket.io#info=devDependencies) [![NPM version](https://badge.fury.io/js/socket.io.svg)](https://www.npmjs.com/package/socket.io) @@ -11,7 +11,7 @@ ## Features -Socket.IO enables real-time bidirectional event-based communication. It consists in: +Socket.IO enables real-time bidirectional event-based communication. It consists of: - a Node.js server (this repository) - a [Javascript client library](https://github.com/socketio/socket.io-client) for the browser (or a Node.js client) @@ -21,6 +21,7 @@ Some implementations in other languages are also available: - [Java](https://github.com/socketio/socket.io-client-java) - [C++](https://github.com/socketio/socket.io-client-cpp) - [Swift](https://github.com/socketio/socket.io-client-swift) +- [Dart](https://github.com/rikulo/socket.io-client-dart) Its main features are: @@ -38,7 +39,7 @@ Unless instructed otherwise a disconnected client will try to reconnect forever, #### Disconnection detection -An heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore. +A heartbeat mechanism is implemented at the Engine.IO level, allowing both the server and the client to know when the other one is not responding anymore. That functionality is achieved with timers set on both the server and the client, with timeout values (the `pingInterval` and `pingTimeout` parameters) shared during the connection handshake. Those timers require any subsequent client calls to be directed to the same server, hence the `sticky-session` requirement when using multiples nodes. @@ -54,10 +55,10 @@ Any serializable data structures can be emitted, including: Sample code: ```js -io.on('connection', function(socket){ - socket.emit('request', /* */); // emit an event to the socket - io.emit('broadcast', /* */); // emit an event to all connected sockets - socket.on('reply', function(){ /* */ }); // listen to the event +io.on('connection', socket => { + socket.emit('request', /* … */); // emit an event to the socket + io.emit('broadcast', /* … */); // emit an event to all connected sockets + socket.on('reply', () => { /* … */ }); // listen to the event }); ``` @@ -83,7 +84,7 @@ This is a useful feature to send notifications to a group of users, or to a give ## Installation ```bash -npm install socket.io --save +npm install socket.io ``` ## How to use @@ -92,11 +93,11 @@ The following example attaches socket.io to a plain Node.JS HTTP server listening on port `3000`. ```js -var server = require('http').createServer(); -var io = require('socket.io')(server); -io.on('connection', function(client){ - client.on('event', function(data){}); - client.on('disconnect', function(){}); +const server = require('http').createServer(); +const io = require('socket.io')(server); +io.on('connection', client => { + client.on('event', data => { /* … */ }); + client.on('disconnect', () => { /* … */ }); }); server.listen(3000); ``` @@ -104,8 +105,8 @@ server.listen(3000); ### Standalone ```js -var io = require('socket.io')(); -io.on('connection', function(client){}); +const io = require('socket.io')(); +io.on('connection', client => { ... }); io.listen(3000); ``` @@ -117,10 +118,10 @@ to pass the `Server` to `socket.io`, and not the express application function. Also make sure to call `.listen` on the `server`, not the `app`. ```js -var app = require('express')(); -var server = require('http').createServer(app); -var io = require('socket.io')(server); -io.on('connection', function(){ /* … */ }); +const app = require('express')(); +const server = require('http').createServer(app); +const io = require('socket.io')(server); +io.on('connection', () => { /* … */ }); server.listen(3000); ``` @@ -130,10 +131,10 @@ Like Express.JS, Koa works by exposing an application as a request handler function, but only by calling the `callback` method. ```js -var app = require('koa')(); -var server = require('http').createServer(app.callback()); -var io = require('socket.io')(server); -io.on('connection', function(){ /* … */ }); +const app = require('koa')(); +const server = require('http').createServer(app.callback()); +const io = require('socket.io')(server); +io.on('connection', () => { /* … */ }); server.listen(3000); ``` diff --git a/docs/API.md b/docs/API.md index 749656f421..a7ee580fc9 100644 --- a/docs/API.md +++ b/docs/API.md @@ -23,6 +23,9 @@ - [Class: Namespace](#namespace) - [namespace.name](#namespacename) - [namespace.connected](#namespaceconnected) + - [namespace.adapter](#namespaceadapter) + - [namespace.to(room)](#namespacetoroom) + - [namespace.in(room)](#namespaceinroom) - [namespace.emit(eventName[, ...args])](#namespaceemiteventname-args) - [namespace.clients(callback)](#namespaceclientscallback) - [namespace.use(fn)](#namespaceusefn) @@ -30,12 +33,14 @@ - [Event: 'connection'](#event-connect) - [Flag: 'volatile'](#flag-volatile) - [Flag: 'local'](#flag-local) + - [Flag: 'binary'](#flag-binary) - [Class: Socket](#socket) - [socket.id](#socketid) - [socket.rooms](#socketrooms) - [socket.client](#socketclient) - [socket.conn](#socketconn) - [socket.request](#socketrequest) + - [socket.handshake](#sockethandshake) - [socket.use(fn)](#socketusefn) - [socket.send([...args][, ack])](#socketsendargs-ack) - [socket.emit(eventName[, ...args][, ack])](#socketemiteventname-args-ack) @@ -53,6 +58,7 @@ - [socket.disconnect(close)](#socketdisconnectclose) - [Flag: 'broadcast'](#flag-broadcast) - [Flag: 'volatile'](#flag-volatile-1) + - [Flag: 'binary'](#flag-binary-1) - [Event: 'disconnect'](#event-disconnect) - [Event: 'error'](#event-error) - [Event: 'disconnecting'](#event-disconnecting) @@ -72,17 +78,16 @@ Exposed by `require('socket.io')`. - `path` _(String)_: name of the path to capture (`/socket.io`) - `serveClient` _(Boolean)_: whether to serve the client files (`true`) - `adapter` _(Adapter)_: the adapter to use. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter) - - `origins` _(String)_: the allowed origins (`*`) - - `allowRequest` _(Function)_: A function that receives a given handshake or upgrade request as its first parameter, and can decide whether to continue or not. The second argument is a function that needs to be called with the decided information: `fn(err, success)`, where `success` is a boolean value where false means that the request is rejected, and err is an error code. + - `origins` _(String)_: the allowed origins (`*:*`) - `parser` _(Parser)_: the parser to use. Defaults to an instance of the `Parser` that ships with socket.io. See [socket.io-parser](https://github.com/socketio/socket.io-parser). Works with and without `new`: ```js -var io = require('socket.io')(); +const io = require('socket.io')(); // or -var Server = require('socket.io'); -var io = new Server(); +const Server = require('socket.io'); +const io = new Server(); ``` The same options passed to socket.io are always passed to the `engine.io` `Server` that gets created. See engine.io [options](https://github.com/socketio/engine.io#methods-1) as reference. @@ -98,6 +103,21 @@ Those two parameters will impact the delay before a client knows the server is n **Note:** The order is important. By default, a long-polling connection is established first, and then upgraded to WebSocket if possible. Using `['websocket']` means there will be no fallback if a WebSocket connection cannot be opened. +```js +const server = require('http').createServer(); + +const io = require('socket.io')(server, { + path: '/test', + serveClient: false, + // below are engine.IO options + pingInterval: 10000, + pingTimeout: 5000, + cookie: false +}); + +server.listen(3000); +``` + #### new Server(port[, options]) - `port` _(Number)_ a port to listen to (a new `http.Server` will be created) @@ -105,12 +125,50 @@ Those two parameters will impact the delay before a client knows the server is n See [above](#new-serverhttpserver-options) for available options. +```js +const server = require('http').createServer(); + +const io = require('socket.io')(3000, { + path: '/test', + serveClient: false, + // below are engine.IO options + pingInterval: 10000, + pingTimeout: 5000, + cookie: false +}); +``` + #### new Server(options) - `options` _(Object)_ See [above](#new-serverhttpserver-options) for available options. +```js +const io = require('socket.io')({ + path: '/test', + serveClient: false, +}); + +// either +const server = require('http').createServer(); + +io.attach(server, { + pingInterval: 10000, + pingTimeout: 5000, + cookie: false +}); + +server.listen(3000); + +// or +io.attach(3000, { + pingInterval: 10000, + pingTimeout: 5000, + cookie: false +}); +``` + #### server.sockets * _(Namespace)_ @@ -126,10 +184,10 @@ If `value` is `true` the attached server (see `Server#attach`) will serve the cl ```js // pass a server and the `serveClient` option -var io = require('socket.io')(http, { serveClient: false }); +const io = require('socket.io')(http, { serveClient: false }); // or pass no server and then you can call the method -var io = require('socket.io')(); +const io = require('socket.io')(); io.serveClient(false); io.attach(http); ``` @@ -141,6 +199,16 @@ io.attach(http); Sets the path `value` under which `engine.io` and the static files will be served. Defaults to `/socket.io`. If no arguments are supplied this method returns the current value. +```js +const io = require('socket.io')(); +io.path('/myownpath'); + +// client-side +const socket = io({ + path: '/myownpath' +}); +``` + #### server.adapter([value]) - `value` _(Adapter)_ @@ -148,24 +216,43 @@ Sets the path `value` under which `engine.io` and the static files will be serve Sets the adapter `value`. Defaults to an instance of the `Adapter` that ships with socket.io which is memory based. See [socket.io-adapter](https://github.com/socketio/socket.io-adapter). If no arguments are supplied this method returns the current value. +```js +const io = require('socket.io')(3000); +const redis = require('socket.io-redis'); +io.adapter(redis({ host: 'localhost', port: 6379 })); +``` + #### server.origins([value]) - - `value` _(String)_ + - `value` _(String|String[])_ - **Returns** `Server|String` Sets the allowed origins `value`. Defaults to any origins being allowed. If no arguments are supplied this method returns the current value. +```js +io.origins(['https://foo.example.com:443']); +``` + #### server.origins(fn) - `fn` _(Function)_ - **Returns** `Server` -Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not. +Provides a function taking two arguments `origin:String` and `callback(error, success)`, where `success` is a boolean value indicating whether origin is allowed or not. If `success` is set to `false`, `error` must be provided as a string value that will be appended to the server response, e.g. "Origin not allowed". __Potential drawbacks__: * in some situations, when it is not possible to determine `origin` it may have value of `*` * As this function will be executed for every request, it is advised to make this function work as fast as possible -* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express can use [cors](https://github.com/expressjs/cors). +* If `socket.io` is used together with `Express`, the CORS headers will be affected only for `socket.io` requests. For Express you can use [cors](https://github.com/expressjs/cors). + +```js +io.origins((origin, callback) => { + if (origin !== 'https://foo.example.com') { + return callback('origin not allowed', false); + } + callback(null, true); +}); +``` #### server.attach(httpServer[, options]) @@ -174,7 +261,7 @@ __Potential drawbacks__: Attaches the `Server` to an engine.io instance on `httpServer` with the supplied `options` (optionally). -### server.attach(port[, options]) +#### server.attach(port[, options]) - `port` _(Number)_ the port to listen on - `options` _(Object)_ @@ -205,11 +292,43 @@ Advanced use only. Creates a new `socket.io` client from the incoming engine.io #### server.of(nsp) - - `nsp` _(String)_ + - `nsp` _(String|RegExp|Function)_ - **Returns** `Namespace` Initializes and retrieves the given `Namespace` by its pathname identifier `nsp`. If the namespace was already initialized it returns it immediately. +```js +const adminNamespace = io.of('/admin'); +``` + +A regex or a function can also be provided, in order to create namespace in a dynamic way: + +```js +const dynamicNsp = io.of(/^\/dynamic-\d+$/).on('connect', (socket) => { + const newNamespace = socket.nsp; // newNamespace.name === '/dynamic-101' + + // broadcast to all clients in the given sub-namespace + newNamespace.emit('hello'); +}); + +// client-side +const socket = io('/dynamic-101'); + +// broadcast to all clients in each sub-namespace +dynamicNsp.emit('hello'); + +// use a middleware for each sub-namespace +dynamicNsp.use((socket, next) => { /* ... */ }); +``` + +With a function: + +```js +io.of((name, query, next) => { + next(null, checkToken(query.token)); +}).on('connect', (socket) => { /* ... */ }); +``` + #### server.close([callback]) - `callback` _(Function)_ @@ -217,11 +336,11 @@ Initializes and retrieves the given `Namespace` by its pathname identifier `nsp` Closes the socket.io server. The `callback` argument is optional and will be called when all connections are closed. ```js -var Server = require('socket.io'); -var PORT = 3030; -var server = require('http').Server(); +const Server = require('socket.io'); +const PORT = 3030; +const server = require('http').Server(); -var io = Server(PORT); +const io = Server(PORT); io.close(); // Close current server @@ -237,7 +356,7 @@ Overwrites the default method to generate your custom socket id. The function is called with a node request object (`http.IncomingMessage`) as first parameter. ```js -io.engine.generateId = function (req) { +io.engine.generateId = (req) => { return "custom:id:" + custom_id++; // custom id must be unique } ``` @@ -247,7 +366,7 @@ io.engine.generateId = function (req) { Represents a pool of sockets connected under a given scope identified by a pathname (eg: `/chat`). -By default the client always connects to `/`. +A client always connects to `/` (the main namespace), then potentially connect to other namespaces (while using the same underlying connection). #### namespace.name @@ -261,6 +380,34 @@ The namespace identifier property. The hash of `Socket` objects that are connected to this namespace, indexed by `id`. +#### namespace.adapter + + * _(Adapter)_ + +The `Adapter` used for the namespace. Useful when using the `Adapter` based on [Redis](https://github.com/socketio/socket.io-redis), as it exposes methods to manage sockets and rooms accross your cluster. + +**Note:** the adapter of the main namespace can be accessed with `io.of('/').adapter`. + +#### namespace.to(room) + + - `room` _(String)_ + - **Returns** `Namespace` for chaining + +Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`. + +To emit to multiple rooms, you can call `to` several times. + +```js +const io = require('socket.io')(); +const adminNamespace = io.of('/admin'); + +adminNamespace.to('level1').emit('an event', { some: 'data' }); +``` + +#### namespace.in(room) + +Synonym of [namespace.to(room)](#namespacetoroom). + #### namespace.emit(eventName[, ...args]) - `eventName` _(String)_ @@ -269,14 +416,15 @@ The hash of `Socket` objects that are connected to this namespace, indexed by `i Emits an event to all connected clients. The following two are equivalent: ```js -var io = require('socket.io')(); - +const io = require('socket.io')(); io.emit('an event sent to all connected clients'); // main namespace -var chat = io.of('/chat'); +const chat = io.of('/chat'); chat.emit('an event sent to all connected clients in chat namespace'); ``` +**Note:** acknowledgements are not supported when emitting from namespace. + #### namespace.clients(callback) - `callback` _(Function)_ @@ -284,8 +432,8 @@ chat.emit('an event sent to all connected clients in chat namespace'); Gets a list of client IDs connected to this namespace (across all nodes if applicable). ```js -var io = require('socket.io')(); -io.of('/chat').clients(function(error, clients){ +const io = require('socket.io')(); +io.of('/chat').clients((error, clients) => { if (error) throw error; console.log(clients); // => [PZDoMHjiu8PYfRiKAAAF, Anw2LatarvGVVXEIAAAD] }); @@ -294,8 +442,7 @@ io.of('/chat').clients(function(error, clients){ An example to get all clients in namespace's room: ```js -var io = require('socket.io')(); -io.of('/chat').in('general').clients(function(error, clients){ +io.of('/chat').in('general').clients((error, clients) => { if (error) throw error; console.log(clients); // => [Anw2LatarvGVVXEIAAAD] }); @@ -304,8 +451,7 @@ io.of('/chat').in('general').clients(function(error, clients){ As with broadcasting, the default is all clients from the default namespace ('/'): ```js -var io = require('socket.io')(); -io.clients(function(error, clients){ +io.clients((error, clients) => { if (error) throw error; console.log(clients); // => [6em3d4TJP8Et9EMNAAAA, G5p55dHhGgUnLUctAAAB] }); @@ -320,8 +466,7 @@ Registers a middleware, which is a function that gets executed for every incomin Errors passed to middleware callbacks are sent as special `error` packets to clients. ```js -var io = require('socket.io')(); -io.use(function(socket, next){ +io.use((socket, next) => { if (socket.request.headers.cookie) return next(); next(new Error('Authentication error')); }); @@ -333,6 +478,16 @@ io.use(function(socket, next){ Fired upon a connection from client. +```js +io.on('connect', (socket) => { + // ... +}); + +io.of('/admin').on('connect', (socket) => { + // ... +}); +``` + #### Event: 'connection' Synonym of [Event: 'connect'](#event-connect). @@ -345,6 +500,14 @@ Sets a modifier for a subsequent event emission that the event data may be lost io.volatile.emit('an event', { some: 'data' }); // the clients may or may not receive it ``` +#### Flag: 'binary' + +Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`. + +```js +io.binary(false).emit('an event', { some: 'data' }); +``` + #### Flag: 'local' Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to the current node (when the [Redis adapter](https://github.com/socketio/socket.io-redis) is used). @@ -375,6 +538,15 @@ A unique identifier for the session, that comes from the underlying `Client`. A hash of strings identifying the rooms this client is in, indexed by room name. +```js +io.on('connection', (socket) => { + socket.join('room 237', () => { + let rooms = Object.keys(socket.rooms); + console.log(rooms); // [ , 'room 237' ] + }); +}); +``` + #### socket.client * _(Client)_ @@ -393,6 +565,39 @@ A reference to the underlying `Client` transport connection (engine.io `Socket` A getter proxy that returns the reference to the `request` that originated the underlying engine.io `Client`. Useful for accessing request headers such as `Cookie` or `User-Agent`. +#### socket.handshake + + * _(Object)_ + +The handshake details: + +```js +{ + headers: /* the headers sent as part of the handshake */, + time: /* the date of creation (as string) */, + address: /* the ip of the client */, + xdomain: /* whether the connection is cross-domain */, + secure: /* whether the connection is secure */, + issued: /* the date of creation (as unix timestamp) */, + url: /* the request URL string */, + query: /* the query object */ +} +``` + +Usage: + +```js +io.use((socket, next) => { + let handshake = socket.handshake; + // ... +}); + +io.on('connection', (socket) => { + let handshake = socket.handshake; + // ... +}); +``` + #### socket.use(fn) - `fn` _(Function)_ @@ -402,9 +607,8 @@ Registers a middleware, which is a function that gets executed for every incomin Errors passed to middleware callbacks are sent as special `error` packets to clients. ```js -var io = require('socket.io')(); -io.on('connection', function(socket){ - socket.use(function(packet, next){ +io.on('connection', (socket) => { + socket.use((packet, next) => { if (packet.doge === true) return next(); next(new Error('Not a doge error')); }); @@ -431,22 +635,21 @@ Emits an event to the socket identified by the string name. Any other parameters ```js socket.emit('hello', 'world'); -socket.emit('with-binary', 1, '2', { 3: '4', 5: new Buffer(6) }); +socket.emit('with-binary', 1, '2', { 3: '4', 5: Buffer.alloc(6) }); ``` The `ack` argument is optional and will be called with the client's answer. ```js -var io = require('socket.io')(); -io.on('connection', function(client){ - client.emit('an event', { some: 'data' }); +io.on('connection', (socket) => { + socket.emit('an event', { some: 'data' }); - client.emit('ferret', 'tobi', function (data) { + socket.emit('ferret', 'tobi', (data) => { console.log(data); // data will be 'woot' }); // the client code - // client.on('ferret', function (name, fn) { + // client.on('ferret', (name, fn) => { // fn('woot'); // }); @@ -463,9 +666,17 @@ io.on('connection', function(client){ Register a new handler for the given event. ```js -socket.on('news', function (data) { +socket.on('news', (data) => { console.log(data); }); +// with several arguments +socket.on('news', (arg1, arg2, arg3) => { + // ... +}); +// or with acknowledgement +socket.on('news', (data, callback) => { + callback(0); +}); ``` #### socket.once(eventName, listener) @@ -484,23 +695,24 @@ Inherited from `EventEmitter` (along with other methods not mentioned here). See Adds the client to the `room`, and fires optionally a callback with `err` signature (if any). ```js -io.on('connection', function(socket){ - socket.join('room 237', function(){ - console.log(socket.rooms); // [ , 'room 237' ] - io.to('room 237', 'a new user has joined the room'); // broadcast to everyone in the room +io.on('connection', (socket) => { + socket.join('room 237', () => { + let rooms = Object.keys(socket.rooms); + console.log(rooms); // [ , 'room 237' ] + io.to('room 237').emit('a new user has joined the room'); // broadcast to everyone in the room }); }); ``` The mechanics of joining rooms are handled by the `Adapter` that has been configured (see `Server#adapter` above), defaulting to [socket.io-adapter](https://github.com/socketio/socket.io-adapter). -For your convenience, each socket automatically joins a room identified by this id (see `Socket#id`). This makes it easy to broadcast messages to other sockets: +For your convenience, each socket automatically joins a room identified by its id (see `Socket#id`). This makes it easy to broadcast messages to other sockets: ```js -io.on('connection', function(client){ - client.on('say to someone', function(id, msg){ +io.on('connection', (socket) => { + socket.on('say to someone', (id, msg) => { // send a private message to the socket with the given id - client.broadcast.to(id).emit('my message', msg); + socket.to(id).emit('my message', msg); }); }); ``` @@ -528,20 +740,23 @@ Removes the client from `room`, and fires optionally a callback with `err` signa - `room` _(String)_ - **Returns** `Socket` for chaining -Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room`. +Sets a modifier for a subsequent event emission that the event will only be _broadcasted_ to clients that have joined the given `room` (the socket itself being excluded). To emit to multiple rooms, you can call `to` several times. ```js -var io = require('socket.io')(); -io.on('connection', function(client){ +io.on('connection', (socket) => { // to one room - client.to('others').emit('an event', { some: 'data' }); + socket.to('others').emit('an event', { some: 'data' }); // to multiple rooms - client.to('room1').to('room2').emit('hello'); + socket.to('room1').to('room2').emit('hello'); + // a private message to another socket + socket.to(/* another socket id */).emit('hey'); }); ``` +**Note:** acknowledgements are not supported when broadcasting. + #### socket.in(room) Synonym of [socket.to(room)](#sockettoroom). @@ -553,6 +768,12 @@ Synonym of [socket.to(room)](#sockettoroom). Sets a modifier for a subsequent event emission that the event data will only be _compressed_ if the value is `true`. Defaults to `true` when you don't call the method. +```js +io.on('connection', (socket) => { + socket.compress(false).emit('uncompressed', "that's rough"); +}); +``` + #### socket.disconnect(close) - `close` _(Boolean)_ whether to close the underlying connection @@ -560,13 +781,18 @@ Sets a modifier for a subsequent event emission that the event data will only be Disconnects this client. If value of close is `true`, closes the underlying connection. Otherwise, it just disconnects the namespace. +```js +io.on('connection', (socket) => { + setTimeout(() => socket.disconnect(true), 5000); +}); +``` + #### Flag: 'broadcast' Sets a modifier for a subsequent event emission that the event data will only be _broadcast_ to every sockets but the sender. ```js -var io = require('socket.io')(); -io.on('connection', function(socket){ +io.on('connection', (socket) => { socket.broadcast.emit('an event', { some: 'data' }); // everyone gets it but the sender }); ``` @@ -575,10 +801,20 @@ io.on('connection', function(socket){ Sets a modifier for a subsequent event emission that the event data may be lost if the client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle). +```js +io.on('connection', (socket) => { + socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it +}); +``` + +#### Flag: 'binary' + +Specifies whether there is binary data in the emitted data. Increases performance when specified. Can be `true` or `false`. + ```js var io = require('socket.io')(); io.on('connection', function(socket){ - socket.volatile.emit('an event', { some: 'data' }); // the client may or may not receive it + socket.binary(false).emit('an event', { some: 'data' }); // The data to send has no binary data }); ``` @@ -588,18 +824,43 @@ io.on('connection', function(socket){ Fired upon disconnection. +```js +io.on('connection', (socket) => { + socket.on('disconnect', (reason) => { + // ... + }); +}); +``` + #### Event: 'error' - `error` _(Object)_ error object Fired when an error occurs. +```js +io.on('connection', (socket) => { + socket.on('error', (error) => { + // ... + }); +}); +``` + #### Event: 'disconnecting' - `reason` _(String)_ the reason of the disconnection (either client or server-side) Fired when the client is going to be disconnected (but hasn't left its `rooms` yet). +```js +io.on('connection', (socket) => { + socket.on('disconnecting', (reason) => { + let rooms = Object.keys(socket.rooms); + // ... + }); +}); +``` + These are reserved events (along with `connect`, `newListener` and `removeListener`) which cannot be used as event names. ### Client diff --git a/docs/emit.md b/docs/emit.md index c022618423..1ff0195fe2 100644 --- a/docs/emit.md +++ b/docs/emit.md @@ -29,7 +29,7 @@ function onConnect(socket){ io.of('myNamespace').to('room').emit('event', 'message'); // sending to individual socketid (private message) - socket.to().emit('hey', 'I just met you'); + io.to().emit('hey', 'I just met you'); // sending with acknowledgement socket.emit('question', 'do you think so?', function (answer) {}); @@ -40,9 +40,15 @@ function onConnect(socket){ // sending a message that might be dropped if the client is not ready to receive messages socket.volatile.emit('maybe', 'do you really need it?'); + // specifying whether the data to send has binary data + socket.binary(false).emit('what', 'I have no binaries!'); + // sending to all clients on this node (when using multiple nodes) io.local.emit('hi', 'my lovely babies'); + // sending to all connected clients + io.emit('an event sent to all connected clients'); + }; ``` diff --git a/examples/chat/index.js b/examples/chat/index.js index c9efcec25e..04b5b9d926 100644 --- a/examples/chat/index.js +++ b/examples/chat/index.js @@ -1,26 +1,27 @@ // Setup basic express server var express = require('express'); var app = express(); +var path = require('path'); var server = require('http').createServer(app); var io = require('../..')(server); var port = process.env.PORT || 3000; -server.listen(port, function () { +server.listen(port, () => { console.log('Server listening at port %d', port); }); // Routing -app.use(express.static(__dirname + '/public')); +app.use(express.static(path.join(__dirname, 'public'))); // Chatroom var numUsers = 0; -io.on('connection', function (socket) { +io.on('connection', (socket) => { var addedUser = false; // when the client emits 'new message', this listens and executes - socket.on('new message', function (data) { + socket.on('new message', (data) => { // we tell the client to execute 'new message' socket.broadcast.emit('new message', { username: socket.username, @@ -29,7 +30,7 @@ io.on('connection', function (socket) { }); // when the client emits 'add user', this listens and executes - socket.on('add user', function (username) { + socket.on('add user', (username) => { if (addedUser) return; // we store the username in the socket session for this client @@ -47,21 +48,21 @@ io.on('connection', function (socket) { }); // when the client emits 'typing', we broadcast it to others - socket.on('typing', function () { + socket.on('typing', () => { socket.broadcast.emit('typing', { username: socket.username }); }); // when the client emits 'stop typing', we broadcast it to others - socket.on('stop typing', function () { + socket.on('stop typing', () => { socket.broadcast.emit('stop typing', { username: socket.username }); }); // when the user disconnects.. perform this - socket.on('disconnect', function () { + socket.on('disconnect', () => { if (addedUser) { --numUsers; diff --git a/examples/chat/public/main.js b/examples/chat/public/main.js index dc1721be99..f20614a89a 100644 --- a/examples/chat/public/main.js +++ b/examples/chat/public/main.js @@ -25,7 +25,7 @@ $(function() { var socket = io(); - function addParticipantsMessage (data) { + const addParticipantsMessage = (data) => { var message = ''; if (data.numUsers === 1) { message += "there's 1 participant"; @@ -36,7 +36,7 @@ $(function() { } // Sets the client's username - function setUsername () { + const setUsername = () => { username = cleanInput($usernameInput.val().trim()); // If the username is valid @@ -52,7 +52,7 @@ $(function() { } // Sends a chat message - function sendMessage () { + const sendMessage = () => { var message = $inputMessage.val(); // Prevent markup from being injected into the message message = cleanInput(message); @@ -69,13 +69,13 @@ $(function() { } // Log a message - function log (message, options) { + const log = (message, options) => { var $el = $('
  • ').addClass('log').text(message); addMessageElement($el, options); } // Adds the visual chat message to the message list - function addChatMessage (data, options) { + const addChatMessage = (data, options) => { // Don't fade the message in if there is an 'X was typing' var $typingMessages = getTypingMessages(data); options = options || {}; @@ -100,14 +100,14 @@ $(function() { } // Adds the visual chat typing message - function addChatTyping (data) { + const addChatTyping = (data) => { data.typing = true; data.message = 'is typing'; addChatMessage(data); } // Removes the visual chat typing message - function removeChatTyping (data) { + const removeChatTyping = (data) => { getTypingMessages(data).fadeOut(function () { $(this).remove(); }); @@ -118,7 +118,7 @@ $(function() { // options.fade - If the element should fade-in (default = true) // options.prepend - If the element should prepend // all other messages (default = false) - function addMessageElement (el, options) { + const addMessageElement = (el, options) => { var $el = $(el); // Setup default options @@ -145,12 +145,12 @@ $(function() { } // Prevents input from having injected markup - function cleanInput (input) { - return $('
    ').text(input).text(); + const cleanInput = (input) => { + return $('
    ').text(input).html(); } // Updates the typing event - function updateTyping () { + const updateTyping = () => { if (connected) { if (!typing) { typing = true; @@ -158,7 +158,7 @@ $(function() { } lastTypingTime = (new Date()).getTime(); - setTimeout(function () { + setTimeout(() => { var typingTimer = (new Date()).getTime(); var timeDiff = typingTimer - lastTypingTime; if (timeDiff >= TYPING_TIMER_LENGTH && typing) { @@ -170,14 +170,14 @@ $(function() { } // Gets the 'X is typing' messages of a user - function getTypingMessages (data) { + const getTypingMessages = (data) => { return $('.typing.message').filter(function (i) { return $(this).data('username') === data.username; }); } // Gets the color of a username through our hash function - function getUsernameColor (username) { + const getUsernameColor = (username) => { // Compute hash code var hash = 7; for (var i = 0; i < username.length; i++) { @@ -190,7 +190,7 @@ $(function() { // Keyboard events - $window.keydown(function (event) { + $window.keydown(event => { // Auto-focus the current input when a key is typed if (!(event.ctrlKey || event.metaKey || event.altKey)) { $currentInput.focus(); @@ -207,26 +207,26 @@ $(function() { } }); - $inputMessage.on('input', function() { + $inputMessage.on('input', () => { updateTyping(); }); // Click events // Focus input when clicking anywhere on login page - $loginPage.click(function () { + $loginPage.click(() => { $currentInput.focus(); }); // Focus input when clicking on the message input's border - $inputMessage.click(function () { + $inputMessage.click(() => { $inputMessage.focus(); }); // Socket events // Whenever the server emits 'login', log the login message - socket.on('login', function (data) { + socket.on('login', (data) => { connected = true; // Display the welcome message var message = "Welcome to Socket.IO Chat – "; @@ -237,45 +237,45 @@ $(function() { }); // Whenever the server emits 'new message', update the chat body - socket.on('new message', function (data) { + socket.on('new message', (data) => { addChatMessage(data); }); // Whenever the server emits 'user joined', log it in the chat body - socket.on('user joined', function (data) { + socket.on('user joined', (data) => { log(data.username + ' joined'); addParticipantsMessage(data); }); // Whenever the server emits 'user left', log it in the chat body - socket.on('user left', function (data) { + socket.on('user left', (data) => { log(data.username + ' left'); addParticipantsMessage(data); removeChatTyping(data); }); // Whenever the server emits 'typing', show the typing message - socket.on('typing', function (data) { + socket.on('typing', (data) => { addChatTyping(data); }); // Whenever the server emits 'stop typing', kill the typing message - socket.on('stop typing', function (data) { + socket.on('stop typing', (data) => { removeChatTyping(data); }); - socket.on('disconnect', function () { + socket.on('disconnect', () => { log('you have been disconnected'); }); - socket.on('reconnect', function () { + socket.on('reconnect', () => { log('you have been reconnected'); if (username) { socket.emit('add user', username); } }); - socket.on('reconnect_error', function () { + socket.on('reconnect_error', () => { log('attempt to reconnect has failed'); }); diff --git a/examples/cluster-nginx/server/package.json b/examples/cluster-nginx/server/package.json index 0fe83ecd3e..a72f945e6a 100644 --- a/examples/cluster-nginx/server/package.json +++ b/examples/cluster-nginx/server/package.json @@ -5,7 +5,7 @@ "main": "index.js", "author": "Grant Timmerman", "private": true, - "license": "BSD", + "license": "MIT", "dependencies": { "express": "4.13.4", "socket.io": "^1.7.2", diff --git a/examples/custom-parsers/README.md b/examples/custom-parsers/README.md index be00e9f3b8..519815d8fe 100644 --- a/examples/custom-parsers/README.md +++ b/examples/custom-parsers/README.md @@ -14,7 +14,7 @@ They are tested with various payloads: - string: `['1', '2', ... '1000']` - numeric: `[1, 2, ... 1000]` -- binary: `new Buffer(1000), where buf[i] = i` +- binary: `Buffer.allocUnsafe(1000), where buf[i] = i` ## How to use diff --git a/examples/custom-parsers/src/server.js b/examples/custom-parsers/src/server.js index 3b20d1703d..c222177af9 100644 --- a/examples/custom-parsers/src/server.js +++ b/examples/custom-parsers/src/server.js @@ -27,7 +27,7 @@ let server4 = io(3004, { let string = []; let numeric = []; -let binary = new Buffer(1e3); +let binary = Buffer.allocUnsafe(1e3); for (var i = 0; i < 1e3; i++) { string.push('' + i); numeric.push(i); diff --git a/examples/webpack-build-server/lib/index.js b/examples/webpack-build-server/lib/index.js index afcdbdb8a4..b05f4fbbbb 100644 --- a/examples/webpack-build-server/lib/index.js +++ b/examples/webpack-build-server/lib/index.js @@ -1,7 +1,8 @@ const server = require('http').createServer(); const io = require('socket.io')(server, { - // serveClient: false // do not serve the client file, in that case the brfs loader is not needed + serveClient: false, + wsEngine: 'ws' // uws is not supported since it is a native module }); const port = process.env.PORT || 3000; diff --git a/examples/webpack-build-server/package.json b/examples/webpack-build-server/package.json index 28ef8ba392..abd095e4cf 100644 --- a/examples/webpack-build-server/package.json +++ b/examples/webpack-build-server/package.json @@ -8,16 +8,8 @@ }, "author": "Damien Arrachequesne", "license": "MIT", - "dependencies": { - "brfs": "^1.4.3", - "bufferutil": "^1.3.0", - "socket.io": "^1.7.2", - "transform-loader": "^0.2.3", - "utf-8-validate": "^2.0.0" - }, "devDependencies": { - "json-loader": "^0.5.4", - "null-loader": "^0.1.1", - "webpack": "^1.14.0" + "socket.io": "^2.0.3", + "webpack": "^2.6.1" } } diff --git a/examples/webpack-build-server/support/webpack.config.js b/examples/webpack-build-server/support/webpack.config.js index a9893a73d2..4e09c86624 100644 --- a/examples/webpack-build-server/support/webpack.config.js +++ b/examples/webpack-build-server/support/webpack.config.js @@ -3,23 +3,7 @@ module.exports = { entry: './lib/index.js', target: 'node', output: { - path: './dist', + path: require('path').join(__dirname, '../dist'), filename: 'server.js' - }, - module: { - loaders: [ - { - test: /(\.md|\.map)$/, - loader: 'null' - }, - { - test: /\.json$/, - loader: 'json' - }, - { - test: /\.js$/, - loader: "transform-loader?brfs" - } - ] } }; diff --git a/examples/whiteboard/public/main.js b/examples/whiteboard/public/main.js index 808f52b223..03ea033469 100644 --- a/examples/whiteboard/public/main.js +++ b/examples/whiteboard/public/main.js @@ -16,6 +16,12 @@ canvas.addEventListener('mouseup', onMouseUp, false); canvas.addEventListener('mouseout', onMouseUp, false); canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false); + + //Touch support for mobile devices + canvas.addEventListener('touchstart', onMouseDown, false); + canvas.addEventListener('touchend', onMouseUp, false); + canvas.addEventListener('touchcancel', onMouseUp, false); + canvas.addEventListener('touchmove', throttle(onMouseMove, 10), false); for (var i = 0; i < colors.length; i++){ colors[i].addEventListener('click', onColorUpdate, false); @@ -51,21 +57,21 @@ function onMouseDown(e){ drawing = true; - current.x = e.clientX; - current.y = e.clientY; + current.x = e.clientX||e.touches[0].clientX; + current.y = e.clientY||e.touches[0].clientY; } function onMouseUp(e){ if (!drawing) { return; } drawing = false; - drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true); + drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true); } function onMouseMove(e){ if (!drawing) { return; } - drawLine(current.x, current.y, e.clientX, e.clientY, current.color, true); - current.x = e.clientX; - current.y = e.clientY; + drawLine(current.x, current.y, e.clientX||e.touches[0].clientX, e.clientY||e.touches[0].clientY, current.color, true); + current.x = e.clientX||e.touches[0].clientX; + current.y = e.clientY||e.touches[0].clientY; } function onColorUpdate(e){ diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 82603d026c..0000000000 --- a/gulpfile.js +++ /dev/null @@ -1,69 +0,0 @@ -const gulp = require('gulp'); -const mocha = require('gulp-mocha'); -const babel = require("gulp-babel"); -const istanbul = require('gulp-istanbul'); -const help = require('gulp-task-listing'); -const del = require('del'); - -gulp.task('help', help); - -gulp.task('default', ['transpile']); - -const TRANSPILE_DEST_DIR = './dist'; - -// By default, individual js files are transformed by babel and exported to /dist -gulp.task('transpile', function () { - return gulp.src("lib/*.js") - .pipe(babel({ "presets": ["es2015"] })) - .pipe(gulp.dest(TRANSPILE_DEST_DIR)); -}); - -gulp.task('clean', function () { - return del([TRANSPILE_DEST_DIR]); -}) - -gulp.task('test', ['transpile'], function(){ - return gulp.src('test/socket.io.js', {read: false}) - .pipe(mocha({ - slow: 200, - reporter: 'spec', - bail: true, - timeout: 10000 - })) - .once('error', function (err) { - console.error(err.stack); - process.exit(1); - }) - .once('end', function () { - process.exit(); - }); -}); - -gulp.task('set-compat-node-env', function() { - process.env.TEST_VERSION = 'compat'; -}); - -gulp.task('test-compat', ['set-compat-node-env', 'test']); - -gulp.task('istanbul-pre-test', function () { - return gulp.src(['lib/**/*.js']) - // Covering files - .pipe(istanbul()) - // Force `require` to return covered files - .pipe(istanbul.hookRequire()); -}); - -gulp.task('test-cov', ['istanbul-pre-test'], function(){ - return gulp.src('test/socket.io.js', {read: false}) - .pipe(mocha({ - reporter: 'dot' - })) - .pipe(istanbul.writeReports()) - .once('error', function (err){ - console.error(err.stack); - process.exit(1); - }) - .once('end', function (){ - process.exit(); - }); -}); diff --git a/lib/client.js b/lib/client.js index 0b5f0446e9..b1f9a22a1c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -56,17 +56,37 @@ Client.prototype.setup = function(){ * Connects a client to a namespace. * * @param {String} name namespace + * @param {Object} query the query parameters * @api private */ Client.prototype.connect = function(name, query){ - debug('connecting to namespace %s', name); - var nsp = this.server.nsps[name]; - if (!nsp) { - this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'}); - return; + if (this.server.nsps[name]) { + debug('connecting to namespace %s', name); + return this.doConnect(name, query); } + this.server.checkNamespace(name, query, (dynamicNsp) => { + if (dynamicNsp) { + this.doConnect(name, query); + } else { + debug('creation of namespace %s was denied', name); + this.packet({ type: parser.ERROR, nsp: name, data: 'Invalid namespace' }); + } + }); +}; + +/** + * Connects a client to a namespace. + * + * @param {String} name namespace + * @param {String} query the query parameters + * @api private + */ + +Client.prototype.doConnect = function(name, query){ + var nsp = this.server.of(name); + if ('/' != name && !this.nsps['/']) { this.connectBuffer.push(name); return; diff --git a/lib/index.js b/lib/index.js index e16133ae4c..bf0d01e21d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1,4 @@ +'use strict'; /** * Module dependencies. @@ -12,6 +13,7 @@ var clientVersion = require('socket.io-client/package.json').version; var Client = require('./client'); var Emitter = require('events').EventEmitter; var Namespace = require('./namespace'); +var ParentNamespace = require('./parent-namespace'); var Adapter = require('socket.io-adapter'); var parser = require('socket.io-parser'); var debug = require('debug')('socket.io:server'); @@ -46,6 +48,7 @@ function Server(srv, opts){ } opts = opts || {}; this.nsps = {}; + this.parentNsps = new Map(); this.path(opts.path || '/socket.io'); this.serveClient(false !== opts.serveClient); this.parser = opts.parser || parser; @@ -79,9 +82,11 @@ Server.prototype.checkRequest = function(req, fn) { ? parts.port : defaultPort; var ok = + ~this._origins.indexOf(parts.protocol + '//' + parts.hostname + ':' + parts.port) || ~this._origins.indexOf(parts.hostname + ':' + parts.port) || ~this._origins.indexOf(parts.hostname + ':*') || ~this._origins.indexOf('*:' + parts.port); + debug('origin %s is %svalid', origin, !!ok ? '' : 'not '); return fn(null, !!ok); } catch (ex) { } @@ -157,6 +162,43 @@ Server.prototype.set = function(key, val){ return this; }; +/** + * Executes the middleware for an incoming namespace not already created on the server. + * + * @param {String} name name of incoming namespace + * @param {Object} query the query parameters + * @param {Function} fn callback + * @api private + */ + +Server.prototype.checkNamespace = function(name, query, fn){ + if (this.parentNsps.size === 0) return fn(false); + + const keysIterator = this.parentNsps.keys(); + + const run = () => { + let nextFn = keysIterator.next(); + if (nextFn.done) { + return fn(false); + } + nextFn.value(name, query, (err, allow) => { + if (err || !allow) { + return run(); + } + if (this.nsps[name]) { + // the namespace was created in the meantime + debug("dynamic namespace %s already exists", name); + return fn(this.nsps[name]); + } + const namespace = this.parentNsps.get(nextFn.value).createChild(name); + debug("dynamic namespace %s was created", name); + fn(namespace); + }); + }; + + run(); +}; + /** * Sets the client serving path. * @@ -193,7 +235,7 @@ Server.prototype.adapter = function(v){ /** * Sets the allowed origins for requests. * - * @param {String} v origins + * @param {String|String[]} v origins * @return {Server|Adapter} self when setting or value when getting * @api public */ @@ -334,6 +376,7 @@ Server.prototype.serve = function(req, res){ } debug('serve client source'); + res.setHeader("Cache-Control", "public, max-age=0"); res.setHeader('Content-Type', 'application/javascript'); res.setHeader('ETag', expectedEtag); res.writeHead(200); @@ -402,12 +445,24 @@ Server.prototype.onconnection = function(conn){ /** * Looks up a namespace. * - * @param {String} name nsp name + * @param {String|RegExp|Function} name nsp name * @param {Function} [fn] optional, nsp `connection` ev handler * @api public */ Server.prototype.of = function(name, fn){ + if (typeof name === 'function' || name instanceof RegExp) { + const parentNsp = new ParentNamespace(this); + debug('initializing parent namespace %s', parentNsp.name); + if (typeof name === 'function') { + this.parentNsps.set(name, parentNsp); + } else { + this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp); + } + if (fn) parentNsp.on('connect', fn); + return parentNsp; + } + if (String(name)[0] !== '/') name = '/' + name; var nsp = this.nsps[name]; @@ -451,7 +506,7 @@ var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){ return typeof Emitter.prototype[key] === 'function'; }); -emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){ +emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress', 'binary']).forEach(function(fn){ Server.prototype[fn] = function(){ return this.sockets[fn].apply(this.sockets, arguments); }; diff --git a/lib/namespace.js b/lib/namespace.js index 3c6b65ca4d..7e034bdb47 100644 --- a/lib/namespace.js +++ b/lib/namespace.js @@ -6,6 +6,7 @@ var Socket = require('./socket'); var Emitter = require('events').EventEmitter; var parser = require('socket.io-parser'); +var hasBin = require('has-binary2'); var debug = require('debug')('socket.io:namespace'); /** @@ -99,7 +100,7 @@ Namespace.prototype.initAdapter = function(){ */ Namespace.prototype.use = function(fn){ - if (this.server.eio) { + if (this.server.eio && this.name === '/') { debug('removing initial packet'); delete this.server.eio.initialPacket; } @@ -162,25 +163,31 @@ Namespace.prototype.add = function(client, query, fn){ var self = this; this.run(socket, function(err){ process.nextTick(function(){ - if ('open' == client.conn.readyState) { - if (err) return socket.error(err.data || err.message); - - // track socket - self.sockets[socket.id] = socket; - - // it's paramount that the internal `onconnect` logic - // fires before user-set events to prevent state order - // violations (such as a disconnection before the connection - // logic is complete) - socket.onconnect(); - if (fn) fn(); - - // fire user-set events - self.emit('connect', socket); - self.emit('connection', socket); - } else { - debug('next called after client was closed - ignoring socket'); + if ("open" !== client.conn.readyState) { + debug("next called after client was closed - ignoring socket"); + socket._cleanup(); + return; } + + if (err) { + debug("middleware error, sending CONNECT_ERROR packet to the client"); + socket._cleanup(); + return socket.error(err.data || err.message); + } + + // track socket + self.sockets[socket.id] = socket; + + // it's paramount that the internal `onconnect` logic + // fires before user-set events to prevent state order + // violations (such as a disconnection before the connection + // logic is complete) + socket.onconnect(); + if (fn) fn(); + + // fire user-set events + self.emit('connect', socket); + self.emit('connection', socket); }); }); return socket; @@ -210,23 +217,31 @@ Namespace.prototype.remove = function(socket){ Namespace.prototype.emit = function(ev){ if (~exports.events.indexOf(ev)) { emit.apply(this, arguments); - } else { - // set up packet object - var args = Array.prototype.slice.call(arguments); - var packet = { type: parser.EVENT, data: args }; + return this; + } + // set up packet object + var args = Array.prototype.slice.call(arguments); + var packet = { + type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT, + data: args + }; - if ('function' == typeof args[args.length - 1]) { - throw new Error('Callbacks are not supported when broadcasting'); - } + if ('function' == typeof args[args.length - 1]) { + throw new Error('Callbacks are not supported when broadcasting'); + } - this.adapter.broadcast(packet, { - rooms: this.rooms, - flags: this.flags - }); + var rooms = this.rooms.slice(0); + var flags = Object.assign({}, this.flags); + + // reset flags + this.rooms = []; + this.flags = {}; + + this.adapter.broadcast(packet, { + rooms: rooms, + flags: flags + }); - this.rooms = []; - this.flags = {}; - } return this; }; @@ -253,6 +268,9 @@ Namespace.prototype.write = function(){ */ Namespace.prototype.clients = function(fn){ + if(!this.adapter){ + throw new Error('No adapter for this namespace, are you trying to get the list of clients of a dynamic namespace?') + } this.adapter.clients(this.rooms, fn); // reset rooms for scenario: // .in('room').clients() (GH-1978) @@ -272,3 +290,16 @@ Namespace.prototype.compress = function(compress){ this.flags.compress = compress; return this; }; + +/** + * Sets the binary flag + * + * @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false` + * @return {Socket} self + * @api public + */ + + Namespace.prototype.binary = function (binary) { + this.flags.binary = binary; + return this; + }; diff --git a/lib/parent-namespace.js b/lib/parent-namespace.js new file mode 100644 index 0000000000..5a2b4fa8e1 --- /dev/null +++ b/lib/parent-namespace.js @@ -0,0 +1,39 @@ +'use strict'; + +const Namespace = require('./namespace'); + +let count = 0; + +class ParentNamespace extends Namespace { + + constructor(server) { + super(server, '/_' + (count++)); + this.children = new Set(); + } + + initAdapter() {} + + emit() { + const args = Array.prototype.slice.call(arguments); + + this.children.forEach(nsp => { + nsp.rooms = this.rooms; + nsp.flags = this.flags; + nsp.emit.apply(nsp, args); + }); + this.rooms = []; + this.flags = {}; + } + + createChild(name) { + const namespace = new Namespace(this.server, name); + namespace.fns = this.fns.slice(0); + this.listeners('connect').forEach(listener => namespace.on('connect', listener)); + this.listeners('connection').forEach(listener => namespace.on('connection', listener)); + this.children.add(namespace); + this.server.nsps[name] = namespace; + return namespace; + } +} + +module.exports = ParentNamespace; diff --git a/lib/socket.js b/lib/socket.js index a907bf3381..bfdd77e856 100644 --- a/lib/socket.js +++ b/lib/socket.js @@ -5,9 +5,9 @@ var Emitter = require('events').EventEmitter; var parser = require('socket.io-parser'); +var hasBin = require('has-binary2'); var url = require('url'); var debug = require('debug')('socket.io:socket'); -var assign = require('object-assign'); /** * Module exports. @@ -39,7 +39,8 @@ exports.events = [ var flags = [ 'json', 'volatile', - 'broadcast' + 'broadcast', + 'local' ]; /** @@ -65,8 +66,8 @@ function Socket(nsp, client, query){ this.conn = client.conn; this.rooms = {}; this.acks = {}; - this.connected = true; - this.disconnected = false; + this.connected = false; + this.disconnected = true; this.handshake = this.buildHandshake(query); this.fns = []; this.flags = {}; @@ -115,7 +116,7 @@ Socket.prototype.buildHandshake = function(query){ function buildQuery(){ var requestQuery = url.parse(self.request.url, true).query; //if socket-specific query exist, replace query strings in requestQuery - return assign({}, query, requestQuery); + return Object.assign({}, requestQuery, query); } return { headers: this.request.headers, @@ -144,7 +145,7 @@ Socket.prototype.emit = function(ev){ var args = Array.prototype.slice.call(arguments); var packet = { - type: parser.EVENT, + type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT, data: args }; @@ -160,7 +161,7 @@ Socket.prototype.emit = function(ev){ } var rooms = this._rooms.slice(0); - var flags = assign({}, this.flags); + var flags = Object.assign({}, this.flags); // reset flags this._rooms = []; @@ -299,6 +300,8 @@ Socket.prototype.leaveAll = function(){ Socket.prototype.onconnect = function(){ debug('socket connected - writing packet'); + this.connected = true; + this.disconnected = false; this.nsp.connected[this.id] = this; this.join(this.id); var skip = this.nsp.name === '/' && this.nsp.fns.length === 0; @@ -340,7 +343,7 @@ Socket.prototype.onpacket = function(packet){ break; case parser.ERROR: - this.emit('error', packet.data); + this.onerror(new Error(packet.data)); } }; @@ -381,7 +384,7 @@ Socket.prototype.ack = function(id){ self.packet({ id: id, - type: parser.ACK, + type: hasBin(args) ? parser.BINARY_ACK : parser.ACK, data: args }); @@ -444,7 +447,7 @@ Socket.prototype.onclose = function(reason){ if (!this.connected) return this; debug('closing socket - reason %s', reason); this.emit('disconnecting', reason); - this.leaveAll(); + this._cleanup(); this.nsp.remove(this); this.client.remove(this); this.connected = false; @@ -496,6 +499,19 @@ Socket.prototype.compress = function(compress){ return this; }; +/** + * Sets the binary flag + * + * @param {Boolean} Encode as if it has binary data if `true`, Encode as if it doesnt have binary data if `false` + * @return {Socket} self + * @api public + */ + + Socket.prototype.binary = function (binary) { + this.flags.binary = binary; + return this; + }; + /** * Dispatch incoming event to socket listeners. * @@ -511,7 +527,11 @@ Socket.prototype.dispatch = function(event){ if (err) { return self.error(err.data || err.message); } - emit.apply(self, event); + if (self.connected) { + emit.apply(self, event); + } else { + debug("ignore packet received after disconnection"); + } }); } this.run(event, dispatchSocket); @@ -556,3 +576,8 @@ Socket.prototype.run = function(event, fn){ run(0); }; + +Socket.prototype._cleanup = function () { + this.leaveAll(); + this.join = function noop() {}; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..4a84fed5ce --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3352 @@ +{ + "name": "socket.io", + "version": "2.4.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "diff": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", + "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "dev": true + }, + "engine.io": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.6.0.tgz", + "integrity": "sha512-Kc8fo5bbg8F4a2f3HPHTEpGyq/IRIQpyeHu3H1ThR14XDD7VrLcsGBo16HUpahgp8YkHJDaU5gNxJZbuGcuueg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "~7.4.2" + } + }, + "engine.io-client": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.2.tgz", + "integrity": "sha512-QEqIp+gJ/kMHeUun7f5Vv3bteRHppHH/FMBQX/esFj/fuYfjyUKWGMo3VCvIP/V8bE9KcjHmRZrhIz2Z9oNsDA==", + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.6.2", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "engine.io-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", + "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.4", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "expect.js": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", + "integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "json3": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", + "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "dev": true + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basecreate": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", + "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash.create": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", + "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", + "dev": true, + "requires": { + "lodash._baseassign": "^3.0.0", + "lodash._basecreate": "^3.0.0", + "lodash._isiterateecall": "^3.0.0" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" + }, + "mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "requires": { + "mime-db": "1.45.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mocha": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", + "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.9.0", + "debug": "2.6.8", + "diff": "3.2.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.1", + "growl": "1.9.2", + "he": "1.1.1", + "json3": "3.3.2", + "lodash.create": "3.1.1", + "mkdirp": "0.5.1", + "supports-color": "3.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", + "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "nyc": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.9.0.tgz", + "integrity": "sha512-w8OdJAhXL5izerzZMdqzYKMj/pgHJyY3qEPYBjLLxrhcVoHEY9pU5ENIiZyCgG9OR7x3VcUMoD40o6PtVpfR4g==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^1.0.0", + "convert-source-map": "^1.5.1", + "debug-log": "^1.0.1", + "default-require-extensions": "^1.0.0", + "find-cache-dir": "^0.1.1", + "find-up": "^2.1.0", + "foreground-child": "^1.5.3", + "glob": "^7.0.6", + "istanbul-lib-coverage": "^1.1.2", + "istanbul-lib-hook": "^1.1.0", + "istanbul-lib-instrument": "^1.10.0", + "istanbul-lib-report": "^1.1.3", + "istanbul-lib-source-maps": "^1.2.3", + "istanbul-reports": "^1.4.0", + "md5-hex": "^1.2.0", + "merge-source-map": "^1.1.0", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.0", + "resolve-from": "^2.0.0", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.1", + "spawn-wrap": "^1.4.2", + "test-exclude": "^4.2.0", + "yargs": "11.1.0", + "yargs-parser": "^8.0.0" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "^1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "atob": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "base": { + "version": "0.11.2", + "bundled": true, + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "^1.2.0", + "mkdirp": "^0.5.1", + "write-file-atomic": "^1.1.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "class-utils": { + "version": "0.3.6", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.6", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^2.0.0" + } + }, + "define-property": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fragment-cache": { + "version": "0.2.1", + "bundled": true, + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "get-value": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-odd": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.1.2", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.3", + "bundled": true, + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.1.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.10", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "^0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "nanomatch": { + "version": "1.2.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "object.pick": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-not": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "ret": { + "version": "0.1.15", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-regex": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "set-value": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "atob": "^2.0.0", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "split-string": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "static-extend": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "^1.0.1", + "micromatch": "^3.1.8", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "bundled": true, + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "bundled": true, + "dev": true + }, + "use": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" + }, + "socket.io-client": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.5.0.tgz", + "integrity": "sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw==", + "requires": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", + "has-binary2": "~1.0.2", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "socket.io-parser": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "requires": { + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "isarray": "2.0.1" + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "supertest": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "ws": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + }, + "xmlhttprequest-ssl": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz", + "integrity": "sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==" + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==" + } + } +} diff --git a/package.json b/package.json index 50dc91fe9c..975856394c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "socket.io", - "version": "2.0.3", + "version": "2.5.0", "description": "node.js realtime framework server", "keywords": [ "realtime", @@ -21,29 +21,22 @@ "url": "git://github.com/socketio/socket.io" }, "scripts": { - "test": "gulp test" + "test": "nyc mocha --reporter spec --slow 200 --bail --timeout 10000 test/socket.io.js" }, "dependencies": { - "debug": "~2.6.6", - "engine.io": "~3.1.0", - "object-assign": "~4.1.1", + "debug": "~4.1.0", + "engine.io": "~3.6.0", + "has-binary2": "~1.0.2", "socket.io-adapter": "~1.1.0", - "socket.io-client": "~2.0.2", - "socket.io-parser": "~3.1.1" + "socket.io-client": "2.5.0", + "socket.io-parser": "~3.4.0" }, "devDependencies": { - "babel-preset-es2015": "^6.24.1", - "del": "^2.2.2", "expect.js": "0.3.1", - "gulp": "^3.9.1", - "gulp-babel": "^6.1.2", - "gulp-istanbul": "^1.1.1", - "gulp-mocha": "^4.3.1", - "gulp-task-listing": "1.0.1", - "istanbul": "^0.4.5", - "mocha": "^3.3.0", - "superagent": "1.6.1", - "supertest": "1.1.0" + "mocha": "^3.5.3", + "nyc": "^11.2.1", + "superagent": "^3.8.2", + "supertest": "^3.0.0" }, "contributors": [ { diff --git a/test/socket.io.js b/test/socket.io.js index bd2e65d2ad..7fe059d4a9 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -1,12 +1,7 @@ -var testVersion = process.env.TEST_VERSION; +'use strict'; + var http = require('http').Server; -var io; -if (testVersion === 'compat') { - console.log('testing compat version'); - io = require('../dist'); -} else { - io = require('../lib'); -} +var io = require('../lib'); var fs = require('fs'); var join = require('path').join; var exec = require('child_process').exec; @@ -28,7 +23,7 @@ function client(srv, nsp, opts){ describe('socket.io', function(){ - it('should be the same version as client', function(){ + it.skip('should be the same version as client', function(){ var version = require('../package').version; expect(version).to.be(require('socket.io-client/package').version); }); @@ -361,6 +356,17 @@ describe('socket.io', function(){ done(); }); }); + + it('should allow request when using an array of origins', function(done) { + io({ origins: [ 'http://foo.example:54024' ] }).listen('54024'); + request.get('http://localhost:54024/socket.io/default/') + .set('origin', 'http://foo.example:54024') + .query({ transport: 'polling' }) + .end(function (err, res) { + expect(res.status).to.be(200); + done(); + }); + }); }); describe('close', function(){ @@ -432,18 +438,9 @@ describe('socket.io', function(){ }); describe('namespaces', function(){ - var Socket; - if (testVersion === 'compat') { - Socket = require('../dist/socket'); - } else { - Socket = require('../lib/socket'); - } - var Namespace; - if (testVersion === 'compat') { - Namespace = require('../dist/namespace'); - } else { - Namespace = require('../lib/namespace'); - } + var Socket = require('../lib/socket'); + var Namespace = require('../lib/namespace'); + it('should be accessible through .sockets', function(){ var sio = io(); expect(sio.sockets).to.be.a(Namespace); @@ -894,6 +891,97 @@ describe('socket.io', function(){ }); }); }); + + describe('dynamic namespaces', function () { + it('should allow connections to dynamic namespaces with a regex', function(done){ + const srv = http(); + const sio = io(srv); + let count = 0; + srv.listen(function(){ + const socket = client(srv, '/dynamic-101'); + let dynamicNsp = sio.of(/^\/dynamic-\d+$/).on('connect', (socket) => { + expect(socket.nsp.name).to.be('/dynamic-101'); + dynamicNsp.emit('hello', 1, '2', { 3: '4'}); + if (++count === 4) done(); + }).use((socket, next) => { + next(); + if (++count === 4) done(); + }); + socket.on('error', function(err) { + expect().fail(); + }); + socket.on('connect', () => { + if (++count === 4) done(); + }); + socket.on('hello', (a, b, c) => { + expect(a).to.eql(1); + expect(b).to.eql('2'); + expect(c).to.eql({ 3: '4' }); + if (++count === 4) done(); + }); + }); + }); + + it('should allow connections to dynamic namespaces with a function', function(done){ + const srv = http(); + const sio = io(srv); + srv.listen(function(){ + const socket = client(srv, '/dynamic-101'); + sio.of((name, query, next) => next(null, '/dynamic-101' === name)); + socket.on('connect', done); + }); + }); + + it('should disallow connections when no dynamic namespace matches', function(done){ + const srv = http(); + const sio = io(srv); + srv.listen(function(){ + const socket = client(srv, '/abc'); + sio.of(/^\/dynamic-\d+$/); + sio.of((name, query, next) => next(null, '/dynamic-101' === name)); + socket.on('error', (err) => { + expect(err).to.be('Invalid namespace'); + done(); + }); + }); + }); + + it("should handle race conditions with dynamic namespaces (#4136)", (done) => { + const srv = http(); + const sio = io(srv); + const counters = { + connected: 0, + created: 0, + events: 0, + }; + const buffer = []; + srv.listen(() => { + const handler = () => { + if (++counters.events === 2) { + done(); + } + }; + + sio + .of((name, query, next) => { + buffer.push(next); + if (buffer.length === 2) { + buffer.forEach((next) => next(null, true)); + } + }) + .on("connection", (socket) => { + if (++counters.connected === 2) { + sio.of("/dynamic-101").emit("message"); + } + }); + + let one = client(srv, "/dynamic-101"); + let two = client(srv, "/dynamic-101"); + one.on("message", handler); + two.on("message", handler); + }); + }); + }); }); describe('socket', function(){ @@ -1074,7 +1162,7 @@ describe('socket.io', function(){ sio.on('connection', function(s){ fs.readFile(join(__dirname, 'support', 'doge.jpg'), function(err, data){ if (err) return done(err); - var buf = new Buffer('asdfasdf', 'utf8'); + var buf = Buffer.from('asdfasdf', 'utf8'); s.emit('multiple', 1, data, '3', [4], buf, [data, 'swag', buf]); }); }); @@ -1091,7 +1179,7 @@ describe('socket.io', function(){ expect(Buffer.isBuffer(a)).to.be(true); done(); }); - var buf = new Buffer('abcdefg', 'utf8'); + var buf = Buffer.from('abcdefg', 'utf8'); socket.emit('buff', buf); }); }); @@ -1116,7 +1204,7 @@ describe('socket.io', function(){ }); fs.readFile(join(__dirname, 'support', 'doge.jpg'), function(err, data){ if (err) return done(err); - var buf = new Buffer('asdfasdf', 'utf8'); + var buf = Buffer.from('asdfasdf', 'utf8'); socket.emit('multiple', 1, data, '3', [4], buf, [data, 'swag', buf]); }); }); @@ -1444,7 +1532,7 @@ describe('socket.io', function(){ expect(Buffer.isBuffer(buf)).to.be(true); fn(1, 2); }); - socket.emit('woot', new Buffer(3), function(a, b){ + socket.emit('woot', Buffer.alloc(3), function(a, b){ expect(a).to.be(1); expect(b).to.be(2); done(); @@ -1463,7 +1551,7 @@ describe('socket.io', function(){ expect(Buffer.isBuffer(a)).to.be(true); fn(); }); - s.emit('hi', new Buffer(4), function(){ + s.emit('hi', Buffer.alloc(4), function(){ done(); }); }); @@ -1477,7 +1565,7 @@ describe('socket.io', function(){ var socket = client(srv); sio.on('connection', function(s){ socket.on('hi', function(fn){ - fn(new Buffer(1)); + fn(Buffer.alloc(1)); }); s.emit('hi', function(a){ expect(Buffer.isBuffer(a)).to.be(true); @@ -1494,7 +1582,7 @@ describe('socket.io', function(){ var socket = client(srv); sio.on('connection', function(s){ s.on('woot', function(fn){ - fn(new Buffer(2)); + fn(Buffer.alloc(2)); }); socket.emit('woot', function(a){ expect(Buffer.isBuffer(a)).to.be(true); @@ -1569,8 +1657,25 @@ describe('socket.io', function(){ expect(s.handshake.query.key2).to.be('&=bb'); done(); }); + }); + + it('should see the query options sent in the Socket.IO handshake (specific to the given socket)', (done) => { + const srv = http(); + const sio = io(srv); + const socket = client(srv, '/namespace',{ query: { key1: 'a', key2: 'b' }}); // manager-specific query option + socket.query = { key2: 'c' }; // socket-specific query option + const success = () => { + sio.close(); + socket.close(); + done(); + } + sio.of('/namespace').on('connection', (s) => { + expect(s.handshake.query.key1).to.be('a'); // in the query params + expect(s.handshake.query.key2).to.be('c'); // in the Socket.IO handshake + success(); + }); }); it('should handle very large json', function(done){ @@ -1689,7 +1794,7 @@ describe('socket.io', function(){ var socket = client(srv, { reconnection: false }); sio.on('connection', function(s){ s.conn.on('upgrade', function(){ - console.log('\033[96mNote: warning expected and normal in test.\033[39m'); + console.log('\u001b[96mNote: warning expected and normal in test.\u001b[39m'); socket.io.engine.write('5woooot'); setTimeout(function(){ done(); @@ -1699,6 +1804,23 @@ describe('socket.io', function(){ }); }); + it('should not crash when receiving an error packet without handler', function(done){ + var srv = http(); + var sio = io(srv); + srv.listen(function(){ + var socket = client(srv, { reconnection: false }); + sio.on('connection', function(s){ + s.conn.on('upgrade', function(){ + console.log('\u001b[96mNote: warning expected and normal in test.\u001b[39m'); + socket.io.engine.write('44["handle me please"]'); + setTimeout(function(){ + done(); + }, 100); + }); + }); + }); + }); + it('should not crash with raw binary', function(done){ var srv = http(); var sio = io(srv); @@ -1752,6 +1874,70 @@ describe('socket.io', function(){ }); }); + it("should ignore a packet received after disconnection", (done) => { + const srv = http(); + const sio = io(srv); + + srv.listen(() => { + const clientSocket = client(srv); + + const success = () => { + clientSocket.close(); + sio.close(); + done(); + }; + + sio.on("connection", (socket) => { + socket.on("test", () => { + done(new Error("should not happen")); + }); + socket.on("disconnect", success); + }); + + clientSocket.on("connect", () => { + clientSocket.emit("test", Buffer.alloc(10)); + clientSocket.disconnect(); + }); + }); + }); + + it("should leave all rooms joined after a middleware failure", (done) => { + const srv = http().listen(0); + const sio = io(srv); + const clientSocket = client(srv, "/"); + + sio.use((socket, next) => { + socket.join("room1"); + next(new Error("nope")); + }); + + clientSocket.on("error", () => { + expect(sio.of("/").adapter.rooms).to.eql(0); + + clientSocket.disconnect(); + sio.close(); + done(); + }); + }); + + it("should not join rooms after disconnection", (done) => { + const srv = http().listen(0); + const sio = io(srv); + const clientSocket = client(srv, "/"); + + sio.on("connection", (socket) => { + socket.disconnect(); + socket.join("room1"); + }); + + clientSocket.on("disconnect", () => { + expect(sio.of("/").adapter.rooms).to.eql(0); + + sio.close(); + done(); + }); + }); + it('should always trigger the callback (if provided) when joining a room', function(done){ var srv = http(); var sio = io(srv); @@ -1830,7 +2016,7 @@ describe('socket.io', function(){ }); function emit(){ - sio.emit('bin', new Buffer(10)); + sio.emit('bin', Buffer.alloc(10)); } }); }); @@ -2014,8 +2200,8 @@ describe('socket.io', function(){ socket.join(room, fn); }); socket.on('broadcast', function(){ - socket.broadcast.to('test').emit('bin', new Buffer(5)); - socket.emit('bin2', new Buffer(5)); + socket.broadcast.to('test').emit('bin', Buffer.alloc(5)); + socket.emit('bin2', Buffer.alloc(5)); }); }); }); @@ -2106,12 +2292,7 @@ describe('socket.io', function(){ }); describe('middleware', function(done){ - var Socket; - if (testVersion === 'compat') { - Socket = require('../dist/socket'); - } else { - Socket = require('../lib/socket'); - } + var Socket = require('../lib/socket'); it('should call functions', function(done){ var srv = http(); @@ -2281,6 +2462,42 @@ describe('socket.io', function(){ done(); }); }); + + it('should work with a custom namespace', (done) => { + var srv = http(); + var sio = io(); + sio.listen(srv); + sio.of('/chat').use(function(socket, next){ + next(); + }); + + var count = 0; + client(srv, '/').on('connect', () => { + if (++count === 2) done(); + }); + client(srv, '/chat').on('connect', () => { + if (++count === 2) done(); + }); + }); + + it("should only set `connected` to true after the middleware execution", (done) => { + const httpServer = http(); + const sio = io(httpServer); + + const clientSocket = client(httpServer, "/"); + + sio.use((socket, next) => { + expect(socket.connected).to.be(false); + expect(socket.disconnected).to.be(true); + next(); + }); + + sio.on("connection", (socket) => { + expect(socket.connected).to.be(true); + expect(socket.disconnected).to.be(false); + done(); + }); + }); }); describe('socket middleware', function(done){