Dependency update

This commit is contained in:
2025-04-08 22:04:41 +02:00
committed by Philipp Horstenkamp
parent 78549e06b4
commit f7b40cb8dd
492 changed files with 7483 additions and 47496 deletions

278
node_modules/ws/README.md generated vendored
View File

@ -1,11 +1,12 @@
# ws: a Node.js WebSocket library
[![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws)
[![CI](https://img.shields.io/github/workflow/status/websockets/ws/CI/master?label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg?logo=coveralls)](https://coveralls.io/github/websockets/ws)
[![Version npm](https://img.shields.io/npm/v/ws.svg)](https://www.npmjs.com/package/ws)
[![Linux Build](https://img.shields.io/travis/websockets/ws/master.svg)](https://travis-ci.org/websockets/ws)
[![Windows Build](https://ci.appveyor.com/api/projects/status/github/websockets/ws?branch=master&svg=true)](https://ci.appveyor.com/project/lpinca/ws)
[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg)](https://coveralls.io/r/websockets/ws?branch=master)
ws is a simple to use, blazing fast, and thoroughly tested WebSocket client and
server implementation.
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].
@ -13,49 +14,47 @@ Passes the quite extensive Autobahn test suite: [server][server-report],
**Note**: This module does not work in the browser. The client in the docs is a
reference to a back end with the role of a client in the WebSocket
communication. Browser clients must use the native
[`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
object. To make the same code work seamlessly on Node.js and the browser, you
can use one of the many wrappers available on npm, like
[`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)
- [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)
- [echo.websocket.org demo](#echowebsocketorg-demo)
- [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](#protocol-support)
* [Installing](#installing)
+ [Opt-in for performance and spec compliance](#opt-in-for-performance-and-spec-compliance)
* [API docs](#api-docs)
* [WebSocket compression](#websocket-compression)
* [Usage examples](#usage-examples)
+ [Sending and receiving text data](#sending-and-receiving-text-data)
+ [Sending binary data](#sending-binary-data)
+ [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)
+ [Server broadcast](#server-broadcast)
+ [echo.websocket.org demo](#echowebsocketorg-demo)
+ [Other examples](#other-examples)
* [Error handling best practices](#error-handling-best-practices)
* [FAQ](#faq)
+ [How to get the IP address of the client?](#how-to-get-the-ip-address-of-the-client)
+ [How to detect and close broken connections?](#how-to-detect-and-close-broken-connections)
+ [How to connect via a proxy?](#how-to-connect-via-a-proxy)
* [Changelog](#changelog)
* [License](#license)
## Protocol support
- **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
- **HyBi drafts 13-17** (Current default, alternatively option
`protocolVersion: 13`)
* **HyBi drafts 07-12** (Use the option `protocolVersion: 8`)
* **HyBi drafts 13-17** (Current default, alternatively option `protocolVersion: 13`)
## Installing
```
npm install ws
npm install --save ws
```
### Opt-in for performance
### Opt-in for performance and spec compliance
There are 2 optional modules that can be installed along side with the ws
module. These modules are binary addons which improve certain operations.
@ -65,30 +64,30 @@ necessarily need to have a C++ compiler installed on your machine.
- `npm install --save-optional bufferutil`: Allows to efficiently perform
operations such as masking and unmasking the data payload of the WebSocket
frames.
- `npm install --save-optional utf-8-validate`: Allows to efficiently check if a
message contains valid UTF-8.
- `npm install --save-optional utf-8-validate`: Allows to efficiently check
if a message contains valid UTF-8 as required by the spec.
## API docs
See [`/doc/ws.md`](./doc/ws.md) for Node.js-like documentation of ws classes and
utility functions.
See [`/doc/ws.md`](./doc/ws.md) for Node.js-like docs for the ws classes.
## WebSocket compression
ws supports the [permessage-deflate extension][permessage-deflate] which enables
the client and server to negotiate a compression algorithm and its parameters,
and then selectively apply it to the data payloads of each WebSocket message.
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
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.
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
@ -102,11 +101,10 @@ const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 8080,
perMessageDeflate: {
zlibDeflateOptions: {
// See zlib defaults.
zlibDeflateOptions: { // See zlib defaults.
chunkSize: 1024,
memLevel: 7,
level: 3
level: 3,
},
zlibInflateOptions: {
chunkSize: 10 * 1024
@ -114,11 +112,12 @@ const wss = new WebSocket.Server({
// Other options settable:
clientNoContextTakeover: true, // Defaults to negotiated value.
serverNoContextTakeover: true, // Defaults to negotiated value.
serverMaxWindowBits: 10, // Defaults to negotiated value.
clientMaxWindowBits: 10, // Defaults to negotiated value.
serverMaxWindowBits: 10, // Defaults to negotiated value.
// Below options specified as default values.
concurrencyLimit: 10, // Limits zlib concurrency for perf.
threshold: 1024 // Size (in bytes) below which messages
// should not be compressed.
concurrencyLimit: 10, // Limits zlib concurrency for perf.
threshold: 1024, // Size (in bytes) below which messages
// should not be compressed.
}
});
```
@ -194,7 +193,7 @@ const fs = require('fs');
const https = require('https');
const WebSocket = require('ws');
const server = https.createServer({
const server = new https.createServer({
cert: fs.readFileSync('/path/to/cert.pem'),
key: fs.readFileSync('/path/to/key.pem')
});
@ -216,7 +215,6 @@ server.listen(8080);
```js
const http = require('http');
const WebSocket = require('ws');
const url = require('url');
const server = http.createServer();
const wss1 = new WebSocket.Server({ noServer: true });
@ -249,72 +247,25 @@ server.on('upgrade', function upgrade(request, socket, head) {
server.listen(8080);
```
### Client authentication
```js
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });
wss.on('connection', function connection(ws, request, client) {
ws.on('message', function message(msg) {
console.log(`Received message ${msg} from user ${client}`);
});
});
server.on('upgrade', function upgrade(request, socket, head) {
// This function is not defined on purpose. Implement it with your own logic.
authenticate(request, (err, client) => {
if (err || !client) {
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
}
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
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
// Broadcast to all.
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
});
```
A client WebSocket broadcasting to every other connected WebSocket clients,
excluding itself.
```js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
};
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(data) {
// Broadcast to everyone else.
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data);
@ -351,21 +302,6 @@ ws.on('message', function incoming(data) {
});
```
### Use the Node.js streams API
```js
const WebSocket = require('ws');
const ws = new WebSocket('wss://echo.websocket.org/', {
origin: 'https://websocket.org'
});
const duplex = WebSocket.createWebSocketStream(ws, { encoding: 'utf8' });
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
@ -373,6 +309,27 @@ examples folder.
Otherwise, see the test cases.
## Error handling best practices
```js
// If the WebSocket is closed before the following send is attempted
ws.send('something');
// Errors (both immediate and async write errors) can be detected in an optional
// callback. The callback is also the only way of being notified that data has
// actually been sent.
ws.send('something', function ack(error) {
// If error is not defined, the send has been completed, otherwise the error
// object will indicate what failed.
});
// Immediate errors can also be handled with `try...catch`, but **note** that
// since sends are inherently asynchronous, socket write failures will *not* be
// captured when this technique is used.
try { ws.send('something'); }
catch (e) { /* handle error */ }
```
## FAQ
### How to get the IP address of the client?
@ -385,7 +342,7 @@ const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws, req) {
const ip = req.socket.remoteAddress;
const ip = req.connection.remoteAddress;
});
```
@ -394,14 +351,14 @@ the `X-Forwarded-For` header.
```js
wss.on('connection', function connection(ws, req) {
const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
const ip = req.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
});
```
### 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
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
@ -410,14 +367,14 @@ endpoint is still responsive.
```js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function noop() {}
function heartbeat() {
this.isAlive = true;
}
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', heartbeat);
@ -431,42 +388,10 @@ const interval = setInterval(function ping() {
ws.ping(noop);
});
}, 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
const WebSocket = require('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://echo.websocket.org/');
client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
clearTimeout(this.pingTimeout);
});
```
Pong messages are automatically sent in response to ping messages as required
by the spec.
### How to connect via a proxy?
@ -481,15 +406,12 @@ We're using the GitHub [releases][changelog] for changelog entries.
[MIT](LICENSE)
[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
[ws-server-options]:
https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
[client-report]: http://websockets.github.io/ws/autobahn/clients/
[server-report]: http://websockets.github.io/ws/autobahn/servers/
[permessage-deflate]: https://tools.ietf.org/html/rfc7692
[changelog]: https://github.com/websockets/ws/releases
[node-zlib-bug]: https://github.com/nodejs/node/issues/8871
[node-zlib-deflaterawdocs]: https://nodejs.org/api/zlib.html#zlib_zlib_createdeflateraw_options
[ws-server-options]: https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback

8
node_modules/ws/browser.js generated vendored
View File

@ -1,8 +0,0 @@
'use strict';
module.exports = function () {
throw new Error(
'ws does not work in the browser. Browser clients must use the native ' +
'WebSocket object'
);
};

1
node_modules/ws/index.js generated vendored
View File

@ -2,7 +2,6 @@
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');

83
node_modules/ws/lib/buffer-util.js generated vendored
View File

@ -1,7 +1,5 @@
'use strict';
const { EMPTY_BUFFER } = require('./constants');
/**
* Merges an array of buffers into a new buffer.
*
@ -10,21 +8,16 @@ const { EMPTY_BUFFER } = require('./constants');
* @return {Buffer} The resulting buffer
* @public
*/
function concat(list, totalLength) {
if (list.length === 0) return EMPTY_BUFFER;
if (list.length === 1) return list[0];
function concat (list, totalLength) {
const target = Buffer.allocUnsafe(totalLength);
let offset = 0;
var offset = 0;
for (let i = 0; i < list.length; i++) {
for (var i = 0; i < list.length; i++) {
const buf = list[i];
target.set(buf, offset);
buf.copy(target, offset);
offset += buf.length;
}
if (offset < totalLength) return target.slice(0, offset);
return target;
}
@ -38,8 +31,8 @@ function concat(list, totalLength) {
* @param {Number} length The number of bytes to mask.
* @public
*/
function _mask(source, mask, output, offset, length) {
for (let i = 0; i < length; i++) {
function _mask (source, mask, output, offset, length) {
for (var i = 0; i < length; i++) {
output[offset + i] = source[i] ^ mask[i & 3];
}
}
@ -51,79 +44,29 @@ function _mask(source, mask, output, offset, length) {
* @param {Buffer} mask The mask to use
* @public
*/
function _unmask(buffer, mask) {
function _unmask (buffer, mask) {
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
const length = buffer.length;
for (let i = 0; i < length; i++) {
for (var i = 0; i < 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.byteLength === buf.buffer.byteLength) {
return buf.buffer;
}
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
/**
* 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 = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
} else {
buf = Buffer.from(data);
toBuffer.readOnly = false;
}
return buf;
}
try {
const bufferUtil = require('bufferutil');
const bu = bufferUtil.BufferUtil || bufferUtil;
module.exports = {
concat,
mask(source, mask, output, offset, length) {
mask (source, mask, output, offset, length) {
if (length < 48) _mask(source, mask, output, offset, length);
else bu.mask(source, mask, output, offset, length);
},
toArrayBuffer,
toBuffer,
unmask(buffer, mask) {
unmask (buffer, mask) {
if (buffer.length < 32) _unmask(buffer, mask);
else bu.unmask(buffer, mask);
}
},
concat
};
} catch (e) /* istanbul ignore next */ {
module.exports = {
concat,
mask: _mask,
toArrayBuffer,
toBuffer,
unmask: _unmask
};
module.exports = { concat, mask: _mask, unmask: _unmask };
}

78
node_modules/ws/lib/event-target.js generated vendored
View File

@ -10,10 +10,9 @@ class Event {
* Create a new `Event`.
*
* @param {String} type The name of the event
* @param {Object} target A reference to the target to which the event was
* dispatched
* @param {Object} target A reference to the target to which the event was dispatched
*/
constructor(type, target) {
constructor (type, target) {
this.target = target;
this.type = type;
}
@ -30,10 +29,9 @@ class MessageEvent extends Event {
* Create a new `MessageEvent`.
*
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor(data, target) {
constructor (data, target) {
super('message', target);
this.data = data;
@ -50,14 +48,11 @@ class CloseEvent extends Event {
/**
* Create a new `CloseEvent`.
*
* @param {Number} code The status code explaining why the connection is being
* closed
* @param {String} reason A human-readable string explaining why the
* connection is closing
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @param {Number} code The status code explaining why the connection is being closed
* @param {String} reason A human-readable string explaining why the connection is closing
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor(code, reason, target) {
constructor (code, reason, target) {
super('close', target);
this.wasClean = target._closeFrameReceived && target._closeFrameSent;
@ -76,10 +71,9 @@ class OpenEvent extends Event {
/**
* Create a new `OpenEvent`.
*
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor(target) {
constructor (target) {
super('open', target);
}
}
@ -95,10 +89,9 @@ class ErrorEvent extends Event {
* Create a new `ErrorEvent`.
*
* @param {Object} error The error that generated this event
* @param {WebSocket} target A reference to the target to which the event was
* dispatched
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor(error, target) {
constructor (error, target) {
super('error', target);
this.message = error.message;
@ -116,66 +109,59 @@ const EventTarget = {
/**
* Register an event listener.
*
* @param {String} type A string representing the event type to listen for
* @param {String} method A string representing the event type to listen for
* @param {Function} listener 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, listener, options) {
addEventListener (method, listener) {
if (typeof listener !== 'function') return;
function onMessage(data) {
function onMessage (data) {
listener.call(this, new MessageEvent(data, this));
}
function onClose(code, message) {
function onClose (code, message) {
listener.call(this, new CloseEvent(code, message, this));
}
function onError(error) {
function onError (error) {
listener.call(this, new ErrorEvent(error, this));
}
function onOpen() {
function onOpen () {
listener.call(this, new OpenEvent(this));
}
const method = options && options.once ? 'once' : 'on';
if (type === 'message') {
if (method === 'message') {
onMessage._listener = listener;
this[method](type, onMessage);
} else if (type === 'close') {
this.on(method, onMessage);
} else if (method === 'close') {
onClose._listener = listener;
this[method](type, onClose);
} else if (type === 'error') {
this.on(method, onClose);
} else if (method === 'error') {
onError._listener = listener;
this[method](type, onError);
} else if (type === 'open') {
this.on(method, onError);
} else if (method === 'open') {
onOpen._listener = listener;
this[method](type, onOpen);
this.on(method, onOpen);
} else {
this[method](type, listener);
this.on(method, listener);
}
},
/**
* Remove an event listener.
*
* @param {String} type A string representing the event type to remove
* @param {String} method A string representing the event type to remove
* @param {Function} listener The listener to remove
* @public
*/
removeEventListener(type, listener) {
const listeners = this.listeners(type);
removeEventListener (method, listener) {
const listeners = this.listeners(method);
for (let i = 0; i < listeners.length; i++) {
for (var i = 0; i < listeners.length; i++) {
if (listeners[i] === listener || listeners[i]._listener === listener) {
this.removeListener(type, listeners[i]);
this.removeListener(method, listeners[i]);
}
}
}

84
node_modules/ws/lib/extension.js generated vendored
View File

@ -11,7 +11,6 @@
// 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
@ -33,9 +32,9 @@ const tokenChars = [
* parameter value
* @private
*/
function push(dest, name, elem) {
if (dest[name] === undefined) dest[name] = [elem];
else dest[name].push(elem);
function push (dest, name, elem) {
if (Object.prototype.hasOwnProperty.call(dest, name)) dest[name].push(elem);
else dest[name] = [elem];
}
/**
@ -45,30 +44,29 @@ function push(dest, name, elem) {
* @return {Object} The parsed object
* @public
*/
function parse(header) {
const offers = Object.create(null);
function parse (header) {
const offers = {};
if (header === undefined || header === '') return offers;
let params = Object.create(null);
let mustUnescape = false;
let isEscaping = false;
let inQuotes = false;
let extensionName;
let paramName;
let start = -1;
let end = -1;
let i = 0;
var params = {};
var mustUnescape = false;
var isEscaping = false;
var inQuotes = false;
var extensionName;
var paramName;
var start = -1;
var end = -1;
for (; i < header.length; i++) {
for (var i = 0; i < header.length; i++) {
const code = header.charCodeAt(i);
if (extensionName === undefined) {
if (end === -1 && tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) {
} else if (code === 0x20/* ' ' */|| code === 0x09/* '\t' */) {
if (end === -1 && start !== -1) end = i;
} else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
} else if (code === 0x3b/* ';' */ || code === 0x2c/* ',' */) {
if (start === -1) {
throw new SyntaxError(`Unexpected character at index ${i}`);
}
@ -77,7 +75,7 @@ function parse(header) {
const name = header.slice(start, end);
if (code === 0x2c) {
push(offers, name, params);
params = Object.create(null);
params = {};
} else {
extensionName = name;
}
@ -100,12 +98,12 @@ function parse(header) {
push(params, header.slice(start, end), true);
if (code === 0x2c) {
push(offers, extensionName, params);
params = Object.create(null);
params = {};
extensionName = undefined;
}
start = end = -1;
} else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
} else if (code === 0x3d/* '=' */&& start !== -1 && end === -1) {
paramName = header.slice(start, i);
start = end = -1;
} else {
@ -127,10 +125,10 @@ function parse(header) {
} else if (inQuotes) {
if (tokenChars[code] === 1) {
if (start === -1) start = i;
} else if (code === 0x22 /* '"' */ && start !== -1) {
} else if (code === 0x22/* '"' */ && start !== -1) {
inQuotes = false;
end = i;
} else if (code === 0x5c /* '\' */) {
} else if (code === 0x5c/* '\' */) {
isEscaping = true;
} else {
throw new SyntaxError(`Unexpected character at index ${i}`);
@ -147,7 +145,7 @@ function parse(header) {
}
if (end === -1) end = i;
let value = header.slice(start, end);
var value = header.slice(start, end);
if (mustUnescape) {
value = value.replace(/\\/g, '');
mustUnescape = false;
@ -155,7 +153,7 @@ function parse(header) {
push(params, paramName, value);
if (code === 0x2c) {
push(offers, extensionName, params);
params = Object.create(null);
params = {};
extensionName = undefined;
}
@ -174,7 +172,7 @@ function parse(header) {
if (end === -1) end = i;
const token = header.slice(start, end);
if (extensionName === undefined) {
push(offers, token, params);
push(offers, token, {});
} else {
if (paramName === undefined) {
push(params, token, true);
@ -196,28 +194,18 @@ function parse(header) {
* @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(', ');
function format (extensions) {
return Object.keys(extensions).map((extension) => {
var configurations = extensions[extension];
if (!Array.isArray(configurations)) configurations = [configurations];
return configurations.map((params) => {
return [extension].concat(Object.keys(params).map((k) => {
var values = params[k];
if (!Array.isArray(values)) values = [values];
return values.map((v) => v === true ? k : `${k}=${v}`).join('; ');
})).join('; ');
}).join(', ');
}).join(', ');
}
module.exports = { format, parse };

55
node_modules/ws/lib/limiter.js generated vendored
View File

@ -1,55 +0,0 @@
'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;

View File

@ -1,13 +1,17 @@
'use strict';
const Limiter = require('async-limiter');
const zlib = require('zlib');
const bufferUtil = require('./buffer-util');
const Limiter = require('./limiter');
const { kStatusCode, NOOP } = require('./constants');
const constants = require('./constants');
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
const EMPTY_BLOCK = Buffer.from([0x00]);
const kPerMessageDeflate = Symbol('permessage-deflate');
const kWriteInProgress = Symbol('write-in-progress');
const kPendingClose = Symbol('pending-close');
const kTotalLength = Symbol('total-length');
const kCallback = Symbol('callback');
const kBuffers = Symbol('buffers');
@ -29,32 +33,31 @@ class PerMessageDeflate {
/**
* Creates a PerMessageDeflate instance.
*
* @param {Object} [options] Configuration options
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
* disabling of server context takeover
* @param {Boolean} [options.clientNoContextTakeover=false] Advertise/
* acknowledge disabling of client context takeover
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
* @param {Object} options Configuration options
* @param {Boolean} options.serverNoContextTakeover Request/accept disabling
* of server context takeover
* @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
* disabling of client context takeover
* @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
* use of a custom server window size
* @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support
* @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
* for, or request, a custom client window size
* @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on
* deflate
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
* inflate
* @param {Number} [options.threshold=1024] Size (in bytes) below which
* messages should not be compressed
* @param {Number} [options.concurrencyLimit=10] The number of concurrent
* calls to zlib
* @param {Boolean} [isServer=false] Create the instance in either server or
* client mode
* @param {Number} [maxPayload=0] The maximum allowed message length
* @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate
* @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate
* @param {Number} options.threshold Size (in bytes) below which messages
* should not be compressed
* @param {Number} options.concurrencyLimit The number of concurrent calls to
* zlib
* @param {Boolean} isServer Create the instance in either server or client
* mode
* @param {Number} maxPayload The maximum allowed message length
*/
constructor(options, isServer, maxPayload) {
constructor (options, isServer, maxPayload) {
this._maxPayload = maxPayload | 0;
this._options = options || {};
this._threshold =
this._options.threshold !== undefined ? this._options.threshold : 1024;
this._threshold = this._options.threshold !== undefined
? this._options.threshold
: 1024;
this._isServer = !!isServer;
this._deflate = null;
this._inflate = null;
@ -62,18 +65,17 @@ class PerMessageDeflate {
this.params = null;
if (!zlibLimiter) {
const concurrency =
this._options.concurrencyLimit !== undefined
? this._options.concurrencyLimit
: 10;
zlibLimiter = new Limiter(concurrency);
const concurrency = this._options.concurrencyLimit !== undefined
? this._options.concurrencyLimit
: 10;
zlibLimiter = new Limiter({ concurrency });
}
}
/**
* @type {String}
*/
static get extensionName() {
static get extensionName () {
return 'permessage-deflate';
}
@ -83,7 +85,7 @@ class PerMessageDeflate {
* @return {Object} Extension parameters
* @public
*/
offer() {
offer () {
const params = {};
if (this._options.serverNoContextTakeover) {
@ -111,7 +113,7 @@ class PerMessageDeflate {
* @return {Object} Accepted configuration
* @public
*/
accept(configurations) {
accept (configurations) {
configurations = this.normalizeParams(configurations);
this.params = this._isServer
@ -126,24 +128,21 @@ class PerMessageDeflate {
*
* @public
*/
cleanup() {
cleanup () {
if (this._inflate) {
this._inflate.close();
this._inflate = null;
if (this._inflate[kWriteInProgress]) {
this._inflate[kPendingClose] = true;
} else {
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'
)
);
if (this._deflate[kWriteInProgress]) {
this._deflate[kPendingClose] = true;
} else {
this._deflate.close();
this._deflate = null;
}
}
}
@ -155,7 +154,7 @@ class PerMessageDeflate {
* @return {Object} Accepted configuration
* @private
*/
acceptAsServer(offers) {
acceptAsServer (offers) {
const opts = this._options;
const accepted = offers.find((params) => {
if (
@ -206,7 +205,7 @@ class PerMessageDeflate {
* @return {Object} Accepted configuration
* @private
*/
acceptAsClient(response) {
acceptAsClient (response) {
const params = response[0];
if (
@ -240,10 +239,10 @@ class PerMessageDeflate {
* @return {Array} The offers/response with normalized parameters
* @private
*/
normalizeParams(configurations) {
normalizeParams (configurations) {
configurations.forEach((params) => {
Object.keys(params).forEach((key) => {
let value = params[key];
var value = params[key];
if (value.length > 1) {
throw new Error(`Parameter "${key}" must have only a single value`);
@ -294,15 +293,15 @@ class PerMessageDeflate {
}
/**
* Decompress data. Concurrency limited.
* Decompress data. Concurrency limited by async-limiter.
*
* @param {Buffer} data Compressed data
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @public
*/
decompress(data, fin, callback) {
zlibLimiter.add((done) => {
decompress (data, fin, callback) {
zlibLimiter.push((done) => {
this._decompress(data, fin, (err, result) => {
done();
callback(err, result);
@ -311,15 +310,15 @@ class PerMessageDeflate {
}
/**
* Compress data. Concurrency limited.
* Compress data. Concurrency limited by async-limiter.
*
* @param {Buffer} data Data to compress
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @public
*/
compress(data, fin, callback) {
zlibLimiter.add((done) => {
compress (data, fin, callback) {
zlibLimiter.push((done) => {
this._compress(data, fin, (err, result) => {
done();
callback(err, result);
@ -335,20 +334,18 @@ class PerMessageDeflate {
* @param {Function} callback Callback
* @private
*/
_decompress(data, fin, callback) {
_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];
const windowBits = typeof this.params[key] !== 'number'
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];
this._inflate = zlib.createInflateRaw({
...this._options.zlibInflateOptions,
windowBits
});
this._inflate = zlib.createInflateRaw(
Object.assign({}, this._options.zlibInflateOptions, { windowBits })
);
this._inflate[kPerMessageDeflate] = this;
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
@ -357,6 +354,7 @@ class PerMessageDeflate {
}
this._inflate[kCallback] = callback;
this._inflate[kWriteInProgress] = true;
this._inflate.write(data);
if (fin) this._inflate.write(TRAILER);
@ -376,16 +374,16 @@ class PerMessageDeflate {
this._inflate[kTotalLength]
);
if (this._inflate._readableState.endEmitted) {
if (
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
this._inflate[kPendingClose]
) {
this._inflate.close();
this._inflate = null;
} else {
this._inflate[kWriteInProgress] = false;
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
this._inflate.reset();
}
}
callback(null, data);
@ -400,63 +398,64 @@ class PerMessageDeflate {
* @param {Function} callback Callback
* @private
*/
_compress(data, fin, callback) {
_compress (data, fin, callback) {
if (!data || data.length === 0) {
process.nextTick(callback, null, EMPTY_BLOCK);
return;
}
const endpoint = this._isServer ? 'server' : 'client';
if (!this._deflate) {
const key = `${endpoint}_max_window_bits`;
const windowBits =
typeof this.params[key] !== 'number'
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];
const windowBits = typeof this.params[key] !== 'number'
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];
this._deflate = zlib.createDeflateRaw({
...this._options.zlibDeflateOptions,
windowBits
});
this._deflate = zlib.createDeflateRaw(
Object.assign(
// TODO deprecate memLevel/level and recommend zlibDeflateOptions instead
{
memLevel: this._options.memLevel,
level: this._options.level
},
this._options.zlibDeflateOptions,
{ windowBits }
)
);
this._deflate[kTotalLength] = 0;
this._deflate[kBuffers] = [];
//
// An `'error'` event is emitted, only on Node.js < 10.0.0, if the
// `zlib.DeflateRaw` instance is closed while data is being processed.
// This can happen if `PerMessageDeflate#cleanup()` is called at the wrong
// time due to an abnormal WebSocket closure.
// `zlib.DeflateRaw` emits an `'error'` event only when an attempt to use
// it is made after it has already been closed. This cannot happen here,
// so we only add a listener for the `'data'` event.
//
this._deflate.on('error', NOOP);
this._deflate.on('data', deflateOnData);
}
this._deflate[kCallback] = callback;
this._deflate[kWriteInProgress] = true;
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(
var data = bufferUtil.concat(
this._deflate[kBuffers],
this._deflate[kTotalLength]
);
if (fin) data = data.slice(0, 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();
if (
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
this._deflate[kPendingClose]
) {
this._deflate.close();
this._deflate = null;
} else {
this._deflate[kWriteInProgress] = false;
this._deflate[kTotalLength] = 0;
this._deflate[kBuffers] = [];
}
callback(null, data);
@ -472,7 +471,7 @@ module.exports = PerMessageDeflate;
* @param {Buffer} chunk A chunk of data
* @private
*/
function deflateOnData(chunk) {
function deflateOnData (chunk) {
this[kBuffers].push(chunk);
this[kTotalLength] += chunk.length;
}
@ -483,7 +482,7 @@ function deflateOnData(chunk) {
* @param {Buffer} chunk A chunk of data
* @private
*/
function inflateOnData(chunk) {
function inflateOnData (chunk) {
this[kTotalLength] += chunk.length;
if (
@ -495,8 +494,7 @@ function inflateOnData(chunk) {
}
this[kError] = new RangeError('Max payload size exceeded');
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
this[kError][kStatusCode] = 1009;
this[kError][constants.kStatusCode] = 1009;
this.removeListener('data', inflateOnData);
this.reset();
}
@ -507,12 +505,12 @@ function inflateOnData(chunk) {
* @param {Error} err The emitted error
* @private
*/
function inflateOnError(err) {
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;
err[constants.kStatusCode] = 1007;
this[kCallback](err);
}

274
node_modules/ws/lib/receiver.js generated vendored
View File

@ -1,16 +1,11 @@
'use strict';
const { Writable } = require('stream');
const stream = 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 bufferUtil = require('./buffer-util');
const validation = require('./validation');
const constants = require('./constants');
const GET_INFO = 0;
const GET_PAYLOAD_LENGTH_16 = 1;
@ -22,25 +17,22 @@ const INFLATING = 5;
/**
* HyBi Receiver implementation.
*
* @extends Writable
* @extends stream.Writable
*/
class Receiver extends Writable {
class Receiver extends stream.Writable {
/**
* Creates a Receiver instance.
*
* @param {String} [binaryType=nodebuffer] The type for binary data
* @param {Object} [extensions] An object containing the negotiated extensions
* @param {Boolean} [isServer=false] Specifies whether to operate in client or
* server mode
* @param {Number} [maxPayload=0] The maximum allowed message length
* @param {String} binaryType The type for binary data
* @param {Object} extensions An object containing the negotiated extensions
* @param {Number} maxPayload The maximum allowed message length
*/
constructor(binaryType, extensions, isServer, maxPayload) {
constructor (binaryType, extensions, maxPayload) {
super();
this._binaryType = binaryType || BINARY_TYPES[0];
this[kWebSocket] = undefined;
this._binaryType = binaryType || constants.BINARY_TYPES[0];
this[constants.kWebSocket] = undefined;
this._extensions = extensions || {};
this._isServer = !!isServer;
this._maxPayload = maxPayload | 0;
this._bufferedBytes = 0;
@ -68,10 +60,9 @@ class Receiver extends Writable {
* @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();
_write (chunk, encoding, cb) {
if (this._opcode === 0x08) return cb();
this._bufferedBytes += chunk.length;
this._buffers.push(chunk);
@ -85,7 +76,7 @@ class Receiver extends Writable {
* @return {Buffer} The consumed bytes
* @private
*/
consume(n) {
consume (n) {
this._bufferedBytes -= n;
if (n === this._buffers[0].length) return this._buffers.shift();
@ -100,12 +91,11 @@ class Receiver extends Writable {
do {
const buf = this._buffers[0];
const offset = dst.length - n;
if (n >= buf.length) {
dst.set(this._buffers.shift(), offset);
this._buffers.shift().copy(dst, dst.length - n);
} else {
dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
buf.copy(dst, dst.length - n, 0, n);
this._buffers[0] = buf.slice(n);
}
@ -121,8 +111,8 @@ class Receiver extends Writable {
* @param {Function} cb Callback
* @private
*/
startLoop(cb) {
let err;
startLoop (cb) {
var err;
this._loop = true;
do {
@ -142,8 +132,7 @@ class Receiver extends Writable {
case GET_DATA:
err = this.getData(cb);
break;
default:
// `INFLATING`
default: // `INFLATING`
this._loop = false;
return;
}
@ -158,7 +147,7 @@ class Receiver extends Writable {
* @return {(RangeError|undefined)} A possible error
* @private
*/
getInfo() {
getInfo () {
if (this._bufferedBytes < 2) {
this._loop = false;
return;
@ -168,26 +157,14 @@ class Receiver extends Writable {
if ((buf[0] & 0x30) !== 0x00) {
this._loop = false;
return error(
RangeError,
'RSV2 and RSV3 must be clear',
true,
1002,
'WS_ERR_UNEXPECTED_RSV_2_3'
);
return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
}
const compressed = (buf[0] & 0x40) === 0x40;
if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
this._loop = false;
return error(
RangeError,
'RSV1 must be clear',
true,
1002,
'WS_ERR_UNEXPECTED_RSV_1'
);
return error(RangeError, 'RSV1 must be clear', true, 1002);
}
this._fin = (buf[0] & 0x80) === 0x80;
@ -197,61 +174,31 @@ class Receiver extends Writable {
if (this._opcode === 0x00) {
if (compressed) {
this._loop = false;
return error(
RangeError,
'RSV1 must be clear',
true,
1002,
'WS_ERR_UNEXPECTED_RSV_1'
);
return error(RangeError, 'RSV1 must be clear', true, 1002);
}
if (!this._fragmented) {
this._loop = false;
return error(
RangeError,
'invalid opcode 0',
true,
1002,
'WS_ERR_INVALID_OPCODE'
);
return error(RangeError, 'invalid opcode 0', true, 1002);
}
this._opcode = this._fragmented;
} else if (this._opcode === 0x01 || this._opcode === 0x02) {
if (this._fragmented) {
this._loop = false;
return error(
RangeError,
`invalid opcode ${this._opcode}`,
true,
1002,
'WS_ERR_INVALID_OPCODE'
);
return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
}
this._compressed = compressed;
} else if (this._opcode > 0x07 && this._opcode < 0x0b) {
if (!this._fin) {
this._loop = false;
return error(
RangeError,
'FIN must be set',
true,
1002,
'WS_ERR_EXPECTED_FIN'
);
return error(RangeError, 'FIN must be set', true, 1002);
}
if (compressed) {
this._loop = false;
return error(
RangeError,
'RSV1 must be clear',
true,
1002,
'WS_ERR_UNEXPECTED_RSV_1'
);
return error(RangeError, 'RSV1 must be clear', true, 1002);
}
if (this._payloadLength > 0x7d) {
@ -260,46 +207,17 @@ class Receiver extends Writable {
RangeError,
`invalid payload length ${this._payloadLength}`,
true,
1002,
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
1002
);
}
} else {
this._loop = false;
return error(
RangeError,
`invalid opcode ${this._opcode}`,
true,
1002,
'WS_ERR_INVALID_OPCODE'
);
return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
}
if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
this._masked = (buf[1] & 0x80) === 0x80;
if (this._isServer) {
if (!this._masked) {
this._loop = false;
return error(
RangeError,
'MASK must be set',
true,
1002,
'WS_ERR_EXPECTED_MASK'
);
}
} else if (this._masked) {
this._loop = false;
return error(
RangeError,
'MASK must be clear',
true,
1002,
'WS_ERR_UNEXPECTED_MASK'
);
}
if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16;
else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
else return this.haveLength();
@ -311,7 +229,7 @@ class Receiver extends Writable {
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength16() {
getPayloadLength16 () {
if (this._bufferedBytes < 2) {
this._loop = false;
return;
@ -327,7 +245,7 @@ class Receiver extends Writable {
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength64() {
getPayloadLength64 () {
if (this._bufferedBytes < 8) {
this._loop = false;
return;
@ -346,8 +264,7 @@ class Receiver extends Writable {
RangeError,
'Unsupported WebSocket frame: payload length > 2^53 - 1',
false,
1009,
'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH'
1009
);
}
@ -361,18 +278,12 @@ class Receiver extends Writable {
* @return {(RangeError|undefined)} A possible error
* @private
*/
haveLength() {
haveLength () {
if (this._payloadLength && this._opcode < 0x08) {
this._totalPayloadLength += this._payloadLength;
if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
this._loop = false;
return error(
RangeError,
'Max payload size exceeded',
false,
1009,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
);
return error(RangeError, 'Max payload size exceeded', false, 1009);
}
}
@ -385,7 +296,7 @@ class Receiver extends Writable {
*
* @private
*/
getMask() {
getMask () {
if (this._bufferedBytes < 4) {
this._loop = false;
return;
@ -402,8 +313,8 @@ class Receiver extends Writable {
* @return {(Error|RangeError|undefined)} A possible error
* @private
*/
getData(cb) {
let data = EMPTY_BUFFER;
getData (cb) {
var data = constants.EMPTY_BUFFER;
if (this._payloadLength) {
if (this._bufferedBytes < this._payloadLength) {
@ -412,7 +323,7 @@ class Receiver extends Writable {
}
data = this.consume(this._payloadLength);
if (this._masked) unmask(data, this._mask);
if (this._masked) bufferUtil.unmask(data, this._mask);
}
if (this._opcode > 0x07) return this.controlMessage(data);
@ -442,7 +353,7 @@ class Receiver extends Writable {
* @param {Function} cb Callback
* @private
*/
decompress(data, cb) {
decompress (data, cb) {
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
perMessageDeflate.decompress(data, this._fin, (err, buf) => {
@ -451,15 +362,7 @@ class Receiver extends Writable {
if (buf.length) {
this._messageLength += buf.length;
if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
return cb(
error(
RangeError,
'Max payload size exceeded',
false,
1009,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
)
);
return cb(error(RangeError, 'Max payload size exceeded', false, 1009));
}
this._fragments.push(buf);
@ -478,7 +381,7 @@ class Receiver extends Writable {
* @return {(Error|undefined)} A possible error
* @private
*/
dataMessage() {
dataMessage () {
if (this._fin) {
const messageLength = this._messageLength;
const fragments = this._fragments;
@ -489,29 +392,23 @@ class Receiver extends Writable {
this._fragments = [];
if (this._opcode === 2) {
let data;
var data;
if (this._binaryType === 'nodebuffer') {
data = concat(fragments, messageLength);
data = toBuffer(fragments, messageLength);
} else if (this._binaryType === 'arraybuffer') {
data = toArrayBuffer(concat(fragments, messageLength));
data = toArrayBuffer(toBuffer(fragments, messageLength));
} else {
data = fragments;
}
this.emit('message', data);
} else {
const buf = concat(fragments, messageLength);
const buf = toBuffer(fragments, messageLength);
if (!isValidUTF8(buf)) {
if (!validation.isValidUTF8(buf)) {
this._loop = false;
return error(
Error,
'invalid UTF-8 sequence',
true,
1007,
'WS_ERR_INVALID_UTF8'
);
return error(Error, 'invalid UTF-8 sequence', true, 1007);
}
this.emit('message', buf.toString());
@ -528,7 +425,7 @@ class Receiver extends Writable {
* @return {(Error|RangeError|undefined)} A possible error
* @private
*/
controlMessage(data) {
controlMessage (data) {
if (this._opcode === 0x08) {
this._loop = false;
@ -536,47 +433,30 @@ class Receiver extends Writable {
this.emit('conclude', 1005, '');
this.end();
} else if (data.length === 1) {
return error(
RangeError,
'invalid payload length 1',
true,
1002,
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
);
return error(RangeError, 'invalid payload length 1', true, 1002);
} else {
const code = data.readUInt16BE(0);
if (!isValidStatusCode(code)) {
return error(
RangeError,
`invalid status code ${code}`,
true,
1002,
'WS_ERR_INVALID_CLOSE_CODE'
);
if (!validation.isValidStatusCode(code)) {
return error(RangeError, `invalid status code ${code}`, true, 1002);
}
const buf = data.slice(2);
if (!isValidUTF8(buf)) {
return error(
Error,
'invalid UTF-8 sequence',
true,
1007,
'WS_ERR_INVALID_UTF8'
);
if (!validation.isValidUTF8(buf)) {
return error(Error, 'invalid UTF-8 sequence', true, 1007);
}
this.emit('conclude', code, buf.toString());
this.end();
}
} else if (this._opcode === 0x09) {
this.emit('ping', data);
} else {
this.emit('pong', data);
return;
}
if (this._opcode === 0x09) this.emit('ping', data);
else this.emit('pong', data);
this._state = GET_INFO;
}
}
@ -586,22 +466,48 @@ module.exports = Receiver;
/**
* Builds an error object.
*
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
* @param {(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
*/
function error(ErrorCtor, message, prefix, statusCode, errorCode) {
function error (ErrorCtor, message, prefix, statusCode) {
const err = new ErrorCtor(
prefix ? `Invalid WebSocket frame: ${message}` : message
);
Error.captureStackTrace(err, error);
err.code = errorCode;
err[kStatusCode] = statusCode;
err[constants.kStatusCode] = statusCode;
return err;
}
/**
* Makes a buffer from a list of fragments.
*
* @param {Buffer[]} fragments The list of fragments composing the message
* @param {Number} messageLength The length of the message
* @return {Buffer}
* @private
*/
function toBuffer (fragments, messageLength) {
if (fragments.length === 1) return fragments[0];
if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
return constants.EMPTY_BUFFER;
}
/**
* Converts a buffer to an `ArrayBuffer`.
*
* @param {Buffer} The buffer to convert
* @return {ArrayBuffer} Converted buffer
*/
function toArrayBuffer (buf) {
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
return buf.buffer;
}
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}

334
node_modules/ws/lib/sender.js generated vendored
View File

@ -1,17 +1,11 @@
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
'use strict';
const net = require('net');
const tls = require('tls');
const { randomFillSync } = require('crypto');
const crypto = require('crypto');
const PerMessageDeflate = require('./permessage-deflate');
const { EMPTY_BUFFER } = require('./constants');
const { isValidStatusCode } = require('./validation');
const { mask: applyMask, toBuffer } = require('./buffer-util');
const mask = Buffer.alloc(4);
const bufferUtil = require('./buffer-util');
const validation = require('./validation');
const constants = require('./constants');
/**
* HyBi Sender implementation.
@ -20,10 +14,10 @@ class Sender {
/**
* Creates a Sender instance.
*
* @param {(net.Socket|tls.Socket)} socket The connection socket
* @param {Object} [extensions] An object containing the negotiated extensions
* @param {net.Socket} socket The connection socket
* @param {Object} extensions An object containing the negotiated extensions
*/
constructor(socket, extensions) {
constructor (socket, extensions) {
this._extensions = extensions || {};
this._socket = socket;
@ -41,21 +35,17 @@ class Sender {
* @param {Buffer} data The data to frame
* @param {Object} options Options object
* @param {Number} options.opcode The opcode
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
* modified
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
* FIN bit
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
* `data`
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
* RSV1 bit
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
* @return {Buffer[]} The framed data as a list of `Buffer` instances
* @public
*/
static frame(data, options) {
const merge = options.mask && options.readOnly;
let offset = options.mask ? 6 : 2;
let payloadLength = data.length;
static frame (data, options) {
const merge = data.length < 1024 || (options.mask && options.readOnly);
var offset = options.mask ? 6 : 2;
var payloadLength = data.length;
if (data.length >= 65536) {
offset += 8;
@ -70,8 +60,6 @@ class Sender {
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
if (options.rsv1) target[0] |= 0x40;
target[1] = payloadLength;
if (payloadLength === 126) {
target.writeUInt16BE(data.length, 2);
} else if (payloadLength === 127) {
@ -79,52 +67,54 @@ class Sender {
target.writeUInt32BE(data.length, 6);
}
if (!options.mask) return [target, data];
if (!options.mask) {
target[1] = payloadLength;
if (merge) {
data.copy(target, offset);
return [target];
}
randomFillSync(mask, 0, 4);
return [target, data];
}
target[1] |= 0x80;
const mask = crypto.randomBytes(4);
target[1] = payloadLength | 0x80;
target[offset - 4] = mask[0];
target[offset - 3] = mask[1];
target[offset - 2] = mask[2];
target[offset - 1] = mask[3];
if (merge) {
applyMask(data, mask, target, offset, data.length);
bufferUtil.mask(data, mask, target, offset, data.length);
return [target];
}
applyMask(data, mask, data, 0, data.length);
bufferUtil.mask(data, mask, data, 0, data.length);
return [target, data];
}
/**
* Sends a close message to the other peer.
*
* @param {Number} [code] The status code component of the body
* @param {String} [data] The message component of the body
* @param {Boolean} [mask=false] Specifies whether or not to mask the message
* @param {Function} [cb] Callback
* @param {(Number|undefined)} code The status code component of the body
* @param {String} data The message component of the body
* @param {Boolean} mask Specifies whether or not to mask the message
* @param {Function} cb Callback
* @public
*/
close(code, data, mask, cb) {
let buf;
close (code, data, mask, cb) {
var buf;
if (code === undefined) {
buf = EMPTY_BUFFER;
} else if (typeof code !== 'number' || !isValidStatusCode(code)) {
buf = constants.EMPTY_BUFFER;
} else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) {
throw new TypeError('First argument must be a valid error code number');
} else if (data === undefined || data === '') {
buf = Buffer.allocUnsafe(2);
buf.writeUInt16BE(code, 0);
} 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 = Buffer.allocUnsafe(2 + Buffer.byteLength(data));
buf.writeUInt16BE(code, 0);
buf.write(data, 2);
}
@ -140,109 +130,114 @@ class Sender {
* Frames and sends a close message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @private
*/
doClose(data, mask, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x08,
mask,
readOnly: false
}),
cb
);
doClose (data, mask, cb) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x08,
mask,
readOnly: false
}), cb);
}
/**
* Sends a ping message to the other peer.
*
* @param {*} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @public
*/
ping(data, mask, cb) {
const buf = toBuffer(data);
ping (data, mask, cb) {
var readOnly = true;
if (buf.length > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
if (this._deflating) {
this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.doPing, data, mask, readOnly, cb]);
} else {
this.doPing(buf, mask, toBuffer.readOnly, cb);
this.doPing(data, mask, readOnly, cb);
}
}
/**
* Frames and sends a ping message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Boolean} readOnly Specifies whether `data` can be modified
* @param {Function} cb Callback
* @private
*/
doPing(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09,
mask,
readOnly
}),
cb
);
doPing (data, mask, readOnly, cb) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09,
mask,
readOnly
}), cb);
}
/**
* Sends a pong message to the other peer.
*
* @param {*} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @public
*/
pong(data, mask, cb) {
const buf = toBuffer(data);
pong (data, mask, cb) {
var readOnly = true;
if (buf.length > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
if (this._deflating) {
this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.doPong, data, mask, readOnly, cb]);
} else {
this.doPong(buf, mask, toBuffer.readOnly, cb);
this.doPong(data, mask, readOnly, cb);
}
}
/**
* Frames and sends a pong message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Boolean} readOnly Specifies whether `data` can be modified
* @param {Function} cb Callback
* @private
*/
doPong(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a,
mask,
readOnly
}),
cb
);
doPong (data, mask, readOnly, cb) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a,
mask,
readOnly
}), cb);
}
/**
@ -250,27 +245,35 @@ class Sender {
*
* @param {*} data The message to send
* @param {Object} options Options object
* @param {Boolean} [options.compress=false] Specifies whether or not to
* compress `data`
* @param {Boolean} [options.binary=false] Specifies whether `data` is binary
* or text
* @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
* @param {Boolean} options.compress Specifies whether or not to compress `data`
* @param {Boolean} options.binary Specifies whether `data` is binary or text
* @param {Boolean} options.fin Specifies whether the fragment is the last one
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @public
*/
send(data, options, cb) {
const buf = toBuffer(data);
send (data, options, cb) {
var opcode = options.binary ? 2 : 1;
var rsv1 = options.compress;
var readOnly = true;
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
let opcode = options.binary ? 2 : 1;
let rsv1 = options.compress;
if (this._firstFragment) {
this._firstFragment = false;
if (rsv1 && perMessageDeflate) {
rsv1 = buf.length >= perMessageDeflate._threshold;
rsv1 = data.length >= perMessageDeflate._threshold;
}
this._compress = rsv1;
} else {
@ -286,25 +289,22 @@ class Sender {
rsv1,
opcode,
mask: options.mask,
readOnly: toBuffer.readOnly
readOnly
};
if (this._deflating) {
this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
this.enqueue([this.dispatch, data, this._compress, opts, cb]);
} else {
this.dispatch(buf, this._compress, opts, cb);
this.dispatch(data, this._compress, opts, cb);
}
} else {
this.sendFrame(
Sender.frame(buf, {
fin: options.fin,
rsv1: false,
opcode,
mask: options.mask,
readOnly: toBuffer.readOnly
}),
cb
);
this.sendFrame(Sender.frame(data, {
fin: options.fin,
rsv1: false,
opcode,
mask: options.mask,
readOnly
}), cb);
}
}
@ -312,22 +312,17 @@ class Sender {
* Dispatches a data message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [compress=false] Specifies whether or not to compress
* `data`
* @param {Boolean} compress Specifies whether or not to compress `data`
* @param {Object} options Options object
* @param {Number} options.opcode The opcode
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
* modified
* @param {Boolean} [options.fin=false] Specifies whether or not to set the
* FIN bit
* @param {Boolean} [options.mask=false] Specifies whether or not to mask
* `data`
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
* RSV1 bit
* @param {Function} [cb] Callback
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
* @param {Function} cb Callback
* @private
*/
dispatch(data, compress, options, cb) {
dispatch (data, compress, options, cb) {
if (!compress) {
this.sendFrame(Sender.frame(data, options), cb);
return;
@ -335,29 +330,11 @@ class Sender {
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
this._bufferedBytes += data.length;
this._deflating = true;
perMessageDeflate.compress(data, options.fin, (_, buf) => {
if (this._socket.destroyed) {
const err = new Error(
'The socket was closed while data was being compressed'
);
if (typeof cb === 'function') cb(err);
for (let i = 0; i < this._queue.length; i++) {
const callback = this._queue[i][4];
if (typeof callback === 'function') callback(err);
}
return;
}
this._bufferedBytes -= data.length;
this._deflating = false;
options.readOnly = false;
this.sendFrame(Sender.frame(buf, options), cb);
this._deflating = false;
this.dequeue();
});
}
@ -367,12 +344,12 @@ class Sender {
*
* @private
*/
dequeue() {
dequeue () {
while (!this._deflating && this._queue.length) {
const params = this._queue.shift();
this._bufferedBytes -= params[1].length;
Reflect.apply(params[0], this, params.slice(1));
params[0].apply(this, params.slice(1));
}
}
@ -382,7 +359,7 @@ class Sender {
* @param {Array} params Send operation parameters.
* @private
*/
enqueue(params) {
enqueue (params) {
this._bufferedBytes += params[1].length;
this._queue.push(params);
}
@ -391,15 +368,13 @@ class Sender {
* Sends a frame.
*
* @param {Buffer[]} list The frame to send
* @param {Function} [cb] Callback
* @param {Function} cb Callback
* @private
*/
sendFrame(list, cb) {
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);
}
@ -407,3 +382,20 @@ class Sender {
}
module.exports = Sender;
/**
* Converts an `ArrayBuffer` view into a buffer.
*
* @param {(DataView|TypedArray)} view The view to convert
* @return {Buffer} Converted view
* @private
*/
function viewToBuffer (view) {
const buf = Buffer.from(view.buffer);
if (view.byteLength !== view.buffer.byteLength) {
return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
}
return buf;
}

180
node_modules/ws/lib/stream.js generated vendored
View File

@ -1,180 +0,0 @@
'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 resumeOnReceiverDrain = true;
let terminateOnDestroy = true;
function receiverOnDrain() {
if (resumeOnReceiverDrain) ws._socket.resume();
}
if (ws.readyState === ws.CONNECTING) {
ws.once('open', function open() {
ws._receiver.removeAllListeners('drain');
ws._receiver.on('drain', receiverOnDrain);
});
} else {
ws._receiver.removeAllListeners('drain');
ws._receiver.on('drain', receiverOnDrain);
}
const duplex = new Duplex({
...options,
autoDestroy: false,
emitClose: false,
objectMode: false,
writableObjectMode: false
});
ws.on('message', function message(msg) {
if (!duplex.push(msg)) {
resumeOnReceiverDrain = false;
ws._socket.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.readyState === ws.OPEN || ws.readyState === ws.CLOSING) &&
!resumeOnReceiverDrain
) {
resumeOnReceiverDrain = true;
if (!ws._receiver._writableState.needDrain) ws._socket.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;

101
node_modules/ws/lib/validation.js generated vendored
View File

@ -1,5 +1,15 @@
'use strict';
try {
const isValidUTF8 = require('utf-8-validate');
exports.isValidUTF8 = typeof isValidUTF8 === 'object'
? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
: isValidUTF8;
} catch (e) /* istanbul ignore next */ {
exports.isValidUTF8 = () => true;
}
/**
* Checks if a status code is allowed in a close frame.
*
@ -7,98 +17,13 @@
* @return {Boolean} `true` if the status code is valid, else `false`
* @public
*/
function isValidStatusCode(code) {
exports.isValidStatusCode = (code) => {
return (
(code >= 1000 &&
code <= 1014 &&
code <= 1013 &&
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;
}
try {
let isValidUTF8 = require('utf-8-validate');
/* istanbul ignore if */
if (typeof isValidUTF8 === 'object') {
isValidUTF8 = isValidUTF8.Validation.isValidUTF8; // utf-8-validate@<3.0.0
}
module.exports = {
isValidStatusCode,
isValidUTF8(buf) {
return buf.length < 150 ? _isValidUTF8(buf) : isValidUTF8(buf);
}
};
} catch (e) /* istanbul ignore next */ {
module.exports = {
isValidStatusCode,
isValidUTF8: _isValidUTF8
};
}
};

View File

@ -1,24 +1,14 @@
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
'use strict';
const EventEmitter = require('events');
const crypto = require('crypto');
const http = require('http');
const https = require('https');
const net = require('net');
const tls = require('tls');
const { createHash } = require('crypto');
const url = require('url');
const PerMessageDeflate = require('./permessage-deflate');
const extension = require('./extension');
const constants = require('./constants');
const WebSocket = require('./websocket');
const { format, parse } = require('./extension');
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.
@ -30,28 +20,22 @@ class WebSocketServer extends EventEmitter {
* Create a `WebSocketServer` instance.
*
* @param {Object} options Configuration options
* @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 {Function} [options.verifyClient] A hook to reject connections
* @param {Function} [callback] A listener for the `listening` event
* @param {String} options.host The hostname where to bind the server
* @param {Number} options.port The port where to bind the server
* @param {http.Server} options.server A pre-created HTTP/S server to use
* @param {Function} options.verifyClient An hook to reject connections
* @param {Function} options.handleProtocols An hook to handle protocols
* @param {String} options.path Accept only connections matching this path
* @param {Boolean} options.noServer Enable no server mode
* @param {Boolean} options.clientTracking Specifies whether or not to track clients
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
* @param {Number} options.maxPayload The maximum allowed message size
* @param {Function} callback A listener for the `listening` event
*/
constructor(options, callback) {
constructor (options, callback) {
super();
options = {
options = Object.assign({
maxPayload: 100 * 1024 * 1024,
perMessageDeflate: false,
handleProtocols: null,
@ -62,18 +46,12 @@ class WebSocketServer extends EventEmitter {
server: null,
host: null,
path: null,
port: null,
...options
};
port: null
}, options);
if (
(options.port == null && !options.server && !options.noServer) ||
(options.port != null && (options.server || options.noServer)) ||
(options.server && options.noServer)
) {
if (options.port == null && !options.server && !options.noServer) {
throw new TypeError(
'One and only one of the "port", "server", or "noServer" options ' +
'must be specified'
'One of the "port", "server", or "noServer" options must be specified'
);
}
@ -87,24 +65,19 @@ class WebSocketServer extends EventEmitter {
});
res.end(body);
});
this._server.listen(
options.port,
options.host,
options.backlog,
callback
);
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);
this.handleUpgrade(req, socket, head, (ws) => {
this.emit('connection', ws, req);
});
}
});
}
@ -112,7 +85,6 @@ class WebSocketServer extends EventEmitter {
if (options.perMessageDeflate === true) options.perMessageDeflate = {};
if (options.clientTracking) this.clients = new Set();
this.options = options;
this._state = RUNNING;
}
/**
@ -124,7 +96,7 @@ class WebSocketServer extends EventEmitter {
* @return {(Object|String|null)} The address of the server
* @public
*/
address() {
address () {
if (this.options.noServer) {
throw new Error('The server is operating in "noServer" mode');
}
@ -136,20 +108,10 @@ class WebSocketServer extends EventEmitter {
/**
* Close the server.
*
* @param {Function} [cb] Callback
* @param {Function} cb Callback
* @public
*/
close(cb) {
if (cb) this.once('close', cb);
if (this._state === CLOSED) {
process.nextTick(emitClose, this);
return;
}
if (this._state === CLOSING) return;
this._state = CLOSING;
close (cb) {
//
// Terminate all associated clients.
//
@ -166,13 +128,10 @@ class WebSocketServer extends EventEmitter {
//
// Close the http server if it was internally created.
//
if (this.options.port != null) {
server.close(emitClose.bind(undefined, this));
return;
}
if (this.options.port != null) return server.close(cb);
}
process.nextTick(emitClose, this);
if (cb) cb();
}
/**
@ -182,12 +141,9 @@ class WebSocketServer extends EventEmitter {
* @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;
shouldHandle (req) {
if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
return false;
}
return true;
@ -197,30 +153,22 @@ class WebSocketServer extends EventEmitter {
* Handle a HTTP Upgrade request.
*
* @param {http.IncomingMessage} req The request object
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Function} cb Callback
* @public
*/
handleUpgrade(req, socket, head, cb) {
handleUpgrade (req, socket, head, cb) {
socket.on('error', socketOnError);
const key =
req.headers['sec-websocket-key'] !== undefined
? req.headers['sec-websocket-key'].trim()
: false;
const upgrade = req.headers.upgrade;
const version = +req.headers['sec-websocket-version'];
const extensions = {};
if (
req.method !== 'GET' ||
upgrade === undefined ||
req.method !== 'GET' || upgrade === undefined ||
upgrade.toLowerCase() !== 'websocket' ||
!key ||
!keyRegex.test(key) ||
(version !== 8 && version !== 13) ||
!req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
!this.shouldHandle(req)
) {
return abortHandshake(socket, 400);
@ -234,7 +182,9 @@ class WebSocketServer extends EventEmitter {
);
try {
const offers = parse(req.headers['sec-websocket-extensions']);
const offers = extension.parse(
req.headers['sec-websocket-extensions']
);
if (offers[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
@ -250,9 +200,8 @@ class WebSocketServer extends EventEmitter {
//
if (this.options.verifyClient) {
const info = {
origin:
req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
secure: !!(req.socket.authorized || req.socket.encrypted),
origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
secure: !!(req.connection.authorized || req.connection.encrypted),
req
};
@ -262,7 +211,7 @@ class WebSocketServer extends EventEmitter {
return abortHandshake(socket, code || 401, message, headers);
}
this.completeUpgrade(key, extensions, req, socket, head, cb);
this.completeUpgrade(extensions, req, socket, head, cb);
});
return;
}
@ -270,50 +219,38 @@ class WebSocketServer extends EventEmitter {
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
}
this.completeUpgrade(key, extensions, req, socket, head, cb);
this.completeUpgrade(extensions, req, socket, head, cb);
}
/**
* Upgrade the connection to WebSocket.
*
* @param {String} key The value of the `Sec-WebSocket-Key` header
* @param {Object} extensions The accepted extensions
* @param {http.IncomingMessage} req The request object
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Function} cb Callback
* @throws {Error} If called more than once with the same socket
* @private
*/
completeUpgrade(key, extensions, req, socket, head, cb) {
completeUpgrade (extensions, req, socket, head, cb) {
//
// Destroy the socket if the client has already sent a FIN packet.
//
if (!socket.readable || !socket.writable) return socket.destroy();
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)
const key = crypto.createHash('sha1')
.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
.digest('base64');
const headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${digest}`
`Sec-WebSocket-Accept: ${key}`
];
const ws = new WebSocket(null);
let protocol = req.headers['sec-websocket-protocol'];
var protocol = req.headers['sec-websocket-protocol'];
if (protocol) {
protocol = protocol.split(',').map(trim);
@ -329,13 +266,13 @@ class WebSocketServer extends EventEmitter {
if (protocol) {
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
ws._protocol = protocol;
ws.protocol = protocol;
}
}
if (extensions[PerMessageDeflate.extensionName]) {
const params = extensions[PerMessageDeflate.extensionName].params;
const value = format({
const value = extension.format({
[PerMessageDeflate.extensionName]: [params]
});
headers.push(`Sec-WebSocket-Extensions: ${value}`);
@ -357,7 +294,7 @@ class WebSocketServer extends EventEmitter {
ws.on('close', () => this.clients.delete(ws));
}
cb(ws, req);
cb(ws);
}
}
@ -369,66 +306,51 @@ module.exports = WebSocketServer;
*
* @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
* @return {Function} A function that will remove the added listeners when called
* @private
*/
function addListeners(server, map) {
function addListeners (server, map) {
for (const event of Object.keys(map)) server.on(event, map[event]);
return function removeListeners() {
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 premature socket errors.
*
* @private
*/
function socketOnError() {
function socketOnError () {
this.destroy();
}
/**
* Close the connection when preconditions are not fulfilled.
*
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
* @param {net.Socket} socket The socket of the upgrade request
* @param {Number} code The HTTP response status code
* @param {String} [message] The HTTP response body
* @param {Object} [headers] Additional HTTP response headers
* @private
*/
function abortHandshake(socket, code, message, headers) {
function abortHandshake (socket, code, message, headers) {
if (socket.writable) {
message = message || http.STATUS_CODES[code];
headers = {
Connection: 'close',
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(message),
...headers
};
headers = Object.assign({
'Connection': 'close',
'Content-type': 'text/html',
'Content-Length': Buffer.byteLength(message)
}, headers);
socket.write(
`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
Object.keys(headers).map(h => `${h}: ${headers[h]}`).join('\r\n') +
'\r\n\r\n' +
message
);
}
@ -444,6 +366,6 @@ function abortHandshake(socket, code, message, headers) {
* characters from both its beginning and end
* @private
*/
function trim(str) {
function trim (str) {
return str.trim();
}

887
node_modules/ws/lib/websocket.js generated vendored

File diff suppressed because it is too large Load Diff

48
node_modules/ws/package.json generated vendored
View File

@ -1,6 +1,6 @@
{
"name": "ws",
"version": "7.5.10",
"version": "5.2.4",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
"keywords": [
"HyBi",
@ -16,41 +16,29 @@
"author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
"license": "MIT",
"main": "index.js",
"browser": "browser.js",
"engines": {
"node": ">=8.3.0"
},
"files": [
"browser.js",
"index.js",
"lib/*.js"
"lib"
],
"scripts": {
"test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
"integration": "mocha --throw-deprecation test/*.integration.js",
"lint": "eslint --ignore-path .gitignore . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
"test": "eslint . && nyc --reporter=html --reporter=text mocha test/*.test.js",
"integration": "eslint . && mocha test/*.integration.js",
"lint": "eslint ."
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
"dependencies": {
"async-limiter": "~1.0.0"
},
"devDependencies": {
"benchmark": "^2.1.4",
"bufferutil": "^4.0.1",
"eslint": "^7.2.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^4.0.0",
"mocha": "^7.0.0",
"nyc": "^15.0.0",
"prettier": "^2.0.5",
"utf-8-validate": "^5.0.2"
"benchmark": "~2.1.2",
"bufferutil": "~3.0.0",
"eslint": "~4.19.0",
"eslint-config-standard": "~11.0.0",
"eslint-plugin-import": "~2.12.0",
"eslint-plugin-node": "~6.0.0",
"eslint-plugin-promise": "~3.8.0",
"eslint-plugin-standard": "~3.0.0",
"mocha": "~5.2.0",
"nyc": "~12.0.2",
"utf-8-validate": "~4.0.0"
}
}