Initial commit

This commit is contained in:
2025-12-27 20:24:47 +01:00
commit 7b6f164130
5573 changed files with 727178 additions and 0 deletions

71
node_modules/ws/lib/BufferUtil.js generated vendored Normal file
View File

@@ -0,0 +1,71 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
const safeBuffer = require('safe-buffer');
const Buffer = safeBuffer.Buffer;
/**
* Merges an array of buffers into a new buffer.
*
* @param {Buffer[]} list The array of buffers to concat
* @param {Number} totalLength The total length of buffers in the list
* @return {Buffer} The resulting buffer
* @public
*/
const concat = (list, totalLength) => {
const target = Buffer.allocUnsafe(totalLength);
var offset = 0;
for (var i = 0; i < list.length; i++) {
const buf = list[i];
buf.copy(target, offset);
offset += buf.length;
}
return target;
};
try {
const bufferUtil = require('bufferutil');
module.exports = Object.assign({ concat }, bufferUtil.BufferUtil || bufferUtil);
} catch (e) /* istanbul ignore next */ {
/**
* Masks a buffer using the given mask.
*
* @param {Buffer} source The buffer to mask
* @param {Buffer} mask The mask to use
* @param {Buffer} output The buffer where to store the result
* @param {Number} offset The offset at which to start writing
* @param {Number} length The number of bytes to mask.
* @public
*/
const mask = (source, mask, output, offset, length) => {
for (var i = 0; i < length; i++) {
output[offset + i] = source[i] ^ mask[i & 3];
}
};
/**
* Unmasks a buffer using the given mask.
*
* @param {Buffer} buffer The buffer to unmask
* @param {Buffer} mask The mask to use
* @public
*/
const unmask = (buffer, mask) => {
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
const length = buffer.length;
for (var i = 0; i < length; i++) {
buffer[i] ^= mask[i & 3];
}
};
module.exports = { concat, mask, unmask };
}

10
node_modules/ws/lib/Constants.js generated vendored Normal file
View File

@@ -0,0 +1,10 @@
'use strict';
const safeBuffer = require('safe-buffer');
const Buffer = safeBuffer.Buffer;
exports.BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
exports.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
exports.EMPTY_BUFFER = Buffer.alloc(0);
exports.NOOP = () => {};

28
node_modules/ws/lib/ErrorCodes.js generated vendored Normal file
View File

@@ -0,0 +1,28 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
module.exports = {
isValidErrorCode: function (code) {
return (code >= 1000 && code <= 1013 && code !== 1004 && code !== 1005 && code !== 1006) ||
(code >= 3000 && code <= 4999);
},
1000: 'normal',
1001: 'going away',
1002: 'protocol error',
1003: 'unsupported data',
1004: 'reserved',
1005: 'reserved for extensions',
1006: 'reserved for extensions',
1007: 'inconsistent or invalid data',
1008: 'policy violation',
1009: 'message too big',
1010: 'extension handshake missing',
1011: 'an unexpected condition prevented the request from being fulfilled',
1012: 'service restart',
1013: 'try again later'
};

155
node_modules/ws/lib/EventTarget.js generated vendored Normal file
View File

@@ -0,0 +1,155 @@
'use strict';
/**
* Class representing an event.
*
* @private
*/
class Event {
/**
* Create a new `Event`.
*
* @param {String} type The name of the event
* @param {Object} target A reference to the target to which the event was dispatched
*/
constructor (type, target) {
this.target = target;
this.type = type;
}
}
/**
* Class representing a message event.
*
* @extends Event
* @private
*/
class MessageEvent extends Event {
/**
* Create a new `MessageEvent`.
*
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data
* @param {Boolean} isBinary Specifies if `data` is binary
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor (data, isBinary, target) {
super('message', target);
this.binary = isBinary; // non-standard.
this.data = data;
}
}
/**
* Class representing a close event.
*
* @extends Event
* @private
*/
class CloseEvent extends Event {
/**
* Create a new `CloseEvent`.
*
* @param {Number} code The status code explaining why the connection is being closed
* @param {String} reason A human-readable string explaining why the connection is closing
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor (code, reason, target) {
super('close', target);
this.wasClean = code === undefined || code === 1000;
this.reason = reason;
this.target = target;
this.type = 'close';
this.code = code;
}
}
/**
* Class representing an open event.
*
* @extends Event
* @private
*/
class OpenEvent extends Event {
/**
* Create a new `OpenEvent`.
*
* @param {WebSocket} target A reference to the target to which the event was dispatched
*/
constructor (target) {
super('open', target);
}
}
/**
* This provides methods for emulating the `EventTarget` interface. It's not
* meant to be used directly.
*
* @mixin
*/
const EventTarget = {
/**
* Register an event listener.
*
* @param {String} method A string representing the event type to listen for
* @param {Function} listener The listener to add
* @public
*/
addEventListener (method, listener) {
if (typeof listener !== 'function') return;
function onMessage (data, flags) {
listener.call(this, new MessageEvent(data, !!flags.binary, this));
}
function onClose (code, message) {
listener.call(this, new CloseEvent(code, message, this));
}
function onError (event) {
event.type = 'error';
event.target = this;
listener.call(this, event);
}
function onOpen () {
listener.call(this, new OpenEvent(this));
}
if (method === 'message') {
onMessage._listener = listener;
this.on(method, onMessage);
} else if (method === 'close') {
onClose._listener = listener;
this.on(method, onClose);
} else if (method === 'error') {
onError._listener = listener;
this.on(method, onError);
} else if (method === 'open') {
onOpen._listener = listener;
this.on(method, onOpen);
} else {
this.on(method, listener);
}
},
/**
* Remove an event listener.
*
* @param {String} method A string representing the event type to remove
* @param {Function} listener The listener to remove
* @public
*/
removeEventListener (method, listener) {
const listeners = this.listeners(method);
for (var i = 0; i < listeners.length; i++) {
if (listeners[i] === listener || listeners[i]._listener === listener) {
this.removeListener(method, listeners[i]);
}
}
}
};
module.exports = EventTarget;

67
node_modules/ws/lib/Extensions.js generated vendored Normal file
View File

@@ -0,0 +1,67 @@
'use strict';
/**
* Parse the `Sec-WebSocket-Extensions` header into an object.
*
* @param {String} value field value of the header
* @return {Object} The parsed object
* @public
*/
const parse = (value) => {
value = value || '';
const extensions = {};
value.split(',').forEach((v) => {
const params = v.split(';');
const token = params.shift().trim();
const paramsList = extensions[token] = extensions[token] || [];
const parsedParams = {};
params.forEach((param) => {
const parts = param.trim().split('=');
const key = parts[0];
var value = parts[1];
if (value === undefined) {
value = true;
} else {
// unquote value
if (value[0] === '"') {
value = value.slice(1);
}
if (value[value.length - 1] === '"') {
value = value.slice(0, value.length - 1);
}
}
(parsedParams[key] = parsedParams[key] || []).push(value);
});
paramsList.push(parsedParams);
});
return extensions;
};
/**
* Serialize a parsed `Sec-WebSocket-Extensions` header to a string.
*
* @param {Object} value The object to format
* @return {String} A string representing the given value
* @public
*/
const format = (value) => {
return Object.keys(value).map((token) => {
var paramsList = value[token];
if (!Array.isArray(paramsList)) paramsList = [paramsList];
return paramsList.map((params) => {
return [token].concat(Object.keys(params).map((k) => {
var p = params[k];
if (!Array.isArray(p)) p = [p];
return p.map((v) => v === true ? k : `${k}=${v}`).join('; ');
})).join('; ');
}).join(', ');
}).join(', ');
};
module.exports = { format, parse };

384
node_modules/ws/lib/PerMessageDeflate.js generated vendored Normal file
View File

@@ -0,0 +1,384 @@
'use strict';
const safeBuffer = require('safe-buffer');
const zlib = require('zlib');
const bufferUtil = require('./BufferUtil');
const Buffer = safeBuffer.Buffer;
const AVAILABLE_WINDOW_BITS = [8, 9, 10, 11, 12, 13, 14, 15];
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
const EMPTY_BLOCK = Buffer.from([0x00]);
const DEFAULT_WINDOW_BITS = 15;
const DEFAULT_MEM_LEVEL = 8;
/**
* Per-message Deflate implementation.
*/
class PerMessageDeflate {
constructor (options, isServer, maxPayload) {
this._options = options || {};
this._isServer = !!isServer;
this._inflate = null;
this._deflate = null;
this.params = null;
this._maxPayload = maxPayload || 0;
this.threshold = this._options.threshold === undefined ? 1024 : this._options.threshold;
}
static get extensionName () {
return 'permessage-deflate';
}
/**
* Create extension parameters 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 extension offer.
*
* @param {Array} paramsList Extension parameters
* @return {Object} Accepted configuration
* @public
*/
accept (paramsList) {
paramsList = this.normalizeParams(paramsList);
var params;
if (this._isServer) {
params = this.acceptAsServer(paramsList);
} else {
params = this.acceptAsClient(paramsList);
}
this.params = params;
return params;
}
/**
* Releases all resources used by the extension.
*
* @public
*/
cleanup () {
if (this._inflate) {
if (this._inflate.writeInProgress) {
this._inflate.pendingClose = true;
} else {
this._inflate.close();
this._inflate = null;
}
}
if (this._deflate) {
if (this._deflate.writeInProgress) {
this._deflate.pendingClose = true;
} else {
this._deflate.close();
this._deflate = null;
}
}
}
/**
* Accept extension offer from client.
*
* @param {Array} paramsList Extension parameters
* @return {Object} Accepted configuration
* @private
*/
acceptAsServer (paramsList) {
const accepted = {};
const result = paramsList.some((params) => {
if ((
this._options.serverNoContextTakeover === false &&
params.server_no_context_takeover
) || (
this._options.serverMaxWindowBits === false &&
params.server_max_window_bits
) || (
typeof this._options.serverMaxWindowBits === 'number' &&
typeof params.server_max_window_bits === 'number' &&
this._options.serverMaxWindowBits > params.server_max_window_bits
) || (
typeof this._options.clientMaxWindowBits === 'number' &&
!params.client_max_window_bits
)) {
return;
}
if (
this._options.serverNoContextTakeover ||
params.server_no_context_takeover
) {
accepted.server_no_context_takeover = true;
}
if (this._options.clientNoContextTakeover) {
accepted.client_no_context_takeover = true;
}
if (
this._options.clientNoContextTakeover !== false &&
params.client_no_context_takeover
) {
accepted.client_no_context_takeover = true;
}
if (typeof this._options.serverMaxWindowBits === 'number') {
accepted.server_max_window_bits = this._options.serverMaxWindowBits;
} else if (typeof params.server_max_window_bits === 'number') {
accepted.server_max_window_bits = params.server_max_window_bits;
}
if (typeof this._options.clientMaxWindowBits === 'number') {
accepted.client_max_window_bits = this._options.clientMaxWindowBits;
} else if (
this._options.clientMaxWindowBits !== false &&
typeof params.client_max_window_bits === 'number'
) {
accepted.client_max_window_bits = params.client_max_window_bits;
}
return true;
});
if (!result) throw new Error(`Doesn't support the offered configuration`);
return accepted;
}
/**
* Accept extension response from server.
*
* @param {Array} paramsList Extension parameters
* @return {Object} Accepted configuration
* @private
*/
acceptAsClient (paramsList) {
const params = paramsList[0];
if (this._options.clientNoContextTakeover != null) {
if (
this._options.clientNoContextTakeover === false &&
params.client_no_context_takeover
) {
throw new Error('Invalid value for "client_no_context_takeover"');
}
}
if (this._options.clientMaxWindowBits != null) {
if (
this._options.clientMaxWindowBits === false &&
params.client_max_window_bits
) {
throw new Error('Invalid value for "client_max_window_bits"');
}
if (
typeof this._options.clientMaxWindowBits === 'number' && (
!params.client_max_window_bits ||
params.client_max_window_bits > this._options.clientMaxWindowBits
)) {
throw new Error('Invalid value for "client_max_window_bits"');
}
}
return params;
}
/**
* Normalize extensions parameters.
*
* @param {Array} paramsList Extension parameters
* @return {Array} Normalized extensions parameters
* @private
*/
normalizeParams (paramsList) {
return paramsList.map((params) => {
Object.keys(params).forEach((key) => {
var value = params[key];
if (value.length > 1) {
throw new Error(`Multiple extension parameters for ${key}`);
}
value = value[0];
switch (key) {
case 'server_no_context_takeover':
case 'client_no_context_takeover':
if (value !== true) {
throw new Error(`invalid extension parameter value for ${key} (${value})`);
}
params[key] = true;
break;
case 'server_max_window_bits':
case 'client_max_window_bits':
if (typeof value === 'string') {
value = parseInt(value, 10);
if (!~AVAILABLE_WINDOW_BITS.indexOf(value)) {
throw new Error(`invalid extension parameter value for ${key} (${value})`);
}
}
if (!this._isServer && value === true) {
throw new Error(`Missing extension parameter value for ${key}`);
}
params[key] = value;
break;
default:
throw new Error(`Not defined extension parameter (${key})`);
}
});
return params;
});
}
/**
* Decompress data.
*
* @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) {
const endpoint = this._isServer ? 'client' : 'server';
if (!this._inflate) {
const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
this._inflate = zlib.createInflateRaw({
windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS
});
}
this._inflate.writeInProgress = true;
var totalLength = 0;
const buffers = [];
var err;
const onData = (data) => {
totalLength += data.length;
if (this._maxPayload < 1 || totalLength <= this._maxPayload) {
return buffers.push(data);
}
err = new Error('max payload size exceeded');
err.closeCode = 1009;
this._inflate.reset();
};
const onError = (err) => {
cleanup();
callback(err);
};
const cleanup = () => {
if (!this._inflate) return;
this._inflate.removeListener('error', onError);
this._inflate.removeListener('data', onData);
this._inflate.writeInProgress = false;
if (
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
this._inflate.pendingClose
) {
this._inflate.close();
this._inflate = null;
}
};
this._inflate.on('error', onError).on('data', onData);
this._inflate.write(data);
if (fin) this._inflate.write(TRAILER);
this._inflate.flush(() => {
cleanup();
if (err) callback(err);
else callback(null, bufferUtil.concat(buffers, totalLength));
});
}
/**
* Compress data.
*
* @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) {
if (!data || data.length === 0) {
process.nextTick(callback, null, EMPTY_BLOCK);
return;
}
const endpoint = this._isServer ? 'server' : 'client';
if (!this._deflate) {
const maxWindowBits = this.params[`${endpoint}_max_window_bits`];
this._deflate = zlib.createDeflateRaw({
flush: zlib.Z_SYNC_FLUSH,
windowBits: typeof maxWindowBits === 'number' ? maxWindowBits : DEFAULT_WINDOW_BITS,
memLevel: this._options.memLevel || DEFAULT_MEM_LEVEL
});
}
this._deflate.writeInProgress = true;
var totalLength = 0;
const buffers = [];
const onData = (data) => {
totalLength += data.length;
buffers.push(data);
};
const onError = (err) => {
cleanup();
callback(err);
};
const cleanup = () => {
if (!this._deflate) return;
this._deflate.removeListener('error', onError);
this._deflate.removeListener('data', onData);
this._deflate.writeInProgress = false;
if (
(fin && this.params[`${endpoint}_no_context_takeover`]) ||
this._deflate.pendingClose
) {
this._deflate.close();
this._deflate = null;
}
};
this._deflate.on('error', onError).on('data', onData);
this._deflate.write(data);
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
cleanup();
var data = bufferUtil.concat(buffers, totalLength);
if (fin) data = data.slice(0, data.length - 4);
callback(null, data);
});
}
}
module.exports = PerMessageDeflate;

555
node_modules/ws/lib/Receiver.js generated vendored Normal file
View File

@@ -0,0 +1,555 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
const safeBuffer = require('safe-buffer');
const PerMessageDeflate = require('./PerMessageDeflate');
const isValidUTF8 = require('./Validation');
const bufferUtil = require('./BufferUtil');
const ErrorCodes = require('./ErrorCodes');
const constants = require('./Constants');
const Buffer = safeBuffer.Buffer;
const GET_INFO = 0;
const GET_PAYLOAD_LENGTH_16 = 1;
const GET_PAYLOAD_LENGTH_64 = 2;
const GET_MASK = 3;
const GET_DATA = 4;
const INFLATING = 5;
/**
* HyBi Receiver implementation.
*/
class Receiver {
/**
* Creates a Receiver instance.
*
* @param {Object} extensions An object containing the negotiated extensions
* @param {Number} maxPayload The maximum allowed message length
* @param {String} binaryType The type for binary data
*/
constructor (extensions, maxPayload, binaryType) {
this.binaryType = binaryType || constants.BINARY_TYPES[0];
this.extensions = extensions || {};
this.maxPayload = maxPayload | 0;
this.bufferedBytes = 0;
this.buffers = [];
this.compressed = false;
this.payloadLength = 0;
this.fragmented = 0;
this.masked = false;
this.fin = false;
this.mask = null;
this.opcode = 0;
this.totalPayloadLength = 0;
this.messageLength = 0;
this.fragments = [];
this.cleanupCallback = null;
this.hadError = false;
this.dead = false;
this.loop = false;
this.onmessage = null;
this.onclose = null;
this.onerror = null;
this.onping = null;
this.onpong = null;
this.state = GET_INFO;
}
/**
* Consumes bytes from the available buffered data.
*
* @param {Number} bytes The number of bytes to consume
* @return {Buffer} Consumed bytes
* @private
*/
readBuffer (bytes) {
var offset = 0;
var dst;
var l;
this.bufferedBytes -= bytes;
if (bytes === this.buffers[0].length) return this.buffers.shift();
if (bytes < this.buffers[0].length) {
dst = this.buffers[0].slice(0, bytes);
this.buffers[0] = this.buffers[0].slice(bytes);
return dst;
}
dst = Buffer.allocUnsafe(bytes);
while (bytes > 0) {
l = this.buffers[0].length;
if (bytes >= l) {
this.buffers[0].copy(dst, offset);
offset += l;
this.buffers.shift();
} else {
this.buffers[0].copy(dst, offset, 0, bytes);
this.buffers[0] = this.buffers[0].slice(bytes);
}
bytes -= l;
}
return dst;
}
/**
* Checks if the number of buffered bytes is bigger or equal than `n` and
* calls `cleanup` if necessary.
*
* @param {Number} n The number of bytes to check against
* @return {Boolean} `true` if `bufferedBytes >= n`, else `false`
* @private
*/
hasBufferedBytes (n) {
if (this.bufferedBytes >= n) return true;
this.loop = false;
if (this.dead) this.cleanup(this.cleanupCallback);
return false;
}
/**
* Adds new data to the parser.
*
* @public
*/
add (data) {
if (this.dead) return;
this.bufferedBytes += data.length;
this.buffers.push(data);
this.startLoop();
}
/**
* Starts the parsing loop.
*
* @private
*/
startLoop () {
this.loop = true;
while (this.loop) {
switch (this.state) {
case GET_INFO:
this.getInfo();
break;
case GET_PAYLOAD_LENGTH_16:
this.getPayloadLength16();
break;
case GET_PAYLOAD_LENGTH_64:
this.getPayloadLength64();
break;
case GET_MASK:
this.getMask();
break;
case GET_DATA:
this.getData();
break;
default: // `INFLATING`
this.loop = false;
}
}
}
/**
* Reads the first two bytes of a frame.
*
* @private
*/
getInfo () {
if (!this.hasBufferedBytes(2)) return;
const buf = this.readBuffer(2);
if ((buf[0] & 0x30) !== 0x00) {
this.error(new Error('RSV2 and RSV3 must be clear'), 1002);
return;
}
const compressed = (buf[0] & 0x40) === 0x40;
if (compressed && !this.extensions[PerMessageDeflate.extensionName]) {
this.error(new Error('RSV1 must be clear'), 1002);
return;
}
this.fin = (buf[0] & 0x80) === 0x80;
this.opcode = buf[0] & 0x0f;
this.payloadLength = buf[1] & 0x7f;
if (this.opcode === 0x00) {
if (compressed) {
this.error(new Error('RSV1 must be clear'), 1002);
return;
}
if (!this.fragmented) {
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
return;
} else {
this.opcode = this.fragmented;
}
} else if (this.opcode === 0x01 || this.opcode === 0x02) {
if (this.fragmented) {
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
return;
}
this.compressed = compressed;
} else if (this.opcode > 0x07 && this.opcode < 0x0b) {
if (!this.fin) {
this.error(new Error('FIN must be set'), 1002);
return;
}
if (compressed) {
this.error(new Error('RSV1 must be clear'), 1002);
return;
}
if (this.payloadLength > 0x7d) {
this.error(new Error('invalid payload length'), 1002);
return;
}
} else {
this.error(new Error(`invalid opcode: ${this.opcode}`), 1002);
return;
}
if (!this.fin && !this.fragmented) this.fragmented = this.opcode;
this.masked = (buf[1] & 0x80) === 0x80;
if (this.payloadLength === 126) this.state = GET_PAYLOAD_LENGTH_16;
else if (this.payloadLength === 127) this.state = GET_PAYLOAD_LENGTH_64;
else this.haveLength();
}
/**
* Gets extended payload length (7+16).
*
* @private
*/
getPayloadLength16 () {
if (!this.hasBufferedBytes(2)) return;
this.payloadLength = this.readBuffer(2).readUInt16BE(0, true);
this.haveLength();
}
/**
* Gets extended payload length (7+64).
*
* @private
*/
getPayloadLength64 () {
if (!this.hasBufferedBytes(8)) return;
const buf = this.readBuffer(8);
const num = buf.readUInt32BE(0, true);
//
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
// if payload length is greater than this number.
//
if (num > Math.pow(2, 53 - 32) - 1) {
this.error(new Error('max payload size exceeded'), 1009);
return;
}
this.payloadLength = (num * Math.pow(2, 32)) + buf.readUInt32BE(4, true);
this.haveLength();
}
/**
* Payload length has been read.
*
* @private
*/
haveLength () {
if (this.opcode < 0x08 && this.maxPayloadExceeded(this.payloadLength)) {
return;
}
if (this.masked) this.state = GET_MASK;
else this.state = GET_DATA;
}
/**
* Reads mask bytes.
*
* @private
*/
getMask () {
if (!this.hasBufferedBytes(4)) return;
this.mask = this.readBuffer(4);
this.state = GET_DATA;
}
/**
* Reads data bytes.
*
* @private
*/
getData () {
var data = constants.EMPTY_BUFFER;
if (this.payloadLength) {
if (!this.hasBufferedBytes(this.payloadLength)) return;
data = this.readBuffer(this.payloadLength);
if (this.masked) bufferUtil.unmask(data, this.mask);
}
if (this.opcode > 0x07) {
this.controlMessage(data);
} else if (this.compressed) {
this.state = INFLATING;
this.decompress(data);
} else if (this.pushFragment(data)) {
this.dataMessage();
}
}
/**
* Decompresses data.
*
* @param {Buffer} data Compressed data
* @private
*/
decompress (data) {
const extension = this.extensions[PerMessageDeflate.extensionName];
extension.decompress(data, this.fin, (err, buf) => {
if (err) {
this.error(err, err.closeCode === 1009 ? 1009 : 1007);
return;
}
if (this.pushFragment(buf)) this.dataMessage();
this.startLoop();
});
}
/**
* Handles a data message.
*
* @private
*/
dataMessage () {
if (this.fin) {
const messageLength = this.messageLength;
const fragments = this.fragments;
this.totalPayloadLength = 0;
this.messageLength = 0;
this.fragmented = 0;
this.fragments = [];
if (this.opcode === 2) {
var data;
if (this.binaryType === 'nodebuffer') {
data = toBuffer(fragments, messageLength);
} else if (this.binaryType === 'arraybuffer') {
data = toArrayBuffer(toBuffer(fragments, messageLength));
} else {
data = fragments;
}
this.onmessage(data, { masked: this.masked, binary: true });
} else {
const buf = toBuffer(fragments, messageLength);
if (!isValidUTF8(buf)) {
this.error(new Error('invalid utf8 sequence'), 1007);
return;
}
this.onmessage(buf.toString(), { masked: this.masked });
}
}
this.state = GET_INFO;
}
/**
* Handles a control message.
*
* @param {Buffer} data Data to handle
* @private
*/
controlMessage (data) {
if (this.opcode === 0x08) {
if (data.length === 0) {
this.onclose(1000, '', { masked: this.masked });
this.loop = false;
this.cleanup(this.cleanupCallback);
} else if (data.length === 1) {
this.error(new Error('invalid payload length'), 1002);
} else {
const code = data.readUInt16BE(0, true);
if (!ErrorCodes.isValidErrorCode(code)) {
this.error(new Error(`invalid status code: ${code}`), 1002);
return;
}
const buf = data.slice(2);
if (!isValidUTF8(buf)) {
this.error(new Error('invalid utf8 sequence'), 1007);
return;
}
this.onclose(code, buf.toString(), { masked: this.masked });
this.loop = false;
this.cleanup(this.cleanupCallback);
}
return;
}
const flags = { masked: this.masked, binary: true };
if (this.opcode === 0x09) this.onping(data, flags);
else this.onpong(data, flags);
this.state = GET_INFO;
}
/**
* Handles an error.
*
* @param {Error} err The error
* @param {Number} code Close code
* @private
*/
error (err, code) {
this.onerror(err, code);
this.hadError = true;
this.loop = false;
this.cleanup(this.cleanupCallback);
}
/**
* Checks payload size, disconnects socket when it exceeds `maxPayload`.
*
* @param {Number} length Payload length
* @private
*/
maxPayloadExceeded (length) {
if (length === 0 || this.maxPayload < 1) return false;
const fullLength = this.totalPayloadLength + length;
if (fullLength <= this.maxPayload) {
this.totalPayloadLength = fullLength;
return false;
}
this.error(new Error('max payload size exceeded'), 1009);
return true;
}
/**
* Appends a fragment in the fragments array after checking that the sum of
* fragment lengths does not exceed `maxPayload`.
*
* @param {Buffer} fragment The fragment to add
* @return {Boolean} `true` if `maxPayload` is not exceeded, else `false`
* @private
*/
pushFragment (fragment) {
if (fragment.length === 0) return true;
const totalLength = this.messageLength + fragment.length;
if (this.maxPayload < 1 || totalLength <= this.maxPayload) {
this.messageLength = totalLength;
this.fragments.push(fragment);
return true;
}
this.error(new Error('max payload size exceeded'), 1009);
return false;
}
/**
* Releases resources used by the receiver.
*
* @param {Function} cb Callback
* @public
*/
cleanup (cb) {
this.dead = true;
if (!this.hadError && (this.loop || this.state === INFLATING)) {
this.cleanupCallback = cb;
} else {
this.extensions = null;
this.fragments = null;
this.buffers = null;
this.mask = null;
this.cleanupCallback = null;
this.onmessage = null;
this.onclose = null;
this.onerror = null;
this.onping = null;
this.onpong = null;
if (cb) cb();
}
}
}
module.exports = Receiver;
/**
* Makes a buffer from a list of fragments.
*
* @param {Buffer[]} fragments The list of fragments composing the message
* @param {Number} messageLength The length of the message
* @return {Buffer}
* @private
*/
function toBuffer (fragments, messageLength) {
if (fragments.length === 1) return fragments[0];
if (fragments.length > 1) return bufferUtil.concat(fragments, messageLength);
return constants.EMPTY_BUFFER;
}
/**
* Converts a buffer to an `ArrayBuffer`.
*
* @param {Buffer} The buffer to convert
* @return {ArrayBuffer} Converted buffer
*/
function toArrayBuffer (buf) {
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) {
return buf.buffer;
}
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}

403
node_modules/ws/lib/Sender.js generated vendored Normal file
View File

@@ -0,0 +1,403 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
const safeBuffer = require('safe-buffer');
const crypto = require('crypto');
const PerMessageDeflate = require('./PerMessageDeflate');
const bufferUtil = require('./BufferUtil');
const ErrorCodes = require('./ErrorCodes');
const Buffer = safeBuffer.Buffer;
/**
* HyBi Sender implementation.
*/
class Sender {
/**
* Creates a Sender instance.
*
* @param {net.Socket} socket The connection socket
* @param {Object} extensions An object containing the negotiated extensions
*/
constructor (socket, extensions) {
this.perMessageDeflate = (extensions || {})[PerMessageDeflate.extensionName];
this._socket = socket;
this.firstFragment = true;
this.compress = false;
this.bufferedBytes = 0;
this.deflating = false;
this.queue = [];
this.onerror = null;
}
/**
* Frames a piece of data according to the HyBi WebSocket protocol.
*
* @param {Buffer} data The data to frame
* @param {Object} options Options object
* @param {Number} options.opcode The opcode
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
* @return {Buffer[]} The framed data as a list of `Buffer` instances
* @public
*/
static frame (data, options) {
const merge = data.length < 1024 || (options.mask && options.readOnly);
var offset = options.mask ? 6 : 2;
var payloadLength = data.length;
if (data.length >= 65536) {
offset += 8;
payloadLength = 127;
} else if (data.length > 125) {
offset += 2;
payloadLength = 126;
}
const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
if (options.rsv1) target[0] |= 0x40;
if (payloadLength === 126) {
target.writeUInt16BE(data.length, 2, true);
} else if (payloadLength === 127) {
target.writeUInt32BE(0, 2, true);
target.writeUInt32BE(data.length, 6, true);
}
if (!options.mask) {
target[1] = payloadLength;
if (merge) {
data.copy(target, offset);
return [target];
}
return [target, data];
}
const mask = crypto.randomBytes(4);
target[1] = payloadLength | 0x80;
target[offset - 4] = mask[0];
target[offset - 3] = mask[1];
target[offset - 2] = mask[2];
target[offset - 1] = mask[3];
if (merge) {
bufferUtil.mask(data, mask, target, offset, data.length);
return [target];
}
bufferUtil.mask(data, mask, data, 0, data.length);
return [target, data];
}
/**
* Sends a close message to the other peer.
*
* @param {(Number|undefined)} code The status code component of the body
* @param {String} data The message component of the body
* @param {Boolean} mask Specifies whether or not to mask the message
* @param {Function} cb Callback
* @public
*/
close (code, data, mask, cb) {
if (code !== undefined && (typeof code !== 'number' || !ErrorCodes.isValidErrorCode(code))) {
throw new Error('first argument must be a valid error code number');
}
const buf = Buffer.allocUnsafe(2 + (data ? Buffer.byteLength(data) : 0));
buf.writeUInt16BE(code || 1000, 0, true);
if (buf.length > 2) buf.write(data, 2);
if (this.deflating) {
this.enqueue([this.doClose, buf, mask, cb]);
} else {
this.doClose(buf, mask, cb);
}
}
/**
* Frames and sends a close message.
*
* @param {Buffer} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @private
*/
doClose (data, mask, cb) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x08,
mask,
readOnly: false
}), cb);
}
/**
* Sends a ping message to the other peer.
*
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @public
*/
ping (data, mask) {
var readOnly = true;
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
if (this.deflating) {
this.enqueue([this.doPing, data, mask, readOnly]);
} else {
this.doPing(data, mask, readOnly);
}
}
/**
* Frames and sends a ping message.
*
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Boolean} readOnly Specifies whether `data` can be modified
* @private
*/
doPing (data, mask, readOnly) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09,
mask,
readOnly
}));
}
/**
* Sends a pong message to the other peer.
*
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @public
*/
pong (data, mask) {
var readOnly = true;
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
if (this.deflating) {
this.enqueue([this.doPong, data, mask, readOnly]);
} else {
this.doPong(data, mask, readOnly);
}
}
/**
* Frames and sends a pong message.
*
* @param {*} data The message to send
* @param {Boolean} mask Specifies whether or not to mask `data`
* @param {Boolean} readOnly Specifies whether `data` can be modified
* @private
*/
doPong (data, mask, readOnly) {
this.sendFrame(Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a,
mask,
readOnly
}));
}
/**
* Sends a data message to the other peer.
*
* @param {*} data The message to send
* @param {Object} options Options object
* @param {Boolean} options.compress Specifies whether or not to compress `data`
* @param {Boolean} options.binary Specifies whether `data` is binary or text
* @param {Boolean} options.fin Specifies whether the fragment is the last one
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Function} cb Callback
* @public
*/
send (data, options, cb) {
var opcode = options.binary ? 2 : 1;
var rsv1 = options.compress;
var readOnly = true;
if (!Buffer.isBuffer(data)) {
if (data instanceof ArrayBuffer) {
data = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
data = viewToBuffer(data);
} else {
data = Buffer.from(data);
readOnly = false;
}
}
if (this.firstFragment) {
this.firstFragment = false;
if (rsv1 && this.perMessageDeflate) {
rsv1 = data.length >= this.perMessageDeflate.threshold;
}
this.compress = rsv1;
} else {
rsv1 = false;
opcode = 0;
}
if (options.fin) this.firstFragment = true;
if (this.perMessageDeflate) {
const opts = {
fin: options.fin,
rsv1,
opcode,
mask: options.mask,
readOnly
};
if (this.deflating) {
this.enqueue([this.dispatch, data, this.compress, opts, cb]);
} else {
this.dispatch(data, this.compress, opts, cb);
}
} else {
this.sendFrame(Sender.frame(data, {
fin: options.fin,
rsv1: false,
opcode,
mask: options.mask,
readOnly
}), cb);
}
}
/**
* Dispatches a data message.
*
* @param {Buffer} data The message to send
* @param {Boolean} compress Specifies whether or not to compress `data`
* @param {Object} options Options object
* @param {Number} options.opcode The opcode
* @param {Boolean} options.readOnly Specifies whether `data` can be modified
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit
* @param {Function} cb Callback
* @private
*/
dispatch (data, compress, options, cb) {
if (!compress) {
this.sendFrame(Sender.frame(data, options), cb);
return;
}
this.deflating = true;
this.perMessageDeflate.compress(data, options.fin, (err, buf) => {
if (err) {
if (cb) cb(err);
else this.onerror(err);
return;
}
options.readOnly = false;
this.sendFrame(Sender.frame(buf, options), cb);
this.deflating = false;
this.dequeue();
});
}
/**
* Executes queued send operations.
*
* @private
*/
dequeue () {
while (!this.deflating && this.queue.length) {
const params = this.queue.shift();
this.bufferedBytes -= params[1].length;
params[0].apply(this, params.slice(1));
}
}
/**
* Enqueues a send operation.
*
* @param {Array} params Send operation parameters.
* @private
*/
enqueue (params) {
this.bufferedBytes += params[1].length;
this.queue.push(params);
}
/**
* Sends a frame.
*
* @param {Buffer[]} list The frame to send
* @param {Function} cb Callback
* @private
*/
sendFrame (list, cb) {
if (list.length === 2) {
this._socket.write(list[0]);
this._socket.write(list[1], cb);
} else {
this._socket.write(list[0], cb);
}
}
}
module.exports = Sender;
/**
* Converts an `ArrayBuffer` view into a buffer.
*
* @param {(DataView|TypedArray)} view The view to convert
* @return {Buffer} Converted view
* @private
*/
function viewToBuffer (view) {
const buf = Buffer.from(view.buffer);
if (view.byteLength !== view.buffer.byteLength) {
return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
}
return buf;
}

17
node_modules/ws/lib/Validation.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
try {
const isValidUTF8 = require('utf-8-validate');
module.exports = typeof isValidUTF8 === 'object'
? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0
: isValidUTF8;
} catch (e) /* istanbul ignore next */ {
module.exports = () => true;
}

712
node_modules/ws/lib/WebSocket.js generated vendored Normal file
View File

@@ -0,0 +1,712 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
const EventEmitter = require('events');
const crypto = require('crypto');
const Ultron = require('ultron');
const https = require('https');
const http = require('http');
const url = require('url');
const PerMessageDeflate = require('./PerMessageDeflate');
const EventTarget = require('./EventTarget');
const Extensions = require('./Extensions');
const constants = require('./Constants');
const Receiver = require('./Receiver');
const Sender = require('./Sender');
const protocolVersions = [8, 13];
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
/**
* Class representing a WebSocket.
*
* @extends EventEmitter
*/
class WebSocket extends EventEmitter {
/**
* Create a new `WebSocket`.
*
* @param {String} address The URL to which to connect
* @param {(String|String[])} protocols The subprotocols
* @param {Object} options Connection options
*/
constructor (address, protocols, options) {
super();
if (!protocols) {
protocols = [];
} else if (typeof protocols === 'string') {
protocols = [protocols];
} else if (!Array.isArray(protocols)) {
options = protocols;
protocols = [];
}
this.readyState = WebSocket.CONNECTING;
this.bytesReceived = 0;
this.extensions = {};
this.protocol = '';
this._binaryType = constants.BINARY_TYPES[0];
this._finalize = this.finalize.bind(this);
this._finalizeCalled = false;
this._closeMessage = null;
this._closeTimer = null;
this._closeCode = null;
this._receiver = null;
this._sender = null;
this._socket = null;
this._ultron = null;
if (Array.isArray(address)) {
initAsServerClient.call(this, address[0], address[1], address[2], options);
} else {
initAsClient.call(this, address, protocols, options);
}
}
get CONNECTING () { return WebSocket.CONNECTING; }
get CLOSING () { return WebSocket.CLOSING; }
get CLOSED () { return WebSocket.CLOSED; }
get OPEN () { return WebSocket.OPEN; }
/**
* @type {Number}
*/
get bufferedAmount () {
var amount = 0;
if (this._socket) {
amount = this._socket.bufferSize + this._sender.bufferedBytes;
}
return amount;
}
/**
* This deviates from the WHATWG interface since ws doesn't support the required
* default "blob" type (instead we define a custom "nodebuffer" type).
*
* @type {String}
*/
get binaryType () {
return this._binaryType;
}
set binaryType (type) {
if (constants.BINARY_TYPES.indexOf(type) < 0) return;
this._binaryType = type;
//
// Allow to change `binaryType` on the fly.
//
if (this._receiver) this._receiver.binaryType = type;
}
/**
* Set up the socket and the internal resources.
*
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @private
*/
setSocket (socket, head) {
socket.setTimeout(0);
socket.setNoDelay();
this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType);
this._sender = new Sender(socket, this.extensions);
this._ultron = new Ultron(socket);
this._socket = socket;
// socket cleanup handlers
this._ultron.on('close', this._finalize);
this._ultron.on('error', this._finalize);
this._ultron.on('end', this._finalize);
// ensure that the head is added to the receiver
if (head && head.length > 0) {
socket.unshift(head);
head = null;
}
// subsequent packets are pushed to the receiver
this._ultron.on('data', (data) => {
this.bytesReceived += data.length;
this._receiver.add(data);
});
// receiver event handlers
this._receiver.onmessage = (data, flags) => this.emit('message', data, flags);
this._receiver.onping = (data, flags) => {
this.pong(data, !this._isServer, true);
this.emit('ping', data, flags);
};
this._receiver.onpong = (data, flags) => this.emit('pong', data, flags);
this._receiver.onclose = (code, reason) => {
this._closeMessage = reason;
this._closeCode = code;
this.close(code, reason);
};
this._receiver.onerror = (error, code) => {
// close the connection when the receiver reports a HyBi error code
this.close(code, '');
this.emit('error', error);
};
// sender event handlers
this._sender.onerror = (error) => {
this.close(1002, '');
this.emit('error', error);
};
this.readyState = WebSocket.OPEN;
this.emit('open');
}
/**
* Clean up and release internal resources.
*
* @param {(Boolean|Error)} Indicates whether or not an error occurred
* @private
*/
finalize (error) {
if (this._finalizeCalled) return;
this.readyState = WebSocket.CLOSING;
this._finalizeCalled = true;
clearTimeout(this._closeTimer);
this._closeTimer = null;
//
// If the connection was closed abnormally (with an error), or if the close
// control frame was malformed or not received then the close code must be
// 1006.
//
if (error) this._closeCode = 1006;
if (this._socket) {
this._ultron.destroy();
this._socket.on('error', function onerror () {
this.destroy();
});
if (!error) this._socket.end();
else this._socket.destroy();
this._socket = null;
this._ultron = null;
}
if (this._sender) {
this._sender = this._sender.onerror = null;
}
if (this._receiver) {
this._receiver.cleanup(() => this.emitClose());
this._receiver = null;
} else {
this.emitClose();
}
}
/**
* Emit the `close` event.
*
* @private
*/
emitClose () {
this.readyState = WebSocket.CLOSED;
this.emit('close', this._closeCode || 1006, this._closeMessage || '');
if (this.extensions[PerMessageDeflate.extensionName]) {
this.extensions[PerMessageDeflate.extensionName].cleanup();
}
this.extensions = null;
this.removeAllListeners();
this.on('error', constants.NOOP); // Catch all errors after this.
}
/**
* Pause the socket stream.
*
* @public
*/
pause () {
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
this._socket.pause();
}
/**
* Resume the socket stream
*
* @public
*/
resume () {
if (this.readyState !== WebSocket.OPEN) throw new Error('not opened');
this._socket.resume();
}
/**
* Start a closing handshake.
*
* @param {Number} code Status code explaining why the connection is closing
* @param {String} data A string explaining why the connection is closing
* @public
*/
close (code, data) {
if (this.readyState === WebSocket.CLOSED) return;
if (this.readyState === WebSocket.CONNECTING) {
if (this._req && !this._req.aborted) {
this._req.abort();
this.emit('error', new Error('closed before the connection is established'));
this.finalize(true);
}
return;
}
if (this.readyState === WebSocket.CLOSING) {
if (this._closeCode && this._socket) this._socket.end();
return;
}
this.readyState = WebSocket.CLOSING;
this._sender.close(code, data, !this._isServer, (err) => {
if (err) this.emit('error', err);
if (this._socket) {
if (this._closeCode) this._socket.end();
//
// Ensure that the connection is cleaned up even when the closing
// handshake fails.
//
clearTimeout(this._closeTimer);
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
}
});
}
/**
* Send a ping message.
*
* @param {*} data The message to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
* @public
*/
ping (data, mask, failSilently) {
if (this.readyState !== WebSocket.OPEN) {
if (failSilently) return;
throw new Error('not opened');
}
if (typeof data === 'number') data = data.toString();
if (mask === undefined) mask = !this._isServer;
this._sender.ping(data || constants.EMPTY_BUFFER, mask);
}
/**
* Send a pong message.
*
* @param {*} data The message to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Boolean} failSilently Indicates whether or not to throw if `readyState` isn't `OPEN`
* @public
*/
pong (data, mask, failSilently) {
if (this.readyState !== WebSocket.OPEN) {
if (failSilently) return;
throw new Error('not opened');
}
if (typeof data === 'number') data = data.toString();
if (mask === undefined) mask = !this._isServer;
this._sender.pong(data || constants.EMPTY_BUFFER, mask);
}
/**
* Send a data message.
*
* @param {*} data The message to send
* @param {Object} options Options object
* @param {Boolean} options.compress Specifies whether or not to compress `data`
* @param {Boolean} options.binary Specifies whether `data` is binary or text
* @param {Boolean} options.fin Specifies whether the fragment is the last one
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Function} cb Callback which is executed when data is written out
* @public
*/
send (data, options, cb) {
if (typeof options === 'function') {
cb = options;
options = {};
}
if (this.readyState !== WebSocket.OPEN) {
if (cb) cb(new Error('not opened'));
else throw new Error('not opened');
return;
}
if (typeof data === 'number') data = data.toString();
const opts = Object.assign({
binary: typeof data !== 'string',
mask: !this._isServer,
compress: true,
fin: true
}, options);
if (!this.extensions[PerMessageDeflate.extensionName]) {
opts.compress = false;
}
this._sender.send(data || constants.EMPTY_BUFFER, opts, cb);
}
/**
* Forcibly close the connection.
*
* @public
*/
terminate () {
if (this.readyState === WebSocket.CLOSED) return;
if (this.readyState === WebSocket.CONNECTING) {
if (this._req && !this._req.aborted) {
this._req.abort();
this.emit('error', new Error('closed before the connection is established'));
this.finalize(true);
}
return;
}
this.finalize(true);
}
}
WebSocket.CONNECTING = 0;
WebSocket.OPEN = 1;
WebSocket.CLOSING = 2;
WebSocket.CLOSED = 3;
//
// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
//
['open', 'error', 'close', 'message'].forEach((method) => {
Object.defineProperty(WebSocket.prototype, `on${method}`, {
/**
* Return the listener of the event.
*
* @return {(Function|undefined)} The event listener or `undefined`
* @public
*/
get () {
const listeners = this.listeners(method);
for (var i = 0; i < listeners.length; i++) {
if (listeners[i]._listener) return listeners[i]._listener;
}
},
/**
* Add a listener for the event.
*
* @param {Function} listener The listener to add
* @public
*/
set (listener) {
const listeners = this.listeners(method);
for (var i = 0; i < listeners.length; i++) {
//
// Remove only the listeners added via `addEventListener`.
//
if (listeners[i]._listener) this.removeListener(method, listeners[i]);
}
this.addEventListener(method, listener);
}
});
});
WebSocket.prototype.addEventListener = EventTarget.addEventListener;
WebSocket.prototype.removeEventListener = EventTarget.removeEventListener;
module.exports = WebSocket;
/**
* Initialize a WebSocket server client.
*
* @param {http.IncomingMessage} req The request object
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Object} options WebSocket attributes
* @param {Number} options.protocolVersion The WebSocket protocol version
* @param {Object} options.extensions The negotiated extensions
* @param {Number} options.maxPayload The maximum allowed message size
* @param {String} options.protocol The chosen subprotocol
* @private
*/
function initAsServerClient (req, socket, head, options) {
this.protocolVersion = options.protocolVersion;
this.extensions = options.extensions;
this.maxPayload = options.maxPayload;
this.protocol = options.protocol;
this.upgradeReq = req;
this._isServer = true;
this.setSocket(socket, head);
}
/**
* Initialize a WebSocket client.
*
* @param {String} address The URL to which to connect
* @param {String[]} protocols The list of subprotocols
* @param {Object} options Connection options
* @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
* @param {String} options.localAddress Local interface to bind for network connections
* @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version` header
* @param {Object} options.headers An object containing request headers
* @param {String} options.origin Value of the `Origin` or `Sec-WebSocket-Origin` header
* @param {http.Agent} options.agent Use the specified Agent
* @param {String} options.host Value of the `Host` header
* @param {Number} options.family IP address family to use during hostname lookup (4 or 6).
* @param {Function} options.checkServerIdentity A function to validate the server hostname
* @param {Boolean} options.rejectUnauthorized Verify or not the server certificate
* @param {String} options.passphrase The passphrase for the private key or pfx
* @param {String} options.ciphers The ciphers to use or exclude
* @param {(String|String[]|Buffer|Buffer[])} options.cert The certificate key
* @param {(String|String[]|Buffer|Buffer[])} options.key The private key
* @param {(String|Buffer)} options.pfx The private key, certificate, and CA certs
* @param {(String|String[]|Buffer|Buffer[])} options.ca Trusted certificates
* @private
*/
function initAsClient (address, protocols, options) {
options = Object.assign({
protocolVersion: protocolVersions[1],
protocol: protocols.join(','),
perMessageDeflate: true,
localAddress: null,
headers: null,
family: null,
origin: null,
agent: null,
host: null,
//
// SSL options.
//
checkServerIdentity: null,
rejectUnauthorized: null,
passphrase: null,
ciphers: null,
cert: null,
key: null,
pfx: null,
ca: null
}, options);
if (protocolVersions.indexOf(options.protocolVersion) === -1) {
throw new Error(
`unsupported protocol version: ${options.protocolVersion} ` +
`(supported versions: ${protocolVersions.join(', ')})`
);
}
this.protocolVersion = options.protocolVersion;
this._isServer = false;
this.url = address;
const serverUrl = url.parse(address);
const isUnixSocket = serverUrl.protocol === 'ws+unix:';
if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
throw new Error('invalid url');
}
const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
const key = crypto.randomBytes(16).toString('base64');
const httpObj = isSecure ? https : http;
//
// Prepare extensions.
//
const extensionsOffer = {};
var perMessageDeflate;
if (options.perMessageDeflate) {
perMessageDeflate = new PerMessageDeflate(
options.perMessageDeflate !== true ? options.perMessageDeflate : {},
false
);
extensionsOffer[PerMessageDeflate.extensionName] = perMessageDeflate.offer();
}
const requestOptions = {
port: serverUrl.port || (isSecure ? 443 : 80),
host: serverUrl.hostname,
path: '/',
headers: {
'Sec-WebSocket-Version': options.protocolVersion,
'Sec-WebSocket-Key': key,
'Connection': 'Upgrade',
'Upgrade': 'websocket'
}
};
if (options.headers) Object.assign(requestOptions.headers, options.headers);
if (Object.keys(extensionsOffer).length) {
requestOptions.headers['Sec-WebSocket-Extensions'] = Extensions.format(extensionsOffer);
}
if (options.protocol) {
requestOptions.headers['Sec-WebSocket-Protocol'] = options.protocol;
}
if (options.origin) {
if (options.protocolVersion < 13) {
requestOptions.headers['Sec-WebSocket-Origin'] = options.origin;
} else {
requestOptions.headers.Origin = options.origin;
}
}
if (options.host) requestOptions.headers.Host = options.host;
if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
if (options.localAddress) requestOptions.localAddress = options.localAddress;
if (options.family) requestOptions.family = options.family;
if (isUnixSocket) {
const parts = serverUrl.path.split(':');
requestOptions.socketPath = parts[0];
requestOptions.path = parts[1];
} else if (serverUrl.path) {
//
// Make sure that path starts with `/`.
//
if (serverUrl.path.charAt(0) !== '/') {
requestOptions.path = `/${serverUrl.path}`;
} else {
requestOptions.path = serverUrl.path;
}
}
var agent = options.agent;
//
// A custom agent is required for these options.
//
if (
options.rejectUnauthorized != null ||
options.checkServerIdentity ||
options.passphrase ||
options.ciphers ||
options.cert ||
options.key ||
options.pfx ||
options.ca
) {
if (options.passphrase) requestOptions.passphrase = options.passphrase;
if (options.ciphers) requestOptions.ciphers = options.ciphers;
if (options.cert) requestOptions.cert = options.cert;
if (options.key) requestOptions.key = options.key;
if (options.pfx) requestOptions.pfx = options.pfx;
if (options.ca) requestOptions.ca = options.ca;
if (options.checkServerIdentity) {
requestOptions.checkServerIdentity = options.checkServerIdentity;
}
if (options.rejectUnauthorized != null) {
requestOptions.rejectUnauthorized = options.rejectUnauthorized;
}
if (!agent) agent = new httpObj.Agent(requestOptions);
}
if (agent) requestOptions.agent = agent;
this._req = httpObj.get(requestOptions);
this._req.on('error', (error) => {
if (this._req.aborted) return;
this._req = null;
this.emit('error', error);
this.finalize(true);
});
this._req.on('response', (res) => {
if (!this.emit('unexpected-response', this._req, res)) {
this._req.abort();
this.emit('error', new Error(`unexpected server response (${res.statusCode})`));
this.finalize(true);
}
});
this._req.on('upgrade', (res, socket, head) => {
this.emit('headers', res.headers, res);
//
// The user may have closed the connection from a listener of the `headers`
// event.
//
if (this.readyState !== WebSocket.CONNECTING) return;
this._req = null;
const digest = crypto.createHash('sha1')
.update(key + constants.GUID, 'binary')
.digest('base64');
if (res.headers['sec-websocket-accept'] !== digest) {
socket.destroy();
this.emit('error', new Error('invalid server key'));
return this.finalize(true);
}
const serverProt = res.headers['sec-websocket-protocol'];
const protList = (options.protocol || '').split(/, */);
var protError;
if (!options.protocol && serverProt) {
protError = 'server sent a subprotocol even though none requested';
} else if (options.protocol && !serverProt) {
protError = 'server sent no subprotocol even though requested';
} else if (serverProt && protList.indexOf(serverProt) === -1) {
protError = 'server responded with an invalid protocol';
}
if (protError) {
socket.destroy();
this.emit('error', new Error(protError));
return this.finalize(true);
}
if (serverProt) this.protocol = serverProt;
const serverExtensions = Extensions.parse(res.headers['sec-websocket-extensions']);
if (perMessageDeflate && serverExtensions[PerMessageDeflate.extensionName]) {
try {
perMessageDeflate.accept(serverExtensions[PerMessageDeflate.extensionName]);
} catch (err) {
socket.destroy();
this.emit('error', new Error('invalid extension parameter'));
return this.finalize(true);
}
this.extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
this.setSocket(socket, head);
});
}

336
node_modules/ws/lib/WebSocketServer.js generated vendored Normal file
View File

@@ -0,0 +1,336 @@
/*!
* ws: a node.js websocket client
* Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
* MIT Licensed
*/
'use strict';
const safeBuffer = require('safe-buffer');
const EventEmitter = require('events');
const crypto = require('crypto');
const Ultron = require('ultron');
const http = require('http');
const url = require('url');
const PerMessageDeflate = require('./PerMessageDeflate');
const Extensions = require('./Extensions');
const constants = require('./Constants');
const WebSocket = require('./WebSocket');
const Buffer = safeBuffer.Buffer;
/**
* Class representing a WebSocket server.
*
* @extends EventEmitter
*/
class WebSocketServer extends EventEmitter {
/**
* Create a `WebSocketServer` instance.
*
* @param {Object} options Configuration options
* @param {String} options.host The hostname where to bind the server
* @param {Number} options.port The port where to bind the server
* @param {http.Server} options.server A pre-created HTTP/S server to use
* @param {Function} options.verifyClient An hook to reject connections
* @param {Function} options.handleProtocols An hook to handle protocols
* @param {String} options.path Accept only connections matching this path
* @param {Boolean} options.noServer Enable no server mode
* @param {Boolean} options.clientTracking Specifies whether or not to track clients
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate
* @param {Number} options.maxPayload The maximum allowed message size
* @param {Function} callback A listener for the `listening` event
*/
constructor (options, callback) {
super();
options = Object.assign({
maxPayload: 100 * 1024 * 1024,
perMessageDeflate: true,
handleProtocols: null,
clientTracking: true,
verifyClient: null,
noServer: false,
backlog: null, // use default (511 as implemented in net.js)
server: null,
host: null,
path: null,
port: null
}, options);
if (options.port == null && !options.server && !options.noServer) {
throw new TypeError('missing or invalid options');
}
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.allowHalfOpen = false;
this._server.listen(options.port, options.host, options.backlog, callback);
} else if (options.server) {
this._server = options.server;
}
if (this._server) {
this._ultron = new Ultron(this._server);
this._ultron.on('listening', () => this.emit('listening'));
this._ultron.on('error', (err) => this.emit('error', err));
this._ultron.on('upgrade', (req, socket, head) => {
this.handleUpgrade(req, socket, head, (client) => {
this.emit(`connection${req.url}`, client);
this.emit('connection', client);
});
});
}
if (options.clientTracking) this.clients = new Set();
this.options = options;
this.path = options.path;
}
/**
* Close the server.
*
* @param {Function} cb Callback
* @public
*/
close (cb) {
//
// Terminate all associated clients.
//
if (this.clients) {
for (const client of this.clients) client.terminate();
}
const server = this._server;
if (server) {
this._ultron.destroy();
this._ultron = this._server = null;
//
// Close the http server if it was internally created.
//
if (this.options.port != null) return server.close(cb);
}
if (cb) cb();
}
/**
* See if a given request should be handled by this server instance.
*
* @param {http.IncomingMessage} req Request object to inspect
* @return {Boolean} `true` if the request is valid, else `false`
* @public
*/
shouldHandle (req) {
if (this.options.path && url.parse(req.url).pathname !== this.options.path) {
return false;
}
return true;
}
/**
* Handle a HTTP Upgrade request.
*
* @param {http.IncomingMessage} req The request object
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Function} cb Callback
* @public
*/
handleUpgrade (req, socket, head, cb) {
socket.on('error', socketError);
const version = +req.headers['sec-websocket-version'];
if (
req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' ||
!req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) ||
!this.shouldHandle(req)
) {
return abortConnection(socket, 400);
}
var protocol = (req.headers['sec-websocket-protocol'] || '').split(/, */);
//
// Optionally call external protocol selection handler.
//
if (this.options.handleProtocols) {
protocol = this.options.handleProtocols(protocol, req);
if (protocol === false) return abortConnection(socket, 401);
} else {
protocol = protocol[0];
}
//
// Optionally call external client verification handler.
//
if (this.options.verifyClient) {
const info = {
origin: req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
secure: !!(req.connection.authorized || req.connection.encrypted),
req
};
if (this.options.verifyClient.length === 2) {
this.options.verifyClient(info, (verified, code, message) => {
if (!verified) return abortConnection(socket, code || 401, message);
this.completeUpgrade(protocol, version, req, socket, head, cb);
});
return;
} else if (!this.options.verifyClient(info)) {
return abortConnection(socket, 401);
}
}
this.completeUpgrade(protocol, version, req, socket, head, cb);
}
/**
* Upgrade the connection to WebSocket.
*
* @param {String} protocol The chosen subprotocol
* @param {Number} version The WebSocket protocol version
* @param {http.IncomingMessage} req The request object
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Function} cb Callback
* @private
*/
completeUpgrade (protocol, version, req, socket, head, cb) {
//
// Destroy the socket if the client has already sent a FIN packet.
//
if (!socket.readable || !socket.writable) return socket.destroy();
const key = crypto.createHash('sha1')
.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary')
.digest('base64');
const headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${key}`
];
if (protocol) headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
const offer = Extensions.parse(req.headers['sec-websocket-extensions']);
var extensions;
try {
extensions = acceptExtensions(this.options, offer);
} catch (err) {
return abortConnection(socket, 400);
}
const props = Object.keys(extensions);
if (props.length) {
const serverExtensions = props.reduce((obj, key) => {
obj[key] = [extensions[key].params];
return obj;
}, {});
headers.push(`Sec-WebSocket-Extensions: ${Extensions.format(serverExtensions)}`);
}
//
// Allow external modification/inspection of handshake headers.
//
this.emit('headers', headers, req);
socket.write(headers.concat('', '').join('\r\n'));
const client = new WebSocket([req, socket, head], null, {
maxPayload: this.options.maxPayload,
protocolVersion: version,
extensions,
protocol
});
if (this.clients) {
this.clients.add(client);
client.on('close', () => this.clients.delete(client));
}
socket.removeListener('error', socketError);
cb(client);
}
}
module.exports = WebSocketServer;
/**
* Handle premature socket errors.
*
* @private
*/
function socketError () {
this.destroy();
}
/**
* Accept WebSocket extensions.
*
* @param {Object} options The `WebSocketServer` configuration options
* @param {Object} offer The parsed value of the `sec-websocket-extensions` header
* @return {Object} Accepted extensions
* @private
*/
function acceptExtensions (options, offer) {
const pmd = options.perMessageDeflate;
const extensions = {};
if (pmd && offer[PerMessageDeflate.extensionName]) {
const perMessageDeflate = new PerMessageDeflate(
pmd !== true ? pmd : {},
true,
options.maxPayload
);
perMessageDeflate.accept(offer[PerMessageDeflate.extensionName]);
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
}
return extensions;
}
/**
* Close the connection when preconditions are not fulfilled.
*
* @param {net.Socket} socket The socket of the upgrade request
* @param {Number} code The HTTP response status code
* @param {String} [message] The HTTP response body
* @private
*/
function abortConnection (socket, code, message) {
if (socket.writable) {
message = message || http.STATUS_CODES[code];
socket.write(
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
'Connection: close\r\n' +
'Content-type: text/html\r\n' +
`Content-Length: ${Buffer.byteLength(message)}\r\n` +
'\r\n' +
message
);
}
socket.removeListener('error', socketError);
socket.destroy();
}