packages feed

jsaddle-warp 0.9.9.2 → 0.9.9.3

raw patch · 31 files changed

+6411/−6 lines, 31 filesdep ~containers

Dependency ranges changed: containers

Files

jsaddle-warp.cabal view
@@ -1,6 +1,6 @@+cabal-version: 2.4 name: jsaddle-warp-version: 0.9.9.2-cabal-version: >=1.10+version: 0.9.9.3 build-type: Simple license: MIT license-file: LICENSE@@ -13,7 +13,42 @@     drive a small JavaScript helper. category: Web, Javascript author: Hamish Mackenzie-+tested-with: GHC==9.12.2, GHC==9.10.1, GHC==9.8.4, GHC==9.6.7, GHC==9.4.8, GHC==9.2.8, GHC==9.0.2, GHC==8.10.7, GHC==8.8.4, GHC==8.6.5, GHC==8.4.4+data-files:+    node-client/index.js+    node-client/node_modules+    node-client/node_modules/.package-lock.json+    node-client/node_modules/ws+    node-client/node_modules/ws/LICENSE+    node-client/node_modules/ws/README.md+    node-client/node_modules/ws/browser.js+    node-client/node_modules/ws/index.js+    node-client/node_modules/ws/lib+    node-client/node_modules/ws/lib/buffer-util.js+    node-client/node_modules/ws/lib/constants.js+    node-client/node_modules/ws/lib/event-target.js+    node-client/node_modules/ws/lib/extension.js+    node-client/node_modules/ws/lib/limiter.js+    node-client/node_modules/ws/lib/permessage-deflate.js+    node-client/node_modules/ws/lib/receiver.js+    node-client/node_modules/ws/lib/sender.js+    node-client/node_modules/ws/lib/stream.js+    node-client/node_modules/ws/lib/subprotocol.js+    node-client/node_modules/ws/lib/validation.js+    node-client/node_modules/ws/lib/websocket-server.js+    node-client/node_modules/ws/lib/websocket.js+    node-client/node_modules/ws/package.json+    node-client/node_modules/ws/wrapper.mjs+    node-client/node_modules/xmlhttprequest+    node-client/node_modules/xmlhttprequest/.jshintrc+    node-client/node_modules/xmlhttprequest/.npmignore+    node-client/node_modules/xmlhttprequest/LICENSE+    node-client/node_modules/xmlhttprequest/README.md+    node-client/node_modules/xmlhttprequest/lib+    node-client/node_modules/xmlhttprequest/lib/XMLHttpRequest.js+    node-client/node_modules/xmlhttprequest/package.json+    node-client/package-lock.json+    node-client/package.json source-repository head     type: git     location: https://github.com/ghcjs/jsaddle@@ -29,7 +64,7 @@         build-depends:             aeson >=0.8.0.2 && <2.3,             bytestring >=0.10.6.0 && <0.13,-            containers >=0.5.6.2 && <0.8,+            containers >=0.5.6.2 && <0.9,             foreign-store >=0.2 && <0.3,             http-types >=0.8.6 && <0.13,             jsaddle >=0.9.9.0 && <0.10,
+ node-client/index.js view
@@ -0,0 +1,34 @@+const http = require('http');+const ws = require('ws');+const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;++let port = 3709;++if (process.argv[2]) {+  port = Number(process.argv[2]);+}+console.log('Running client on port:', port);+++let request = http.get('http://0.0.0.0:' + port + '/jsaddle.js', (res) => {+  if (res.statusCode !== 200) {+    console.error(`Did not get an OK from the server. Code: ${res.statusCode}`);+    res.resume();+    return;+  }++  let data = '';++  res.on('data', (chunk) => {+    data += chunk;+  });++  res.on('close', () => {+    console.log("Connecting to JSaddle running on port", port);+    eval(data);+  });+});++request.on('error', (err) => {+  console.error(`Encountered an error trying to make a request: ${err.message}`);+});
+ node-client/node_modules/.package-lock.json view
@@ -0,0 +1,36 @@+{+  "name": "jsaddle-warp-node-client",+  "version": "0.1.0",+  "lockfileVersion": 3,+  "requires": true,+  "packages": {+    "node_modules/ws": {+      "version": "8.18.0",+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",+      "engines": {+        "node": ">=10.0.0"+      },+      "peerDependencies": {+        "bufferutil": "^4.0.1",+        "utf-8-validate": ">=5.0.2"+      },+      "peerDependenciesMeta": {+        "bufferutil": {+          "optional": true+        },+        "utf-8-validate": {+          "optional": true+        }+      }+    },+    "node_modules/xmlhttprequest": {+      "version": "1.8.0",+      "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",+      "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",+      "engines": {+        "node": ">=0.4.0"+      }+    }+  }+}
+ node-client/node_modules/ws/LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>+Copyright (c) 2013 Arnout Kazemier and contributors+Copyright (c) 2016 Luigi Pinca and contributors++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.
+ node-client/node_modules/ws/README.md view
@@ -0,0 +1,548 @@+# ws: a Node.js WebSocket library++[![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws)+[![CI](https://img.shields.io/github/actions/workflow/status/websockets/ws/ci.yml?branch=master&label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)+[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg?logo=coveralls)](https://coveralls.io/github/websockets/ws)++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 backend 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](#opt-in-for-performance)+    - [Legacy opt-in for performance](#legacy-opt-in-for-performance)+- [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)+  - [Simple server](#simple-server)+  - [External HTTP/S server](#external-https-server)+  - [Multiple servers sharing a single HTTP/S server](#multiple-servers-sharing-a-single-https-server)+  - [Client authentication](#client-authentication)+  - [Server broadcast](#server-broadcast)+  - [Round-trip time](#round-trip-time)+  - [Use the Node.js streams API](#use-the-nodejs-streams-api)+  - [Other examples](#other-examples)+- [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 ws+```++### Opt-in for performance++[bufferutil][] is an optional module that can be installed alongside the ws+module:++```+npm install --save-optional bufferutil+```++This is a binary addon that improves the performance of certain operations such+as masking and unmasking the data payload of the WebSocket frames. Prebuilt+binaries are available for the most popular platforms, so you don't necessarily+need to have a C++ compiler installed on your machine.++To force ws to not use bufferutil, use the+[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This+can be useful to enhance security in systems where a user can put a package in+the package search path of an application of another user, due to how the+Node.js resolver algorithm works.++#### Legacy opt-in for performance++If you are running on an old version of Node.js (prior to v18.14.0), ws also+supports the [utf-8-validate][] module:++```+npm install --save-optional utf-8-validate+```++This contains a binary polyfill for [`buffer.isUtf8()`][].++To force ws not to use utf-8-validate, use the+[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.++## API docs++See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and+utility functions.++## 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+import WebSocket, { WebSocketServer } from 'ws';++const wss = new WebSocketServer({+  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.+    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 if context takeover is disabled.+  }+});+```++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+import WebSocket from 'ws';++const ws = new WebSocket('ws://www.host.com/path', {+  perMessageDeflate: false+});+```++## Usage examples++### Sending and receiving text data++```js+import WebSocket from 'ws';++const ws = new WebSocket('ws://www.host.com/path');++ws.on('error', console.error);++ws.on('open', function open() {+  ws.send('something');+});++ws.on('message', function message(data) {+  console.log('received: %s', data);+});+```++### Sending binary data++```js+import WebSocket from 'ws';++const ws = new WebSocket('ws://www.host.com/path');++ws.on('error', console.error);++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);+});+```++### Simple server++```js+import { WebSocketServer } from 'ws';++const wss = new WebSocketServer({ port: 8080 });++wss.on('connection', function connection(ws) {+  ws.on('error', console.error);++  ws.on('message', function message(data) {+    console.log('received: %s', data);+  });++  ws.send('something');+});+```++### External HTTP/S server++```js+import { createServer } from 'https';+import { readFileSync } from 'fs';+import { WebSocketServer } from 'ws';++const server = createServer({+  cert: readFileSync('/path/to/cert.pem'),+  key: readFileSync('/path/to/key.pem')+});+const wss = new WebSocketServer({ server });++wss.on('connection', function connection(ws) {+  ws.on('error', console.error);++  ws.on('message', function message(data) {+    console.log('received: %s', data);+  });++  ws.send('something');+});++server.listen(8080);+```++### Multiple servers sharing a single HTTP/S server++```js+import { createServer } from 'http';+import { WebSocketServer } from 'ws';++const server = createServer();+const wss1 = new WebSocketServer({ noServer: true });+const wss2 = new WebSocketServer({ noServer: true });++wss1.on('connection', function connection(ws) {+  ws.on('error', console.error);++  // ...+});++wss2.on('connection', function connection(ws) {+  ws.on('error', console.error);++  // ...+});++server.on('upgrade', function upgrade(request, socket, head) {+  const { pathname } = new URL(request.url, 'wss://base.url');++  if (pathname === '/foo') {+    wss1.handleUpgrade(request, socket, head, function done(ws) {+      wss1.emit('connection', ws, request);+    });+  } else if (pathname === '/bar') {+    wss2.handleUpgrade(request, socket, head, function done(ws) {+      wss2.emit('connection', ws, request);+    });+  } else {+    socket.destroy();+  }+});++server.listen(8080);+```++### Client authentication++```js+import { createServer } from 'http';+import { WebSocketServer } from 'ws';++function onSocketError(err) {+  console.error(err);+}++const server = createServer();+const wss = new WebSocketServer({ noServer: true });++wss.on('connection', function connection(ws, request, client) {+  ws.on('error', console.error);++  ws.on('message', function message(data) {+    console.log(`Received message ${data} from user ${client}`);+  });+});++server.on('upgrade', function upgrade(request, socket, head) {+  socket.on('error', onSocketError);++  // This function is not defined on purpose. Implement it with your own logic.+  authenticate(request, function next(err, client) {+    if (err || !client) {+      socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');+      socket.destroy();+      return;+    }++    socket.removeListener('error', onSocketError);++    wss.handleUpgrade(request, socket, head, function done(ws) {+      wss.emit('connection', ws, request, client);+    });+  });+});++server.listen(8080);+```++Also see the provided [example][session-parse-example] using `express-session`.++### Server broadcast++A client WebSocket broadcasting to all connected WebSocket clients, including+itself.++```js+import WebSocket, { WebSocketServer } from 'ws';++const wss = new WebSocketServer({ port: 8080 });++wss.on('connection', function connection(ws) {+  ws.on('error', console.error);++  ws.on('message', function message(data, isBinary) {+    wss.clients.forEach(function each(client) {+      if (client.readyState === WebSocket.OPEN) {+        client.send(data, { binary: isBinary });+      }+    });+  });+});+```++A client WebSocket broadcasting to every other connected WebSocket clients,+excluding itself.++```js+import WebSocket, { WebSocketServer } from 'ws';++const wss = new WebSocketServer({ port: 8080 });++wss.on('connection', function connection(ws) {+  ws.on('error', console.error);++  ws.on('message', function message(data, isBinary) {+    wss.clients.forEach(function each(client) {+      if (client !== ws && client.readyState === WebSocket.OPEN) {+        client.send(data, { binary: isBinary });+      }+    });+  });+});+```++### Round-trip time++```js+import WebSocket from 'ws';++const ws = new WebSocket('wss://websocket-echo.com/');++ws.on('error', console.error);++ws.on('open', function open() {+  console.log('connected');+  ws.send(Date.now());+});++ws.on('close', function close() {+  console.log('disconnected');+});++ws.on('message', function message(data) {+  console.log(`Round-trip time: ${Date.now() - data} ms`);++  setTimeout(function timeout() {+    ws.send(Date.now());+  }, 500);+});+```++### Use the Node.js streams API++```js+import WebSocket, { createWebSocketStream } from 'ws';++const ws = new WebSocket('wss://websocket-echo.com/');++const duplex = createWebSocketStream(ws, { encoding: 'utf8' });++duplex.on('error', console.error);++duplex.pipe(process.stdout);+process.stdin.pipe(duplex);+```++### Other examples++For a full example with a browser client communicating with a ws server, see the+examples folder.++Otherwise, see the test cases.++## FAQ++### How to get the IP address of the client?++The remote IP address can be obtained from the raw socket.++```js+import { WebSocketServer } from 'ws';++const wss = new WebSocketServer({ port: 8080 });++wss.on('connection', function connection(ws, req) {+  const ip = req.socket.remoteAddress;++  ws.on('error', console.error);+});+```++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'].split(',')[0].trim();++  ws.on('error', console.error);+});+```++### 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+import { WebSocketServer } from 'ws';++function heartbeat() {+  this.isAlive = true;+}++const wss = new WebSocketServer({ port: 8080 });++wss.on('connection', function connection(ws) {+  ws.isAlive = true;+  ws.on('error', console.error);+  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();+  });+}, 30000);++wss.on('close', function close() {+  clearInterval(interval);+});+```++Pong messages are automatically sent in response to ping messages as required by+the spec.++Just like the server example above, your clients might as well lose connection+without knowing it. You might want to add a ping listener on your clients to+prevent that. A simple implementation would be:++```js+import WebSocket from 'ws';++function heartbeat() {+  clearTimeout(this.pingTimeout);++  // Use `WebSocket#terminate()`, which immediately destroys the connection,+  // instead of `WebSocket#close()`, which waits for the close timer.+  // Delay should be equal to the interval at which your server+  // sends out pings plus a conservative assumption of the latency.+  this.pingTimeout = setTimeout(() => {+    this.terminate();+  }, 30000 + 1000);+}++const client = new WebSocket('wss://websocket-echo.com/');++client.on('error', console.error);+client.on('open', heartbeat);+client.on('ping', heartbeat);+client.on('close', function clear() {+  clearTimeout(this.pingTimeout);+});+```++### 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)++[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input+[bufferutil]: https://github.com/websockets/bufferutil+[changelog]: https://github.com/websockets/ws/releases+[client-report]: http://websockets.github.io/ws/autobahn/clients/+[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent+[node-zlib-bug]: https://github.com/nodejs/node/issues/8871+[node-zlib-deflaterawdocs]:+  https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options+[permessage-deflate]: https://tools.ietf.org/html/rfc7692+[server-report]: http://websockets.github.io/ws/autobahn/servers/+[session-parse-example]: ./examples/express-session-parse+[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent+[utf-8-validate]: https://github.com/websockets/utf-8-validate+[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback
+ node-client/node_modules/ws/browser.js view
@@ -0,0 +1,8 @@+'use strict';++module.exports = function () {+  throw new Error(+    'ws does not work in the browser. Browser clients must use the native ' ++      'WebSocket object'+  );+};
+ node-client/node_modules/ws/index.js view
@@ -0,0 +1,13 @@+'use strict';++const WebSocket = require('./lib/websocket');++WebSocket.createWebSocketStream = require('./lib/stream');+WebSocket.Server = require('./lib/websocket-server');+WebSocket.Receiver = require('./lib/receiver');+WebSocket.Sender = require('./lib/sender');++WebSocket.WebSocket = WebSocket;+WebSocket.WebSocketServer = WebSocket.Server;++module.exports = WebSocket;
+ node-client/node_modules/ws/lib/buffer-util.js view
@@ -0,0 +1,131 @@+'use strict';++const { EMPTY_BUFFER } = require('./constants');++const FastBuffer = Buffer[Symbol.species];++/**+ * 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+ */+function concat(list, totalLength) {+  if (list.length === 0) return EMPTY_BUFFER;+  if (list.length === 1) return list[0];++  const target = Buffer.allocUnsafe(totalLength);+  let offset = 0;++  for (let i = 0; i < list.length; i++) {+    const buf = list[i];+    target.set(buf, offset);+    offset += buf.length;+  }++  if (offset < totalLength) {+    return new FastBuffer(target.buffer, target.byteOffset, offset);+  }++  return target;+}++/**+ * 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+ */+function _mask(source, mask, output, offset, length) {+  for (let 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+ */+function _unmask(buffer, mask) {+  for (let i = 0; i < buffer.length; i++) {+    buffer[i] ^= mask[i & 3];+  }+}++/**+ * Converts a buffer to an `ArrayBuffer`.+ *+ * @param {Buffer} buf The buffer to convert+ * @return {ArrayBuffer} Converted buffer+ * @public+ */+function toArrayBuffer(buf) {+  if (buf.length === buf.buffer.byteLength) {+    return buf.buffer;+  }++  return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);+}++/**+ * Converts `data` to a `Buffer`.+ *+ * @param {*} data The data to convert+ * @return {Buffer} The buffer+ * @throws {TypeError}+ * @public+ */+function toBuffer(data) {+  toBuffer.readOnly = true;++  if (Buffer.isBuffer(data)) return data;++  let buf;++  if (data instanceof ArrayBuffer) {+    buf = new FastBuffer(data);+  } else if (ArrayBuffer.isView(data)) {+    buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);+  } else {+    buf = Buffer.from(data);+    toBuffer.readOnly = false;+  }++  return buf;+}++module.exports = {+  concat,+  mask: _mask,+  toArrayBuffer,+  toBuffer,+  unmask: _unmask+};++/* istanbul ignore else  */+if (!process.env.WS_NO_BUFFER_UTIL) {+  try {+    const bufferUtil = require('bufferutil');++    module.exports.mask = function (source, mask, output, offset, length) {+      if (length < 48) _mask(source, mask, output, offset, length);+      else bufferUtil.mask(source, mask, output, offset, length);+    };++    module.exports.unmask = function (buffer, mask) {+      if (buffer.length < 32) _unmask(buffer, mask);+      else bufferUtil.unmask(buffer, mask);+    };+  } catch (e) {+    // Continue regardless of the error.+  }+}
+ node-client/node_modules/ws/lib/constants.js view
@@ -0,0 +1,18 @@+'use strict';++const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];+const hasBlob = typeof Blob !== 'undefined';++if (hasBlob) BINARY_TYPES.push('blob');++module.exports = {+  BINARY_TYPES,+  EMPTY_BUFFER: Buffer.alloc(0),+  GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',+  hasBlob,+  kForOnEventAttribute: Symbol('kIsForOnEventAttribute'),+  kListener: Symbol('kListener'),+  kStatusCode: Symbol('status-code'),+  kWebSocket: Symbol('websocket'),+  NOOP: () => {}+};
+ node-client/node_modules/ws/lib/event-target.js view
@@ -0,0 +1,292 @@+'use strict';++const { kForOnEventAttribute, kListener } = require('./constants');++const kCode = Symbol('kCode');+const kData = Symbol('kData');+const kError = Symbol('kError');+const kMessage = Symbol('kMessage');+const kReason = Symbol('kReason');+const kTarget = Symbol('kTarget');+const kType = Symbol('kType');+const kWasClean = Symbol('kWasClean');++/**+ * Class representing an event.+ */+class Event {+  /**+   * Create a new `Event`.+   *+   * @param {String} type The name of the event+   * @throws {TypeError} If the `type` argument is not specified+   */+  constructor(type) {+    this[kTarget] = null;+    this[kType] = type;+  }++  /**+   * @type {*}+   */+  get target() {+    return this[kTarget];+  }++  /**+   * @type {String}+   */+  get type() {+    return this[kType];+  }+}++Object.defineProperty(Event.prototype, 'target', { enumerable: true });+Object.defineProperty(Event.prototype, 'type', { enumerable: true });++/**+ * Class representing a close event.+ *+ * @extends Event+ */+class CloseEvent extends Event {+  /**+   * Create a new `CloseEvent`.+   *+   * @param {String} type The name of the event+   * @param {Object} [options] A dictionary object that allows for setting+   *     attributes via object members of the same name+   * @param {Number} [options.code=0] The status code explaining why the+   *     connection was closed+   * @param {String} [options.reason=''] A human-readable string explaining why+   *     the connection was closed+   * @param {Boolean} [options.wasClean=false] Indicates whether or not the+   *     connection was cleanly closed+   */+  constructor(type, options = {}) {+    super(type);++    this[kCode] = options.code === undefined ? 0 : options.code;+    this[kReason] = options.reason === undefined ? '' : options.reason;+    this[kWasClean] = options.wasClean === undefined ? false : options.wasClean;+  }++  /**+   * @type {Number}+   */+  get code() {+    return this[kCode];+  }++  /**+   * @type {String}+   */+  get reason() {+    return this[kReason];+  }++  /**+   * @type {Boolean}+   */+  get wasClean() {+    return this[kWasClean];+  }+}++Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true });+Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true });+Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true });++/**+ * Class representing an error event.+ *+ * @extends Event+ */+class ErrorEvent extends Event {+  /**+   * Create a new `ErrorEvent`.+   *+   * @param {String} type The name of the event+   * @param {Object} [options] A dictionary object that allows for setting+   *     attributes via object members of the same name+   * @param {*} [options.error=null] The error that generated this event+   * @param {String} [options.message=''] The error message+   */+  constructor(type, options = {}) {+    super(type);++    this[kError] = options.error === undefined ? null : options.error;+    this[kMessage] = options.message === undefined ? '' : options.message;+  }++  /**+   * @type {*}+   */+  get error() {+    return this[kError];+  }++  /**+   * @type {String}+   */+  get message() {+    return this[kMessage];+  }+}++Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true });+Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true });++/**+ * Class representing a message event.+ *+ * @extends Event+ */+class MessageEvent extends Event {+  /**+   * Create a new `MessageEvent`.+   *+   * @param {String} type The name of the event+   * @param {Object} [options] A dictionary object that allows for setting+   *     attributes via object members of the same name+   * @param {*} [options.data=null] The message content+   */+  constructor(type, options = {}) {+    super(type);++    this[kData] = options.data === undefined ? null : options.data;+  }++  /**+   * @type {*}+   */+  get data() {+    return this[kData];+  }+}++Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });++/**+ * 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} type A string representing the event type to listen for+   * @param {(Function|Object)} handler The listener to add+   * @param {Object} [options] An options object specifies characteristics about+   *     the event listener+   * @param {Boolean} [options.once=false] A `Boolean` indicating that the+   *     listener should be invoked at most once after being added. If `true`,+   *     the listener would be automatically removed when invoked.+   * @public+   */+  addEventListener(type, handler, options = {}) {+    for (const listener of this.listeners(type)) {+      if (+        !options[kForOnEventAttribute] &&+        listener[kListener] === handler &&+        !listener[kForOnEventAttribute]+      ) {+        return;+      }+    }++    let wrapper;++    if (type === 'message') {+      wrapper = function onMessage(data, isBinary) {+        const event = new MessageEvent('message', {+          data: isBinary ? data : data.toString()+        });++        event[kTarget] = this;+        callListener(handler, this, event);+      };+    } else if (type === 'close') {+      wrapper = function onClose(code, message) {+        const event = new CloseEvent('close', {+          code,+          reason: message.toString(),+          wasClean: this._closeFrameReceived && this._closeFrameSent+        });++        event[kTarget] = this;+        callListener(handler, this, event);+      };+    } else if (type === 'error') {+      wrapper = function onError(error) {+        const event = new ErrorEvent('error', {+          error,+          message: error.message+        });++        event[kTarget] = this;+        callListener(handler, this, event);+      };+    } else if (type === 'open') {+      wrapper = function onOpen() {+        const event = new Event('open');++        event[kTarget] = this;+        callListener(handler, this, event);+      };+    } else {+      return;+    }++    wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute];+    wrapper[kListener] = handler;++    if (options.once) {+      this.once(type, wrapper);+    } else {+      this.on(type, wrapper);+    }+  },++  /**+   * Remove an event listener.+   *+   * @param {String} type A string representing the event type to remove+   * @param {(Function|Object)} handler The listener to remove+   * @public+   */+  removeEventListener(type, handler) {+    for (const listener of this.listeners(type)) {+      if (listener[kListener] === handler && !listener[kForOnEventAttribute]) {+        this.removeListener(type, listener);+        break;+      }+    }+  }+};++module.exports = {+  CloseEvent,+  ErrorEvent,+  Event,+  EventTarget,+  MessageEvent+};++/**+ * Call an event listener+ *+ * @param {(Function|Object)} listener The listener to call+ * @param {*} thisArg The value to use as `this`` when calling the listener+ * @param {Event} event The event to pass to the listener+ * @private+ */+function callListener(listener, thisArg, event) {+  if (typeof listener === 'object' && listener.handleEvent) {+    listener.handleEvent.call(listener, event);+  } else {+    listener.call(thisArg, event);+  }+}
+ node-client/node_modules/ws/lib/extension.js view
@@ -0,0 +1,203 @@+'use strict';++const { tokenChars } = require('./validation');++/**+ * 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 (dest[name] === undefined) dest[name] = [elem];+  else dest[name].push(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 = Object.create(null);+  let params = Object.create(null);+  let mustUnescape = false;+  let isEscaping = false;+  let inQuotes = false;+  let extensionName;+  let paramName;+  let start = -1;+  let code = -1;+  let end = -1;+  let i = 0;++  for (; i < header.length; i++) {+    code = header.charCodeAt(i);++    if (extensionName === undefined) {+      if (end === -1 && tokenChars[code] === 1) {+        if (start === -1) start = i;+      } else if (+        i !== 0 &&+        (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 = Object.create(null);+        } 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 = Object.create(null);+          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;+        let 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 = Object.create(null);+          extensionName = undefined;+        }++        paramName = undefined;+        start = end = -1;+      } else {+        throw new SyntaxError(`Unexpected character at index ${i}`);+      }+    }+  }++  if (start === -1 || inQuotes || code === 0x20 || code === 0x09) {+    throw new SyntaxError('Unexpected end of input');+  }++  if (end === -1) end = i;+  const token = header.slice(start, end);+  if (extensionName === undefined) {+    push(offers, token, params);+  } 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) => {+      let configurations = extensions[extension];+      if (!Array.isArray(configurations)) configurations = [configurations];+      return configurations+        .map((params) => {+          return [extension]+            .concat(+              Object.keys(params).map((k) => {+                let 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 };
+ node-client/node_modules/ws/lib/limiter.js view
@@ -0,0 +1,55 @@+'use strict';++const kDone = Symbol('kDone');+const kRun = Symbol('kRun');++/**+ * A very simple job queue with adjustable concurrency. Adapted from+ * https://github.com/STRML/async-limiter+ */+class Limiter {+  /**+   * Creates a new `Limiter`.+   *+   * @param {Number} [concurrency=Infinity] The maximum number of jobs allowed+   *     to run concurrently+   */+  constructor(concurrency) {+    this[kDone] = () => {+      this.pending--;+      this[kRun]();+    };+    this.concurrency = concurrency || Infinity;+    this.jobs = [];+    this.pending = 0;+  }++  /**+   * Adds a job to the queue.+   *+   * @param {Function} job The job to run+   * @public+   */+  add(job) {+    this.jobs.push(job);+    this[kRun]();+  }++  /**+   * Removes a job from the queue and runs it if possible.+   *+   * @private+   */+  [kRun]() {+    if (this.pending === this.concurrency) return;++    if (this.jobs.length) {+      const job = this.jobs.shift();++      this.pending++;+      job(this[kDone]);+    }+  }+}++module.exports = Limiter;
+ node-client/node_modules/ws/lib/permessage-deflate.js view
@@ -0,0 +1,514 @@+'use strict';++const zlib = require('zlib');++const bufferUtil = require('./buffer-util');+const Limiter = require('./limiter');+const { kStatusCode } = require('./constants');++const FastBuffer = Buffer[Symbol.species];+const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);+const kPerMessageDeflate = Symbol('permessage-deflate');+const kTotalLength = Symbol('total-length');+const kCallback = Symbol('callback');+const kBuffers = Symbol('buffers');+const kError = Symbol('error');++//+// 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|Number)} [options.clientMaxWindowBits] Advertise support+   *     for, or request, a custom client window size+   * @param {Boolean} [options.clientNoContextTakeover=false] Advertise/+   *     acknowledge disabling of client context takeover+   * @param {Number} [options.concurrencyLimit=10] The number of concurrent+   *     calls to zlib+   * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the+   *     use of a custom server window size+   * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept+   *     disabling of server context takeover+   * @param {Number} [options.threshold=1024] Size (in bytes) below which+   *     messages should not be compressed if context takeover is disabled+   * @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on+   *     deflate+   * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on+   *     inflate+   * @param {Boolean} [isServer=false] Create the instance in either server or+   *     client mode+   * @param {Number} [maxPayload=0] 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) {+      this._inflate.close();+      this._inflate = null;+    }++    if (this._deflate) {+      const callback = this._deflate[kCallback];++      this._deflate.close();+      this._deflate = null;++      if (callback) {+        callback(+          new Error(+            'The deflate stream was closed while data was being processed'+          )+        );+      }+    }+  }++  /**+   *  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) => {+        let 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.+   *+   * @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.add((done) => {+      this._decompress(data, fin, (err, result) => {+        done();+        callback(err, result);+      });+    });+  }++  /**+   * Compress data. Concurrency limited.+   *+   * @param {(Buffer|String)} 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.add((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({+        ...this._options.zlibInflateOptions,+        windowBits+      });+      this._inflate[kPerMessageDeflate] = this;+      this._inflate[kTotalLength] = 0;+      this._inflate[kBuffers] = [];+      this._inflate.on('error', inflateOnError);+      this._inflate.on('data', inflateOnData);+    }++    this._inflate[kCallback] = callback;++    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 (this._inflate._readableState.endEmitted) {+        this._inflate.close();+        this._inflate = null;+      } else {+        this._inflate[kTotalLength] = 0;+        this._inflate[kBuffers] = [];++        if (fin && this.params[`${endpoint}_no_context_takeover`]) {+          this._inflate.reset();+        }+      }++      callback(null, data);+    });+  }++  /**+   * Compress data.+   *+   * @param {(Buffer|String)} 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) {+    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({+        ...this._options.zlibDeflateOptions,+        windowBits+      });++      this._deflate[kTotalLength] = 0;+      this._deflate[kBuffers] = [];++      this._deflate.on('data', deflateOnData);+    }++    this._deflate[kCallback] = callback;++    this._deflate.write(data);+    this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {+      if (!this._deflate) {+        //+        // The deflate stream was closed while data was being processed.+        //+        return;+      }++      let data = bufferUtil.concat(+        this._deflate[kBuffers],+        this._deflate[kTotalLength]+      );++      if (fin) {+        data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4);+      }++      //+      // Ensure that the callback will not be called again in+      // `PerMessageDeflate#cleanup()`.+      //+      this._deflate[kCallback] = null;++      this._deflate[kTotalLength] = 0;+      this._deflate[kBuffers] = [];++      if (fin && this.params[`${endpoint}_no_context_takeover`]) {+        this._deflate.reset();+      }++      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[kPerMessageDeflate]._maxPayload < 1 ||+    this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload+  ) {+    this[kBuffers].push(chunk);+    return;+  }++  this[kError] = new RangeError('Max payload size exceeded');+  this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';+  this[kError][kStatusCode] = 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[kPerMessageDeflate]._inflate = null;+  err[kStatusCode] = 1007;+  this[kCallback](err);+}
+ node-client/node_modules/ws/lib/receiver.js view
@@ -0,0 +1,706 @@+'use strict';++const { Writable } = require('stream');++const PerMessageDeflate = require('./permessage-deflate');+const {+  BINARY_TYPES,+  EMPTY_BUFFER,+  kStatusCode,+  kWebSocket+} = require('./constants');+const { concat, toArrayBuffer, unmask } = require('./buffer-util');+const { isValidStatusCode, isValidUTF8 } = require('./validation');++const FastBuffer = Buffer[Symbol.species];++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;+const DEFER_EVENT = 6;++/**+ * HyBi Receiver implementation.+ *+ * @extends Writable+ */+class Receiver extends Writable {+  /**+   * Creates a Receiver instance.+   *+   * @param {Object} [options] Options object+   * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether+   *     any of the `'message'`, `'ping'`, and `'pong'` events can be emitted+   *     multiple times in the same tick+   * @param {String} [options.binaryType=nodebuffer] The type for binary data+   * @param {Object} [options.extensions] An object containing the negotiated+   *     extensions+   * @param {Boolean} [options.isServer=false] Specifies whether to operate in+   *     client or server mode+   * @param {Number} [options.maxPayload=0] The maximum allowed message length+   * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or+   *     not to skip UTF-8 validation for text and close messages+   */+  constructor(options = {}) {+    super();++    this._allowSynchronousEvents =+      options.allowSynchronousEvents !== undefined+        ? options.allowSynchronousEvents+        : true;+    this._binaryType = options.binaryType || BINARY_TYPES[0];+    this._extensions = options.extensions || {};+    this._isServer = !!options.isServer;+    this._maxPayload = options.maxPayload | 0;+    this._skipUTF8Validation = !!options.skipUTF8Validation;+    this[kWebSocket] = undefined;++    this._bufferedBytes = 0;+    this._buffers = [];++    this._compressed = false;+    this._payloadLength = 0;+    this._mask = undefined;+    this._fragmented = 0;+    this._masked = false;+    this._fin = false;+    this._opcode = 0;++    this._totalPayloadLength = 0;+    this._messageLength = 0;+    this._fragments = [];++    this._errored = false;+    this._loop = false;+    this._state = GET_INFO;+  }++  /**+   * Implements `Writable.prototype._write()`.+   *+   * @param {Buffer} chunk The chunk of data to write+   * @param {String} encoding The character encoding of `chunk`+   * @param {Function} cb Callback+   * @private+   */+  _write(chunk, encoding, cb) {+    if (this._opcode === 0x08 && this._state == GET_INFO) return cb();++    this._bufferedBytes += chunk.length;+    this._buffers.push(chunk);+    this.startLoop(cb);+  }++  /**+   * Consumes `n` bytes from the buffered data.+   *+   * @param {Number} n The number of bytes to consume+   * @return {Buffer} The consumed bytes+   * @private+   */+  consume(n) {+    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] = new FastBuffer(+        buf.buffer,+        buf.byteOffset + n,+        buf.length - n+      );++      return new FastBuffer(buf.buffer, buf.byteOffset, n);+    }++    const dst = Buffer.allocUnsafe(n);++    do {+      const buf = this._buffers[0];+      const offset = dst.length - n;++      if (n >= buf.length) {+        dst.set(this._buffers.shift(), offset);+      } else {+        dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);+        this._buffers[0] = new FastBuffer(+          buf.buffer,+          buf.byteOffset + n,+          buf.length - n+        );+      }++      n -= buf.length;+    } while (n > 0);++    return dst;+  }++  /**+   * Starts the parsing loop.+   *+   * @param {Function} cb Callback+   * @private+   */+  startLoop(cb) {+    this._loop = true;++    do {+      switch (this._state) {+        case GET_INFO:+          this.getInfo(cb);+          break;+        case GET_PAYLOAD_LENGTH_16:+          this.getPayloadLength16(cb);+          break;+        case GET_PAYLOAD_LENGTH_64:+          this.getPayloadLength64(cb);+          break;+        case GET_MASK:+          this.getMask();+          break;+        case GET_DATA:+          this.getData(cb);+          break;+        case INFLATING:+        case DEFER_EVENT:+          this._loop = false;+          return;+      }+    } while (this._loop);++    if (!this._errored) cb();+  }++  /**+   * Reads the first two bytes of a frame.+   *+   * @param {Function} cb Callback+   * @private+   */+  getInfo(cb) {+    if (this._bufferedBytes < 2) {+      this._loop = false;+      return;+    }++    const buf = this.consume(2);++    if ((buf[0] & 0x30) !== 0x00) {+      const error = this.createError(+        RangeError,+        'RSV2 and RSV3 must be clear',+        true,+        1002,+        'WS_ERR_UNEXPECTED_RSV_2_3'+      );++      cb(error);+      return;+    }++    const compressed = (buf[0] & 0x40) === 0x40;++    if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {+      const error = this.createError(+        RangeError,+        'RSV1 must be clear',+        true,+        1002,+        'WS_ERR_UNEXPECTED_RSV_1'+      );++      cb(error);+      return;+    }++    this._fin = (buf[0] & 0x80) === 0x80;+    this._opcode = buf[0] & 0x0f;+    this._payloadLength = buf[1] & 0x7f;++    if (this._opcode === 0x00) {+      if (compressed) {+        const error = this.createError(+          RangeError,+          'RSV1 must be clear',+          true,+          1002,+          'WS_ERR_UNEXPECTED_RSV_1'+        );++        cb(error);+        return;+      }++      if (!this._fragmented) {+        const error = this.createError(+          RangeError,+          'invalid opcode 0',+          true,+          1002,+          'WS_ERR_INVALID_OPCODE'+        );++        cb(error);+        return;+      }++      this._opcode = this._fragmented;+    } else if (this._opcode === 0x01 || this._opcode === 0x02) {+      if (this._fragmented) {+        const error = this.createError(+          RangeError,+          `invalid opcode ${this._opcode}`,+          true,+          1002,+          'WS_ERR_INVALID_OPCODE'+        );++        cb(error);+        return;+      }++      this._compressed = compressed;+    } else if (this._opcode > 0x07 && this._opcode < 0x0b) {+      if (!this._fin) {+        const error = this.createError(+          RangeError,+          'FIN must be set',+          true,+          1002,+          'WS_ERR_EXPECTED_FIN'+        );++        cb(error);+        return;+      }++      if (compressed) {+        const error = this.createError(+          RangeError,+          'RSV1 must be clear',+          true,+          1002,+          'WS_ERR_UNEXPECTED_RSV_1'+        );++        cb(error);+        return;+      }++      if (+        this._payloadLength > 0x7d ||+        (this._opcode === 0x08 && this._payloadLength === 1)+      ) {+        const error = this.createError(+          RangeError,+          `invalid payload length ${this._payloadLength}`,+          true,+          1002,+          'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'+        );++        cb(error);+        return;+      }+    } else {+      const error = this.createError(+        RangeError,+        `invalid opcode ${this._opcode}`,+        true,+        1002,+        'WS_ERR_INVALID_OPCODE'+      );++      cb(error);+      return;+    }++    if (!this._fin && !this._fragmented) this._fragmented = this._opcode;+    this._masked = (buf[1] & 0x80) === 0x80;++    if (this._isServer) {+      if (!this._masked) {+        const error = this.createError(+          RangeError,+          'MASK must be set',+          true,+          1002,+          'WS_ERR_EXPECTED_MASK'+        );++        cb(error);+        return;+      }+    } else if (this._masked) {+      const error = this.createError(+        RangeError,+        'MASK must be clear',+        true,+        1002,+        'WS_ERR_UNEXPECTED_MASK'+      );++      cb(error);+      return;+    }++    if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;+    else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;+    else this.haveLength(cb);+  }++  /**+   * Gets extended payload length (7+16).+   *+   * @param {Function} cb Callback+   * @private+   */+  getPayloadLength16(cb) {+    if (this._bufferedBytes < 2) {+      this._loop = false;+      return;+    }++    this._payloadLength = this.consume(2).readUInt16BE(0);+    this.haveLength(cb);+  }++  /**+   * Gets extended payload length (7+64).+   *+   * @param {Function} cb Callback+   * @private+   */+  getPayloadLength64(cb) {+    if (this._bufferedBytes < 8) {+      this._loop = false;+      return;+    }++    const buf = this.consume(8);+    const num = buf.readUInt32BE(0);++    //+    // 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) {+      const error = this.createError(+        RangeError,+        'Unsupported WebSocket frame: payload length > 2^53 - 1',+        false,+        1009,+        'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'+      );++      cb(error);+      return;+    }++    this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);+    this.haveLength(cb);+  }++  /**+   * Payload length has been read.+   *+   * @param {Function} cb Callback+   * @private+   */+  haveLength(cb) {+    if (this._payloadLength && this._opcode < 0x08) {+      this._totalPayloadLength += this._payloadLength;+      if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {+        const error = this.createError(+          RangeError,+          'Max payload size exceeded',+          false,+          1009,+          'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'+        );++        cb(error);+        return;+      }+    }++    if (this._masked) this._state = GET_MASK;+    else this._state = GET_DATA;+  }++  /**+   * Reads mask bytes.+   *+   * @private+   */+  getMask() {+    if (this._bufferedBytes < 4) {+      this._loop = false;+      return;+    }++    this._mask = this.consume(4);+    this._state = GET_DATA;+  }++  /**+   * Reads data bytes.+   *+   * @param {Function} cb Callback+   * @private+   */+  getData(cb) {+    let data = EMPTY_BUFFER;++    if (this._payloadLength) {+      if (this._bufferedBytes < this._payloadLength) {+        this._loop = false;+        return;+      }++      data = this.consume(this._payloadLength);++      if (+        this._masked &&+        (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0+      ) {+        unmask(data, this._mask);+      }+    }++    if (this._opcode > 0x07) {+      this.controlMessage(data, cb);+      return;+    }++    if (this._compressed) {+      this._state = INFLATING;+      this.decompress(data, cb);+      return;+    }++    if (data.length) {+      //+      // This message is not compressed so its length is the sum of the payload+      // length of all fragments.+      //+      this._messageLength = this._totalPayloadLength;+      this._fragments.push(data);+    }++    this.dataMessage(cb);+  }++  /**+   * Decompresses data.+   *+   * @param {Buffer} data Compressed data+   * @param {Function} cb Callback+   * @private+   */+  decompress(data, cb) {+    const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];++    perMessageDeflate.decompress(data, this._fin, (err, buf) => {+      if (err) return cb(err);++      if (buf.length) {+        this._messageLength += buf.length;+        if (this._messageLength > this._maxPayload && this._maxPayload > 0) {+          const error = this.createError(+            RangeError,+            'Max payload size exceeded',+            false,+            1009,+            'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'+          );++          cb(error);+          return;+        }++        this._fragments.push(buf);+      }++      this.dataMessage(cb);+      if (this._state === GET_INFO) this.startLoop(cb);+    });+  }++  /**+   * Handles a data message.+   *+   * @param {Function} cb Callback+   * @private+   */+  dataMessage(cb) {+    if (!this._fin) {+      this._state = GET_INFO;+      return;+    }++    const messageLength = this._messageLength;+    const fragments = this._fragments;++    this._totalPayloadLength = 0;+    this._messageLength = 0;+    this._fragmented = 0;+    this._fragments = [];++    if (this._opcode === 2) {+      let data;++      if (this._binaryType === 'nodebuffer') {+        data = concat(fragments, messageLength);+      } else if (this._binaryType === 'arraybuffer') {+        data = toArrayBuffer(concat(fragments, messageLength));+      } else if (this._binaryType === 'blob') {+        data = new Blob(fragments);+      } else {+        data = fragments;+      }++      if (this._allowSynchronousEvents) {+        this.emit('message', data, true);+        this._state = GET_INFO;+      } else {+        this._state = DEFER_EVENT;+        setImmediate(() => {+          this.emit('message', data, true);+          this._state = GET_INFO;+          this.startLoop(cb);+        });+      }+    } else {+      const buf = concat(fragments, messageLength);++      if (!this._skipUTF8Validation && !isValidUTF8(buf)) {+        const error = this.createError(+          Error,+          'invalid UTF-8 sequence',+          true,+          1007,+          'WS_ERR_INVALID_UTF8'+        );++        cb(error);+        return;+      }++      if (this._state === INFLATING || this._allowSynchronousEvents) {+        this.emit('message', buf, false);+        this._state = GET_INFO;+      } else {+        this._state = DEFER_EVENT;+        setImmediate(() => {+          this.emit('message', buf, false);+          this._state = GET_INFO;+          this.startLoop(cb);+        });+      }+    }+  }++  /**+   * Handles a control message.+   *+   * @param {Buffer} data Data to handle+   * @return {(Error|RangeError|undefined)} A possible error+   * @private+   */+  controlMessage(data, cb) {+    if (this._opcode === 0x08) {+      if (data.length === 0) {+        this._loop = false;+        this.emit('conclude', 1005, EMPTY_BUFFER);+        this.end();+      } else {+        const code = data.readUInt16BE(0);++        if (!isValidStatusCode(code)) {+          const error = this.createError(+            RangeError,+            `invalid status code ${code}`,+            true,+            1002,+            'WS_ERR_INVALID_CLOSE_CODE'+          );++          cb(error);+          return;+        }++        const buf = new FastBuffer(+          data.buffer,+          data.byteOffset + 2,+          data.length - 2+        );++        if (!this._skipUTF8Validation && !isValidUTF8(buf)) {+          const error = this.createError(+            Error,+            'invalid UTF-8 sequence',+            true,+            1007,+            'WS_ERR_INVALID_UTF8'+          );++          cb(error);+          return;+        }++        this._loop = false;+        this.emit('conclude', code, buf);+        this.end();+      }++      this._state = GET_INFO;+      return;+    }++    if (this._allowSynchronousEvents) {+      this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);+      this._state = GET_INFO;+    } else {+      this._state = DEFER_EVENT;+      setImmediate(() => {+        this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);+        this._state = GET_INFO;+        this.startLoop(cb);+      });+    }+  }++  /**+   * Builds an error object.+   *+   * @param {function(new:Error|RangeError)} ErrorCtor The error constructor+   * @param {String} message The error message+   * @param {Boolean} prefix Specifies whether or not to add a default prefix to+   *     `message`+   * @param {Number} statusCode The status code+   * @param {String} errorCode The exposed error code+   * @return {(Error|RangeError)} The error+   * @private+   */+  createError(ErrorCtor, message, prefix, statusCode, errorCode) {+    this._loop = false;+    this._errored = true;++    const err = new ErrorCtor(+      prefix ? `Invalid WebSocket frame: ${message}` : message+    );++    Error.captureStackTrace(err, this.createError);+    err.code = errorCode;+    err[kStatusCode] = statusCode;+    return err;+  }+}++module.exports = Receiver;
+ node-client/node_modules/ws/lib/sender.js view
@@ -0,0 +1,602 @@+/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */++'use strict';++const { Duplex } = require('stream');+const { randomFillSync } = require('crypto');++const PerMessageDeflate = require('./permessage-deflate');+const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants');+const { isBlob, isValidStatusCode } = require('./validation');+const { mask: applyMask, toBuffer } = require('./buffer-util');++const kByteLength = Symbol('kByteLength');+const maskBuffer = Buffer.alloc(4);+const RANDOM_POOL_SIZE = 8 * 1024;+let randomPool;+let randomPoolPointer = RANDOM_POOL_SIZE;++const DEFAULT = 0;+const DEFLATING = 1;+const GET_BLOB_DATA = 2;++/**+ * HyBi Sender implementation.+ */+class Sender {+  /**+   * Creates a Sender instance.+   *+   * @param {Duplex} socket The connection socket+   * @param {Object} [extensions] An object containing the negotiated extensions+   * @param {Function} [generateMask] The function used to generate the masking+   *     key+   */+  constructor(socket, extensions, generateMask) {+    this._extensions = extensions || {};++    if (generateMask) {+      this._generateMask = generateMask;+      this._maskBuffer = Buffer.alloc(4);+    }++    this._socket = socket;++    this._firstFragment = true;+    this._compress = false;++    this._bufferedBytes = 0;+    this._queue = [];+    this._state = DEFAULT;+    this.onerror = NOOP;+    this[kWebSocket] = undefined;+  }++  /**+   * Frames a piece of data according to the HyBi WebSocket protocol.+   *+   * @param {(Buffer|String)} data The data to frame+   * @param {Object} options Options object+   * @param {Boolean} [options.fin=false] Specifies whether or not to set the+   *     FIN bit+   * @param {Function} [options.generateMask] The function used to generate the+   *     masking key+   * @param {Boolean} [options.mask=false] Specifies whether or not to mask+   *     `data`+   * @param {Buffer} [options.maskBuffer] The buffer used to store the masking+   *     key+   * @param {Number} options.opcode The opcode+   * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be+   *     modified+   * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the+   *     RSV1 bit+   * @return {(Buffer|String)[]} The framed data+   * @public+   */+  static frame(data, options) {+    let mask;+    let merge = false;+    let offset = 2;+    let skipMasking = false;++    if (options.mask) {+      mask = options.maskBuffer || maskBuffer;++      if (options.generateMask) {+        options.generateMask(mask);+      } else {+        if (randomPoolPointer === RANDOM_POOL_SIZE) {+          /* istanbul ignore else  */+          if (randomPool === undefined) {+            //+            // This is lazily initialized because server-sent frames must not+            // be masked so it may never be used.+            //+            randomPool = Buffer.alloc(RANDOM_POOL_SIZE);+          }++          randomFillSync(randomPool, 0, RANDOM_POOL_SIZE);+          randomPoolPointer = 0;+        }++        mask[0] = randomPool[randomPoolPointer++];+        mask[1] = randomPool[randomPoolPointer++];+        mask[2] = randomPool[randomPoolPointer++];+        mask[3] = randomPool[randomPoolPointer++];+      }++      skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;+      offset = 6;+    }++    let dataLength;++    if (typeof data === 'string') {+      if (+        (!options.mask || skipMasking) &&+        options[kByteLength] !== undefined+      ) {+        dataLength = options[kByteLength];+      } else {+        data = Buffer.from(data);+        dataLength = data.length;+      }+    } else {+      dataLength = data.length;+      merge = options.mask && options.readOnly && !skipMasking;+    }++    let payloadLength = dataLength;++    if (dataLength >= 65536) {+      offset += 8;+      payloadLength = 127;+    } else if (dataLength > 125) {+      offset += 2;+      payloadLength = 126;+    }++    const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);++    target[0] = options.fin ? options.opcode | 0x80 : options.opcode;+    if (options.rsv1) target[0] |= 0x40;++    target[1] = payloadLength;++    if (payloadLength === 126) {+      target.writeUInt16BE(dataLength, 2);+    } else if (payloadLength === 127) {+      target[2] = target[3] = 0;+      target.writeUIntBE(dataLength, 4, 6);+    }++    if (!options.mask) return [target, data];++    target[1] |= 0x80;+    target[offset - 4] = mask[0];+    target[offset - 3] = mask[1];+    target[offset - 2] = mask[2];+    target[offset - 1] = mask[3];++    if (skipMasking) return [target, data];++    if (merge) {+      applyMask(data, mask, target, offset, dataLength);+      return [target];+    }++    applyMask(data, mask, data, 0, dataLength);+    return [target, data];+  }++  /**+   * Sends a close message to the other peer.+   *+   * @param {Number} [code] The status code component of the body+   * @param {(String|Buffer)} [data] The message component of the body+   * @param {Boolean} [mask=false] Specifies whether or not to mask the message+   * @param {Function} [cb] Callback+   * @public+   */+  close(code, data, mask, cb) {+    let buf;++    if (code === undefined) {+      buf = EMPTY_BUFFER;+    } else if (typeof code !== 'number' || !isValidStatusCode(code)) {+      throw new TypeError('First argument must be a valid error code number');+    } else if (data === undefined || !data.length) {+      buf = Buffer.allocUnsafe(2);+      buf.writeUInt16BE(code, 0);+    } else {+      const length = Buffer.byteLength(data);++      if (length > 123) {+        throw new RangeError('The message must not be greater than 123 bytes');+      }++      buf = Buffer.allocUnsafe(2 + length);+      buf.writeUInt16BE(code, 0);++      if (typeof data === 'string') {+        buf.write(data, 2);+      } else {+        buf.set(data, 2);+      }+    }++    const options = {+      [kByteLength]: buf.length,+      fin: true,+      generateMask: this._generateMask,+      mask,+      maskBuffer: this._maskBuffer,+      opcode: 0x08,+      readOnly: false,+      rsv1: false+    };++    if (this._state !== DEFAULT) {+      this.enqueue([this.dispatch, buf, false, options, cb]);+    } else {+      this.sendFrame(Sender.frame(buf, options), cb);+    }+  }++  /**+   * Sends a ping message to the other peer.+   *+   * @param {*} data The message to send+   * @param {Boolean} [mask=false] Specifies whether or not to mask `data`+   * @param {Function} [cb] Callback+   * @public+   */+  ping(data, mask, cb) {+    let byteLength;+    let readOnly;++    if (typeof data === 'string') {+      byteLength = Buffer.byteLength(data);+      readOnly = false;+    } else if (isBlob(data)) {+      byteLength = data.size;+      readOnly = false;+    } else {+      data = toBuffer(data);+      byteLength = data.length;+      readOnly = toBuffer.readOnly;+    }++    if (byteLength > 125) {+      throw new RangeError('The data size must not be greater than 125 bytes');+    }++    const options = {+      [kByteLength]: byteLength,+      fin: true,+      generateMask: this._generateMask,+      mask,+      maskBuffer: this._maskBuffer,+      opcode: 0x09,+      readOnly,+      rsv1: false+    };++    if (isBlob(data)) {+      if (this._state !== DEFAULT) {+        this.enqueue([this.getBlobData, data, false, options, cb]);+      } else {+        this.getBlobData(data, false, options, cb);+      }+    } else if (this._state !== DEFAULT) {+      this.enqueue([this.dispatch, data, false, options, cb]);+    } else {+      this.sendFrame(Sender.frame(data, options), cb);+    }+  }++  /**+   * Sends a pong message to the other peer.+   *+   * @param {*} data The message to send+   * @param {Boolean} [mask=false] Specifies whether or not to mask `data`+   * @param {Function} [cb] Callback+   * @public+   */+  pong(data, mask, cb) {+    let byteLength;+    let readOnly;++    if (typeof data === 'string') {+      byteLength = Buffer.byteLength(data);+      readOnly = false;+    } else if (isBlob(data)) {+      byteLength = data.size;+      readOnly = false;+    } else {+      data = toBuffer(data);+      byteLength = data.length;+      readOnly = toBuffer.readOnly;+    }++    if (byteLength > 125) {+      throw new RangeError('The data size must not be greater than 125 bytes');+    }++    const options = {+      [kByteLength]: byteLength,+      fin: true,+      generateMask: this._generateMask,+      mask,+      maskBuffer: this._maskBuffer,+      opcode: 0x0a,+      readOnly,+      rsv1: false+    };++    if (isBlob(data)) {+      if (this._state !== DEFAULT) {+        this.enqueue([this.getBlobData, data, false, options, cb]);+      } else {+        this.getBlobData(data, false, options, cb);+      }+    } else if (this._state !== DEFAULT) {+      this.enqueue([this.dispatch, data, false, options, cb]);+    } else {+      this.sendFrame(Sender.frame(data, options), cb);+    }+  }++  /**+   * Sends a data message to the other peer.+   *+   * @param {*} data The message to send+   * @param {Object} options Options object+   * @param {Boolean} [options.binary=false] Specifies whether `data` is binary+   *     or text+   * @param {Boolean} [options.compress=false] Specifies whether or not to+   *     compress `data`+   * @param {Boolean} [options.fin=false] Specifies whether the fragment is the+   *     last one+   * @param {Boolean} [options.mask=false] Specifies whether or not to mask+   *     `data`+   * @param {Function} [cb] Callback+   * @public+   */+  send(data, options, cb) {+    const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];+    let opcode = options.binary ? 2 : 1;+    let rsv1 = options.compress;++    let byteLength;+    let readOnly;++    if (typeof data === 'string') {+      byteLength = Buffer.byteLength(data);+      readOnly = false;+    } else if (isBlob(data)) {+      byteLength = data.size;+      readOnly = false;+    } else {+      data = toBuffer(data);+      byteLength = data.length;+      readOnly = toBuffer.readOnly;+    }++    if (this._firstFragment) {+      this._firstFragment = false;+      if (+        rsv1 &&+        perMessageDeflate &&+        perMessageDeflate.params[+          perMessageDeflate._isServer+            ? 'server_no_context_takeover'+            : 'client_no_context_takeover'+        ]+      ) {+        rsv1 = byteLength >= perMessageDeflate._threshold;+      }+      this._compress = rsv1;+    } else {+      rsv1 = false;+      opcode = 0;+    }++    if (options.fin) this._firstFragment = true;++    const opts = {+      [kByteLength]: byteLength,+      fin: options.fin,+      generateMask: this._generateMask,+      mask: options.mask,+      maskBuffer: this._maskBuffer,+      opcode,+      readOnly,+      rsv1+    };++    if (isBlob(data)) {+      if (this._state !== DEFAULT) {+        this.enqueue([this.getBlobData, data, this._compress, opts, cb]);+      } else {+        this.getBlobData(data, this._compress, opts, cb);+      }+    } else if (this._state !== DEFAULT) {+      this.enqueue([this.dispatch, data, this._compress, opts, cb]);+    } else {+      this.dispatch(data, this._compress, opts, cb);+    }+  }++  /**+   * Gets the contents of a blob as binary data.+   *+   * @param {Blob} blob The blob+   * @param {Boolean} [compress=false] Specifies whether or not to compress+   *     the data+   * @param {Object} options Options object+   * @param {Boolean} [options.fin=false] Specifies whether or not to set the+   *     FIN bit+   * @param {Function} [options.generateMask] The function used to generate the+   *     masking key+   * @param {Boolean} [options.mask=false] Specifies whether or not to mask+   *     `data`+   * @param {Buffer} [options.maskBuffer] The buffer used to store the masking+   *     key+   * @param {Number} options.opcode The opcode+   * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be+   *     modified+   * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the+   *     RSV1 bit+   * @param {Function} [cb] Callback+   * @private+   */+  getBlobData(blob, compress, options, cb) {+    this._bufferedBytes += options[kByteLength];+    this._state = GET_BLOB_DATA;++    blob+      .arrayBuffer()+      .then((arrayBuffer) => {+        if (this._socket.destroyed) {+          const err = new Error(+            'The socket was closed while the blob was being read'+          );++          //+          // `callCallbacks` is called in the next tick to ensure that errors+          // that might be thrown in the callbacks behave like errors thrown+          // outside the promise chain.+          //+          process.nextTick(callCallbacks, this, err, cb);+          return;+        }++        this._bufferedBytes -= options[kByteLength];+        const data = toBuffer(arrayBuffer);++        if (!compress) {+          this._state = DEFAULT;+          this.sendFrame(Sender.frame(data, options), cb);+          this.dequeue();+        } else {+          this.dispatch(data, compress, options, cb);+        }+      })+      .catch((err) => {+        //+        // `onError` is called in the next tick for the same reason that+        // `callCallbacks` above is.+        //+        process.nextTick(onError, this, err, cb);+      });+  }++  /**+   * Dispatches a message.+   *+   * @param {(Buffer|String)} data The message to send+   * @param {Boolean} [compress=false] Specifies whether or not to compress+   *     `data`+   * @param {Object} options Options object+   * @param {Boolean} [options.fin=false] Specifies whether or not to set the+   *     FIN bit+   * @param {Function} [options.generateMask] The function used to generate the+   *     masking key+   * @param {Boolean} [options.mask=false] Specifies whether or not to mask+   *     `data`+   * @param {Buffer} [options.maskBuffer] The buffer used to store the masking+   *     key+   * @param {Number} options.opcode The opcode+   * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be+   *     modified+   * @param {Boolean} [options.rsv1=false] 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._bufferedBytes += options[kByteLength];+    this._state = DEFLATING;+    perMessageDeflate.compress(data, options.fin, (_, buf) => {+      if (this._socket.destroyed) {+        const err = new Error(+          'The socket was closed while data was being compressed'+        );++        callCallbacks(this, err, cb);+        return;+      }++      this._bufferedBytes -= options[kByteLength];+      this._state = DEFAULT;+      options.readOnly = false;+      this.sendFrame(Sender.frame(buf, options), cb);+      this.dequeue();+    });+  }++  /**+   * Executes queued send operations.+   *+   * @private+   */+  dequeue() {+    while (this._state === DEFAULT && this._queue.length) {+      const params = this._queue.shift();++      this._bufferedBytes -= params[3][kByteLength];+      Reflect.apply(params[0], this, params.slice(1));+    }+  }++  /**+   * Enqueues a send operation.+   *+   * @param {Array} params Send operation parameters.+   * @private+   */+  enqueue(params) {+    this._bufferedBytes += params[3][kByteLength];+    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.cork();+      this._socket.write(list[0]);+      this._socket.write(list[1], cb);+      this._socket.uncork();+    } else {+      this._socket.write(list[0], cb);+    }+  }+}++module.exports = Sender;++/**+ * Calls queued callbacks with an error.+ *+ * @param {Sender} sender The `Sender` instance+ * @param {Error} err The error to call the callbacks with+ * @param {Function} [cb] The first callback+ * @private+ */+function callCallbacks(sender, err, cb) {+  if (typeof cb === 'function') cb(err);++  for (let i = 0; i < sender._queue.length; i++) {+    const params = sender._queue[i];+    const callback = params[params.length - 1];++    if (typeof callback === 'function') callback(err);+  }+}++/**+ * Handles a `Sender` error.+ *+ * @param {Sender} sender The `Sender` instance+ * @param {Error} err The error+ * @param {Function} [cb] The first pending callback+ * @private+ */+function onError(sender, err, cb) {+  callCallbacks(sender, err, cb);+  sender.onerror(err);+}
+ node-client/node_modules/ws/lib/stream.js view
@@ -0,0 +1,159 @@+'use strict';++const { Duplex } = require('stream');++/**+ * Emits the `'close'` event on a stream.+ *+ * @param {Duplex} stream The stream.+ * @private+ */+function emitClose(stream) {+  stream.emit('close');+}++/**+ * The listener of the `'end'` event.+ *+ * @private+ */+function duplexOnEnd() {+  if (!this.destroyed && this._writableState.finished) {+    this.destroy();+  }+}++/**+ * The listener of the `'error'` event.+ *+ * @param {Error} err The error+ * @private+ */+function duplexOnError(err) {+  this.removeListener('error', duplexOnError);+  this.destroy();+  if (this.listenerCount('error') === 0) {+    // Do not suppress the throwing behavior.+    this.emit('error', err);+  }+}++/**+ * Wraps a `WebSocket` in a duplex stream.+ *+ * @param {WebSocket} ws The `WebSocket` to wrap+ * @param {Object} [options] The options for the `Duplex` constructor+ * @return {Duplex} The duplex stream+ * @public+ */+function createWebSocketStream(ws, options) {+  let terminateOnDestroy = true;++  const duplex = new Duplex({+    ...options,+    autoDestroy: false,+    emitClose: false,+    objectMode: false,+    writableObjectMode: false+  });++  ws.on('message', function message(msg, isBinary) {+    const data =+      !isBinary && duplex._readableState.objectMode ? msg.toString() : msg;++    if (!duplex.push(data)) ws.pause();+  });++  ws.once('error', function error(err) {+    if (duplex.destroyed) return;++    // Prevent `ws.terminate()` from being called by `duplex._destroy()`.+    //+    // - If the `'error'` event is emitted before the `'open'` event, then+    //   `ws.terminate()` is a noop as no socket is assigned.+    // - Otherwise, the error is re-emitted by the listener of the `'error'`+    //   event of the `Receiver` object. The listener already closes the+    //   connection by calling `ws.close()`. This allows a close frame to be+    //   sent to the other peer. If `ws.terminate()` is called right after this,+    //   then the close frame might not be sent.+    terminateOnDestroy = false;+    duplex.destroy(err);+  });++  ws.once('close', function close() {+    if (duplex.destroyed) return;++    duplex.push(null);+  });++  duplex._destroy = function (err, callback) {+    if (ws.readyState === ws.CLOSED) {+      callback(err);+      process.nextTick(emitClose, duplex);+      return;+    }++    let called = false;++    ws.once('error', function error(err) {+      called = true;+      callback(err);+    });++    ws.once('close', function close() {+      if (!called) callback(err);+      process.nextTick(emitClose, duplex);+    });++    if (terminateOnDestroy) ws.terminate();+  };++  duplex._final = function (callback) {+    if (ws.readyState === ws.CONNECTING) {+      ws.once('open', function open() {+        duplex._final(callback);+      });+      return;+    }++    // If the value of the `_socket` property is `null` it means that `ws` is a+    // client websocket and the handshake failed. In fact, when this happens, a+    // socket is never assigned to the websocket. Wait for the `'error'` event+    // that will be emitted by the websocket.+    if (ws._socket === null) return;++    if (ws._socket._writableState.finished) {+      callback();+      if (duplex._readableState.endEmitted) duplex.destroy();+    } else {+      ws._socket.once('finish', function finish() {+        // `duplex` is not destroyed here because the `'end'` event will be+        // emitted on `duplex` after this `'finish'` event. The EOF signaling+        // `null` chunk is, in fact, pushed when the websocket emits `'close'`.+        callback();+      });+      ws.close();+    }+  };++  duplex._read = function () {+    if (ws.isPaused) ws.resume();+  };++  duplex._write = function (chunk, encoding, callback) {+    if (ws.readyState === ws.CONNECTING) {+      ws.once('open', function open() {+        duplex._write(chunk, encoding, callback);+      });+      return;+    }++    ws.send(chunk, callback);+  };++  duplex.on('end', duplexOnEnd);+  duplex.on('error', duplexOnError);+  return duplex;+}++module.exports = createWebSocketStream;
+ node-client/node_modules/ws/lib/subprotocol.js view
@@ -0,0 +1,62 @@+'use strict';++const { tokenChars } = require('./validation');++/**+ * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names.+ *+ * @param {String} header The field value of the header+ * @return {Set} The subprotocol names+ * @public+ */+function parse(header) {+  const protocols = new Set();+  let start = -1;+  let end = -1;+  let i = 0;++  for (i; i < header.length; i++) {+    const code = header.charCodeAt(i);++    if (end === -1 && tokenChars[code] === 1) {+      if (start === -1) start = i;+    } else if (+      i !== 0 &&+      (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */+    ) {+      if (end === -1 && start !== -1) end = i;+    } else if (code === 0x2c /* ',' */) {+      if (start === -1) {+        throw new SyntaxError(`Unexpected character at index ${i}`);+      }++      if (end === -1) end = i;++      const protocol = header.slice(start, end);++      if (protocols.has(protocol)) {+        throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);+      }++      protocols.add(protocol);+      start = end = -1;+    } else {+      throw new SyntaxError(`Unexpected character at index ${i}`);+    }+  }++  if (start === -1 || end !== -1) {+    throw new SyntaxError('Unexpected end of input');+  }++  const protocol = header.slice(start, i);++  if (protocols.has(protocol)) {+    throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);+  }++  protocols.add(protocol);+  return protocols;+}++module.exports = { parse };
+ node-client/node_modules/ws/lib/validation.js view
@@ -0,0 +1,152 @@+'use strict';++const { isUtf8 } = require('buffer');++const { hasBlob } = require('./constants');++//+// Allowed token characters:+//+// '!', '#', '$', '%', '&', ''', '*', '+', '-',+// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'+//+// tokenChars[32] === 0 // ' '+// tokenChars[33] === 1 // '!'+// tokenChars[34] === 0 // '"'+// ...+//+// prettier-ignore+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+];++/**+ * 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+ */+function isValidStatusCode(code) {+  return (+    (code >= 1000 &&+      code <= 1014 &&+      code !== 1004 &&+      code !== 1005 &&+      code !== 1006) ||+    (code >= 3000 && code <= 4999)+  );+}++/**+ * Checks if a given buffer contains only correct UTF-8.+ * Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by+ * Markus Kuhn.+ *+ * @param {Buffer} buf The buffer to check+ * @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false`+ * @public+ */+function _isValidUTF8(buf) {+  const len = buf.length;+  let i = 0;++  while (i < len) {+    if ((buf[i] & 0x80) === 0) {+      // 0xxxxxxx+      i++;+    } else if ((buf[i] & 0xe0) === 0xc0) {+      // 110xxxxx 10xxxxxx+      if (+        i + 1 === len ||+        (buf[i + 1] & 0xc0) !== 0x80 ||+        (buf[i] & 0xfe) === 0xc0 // Overlong+      ) {+        return false;+      }++      i += 2;+    } else if ((buf[i] & 0xf0) === 0xe0) {+      // 1110xxxx 10xxxxxx 10xxxxxx+      if (+        i + 2 >= len ||+        (buf[i + 1] & 0xc0) !== 0x80 ||+        (buf[i + 2] & 0xc0) !== 0x80 ||+        (buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong+        (buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF)+      ) {+        return false;+      }++      i += 3;+    } else if ((buf[i] & 0xf8) === 0xf0) {+      // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx+      if (+        i + 3 >= len ||+        (buf[i + 1] & 0xc0) !== 0x80 ||+        (buf[i + 2] & 0xc0) !== 0x80 ||+        (buf[i + 3] & 0xc0) !== 0x80 ||+        (buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong+        (buf[i] === 0xf4 && buf[i + 1] > 0x8f) ||+        buf[i] > 0xf4 // > U+10FFFF+      ) {+        return false;+      }++      i += 4;+    } else {+      return false;+    }+  }++  return true;+}++/**+ * Determines whether a value is a `Blob`.+ *+ * @param {*} value The value to be tested+ * @return {Boolean} `true` if `value` is a `Blob`, else `false`+ * @private+ */+function isBlob(value) {+  return (+    hasBlob &&+    typeof value === 'object' &&+    typeof value.arrayBuffer === 'function' &&+    typeof value.type === 'string' &&+    typeof value.stream === 'function' &&+    (value[Symbol.toStringTag] === 'Blob' ||+      value[Symbol.toStringTag] === 'File')+  );+}++module.exports = {+  isBlob,+  isValidStatusCode,+  isValidUTF8: _isValidUTF8,+  tokenChars+};++if (isUtf8) {+  module.exports.isValidUTF8 = function (buf) {+    return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf);+  };+} /* istanbul ignore else  */ else if (!process.env.WS_NO_UTF_8_VALIDATE) {+  try {+    const isValidUTF8 = require('utf-8-validate');++    module.exports.isValidUTF8 = function (buf) {+      return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);+    };+  } catch (e) {+    // Continue regardless of the error.+  }+}
+ node-client/node_modules/ws/lib/websocket-server.js view
@@ -0,0 +1,540 @@+/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */++'use strict';++const EventEmitter = require('events');+const http = require('http');+const { Duplex } = require('stream');+const { createHash } = require('crypto');++const extension = require('./extension');+const PerMessageDeflate = require('./permessage-deflate');+const subprotocol = require('./subprotocol');+const WebSocket = require('./websocket');+const { GUID, kWebSocket } = require('./constants');++const keyRegex = /^[+/0-9A-Za-z]{22}==$/;++const RUNNING = 0;+const CLOSING = 1;+const CLOSED = 2;++/**+ * Class representing a WebSocket server.+ *+ * @extends EventEmitter+ */+class WebSocketServer extends EventEmitter {+  /**+   * Create a `WebSocketServer` instance.+   *+   * @param {Object} options Configuration options+   * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether+   *     any of the `'message'`, `'ping'`, and `'pong'` events can be emitted+   *     multiple times in the same tick+   * @param {Boolean} [options.autoPong=true] Specifies whether or not to+   *     automatically send a pong in response to a ping+   * @param {Number} [options.backlog=511] The maximum length of the queue of+   *     pending connections+   * @param {Boolean} [options.clientTracking=true] Specifies whether or not to+   *     track clients+   * @param {Function} [options.handleProtocols] A hook to handle protocols+   * @param {String} [options.host] The hostname where to bind the server+   * @param {Number} [options.maxPayload=104857600] The maximum allowed message+   *     size+   * @param {Boolean} [options.noServer=false] Enable no server mode+   * @param {String} [options.path] Accept only connections matching this path+   * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable+   *     permessage-deflate+   * @param {Number} [options.port] The port where to bind the server+   * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S+   *     server to use+   * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or+   *     not to skip UTF-8 validation for text and close messages+   * @param {Function} [options.verifyClient] A hook to reject connections+   * @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`+   *     class to use. It must be the `WebSocket` class or class that extends it+   * @param {Function} [callback] A listener for the `listening` event+   */+  constructor(options, callback) {+    super();++    options = {+      allowSynchronousEvents: true,+      autoPong: true,+      maxPayload: 100 * 1024 * 1024,+      skipUTF8Validation: false,+      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,+      WebSocket,+      ...options+    };++    if (+      (options.port == null && !options.server && !options.noServer) ||+      (options.port != null && (options.server || options.noServer)) ||+      (options.server && options.noServer)+    ) {+      throw new TypeError(+        'One and only 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) {+      const emitConnection = this.emit.bind(this, 'connection');++      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, emitConnection);+        }+      });+    }++    if (options.perMessageDeflate === true) options.perMessageDeflate = {};+    if (options.clientTracking) {+      this.clients = new Set();+      this._shouldEmitClose = false;+    }++    this.options = options;+    this._state = RUNNING;+  }++  /**+   * 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();+  }++  /**+   * Stop the server from accepting new connections and emit the `'close'` event+   * when all existing connections are closed.+   *+   * @param {Function} [cb] A one-time listener for the `'close'` event+   * @public+   */+  close(cb) {+    if (this._state === CLOSED) {+      if (cb) {+        this.once('close', () => {+          cb(new Error('The server is not running'));+        });+      }++      process.nextTick(emitClose, this);+      return;+    }++    if (cb) this.once('close', cb);++    if (this._state === CLOSING) return;+    this._state = CLOSING;++    if (this.options.noServer || this.options.server) {+      if (this._server) {+        this._removeListeners();+        this._removeListeners = this._server = null;+      }++      if (this.clients) {+        if (!this.clients.size) {+          process.nextTick(emitClose, this);+        } else {+          this._shouldEmitClose = true;+        }+      } else {+        process.nextTick(emitClose, this);+      }+    } else {+      const server = this._server;++      this._removeListeners();+      this._removeListeners = this._server = null;++      //+      // The HTTP/S server was created internally. Close it, and rely on its+      // `'close'` event.+      //+      server.close(() => {+        emitClose(this);+      });+    }+  }++  /**+   * 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) {+      const index = req.url.indexOf('?');+      const pathname = index !== -1 ? req.url.slice(0, index) : req.url;++      if (pathname !== this.options.path) return false;+    }++    return true;+  }++  /**+   * Handle a HTTP Upgrade request.+   *+   * @param {http.IncomingMessage} req The request object+   * @param {Duplex} 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 key = req.headers['sec-websocket-key'];+    const upgrade = req.headers.upgrade;+    const version = +req.headers['sec-websocket-version'];++    if (req.method !== 'GET') {+      const message = 'Invalid HTTP method';+      abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);+      return;+    }++    if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {+      const message = 'Invalid Upgrade header';+      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);+      return;+    }++    if (key === undefined || !keyRegex.test(key)) {+      const message = 'Missing or invalid Sec-WebSocket-Key header';+      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);+      return;+    }++    if (version !== 8 && version !== 13) {+      const message = 'Missing or invalid Sec-WebSocket-Version header';+      abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);+      return;+    }++    if (!this.shouldHandle(req)) {+      abortHandshake(socket, 400);+      return;+    }++    const secWebSocketProtocol = req.headers['sec-websocket-protocol'];+    let protocols = new Set();++    if (secWebSocketProtocol !== undefined) {+      try {+        protocols = subprotocol.parse(secWebSocketProtocol);+      } catch (err) {+        const message = 'Invalid Sec-WebSocket-Protocol header';+        abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);+        return;+      }+    }++    const secWebSocketExtensions = req.headers['sec-websocket-extensions'];+    const extensions = {};++    if (+      this.options.perMessageDeflate &&+      secWebSocketExtensions !== undefined+    ) {+      const perMessageDeflate = new PerMessageDeflate(+        this.options.perMessageDeflate,+        true,+        this.options.maxPayload+      );++      try {+        const offers = extension.parse(secWebSocketExtensions);++        if (offers[PerMessageDeflate.extensionName]) {+          perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);+          extensions[PerMessageDeflate.extensionName] = perMessageDeflate;+        }+      } catch (err) {+        const message =+          'Invalid or unacceptable Sec-WebSocket-Extensions header';+        abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);+        return;+      }+    }++    //+    // Optionally call external client verification handler.+    //+    if (this.options.verifyClient) {+      const info = {+        origin:+          req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],+        secure: !!(req.socket.authorized || req.socket.encrypted),+        req+      };++      if (this.options.verifyClient.length === 2) {+        this.options.verifyClient(info, (verified, code, message, headers) => {+          if (!verified) {+            return abortHandshake(socket, code || 401, message, headers);+          }++          this.completeUpgrade(+            extensions,+            key,+            protocols,+            req,+            socket,+            head,+            cb+          );+        });+        return;+      }++      if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);+    }++    this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);+  }++  /**+   * Upgrade the connection to WebSocket.+   *+   * @param {Object} extensions The accepted extensions+   * @param {String} key The value of the `Sec-WebSocket-Key` header+   * @param {Set} protocols The subprotocols+   * @param {http.IncomingMessage} req The request object+   * @param {Duplex} socket The network socket between the server and client+   * @param {Buffer} head The first packet of the upgraded stream+   * @param {Function} cb Callback+   * @throws {Error} If called more than once with the same socket+   * @private+   */+  completeUpgrade(extensions, key, protocols, req, socket, head, cb) {+    //+    // Destroy the socket if the client has already sent a FIN packet.+    //+    if (!socket.readable || !socket.writable) return socket.destroy();++    if (socket[kWebSocket]) {+      throw new Error(+        'server.handleUpgrade() was called more than once with the same ' ++          'socket, possibly due to a misconfiguration'+      );+    }++    if (this._state > RUNNING) return abortHandshake(socket, 503);++    const digest = createHash('sha1')+      .update(key + GUID)+      .digest('base64');++    const headers = [+      'HTTP/1.1 101 Switching Protocols',+      'Upgrade: websocket',+      'Connection: Upgrade',+      `Sec-WebSocket-Accept: ${digest}`+    ];++    const ws = new this.options.WebSocket(null, undefined, this.options);++    if (protocols.size) {+      //+      // Optionally call external protocol selection handler.+      //+      const protocol = this.options.handleProtocols+        ? this.options.handleProtocols(protocols, req)+        : protocols.values().next().value;++      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, {+      allowSynchronousEvents: this.options.allowSynchronousEvents,+      maxPayload: this.options.maxPayload,+      skipUTF8Validation: this.options.skipUTF8Validation+    });++    if (this.clients) {+      this.clients.add(ws);+      ws.on('close', () => {+        this.clients.delete(ws);++        if (this._shouldEmitClose && !this.clients.size) {+          process.nextTick(emitClose, this);+        }+      });+    }++    cb(ws, req);+  }+}++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]);+    }+  };+}++/**+ * Emit a `'close'` event on an `EventEmitter`.+ *+ * @param {EventEmitter} server The event emitter+ * @private+ */+function emitClose(server) {+  server._state = CLOSED;+  server.emit('close');+}++/**+ * Handle socket errors.+ *+ * @private+ */+function socketOnError() {+  this.destroy();+}++/**+ * Close the connection when preconditions are not fulfilled.+ *+ * @param {Duplex} socket The socket of the upgrade request+ * @param {Number} code The HTTP response status code+ * @param {String} [message] The HTTP response body+ * @param {Object} [headers] Additional HTTP response headers+ * @private+ */+function abortHandshake(socket, code, message, headers) {+  //+  // The socket is writable unless the user destroyed or ended it before calling+  // `server.handleUpgrade()` or in the `verifyClient` function, which is a user+  // error. Handling this does not make much sense as the worst that can happen+  // is that some of the data written by the user might be discarded due to the+  // call to `socket.end()` below, which triggers an `'error'` event that in+  // turn causes the socket to be destroyed.+  //+  message = message || http.STATUS_CODES[code];+  headers = {+    Connection: 'close',+    'Content-Type': 'text/html',+    'Content-Length': Buffer.byteLength(message),+    ...headers+  };++  socket.once('finish', socket.destroy);++  socket.end(+    `HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` ++      Object.keys(headers)+        .map((h) => `${h}: ${headers[h]}`)+        .join('\r\n') ++      '\r\n\r\n' ++      message+  );+}++/**+ * Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least+ * one listener for it, otherwise call `abortHandshake()`.+ *+ * @param {WebSocketServer} server The WebSocket server+ * @param {http.IncomingMessage} req The request object+ * @param {Duplex} socket The socket of the upgrade request+ * @param {Number} code The HTTP response status code+ * @param {String} message The HTTP response body+ * @private+ */+function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) {+  if (server.listenerCount('wsClientError')) {+    const err = new Error(message);+    Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);++    server.emit('wsClientError', err, socket, req);+  } else {+    abortHandshake(socket, code, message);+  }+}
+ node-client/node_modules/ws/lib/websocket.js view
@@ -0,0 +1,1388 @@+/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */++'use strict';++const EventEmitter = require('events');+const https = require('https');+const http = require('http');+const net = require('net');+const tls = require('tls');+const { randomBytes, createHash } = require('crypto');+const { Duplex, Readable } = require('stream');+const { URL } = require('url');++const PerMessageDeflate = require('./permessage-deflate');+const Receiver = require('./receiver');+const Sender = require('./sender');+const { isBlob } = require('./validation');++const {+  BINARY_TYPES,+  EMPTY_BUFFER,+  GUID,+  kForOnEventAttribute,+  kListener,+  kStatusCode,+  kWebSocket,+  NOOP+} = require('./constants');+const {+  EventTarget: { addEventListener, removeEventListener }+} = require('./event-target');+const { format, parse } = require('./extension');+const { toBuffer } = require('./buffer-util');++const closeTimeout = 30 * 1000;+const kAborted = Symbol('kAborted');+const protocolVersions = [8, 13];+const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];+const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;++/**+ * Class representing a WebSocket.+ *+ * @extends EventEmitter+ */+class WebSocket extends EventEmitter {+  /**+   * Create a new `WebSocket`.+   *+   * @param {(String|URL)} address The URL to which to connect+   * @param {(String|String[])} [protocols] The subprotocols+   * @param {Object} [options] Connection options+   */+  constructor(address, protocols, options) {+    super();++    this._binaryType = BINARY_TYPES[0];+    this._closeCode = 1006;+    this._closeFrameReceived = false;+    this._closeFrameSent = false;+    this._closeMessage = EMPTY_BUFFER;+    this._closeTimer = null;+    this._errorEmitted = false;+    this._extensions = {};+    this._paused = false;+    this._protocol = '';+    this._readyState = WebSocket.CONNECTING;+    this._receiver = null;+    this._sender = null;+    this._socket = null;++    if (address !== null) {+      this._bufferedAmount = 0;+      this._isServer = false;+      this._redirects = 0;++      if (protocols === undefined) {+        protocols = [];+      } else if (!Array.isArray(protocols)) {+        if (typeof protocols === 'object' && protocols !== null) {+          options = protocols;+          protocols = [];+        } else {+          protocols = [protocols];+        }+      }++      initAsClient(this, address, protocols, options);+    } else {+      this._autoPong = options.autoPong;+      this._isServer = true;+    }+  }++  /**+   * For historical reasons, the custom "nodebuffer" type is used by the default+   * instead of "blob".+   *+   * @type {String}+   */+  get binaryType() {+    return this._binaryType;+  }++  set binaryType(type) {+    if (!BINARY_TYPES.includes(type)) 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 this._bufferedAmount;++    return this._socket._writableState.length + this._sender._bufferedBytes;+  }++  /**+   * @type {String}+   */+  get extensions() {+    return Object.keys(this._extensions).join();+  }++  /**+   * @type {Boolean}+   */+  get isPaused() {+    return this._paused;+  }++  /**+   * @type {Function}+   */+  /* istanbul ignore next */+  get onclose() {+    return null;+  }++  /**+   * @type {Function}+   */+  /* istanbul ignore next */+  get onerror() {+    return null;+  }++  /**+   * @type {Function}+   */+  /* istanbul ignore next */+  get onopen() {+    return null;+  }++  /**+   * @type {Function}+   */+  /* istanbul ignore next */+  get onmessage() {+    return null;+  }++  /**+   * @type {String}+   */+  get protocol() {+    return this._protocol;+  }++  /**+   * @type {Number}+   */+  get readyState() {+    return this._readyState;+  }++  /**+   * @type {String}+   */+  get url() {+    return this._url;+  }++  /**+   * Set up the socket and the internal resources.+   *+   * @param {Duplex} socket The network socket between the server and client+   * @param {Buffer} head The first packet of the upgraded stream+   * @param {Object} options Options object+   * @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether+   *     any of the `'message'`, `'ping'`, and `'pong'` events can be emitted+   *     multiple times in the same tick+   * @param {Function} [options.generateMask] The function used to generate the+   *     masking key+   * @param {Number} [options.maxPayload=0] The maximum allowed message size+   * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or+   *     not to skip UTF-8 validation for text and close messages+   * @private+   */+  setSocket(socket, head, options) {+    const receiver = new Receiver({+      allowSynchronousEvents: options.allowSynchronousEvents,+      binaryType: this.binaryType,+      extensions: this._extensions,+      isServer: this._isServer,+      maxPayload: options.maxPayload,+      skipUTF8Validation: options.skipUTF8Validation+    });++    const sender = new Sender(socket, this._extensions, options.generateMask);++    this._receiver = receiver;+    this._sender = sender;+    this._socket = socket;++    receiver[kWebSocket] = this;+    sender[kWebSocket] = this;+    socket[kWebSocket] = this;++    receiver.on('conclude', receiverOnConclude);+    receiver.on('drain', receiverOnDrain);+    receiver.on('error', receiverOnError);+    receiver.on('message', receiverOnMessage);+    receiver.on('ping', receiverOnPing);+    receiver.on('pong', receiverOnPong);++    sender.onerror = senderOnError;++    //+    // These methods may not be available if `socket` is just a `Duplex`.+    //+    if (socket.setTimeout) socket.setTimeout(0);+    if (socket.setNoDelay) socket.setNoDelay();++    if (head.length > 0) socket.unshift(head);++    socket.on('close', socketOnClose);+    socket.on('data', socketOnData);+    socket.on('end', socketOnEnd);+    socket.on('error', socketOnError);++    this._readyState = WebSocket.OPEN;+    this.emit('open');+  }++  /**+   * Emit the `'close'` event.+   *+   * @private+   */+  emitClose() {+    if (!this._socket) {+      this._readyState = WebSocket.CLOSED;+      this.emit('close', this._closeCode, this._closeMessage);+      return;+    }++    if (this._extensions[PerMessageDeflate.extensionName]) {+      this._extensions[PerMessageDeflate.extensionName].cleanup();+    }++    this._receiver.removeAllListeners();+    this._readyState = WebSocket.CLOSED;+    this.emit('close', this._closeCode, this._closeMessage);+  }++  /**+   * Start a closing handshake.+   *+   *          +----------+   +-----------+   +----------++   *     - - -|ws.close()|-->|close frame|-->|ws.close()|- - -+   *    |     +----------+   +-----------+   +----------+     |+   *          +----------+   +-----------+         |+   * CLOSING  |ws.close()|<--|close frame|<--+-----+       CLOSING+   *          +----------+   +-----------+   |+   *    |           |                        |   +---+        |+   *                +------------------------+-->|fin| - - - -+   *    |         +---+                      |   +---++   *     - - - - -|fin|<---------------------++   *              +---++   *+   * @param {Number} [code] Status code explaining why the connection is closing+   * @param {(String|Buffer)} [data] The reason why the connection is+   *     closing+   * @public+   */+  close(code, data) {+    if (this.readyState === WebSocket.CLOSED) return;+    if (this.readyState === WebSocket.CONNECTING) {+      const msg = 'WebSocket was closed before the connection was established';+      abortHandshake(this, this._req, msg);+      return;+    }++    if (this.readyState === WebSocket.CLOSING) {+      if (+        this._closeFrameSent &&+        (this._closeFrameReceived || this._receiver._writableState.errorEmitted)+      ) {+        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._closeFrameReceived ||+        this._receiver._writableState.errorEmitted+      ) {+        this._socket.end();+      }+    });++    setCloseTimer(this);+  }++  /**+   * Pause the socket.+   *+   * @public+   */+  pause() {+    if (+      this.readyState === WebSocket.CONNECTING ||+      this.readyState === WebSocket.CLOSED+    ) {+      return;+    }++    this._paused = true;+    this._socket.pause();+  }++  /**+   * 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 (this.readyState === WebSocket.CONNECTING) {+      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');+    }++    if (typeof data === 'function') {+      cb = data;+      data = mask = undefined;+    } else if (typeof mask === 'function') {+      cb = mask;+      mask = undefined;+    }++    if (typeof data === 'number') data = data.toString();++    if (this.readyState !== WebSocket.OPEN) {+      sendAfterClose(this, data, cb);+      return;+    }++    if (mask === undefined) mask = !this._isServer;+    this._sender.ping(data || 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 (this.readyState === WebSocket.CONNECTING) {+      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');+    }++    if (typeof data === 'function') {+      cb = data;+      data = mask = undefined;+    } else if (typeof mask === 'function') {+      cb = mask;+      mask = undefined;+    }++    if (typeof data === 'number') data = data.toString();++    if (this.readyState !== WebSocket.OPEN) {+      sendAfterClose(this, data, cb);+      return;+    }++    if (mask === undefined) mask = !this._isServer;+    this._sender.pong(data || EMPTY_BUFFER, mask, cb);+  }++  /**+   * Resume the socket.+   *+   * @public+   */+  resume() {+    if (+      this.readyState === WebSocket.CONNECTING ||+      this.readyState === WebSocket.CLOSED+    ) {+      return;+    }++    this._paused = false;+    if (!this._receiver._writableState.needDrain) this._socket.resume();+  }++  /**+   * Send a data message.+   *+   * @param {*} data The message to send+   * @param {Object} [options] Options object+   * @param {Boolean} [options.binary] Specifies whether `data` is binary or+   *     text+   * @param {Boolean} [options.compress] Specifies whether or not to compress+   *     `data`+   * @param {Boolean} [options.fin=true] 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 (this.readyState === WebSocket.CONNECTING) {+      throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');+    }++    if (typeof options === 'function') {+      cb = options;+      options = {};+    }++    if (typeof data === 'number') data = data.toString();++    if (this.readyState !== WebSocket.OPEN) {+      sendAfterClose(this, data, cb);+      return;+    }++    const opts = {+      binary: typeof data !== 'string',+      mask: !this._isServer,+      compress: true,+      fin: true,+      ...options+    };++    if (!this._extensions[PerMessageDeflate.extensionName]) {+      opts.compress = false;+    }++    this._sender.send(data || EMPTY_BUFFER, opts, cb);+  }++  /**+   * Forcibly close the connection.+   *+   * @public+   */+  terminate() {+    if (this.readyState === WebSocket.CLOSED) return;+    if (this.readyState === WebSocket.CONNECTING) {+      const msg = 'WebSocket was closed before the connection was established';+      abortHandshake(this, this._req, msg);+      return;+    }++    if (this._socket) {+      this._readyState = WebSocket.CLOSING;+      this._socket.destroy();+    }+  }+}++/**+ * @constant {Number} CONNECTING+ * @memberof WebSocket+ */+Object.defineProperty(WebSocket, 'CONNECTING', {+  enumerable: true,+  value: readyStates.indexOf('CONNECTING')+});++/**+ * @constant {Number} CONNECTING+ * @memberof WebSocket.prototype+ */+Object.defineProperty(WebSocket.prototype, 'CONNECTING', {+  enumerable: true,+  value: readyStates.indexOf('CONNECTING')+});++/**+ * @constant {Number} OPEN+ * @memberof WebSocket+ */+Object.defineProperty(WebSocket, 'OPEN', {+  enumerable: true,+  value: readyStates.indexOf('OPEN')+});++/**+ * @constant {Number} OPEN+ * @memberof WebSocket.prototype+ */+Object.defineProperty(WebSocket.prototype, 'OPEN', {+  enumerable: true,+  value: readyStates.indexOf('OPEN')+});++/**+ * @constant {Number} CLOSING+ * @memberof WebSocket+ */+Object.defineProperty(WebSocket, 'CLOSING', {+  enumerable: true,+  value: readyStates.indexOf('CLOSING')+});++/**+ * @constant {Number} CLOSING+ * @memberof WebSocket.prototype+ */+Object.defineProperty(WebSocket.prototype, 'CLOSING', {+  enumerable: true,+  value: readyStates.indexOf('CLOSING')+});++/**+ * @constant {Number} CLOSED+ * @memberof WebSocket+ */+Object.defineProperty(WebSocket, 'CLOSED', {+  enumerable: true,+  value: readyStates.indexOf('CLOSED')+});++/**+ * @constant {Number} CLOSED+ * @memberof WebSocket.prototype+ */+Object.defineProperty(WebSocket.prototype, 'CLOSED', {+  enumerable: true,+  value: readyStates.indexOf('CLOSED')+});++[+  'binaryType',+  'bufferedAmount',+  'extensions',+  'isPaused',+  'protocol',+  'readyState',+  'url'+].forEach((property) => {+  Object.defineProperty(WebSocket.prototype, property, { enumerable: true });+});++//+// 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}`, {+    enumerable: true,+    get() {+      for (const listener of this.listeners(method)) {+        if (listener[kForOnEventAttribute]) return listener[kListener];+      }++      return null;+    },+    set(handler) {+      for (const listener of this.listeners(method)) {+        if (listener[kForOnEventAttribute]) {+          this.removeListener(method, listener);+          break;+        }+      }++      if (typeof handler !== 'function') return;++      this.addEventListener(method, handler, {+        [kForOnEventAttribute]: true+      });+    }+  });+});++WebSocket.prototype.addEventListener = addEventListener;+WebSocket.prototype.removeEventListener = removeEventListener;++module.exports = WebSocket;++/**+ * Initialize a WebSocket client.+ *+ * @param {WebSocket} websocket The client to initialize+ * @param {(String|URL)} address The URL to which to connect+ * @param {Array} protocols The subprotocols+ * @param {Object} [options] Connection options+ * @param {Boolean} [options.allowSynchronousEvents=true] Specifies whether any+ *     of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple+ *     times in the same tick+ * @param {Boolean} [options.autoPong=true] Specifies whether or not to+ *     automatically send a pong in response to a ping+ * @param {Function} [options.finishRequest] A function which can be used to+ *     customize the headers of each http request before it is sent+ * @param {Boolean} [options.followRedirects=false] Whether or not to follow+ *     redirects+ * @param {Function} [options.generateMask] The function used to generate the+ *     masking key+ * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the+ *     handshake request+ * @param {Number} [options.maxPayload=104857600] The maximum allowed message+ *     size+ * @param {Number} [options.maxRedirects=10] The maximum number of redirects+ *     allowed+ * @param {String} [options.origin] Value of the `Origin` or+ *     `Sec-WebSocket-Origin` header+ * @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable+ *     permessage-deflate+ * @param {Number} [options.protocolVersion=13] Value of the+ *     `Sec-WebSocket-Version` header+ * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or+ *     not to skip UTF-8 validation for text and close messages+ * @private+ */+function initAsClient(websocket, address, protocols, options) {+  const opts = {+    allowSynchronousEvents: true,+    autoPong: true,+    protocolVersion: protocolVersions[1],+    maxPayload: 100 * 1024 * 1024,+    skipUTF8Validation: false,+    perMessageDeflate: true,+    followRedirects: false,+    maxRedirects: 10,+    ...options,+    socketPath: undefined,+    hostname: undefined,+    protocol: undefined,+    timeout: undefined,+    method: 'GET',+    host: undefined,+    path: undefined,+    port: undefined+  };++  websocket._autoPong = opts.autoPong;++  if (!protocolVersions.includes(opts.protocolVersion)) {+    throw new RangeError(+      `Unsupported protocol version: ${opts.protocolVersion} ` ++        `(supported versions: ${protocolVersions.join(', ')})`+    );+  }++  let parsedUrl;++  if (address instanceof URL) {+    parsedUrl = address;+  } else {+    try {+      parsedUrl = new URL(address);+    } catch (e) {+      throw new SyntaxError(`Invalid URL: ${address}`);+    }+  }++  if (parsedUrl.protocol === 'http:') {+    parsedUrl.protocol = 'ws:';+  } else if (parsedUrl.protocol === 'https:') {+    parsedUrl.protocol = 'wss:';+  }++  websocket._url = parsedUrl.href;++  const isSecure = parsedUrl.protocol === 'wss:';+  const isIpcUrl = parsedUrl.protocol === 'ws+unix:';+  let invalidUrlMessage;++  if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {+    invalidUrlMessage =+      'The URL\'s protocol must be one of "ws:", "wss:", ' ++      '"http:", "https", or "ws+unix:"';+  } else if (isIpcUrl && !parsedUrl.pathname) {+    invalidUrlMessage = "The URL's pathname is empty";+  } else if (parsedUrl.hash) {+    invalidUrlMessage = 'The URL contains a fragment identifier';+  }++  if (invalidUrlMessage) {+    const err = new SyntaxError(invalidUrlMessage);++    if (websocket._redirects === 0) {+      throw err;+    } else {+      emitErrorAndClose(websocket, err);+      return;+    }+  }++  const defaultPort = isSecure ? 443 : 80;+  const key = randomBytes(16).toString('base64');+  const request = isSecure ? https.request : http.request;+  const protocolSet = new Set();+  let perMessageDeflate;++  opts.createConnection =+    opts.createConnection || (isSecure ? tlsConnect : netConnect);+  opts.defaultPort = opts.defaultPort || defaultPort;+  opts.port = parsedUrl.port || defaultPort;+  opts.host = parsedUrl.hostname.startsWith('[')+    ? parsedUrl.hostname.slice(1, -1)+    : parsedUrl.hostname;+  opts.headers = {+    ...opts.headers,+    'Sec-WebSocket-Version': opts.protocolVersion,+    'Sec-WebSocket-Key': key,+    Connection: 'Upgrade',+    Upgrade: 'websocket'+  };+  opts.path = parsedUrl.pathname + parsedUrl.search;+  opts.timeout = opts.handshakeTimeout;++  if (opts.perMessageDeflate) {+    perMessageDeflate = new PerMessageDeflate(+      opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},+      false,+      opts.maxPayload+    );+    opts.headers['Sec-WebSocket-Extensions'] = format({+      [PerMessageDeflate.extensionName]: perMessageDeflate.offer()+    });+  }+  if (protocols.length) {+    for (const protocol of protocols) {+      if (+        typeof protocol !== 'string' ||+        !subprotocolRegex.test(protocol) ||+        protocolSet.has(protocol)+      ) {+        throw new SyntaxError(+          'An invalid or duplicated subprotocol was specified'+        );+      }++      protocolSet.add(protocol);+    }++    opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');+  }+  if (opts.origin) {+    if (opts.protocolVersion < 13) {+      opts.headers['Sec-WebSocket-Origin'] = opts.origin;+    } else {+      opts.headers.Origin = opts.origin;+    }+  }+  if (parsedUrl.username || parsedUrl.password) {+    opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;+  }++  if (isIpcUrl) {+    const parts = opts.path.split(':');++    opts.socketPath = parts[0];+    opts.path = parts[1];+  }++  let req;++  if (opts.followRedirects) {+    if (websocket._redirects === 0) {+      websocket._originalIpc = isIpcUrl;+      websocket._originalSecure = isSecure;+      websocket._originalHostOrSocketPath = isIpcUrl+        ? opts.socketPath+        : parsedUrl.host;++      const headers = options && options.headers;++      //+      // Shallow copy the user provided options so that headers can be changed+      // without mutating the original object.+      //+      options = { ...options, headers: {} };++      if (headers) {+        for (const [key, value] of Object.entries(headers)) {+          options.headers[key.toLowerCase()] = value;+        }+      }+    } else if (websocket.listenerCount('redirect') === 0) {+      const isSameHost = isIpcUrl+        ? websocket._originalIpc+          ? opts.socketPath === websocket._originalHostOrSocketPath+          : false+        : websocket._originalIpc+          ? false+          : parsedUrl.host === websocket._originalHostOrSocketPath;++      if (!isSameHost || (websocket._originalSecure && !isSecure)) {+        //+        // Match curl 7.77.0 behavior and drop the following headers. These+        // headers are also dropped when following a redirect to a subdomain.+        //+        delete opts.headers.authorization;+        delete opts.headers.cookie;++        if (!isSameHost) delete opts.headers.host;++        opts.auth = undefined;+      }+    }++    //+    // Match curl 7.77.0 behavior and make the first `Authorization` header win.+    // If the `Authorization` header is set, then there is nothing to do as it+    // will take precedence.+    //+    if (opts.auth && !options.headers.authorization) {+      options.headers.authorization =+        'Basic ' + Buffer.from(opts.auth).toString('base64');+    }++    req = websocket._req = request(opts);++    if (websocket._redirects) {+      //+      // Unlike what is done for the `'upgrade'` event, no early exit is+      // triggered here if the user calls `websocket.close()` or+      // `websocket.terminate()` from a listener of the `'redirect'` event. This+      // is because the user can also call `request.destroy()` with an error+      // before calling `websocket.close()` or `websocket.terminate()` and this+      // would result in an error being emitted on the `request` object with no+      // `'error'` event listeners attached.+      //+      websocket.emit('redirect', websocket.url, req);+    }+  } else {+    req = websocket._req = request(opts);+  }++  if (opts.timeout) {+    req.on('timeout', () => {+      abortHandshake(websocket, req, 'Opening handshake has timed out');+    });+  }++  req.on('error', (err) => {+    if (req === null || req[kAborted]) return;++    req = websocket._req = null;+    emitErrorAndClose(websocket, err);+  });++  req.on('response', (res) => {+    const location = res.headers.location;+    const statusCode = res.statusCode;++    if (+      location &&+      opts.followRedirects &&+      statusCode >= 300 &&+      statusCode < 400+    ) {+      if (++websocket._redirects > opts.maxRedirects) {+        abortHandshake(websocket, req, 'Maximum redirects exceeded');+        return;+      }++      req.abort();++      let addr;++      try {+        addr = new URL(location, address);+      } catch (e) {+        const err = new SyntaxError(`Invalid URL: ${location}`);+        emitErrorAndClose(websocket, err);+        return;+      }++      initAsClient(websocket, addr, protocols, options);+    } else if (!websocket.emit('unexpected-response', req, res)) {+      abortHandshake(+        websocket,+        req,+        `Unexpected server response: ${res.statusCode}`+      );+    }+  });++  req.on('upgrade', (res, socket, head) => {+    websocket.emit('upgrade', res);++    //+    // The user may have closed the connection from a listener of the+    // `'upgrade'` event.+    //+    if (websocket.readyState !== WebSocket.CONNECTING) return;++    req = websocket._req = null;++    const upgrade = res.headers.upgrade;++    if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {+      abortHandshake(websocket, socket, 'Invalid Upgrade header');+      return;+    }++    const digest = createHash('sha1')+      .update(key + GUID)+      .digest('base64');++    if (res.headers['sec-websocket-accept'] !== digest) {+      abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');+      return;+    }++    const serverProt = res.headers['sec-websocket-protocol'];+    let protError;++    if (serverProt !== undefined) {+      if (!protocolSet.size) {+        protError = 'Server sent a subprotocol but none was requested';+      } else if (!protocolSet.has(serverProt)) {+        protError = 'Server sent an invalid subprotocol';+      }+    } else if (protocolSet.size) {+      protError = 'Server sent no subprotocol';+    }++    if (protError) {+      abortHandshake(websocket, socket, protError);+      return;+    }++    if (serverProt) websocket._protocol = serverProt;++    const secWebSocketExtensions = res.headers['sec-websocket-extensions'];++    if (secWebSocketExtensions !== undefined) {+      if (!perMessageDeflate) {+        const message =+          'Server sent a Sec-WebSocket-Extensions header but no extension ' ++          'was requested';+        abortHandshake(websocket, socket, message);+        return;+      }++      let extensions;++      try {+        extensions = parse(secWebSocketExtensions);+      } catch (err) {+        const message = 'Invalid Sec-WebSocket-Extensions header';+        abortHandshake(websocket, socket, message);+        return;+      }++      const extensionNames = Object.keys(extensions);++      if (+        extensionNames.length !== 1 ||+        extensionNames[0] !== PerMessageDeflate.extensionName+      ) {+        const message = 'Server indicated an extension that was not requested';+        abortHandshake(websocket, socket, message);+        return;+      }++      try {+        perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);+      } catch (err) {+        const message = 'Invalid Sec-WebSocket-Extensions header';+        abortHandshake(websocket, socket, message);+        return;+      }++      websocket._extensions[PerMessageDeflate.extensionName] =+        perMessageDeflate;+    }++    websocket.setSocket(socket, head, {+      allowSynchronousEvents: opts.allowSynchronousEvents,+      generateMask: opts.generateMask,+      maxPayload: opts.maxPayload,+      skipUTF8Validation: opts.skipUTF8Validation+    });+  });++  if (opts.finishRequest) {+    opts.finishRequest(req, websocket);+  } else {+    req.end();+  }+}++/**+ * Emit the `'error'` and `'close'` events.+ *+ * @param {WebSocket} websocket The WebSocket instance+ * @param {Error} The error to emit+ * @private+ */+function emitErrorAndClose(websocket, err) {+  websocket._readyState = WebSocket.CLOSING;+  //+  // The following assignment is practically useless and is done only for+  // consistency.+  //+  websocket._errorEmitted = true;+  websocket.emit('error', err);+  websocket.emitClose();+}++/**+ * Create a `net.Socket` and initiate a connection.+ *+ * @param {Object} options Connection options+ * @return {net.Socket} The newly created socket used to start the connection+ * @private+ */+function netConnect(options) {+  options.path = options.socketPath;+  return net.connect(options);+}++/**+ * Create a `tls.TLSSocket` and initiate a connection.+ *+ * @param {Object} options Connection options+ * @return {tls.TLSSocket} The newly created socket used to start the connection+ * @private+ */+function tlsConnect(options) {+  options.path = undefined;++  if (!options.servername && options.servername !== '') {+    options.servername = net.isIP(options.host) ? '' : options.host;+  }++  return tls.connect(options);+}++/**+ * Abort the handshake and emit an error.+ *+ * @param {WebSocket} websocket The WebSocket instance+ * @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to+ *     abort or the socket to destroy+ * @param {String} message The error message+ * @private+ */+function abortHandshake(websocket, stream, message) {+  websocket._readyState = WebSocket.CLOSING;++  const err = new Error(message);+  Error.captureStackTrace(err, abortHandshake);++  if (stream.setHeader) {+    stream[kAborted] = true;+    stream.abort();++    if (stream.socket && !stream.socket.destroyed) {+      //+      // On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if+      // called after the request completed. See+      // https://github.com/websockets/ws/issues/1869.+      //+      stream.socket.destroy();+    }++    process.nextTick(emitErrorAndClose, websocket, err);+  } else {+    stream.destroy(err);+    stream.once('error', websocket.emit.bind(websocket, 'error'));+    stream.once('close', websocket.emitClose.bind(websocket));+  }+}++/**+ * Handle cases where the `ping()`, `pong()`, or `send()` methods are called+ * when the `readyState` attribute is `CLOSING` or `CLOSED`.+ *+ * @param {WebSocket} websocket The WebSocket instance+ * @param {*} [data] The data to send+ * @param {Function} [cb] Callback+ * @private+ */+function sendAfterClose(websocket, data, cb) {+  if (data) {+    const length = isBlob(data) ? data.size : toBuffer(data).length;++    //+    // The `_bufferedAmount` property is used only when the peer is a client and+    // the opening handshake fails. Under these circumstances, in fact, the+    // `setSocket()` method is not called, so the `_socket` and `_sender`+    // properties are set to `null`.+    //+    if (websocket._socket) websocket._sender._bufferedBytes += length;+    else websocket._bufferedAmount += length;+  }++  if (cb) {+    const err = new Error(+      `WebSocket is not open: readyState ${websocket.readyState} ` ++        `(${readyStates[websocket.readyState]})`+    );+    process.nextTick(cb, err);+  }+}++/**+ * The listener of the `Receiver` `'conclude'` event.+ *+ * @param {Number} code The status code+ * @param {Buffer} reason The reason for closing+ * @private+ */+function receiverOnConclude(code, reason) {+  const websocket = this[kWebSocket];++  websocket._closeFrameReceived = true;+  websocket._closeMessage = reason;+  websocket._closeCode = code;++  if (websocket._socket[kWebSocket] === undefined) return;++  websocket._socket.removeListener('data', socketOnData);+  process.nextTick(resume, websocket._socket);++  if (code === 1005) websocket.close();+  else websocket.close(code, reason);+}++/**+ * The listener of the `Receiver` `'drain'` event.+ *+ * @private+ */+function receiverOnDrain() {+  const websocket = this[kWebSocket];++  if (!websocket.isPaused) websocket._socket.resume();+}++/**+ * The listener of the `Receiver` `'error'` event.+ *+ * @param {(RangeError|Error)} err The emitted error+ * @private+ */+function receiverOnError(err) {+  const websocket = this[kWebSocket];++  if (websocket._socket[kWebSocket] !== undefined) {+    websocket._socket.removeListener('data', socketOnData);++    //+    // On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See+    // https://github.com/websockets/ws/issues/1940.+    //+    process.nextTick(resume, websocket._socket);++    websocket.close(err[kStatusCode]);+  }++  if (!websocket._errorEmitted) {+    websocket._errorEmitted = true;+    websocket.emit('error', err);+  }+}++/**+ * The listener of the `Receiver` `'finish'` event.+ *+ * @private+ */+function receiverOnFinish() {+  this[kWebSocket].emitClose();+}++/**+ * The listener of the `Receiver` `'message'` event.+ *+ * @param {Buffer|ArrayBuffer|Buffer[])} data The message+ * @param {Boolean} isBinary Specifies whether the message is binary or not+ * @private+ */+function receiverOnMessage(data, isBinary) {+  this[kWebSocket].emit('message', data, isBinary);+}++/**+ * The listener of the `Receiver` `'ping'` event.+ *+ * @param {Buffer} data The data included in the ping frame+ * @private+ */+function receiverOnPing(data) {+  const websocket = this[kWebSocket];++  if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);+  websocket.emit('ping', data);+}++/**+ * The listener of the `Receiver` `'pong'` event.+ *+ * @param {Buffer} data The data included in the pong frame+ * @private+ */+function receiverOnPong(data) {+  this[kWebSocket].emit('pong', data);+}++/**+ * Resume a readable stream+ *+ * @param {Readable} stream The readable stream+ * @private+ */+function resume(stream) {+  stream.resume();+}++/**+ * The `Sender` error event handler.+ *+ * @param {Error} The error+ * @private+ */+function senderOnError(err) {+  const websocket = this[kWebSocket];++  if (websocket.readyState === WebSocket.CLOSED) return;+  if (websocket.readyState === WebSocket.OPEN) {+    websocket._readyState = WebSocket.CLOSING;+    setCloseTimer(websocket);+  }++  //+  // `socket.end()` is used instead of `socket.destroy()` to allow the other+  // peer to finish sending queued data. There is no need to set a timer here+  // because `CLOSING` means that it is already set or not needed.+  //+  this._socket.end();++  if (!websocket._errorEmitted) {+    websocket._errorEmitted = true;+    websocket.emit('error', err);+  }+}++/**+ * Set a timer to destroy the underlying raw socket of a WebSocket.+ *+ * @param {WebSocket} websocket The WebSocket instance+ * @private+ */+function setCloseTimer(websocket) {+  websocket._closeTimer = setTimeout(+    websocket._socket.destroy.bind(websocket._socket),+    closeTimeout+  );+}++/**+ * The listener of the socket `'close'` event.+ *+ * @private+ */+function socketOnClose() {+  const websocket = this[kWebSocket];++  this.removeListener('close', socketOnClose);+  this.removeListener('data', socketOnData);+  this.removeListener('end', socketOnEnd);++  websocket._readyState = WebSocket.CLOSING;++  let chunk;++  //+  // The close frame might not have been received or the `'end'` event emitted,+  // for example, if the socket was destroyed due to an error. Ensure that the+  // `receiver` stream is closed after writing any remaining buffered data to+  // it. If the readable side of the socket is in flowing mode then there is no+  // buffered data as everything has been already written and `readable.read()`+  // will return `null`. If instead, the socket is paused, any possible buffered+  // data will be read as a single chunk.+  //+  if (+    !this._readableState.endEmitted &&+    !websocket._closeFrameReceived &&+    !websocket._receiver._writableState.errorEmitted &&+    (chunk = websocket._socket.read()) !== null+  ) {+    websocket._receiver.write(chunk);+  }++  websocket._receiver.end();++  this[kWebSocket] = undefined;++  clearTimeout(websocket._closeTimer);++  if (+    websocket._receiver._writableState.finished ||+    websocket._receiver._writableState.errorEmitted+  ) {+    websocket.emitClose();+  } else {+    websocket._receiver.on('error', receiverOnFinish);+    websocket._receiver.on('finish', receiverOnFinish);+  }+}++/**+ * The listener of the socket `'data'` event.+ *+ * @param {Buffer} chunk A chunk of data+ * @private+ */+function socketOnData(chunk) {+  if (!this[kWebSocket]._receiver.write(chunk)) {+    this.pause();+  }+}++/**+ * The listener of the socket `'end'` event.+ *+ * @private+ */+function socketOnEnd() {+  const websocket = this[kWebSocket];++  websocket._readyState = WebSocket.CLOSING;+  websocket._receiver.end();+  this.end();+}++/**+ * The listener of the socket `'error'` event.+ *+ * @private+ */+function socketOnError() {+  const websocket = this[kWebSocket];++  this.removeListener('error', socketOnError);+  this.on('error', NOOP);++  if (websocket) {+    websocket._readyState = WebSocket.CLOSING;+    this.destroy();+  }+}
+ node-client/node_modules/ws/package.json view
@@ -0,0 +1,69 @@+{+  "name": "ws",+  "version": "8.18.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": {+    "type": "git",+    "url": "git+https://github.com/websockets/ws.git"+  },+  "author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",+  "license": "MIT",+  "main": "index.js",+  "exports": {+    ".": {+      "browser": "./browser.js",+      "import": "./wrapper.mjs",+      "require": "./index.js"+    },+    "./package.json": "./package.json"+  },+  "browser": "browser.js",+  "engines": {+    "node": ">=10.0.0"+  },+  "files": [+    "browser.js",+    "index.js",+    "lib/*.js",+    "wrapper.mjs"+  ],+  "scripts": {+    "test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",+    "integration": "mocha --throw-deprecation test/*.integration.js",+    "lint": "eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""+  },+  "peerDependencies": {+    "bufferutil": "^4.0.1",+    "utf-8-validate": ">=5.0.2"+  },+  "peerDependenciesMeta": {+    "bufferutil": {+      "optional": true+    },+    "utf-8-validate": {+      "optional": true+    }+  },+  "devDependencies": {+    "benchmark": "^2.1.4",+    "bufferutil": "^4.0.1",+    "eslint": "^9.0.0",+    "eslint-config-prettier": "^9.0.0",+    "eslint-plugin-prettier": "^5.0.0",+    "globals": "^15.0.0",+    "mocha": "^8.4.0",+    "nyc": "^15.0.0",+    "prettier": "^3.0.0",+    "utf-8-validate": "^6.0.0"+  }+}
+ node-client/node_modules/ws/wrapper.mjs view
@@ -0,0 +1,8 @@+import createWebSocketStream from './lib/stream.js';+import Receiver from './lib/receiver.js';+import Sender from './lib/sender.js';+import WebSocket from './lib/websocket.js';+import WebSocketServer from './lib/websocket-server.js';++export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer };+export default WebSocket;
+ node-client/node_modules/xmlhttprequest/.jshintrc view
@@ -0,0 +1,26 @@+{+  "node": true,+  "browser": false,+  "esnext": true,+  "bitwise": false,+  "camelcase": true,+  "curly": true,+  "eqeqeq": true,+  "eqnull": true,+  "immed": true,+  "indent": 2,+  "latedef": true,+  "laxbreak": true,+  "newcap": true,+  "noarg": true,+  "quotmark": "double",+  "regexp": true,+  "undef": true,+  "unused": true,+  "strict": true,+  "trailing": true,+  "smarttabs": true,+  "globals": {+    "define": false+  }+}
+ node-client/node_modules/xmlhttprequest/.npmignore view
@@ -0,0 +1,4 @@+example+tests++autotest.watchr
+ node-client/node_modules/xmlhttprequest/LICENSE view
@@ -0,0 +1,22 @@+ Copyright (c) 2010 passive.ly LLC++ 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.
+ node-client/node_modules/xmlhttprequest/README.md view
@@ -0,0 +1,55 @@+# node-XMLHttpRequest #++node-XMLHttpRequest is a wrapper for the built-in http client to emulate the+browser XMLHttpRequest object.++This can be used with JS designed for browsers to improve reuse of code and+allow the use of existing libraries.++Note: This library currently conforms to [XMLHttpRequest 1](http://www.w3.org/TR/XMLHttpRequest/). Version 2.0 will target [XMLHttpRequest Level 2](http://www.w3.org/TR/XMLHttpRequest2/).++## Usage ##++Here's how to include the module in your project and use as the browser-based+XHR object.++	var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;+	var xhr = new XMLHttpRequest();++Note: use the lowercase string "xmlhttprequest" in your require(). On+case-sensitive systems (eg Linux) using uppercase letters won't work.++## Versions ##++Prior to 1.4.0 version numbers were arbitrary. From 1.4.0 on they conform to+the standard major.minor.bugfix. 1.x shouldn't necessarily be considered+stable just because it's above 0.x.++Since the XMLHttpRequest API is stable this library's API is stable as+well. Major version numbers indicate significant core code changes.+Minor versions indicate minor core code changes or better conformity to+the W3C spec.++## License ##++MIT license. See LICENSE for full details.++## Supports ##++* Async and synchronous requests+* GET, POST, PUT, and DELETE requests+* All spec methods (open, send, abort, getRequestHeader,+  getAllRequestHeaders, event methods)+* Requests to all domains++## Known Issues / Missing Features ##++For a list of open issues or to report your own visit the [github issues+page](https://github.com/driverdan/node-XMLHttpRequest/issues).++* Local file access may have unexpected results for non-UTF8 files+* Synchronous requests don't set headers properly+* Synchronous requests freeze node while waiting for response (But that's what you want, right? Stick with async!).+* Some events are missing, such as abort+* Cookies aren't persisted between requests+* Missing XML support
+ node-client/node_modules/xmlhttprequest/lib/XMLHttpRequest.js view
@@ -0,0 +1,620 @@+/**+ * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.+ *+ * This can be used with JS designed for browsers to improve reuse of code and+ * allow the use of existing libraries.+ *+ * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.+ *+ * @author Dan DeFelippi <dan@driverdan.com>+ * @contributor David Ellis <d.f.ellis@ieee.org>+ * @license MIT+ */++var Url = require("url");+var spawn = require("child_process").spawn;+var fs = require("fs");++exports.XMLHttpRequest = function() {+  "use strict";++  /**+   * Private variables+   */+  var self = this;+  var http = require("http");+  var https = require("https");++  // Holds http.js objects+  var request;+  var response;++  // Request settings+  var settings = {};++  // Disable header blacklist.+  // Not part of XHR specs.+  var disableHeaderCheck = false;++  // Set some default headers+  var defaultHeaders = {+    "User-Agent": "node-XMLHttpRequest",+    "Accept": "*/*",+  };++  var headers = {};+  var headersCase = {};++  // These headers are not user setable.+  // The following are allowed but banned in the spec:+  // * user-agent+  var forbiddenRequestHeaders = [+    "accept-charset",+    "accept-encoding",+    "access-control-request-headers",+    "access-control-request-method",+    "connection",+    "content-length",+    "content-transfer-encoding",+    "cookie",+    "cookie2",+    "date",+    "expect",+    "host",+    "keep-alive",+    "origin",+    "referer",+    "te",+    "trailer",+    "transfer-encoding",+    "upgrade",+    "via"+  ];++  // These request methods are not allowed+  var forbiddenRequestMethods = [+    "TRACE",+    "TRACK",+    "CONNECT"+  ];++  // Send flag+  var sendFlag = false;+  // Error flag, used when errors occur or abort is called+  var errorFlag = false;++  // Event listeners+  var listeners = {};++  /**+   * Constants+   */++  this.UNSENT = 0;+  this.OPENED = 1;+  this.HEADERS_RECEIVED = 2;+  this.LOADING = 3;+  this.DONE = 4;++  /**+   * Public vars+   */++  // Current state+  this.readyState = this.UNSENT;++  // default ready state change handler in case one is not set or is set late+  this.onreadystatechange = null;++  // Result & response+  this.responseText = "";+  this.responseXML = "";+  this.status = null;+  this.statusText = null;+  +  // Whether cross-site Access-Control requests should be made using+  // credentials such as cookies or authorization headers+  this.withCredentials = false;++  /**+   * Private methods+   */++  /**+   * Check if the specified header is allowed.+   *+   * @param string header Header to validate+   * @return boolean False if not allowed, otherwise true+   */+  var isAllowedHttpHeader = function(header) {+    return disableHeaderCheck || (header && forbiddenRequestHeaders.indexOf(header.toLowerCase()) === -1);+  };++  /**+   * Check if the specified method is allowed.+   *+   * @param string method Request method to validate+   * @return boolean False if not allowed, otherwise true+   */+  var isAllowedHttpMethod = function(method) {+    return (method && forbiddenRequestMethods.indexOf(method) === -1);+  };++  /**+   * Public methods+   */++  /**+   * Open the connection. Currently supports local server requests.+   *+   * @param string method Connection method (eg GET, POST)+   * @param string url URL for the connection.+   * @param boolean async Asynchronous connection. Default is true.+   * @param string user Username for basic authentication (optional)+   * @param string password Password for basic authentication (optional)+   */+  this.open = function(method, url, async, user, password) {+    this.abort();+    errorFlag = false;++    // Check for valid request method+    if (!isAllowedHttpMethod(method)) {+      throw new Error("SecurityError: Request method not allowed");+    }++    settings = {+      "method": method,+      "url": url.toString(),+      "async": (typeof async !== "boolean" ? true : async),+      "user": user || null,+      "password": password || null+    };++    setState(this.OPENED);+  };++  /**+   * Disables or enables isAllowedHttpHeader() check the request. Enabled by default.+   * This does not conform to the W3C spec.+   *+   * @param boolean state Enable or disable header checking.+   */+  this.setDisableHeaderCheck = function(state) {+    disableHeaderCheck = state;+  };++  /**+   * Sets a header for the request or appends the value if one is already set.+   *+   * @param string header Header name+   * @param string value Header value+   */+  this.setRequestHeader = function(header, value) {+    if (this.readyState !== this.OPENED) {+      throw new Error("INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN");+    }+    if (!isAllowedHttpHeader(header)) {+      console.warn("Refused to set unsafe header \"" + header + "\"");+      return;+    }+    if (sendFlag) {+      throw new Error("INVALID_STATE_ERR: send flag is true");+    }+    header = headersCase[header.toLowerCase()] || header;+    headersCase[header.toLowerCase()] = header;+    headers[header] = headers[header] ? headers[header] + ', ' + value : value;+  };++  /**+   * Gets a header from the server response.+   *+   * @param string header Name of header to get.+   * @return string Text of the header or null if it doesn't exist.+   */+  this.getResponseHeader = function(header) {+    if (typeof header === "string"+      && this.readyState > this.OPENED+      && response+      && response.headers+      && response.headers[header.toLowerCase()]+      && !errorFlag+    ) {+      return response.headers[header.toLowerCase()];+    }++    return null;+  };++  /**+   * Gets all the response headers.+   *+   * @return string A string with all response headers separated by CR+LF+   */+  this.getAllResponseHeaders = function() {+    if (this.readyState < this.HEADERS_RECEIVED || errorFlag) {+      return "";+    }+    var result = "";++    for (var i in response.headers) {+      // Cookie headers are excluded+      if (i !== "set-cookie" && i !== "set-cookie2") {+        result += i + ": " + response.headers[i] + "\r\n";+      }+    }+    return result.substr(0, result.length - 2);+  };++  /**+   * Gets a request header+   *+   * @param string name Name of header to get+   * @return string Returns the request header or empty string if not set+   */+  this.getRequestHeader = function(name) {+    if (typeof name === "string" && headersCase[name.toLowerCase()]) {+      return headers[headersCase[name.toLowerCase()]];+    }++    return "";+  };++  /**+   * Sends the request to the server.+   *+   * @param string data Optional data to send as request body.+   */+  this.send = function(data) {+    if (this.readyState !== this.OPENED) {+      throw new Error("INVALID_STATE_ERR: connection must be opened before send() is called");+    }++    if (sendFlag) {+      throw new Error("INVALID_STATE_ERR: send has already been called");+    }++    var ssl = false, local = false;+    var url = Url.parse(settings.url);+    var host;+    // Determine the server+    switch (url.protocol) {+      case "https:":+        ssl = true;+        // SSL & non-SSL both need host, no break here.+      case "http:":+        host = url.hostname;+        break;++      case "file:":+        local = true;+        break;++      case undefined:+      case null:+      case "":+        host = "localhost";+        break;++      default:+        throw new Error("Protocol not supported.");+    }++    // Load files off the local filesystem (file://)+    if (local) {+      if (settings.method !== "GET") {+        throw new Error("XMLHttpRequest: Only GET method is supported");+      }++      if (settings.async) {+        fs.readFile(url.pathname, "utf8", function(error, data) {+          if (error) {+            self.handleError(error);+          } else {+            self.status = 200;+            self.responseText = data;+            setState(self.DONE);+          }+        });+      } else {+        try {+          this.responseText = fs.readFileSync(url.pathname, "utf8");+          this.status = 200;+          setState(self.DONE);+        } catch(e) {+          this.handleError(e);+        }+      }++      return;+    }++    // Default to port 80. If accessing localhost on another port be sure+    // to use http://localhost:port/path+    var port = url.port || (ssl ? 443 : 80);+    // Add query string if one is used+    var uri = url.pathname + (url.search ? url.search : "");++    // Set the defaults if they haven't been set+    for (var name in defaultHeaders) {+      if (!headersCase[name.toLowerCase()]) {+        headers[name] = defaultHeaders[name];+      }+    }++    // Set the Host header or the server may reject the request+    headers.Host = host;+    if (!((ssl && port === 443) || port === 80)) {+      headers.Host += ":" + url.port;+    }++    // Set Basic Auth if necessary+    if (settings.user) {+      if (typeof settings.password === "undefined") {+        settings.password = "";+      }+      var authBuf = new Buffer(settings.user + ":" + settings.password);+      headers.Authorization = "Basic " + authBuf.toString("base64");+    }++    // Set content length header+    if (settings.method === "GET" || settings.method === "HEAD") {+      data = null;+    } else if (data) {+      headers["Content-Length"] = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);++      if (!headers["Content-Type"]) {+        headers["Content-Type"] = "text/plain;charset=UTF-8";+      }+    } else if (settings.method === "POST") {+      // For a post with no data set Content-Length: 0.+      // This is required by buggy servers that don't meet the specs.+      headers["Content-Length"] = 0;+    }++    var options = {+      host: host,+      port: port,+      path: uri,+      method: settings.method,+      headers: headers,+      agent: false,+      withCredentials: self.withCredentials+    };++    // Reset error flag+    errorFlag = false;++    // Handle async requests+    if (settings.async) {+      // Use the proper protocol+      var doRequest = ssl ? https.request : http.request;++      // Request is being sent, set send flag+      sendFlag = true;++      // As per spec, this is called here for historical reasons.+      self.dispatchEvent("readystatechange");++      // Handler for the response+      var responseHandler = function responseHandler(resp) {+        // Set response var to the response we got back+        // This is so it remains accessable outside this scope+        response = resp;+        // Check for redirect+        // @TODO Prevent looped redirects+        if (response.statusCode === 301 || response.statusCode === 302 || response.statusCode === 303 || response.statusCode === 307) {+          // Change URL to the redirect location+          settings.url = response.headers.location;+          var url = Url.parse(settings.url);+          // Set host var in case it's used later+          host = url.hostname;+          // Options for the new request+          var newOptions = {+            hostname: url.hostname,+            port: url.port,+            path: url.path,+            method: response.statusCode === 303 ? "GET" : settings.method,+            headers: headers,+            withCredentials: self.withCredentials+          };++          // Issue the new request+          request = doRequest(newOptions, responseHandler).on("error", errorHandler);+          request.end();+          // @TODO Check if an XHR event needs to be fired here+          return;+        }++        response.setEncoding("utf8");++        setState(self.HEADERS_RECEIVED);+        self.status = response.statusCode;++        response.on("data", function(chunk) {+          // Make sure there's some data+          if (chunk) {+            self.responseText += chunk;+          }+          // Don't emit state changes if the connection has been aborted.+          if (sendFlag) {+            setState(self.LOADING);+          }+        });++        response.on("end", function() {+          if (sendFlag) {+            // Discard the end event if the connection has been aborted+            setState(self.DONE);+            sendFlag = false;+          }+        });++        response.on("error", function(error) {+          self.handleError(error);+        });+      };++      // Error handler for the request+      var errorHandler = function errorHandler(error) {+        self.handleError(error);+      };++      // Create the request+      request = doRequest(options, responseHandler).on("error", errorHandler);++      // Node 0.4 and later won't accept empty data. Make sure it's needed.+      if (data) {+        request.write(data);+      }++      request.end();++      self.dispatchEvent("loadstart");+    } else { // Synchronous+      // Create a temporary file for communication with the other Node process+      var contentFile = ".node-xmlhttprequest-content-" + process.pid;+      var syncFile = ".node-xmlhttprequest-sync-" + process.pid;+      fs.writeFileSync(syncFile, "", "utf8");+      // The async request the other Node process executes+      var execString = "var http = require('http'), https = require('https'), fs = require('fs');"+        + "var doRequest = http" + (ssl ? "s" : "") + ".request;"+        + "var options = " + JSON.stringify(options) + ";"+        + "var responseText = '';"+        + "var req = doRequest(options, function(response) {"+        + "response.setEncoding('utf8');"+        + "response.on('data', function(chunk) {"+        + "  responseText += chunk;"+        + "});"+        + "response.on('end', function() {"+        + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText}}), 'utf8');"+        + "fs.unlinkSync('" + syncFile + "');"+        + "});"+        + "response.on('error', function(error) {"+        + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: error}), 'utf8');"+        + "fs.unlinkSync('" + syncFile + "');"+        + "});"+        + "}).on('error', function(error) {"+        + "fs.writeFileSync('" + contentFile + "', JSON.stringify({err: error}), 'utf8');"+        + "fs.unlinkSync('" + syncFile + "');"+        + "});"+        + (data ? "req.write('" + JSON.stringify(data).slice(1,-1).replace(/'/g, "\\'") + "');":"")+        + "req.end();";+      // Start the other Node Process, executing this string+      var syncProc = spawn(process.argv[0], ["-e", execString]);+      while(fs.existsSync(syncFile)) {+        // Wait while the sync file is empty+      }+      var resp = JSON.parse(fs.readFileSync(contentFile, 'utf8'));+      // Kill the child process once the file has data+      syncProc.stdin.end();+      // Remove the temporary file+      fs.unlinkSync(contentFile);++      if (resp.err) {+        self.handleError(resp.err);+      } else {+        response = resp.data;+        self.status = resp.data.statusCode;+        self.responseText = resp.data.text;+        setState(self.DONE);+      }+    }+  };++  /**+   * Called when an error is encountered to deal with it.+   */+  this.handleError = function(error) {+    this.status = 0;+    this.statusText = error;+    this.responseText = error.stack;+    errorFlag = true;+    setState(this.DONE);+    this.dispatchEvent('error');+  };++  /**+   * Aborts a request.+   */+  this.abort = function() {+    if (request) {+      request.abort();+      request = null;+    }++    headers = defaultHeaders;+    this.status = 0;+    this.responseText = "";+    this.responseXML = "";++    errorFlag = true;++    if (this.readyState !== this.UNSENT+        && (this.readyState !== this.OPENED || sendFlag)+        && this.readyState !== this.DONE) {+      sendFlag = false;+      setState(this.DONE);+    }+    this.readyState = this.UNSENT;+    this.dispatchEvent('abort');+  };++  /**+   * Adds an event listener. Preferred method of binding to events.+   */+  this.addEventListener = function(event, callback) {+    if (!(event in listeners)) {+      listeners[event] = [];+    }+    // Currently allows duplicate callbacks. Should it?+    listeners[event].push(callback);+  };++  /**+   * Remove an event callback that has already been bound.+   * Only works on the matching funciton, cannot be a copy.+   */+  this.removeEventListener = function(event, callback) {+    if (event in listeners) {+      // Filter will return a new array with the callback removed+      listeners[event] = listeners[event].filter(function(ev) {+        return ev !== callback;+      });+    }+  };++  /**+   * Dispatch any events, including both "on" methods and events attached using addEventListener.+   */+  this.dispatchEvent = function(event) {+    if (typeof self["on" + event] === "function") {+      self["on" + event]();+    }+    if (event in listeners) {+      for (var i = 0, len = listeners[event].length; i < len; i++) {+        listeners[event][i].call(self);+      }+    }+  };++  /**+   * Changes readyState and calls onreadystatechange.+   *+   * @param int state New state+   */+  var setState = function(state) {+    if (state == self.LOADING || self.readyState !== state) {+      self.readyState = state;++      if (settings.async || self.readyState < self.OPENED || self.readyState === self.DONE) {+        self.dispatchEvent("readystatechange");+      }++      if (self.readyState === self.DONE && !errorFlag) {+        self.dispatchEvent("load");+        // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)+        self.dispatchEvent("loadend");+      }+    }+  };+};
+ node-client/node_modules/xmlhttprequest/package.json view
@@ -0,0 +1,24 @@+{+  "name": "xmlhttprequest",+  "description": "XMLHttpRequest for Node",+  "version": "1.8.0",+  "author": {+    "name": "Dan DeFelippi",+    "url": "http://driverdan.com"+  },+  "keywords": ["xhr", "ajax"],+  "license": "MIT",+  "repository": {+    "type": "git",+    "url": "git://github.com/driverdan/node-XMLHttpRequest.git"+  },+  "bugs": "http://github.com/driverdan/node-XMLHttpRequest/issues",+  "engines": {+    "node": ">=0.4.0"+  },+  "directories": {+    "lib": "./lib",+    "example": "./example"+  },+  "main": "./lib/XMLHttpRequest.js"+}
+ node-client/package-lock.json view
@@ -0,0 +1,45 @@+{+  "name": "jsaddle-warp-node-client",+  "version": "0.1.0",+  "lockfileVersion": 3,+  "requires": true,+  "packages": {+    "": {+      "name": "jsaddle-warp-node-client",+      "version": "0.1.0",+      "license": "ISC",+      "dependencies": {+        "ws": "^8.13.0",+        "xmlhttprequest": "^1.8.0"+      }+    },+    "node_modules/ws": {+      "version": "8.18.0",+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",+      "engines": {+        "node": ">=10.0.0"+      },+      "peerDependencies": {+        "bufferutil": "^4.0.1",+        "utf-8-validate": ">=5.0.2"+      },+      "peerDependenciesMeta": {+        "bufferutil": {+          "optional": true+        },+        "utf-8-validate": {+          "optional": true+        }+      }+    },+    "node_modules/xmlhttprequest": {+      "version": "1.8.0",+      "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",+      "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",+      "engines": {+        "node": ">=0.4.0"+      }+    }+  }+}
+ node-client/package.json view
@@ -0,0 +1,15 @@+{+  "name": "jsaddle-warp-node-client",+  "version": "0.1.0",+  "description": "A minimal JS client to use with JSaddle warp testing",+  "main": "index.js",+  "scripts": {+    "test": "echo \"Error: no test specified\" && exit 1"+  },+  "author": "",+  "license": "ISC",+  "dependencies": {+    "ws": "^8.13.0",+    "xmlhttprequest": "^1.8.0"+  }+}
src/Language/Javascript/JSaddle/WebSockets.hs view
@@ -243,8 +243,9 @@     \"  -- | Start or restart the server.--- To run this as part of every :reload use--- > :def! reload (const $ return "::reload\nLanguage.Javascript.JSaddle.Warp.debug 3708 SomeMainModule.someMainFunction")+-- To run this as part of every GHCI @:reload@ use:+--+-- >>> :def! reload (const $ return "::reload\nLanguage.Javascript.JSaddle.Warp.debug 3708 SomeMainModule.someMainFunction") debug :: Int -> JSM () -> IO () debug port f = do     debugWrapper $ \withRefresh registerContext ->