aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ws
diff options
context:
space:
mode:
authorruki <waruqi@gmail.com>2018-11-08 00:38:48 +0800
committerruki <waruqi@gmail.com>2018-11-07 21:53:09 +0800
commit26105034da4fcce7ac883c899d781f016559310d (patch)
treec459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/ws
parent2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff)
downloadxmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz
xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip
switch to vuepress
Diffstat (limited to 'node_modules/ws')
-rw-r--r--node_modules/ws/LICENSE21
-rw-r--r--node_modules/ws/README.md389
-rw-r--r--node_modules/ws/index.js9
-rw-r--r--node_modules/ws/lib/buffer-util.js65
-rw-r--r--node_modules/ws/lib/constants.js10
-rw-r--r--node_modules/ws/lib/event-target.js170
-rw-r--r--node_modules/ws/lib/extension.js211
-rw-r--r--node_modules/ws/lib/permessage-deflate.js521
-rw-r--r--node_modules/ws/lib/receiver.js589
-rw-r--r--node_modules/ws/lib/sender.js404
-rw-r--r--node_modules/ws/lib/validation.js29
-rw-r--r--node_modules/ws/lib/websocket-server.js351
-rw-r--r--node_modules/ws/lib/websocket.js705
-rw-r--r--node_modules/ws/package.json45
14 files changed, 3519 insertions, 0 deletions
diff --git a/node_modules/ws/LICENSE b/node_modules/ws/LICENSE
new file mode 100644
index 00000000..a145cd1d
--- /dev/null
+++ b/node_modules/ws/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/node_modules/ws/README.md b/node_modules/ws/README.md
new file mode 100644
index 00000000..8f94ca30
--- /dev/null
+++ b/node_modules/ws/README.md
@@ -0,0 +1,389 @@
+# ws: a Node.js WebSocket library
+
+[![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws)
+[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws)
+[![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws)
+[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master)
+
+ws is a simple to use, blazing fast, and thoroughly tested WebSocket client
+and server implementation.
+
+Passes the quite extensive Autobahn test suite: [server][server-report],
+[client][client-report].
+
+**Note**: This module does not work in the browser. The client in the docs is a
+reference to a back end with the role of a client in the WebSocket
+communication. Browser clients must use the native
+[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) object.
+To make the same code work seamlessly on Node.js and the browser, you can use
+one of the many wrappers available on npm, like
+[isomorphic-ws](https://github.com/heineiuo/isomorphic-ws).
+
+## Table of Contents
+
+* [Protocol support](#protocol-support)
+* [Installing](#installing)
+ + [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance)
+* [API docs](#api-docs)
+* [WebSocket compression](#websocket-compression)
+* [Usage examples](#usage-examples)
+ + [Sending and receiving text data](#sending-and-receiving-text-data)
+ + [Sending binary data](#sending-binary-data)
+ + [Server example](#server-example)
+ + [Broadcast example](#broadcast-example)
+ + [ExpressJS example](#expressjs-example)
+ + [echo.websocket.org demo](#echowebsocketorg-demo)
+ + [Other examples](#other-examples)
+* [Error handling best practices](#error-handling-best-practices)
+* [FAQ](#faq)
+ + [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
+ + [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
+ + [How to connect via a proxy?](#how-to-connect-via-a-proxy)
+* [Changelog](#changelog)
+* [License](#license)
+
+## Protocol support
+
+* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
+* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`)
+
+## Installing
+
+```
+npm install --save ws
+```
+
+### Opt-in for performance and spec compliance
+
+There are 2 optional modules that can be installed along side with the ws
+module. These modules are binary addons which improve certain operations.
+Prebuilt binaries are available for the most popular platforms so you don't
+necessarily need to have a C++ compiler installed on your machine.
+
+- `npm install --save-optional bufferutil`: Allows to efficiently perform
+ operations such as masking and unmasking the data payload of the WebSocket
+ frames.
+- `npm install --save-optional utf-8-validate`: Allows to efficiently check
+ if a message contains valid UTF-8 as required by the spec.
+
+## API docs
+
+See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes.
+
+## WebSocket compression
+
+ws supports the [permessage-deflate extension][permessage-deflate] which
+enables the client and server to negotiate a compression algorithm and its
+parameters, and then selectively apply it to the data payloads of each
+WebSocket message.
+
+The extension is disabled by default on the server and enabled by default on
+the client. It adds a significant overhead in terms of performance and memory
+consumption so we suggest to enable it only if it is really needed.
+
+Note that Node.js has a variety of issues with high-performance compression,
+where increased concurrency, especially on Linux, can lead to
+[catastrophic memory fragmentation][node-zlib-bug] and slow performance.
+If you intend to use permessage-deflate in production, it is worthwhile to set
+up a test representative of your workload and ensure Node.js/zlib will handle
+it with acceptable performance and memory usage.
+
+Tuning of permessage-deflate can be done via the options defined below. You can
+also use `zlibDeflateOptions` and `zlibInflateOptions`, which is passed directly
+into the creation of [raw deflate/inflate streams][node-zlib-deflaterawdocs].
+
+See [the docs][ws-server-options] for more options.
+
+```js
+const WebSocket = require('ws');
+
+const wss = new WebSocket.Server({
+ port: 8080,
+ perMessageDeflate: {
+ zlibDeflateOptions: { // See zlib defaults.
+ chunkSize: 1024,
+ memLevel: 7,
+ level: 3,
+ },
+ zlibInflateOptions: {
+ chunkSize: 10 * 1024
+ },
+ // Other options settable:
+ clientNoContextTakeover: true, // Defaults to negotiated value.
+ serverNoContextTakeover: true, // Defaults to negotiated value.
+ clientMaxWindowBits: 10, // Defaults to negotiated value.
+ serverMaxWindowBits: 10, // Defaults to negotiated value.
+ // Below options specified as default values.
+ concurrencyLimit: 10, // Limits zlib concurrency for perf.
+ threshold: 1024, // Size (in bytes) below which messages
+ // should not be compressed.
+ }
+});
+```
+
+The client will only use the extension if it is supported and enabled on the
+server. To always disable the extension on the client set the
+`perMessageDeflate` option to `false`.
+
+```js
+const WebSocket = require('ws');
+
+const ws = new WebSocket('ws://www.host.com/path', {
+ perMessageDeflate: false
+});
+```
+
+## Usage examples
+
+### Sending and receiving text data
+
+```js
+const WebSocket = require('ws');
+
+const ws = new WebSocket('ws://www.host.com/path');
+
+ws.on('open', function open() {
+ ws.send('something');
+});
+
+ws.on('message', function incoming(data) {
+ console.log(data);
+});
+```
+
+### Sending binary data
+
+```js
+const WebSocket = require('ws');
+
+const ws = new WebSocket('ws://www.host.com/path');
+
+ws.on('open', function open() {
+ const array = new Float32Array(5);
+
+ for (var i = 0; i < array.length; ++i) {
+ array[i] = i / 2;
+ }
+
+ ws.send(array);
+});
+```
+
+### Server example
+
+```js
+const WebSocket = require('ws');
+
+const wss = new WebSocket.Server({ port: 8080 });
+
+wss.on('connection', function connection(ws) {
+ ws.on('message', function incoming(message) {
+ console.log('received: %s', message);
+ });
+
+ ws.send('something');
+});
+```
+
+### Broadcast example
+
+```js
+const WebSocket = require('ws');
+
+const wss = new WebSocket.Server({ port: 8080 });
+
+// Broadcast to all.
+wss.broadcast = function broadcast(data) {
+ wss.clients.forEach(function each(client) {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(data);
+ }
+ });
+};
+
+wss.on('connection', function connection(ws) {
+ ws.on('message', function incoming(data) {
+ // Broadcast to everyone else.
+ wss.clients.forEach(function each(client) {
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
+ client.send(data);
+ }
+ });
+ });
+});
+```
+
+### ExpressJS example
+
+```js
+const express = require('express');
+const http = require('http');
+const url = require('url');
+const WebSocket = require('ws');
+
+const app = express();
+
+app.use(function (req, res) {
+ res.send({ msg: "hello" });
+});
+
+const server = http.createServer(app);
+const wss = new WebSocket.Server({ server });
+
+wss.on('connection', function connection(ws, req) {
+ const location = url.parse(req.url, true);
+ // You might use location.query.access_token to authenticate or share sessions
+ // or req.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
+
+ ws.on('message', function incoming(message) {
+ console.log('received: %s', message);
+ });
+
+ ws.send('something');
+});
+
+server.listen(8080, function listening() {
+ console.log('Listening on %d', server.address().port);
+});
+```
+
+### echo.websocket.org demo
+
+```js
+const WebSocket = require('ws');
+
+const ws = new WebSocket('wss://echo.websocket.org/', {
+ origin: 'https://websocket.org'
+});
+
+ws.on('open', function open() {
+ console.log('connected');
+ ws.send(Date.now());
+});
+
+ws.on('close', function close() {
+ console.log('disconnected');
+});
+
+ws.on('message', function incoming(data) {
+ console.log(`Roundtrip time: ${Date.now() - data} ms`);
+
+ setTimeout(function timeout() {
+ ws.send(Date.now());
+ }, 500);
+});
+```
+
+### Other examples
+
+For a full example with a browser client communicating with a ws server, see the
+examples folder.
+
+Otherwise, see the test cases.
+
+## Error handling best practices
+
+```js
+// If the WebSocket is closed before the following send is attempted
+ws.send('something');
+
+// Errors (both immediate and async write errors) can be detected in an optional
+// callback. The callback is also the only way of being notified that data has
+// actually been sent.
+ws.send('something', function ack(error) {
+ // If error is not defined, the send has been completed, otherwise the error
+ // object will indicate what failed.
+});
+
+// Immediate errors can also be handled with `try...catch`, but **note** that
+// since sends are inherently asynchronous, socket write failures will *not* be
+// captured when this technique is used.
+try { ws.send('something'); }
+catch (e) { /* handle error */ }
+```
+
+## FAQ
+
+### How to get the IP address of the client?
+
+The remote IP address can be obtained from the raw socket.
+
+```js
+const WebSocket = require('ws');
+
+const wss = new WebSocket.Server({ port: 8080 });
+
+wss.on('connection', function connection(ws, req) {
+ const ip = req.connection.remoteAddress;
+});
+```
+
+When the server runs behind a proxy like NGINX, the de-facto standard is to use
+the `X-Forwarded-For` header.
+
+```js
+wss.on('connection', function connection(ws, req) {
+ const ip = req.headers['x-forwarded-for'];
+});
+```
+
+### How to detect and close broken connections?
+
+Sometimes the link between the server and the client can be interrupted in a
+way that keeps both the server and the client unaware of the broken state of the
+connection (e.g. when pulling the cord).
+
+In these cases ping messages can be used as a means to verify that the remote
+endpoint is still responsive.
+
+```js
+const WebSocket = require('ws');
+
+const wss = new WebSocket.Server({ port: 8080 });
+
+function noop() {}
+
+function heartbeat() {
+ this.isAlive = true;
+}
+
+wss.on('connection', function connection(ws) {
+ ws.isAlive = true;
+ ws.on('pong', heartbeat);
+});
+
+const interval = setInterval(function ping() {
+ wss.clients.forEach(function each(ws) {
+ if (ws.isAlive === false) return ws.terminate();
+
+ ws.isAlive = false;
+ ws.ping(noop);
+ });
+}, 30000);
+```
+
+Pong messages are automatically sent in response to ping messages as required
+by the spec.
+
+### How to connect via a proxy?
+
+Use a custom `http.Agent` implementation like [https-proxy-agent][] or
+[socks-proxy-agent][].
+
+## Changelog
+
+We're using the GitHub [releases][changelog] for changelog entries.
+
+## License
+
+[MIT](LICENSE)
+
+[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
+[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
+[client-report]: http://websockets.github.io/ws/autobahn/clients/
+[server-report]: http://websockets.github.io/ws/autobahn/servers/
+[permessage-deflate]: https://tools.ietf.org/html/rfc7692
+[changelog]: https://github.com/websockets/ws/releases
+[node-zlib-bug]: https://github.com/nodejs/node/issues/8871
+[node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
+[ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
diff --git a/node_modules/ws/index.js b/node_modules/ws/index.js
new file mode 100644
index 00000000..b8d6be1c
--- /dev/null
+++ b/node_modules/ws/index.js
@@ -0,0 +1,9 @@
+'use strict';
+
+const WebSocket = require('./lib/websocket');
+
+WebSocket.Server = require('./lib/websocket-server');
+WebSocket.Receiver = require('./lib/receiver');
+WebSocket.Sender = require('./lib/sender');
+
+module.exports = WebSocket;
diff --git a/node_modules/ws/lib/buffer-util.js b/node_modules/ws/lib/buffer-util.js
new file mode 100644
index 00000000..5ab9e289
--- /dev/null
+++ b/node_modules/ws/lib/buffer-util.js
@@ -0,0 +1,65 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
+/**
+ * Merges an array of buffers into a new buffer.
+ *
+ * @param {Buffer[]} list The array of buffers to concat
+ * @param {Number} totalLength The total length of buffers in the list
+ * @return {Buffer} The resulting buffer
+ * @public
+ */
+const concat = (list, totalLength) => {
+ const target = Buffer.allocUnsafe(totalLength);
+ var offset = 0;
+
+ for (var i = 0; i < list.length; i++) {
+ const buf = list[i];
+ buf.copy(target, offset);
+ offset += buf.length;
+ }
+
+ return target;
+};
+
+try {
+ const bufferUtil = require('bufferutil');
+
+ module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
+} catch (e) /* istanbul ignore next */ {
+ /**
+ * Masks a buffer using the given mask.
+ *
+ * @param {Buffer} source The buffer to mask
+ * @param {Buffer} mask The mask to use
+ * @param {Buffer} output The buffer where to store the result
+ * @param {Number} offset The offset at which to start writing
+ * @param {Number} length The number of bytes to mask.
+ * @public
+ */
+ const mask = (source, mask, output, offset, length) => {
+ for (var i = 0; i < length; i++) {
+ output[offset + i] = source[i] ^ mask[i & 3];
+ }
+ };
+
+ /**
+ * Unmasks a buffer using the given mask.
+ *
+ * @param {Buffer} buffer The buffer to unmask
+ * @param {Buffer} mask The mask to use
+ * @public
+ */
+ const unmask = (buffer, mask) => {
+ // Required until https://github.com/nodejs/node/issues/9006 is resolved.
+ const length = buffer.length;
+ for (var i = 0; i < length; i++) {
+ buffer[i] ^= mask[i & 3];
+ }
+ };
+
+ module.exports = { concat, mask, unmask };
+}
diff --git a/node_modules/ws/lib/constants.js b/node_modules/ws/lib/constants.js
new file mode 100644
index 00000000..39044146
--- /dev/null
+++ b/node_modules/ws/lib/constants.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+
+const Buffer = safeBuffer.Buffer;
+
+exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
+exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
+exports.EMPTY_BUFFER = Buffer.alloc(0);
+exports.NOOP = () => {};
diff --git a/node_modules/ws/lib/event-target.js b/node_modules/ws/lib/event-target.js
new file mode 100644
index 00000000..574e9080
--- /dev/null
+++ b/node_modules/ws/lib/event-target.js
@@ -0,0 +1,170 @@
+'use strict';
+
+/**
+ * Class representing an event.
+ *
+ * @private
+ */
+class Event {
+ /**
+ * Create a new `Event`.
+ *
+ * @param {String} type The name of the event
+ * @param {Object} target A reference to the target to which the event was dispatched
+ */
+ constructor (type, target) {
+ this.target = target;
+ this.type = type;
+ }
+}
+
+/**
+ * Class representing a message event.
+ *
+ * @extends Event
+ * @private
+ */
+class MessageEvent extends Event {
+ /**
+ * Create a new `MessageEvent`.
+ *
+ * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
+ */
+ constructor (data, target) {
+ super('message', target);
+
+ this.data = data;
+ }
+}
+
+/**
+ * Class representing a close event.
+ *
+ * @extends Event
+ * @private
+ */
+class CloseEvent extends Event {
+ /**
+ * Create a new `CloseEvent`.
+ *
+ * @param {Number} code The status code explaining why the connection is being closed
+ * @param {String} reason A human-readable string explaining why the connection is closing
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
+ */
+ constructor (code, reason, target) {
+ super('close', target);
+
+ this.wasClean = target._closeFrameReceived && target._closeFrameSent;
+ this.reason = reason;
+ this.code = code;
+ }
+}
+
+/**
+ * Class representing an open event.
+ *
+ * @extends Event
+ * @private
+ */
+class OpenEvent extends Event {
+ /**
+ * Create a new `OpenEvent`.
+ *
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
+ */
+ constructor (target) {
+ super('open', target);
+ }
+}
+
+/**
+ * Class representing an error event.
+ *
+ * @extends Event
+ * @private
+ */
+class ErrorEvent extends Event {
+ /**
+ * Create a new `ErrorEvent`.
+ *
+ * @param {Object} error The error that generated this event
+ * @param {WebSocket} target A reference to the target to which the event was dispatched
+ */
+ constructor (error, target) {
+ super('error', target);
+
+ this.message = error.message;
+ this.error = error;
+ }
+}
+
+/**
+ * This provides methods for emulating the `EventTarget` interface. It's not
+ * meant to be used directly.
+ *
+ * @mixin
+ */
+const EventTarget = {
+ /**
+ * Register an event listener.
+ *
+ * @param {String} method A string representing the event type to listen for
+ * @param {Function} listener The listener to add
+ * @public
+ */
+ addEventListener (method, listener) {
+ if (typeof listener !== 'function') return;
+
+ function onMessage (data) {
+ listener.call(this, new MessageEvent(data, this));
+ }
+
+ function onClose (code, message) {
+ listener.call(this, new CloseEvent(code, message, this));
+ }
+
+ function onError (error) {
+ listener.call(this, new ErrorEvent(error, this));
+ }
+
+ function onOpen () {
+ listener.call(this, new OpenEvent(this));
+ }
+
+ if (method === 'message') {
+ onMessage._listener = listener;
+ this.on(method, onMessage);
+ } else if (method === 'close') {
+ onClose._listener = listener;
+ this.on(method, onClose);
+ } else if (method === 'error') {
+ onError._listener = listener;
+ this.on(method, onError);
+ } else if (method === 'open') {
+ onOpen._listener = listener;
+ this.on(method, onOpen);
+ } else {
+ this.on(method, listener);
+ }
+ },
+
+ /**
+ * Remove an event listener.
+ *
+ * @param {String} method A string representing the event type to remove
+ * @param {Function} listener The listener to remove
+ * @public
+ */
+ removeEventListener (method, listener) {
+ const listeners = this.listeners(method);
+
+ for (var i = 0; i < listeners.length; i++) {
+ if (listeners[i] === listener || listeners[i]._listener === listener) {
+ this.removeListener(method, listeners[i]);
+ }
+ }
+ }
+};
+
+module.exports = EventTarget;
diff --git a/node_modules/ws/lib/extension.js b/node_modules/ws/lib/extension.js
new file mode 100644
index 00000000..3f48d751
--- /dev/null
+++ b/node_modules/ws/lib/extension.js
@@ -0,0 +1,211 @@
+'use strict';
+
+//
+// Allowed token characters:
+//
+// '!', '#', '$', '%', '&', ''', '*', '+', '-',
+// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
+//
+// tokenChars[32] === 0 // ' '
+// tokenChars[33] === 1 // '!'
+// tokenChars[34] === 0 // '"'
+// ...
+//
+const tokenChars = [
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
+ 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127
+];
+
+/**
+ * Adds an offer to the map of extension offers or a parameter to the map of
+ * parameters.
+ *
+ * @param {Object} dest The map of extension offers or parameters
+ * @param {String} name The extension or parameter name
+ * @param {(Object|Boolean|String)} elem The extension parameters or the
+ * parameter value
+ * @private
+ */
+function push (dest, name, elem) {
+ if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
+ else dest[name] = [elem];
+}
+
+/**
+ * Parses the `Sec-WebSocket-Extensions` header into an object.
+ *
+ * @param {String} header The field value of the header
+ * @return {Object} The parsed object
+ * @public
+ */
+function parse (header) {
+ const offers = {};
+
+ if (header === undefined || header === '') return offers;
+
+ var params = {};
+ var mustUnescape = false;
+ var isEscaping = false;
+ var inQuotes = false;
+ var extensionName;
+ var paramName;
+ var start = -1;
+ var end = -1;
+
+ for (var i = 0; i < header.length; i++) {
+ const code = header.charCodeAt(i);
+
+ if (extensionName === undefined) {
+ if (end === -1 && tokenChars[code] === 1) {
+ if (start === -1) start = i;
+ } else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) {
+ if (end === -1 && start !== -1) end = i;
+ } else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) {
+ if (start === -1) {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+
+ if (end === -1) end = i;
+ const name = header.slice(start, end);
+ if (code === 0x2c) {
+ push(offers, name, params);
+ params = {};
+ } else {
+ extensionName = name;
+ }
+
+ start = end = -1;
+ } else {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+ } else if (paramName === undefined) {
+ if (end === -1 && tokenChars[code] === 1) {
+ if (start === -1) start = i;
+ } else if (code === 0x20 || code === 0x09) {
+ if (end === -1 && start !== -1) end = i;
+ } else if (code === 0x3b || code === 0x2c) {
+ if (start === -1) {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+
+ if (end === -1) end = i;
+ push(params, header.slice(start, end), true);
+ if (code === 0x2c) {
+ push(offers, extensionName, params);
+ params = {};
+ extensionName = undefined;
+ }
+
+ start = end = -1;
+ } else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) {
+ paramName = header.slice(start, i);
+ start = end = -1;
+ } else {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+ } else {
+ //
+ // The value of a quoted-string after unescaping must conform to the
+ // token ABNF, so only token characters are valid.
+ // Ref: https://tools.ietf.org/html/rfc6455#section-9.1
+ //
+ if (isEscaping) {
+ if (tokenChars[code] !== 1) {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+ if (start === -1) start = i;
+ else if (!mustUnescape) mustUnescape = true;
+ isEscaping = false;
+ } else if (inQuotes) {
+ if (tokenChars[code] === 1) {
+ if (start === -1) start = i;
+ } else if (code === 0x22/* '"' */ && start !== -1) {
+ inQuotes = false;
+ end = i;
+ } else if (code === 0x5c/* '\' */) {
+ isEscaping = true;
+ } else {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+ } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
+ inQuotes = true;
+ } else if (end === -1 && tokenChars[code] === 1) {
+ if (start === -1) start = i;
+ } else if (start !== -1 && (code === 0x20 || code === 0x09)) {
+ if (end === -1) end = i;
+ } else if (code === 0x3b || code === 0x2c) {
+ if (start === -1) {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+
+ if (end === -1) end = i;
+ var value = header.slice(start, end);
+ if (mustUnescape) {
+ value = value.replace(/\\/g, '');
+ mustUnescape = false;
+ }
+ push(params, paramName, value);
+ if (code === 0x2c) {
+ push(offers, extensionName, params);
+ params = {};
+ extensionName = undefined;
+ }
+
+ paramName = undefined;
+ start = end = -1;
+ } else {
+ throw new SyntaxError(`Unexpected character at index ${i}`);
+ }
+ }
+ }
+
+ if (start === -1 || inQuotes) {
+ throw new SyntaxError('Unexpected end of input');
+ }
+
+ if (end === -1) end = i;
+ const token = header.slice(start, end);
+ if (extensionName === undefined) {
+ push(offers, token, {});
+ } else {
+ if (paramName === undefined) {
+ push(params, token, true);
+ } else if (mustUnescape) {
+ push(params, paramName, token.replace(/\\/g, ''));
+ } else {
+ push(params, paramName, token);
+ }
+ push(offers, extensionName, params);
+ }
+
+ return offers;
+}
+
+/**
+ * Builds the `Sec-WebSocket-Extensions` header field value.
+ *
+ * @param {Object} extensions The map of extensions and parameters to format
+ * @return {String} A string representing the given object
+ * @public
+ */
+function format (extensions) {
+ return Object.keys(extensions).map((extension) => {
+ var configurations = extensions[extension];
+ if (!Array.isArray(configurations)) configurations = [configurations];
+ return configurations.map((params) => {
+ return [extension].concat(Object.keys(params).map((k) => {
+ var values = params[k];
+ if (!Array.isArray(values)) values = [values];
+ return values.map((v) => v === true ? k : `${k}=${v}`).join('; ');
+ })).join('; ');
+ }).join(', ');
+ }).join(', ');
+}
+
+module.exports = { format, parse };
diff --git a/node_modules/ws/lib/permessage-deflate.js b/node_modules/ws/lib/permessage-deflate.js
new file mode 100644
index 00000000..cabab971
--- /dev/null
+++ b/node_modules/ws/lib/permessage-deflate.js
@@ -0,0 +1,521 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+const Limiter = require('async-limiter');
+const zlib = require('zlib');
+
+const bufferUtil = require('./buffer-util');
+
+const Buffer = safeBuffer.Buffer;
+
+const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
+const EMPTY_BLOCK = Buffer.from([0x00]);
+
+const kWriteInProgress = Symbol('write-in-progress');
+const kPendingClose = Symbol('pending-close');
+const kTotalLength = Symbol('total-length');
+const kCallback = Symbol('callback');
+const kBuffers = Symbol('buffers');
+const kError = Symbol('error');
+const kOwner = Symbol('owner');
+
+//
+// We limit zlib concurrency, which prevents severe memory fragmentation
+// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
+// and https://github.com/websockets/ws/issues/1202
+//
+// Intentionally global; it's the global thread pool that's an issue.
+//
+let zlibLimiter;
+
+/**
+ * permessage-deflate implementation.
+ */
+class PerMessageDeflate {
+ /**
+ * Creates a PerMessageDeflate instance.
+ *
+ * @param {Object} options Configuration options
+ * @param {Boolean} options.serverNoContextTakeover Request/accept disabling
+ * of server context takeover
+ * @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
+ * disabling of client context takeover
+ * @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
+ * use of a custom server window size
+ * @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
+ * for, or request, a custom client window size
+ * @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate
+ * @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate
+ * @param {Number} options.threshold Size (in bytes) below which messages
+ * should not be compressed
+ * @param {Number} options.concurrencyLimit The number of concurrent calls to
+ * zlib
+ * @param {Boolean} isServer Create the instance in either server or client
+ * mode
+ * @param {Number} maxPayload The maximum allowed message length
+ */
+ constructor (options, isServer, maxPayload) {
+ this._maxPayload = maxPayload | 0;
+ this._options = options || {};
+ this._threshold = this._options.threshold !== undefined
+ ? this._options.threshold
+ : 1024;
+ this._isServer = !!isServer;
+ this._deflate = null;
+ this._inflate = null;
+
+ this.params = null;
+
+ if (!zlibLimiter) {
+ const concurrency = this._options.concurrencyLimit !== undefined
+ ? this._options.concurrencyLimit
+ : 10;
+ zlibLimiter = new Limiter({ concurrency });
+ }
+ }
+
+ /**
+ * @type {String}
+ */
+ static get extensionName () {
+ return 'permessage-deflate';
+ }
+
+ /**
+ * Create an extension negotiation offer.
+ *
+ * @return {Object} Extension parameters
+ * @public
+ */
+ offer () {
+ const params = {};
+
+ if (this._options.serverNoContextTakeover) {
+ params.server_no_context_takeover = true;
+ }
+ if (this._options.clientNoContextTakeover) {
+ params.client_no_context_takeover = true;
+ }
+ if (this._options.serverMaxWindowBits) {
+ params.server_max_window_bits = this._options.serverMaxWindowBits;
+ }
+ if (this._options.clientMaxWindowBits) {
+ params.client_max_window_bits = this._options.clientMaxWindowBits;
+ } else if (this._options.clientMaxWindowBits == null) {
+ params.client_max_window_bits = true;
+ }
+
+ return params;
+ }
+
+ /**
+ * Accept an extension negotiation offer/response.
+ *
+ * @param {Array} configurations The extension negotiation offers/reponse
+ * @return {Object} Accepted configuration
+ * @public
+ */
+ accept (configurations) {
+ configurations = this.normalizeParams(configurations);
+
+ this.params = this._isServer
+ ? this.acceptAsServer(configurations)
+ : this.acceptAsClient(configurations);
+
+ return this.params;
+ }
+
+ /**
+ * Releases all resources used by the extension.
+ *
+ * @public
+ */
+ cleanup () {
+ if (this._inflate) {
+ if (this._inflate[kWriteInProgress]) {
+ this._inflate[kPendingClose] = true;
+ } else {
+ this._inflate.close();
+ this._inflate = null;
+ }
+ }
+ if (this._deflate) {
+ if (this._deflate[kWriteInProgress]) {
+ this._deflate[kPendingClose] = true;
+ } else {
+ this._deflate.close();
+ this._deflate = null;
+ }
+ }
+ }
+
+ /**
+ * Accept an extension negotiation offer.
+ *
+ * @param {Array} offers The extension negotiation offers
+ * @return {Object} Accepted configuration
+ * @private
+ */
+ acceptAsServer (offers) {
+ const opts = this._options;
+ const accepted = offers.find((params) => {
+ if (
+ (opts.serverNoContextTakeover === false &&
+ params.server_no_context_takeover) ||
+ (params.server_max_window_bits &&
+ (opts.serverMaxWindowBits === false ||
+ (typeof opts.serverMaxWindowBits === 'number' &&
+ opts.serverMaxWindowBits > params.server_max_window_bits))) ||
+ (typeof opts.clientMaxWindowBits === 'number' &&
+ !params.client_max_window_bits)
+ ) {
+ return false;
+ }
+
+ return true;
+ });
+
+ if (!accepted) {
+ throw new Error('None of the extension offers can be accepted');
+ }
+
+ if (opts.serverNoContextTakeover) {
+ accepted.server_no_context_takeover = true;
+ }
+ if (opts.clientNoContextTakeover) {
+ accepted.client_no_context_takeover = true;
+ }
+ if (typeof opts.serverMaxWindowBits === 'number') {
+ accepted.server_max_window_bits = opts.serverMaxWindowBits;
+ }
+ if (typeof opts.clientMaxWindowBits === 'number') {
+ accepted.client_max_window_bits = opts.clientMaxWindowBits;
+ } else if (
+ accepted.client_max_window_bits === true ||
+ opts.clientMaxWindowBits === false
+ ) {
+ delete accepted.client_max_window_bits;
+ }
+
+ return accepted;
+ }
+
+ /**
+ * Accept the extension negotiation response.
+ *
+ * @param {Array} response The extension negotiation response
+ * @return {Object} Accepted configuration
+ * @private
+ */
+ acceptAsClient (response) {
+ const params = response[0];
+
+ if (
+ this._options.clientNoContextTakeover === false &&
+ params.client_no_context_takeover
+ ) {
+ throw new Error('Unexpected parameter "client_no_context_takeover"');
+ }
+
+ if (!params.client_max_window_bits) {
+ if (typeof this._options.clientMaxWindowBits === 'number') {
+ params.client_max_window_bits = this._options.clientMaxWindowBits;
+ }
+ } else if (
+ this._options.clientMaxWindowBits === false ||
+ (typeof this._options.clientMaxWindowBits === 'number' &&
+ params.client_max_window_bits > this._options.clientMaxWindowBits)
+ ) {
+ throw new Error(
+ 'Unexpected or invalid parameter "client_max_window_bits"'
+ );
+ }
+
+ return params;
+ }
+
+ /**
+ * Normalize parameters.
+ *
+ * @param {Array} configurations The extension negotiation offers/reponse
+ * @return {Array} The offers/response with normalized parameters
+ * @private
+ */
+ normalizeParams (configurations) {
+ configurations.forEach((params) => {
+ Object.keys(params).forEach((key) => {
+ var value = params[key];
+
+ if (value.length > 1) {
+ throw new Error(`Parameter "${key}" must have only a single value`);
+ }
+
+ value = value[0];
+
+ if (key === 'client_max_window_bits') {
+ if (value !== true) {
+ const num = +value;
+ if (!Number.isInteger(num) || num < 8 || num > 15) {
+ throw new TypeError(
+ `Invalid value for parameter "${key}": ${value}`
+ );
+ }
+ value = num;
+ } else if (!this._isServer) {
+ throw new TypeError(
+ `Invalid value for parameter "${key}": ${value}`
+ );
+ }
+ } else if (key === 'server_max_window_bits') {
+ const num = +value;
+ if (!Number.isInteger(num) || num < 8 || num > 15) {
+ throw new TypeError(
+ `Invalid value for parameter "${key}": ${value}`
+ );
+ }
+ value = num;
+ } else if (
+ key === 'client_no_context_takeover' ||
+ key === 'server_no_context_takeover'
+ ) {
+ if (value !== true) {
+ throw new TypeError(
+ `Invalid value for parameter "${key}": ${value}`
+ );
+ }
+ } else {
+ throw new Error(`Unknown parameter "${key}"`);
+ }
+
+ params[key] = value;
+ });
+ });
+
+ return configurations;
+ }
+
+ /**
+ * Decompress data. Concurrency limited by async-limiter.
+ *
+ * @param {Buffer} data Compressed data
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
+ * @param {Function} callback Callback
+ * @public
+ */
+ decompress (data, fin, callback) {
+ zlibLimiter.push((done) => {
+ this._decompress(data, fin, (err, result) => {
+ done();
+ callback(err, result);
+ });
+ });
+ }
+
+ /**
+ * Compress data. Concurrency limited by async-limiter.
+ *
+ * @param {Buffer} data Data to compress
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
+ * @param {Function} callback Callback
+ * @public
+ */
+ compress (data, fin, callback) {
+ zlibLimiter.push((done) => {
+ this._compress(data, fin, (err, result) => {
+ done();
+ callback(err, result);
+ });
+ });
+ }
+
+ /**
+ * Decompress data.
+ *
+ * @param {Buffer} data Compressed data
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
+ * @param {Function} callback Callback
+ * @private
+ */
+ _decompress (data, fin, callback) {
+ const endpoint = this._isServer ? 'client' : 'server';
+
+ if (!this._inflate) {
+ const key = `${endpoint}_max_window_bits`;
+ const windowBits = typeof this.params[key] !== 'number'
+ ? zlib.Z_DEFAULT_WINDOWBITS
+ : this.params[key];
+
+ this._inflate = zlib.createInflateRaw(
+ Object.assign(
+ {},
+ this._options.zlibInflateOptions,
+ { windowBits }
+ )
+ );
+ this._inflate[kTotalLength] = 0;
+ this._inflate[kBuffers] = [];
+ this._inflate[kOwner] = this;
+ this._inflate.on('error', inflateOnError);
+ this._inflate.on('data', inflateOnData);
+ }
+
+ this._inflate[kCallback] = callback;
+ this._inflate[kWriteInProgress] = true;
+
+ this._inflate.write(data);
+ if (fin) this._inflate.write(TRAILER);
+
+ this._inflate.flush(() => {
+ const err = this._inflate[kError];
+
+ if (err) {
+ this._inflate.close();
+ this._inflate = null;
+ callback(err);
+ return;
+ }
+
+ const data = bufferUtil.concat(
+ this._inflate[kBuffers],
+ this._inflate[kTotalLength]
+ );
+
+ if (
+ (fin && this.params[`${endpoint}_no_context_takeover`]) ||
+ this._inflate[kPendingClose]
+ ) {
+ this._inflate.close();
+ this._inflate = null;
+ } else {
+ this._inflate[kWriteInProgress] = false;
+ this._inflate[kTotalLength] = 0;
+ this._inflate[kBuffers] = [];
+ }
+
+ callback(null, data);
+ });
+ }
+
+ /**
+ * Compress data.
+ *
+ * @param {Buffer} data Data to compress
+ * @param {Boolean} fin Specifies whether or not this is the last fragment
+ * @param {Function} callback Callback
+ * @private
+ */
+ _compress (data, fin, callback) {
+ if (!data || data.length === 0) {
+ process.nextTick(callback, null, EMPTY_BLOCK);
+ return;
+ }
+
+ const endpoint = this._isServer ? 'server' : 'client';
+
+ if (!this._deflate) {
+ const key = `${endpoint}_max_window_bits`;
+ const windowBits = typeof this.params[key] !== 'number'
+ ? zlib.Z_DEFAULT_WINDOWBITS
+ : this.params[key];
+
+ this._deflate = zlib.createDeflateRaw(
+ Object.assign(
+ // TODO deprecate memLevel/level and recommend zlibDeflateOptions instead
+ {
+ memLevel: this._options.memLevel,
+ level: this._options.level
+ },
+ this._options.zlibDeflateOptions,
+ { windowBits }
+ )
+ );
+
+ this._deflate[kTotalLength] = 0;
+ this._deflate[kBuffers] = [];
+
+ //
+ // `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use
+ // it is made after it has already been closed. This cannot happen here,
+ // so we only add a listener for the `'data'` event.
+ //
+ this._deflate.on('data', deflateOnData);
+ }
+
+ this._deflate[kWriteInProgress] = true;
+
+ this._deflate.write(data);
+ this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
+ var data = bufferUtil.concat(
+ this._deflate[kBuffers],
+ this._deflate[kTotalLength]
+ );
+
+ if (fin) data = data.slice(0, data.length - 4);
+
+ if (
+ (fin && this.params[`${endpoint}_no_context_takeover`]) ||
+ this._deflate[kPendingClose]
+ ) {
+ this._deflate.close();
+ this._deflate = null;
+ } else {
+ this._deflate[kWriteInProgress] = false;
+ this._deflate[kTotalLength] = 0;
+ this._deflate[kBuffers] = [];
+ }
+
+ callback(null, data);
+ });
+ }
+}
+
+module.exports = PerMessageDeflate;
+
+/**
+ * The listener of the `zlib.DeflateRaw` stream `'data'` event.
+ *
+ * @param {Buffer} chunk A chunk of data
+ * @private
+ */
+function deflateOnData (chunk) {
+ this[kBuffers].push(chunk);
+ this[kTotalLength] += chunk.length;
+}
+
+/**
+ * The listener of the `zlib.InflateRaw` stream `'data'` event.
+ *
+ * @param {Buffer} chunk A chunk of data
+ * @private
+ */
+function inflateOnData (chunk) {
+ this[kTotalLength] += chunk.length;
+
+ if (
+ this[kOwner]._maxPayload < 1 ||
+ this[kTotalLength] <= this[kOwner]._maxPayload
+ ) {
+ this[kBuffers].push(chunk);
+ return;
+ }
+
+ this[kError] = new RangeError('Max payload size exceeded');
+ this[kError].closeCode = 1009;
+ this.removeListener('data', inflateOnData);
+ this.reset();
+}
+
+/**
+ * The listener of the `zlib.InflateRaw` stream `'error'` event.
+ *
+ * @param {Error} err The emitted error
+ * @private
+ */
+function inflateOnError (err) {
+ //
+ // There is no need to call `Zlib#close()` as the handle is automatically
+ // closed when an error is emitted.
+ //
+ this[kOwner]._inflate = null;
+ this[kCallback](err);
+}
diff --git a/node_modules/ws/lib/receiver.js b/node_modules/ws/lib/receiver.js
new file mode 100644
index 00000000..ad1187b8
--- /dev/null
+++ b/node_modules/ws/lib/receiver.js
@@ -0,0 +1,589 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+
+const PerMessageDeflate = require('./permessage-deflate');
+const bufferUtil = require('./buffer-util');
+const validation = require('./validation');
+const constants = require('./constants');
+
+const Buffer = safeBuffer.Buffer;
+
+const GET_INFO = 0;
+const GET_PAYLOAD_LENGTH_16 = 1;
+const GET_PAYLOAD_LENGTH_64 = 2;
+const GET_MASK = 3;
+const GET_DATA = 4;
+const INFLATING = 5;
+
+/**
+ * HyBi Receiver implementation.
+ */
+class Receiver {
+ /**
+ * Creates a Receiver instance.
+ *
+ * @param {Object} extensions An object containing the negotiated extensions
+ * @param {Number} maxPayload The maximum allowed message length
+ * @param {String} binaryType The type for binary data
+ */
+ constructor (extensions, maxPayload, binaryType) {
+ this._binaryType = binaryType || constants.BINARY_TYPES[0];
+ this._extensions = extensions || {};
+ this._maxPayload = maxPayload | 0;
+
+ this._bufferedBytes = 0;
+ this._buffers = [];
+
+ this._compressed = false;
+ this._payloadLength = 0;
+ this._fragmented = 0;
+ this._masked = false;
+ this._fin = false;
+ this._mask = null;
+ this._opcode = 0;
+
+ this._totalPayloadLength = 0;
+ this._messageLength = 0;
+ this._fragments = [];
+
+ this._cleanupCallback = null;
+ this._isCleaningUp = false;
+ this._hadError = false;
+ this._loop = false;
+
+ this.add = this.add.bind(this);
+ this.onmessage = null;
+ this.onclose = null;
+ this.onerror = null;
+ this.onping = null;
+ this.onpong = null;
+
+ this._state = GET_INFO;
+ }
+
+ /**
+ * Consumes `n` bytes from the buffered data, calls `cleanup` if necessary.
+ *
+ * @param {Number} n The number of bytes to consume
+ * @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not
+ * available
+ * @private
+ */
+ consume (n) {
+ if (this._bufferedBytes < n) {
+ this._loop = false;
+ if (this._isCleaningUp) this.cleanup(this._cleanupCallback);
+ return null;
+ }
+
+ this._bufferedBytes -= n;
+
+ if (n === this._buffers[0].length) return this._buffers.shift();
+
+ if (n < this._buffers[0].length) {
+ const buf = this._buffers[0];
+ this._buffers[0] = buf.slice(n);
+ return buf.slice(0, n);
+ }
+
+ const dst = Buffer.allocUnsafe(n);
+
+ do {
+ const buf = this._buffers[0];
+
+ if (n >= buf.length) {
+ this._buffers.shift().copy(dst, dst.length - n);
+ } else {
+ buf.copy(dst, dst.length - n, 0, n);
+ this._buffers[0] = buf.slice(n);
+ }
+
+ n -= buf.length;
+ } while (n > 0);
+
+ return dst;
+ }
+
+ /**
+ * Adds new data to the parser.
+ *
+ * @param {Buffer} chunk A chunk of data
+ * @public
+ */
+ add (chunk) {
+ this._bufferedBytes += chunk.length;
+ this._buffers.push(chunk);
+ this.startLoop();
+ }
+
+ /**
+ * Starts the parsing loop.
+ *
+ * @private
+ */
+ startLoop () {
+ this._loop = true;
+
+ do {
+ switch (this._state) {
+ case GET_INFO:
+ this.getInfo();
+ break;
+ case GET_PAYLOAD_LENGTH_16:
+ this.getPayloadLength16();
+ break;
+ case GET_PAYLOAD_LENGTH_64:
+ this.getPayloadLength64();
+ break;
+ case GET_MASK:
+ this.getMask();
+ break;
+ case GET_DATA:
+ this.getData();
+ break;
+ default: // `INFLATING`
+ this._loop = false;
+ }
+ } while (this._loop);
+ }
+
+ /**
+ * Reads the first two bytes of a frame.
+ *
+ * @private
+ */
+ getInfo () {
+ const buf = this.consume(2);
+ if (buf === null) return;
+
+ if ((buf[0] & 0x30) !== 0x00) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'),
+ 1002
+ );
+ return;
+ }
+
+ const compressed = (buf[0] & 0x40) === 0x40;
+
+ if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
+ 1002
+ );
+ return;
+ }
+
+ this._fin = (buf[0] & 0x80) === 0x80;
+ this._opcode = buf[0] & 0x0f;
+ this._payloadLength = buf[1] & 0x7f;
+
+ if (this._opcode === 0x00) {
+ if (compressed) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
+ 1002
+ );
+ return;
+ }
+
+ if (!this._fragmented) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: invalid opcode 0'),
+ 1002
+ );
+ return;
+ } else {
+ this._opcode = this._fragmented;
+ }
+ } else if (this._opcode === 0x01 || this._opcode === 0x02) {
+ if (this._fragmented) {
+ this.error(
+ new RangeError(
+ `Invalid WebSocket frame: invalid opcode ${this._opcode}`
+ ),
+ 1002
+ );
+ return;
+ }
+
+ this._compressed = compressed;
+ } else if (this._opcode > 0x07 && this._opcode < 0x0b) {
+ if (!this._fin) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: FIN must be set'),
+ 1002
+ );
+ return;
+ }
+
+ if (compressed) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
+ 1002
+ );
+ return;
+ }
+
+ if (this._payloadLength > 0x7d) {
+ this.error(
+ new RangeError(
+ `Invalid WebSocket frame: invalid payload length ` +
+ `${this._payloadLength}`
+ ),
+ 1002
+ );
+ return;
+ }
+ } else {
+ this.error(
+ new RangeError(
+ `Invalid WebSocket frame: invalid opcode ${this._opcode}`
+ ),
+ 1002
+ );
+ return;
+ }
+
+ if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
+
+ this._masked = (buf[1] & 0x80) === 0x80;
+
+ if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
+ else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
+ else this.haveLength();
+ }
+
+ /**
+ * Gets extended payload length (7+16).
+ *
+ * @private
+ */
+ getPayloadLength16 () {
+ const buf = this.consume(2);
+ if (buf === null) return;
+
+ this._payloadLength = buf.readUInt16BE(0, true);
+ this.haveLength();
+ }
+
+ /**
+ * Gets extended payload length (7+64).
+ *
+ * @private
+ */
+ getPayloadLength64 () {
+ const buf = this.consume(8);
+ if (buf === null) return;
+
+ const num = buf.readUInt32BE(0, true);
+
+ //
+ // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
+ // if payload length is greater than this number.
+ //
+ if (num > Math.pow(2, 53 - 32) - 1) {
+ this.error(
+ new RangeError(
+ 'Unsupported WebSocket frame: payload length > 2^53 - 1'
+ ),
+ 1009
+ );
+ return;
+ }
+
+ this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
+ this.haveLength();
+ }
+
+ /**
+ * Payload length has been read.
+ *
+ * @private
+ */
+ haveLength () {
+ if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) {
+ return;
+ }
+
+ if (this._masked) this._state = GET_MASK;
+ else this._state = GET_DATA;
+ }
+
+ /**
+ * Reads mask bytes.
+ *
+ * @private
+ */
+ getMask () {
+ this._mask = this.consume(4);
+ if (this._mask === null) return;
+
+ this._state = GET_DATA;
+ }
+
+ /**
+ * Reads data bytes.
+ *
+ * @private
+ */
+ getData () {
+ var data = constants.EMPTY_BUFFER;
+
+ if (this._payloadLength) {
+ data = this.consume(this._payloadLength);
+ if (data === null) return;
+
+ if (this._masked) bufferUtil.unmask(data, this._mask);
+ }
+
+ if (this._opcode > 0x07) {
+ this.controlMessage(data);
+ } else if (this._compressed) {
+ this._state = INFLATING;
+ this.decompress(data);
+ } else if (this.pushFragment(data)) {
+ this.dataMessage();
+ }
+ }
+
+ /**
+ * Decompresses data.
+ *
+ * @param {Buffer} data Compressed data
+ * @private
+ */
+ decompress (data) {
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
+
+ perMessageDeflate.decompress(data, this._fin, (err, buf) => {
+ if (err) {
+ this.error(err, err.closeCode === 1009 ? 1009 : 1007);
+ return;
+ }
+
+ if (this.pushFragment(buf)) this.dataMessage();
+ this.startLoop();
+ });
+ }
+
+ /**
+ * Handles a data message.
+ *
+ * @private
+ */
+ dataMessage () {
+ if (this._fin) {
+ const messageLength = this._messageLength;
+ const fragments = this._fragments;
+
+ this._totalPayloadLength = 0;
+ this._messageLength = 0;
+ this._fragmented = 0;
+ this._fragments = [];
+
+ if (this._opcode === 2) {
+ var data;
+
+ if (this._binaryType === 'nodebuffer') {
+ data = toBuffer(fragments, messageLength);
+ } else if (this._binaryType === 'arraybuffer') {
+ data = toArrayBuffer(toBuffer(fragments, messageLength));
+ } else {
+ data = fragments;
+ }
+
+ this.onmessage(data);
+ } else {
+ const buf = toBuffer(fragments, messageLength);
+
+ if (!validation.isValidUTF8(buf)) {
+ this.error(
+ new Error('Invalid WebSocket frame: invalid UTF-8 sequence'),
+ 1007
+ );
+ return;
+ }
+
+ this.onmessage(buf.toString());
+ }
+ }
+
+ this._state = GET_INFO;
+ }
+
+ /**
+ * Handles a control message.
+ *
+ * @param {Buffer} data Data to handle
+ * @private
+ */
+ controlMessage (data) {
+ if (this._opcode === 0x08) {
+ if (data.length === 0) {
+ this._loop = false;
+ this.onclose(1005, '');
+ this.cleanup(this._cleanupCallback);
+ } else if (data.length === 1) {
+ this.error(
+ new RangeError('Invalid WebSocket frame: invalid payload length 1'),
+ 1002
+ );
+ } else {
+ const code = data.readUInt16BE(0, true);
+
+ if (!validation.isValidStatusCode(code)) {
+ this.error(
+ new RangeError(
+ `Invalid WebSocket frame: invalid status code ${code}`
+ ),
+ 1002
+ );
+ return;
+ }
+
+ const buf = data.slice(2);
+
+ if (!validation.isValidUTF8(buf)) {
+ this.error(
+ new Error('Invalid WebSocket frame: invalid UTF-8 sequence'),
+ 1007
+ );
+ return;
+ }
+
+ this._loop = false;
+ this.onclose(code, buf.toString());
+ this.cleanup(this._cleanupCallback);
+ }
+
+ return;
+ }
+
+ if (this._opcode === 0x09) this.onping(data);
+ else this.onpong(data);
+
+ this._state = GET_INFO;
+ }
+
+ /**
+ * Handles an error.
+ *
+ * @param {Error} err The error
+ * @param {Number} code Close code
+ * @private
+ */
+ error (err, code) {
+ this._hadError = true;
+ this._loop = false;
+ this.onerror(err, code);
+ this.cleanup(this._cleanupCallback);
+ }
+
+ /**
+ * Checks payload size, disconnects socket when it exceeds `maxPayload`.
+ *
+ * @param {Number} length Payload length
+ * @private
+ */
+ maxPayloadExceeded (length) {
+ if (length === 0 || this._maxPayload < 1) return false;
+
+ const fullLength = this._totalPayloadLength + length;
+
+ if (fullLength <= this._maxPayload) {
+ this._totalPayloadLength = fullLength;
+ return false;
+ }
+
+ this.error(new RangeError('Max payload size exceeded'), 1009);
+ return true;
+ }
+
+ /**
+ * Appends a fragment in the fragments array after checking that the sum of
+ * fragment lengths does not exceed `maxPayload`.
+ *
+ * @param {Buffer} fragment The fragment to add
+ * @return {Boolean} `true` if `maxPayload` is not exceeded, else `false`
+ * @private
+ */
+ pushFragment (fragment) {
+ if (fragment.length === 0) return true;
+
+ const totalLength = this._messageLength + fragment.length;
+
+ if (this._maxPayload < 1 || totalLength <= this._maxPayload) {
+ this._messageLength = totalLength;
+ this._fragments.push(fragment);
+ return true;
+ }
+
+ this.error(new RangeError('Max payload size exceeded'), 1009);
+ return false;
+ }
+
+ /**
+ * Releases resources used by the receiver.
+ *
+ * @param {Function} cb Callback
+ * @public
+ */
+ cleanup (cb) {
+ if (this._extensions === null) {
+ if (cb) cb();
+ return;
+ }
+
+ if (!this._hadError && (this._loop || this._state === INFLATING)) {
+ this._cleanupCallback = cb;
+ this._isCleaningUp = true;
+ return;
+ }
+
+ this._extensions = null;
+ this._fragments = null;
+ this._buffers = null;
+ this._mask = null;
+
+ this._cleanupCallback = null;
+ this.onmessage = null;
+ this.onclose = null;
+ this.onerror = null;
+ this.onping = null;
+ this.onpong = null;
+
+ if (cb) cb();
+ }
+}
+
+module.exports = Receiver;
+
+/**
+ * Makes a buffer from a list of fragments.
+ *
+ * @param {Buffer[]} fragments The list of fragments composing the message
+ * @param {Number} messageLength The length of the message
+ * @return {Buffer}
+ * @private
+ */
+function toBuffer (fragments, messageLength) {
+ if (fragments.length === 1) return fragments[0];
+ if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
+ return constants.EMPTY_BUFFER;
+}
+
+/**
+ * Converts a buffer to an `ArrayBuffer`.
+ *
+ * @param {Buffer} The buffer to convert
+ * @return {ArrayBuffer} Converted buffer
+ */
+function toArrayBuffer (buf) {
+ if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
+ return buf.buffer;
+ }
+
+ return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+}
diff --git a/node_modules/ws/lib/sender.js b/node_modules/ws/lib/sender.js
new file mode 100644
index 00000000..b3dacbc4
--- /dev/null
+++ b/node_modules/ws/lib/sender.js
@@ -0,0 +1,404 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+const crypto = require('crypto');
+
+const PerMessageDeflate = require('./permessage-deflate');
+const bufferUtil = require('./buffer-util');
+const validation = require('./validation');
+const constants = require('./constants');
+
+const Buffer = safeBuffer.Buffer;
+
+/**
+ * HyBi Sender implementation.
+ */
+class Sender {
+ /**
+ * Creates a Sender instance.
+ *
+ * @param {net.Socket} socket The connection socket
+ * @param {Object} extensions An object containing the negotiated extensions
+ */
+ constructor (socket, extensions) {
+ this._extensions = extensions || {};
+ this._socket = socket;
+
+ this._firstFragment = true;
+ this._compress = false;
+
+ this._bufferedBytes = 0;
+ this._deflating = false;
+ this._queue = [];
+ }
+
+ /**
+ * Frames a piece of data according to the HyBi WebSocket protocol.
+ *
+ * @param {Buffer} data The data to frame
+ * @param {Object} options Options object
+ * @param {Number} options.opcode The opcode
+ * @param {Boolean} options.readOnly Specifies whether `data` can be modified
+ * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
+ * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
+ * @return {Buffer[]} The framed data as a list of `Buffer` instances
+ * @public
+ */
+ static frame (data, options) {
+ const merge = data.length < 1024 || (options.mask && options.readOnly);
+ var offset = options.mask ? 6 : 2;
+ var payloadLength = data.length;
+
+ if (data.length >= 65536) {
+ offset += 8;
+ payloadLength = 127;
+ } else if (data.length > 125) {
+ offset += 2;
+ payloadLength = 126;
+ }
+
+ const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
+
+ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
+ if (options.rsv1) target[0] |= 0x40;
+
+ if (payloadLength === 126) {
+ target.writeUInt16BE(data.length, 2, true);
+ } else if (payloadLength === 127) {
+ target.writeUInt32BE(0, 2, true);
+ target.writeUInt32BE(data.length, 6, true);
+ }
+
+ if (!options.mask) {
+ target[1] = payloadLength;
+ if (merge) {
+ data.copy(target, offset);
+ return [target];
+ }
+
+ return [target, data];
+ }
+
+ const mask = crypto.randomBytes(4);
+
+ target[1] = payloadLength | 0x80;
+ target[offset - 4] = mask[0];
+ target[offset - 3] = mask[1];
+ target[offset - 2] = mask[2];
+ target[offset - 1] = mask[3];
+
+ if (merge) {
+ bufferUtil.mask(data, mask, target, offset, data.length);
+ return [target];
+ }
+
+ bufferUtil.mask(data, mask, data, 0, data.length);
+ return [target, data];
+ }
+
+ /**
+ * Sends a close message to the other peer.
+ *
+ * @param {(Number|undefined)} code The status code component of the body
+ * @param {String} data The message component of the body
+ * @param {Boolean} mask Specifies whether or not to mask the message
+ * @param {Function} cb Callback
+ * @public
+ */
+ close (code, data, mask, cb) {
+ var buf;
+
+ if (code === undefined) {
+ buf = constants.EMPTY_BUFFER;
+ } else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) {
+ throw new TypeError('First argument must be a valid error code number');
+ } else if (data === undefined || data === '') {
+ buf = Buffer.allocUnsafe(2);
+ buf.writeUInt16BE(code, 0, true);
+ } else {
+ buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
+ buf.writeUInt16BE(code, 0, true);
+ buf.write(data, 2);
+ }
+
+ if (this._deflating) {
+ this.enqueue([this.doClose, buf, mask, cb]);
+ } else {
+ this.doClose(buf, mask, cb);
+ }
+ }
+
+ /**
+ * Frames and sends a close message.
+ *
+ * @param {Buffer} data The message to send
+ * @param {Boolean} mask Specifies whether or not to mask `data`
+ * @param {Function} cb Callback
+ * @private
+ */
+ doClose (data, mask, cb) {
+ this.sendFrame(Sender.frame(data, {
+ fin: true,
+ rsv1: false,
+ opcode: 0x08,
+ mask,
+ readOnly: false
+ }), cb);
+ }
+
+ /**
+ * Sends a ping message to the other peer.
+ *
+ * @param {*} data The message to send
+ * @param {Boolean} mask Specifies whether or not to mask `data`
+ * @param {Function} cb Callback
+ * @public
+ */
+ ping (data, mask, cb) {
+ var readOnly = true;
+
+ if (!Buffer.isBuffer(data)) {
+ if (data instanceof ArrayBuffer) {
+ data = Buffer.from(data);
+ } else if (ArrayBuffer.isView(data)) {
+ data = viewToBuffer(data);
+ } else {
+ data = Buffer.from(data);
+ readOnly = false;
+ }
+ }
+
+ if (this._deflating) {
+ this.enqueue([this.doPing, data, mask, readOnly, cb]);
+ } else {
+ this.doPing(data, mask, readOnly, cb);
+ }
+ }
+
+ /**
+ * Frames and sends a ping message.
+ *
+ * @param {*} data The message to send
+ * @param {Boolean} mask Specifies whether or not to mask `data`
+ * @param {Boolean} readOnly Specifies whether `data` can be modified
+ * @param {Function} cb Callback
+ * @private
+ */
+ doPing (data, mask, readOnly, cb) {
+ this.sendFrame(Sender.frame(data, {
+ fin: true,
+ rsv1: false,
+ opcode: 0x09,
+ mask,
+ readOnly
+ }), cb);
+ }
+
+ /**
+ * Sends a pong message to the other peer.
+ *
+ * @param {*} data The message to send
+ * @param {Boolean} mask Specifies whether or not to mask `data`
+ * @param {Function} cb Callback
+ * @public
+ */
+ pong (data, mask, cb) {
+ var readOnly = true;
+
+ if (!Buffer.isBuffer(data)) {
+ if (data instanceof ArrayBuffer) {
+ data = Buffer.from(data);
+ } else if (ArrayBuffer.isView(data)) {
+ data = viewToBuffer(data);
+ } else {
+ data = Buffer.from(data);
+ readOnly = false;
+ }
+ }
+
+ if (this._deflating) {
+ this.enqueue([this.doPong, data, mask, readOnly, cb]);
+ } else {
+ this.doPong(data, mask, readOnly, cb);
+ }
+ }
+
+ /**
+ * Frames and sends a pong message.
+ *
+ * @param {*} data The message to send
+ * @param {Boolean} mask Specifies whether or not to mask `data`
+ * @param {Boolean} readOnly Specifies whether `data` can be modified
+ * @param {Function} cb Callback
+ * @private
+ */
+ doPong (data, mask, readOnly, cb) {
+ this.sendFrame(Sender.frame(data, {
+ fin: true,
+ rsv1: false,
+ opcode: 0x0a,
+ mask,
+ readOnly
+ }), cb);
+ }
+
+ /**
+ * Sends a data message to the other peer.
+ *
+ * @param {*} data The message to send
+ * @param {Object} options Options object
+ * @param {Boolean} options.compress Specifies whether or not to compress `data`
+ * @param {Boolean} options.binary Specifies whether `data` is binary or text
+ * @param {Boolean} options.fin Specifies whether the fragment is the last one
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
+ * @param {Function} cb Callback
+ * @public
+ */
+ send (data, options, cb) {
+ var opcode = options.binary ? 2 : 1;
+ var rsv1 = options.compress;
+ var readOnly = true;
+
+ if (!Buffer.isBuffer(data)) {
+ if (data instanceof ArrayBuffer) {
+ data = Buffer.from(data);
+ } else if (ArrayBuffer.isView(data)) {
+ data = viewToBuffer(data);
+ } else {
+ data = Buffer.from(data);
+ readOnly = false;
+ }
+ }
+
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
+
+ if (this._firstFragment) {
+ this._firstFragment = false;
+ if (rsv1 && perMessageDeflate) {
+ rsv1 = data.length >= perMessageDeflate._threshold;
+ }
+ this._compress = rsv1;
+ } else {
+ rsv1 = false;
+ opcode = 0;
+ }
+
+ if (options.fin) this._firstFragment = true;
+
+ if (perMessageDeflate) {
+ const opts = {
+ fin: options.fin,
+ rsv1,
+ opcode,
+ mask: options.mask,
+ readOnly
+ };
+
+ if (this._deflating) {
+ this.enqueue([this.dispatch, data, this._compress, opts, cb]);
+ } else {
+ this.dispatch(data, this._compress, opts, cb);
+ }
+ } else {
+ this.sendFrame(Sender.frame(data, {
+ fin: options.fin,
+ rsv1: false,
+ opcode,
+ mask: options.mask,
+ readOnly
+ }), cb);
+ }
+ }
+
+ /**
+ * Dispatches a data message.
+ *
+ * @param {Buffer} data The message to send
+ * @param {Boolean} compress Specifies whether or not to compress `data`
+ * @param {Object} options Options object
+ * @param {Number} options.opcode The opcode
+ * @param {Boolean} options.readOnly Specifies whether `data` can be modified
+ * @param {Boolean} options.fin Specifies whether or not to set the FIN bit
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
+ * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
+ * @param {Function} cb Callback
+ * @private
+ */
+ dispatch (data, compress, options, cb) {
+ if (!compress) {
+ this.sendFrame(Sender.frame(data, options), cb);
+ return;
+ }
+
+ const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
+
+ this._deflating = true;
+ perMessageDeflate.compress(data, options.fin, (_, buf) => {
+ options.readOnly = false;
+ this.sendFrame(Sender.frame(buf, options), cb);
+ this._deflating = false;
+ this.dequeue();
+ });
+ }
+
+ /**
+ * Executes queued send operations.
+ *
+ * @private
+ */
+ dequeue () {
+ while (!this._deflating && this._queue.length) {
+ const params = this._queue.shift();
+
+ this._bufferedBytes -= params[1].length;
+ params[0].apply(this, params.slice(1));
+ }
+ }
+
+ /**
+ * Enqueues a send operation.
+ *
+ * @param {Array} params Send operation parameters.
+ * @private
+ */
+ enqueue (params) {
+ this._bufferedBytes += params[1].length;
+ this._queue.push(params);
+ }
+
+ /**
+ * Sends a frame.
+ *
+ * @param {Buffer[]} list The frame to send
+ * @param {Function} cb Callback
+ * @private
+ */
+ sendFrame (list, cb) {
+ if (list.length === 2) {
+ this._socket.write(list[0]);
+ this._socket.write(list[1], cb);
+ } else {
+ this._socket.write(list[0], cb);
+ }
+ }
+}
+
+module.exports = Sender;
+
+/**
+ * Converts an `ArrayBuffer` view into a buffer.
+ *
+ * @param {(DataView|TypedArray)} view The view to convert
+ * @return {Buffer} Converted view
+ * @private
+ */
+function viewToBuffer (view) {
+ const buf = Buffer.from(view.buffer);
+
+ if (view.byteLength !== view.buffer.byteLength) {
+ return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
+ }
+
+ return buf;
+}
diff --git a/node_modules/ws/lib/validation.js b/node_modules/ws/lib/validation.js
new file mode 100644
index 00000000..06269fcf
--- /dev/null
+++ b/node_modules/ws/lib/validation.js
@@ -0,0 +1,29 @@
+'use strict';
+
+try {
+ const isValidUTF8 = require('utf-8-validate');
+
+ exports.isValidUTF8 = typeof isValidUTF8 === 'object'
+ ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
+ : isValidUTF8;
+} catch (e) /* istanbul ignore next */ {
+ exports.isValidUTF8 = () => true;
+}
+
+/**
+ * Checks if a status code is allowed in a close frame.
+ *
+ * @param {Number} code The status code
+ * @return {Boolean} `true` if the status code is valid, else `false`
+ * @public
+ */
+exports.isValidStatusCode = (code) => {
+ return (
+ (code >= 1000 &&
+ code <= 1013 &&
+ code !== 1004 &&
+ code !== 1005 &&
+ code !== 1006) ||
+ (code >= 3000 && code <= 4999)
+ );
+};
diff --git a/node_modules/ws/lib/websocket-server.js b/node_modules/ws/lib/websocket-server.js
new file mode 100644
index 00000000..ee0913fb
--- /dev/null
+++ b/node_modules/ws/lib/websocket-server.js
@@ -0,0 +1,351 @@
+'use strict';
+
+const safeBuffer = require('safe-buffer');
+const EventEmitter = require('events');
+const crypto = require('crypto');
+const http = require('http');
+const url = require('url');
+
+const PerMessageDeflate = require('./permessage-deflate');
+const extension = require('./extension');
+const constants = require('./constants');
+const WebSocket = require('./websocket');
+
+const Buffer = safeBuffer.Buffer;
+
+/**
+ * Class representing a WebSocket server.
+ *
+ * @extends EventEmitter
+ */
+class WebSocketServer extends EventEmitter {
+ /**
+ * Create a `WebSocketServer` instance.
+ *
+ * @param {Object} options Configuration options
+ * @param {String} options.host The hostname where to bind the server
+ * @param {Number} options.port The port where to bind the server
+ * @param {http.Server} options.server A pre-created HTTP/S server to use
+ * @param {Function} options.verifyClient An hook to reject connections
+ * @param {Function} options.handleProtocols An hook to handle protocols
+ * @param {String} options.path Accept only connections matching this path
+ * @param {Boolean} options.noServer Enable no server mode
+ * @param {Boolean} options.clientTracking Specifies whether or not to track clients
+ * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
+ * @param {Number} options.maxPayload The maximum allowed message size
+ * @param {Function} callback A listener for the `listening` event
+ */
+ constructor (options, callback) {
+ super();
+
+ options = Object.assign({
+ maxPayload: 100 * 1024 * 1024,
+ perMessageDeflate: false,
+ handleProtocols: null,
+ clientTracking: true,
+ verifyClient: null,
+ noServer: false,
+ backlog: null, // use default (511 as implemented in net.js)
+ server: null,
+ host: null,
+ path: null,
+ port: null
+ }, options);
+
+ if (options.port == null && !options.server && !options.noServer) {
+ throw new TypeError(
+ 'One of the "port", "server", or "noServer" options must be specified'
+ );
+ }
+
+ if (options.port != null) {
+ this._server = http.createServer((req, res) => {
+ const body = http.STATUS_CODES[426];
+
+ res.writeHead(426, {
+ 'Content-Length': body.length,
+ 'Content-Type': 'text/plain'
+ });
+ res.end(body);
+ });
+ this._server.listen(options.port, options.host, options.backlog, callback);
+ } else if (options.server) {
+ this._server = options.server;
+ }
+
+ if (this._server) {
+ this._removeListeners = addListeners(this._server, {
+ listening: this.emit.bind(this, 'listening'),
+ error: this.emit.bind(this, 'error'),
+ upgrade: (req, socket, head) => {
+ this.handleUpgrade(req, socket, head, (ws) => {
+ this.emit('connection', ws, req);
+ });
+ }
+ });
+ }
+
+ if (options.perMessageDeflate === true) options.perMessageDeflate = {};
+ if (options.clientTracking) this.clients = new Set();
+ this.options = options;
+ }
+
+ /**
+ * Returns the bound address, the address family name, and port of the server
+ * as reported by the operating system if listening on an IP socket.
+ * If the server is listening on a pipe or UNIX domain socket, the name is
+ * returned as a string.
+ *
+ * @return {(Object|String|null)} The address of the server
+ * @public
+ */
+ address () {
+ if (this.options.noServer) {
+ throw new Error('The server is operating in "noServer" mode');
+ }
+
+ if (!this._server) return null;
+ return this._server.address();
+ }
+
+ /**
+ * Close the server.
+ *
+ * @param {Function} cb Callback
+ * @public
+ */
+ close (cb) {
+ //
+ // Terminate all associated clients.
+ //
+ if (this.clients) {
+ for (const client of this.clients) client.terminate();
+ }
+
+ const server = this._server;
+
+ if (server) {
+ this._removeListeners();
+ this._removeListeners = this._server = null;
+
+ //
+ // Close the http server if it was internally created.
+ //
+ if (this.options.port != null) return server.close(cb);
+ }
+
+ if (cb) cb();
+ }
+
+ /**
+ * See if a given request should be handled by this server instance.
+ *
+ * @param {http.IncomingMessage} req Request object to inspect
+ * @return {Boolean} `true` if the request is valid, else `false`
+ * @public
+ */
+ shouldHandle (req) {
+ if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Handle a HTTP Upgrade request.
+ *
+ * @param {http.IncomingMessage} req The request object
+ * @param {net.Socket} socket The network socket between the server and client
+ * @param {Buffer} head The first packet of the upgraded stream
+ * @param {Function} cb Callback
+ * @public
+ */
+ handleUpgrade (req, socket, head, cb) {
+ socket.on('error', socketOnError);
+
+ const version = +req.headers['sec-websocket-version'];
+ const extensions = {};
+
+ if (
+ req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
+ !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
+ !this.shouldHandle(req)
+ ) {
+ return abortConnection(socket, 400);
+ }
+
+ if (this.options.perMessageDeflate) {
+ const perMessageDeflate = new PerMessageDeflate(
+ this.options.perMessageDeflate,
+ true,
+ this.options.maxPayload
+ );
+
+ try {
+ const offers = extension.parse(
+ req.headers['sec-websocket-extensions']
+ );
+
+ if (offers[PerMessageDeflate.extensionName]) {
+ perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
+ extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
+ }
+ } catch (err) {
+ return abortConnection(socket, 400);
+ }
+ }
+
+ var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */);
+
+ //
+ // Optionally call external protocol selection handler.
+ //
+ if (this.options.handleProtocols) {
+ protocol = this.options.handleProtocols(protocol, req);
+ if (protocol === false) return abortConnection(socket, 401);
+ } else {
+ protocol = protocol[0];
+ }
+
+ //
+ // Optionally call external client verification handler.
+ //
+ if (this.options.verifyClient) {
+ const info = {
+ origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
+ secure: !!(req.connection.authorized || req.connection.encrypted),
+ req
+ };
+
+ if (this.options.verifyClient.length === 2) {
+ this.options.verifyClient(info, (verified, code, message) => {
+ if (!verified) return abortConnection(socket, code || 401, message);
+
+ this.completeUpgrade(protocol, extensions, req, socket, head, cb);
+ });
+ return;
+ }
+
+ if (!this.options.verifyClient(info)) return abortConnection(socket, 401);
+ }
+
+ this.completeUpgrade(protocol, extensions, req, socket, head, cb);
+ }
+
+ /**
+ * Upgrade the connection to WebSocket.
+ *
+ * @param {String} protocol The chosen subprotocol
+ * @param {Object} extensions The accepted extensions
+ * @param {http.IncomingMessage} req The request object
+ * @param {net.Socket} socket The network socket between the server and client
+ * @param {Buffer} head The first packet of the upgraded stream
+ * @param {Function} cb Callback
+ * @private
+ */
+ completeUpgrade (protocol, extensions, req, socket, head, cb) {
+ //
+ // Destroy the socket if the client has already sent a FIN packet.
+ //
+ if (!socket.readable || !socket.writable) return socket.destroy();
+
+ const key = crypto.createHash('sha1')
+ .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
+ .digest('base64');
+
+ const headers = [
+ 'HTTP/1.1 101 Switching Protocols',
+ 'Upgrade: websocket',
+ 'Connection: Upgrade',
+ `Sec-WebSocket-Accept: ${key}`
+ ];
+
+ const ws = new WebSocket(null);
+
+ if (protocol) {
+ headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
+ ws.protocol = protocol;
+ }
+ if (extensions[PerMessageDeflate.extensionName]) {
+ const params = extensions[PerMessageDeflate.extensionName].params;
+ const value = extension.format({
+ [PerMessageDeflate.extensionName]: [params]
+ });
+ headers.push(`Sec-WebSocket-Extensions: ${value}`);
+ ws._extensions = extensions;
+ }
+
+ //
+ // Allow external modification/inspection of handshake headers.
+ //
+ this.emit('headers', headers, req);
+
+ socket.write(headers.concat('\r\n').join('\r\n'));
+ socket.removeListener('error', socketOnError);
+
+ ws.setSocket(socket, head, this.options.maxPayload);
+
+ if (this.clients) {
+ this.clients.add(ws);
+ ws.on('close', () => this.clients.delete(ws));
+ }
+
+ cb(ws);
+ }
+}
+
+module.exports = WebSocketServer;
+
+/**
+ * Add event listeners on an `EventEmitter` using a map of <event, listener>
+ * pairs.
+ *
+ * @param {EventEmitter} server The event emitter
+ * @param {Object.<String, Function>} map The listeners to add
+ * @return {Function} A function that will remove the added listeners when called
+ * @private
+ */
+function addListeners (server, map) {
+ for (const event of Object.keys(map)) server.on(event, map[event]);
+
+ return function removeListeners () {
+ for (const event of Object.keys(map)) {
+ server.removeListener(event, map[event]);
+ }
+ };
+}
+
+/**
+ * Handle premature socket errors.
+ *
+ * @private
+ */
+function socketOnError () {
+ this.destroy();
+}
+
+/**
+ * Close the connection when preconditions are not fulfilled.
+ *
+ * @param {net.Socket} socket The socket of the upgrade request
+ * @param {Number} code The HTTP response status code
+ * @param {String} [message] The HTTP response body
+ * @private
+ */
+function abortConnection (socket, code, message) {
+ if (socket.writable) {
+ message = message || http.STATUS_CODES[code];
+ socket.write(
+ `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
+ 'Connection: close\r\n' +
+ 'Content-type: text/html\r\n' +
+ `Content-Length: ${Buffer.byteLength(message)}\r\n` +
+ '\r\n' +
+ message
+ );
+ }
+
+ socket.removeListener('error', socketOnError);
+ socket.destroy();
+}
diff --git a/node_modules/ws/lib/websocket.js b/node_modules/ws/lib/websocket.js
new file mode 100644
index 00000000..a5f606f9
--- /dev/null
+++ b/node_modules/ws/lib/websocket.js
@@ -0,0 +1,705 @@
+'use strict';
+
+const EventEmitter = require('events');
+const crypto = require('crypto');
+const https = require('https');
+const http = require('http');
+const url = require('url');
+
+const PerMessageDeflate = require('./permessage-deflate');
+const EventTarget = require('./event-target');
+const extension = require('./extension');
+const constants = require('./constants');
+const Receiver = require('./receiver');
+const Sender = require('./sender');
+
+const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
+const protocolVersions = [8, 13];
+const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
+
+/**
+ * Class representing a WebSocket.
+ *
+ * @extends EventEmitter
+ */
+class WebSocket extends EventEmitter {
+ /**
+ * Create a new `WebSocket`.
+ *
+ * @param {String} address The URL to which to connect
+ * @param {(String|String[])} protocols The subprotocols
+ * @param {Object} options Connection options
+ */
+ constructor (address, protocols, options) {
+ super();
+
+ this.readyState = WebSocket.CONNECTING;
+ this.protocol = '';
+
+ this._binaryType = constants.BINARY_TYPES[0];
+ this._finalize = this.finalize.bind(this);
+ this._closeFrameReceived = false;
+ this._closeFrameSent = false;
+ this._closeMessage = '';
+ this._closeTimer = null;
+ this._finalized = false;
+ this._closeCode = 1006;
+ this._extensions = {};
+ this._isServer = true;
+ this._receiver = null;
+ this._sender = null;
+ this._socket = null;
+ this._error = null;
+
+ if (address !== null) {
+ if (!protocols) {
+ protocols = [];
+ } else if (typeof protocols === 'string') {
+ protocols = [protocols];
+ } else if (!Array.isArray(protocols)) {
+ options = protocols;
+ protocols = [];
+ }
+
+ initAsClient.call(this, address, protocols, options);
+ }
+ }
+
+ get CONNECTING () { return WebSocket.CONNECTING; }
+ get CLOSING () { return WebSocket.CLOSING; }
+ get CLOSED () { return WebSocket.CLOSED; }
+ get OPEN () { return WebSocket.OPEN; }
+
+ /**
+ * This deviates from the WHATWG interface since ws doesn't support the required
+ * default "blob" type (instead we define a custom "nodebuffer" type).
+ *
+ * @type {String}
+ */
+ get binaryType () {
+ return this._binaryType;
+ }
+
+ set binaryType (type) {
+ if (constants.BINARY_TYPES.indexOf(type) < 0) return;
+
+ this._binaryType = type;
+
+ //
+ // Allow to change `binaryType` on the fly.
+ //
+ if (this._receiver) this._receiver._binaryType = type;
+ }
+
+ /**
+ * @type {Number}
+ */
+ get bufferedAmount () {
+ if (!this._socket) return 0;
+
+ //
+ // `socket.bufferSize` is `undefined` if the socket is closed.
+ //
+ return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
+ }
+
+ /**
+ * @type {String}
+ */
+ get extensions () {
+ return Object.keys(this._extensions).join();
+ }
+
+ /**
+ * Set up the socket and the internal resources.
+ *
+ * @param {net.Socket} socket The network socket between the server and client
+ * @param {Buffer} head The first packet of the upgraded stream
+ * @param {Number} maxPayload The maximum allowed message size
+ * @private
+ */
+ setSocket (socket, head, maxPayload) {
+ socket.setTimeout(0);
+ socket.setNoDelay();
+
+ socket.on('close', this._finalize);
+ socket.on('error', this._finalize);
+ socket.on('end', this._finalize);
+
+ this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType);
+ this._sender = new Sender(socket, this._extensions);
+ this._socket = socket;
+
+ if (head.length > 0) socket.unshift(head);
+
+ socket.on('data', this._receiver.add);
+
+ this._receiver.onmessage = (data) => this.emit('message', data);
+ this._receiver.onping = (data) => {
+ this.pong(data, !this._isServer, constants.NOOP);
+ this.emit('ping', data);
+ };
+ this._receiver.onpong = (data) => this.emit('pong', data);
+ this._receiver.onclose = (code, reason) => {
+ //
+ // Discard any additional data that is received on the socket.
+ //
+ this._socket.removeListener('data', this._receiver.add);
+
+ this._closeFrameReceived = true;
+ this._closeMessage = reason;
+ this._closeCode = code;
+
+ if (code === 1005) this.close();
+ else this.close(code, reason);
+ };
+ this._receiver.onerror = (error, code) => {
+ if (this._error) return;
+
+ this._closeCode = code;
+
+ if (!this._finalized) this.finalize(error);
+ else this.emit('error', error);
+ };
+
+ this.readyState = WebSocket.OPEN;
+ this.emit('open');
+ }
+
+ /**
+ * Clean up internal resources and emit the `'close'` event.
+ *
+ * @param {(Boolean|Error)} error Indicates whether or not an error occurred
+ * @private
+ */
+ finalize (error) {
+ if (this._finalized) return;
+
+ this.readyState = WebSocket.CLOSING;
+ this._finalized = true;
+
+ if (!this._socket) {
+ //
+ // `error` is always an `Error` instance in this case.
+ //
+ this.emit('error', error);
+ this.readyState = WebSocket.CLOSED;
+ this.emit('close', this._closeCode, this._closeMessage);
+ return;
+ }
+
+ clearTimeout(this._closeTimer);
+
+ this._socket.removeListener('data', this._receiver.add);
+ this._socket.removeListener('close', this._finalize);
+ this._socket.removeListener('error', this._finalize);
+ this._socket.removeListener('end', this._finalize);
+ this._socket.on('error', constants.NOOP);
+
+ if (error) {
+ if (error !== true) this._error = error;
+ this._socket.destroy();
+ } else {
+ this._socket.end();
+ }
+
+ this._receiver.cleanup(() => {
+ const err = this._error;
+
+ if (err) {
+ this._error = null;
+ this.emit('error', err);
+ }
+
+ this.readyState = WebSocket.CLOSED;
+
+ if (this._extensions[PerMessageDeflate.extensionName]) {
+ this._extensions[PerMessageDeflate.extensionName].cleanup();
+ }
+
+ this.emit('close', this._closeCode, this._closeMessage);
+ });
+ }
+
+ /**
+ * Start a closing handshake.
+ *
+ * +----------+ +-----------+ +----------+
+ * + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - -
+ * +----------+ +-----------+ +----------+ |
+ * | +----------+ +-----------+ |
+ * |ws.close()|<----|close frame|<--------+ |
+ * +----------+ +-----------+ |
+ * CLOSING | +---+ | CLOSING
+ * | +---|fin|<------------+
+ * | | | +---+ |
+ * | | +---+ +-------------+
+ * | +----------+-->|fin|----->|ws.finalize()| - - +
+ * | +---+ +-------------+
+ * | +-------------+ |
+ * - - -|ws.finalize()|<--+
+ * +-------------+
+ *
+ * @param {Number} code Status code explaining why the connection is closing
+ * @param {String} data A string explaining why the connection is closing
+ * @public
+ */
+ close (code, data) {
+ if (this.readyState === WebSocket.CLOSED) return;
+ if (this.readyState === WebSocket.CONNECTING) {
+ this._req.abort();
+ this.finalize(
+ new Error('WebSocket was closed before the connection was established')
+ );
+ return;
+ }
+
+ if (this.readyState === WebSocket.CLOSING) {
+ if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
+ return;
+ }
+
+ this.readyState = WebSocket.CLOSING;
+ this._sender.close(code, data, !this._isServer, (err) => {
+ //
+ // This error is handled by the `'error'` listener on the socket. We only
+ // want to know if the close frame has been sent here.
+ //
+ if (err) return;
+
+ this._closeFrameSent = true;
+
+ if (!this._finalized) {
+ if (this._closeFrameReceived) this._socket.end();
+
+ //
+ // Ensure that the connection is cleaned up even when the closing
+ // handshake fails.
+ //
+ this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
+ }
+ });
+ }
+
+ /**
+ * Send a ping.
+ *
+ * @param {*} data The data to send
+ * @param {Boolean} mask Indicates whether or not to mask `data`
+ * @param {Function} cb Callback which is executed when the ping is sent
+ * @public
+ */
+ ping (data, mask, cb) {
+ if (typeof data === 'function') {
+ cb = data;
+ data = mask = undefined;
+ } else if (typeof mask === 'function') {
+ cb = mask;
+ mask = undefined;
+ }
+
+ if (this.readyState !== WebSocket.OPEN) {
+ const err = new Error(
+ `WebSocket is not open: readyState ${this.readyState} ` +
+ `(${readyStates[this.readyState]})`
+ );
+
+ if (cb) return cb(err);
+ throw err;
+ }
+
+ if (typeof data === 'number') data = data.toString();
+ if (mask === undefined) mask = !this._isServer;
+ this._sender.ping(data || constants.EMPTY_BUFFER, mask, cb);
+ }
+
+ /**
+ * Send a pong.
+ *
+ * @param {*} data The data to send
+ * @param {Boolean} mask Indicates whether or not to mask `data`
+ * @param {Function} cb Callback which is executed when the pong is sent
+ * @public
+ */
+ pong (data, mask, cb) {
+ if (typeof data === 'function') {
+ cb = data;
+ data = mask = undefined;
+ } else if (typeof mask === 'function') {
+ cb = mask;
+ mask = undefined;
+ }
+
+ if (this.readyState !== WebSocket.OPEN) {
+ const err = new Error(
+ `WebSocket is not open: readyState ${this.readyState} ` +
+ `(${readyStates[this.readyState]})`
+ );
+
+ if (cb) return cb(err);
+ throw err;
+ }
+
+ if (typeof data === 'number') data = data.toString();
+ if (mask === undefined) mask = !this._isServer;
+ this._sender.pong(data || constants.EMPTY_BUFFER, mask, cb);
+ }
+
+ /**
+ * Send a data message.
+ *
+ * @param {*} data The message to send
+ * @param {Object} options Options object
+ * @param {Boolean} options.compress Specifies whether or not to compress `data`
+ * @param {Boolean} options.binary Specifies whether `data` is binary or text
+ * @param {Boolean} options.fin Specifies whether the fragment is the last one
+ * @param {Boolean} options.mask Specifies whether or not to mask `data`
+ * @param {Function} cb Callback which is executed when data is written out
+ * @public
+ */
+ send (data, options, cb) {
+ if (typeof options === 'function') {
+ cb = options;
+ options = {};
+ }
+
+ if (this.readyState !== WebSocket.OPEN) {
+ const err = new Error(
+ `WebSocket is not open: readyState ${this.readyState} ` +
+ `(${readyStates[this.readyState]})`
+ );
+
+ if (cb) return cb(err);
+ throw err;
+ }
+
+ if (typeof data === 'number') data = data.toString();
+
+ const opts = Object.assign({
+ binary: typeof data !== 'string',
+ mask: !this._isServer,
+ compress: true,
+ fin: true
+ }, options);
+
+ if (!this._extensions[PerMessageDeflate.extensionName]) {
+ opts.compress = false;
+ }
+
+ this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
+ }
+
+ /**
+ * Forcibly close the connection.
+ *
+ * @public
+ */
+ terminate () {
+ if (this.readyState === WebSocket.CLOSED) return;
+ if (this.readyState === WebSocket.CONNECTING) {
+ this._req.abort();
+ this.finalize(
+ new Error('WebSocket was closed before the connection was established')
+ );
+ return;
+ }
+
+ this.finalize(true);
+ }
+}
+
+readyStates.forEach((readyState, i) => {
+ WebSocket[readyStates[i]] = i;
+});
+
+//
+// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
+// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
+//
+['open', 'error', 'close', 'message'].forEach((method) => {
+ Object.defineProperty(WebSocket.prototype, `on${method}`, {
+ /**
+ * Return the listener of the event.
+ *
+ * @return {(Function|undefined)} The event listener or `undefined`
+ * @public
+ */
+ get () {
+ const listeners = this.listeners(method);
+ for (var i = 0; i < listeners.length; i++) {
+ if (listeners[i]._listener) return listeners[i]._listener;
+ }
+ },
+ /**
+ * Add a listener for the event.
+ *
+ * @param {Function} listener The listener to add
+ * @public
+ */
+ set (listener) {
+ const listeners = this.listeners(method);
+ for (var i = 0; i < listeners.length; i++) {
+ //
+ // Remove only the listeners added via `addEventListener`.
+ //
+ if (listeners[i]._listener) this.removeListener(method, listeners[i]);
+ }
+ this.addEventListener(method, listener);
+ }
+ });
+});
+
+WebSocket.prototype.addEventListener = EventTarget.addEventListener;
+WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
+
+module.exports = WebSocket;
+
+/**
+ * Initialize a WebSocket client.
+ *
+ * @param {String} address The URL to which to connect
+ * @param {String[]} protocols The list of subprotocols
+ * @param {Object} options Connection options
+ * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
+ * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
+ * @param {Number} options.handshakeTimeout Timeout in milliseconds for the handshake request
+ * @param {String} options.localAddress Local interface to bind for network connections
+ * @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
+ * @param {Object} options.headers An object containing request headers
+ * @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
+ * @param {http.Agent} options.agent Use the specified Agent
+ * @param {String} options.host Value of the `Host` header
+ * @param {Number} options.family IP address family to use during hostname lookup (4 or 6).
+ * @param {Function} options.checkServerIdentity A function to validate the server hostname
+ * @param {Boolean} options.rejectUnauthorized Verify or not the server certificate
+ * @param {String} options.passphrase The passphrase for the private key or pfx
+ * @param {String} options.ciphers The ciphers to use or exclude
+ * @param {String} options.ecdhCurve The curves for ECDH key agreement to use or exclude
+ * @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key
+ * @param {(String|String[]|Buffer|Buffer[])} options.key The private key
+ * @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs
+ * @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates
+ * @private
+ */
+function initAsClient (address, protocols, options) {
+ options = Object.assign({
+ protocolVersion: protocolVersions[1],
+ protocol: protocols.join(','),
+ perMessageDeflate: true,
+ handshakeTimeout: null,
+ localAddress: null,
+ headers: null,
+ family: null,
+ origin: null,
+ agent: null,
+ host: null,
+
+ //
+ // SSL options.
+ //
+ checkServerIdentity: null,
+ rejectUnauthorized: null,
+ passphrase: null,
+ ciphers: null,
+ ecdhCurve: null,
+ cert: null,
+ key: null,
+ pfx: null,
+ ca: null
+ }, options);
+
+ if (protocolVersions.indexOf(options.protocolVersion) === -1) {
+ throw new RangeError(
+ `Unsupported protocol version: ${options.protocolVersion} ` +
+ `(supported versions: ${protocolVersions.join(', ')})`
+ );
+ }
+
+ this._isServer = false;
+ this.url = address;
+
+ const serverUrl = url.parse(address);
+ const isUnixSocket = serverUrl.protocol === 'ws+unix:';
+
+ if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
+ throw new Error(`Invalid URL: ${address}`);
+ }
+
+ const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
+ const key = crypto.randomBytes(16).toString('base64');
+ const httpObj = isSecure ? https : http;
+ var perMessageDeflate;
+
+ const requestOptions = {
+ port: serverUrl.port || (isSecure ? 443 : 80),
+ host: serverUrl.hostname,
+ path: '/',
+ headers: {
+ 'Sec-WebSocket-Version': options.protocolVersion,
+ 'Sec-WebSocket-Key': key,
+ 'Connection': 'Upgrade',
+ 'Upgrade': 'websocket'
+ }
+ };
+
+ if (options.headers) Object.assign(requestOptions.headers, options.headers);
+ if (options.perMessageDeflate) {
+ perMessageDeflate = new PerMessageDeflate(
+ options.perMessageDeflate !== true ? options.perMessageDeflate : {},
+ false
+ );
+ requestOptions.headers['Sec-WebSocket-Extensions'] = extension.format({
+ [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
+ });
+ }
+ if (options.protocol) {
+ requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol;
+ }
+ if (options.origin) {
+ if (options.protocolVersion < 13) {
+ requestOptions.headers['Sec-WebSocket-Origin'] = options.origin;
+ } else {
+ requestOptions.headers.Origin = options.origin;
+ }
+ }
+ if (options.host) requestOptions.headers.Host = options.host;
+ if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
+
+ if (options.localAddress) requestOptions.localAddress = options.localAddress;
+ if (options.family) requestOptions.family = options.family;
+
+ if (isUnixSocket) {
+ const parts = serverUrl.path.split(':');
+
+ requestOptions.socketPath = parts[0];
+ requestOptions.path = parts[1];
+ } else if (serverUrl.path) {
+ //
+ // Make sure that path starts with `/`.
+ //
+ if (serverUrl.path.charAt(0) !== '/') {
+ requestOptions.path = `/${serverUrl.path}`;
+ } else {
+ requestOptions.path = serverUrl.path;
+ }
+ }
+
+ var agent = options.agent;
+
+ //
+ // A custom agent is required for these options.
+ //
+ if (
+ options.rejectUnauthorized != null ||
+ options.checkServerIdentity ||
+ options.passphrase ||
+ options.ciphers ||
+ options.ecdhCurve ||
+ options.cert ||
+ options.key ||
+ options.pfx ||
+ options.ca
+ ) {
+ if (options.passphrase) requestOptions.passphrase = options.passphrase;
+ if (options.ciphers) requestOptions.ciphers = options.ciphers;
+ if (options.ecdhCurve) requestOptions.ecdhCurve = options.ecdhCurve;
+ if (options.cert) requestOptions.cert = options.cert;
+ if (options.key) requestOptions.key = options.key;
+ if (options.pfx) requestOptions.pfx = options.pfx;
+ if (options.ca) requestOptions.ca = options.ca;
+ if (options.checkServerIdentity) {
+ requestOptions.checkServerIdentity = options.checkServerIdentity;
+ }
+ if (options.rejectUnauthorized != null) {
+ requestOptions.rejectUnauthorized = options.rejectUnauthorized;
+ }
+
+ if (!agent) agent = new httpObj.Agent(requestOptions);
+ }
+
+ if (agent) requestOptions.agent = agent;
+
+ this._req = httpObj.get(requestOptions);
+
+ if (options.handshakeTimeout) {
+ this._req.setTimeout(options.handshakeTimeout, () => {
+ this._req.abort();
+ this.finalize(new Error('Opening handshake has timed out'));
+ });
+ }
+
+ this._req.on('error', (error) => {
+ if (this._req.aborted) return;
+
+ this._req = null;
+ this.finalize(error);
+ });
+
+ this._req.on('response', (res) => {
+ if (!this.emit('unexpected-response', this._req, res)) {
+ this._req.abort();
+ this.finalize(new Error(`Unexpected server response: ${res.statusCode}`));
+ }
+ });
+
+ this._req.on('upgrade', (res, socket, head) => {
+ this.emit('upgrade', res);
+
+ //
+ // The user may have closed the connection from a listener of the `upgrade`
+ // event.
+ //
+ if (this.readyState !== WebSocket.CONNECTING) return;
+
+ this._req = null;
+
+ const digest = crypto.createHash('sha1')
+ .update(key + constants.GUID, 'binary')
+ .digest('base64');
+
+ if (res.headers['sec-websocket-accept'] !== digest) {
+ socket.destroy();
+ return this.finalize(new Error('Invalid Sec-WebSocket-Accept header'));
+ }
+
+ const serverProt = res.headers['sec-websocket-protocol'];
+ const protList = (options.protocol || '').split(/, */);
+ var protError;
+
+ if (!options.protocol && serverProt) {
+ protError = 'Server sent a subprotocol but none was requested';
+ } else if (options.protocol && !serverProt) {
+ protError = 'Server sent no subprotocol';
+ } else if (serverProt && protList.indexOf(serverProt) === -1) {
+ protError = 'Server sent an invalid subprotocol';
+ }
+
+ if (protError) {
+ socket.destroy();
+ return this.finalize(new Error(protError));
+ }
+
+ if (serverProt) this.protocol = serverProt;
+
+ if (perMessageDeflate) {
+ try {
+ const extensions = extension.parse(
+ res.headers['sec-websocket-extensions']
+ );
+
+ if (extensions[PerMessageDeflate.extensionName]) {
+ perMessageDeflate.accept(
+ extensions[PerMessageDeflate.extensionName]
+ );
+ this._extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
+ }
+ } catch (err) {
+ socket.destroy();
+ this.finalize(new Error('Invalid Sec-WebSocket-Extensions header'));
+ return;
+ }
+ }
+
+ this.setSocket(socket, head, 0);
+ });
+}
diff --git a/node_modules/ws/package.json b/node_modules/ws/package.json
new file mode 100644
index 00000000..0623d243
--- /dev/null
+++ b/node_modules/ws/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "ws",
+ "version": "4.1.0",
+ "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
+ "keywords": [
+ "HyBi",
+ "Push",
+ "RFC-6455",
+ "WebSocket",
+ "WebSockets",
+ "real-time"
+ ],
+ "homepage": "https://github.com/websockets/ws",
+ "bugs": "https://github.com/websockets/ws/issues",
+ "repository": "websockets/ws",
+ "author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
+ "license": "MIT",
+ "main": "index.js",
+ "files": [
+ "index.js",
+ "lib"
+ ],
+ "scripts": {
+ "test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
+ "integration": "eslint . && mocha test/*.integration.js",
+ "lint": "eslint ."
+ },
+ "dependencies": {
+ "async-limiter": "~1.0.0",
+ "safe-buffer": "~5.1.0"
+ },
+ "devDependencies": {
+ "benchmark": "~2.1.2",
+ "bufferutil": "~3.0.0",
+ "eslint": "~4.18.0",
+ "eslint-config-standard": "~11.0.0",
+ "eslint-plugin-import": "~2.9.0",
+ "eslint-plugin-node": "~6.0.0",
+ "eslint-plugin-promise": "~3.6.0",
+ "eslint-plugin-standard": "~3.0.0",
+ "mocha": "~5.0.0",
+ "nyc": "~11.4.1",
+ "utf-8-validate": "~4.0.0"
+ }
+}