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 +39/−4
- node-client/index.js +34/−0
- node-client/node_modules/.package-lock.json +36/−0
- node-client/node_modules/ws/LICENSE +20/−0
- node-client/node_modules/ws/README.md +548/−0
- node-client/node_modules/ws/browser.js +8/−0
- node-client/node_modules/ws/index.js +13/−0
- node-client/node_modules/ws/lib/buffer-util.js +131/−0
- node-client/node_modules/ws/lib/constants.js +18/−0
- node-client/node_modules/ws/lib/event-target.js +292/−0
- node-client/node_modules/ws/lib/extension.js +203/−0
- node-client/node_modules/ws/lib/limiter.js +55/−0
- node-client/node_modules/ws/lib/permessage-deflate.js +514/−0
- node-client/node_modules/ws/lib/receiver.js +706/−0
- node-client/node_modules/ws/lib/sender.js +602/−0
- node-client/node_modules/ws/lib/stream.js +159/−0
- node-client/node_modules/ws/lib/subprotocol.js +62/−0
- node-client/node_modules/ws/lib/validation.js +152/−0
- node-client/node_modules/ws/lib/websocket-server.js +540/−0
- node-client/node_modules/ws/lib/websocket.js +1388/−0
- node-client/node_modules/ws/package.json +69/−0
- node-client/node_modules/ws/wrapper.mjs +8/−0
- node-client/node_modules/xmlhttprequest/.jshintrc +26/−0
- node-client/node_modules/xmlhttprequest/.npmignore +4/−0
- node-client/node_modules/xmlhttprequest/LICENSE +22/−0
- node-client/node_modules/xmlhttprequest/README.md +55/−0
- node-client/node_modules/xmlhttprequest/lib/XMLHttpRequest.js +620/−0
- node-client/node_modules/xmlhttprequest/package.json +24/−0
- node-client/package-lock.json +45/−0
- node-client/package.json +15/−0
- src/Language/Javascript/JSaddle/WebSockets.hs +3/−2
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++[](https://www.npmjs.com/package/ws)+[](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)+[](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 ->