Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ws

Package Overview
Dependencies
Maintainers
4
Versions
169
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ws - npm Package Compare versions

Comparing version 4.1.0 to 5.0.0

4

lib/buffer-util.js
'use strict';
const safeBuffer = require('safe-buffer');
const Buffer = safeBuffer.Buffer;
/**

@@ -8,0 +4,0 @@ * Merges an array of buffers into a new buffer.

16

lib/constants.js
'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 = () => {};
module.exports = {
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
kStatusCode: Symbol('status-code'),
kWebSocket: Symbol('websocket'),
EMPTY_BUFFER: Buffer.alloc(0),
NOOP: () => {}
};
'use strict';
const safeBuffer = require('safe-buffer');
const Limiter = require('async-limiter');

@@ -8,8 +7,8 @@ const zlib = require('zlib');

const bufferUtil = require('./buffer-util');
const constants = require('./constants');
const Buffer = safeBuffer.Buffer;
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');

@@ -21,3 +20,2 @@ const kPendingClose = Symbol('pending-close');

const kError = Symbol('error');
const kOwner = Symbol('owner');

@@ -351,11 +349,7 @@ //

this._inflate = zlib.createInflateRaw(
Object.assign(
{},
this._options.zlibInflateOptions,
{ windowBits }
)
Object.assign({}, this._options.zlibInflateOptions, { windowBits })
);
this._inflate[kPerMessageDeflate] = this;
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
this._inflate[kOwner] = this;
this._inflate.on('error', inflateOnError);

@@ -498,4 +492,4 @@ this._inflate.on('data', inflateOnData);

if (
this[kOwner]._maxPayload < 1 ||
this[kTotalLength] <= this[kOwner]._maxPayload
this[kPerMessageDeflate]._maxPayload < 1 ||
this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
) {

@@ -507,3 +501,3 @@ this[kBuffers].push(chunk);

this[kError] = new RangeError('Max payload size exceeded');
this[kError].closeCode = 1009;
this[kError][constants.kStatusCode] = 1009;
this.removeListener('data', inflateOnData);

@@ -524,4 +518,5 @@ this.reset();

//
this[kOwner]._inflate = null;
this[kPerMessageDeflate]._inflate = null;
err[constants.kStatusCode] = 1007;
this[kCallback](err);
}
'use strict';
const safeBuffer = require('safe-buffer');
const stream = require('stream');

@@ -10,4 +10,2 @@ const PerMessageDeflate = require('./permessage-deflate');

const Buffer = safeBuffer.Buffer;
const GET_INFO = 0;

@@ -22,13 +20,18 @@ const GET_PAYLOAD_LENGTH_16 = 1;

* HyBi Receiver implementation.
*
* @extends stream.Writable
*/
class Receiver {
class Receiver extends stream.Writable {
/**
* Creates a Receiver instance.
*
* @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
* @param {String} binaryType The type for binary data
*/
constructor (extensions, maxPayload, binaryType) {
constructor (binaryType, extensions, maxPayload) {
super();
this._binaryType = binaryType || constants.BINARY_TYPES[0];
this[constants.kWebSocket] = undefined;
this._extensions = extensions || {};

@@ -42,6 +45,6 @@ this._maxPayload = maxPayload | 0;

this._payloadLength = 0;
this._mask = undefined;
this._fragmented = 0;
this._masked = false;
this._fin = false;
this._mask = null;
this._opcode = 0;

@@ -53,32 +56,29 @@

this._cleanupCallback = null;
this._isCleaningUp = false;
this._hadError = false;
this._state = GET_INFO;
this._loop = false;
}
this.add = this.add.bind(this);
this.onmessage = null;
this.onclose = null;
this.onerror = null;
this.onping = null;
this.onpong = null;
/**
* Implements `Writable.prototype._write()`.
*
* @param {Buffer} chunk The chunk of data to write
* @param {String} encoding The character encoding of `chunk`
* @param {Function} cb Callback
*/
_write (chunk, encoding, cb) {
if (this._opcode === 0x08) return cb();
this._state = GET_INFO;
this._bufferedBytes += chunk.length;
this._buffers.push(chunk);
this.startLoop(cb);
}
/**
* Consumes `n` bytes from the buffered data, calls `cleanup` if necessary.
* Consumes `n` bytes from the buffered data.
*
* @param {Number} n The number of bytes to consume
* @return {(Buffer|null)} The consumed bytes or `null` if `n` bytes are not
* available
* @return {Buffer} The consumed bytes
* @private
*/
consume (n) {
if (this._bufferedBytes < n) {
this._loop = false;
if (this._isCleaningUp) this.cleanup(this._cleanupCallback);
return null;
}
this._bufferedBytes -= n;

@@ -113,19 +113,9 @@

/**
* Adds new data to the parser.
*
* @param {Buffer} chunk A chunk of data
* @public
*/
add (chunk) {
this._bufferedBytes += chunk.length;
this._buffers.push(chunk);
this.startLoop();
}
/**
* Starts the parsing loop.
*
* @param {Function} cb Callback
* @private
*/
startLoop () {
startLoop (cb) {
var err;
this._loop = true;

@@ -136,9 +126,9 @@

case GET_INFO:
this.getInfo();
err = this.getInfo();
break;
case GET_PAYLOAD_LENGTH_16:
this.getPayloadLength16();
err = this.getPayloadLength16();
break;
case GET_PAYLOAD_LENGTH_64:
this.getPayloadLength64();
err = this.getPayloadLength64();
break;

@@ -149,8 +139,11 @@ case GET_MASK:

case GET_DATA:
this.getData();
err = this.getData(cb);
break;
default: // `INFLATING`
this._loop = false;
return;
}
} while (this._loop);
cb(err);
}

@@ -161,14 +154,16 @@

*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getInfo () {
if (this._bufferedBytes < 2) {
this._loop = false;
return;
}
const buf = this.consume(2);
if (buf === null) return;
if ((buf[0] & 0x30) !== 0x00) {
this.error(
new RangeError('Invalid WebSocket frame: RSV2 and RSV3 must be clear'),
1002
);
return;
this._loop = false;
return error(RangeError, 'RSV2 and RSV3 must be clear', true, 1002);
}

@@ -179,7 +174,4 @@

if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
this.error(
new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
1002
);
return;
this._loop = false;
return error(RangeError, 'RSV1 must be clear', true, 1002);
}

@@ -193,27 +185,16 @@

if (compressed) {
this.error(
new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
1002
);
return;
this._loop = false;
return error(RangeError, 'RSV1 must be clear', true, 1002);
}
if (!this._fragmented) {
this.error(
new RangeError('Invalid WebSocket frame: invalid opcode 0'),
1002
);
return;
} else {
this._opcode = this._fragmented;
this._loop = false;
return error(RangeError, 'invalid opcode 0', true, 1002);
}
this._opcode = this._fragmented;
} else if (this._opcode === 0x01 || this._opcode === 0x02) {
if (this._fragmented) {
this.error(
new RangeError(
`Invalid WebSocket frame: invalid opcode ${this._opcode}`
),
1002
);
return;
this._loop = false;
return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
}

@@ -224,39 +205,26 @@

if (!this._fin) {
this.error(
new RangeError('Invalid WebSocket frame: FIN must be set'),
1002
);
return;
this._loop = false;
return error(RangeError, 'FIN must be set', true, 1002);
}
if (compressed) {
this.error(
new RangeError('Invalid WebSocket frame: RSV1 must be clear'),
1002
);
return;
this._loop = false;
return error(RangeError, 'RSV1 must be clear', true, 1002);
}
if (this._payloadLength > 0x7d) {
this.error(
new RangeError(
`Invalid WebSocket frame: invalid payload length ` +
`${this._payloadLength}`
),
this._loop = false;
return error(
RangeError,
`invalid payload length ${this._payloadLength}`,
true,
1002
);
return;
}
} else {
this.error(
new RangeError(
`Invalid WebSocket frame: invalid opcode ${this._opcode}`
),
1002
);
return;
this._loop = false;
return error(RangeError, `invalid opcode ${this._opcode}`, true, 1002);
}
if (!this._fin && !this._fragmented) this._fragmented = this._opcode;
this._masked = (buf[1] & 0x80) === 0x80;

@@ -266,3 +234,3 @@

else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64;
else this.haveLength();
else return this.haveLength();
}

@@ -273,10 +241,13 @@

*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength16 () {
const buf = this.consume(2);
if (buf === null) return;
if (this._bufferedBytes < 2) {
this._loop = false;
return;
}
this._payloadLength = buf.readUInt16BE(0, true);
this.haveLength();
this._payloadLength = this.consume(2).readUInt16BE(0, true);
return this.haveLength();
}

@@ -287,8 +258,12 @@

*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getPayloadLength64 () {
if (this._bufferedBytes < 8) {
this._loop = false;
return;
}
const buf = this.consume(8);
if (buf === null) return;
const num = buf.readUInt32BE(0, true);

@@ -301,13 +276,13 @@

if (num > Math.pow(2, 53 - 32) - 1) {
this.error(
new RangeError(
'Unsupported WebSocket frame: payload length > 2^53 - 1'
),
this._loop = false;
return error(
RangeError,
'Unsupported WebSocket frame: payload length > 2^53 - 1',
false,
1009
);
return;
}
this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4, true);
this.haveLength();
return this.haveLength();
}

@@ -318,7 +293,12 @@

*
* @return {(RangeError|undefined)} A possible error
* @private
*/
haveLength () {
if (this._opcode < 0x08 && this.maxPayloadExceeded(this._payloadLength)) {
return;
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);
}
}

@@ -336,5 +316,8 @@

getMask () {
if (this._bufferedBytes < 4) {
this._loop = false;
return;
}
this._mask = this.consume(4);
if (this._mask === null) return;
this._state = GET_DATA;

@@ -346,22 +329,37 @@ }

*
* @param {Function} cb Callback
* @return {(Error|RangeError|undefined)} A possible error
* @private
*/
getData () {
getData (cb) {
var data = constants.EMPTY_BUFFER;
if (this._payloadLength) {
if (this._bufferedBytes < this._payloadLength) {
this._loop = false;
return;
}
data = this.consume(this._payloadLength);
if (data === null) return;
if (this._masked) bufferUtil.unmask(data, this._mask);
}
if (this._opcode > 0x07) {
this.controlMessage(data);
} else if (this._compressed) {
if (this._opcode > 0x07) return this.controlMessage(data);
if (this._compressed) {
this._state = INFLATING;
this.decompress(data);
} else if (this.pushFragment(data)) {
this.dataMessage();
this.decompress(data, cb);
return;
}
if (data.length) {
//
// This message is not compressed so its lenght is the sum of the payload
// length of all fragments.
//
this._messageLength = this._totalPayloadLength;
this._fragments.push(data);
}
return this.dataMessage();
}

@@ -373,15 +371,24 @@

* @param {Buffer} data Compressed data
* @param {Function} cb Callback
* @private
*/
decompress (data) {
decompress (data, cb) {
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
perMessageDeflate.decompress(data, this._fin, (err, buf) => {
if (err) {
this.error(err, err.closeCode === 1009 ? 1009 : 1007);
return;
if (err) return cb(err);
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));
}
this._fragments.push(buf);
}
if (this.pushFragment(buf)) this.dataMessage();
this.startLoop();
const er = this.dataMessage();
if (er) return cb(er);
this.startLoop(cb);
});

@@ -393,2 +400,3 @@ }

*
* @return {(Error|undefined)} A possible error
* @private

@@ -417,3 +425,3 @@ */

this.onmessage(data);
this.emit('message', data);
} else {

@@ -423,10 +431,7 @@ const buf = toBuffer(fragments, messageLength);

if (!validation.isValidUTF8(buf)) {
this.error(
new Error('Invalid WebSocket frame: invalid UTF-8 sequence'),
1007
);
return;
this._loop = false;
return error(Error, 'invalid UTF-8 sequence', true, 1007);
}
this.onmessage(buf.toString());
this.emit('message', buf.toString());
}

@@ -442,2 +447,3 @@ }

* @param {Buffer} data Data to handle
* @return {(Error|RangeError|undefined)} A possible error
* @private

@@ -447,11 +453,9 @@ */

if (this._opcode === 0x08) {
this._loop = false;
if (data.length === 0) {
this._loop = false;
this.onclose(1005, '');
this.cleanup(this._cleanupCallback);
this.emit('conclude', 1005, '');
this.end();
} else if (data.length === 1) {
this.error(
new RangeError('Invalid WebSocket frame: invalid payload length 1'),
1002
);
return error(RangeError, 'invalid payload length 1', true, 1002);
} else {

@@ -461,9 +465,3 @@ const code = data.readUInt16BE(0, true);

if (!validation.isValidStatusCode(code)) {
this.error(
new RangeError(
`Invalid WebSocket frame: invalid status code ${code}`
),
1002
);
return;
return error(RangeError, `invalid status code ${code}`, true, 1002);
}

@@ -474,12 +472,7 @@

if (!validation.isValidUTF8(buf)) {
this.error(
new Error('Invalid WebSocket frame: invalid UTF-8 sequence'),
1007
);
return;
return error(Error, 'invalid UTF-8 sequence', true, 1007);
}
this._loop = false;
this.onclose(code, buf.toString());
this.cleanup(this._cleanupCallback);
this.emit('conclude', code, buf.toString());
this.end();
}

@@ -490,101 +483,32 @@

if (this._opcode === 0x09) this.onping(data);
else this.onpong(data);
if (this._opcode === 0x09) this.emit('ping', data);
else this.emit('pong', data);
this._state = GET_INFO;
}
}
/**
* Handles an error.
*
* @param {Error} err The error
* @param {Number} code Close code
* @private
*/
error (err, code) {
this._hadError = true;
this._loop = false;
this.onerror(err, code);
this.cleanup(this._cleanupCallback);
}
module.exports = Receiver;
/**
* 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;
/**
* Builds an error object.
*
* @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
* @return {(Error|RangeError)} The error
* @private
*/
function error (ErrorCtor, message, prefix, statusCode) {
const err = new ErrorCtor(
prefix ? `Invalid WebSocket frame: ${message}` : message
);
const fullLength = this._totalPayloadLength + length;
if (fullLength <= this._maxPayload) {
this._totalPayloadLength = fullLength;
return false;
}
this.error(new RangeError('Max payload size exceeded'), 1009);
return true;
}
/**
* Appends a fragment in the fragments array after checking that the sum of
* fragment lengths does not exceed `maxPayload`.
*
* @param {Buffer} fragment The fragment to add
* @return {Boolean} `true` if `maxPayload` is not exceeded, else `false`
* @private
*/
pushFragment (fragment) {
if (fragment.length === 0) return true;
const totalLength = this._messageLength + fragment.length;
if (this._maxPayload < 1 || totalLength <= this._maxPayload) {
this._messageLength = totalLength;
this._fragments.push(fragment);
return true;
}
this.error(new RangeError('Max payload size exceeded'), 1009);
return false;
}
/**
* Releases resources used by the receiver.
*
* @param {Function} cb Callback
* @public
*/
cleanup (cb) {
if (this._extensions === null) {
if (cb) cb();
return;
}
if (!this._hadError && (this._loop || this._state === INFLATING)) {
this._cleanupCallback = cb;
this._isCleaningUp = true;
return;
}
this._extensions = null;
this._fragments = null;
this._buffers = null;
this._mask = null;
this._cleanupCallback = null;
this.onmessage = null;
this.onclose = null;
this.onerror = null;
this.onping = null;
this.onpong = null;
if (cb) cb();
}
Error.captureStackTrace(err, error);
err[constants.kStatusCode] = statusCode;
return err;
}
module.exports = Receiver;
/**

@@ -591,0 +515,0 @@ * Makes a buffer from a list of fragments.

'use strict';
const safeBuffer = require('safe-buffer');
const crypto = require('crypto');

@@ -11,4 +10,2 @@

const Buffer = safeBuffer.Buffer;
/**

@@ -15,0 +12,0 @@ * HyBi Sender implementation.

'use strict';
const safeBuffer = require('safe-buffer');
const EventEmitter = require('events');

@@ -14,4 +13,2 @@ const crypto = require('crypto');

const Buffer = safeBuffer.Buffer;
/**

@@ -176,3 +173,3 @@ * Class representing a WebSocket server.

) {
return abortConnection(socket, 400);
return abortHandshake(socket, 400);
}

@@ -197,19 +194,7 @@

} catch (err) {
return abortConnection(socket, 400);
return abortHandshake(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.

@@ -226,5 +211,5 @@ //

this.options.verifyClient(info, (verified, code, message) => {
if (!verified) return abortConnection(socket, code || 401, message);
if (!verified) return abortHandshake(socket, code || 401, message);
this.completeUpgrade(protocol, extensions, req, socket, head, cb);
this.completeUpgrade(extensions, req, socket, head, cb);
});

@@ -234,6 +219,6 @@ return;

if (!this.options.verifyClient(info)) return abortConnection(socket, 401);
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
}
this.completeUpgrade(protocol, extensions, req, socket, head, cb);
this.completeUpgrade(extensions, req, socket, head, cb);
}

@@ -244,3 +229,2 @@

*
* @param {String} protocol The chosen subprotocol
* @param {Object} extensions The accepted extensions

@@ -253,3 +237,3 @@ * @param {http.IncomingMessage} req The request object

*/
completeUpgrade (protocol, extensions, req, socket, head, cb) {
completeUpgrade (extensions, req, socket, head, cb) {
//

@@ -272,7 +256,22 @@ // Destroy the socket if the client has already sent a FIN packet.

const ws = new WebSocket(null);
var protocol = req.headers['sec-websocket-protocol'];
if (protocol) {
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
ws.protocol = protocol;
protocol = protocol.trim().split(/ *, */);
//
// Optionally call external protocol selection handler.
//
if (this.options.handleProtocols) {
protocol = this.options.handleProtocols(protocol, req);
} else {
protocol = protocol[0];
}
if (protocol) {
headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
ws.protocol = protocol;
}
}
if (extensions[PerMessageDeflate.extensionName]) {

@@ -344,3 +343,3 @@ const params = extensions[PerMessageDeflate.extensionName].params;

*/
function abortConnection (socket, code, message) {
function abortHandshake (socket, code, message) {
if (socket.writable) {

@@ -347,0 +346,0 @@ message = message || http.STATUS_CODES[code];

@@ -17,2 +17,3 @@ 'use strict';

const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
const kWebSocket = constants.kWebSocket;
const protocolVersions = [8, 13];

@@ -41,3 +42,2 @@ const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.

this._binaryType = constants.BINARY_TYPES[0];
this._finalize = this.finalize.bind(this);
this._closeFrameReceived = false;

@@ -47,3 +47,2 @@ this._closeFrameSent = false;

this._closeTimer = null;
this._finalized = false;
this._closeCode = 1006;

@@ -55,3 +54,2 @@ this._extensions = {};

this._socket = null;
this._error = null;

@@ -126,45 +124,32 @@ if (address !== null) {

setSocket (socket, head, maxPayload) {
socket.setTimeout(0);
socket.setNoDelay();
const receiver = new Receiver(
this._binaryType,
this._extensions,
maxPayload
);
socket.on('close', this._finalize);
socket.on('error', this._finalize);
socket.on('end', this._finalize);
this._receiver = new Receiver(this._extensions, maxPayload, this.binaryType);
this._sender = new Sender(socket, this._extensions);
this._receiver = receiver;
this._socket = socket;
if (head.length > 0) socket.unshift(head);
receiver[kWebSocket] = this;
socket[kWebSocket] = this;
socket.on('data', this._receiver.add);
receiver.on('conclude', receiverOnConclude);
receiver.on('drain', receiverOnDrain);
receiver.on('error', receiverOnError);
receiver.on('message', receiverOnMessage);
receiver.on('ping', receiverOnPing);
receiver.on('pong', receiverOnPong);
this._receiver.onmessage = (data) => this.emit('message', data);
this._receiver.onping = (data) => {
this.pong(data, !this._isServer, constants.NOOP);
this.emit('ping', data);
};
this._receiver.onpong = (data) => this.emit('pong', data);
this._receiver.onclose = (code, reason) => {
//
// Discard any additional data that is received on the socket.
//
this._socket.removeListener('data', this._receiver.add);
socket.setTimeout(0);
socket.setNoDelay();
this._closeFrameReceived = true;
this._closeMessage = reason;
this._closeCode = code;
if (head.length > 0) socket.unshift(head);
if (code === 1005) this.close();
else this.close(code, reason);
};
this._receiver.onerror = (error, code) => {
if (this._error) return;
socket.on('close', socketOnClose);
socket.on('data', socketOnData);
socket.on('end', socketOnEnd);
socket.on('error', socketOnError);
this._closeCode = code;
if (!this._finalized) this.finalize(error);
else this.emit('error', error);
};
this.readyState = WebSocket.OPEN;

@@ -175,19 +160,10 @@ this.emit('open');

/**
* Clean up internal resources and emit the `'close'` event.
* Emit the `'close'` event.
*
* @param {(Boolean|Error)} error Indicates whether or not an error occurred
* @private
*/
finalize (error) {
if (this._finalized) return;
emitClose () {
this.readyState = WebSocket.CLOSED;
this.readyState = WebSocket.CLOSING;
this._finalized = true;
if (!this._socket) {
//
// `error` is always an `Error` instance in this case.
//
this.emit('error', error);
this.readyState = WebSocket.CLOSED;
this.emit('close', this._closeCode, this._closeMessage);

@@ -197,33 +173,8 @@ return;

clearTimeout(this._closeTimer);
this._socket.removeListener('data', this._receiver.add);
this._socket.removeListener('close', this._finalize);
this._socket.removeListener('error', this._finalize);
this._socket.removeListener('end', this._finalize);
this._socket.on('error', constants.NOOP);
if (error) {
if (error !== true) this._error = error;
this._socket.destroy();
} else {
this._socket.end();
if (this._extensions[PerMessageDeflate.extensionName]) {
this._extensions[PerMessageDeflate.extensionName].cleanup();
}
this._receiver.cleanup(() => {
const err = this._error;
if (err) {
this._error = null;
this.emit('error', err);
}
this.readyState = WebSocket.CLOSED;
if (this._extensions[PerMessageDeflate.extensionName]) {
this._extensions[PerMessageDeflate.extensionName].cleanup();
}
this.emit('close', this._closeCode, this._closeMessage);
});
this._receiver.removeAllListeners();
this.emit('close', this._closeCode, this._closeMessage);
}

@@ -234,17 +185,13 @@

*
* +----------+ +-----------+ +----------+
* + - - -|ws.close()|---->|close frame|-->|ws.close()|- - - -
* +----------+ +-----------+ +----------+ |
* | +----------+ +-----------+ |
* |ws.close()|<----|close frame|<--------+ |
* +----------+ +-----------+ |
* CLOSING | +---+ | CLOSING
* | +---|fin|<------------+
* | | | +---+ |
* | | +---+ +-------------+
* | +----------+-->|fin|----->|ws.finalize()| - - +
* | +---+ +-------------+
* | +-------------+ |
* - - -|ws.finalize()|<--+
* +-------------+
* +----------+ +-----------+ +----------+
* - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
* | +----------+ +-----------+ +----------+ |
* +----------+ +-----------+ |
* CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
* +----------+ +-----------+ |
* | | | +---+ |
* +------------------------+-->|fin| - - - -
* | +---+ | +---+
* - - - - -|fin|<---------------------+
* +---+
*

@@ -258,7 +205,4 @@ * @param {Number} code Status code explaining why the connection is closing

if (this.readyState === WebSocket.CONNECTING) {
this._req.abort();
this.finalize(
new Error('WebSocket was closed before the connection was established')
);
return;
const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
}

@@ -281,10 +225,13 @@

if (!this._finalized) {
if (this._socket.writable) {
if (this._closeFrameReceived) this._socket.end();
//
// Ensure that the connection is cleaned up even when the closing
// handshake fails.
// Ensure that the connection is closed even if the closing handshake
// fails.
//
this._closeTimer = setTimeout(this._finalize, closeTimeout, true);
this._closeTimer = setTimeout(
this._socket.destroy.bind(this._socket),
closeTimeout
);
}

@@ -410,10 +357,10 @@ });

if (this.readyState === WebSocket.CONNECTING) {
this._req.abort();
this.finalize(
new Error('WebSocket was closed before the connection was established')
);
return;
const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
}
this.finalize(true);
if (this._socket) {
this.readyState = WebSocket.CLOSING;
this._socket.destroy();
}
}

@@ -633,26 +580,27 @@ }

this._req = httpObj.get(requestOptions);
var req = this._req = httpObj.get(requestOptions);
if (options.handshakeTimeout) {
this._req.setTimeout(options.handshakeTimeout, () => {
this._req.abort();
this.finalize(new Error('Opening handshake has timed out'));
});
req.setTimeout(
options.handshakeTimeout,
abortHandshake.bind(null, this, req, 'Opening handshake has timed out')
);
}
this._req.on('error', (error) => {
req.on('error', (err) => {
if (this._req.aborted) return;
this._req = null;
this.finalize(error);
req = this._req = null;
this.readyState = WebSocket.CLOSING;
this.emit('error', err);
this.emitClose();
});
this._req.on('response', (res) => {
if (!this.emit('unexpected-response', this._req, res)) {
this._req.abort();
this.finalize(new Error(`Unexpected server response: ${res.statusCode}`));
}
req.on('response', (res) => {
if (this.emit('unexpected-response', req, res)) return;
abortHandshake(this, req, `Unexpected server response: ${res.statusCode}`);
});
this._req.on('upgrade', (res, socket, head) => {
req.on('upgrade', (res, socket, head) => {
this.emit('upgrade', res);

@@ -666,3 +614,3 @@

this._req = null;
req = this._req = null;

@@ -674,4 +622,4 @@ const digest = crypto.createHash('sha1')

if (res.headers['sec-websocket-accept'] !== digest) {
socket.destroy();
return this.finalize(new Error('Invalid Sec-WebSocket-Accept header'));
abortHandshake(this, socket, 'Invalid Sec-WebSocket-Accept header');
return;
}

@@ -692,4 +640,4 @@

if (protError) {
socket.destroy();
return this.finalize(new Error(protError));
abortHandshake(this, socket, protError);
return;
}

@@ -712,4 +660,3 @@

} catch (err) {
socket.destroy();
this.finalize(new Error('Invalid Sec-WebSocket-Extensions header'));
abortHandshake(this, socket, 'Invalid Sec-WebSocket-Extensions header');
return;

@@ -722,1 +669,193 @@ }

}
/**
* Abort the handshake and emit an error.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
* socket to destroy
* @param {String} message The error message
* @private
*/
function abortHandshake (websocket, stream, message) {
websocket.readyState = WebSocket.CLOSING;
const err = new Error(message);
Error.captureStackTrace(err, abortHandshake);
if (stream.setHeader) {
stream.abort();
stream.once('abort', websocket.emitClose.bind(websocket));
websocket.emit('error', err);
} else {
stream.destroy(err);
stream.once('error', websocket.emit.bind(websocket, 'error'));
stream.once('close', websocket.emitClose.bind(websocket));
}
}
/**
* The listener of the `Receiver` `'conclude'` event.
*
* @param {Number} code The status code
* @param {String} reason The reason for closing
* @private
*/
function receiverOnConclude (code, reason) {
const websocket = this[kWebSocket];
websocket._socket.removeListener('data', socketOnData);
websocket._socket.resume();
websocket._closeFrameReceived = true;
websocket._closeMessage = reason;
websocket._closeCode = code;
if (code === 1005) websocket.close();
else websocket.close(code, reason);
}
/**
* The listener of the `Receiver` `'drain'` event.
*
* @private
*/
function receiverOnDrain () {
this[kWebSocket]._socket.resume();
}
/**
* The listener of the `Receiver` `'error'` event.
*
* @param {(RangeError|Error)} err The emitted error
* @private
*/
function receiverOnError (err) {
const websocket = this[kWebSocket];
websocket.readyState = WebSocket.CLOSING;
websocket._closeCode = err[constants.kStatusCode];
websocket.emit('error', err);
websocket._socket.destroy();
}
/**
* The listener of the `Receiver` `'finish'` event.
*
* @private
*/
function receiverOnFinish () {
this[kWebSocket].emitClose();
}
/**
* The listener of the `Receiver` `'message'` event.
*
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
* @private
*/
function receiverOnMessage (data) {
this[kWebSocket].emit('message', data);
}
/**
* The listener of the `Receiver` `'ping'` event.
*
* @param {Buffer} data The data included in the ping frame
* @private
*/
function receiverOnPing (data) {
const websocket = this[kWebSocket];
websocket.pong(data, !websocket._isServer, constants.NOOP);
websocket.emit('ping', data);
}
/**
* The listener of the `Receiver` `'pong'` event.
*
* @param {Buffer} data The data included in the pong frame
* @private
*/
function receiverOnPong (data) {
this[kWebSocket].emit('pong', data);
}
/**
* The listener of the `net.Socket` `'close'` event.
*
* @private
*/
function socketOnClose () {
const websocket = this[kWebSocket];
this.removeListener('close', socketOnClose);
this.removeListener('data', socketOnData);
this.removeListener('end', socketOnEnd);
this[kWebSocket] = undefined;
websocket.readyState = WebSocket.CLOSING;
//
// The close frame might not have been received or the `'end'` event emitted,
// for example, if the socket was destroyed due to an error. Ensure that the
// `receiver` stream is closed after writing any remaining buffered data to
// it.
//
websocket._socket.read();
websocket._receiver.end();
clearTimeout(websocket._closeTimer);
if (
websocket._receiver._writableState.finished ||
websocket._receiver._writableState.errorEmitted
) {
websocket.emitClose();
} else {
websocket._receiver.on('error', receiverOnFinish);
websocket._receiver.on('finish', receiverOnFinish);
}
}
/**
* The listener of the `net.Socket` `'data'` event.
*
* @param {Buffer} chunk A chunk of data
* @private
*/
function socketOnData (chunk) {
if (!this[kWebSocket]._receiver.write(chunk)) {
this.pause();
}
}
/**
* The listener of the `net.Socket` `'end'` event.
*
* @private
*/
function socketOnEnd () {
const websocket = this[kWebSocket];
websocket.readyState = WebSocket.CLOSING;
websocket._receiver.end();
this.end();
}
/**
* The listener of the `net.Socket` `'error'` event.
*
* @private
*/
function socketOnError () {
const websocket = this[kWebSocket];
this.removeListener('error', socketOnError);
this.on('error', constants.NOOP);
if (websocket) {
websocket.readyState = WebSocket.CLOSING;
this.destroy();
}
}
{
"name": "ws",
"version": "4.1.0",
"version": "5.0.0",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",

@@ -29,4 +29,3 @@ "keywords": [

"dependencies": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
"async-limiter": "~1.0.0"
},

@@ -33,0 +32,0 @@ "devDependencies": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc