Socket
Socket
Sign inDemoInstall

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 8.4.0 to 8.16.0

53

lib/buffer-util.js

@@ -5,2 +5,4 @@ 'use strict';

const FastBuffer = Buffer[Symbol.species];
/**

@@ -27,3 +29,5 @@ * Merges an array of buffers into a new buffer.

if (offset < totalLength) return target.slice(0, offset);
if (offset < totalLength) {
return new FastBuffer(target.buffer, target.byteOffset, offset);
}

@@ -70,7 +74,7 @@ return target;

function toArrayBuffer(buf) {
if (buf.byteLength === buf.buffer.byteLength) {
if (buf.length === buf.buffer.byteLength) {
return buf.buffer;
}
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
}

@@ -94,5 +98,5 @@

if (data instanceof ArrayBuffer) {
buf = Buffer.from(data);
buf = new FastBuffer(data);
} else if (ArrayBuffer.isView(data)) {
buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
} else {

@@ -106,26 +110,27 @@ buf = Buffer.from(data);

try {
const bufferUtil = require('bufferutil');
module.exports = {
concat,
mask: _mask,
toArrayBuffer,
toBuffer,
unmask: _unmask
};
module.exports = {
concat,
mask(source, mask, output, offset, length) {
/* istanbul ignore else */
if (!process.env.WS_NO_BUFFER_UTIL) {
try {
const bufferUtil = require('bufferutil');
module.exports.mask = function (source, mask, output, offset, length) {
if (length < 48) _mask(source, mask, output, offset, length);
else bufferUtil.mask(source, mask, output, offset, length);
},
toArrayBuffer,
toBuffer,
unmask(buffer, mask) {
};
module.exports.unmask = function (buffer, mask) {
if (buffer.length < 32) _unmask(buffer, mask);
else bufferUtil.unmask(buffer, mask);
}
};
} catch (e) /* istanbul ignore next */ {
module.exports = {
concat,
mask: _mask,
toArrayBuffer,
toBuffer,
unmask: _unmask
};
};
} catch (e) {
// Continue regardless of the error.
}
}

@@ -181,3 +181,3 @@ 'use strict';

* @param {String} type A string representing the event type to listen for
* @param {Function} listener The listener to add
* @param {(Function|Object)} handler The listener to add
* @param {Object} [options] An options object specifies characteristics about

@@ -190,3 +190,13 @@ * the event listener

*/
addEventListener(type, listener, options = {}) {
addEventListener(type, handler, options = {}) {
for (const listener of this.listeners(type)) {
if (
!options[kForOnEventAttribute] &&
listener[kListener] === handler &&
!listener[kForOnEventAttribute]
) {
return;
}
}
let wrapper;

@@ -201,3 +211,3 @@

event[kTarget] = this;
listener.call(this, event);
callListener(handler, this, event);
};

@@ -213,3 +223,3 @@ } else if (type === 'close') {

event[kTarget] = this;
listener.call(this, event);
callListener(handler, this, event);
};

@@ -224,3 +234,3 @@ } else if (type === 'error') {

event[kTarget] = this;
listener.call(this, event);
callListener(handler, this, event);
};

@@ -232,3 +242,3 @@ } else if (type === 'open') {

event[kTarget] = this;
listener.call(this, event);
callListener(handler, this, event);
};

@@ -240,3 +250,3 @@ } else {

wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute];
wrapper[kListener] = listener;
wrapper[kListener] = handler;

@@ -254,3 +264,3 @@ if (options.once) {

* @param {String} type A string representing the event type to remove
* @param {Function} handler The listener to remove
* @param {(Function|Object)} handler The listener to remove
* @public

@@ -275,1 +285,17 @@ */

};
/**
* Call an event listener
*
* @param {(Function|Object)} listener The listener to call
* @param {*} thisArg The value to use as `this`` when calling the listener
* @param {Event} event The event to pass to the listener
* @private
*/
function callListener(listener, thisArg, event) {
if (typeof listener === 'object' && listener.handleEvent) {
listener.handleEvent.call(listener, event);
} else {
listener.call(thisArg, event);
}
}

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

const FastBuffer = Buffer[Symbol.species];
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);

@@ -317,3 +318,3 @@ const kPerMessageDeflate = Symbol('permessage-deflate');

*
* @param {Buffer} data Data to compress
* @param {(Buffer|String)} data Data to compress
* @param {Boolean} fin Specifies whether or not this is the last fragment

@@ -400,3 +401,3 @@ * @param {Function} callback Callback

*
* @param {Buffer} data Data to compress
* @param {(Buffer|String)} data Data to compress
* @param {Boolean} fin Specifies whether or not this is the last fragment

@@ -443,3 +444,5 @@ * @param {Function} callback Callback

if (fin) data = data.slice(0, data.length - 4);
if (fin) {
data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4);
}

@@ -446,0 +449,0 @@ //

@@ -15,2 +15,11 @@ 'use strict';

const FastBuffer = Buffer[Symbol.species];
const promise = Promise.resolve();
//
// `queueMicrotask()` is not available in Node.js < 11.
//
const queueTask =
typeof queueMicrotask === 'function' ? queueMicrotask : queueMicrotaskShim;
const GET_INFO = 0;

@@ -22,2 +31,3 @@ const GET_PAYLOAD_LENGTH_16 = 1;

const INFLATING = 5;
const DEFER_EVENT = 6;

@@ -34,2 +44,5 @@ /**

* @param {Object} [options] Options object
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
* multiple times in the same tick
* @param {String} [options.binaryType=nodebuffer] The type for binary data

@@ -47,2 +60,3 @@ * @param {Object} [options.extensions] An object containing the negotiated

this._allowSynchronousEvents = !!options.allowSynchronousEvents;
this._binaryType = options.binaryType || BINARY_TYPES[0];

@@ -70,4 +84,5 @@ this._extensions = options.extensions || {};

this._errored = false;
this._loop = false;
this._state = GET_INFO;
this._loop = false;
}

@@ -105,4 +120,9 @@

const buf = this._buffers[0];
this._buffers[0] = buf.slice(n);
return buf.slice(0, n);
this._buffers[0] = new FastBuffer(
buf.buffer,
buf.byteOffset + n,
buf.length - n
);
return new FastBuffer(buf.buffer, buf.byteOffset, n);
}

@@ -120,3 +140,7 @@

dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset);
this._buffers[0] = buf.slice(n);
this._buffers[0] = new FastBuffer(
buf.buffer,
buf.byteOffset + n,
buf.length - n
);
}

@@ -137,3 +161,2 @@

startLoop(cb) {
let err;
this._loop = true;

@@ -144,9 +167,9 @@

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

@@ -157,6 +180,6 @@ case GET_MASK:

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

@@ -167,3 +190,3 @@ return;

cb(err);
if (!this._errored) cb();
}

@@ -174,6 +197,6 @@

*
* @return {(RangeError|undefined)} A possible error
* @param {Function} cb Callback
* @private
*/
getInfo() {
getInfo(cb) {
if (this._bufferedBytes < 2) {

@@ -187,4 +210,3 @@ this._loop = false;

if ((buf[0] & 0x30) !== 0x00) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -196,2 +218,5 @@ 'RSV2 and RSV3 must be clear',

);
cb(error);
return;
}

@@ -202,4 +227,3 @@

if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -211,2 +235,5 @@ 'RSV1 must be clear',

);
cb(error);
return;
}

@@ -220,4 +247,3 @@

if (compressed) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -229,7 +255,9 @@ 'RSV1 must be clear',

);
cb(error);
return;
}
if (!this._fragmented) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -241,2 +269,5 @@ 'invalid opcode 0',

);
cb(error);
return;
}

@@ -247,4 +278,3 @@

if (this._fragmented) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -256,2 +286,5 @@ `invalid opcode ${this._opcode}`,

);
cb(error);
return;
}

@@ -262,4 +295,3 @@

if (!this._fin) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -271,7 +303,9 @@ 'FIN must be set',

);
cb(error);
return;
}
if (compressed) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -283,7 +317,12 @@ 'RSV1 must be clear',

);
cb(error);
return;
}
if (this._payloadLength > 0x7d) {
this._loop = false;
return error(
if (
this._payloadLength > 0x7d ||
(this._opcode === 0x08 && this._payloadLength === 1)
) {
const error = this.createError(
RangeError,

@@ -295,6 +334,8 @@ `invalid payload length ${this._payloadLength}`,

);
cb(error);
return;
}
} else {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -306,2 +347,5 @@ `invalid opcode ${this._opcode}`,

);
cb(error);
return;
}

@@ -314,4 +358,3 @@

if (!this._masked) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -323,6 +366,8 @@ 'MASK must be set',

);
cb(error);
return;
}
} else if (this._masked) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -334,2 +379,5 @@ 'MASK must be clear',

);
cb(error);
return;
}

@@ -339,3 +387,3 @@

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

@@ -346,6 +394,6 @@

*
* @return {(RangeError|undefined)} A possible error
* @param {Function} cb Callback
* @private
*/
getPayloadLength16() {
getPayloadLength16(cb) {
if (this._bufferedBytes < 2) {

@@ -357,3 +405,3 @@ this._loop = false;

this._payloadLength = this.consume(2).readUInt16BE(0);
return this.haveLength();
this.haveLength(cb);
}

@@ -364,6 +412,6 @@

*
* @return {(RangeError|undefined)} A possible error
* @param {Function} cb Callback
* @private
*/
getPayloadLength64() {
getPayloadLength64(cb) {
if (this._bufferedBytes < 8) {

@@ -382,4 +430,3 @@ this._loop = false;

if (num > Math.pow(2, 53 - 32) - 1) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -391,6 +438,9 @@ 'Unsupported WebSocket frame: payload length > 2^53 - 1',

);
cb(error);
return;
}
this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4);
return this.haveLength();
this.haveLength(cb);
}

@@ -401,11 +451,10 @@

*
* @return {(RangeError|undefined)} A possible error
* @param {Function} cb Callback
* @private
*/
haveLength() {
haveLength(cb) {
if (this._payloadLength && this._opcode < 0x08) {
this._totalPayloadLength += this._payloadLength;
if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) {
this._loop = false;
return error(
const error = this.createError(
RangeError,

@@ -417,2 +466,5 @@ 'Max payload size exceeded',

);
cb(error);
return;
}

@@ -444,3 +496,2 @@ }

* @param {Function} cb Callback
* @return {(Error|RangeError|undefined)} A possible error
* @private

@@ -467,3 +518,6 @@ */

if (this._opcode > 0x07) return this.controlMessage(data);
if (this._opcode > 0x07) {
this.controlMessage(data, cb);
return;
}

@@ -485,3 +539,3 @@ if (this._compressed) {

return this.dataMessage();
this.dataMessage(cb);
}

@@ -505,11 +559,12 @@

if (this._messageLength > this._maxPayload && this._maxPayload > 0) {
return cb(
error(
RangeError,
'Max payload size exceeded',
false,
1009,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
)
const error = this.createError(
RangeError,
'Max payload size exceeded',
false,
1009,
'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'
);
cb(error);
return;
}

@@ -520,6 +575,4 @@

const er = this.dataMessage();
if (er) return cb(er);
this.startLoop(cb);
this.dataMessage(cb);
if (this._state === GET_INFO) this.startLoop(cb);
});

@@ -531,46 +584,74 @@ }

*
* @return {(Error|undefined)} A possible error
* @param {Function} cb Callback
* @private
*/
dataMessage() {
if (this._fin) {
const messageLength = this._messageLength;
const fragments = this._fragments;
dataMessage(cb) {
if (!this._fin) {
this._state = GET_INFO;
return;
}
this._totalPayloadLength = 0;
this._messageLength = 0;
this._fragmented = 0;
this._fragments = [];
const messageLength = this._messageLength;
const fragments = this._fragments;
if (this._opcode === 2) {
let data;
this._totalPayloadLength = 0;
this._messageLength = 0;
this._fragmented = 0;
this._fragments = [];
if (this._binaryType === 'nodebuffer') {
data = concat(fragments, messageLength);
} else if (this._binaryType === 'arraybuffer') {
data = toArrayBuffer(concat(fragments, messageLength));
} else {
data = fragments;
}
if (this._opcode === 2) {
let data;
if (this._binaryType === 'nodebuffer') {
data = concat(fragments, messageLength);
} else if (this._binaryType === 'arraybuffer') {
data = toArrayBuffer(concat(fragments, messageLength));
} else {
data = fragments;
}
//
// If the state is `INFLATING`, it means that the frame data was
// decompressed asynchronously, so there is no need to defer the event
// as it will be emitted asynchronously anyway.
//
if (this._state === INFLATING || this._allowSynchronousEvents) {
this.emit('message', data, true);
this._state = GET_INFO;
} else {
const buf = concat(fragments, messageLength);
this._state = DEFER_EVENT;
queueTask(() => {
this.emit('message', data, true);
this._state = GET_INFO;
this.startLoop(cb);
});
}
} else {
const buf = concat(fragments, messageLength);
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
this._loop = false;
return error(
Error,
'invalid UTF-8 sequence',
true,
1007,
'WS_ERR_INVALID_UTF8'
);
}
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
const error = this.createError(
Error,
'invalid UTF-8 sequence',
true,
1007,
'WS_ERR_INVALID_UTF8'
);
cb(error);
return;
}
if (this._state === INFLATING || this._allowSynchronousEvents) {
this.emit('message', buf, false);
this._state = GET_INFO;
} else {
this._state = DEFER_EVENT;
queueTask(() => {
this.emit('message', buf, false);
this._state = GET_INFO;
this.startLoop(cb);
});
}
}
this._state = GET_INFO;
}

@@ -585,17 +666,8 @@

*/
controlMessage(data) {
controlMessage(data, cb) {
if (this._opcode === 0x08) {
this._loop = false;
if (data.length === 0) {
this._loop = false;
this.emit('conclude', 1005, EMPTY_BUFFER);
this.end();
} else if (data.length === 1) {
return error(
RangeError,
'invalid payload length 1',
true,
1002,
'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH'
);
} else {

@@ -605,3 +677,3 @@ const code = data.readUInt16BE(0);

if (!isValidStatusCode(code)) {
return error(
const error = this.createError(
RangeError,

@@ -613,8 +685,15 @@ `invalid status code ${code}`,

);
cb(error);
return;
}
const buf = data.slice(2);
const buf = new FastBuffer(
data.buffer,
data.byteOffset + 2,
data.length - 2
);
if (!this._skipUTF8Validation && !isValidUTF8(buf)) {
return error(
const error = this.createError(
Error,

@@ -626,14 +705,53 @@ 'invalid UTF-8 sequence',

);
cb(error);
return;
}
this._loop = false;
this.emit('conclude', code, buf);
this.end();
}
} else if (this._opcode === 0x09) {
this.emit('ping', data);
this._state = GET_INFO;
return;
}
if (this._allowSynchronousEvents) {
this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
this._state = GET_INFO;
} else {
this.emit('pong', data);
this._state = DEFER_EVENT;
queueTask(() => {
this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
this._state = GET_INFO;
this.startLoop(cb);
});
}
}
this._state = GET_INFO;
/**
* Builds an error object.
*
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
* @param {String} message The error message
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
* `message`
* @param {Number} statusCode The status code
* @param {String} errorCode The exposed error code
* @return {(Error|RangeError)} The error
* @private
*/
createError(ErrorCtor, message, prefix, statusCode, errorCode) {
this._loop = false;
this._errored = true;
const err = new ErrorCtor(
prefix ? `Invalid WebSocket frame: ${message}` : message
);
Error.captureStackTrace(err, this.createError);
err.code = errorCode;
err[kStatusCode] = statusCode;
return err;
}

@@ -645,22 +763,28 @@ }

/**
* Builds an error object.
* A shim for `queueMicrotask()`.
*
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
* @param {String} message The error message
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
* `message`
* @param {Number} statusCode The status code
* @param {String} errorCode The exposed error code
* @return {(Error|RangeError)} The error
* @param {Function} cb Callback
*/
function queueMicrotaskShim(cb) {
promise.then(cb).catch(throwErrorNextTick);
}
/**
* Throws an error.
*
* @param {Error} err The error to throw
* @private
*/
function error(ErrorCtor, message, prefix, statusCode, errorCode) {
const err = new ErrorCtor(
prefix ? `Invalid WebSocket frame: ${message}` : message
);
function throwError(err) {
throw err;
}
Error.captureStackTrace(err, error);
err.code = errorCode;
err[kStatusCode] = statusCode;
return err;
/**
* Throws an error in the next tick.
*
* @param {Error} err The error to throw
* @private
*/
function throwErrorNextTick(err) {
process.nextTick(throwError, err);
}

@@ -1,7 +0,6 @@

/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */
'use strict';
const net = require('net');
const tls = require('tls');
const { Duplex } = require('stream');
const { randomFillSync } = require('crypto');

@@ -14,2 +13,3 @@

const kByteLength = Symbol('kByteLength');
const maskBuffer = Buffer.alloc(4);

@@ -24,3 +24,3 @@

*
* @param {(net.Socket|tls.Socket)} socket The connection socket
* @param {Duplex} socket The connection socket
* @param {Object} [extensions] An object containing the negotiated extensions

@@ -51,3 +51,3 @@ * @param {Function} [generateMask] The function used to generate the masking

*
* @param {Buffer} data The data to frame
* @param {(Buffer|String)} data The data to frame
* @param {Object} options Options object

@@ -67,3 +67,3 @@ * @param {Boolean} [options.fin=false] Specifies whether or not to set the

* RSV1 bit
* @return {Buffer[]} The framed data as a list of `Buffer` instances
* @return {(Buffer|String)[]} The framed data
* @public

@@ -87,13 +87,28 @@ */

skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
if (options.readOnly && !skipMasking) merge = true;
offset = 6;
}
let payloadLength = data.length;
let dataLength;
if (data.length >= 65536) {
if (typeof data === 'string') {
if (
(!options.mask || skipMasking) &&
options[kByteLength] !== undefined
) {
dataLength = options[kByteLength];
} else {
data = Buffer.from(data);
dataLength = data.length;
}
} else {
dataLength = data.length;
merge = options.mask && options.readOnly && !skipMasking;
}
let payloadLength = dataLength;
if (dataLength >= 65536) {
offset += 8;
payloadLength = 127;
} else if (data.length > 125) {
} else if (dataLength > 125) {
offset += 2;

@@ -103,3 +118,3 @@ payloadLength = 126;

const target = Buffer.allocUnsafe(merge ? data.length + offset : offset);
const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);

@@ -112,6 +127,6 @@ target[0] = options.fin ? options.opcode | 0x80 : options.opcode;

if (payloadLength === 126) {
target.writeUInt16BE(data.length, 2);
target.writeUInt16BE(dataLength, 2);
} else if (payloadLength === 127) {
target[2] = target[3] = 0;
target.writeUIntBE(data.length, 4, 6);
target.writeUIntBE(dataLength, 4, 6);
}

@@ -130,7 +145,7 @@

if (merge) {
applyMask(data, mask, target, offset, data.length);
applyMask(data, mask, target, offset, dataLength);
return [target];
}
applyMask(data, mask, data, 0, data.length);
applyMask(data, mask, data, 0, dataLength);
return [target, data];

@@ -175,6 +190,17 @@ }

const options = {
[kByteLength]: buf.length,
fin: true,
generateMask: this._generateMask,
mask,
maskBuffer: this._maskBuffer,
opcode: 0x08,
readOnly: false,
rsv1: false
};
if (this._deflating) {
this.enqueue([this.doClose, buf, mask, cb]);
this.enqueue([this.dispatch, buf, false, options, cb]);
} else {
this.doClose(buf, mask, cb);
this.sendFrame(Sender.frame(buf, options), cb);
}

@@ -184,25 +210,2 @@ }

/**
* Frames and sends a close message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback
* @private
*/
doClose(data, mask, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x08,
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: false
}),
cb
);
}
/**
* Sends a ping message to the other peer.

@@ -216,12 +219,33 @@ *

ping(data, mask, cb) {
const buf = toBuffer(data);
let byteLength;
let readOnly;
if (buf.length > 125) {
if (typeof data === 'string') {
byteLength = Buffer.byteLength(data);
readOnly = false;
} else {
data = toBuffer(data);
byteLength = data.length;
readOnly = toBuffer.readOnly;
}
if (byteLength > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
}
const options = {
[kByteLength]: byteLength,
fin: true,
generateMask: this._generateMask,
mask,
maskBuffer: this._maskBuffer,
opcode: 0x09,
readOnly,
rsv1: false
};
if (this._deflating) {
this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.dispatch, data, false, options, cb]);
} else {
this.doPing(buf, mask, toBuffer.readOnly, cb);
this.sendFrame(Sender.frame(data, options), cb);
}

@@ -231,26 +255,2 @@ }

/**
* Frames and sends a ping message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @private
*/
doPing(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x09,
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly
}),
cb
);
}
/**
* Sends a pong message to the other peer.

@@ -264,12 +264,33 @@ *

pong(data, mask, cb) {
const buf = toBuffer(data);
let byteLength;
let readOnly;
if (buf.length > 125) {
if (typeof data === 'string') {
byteLength = Buffer.byteLength(data);
readOnly = false;
} else {
data = toBuffer(data);
byteLength = data.length;
readOnly = toBuffer.readOnly;
}
if (byteLength > 125) {
throw new RangeError('The data size must not be greater than 125 bytes');
}
const options = {
[kByteLength]: byteLength,
fin: true,
generateMask: this._generateMask,
mask,
maskBuffer: this._maskBuffer,
opcode: 0x0a,
readOnly,
rsv1: false
};
if (this._deflating) {
this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]);
this.enqueue([this.dispatch, data, false, options, cb]);
} else {
this.doPong(buf, mask, toBuffer.readOnly, cb);
this.sendFrame(Sender.frame(data, options), cb);
}

@@ -279,26 +300,2 @@ }

/**
* Frames and sends a pong message.
*
* @param {Buffer} data The message to send
* @param {Boolean} [mask=false] Specifies whether or not to mask `data`
* @param {Boolean} [readOnly=false] Specifies whether `data` can be modified
* @param {Function} [cb] Callback
* @private
*/
doPong(data, mask, readOnly, cb) {
this.sendFrame(
Sender.frame(data, {
fin: true,
rsv1: false,
opcode: 0x0a,
mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly
}),
cb
);
}
/**
* Sends a data message to the other peer.

@@ -320,3 +317,2 @@ *

send(data, options, cb) {
const buf = toBuffer(data);
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];

@@ -326,2 +322,14 @@ let opcode = options.binary ? 2 : 1;

let byteLength;
let readOnly;
if (typeof data === 'string') {
byteLength = Buffer.byteLength(data);
readOnly = false;
} else {
data = toBuffer(data);
byteLength = data.length;
readOnly = toBuffer.readOnly;
}
if (this._firstFragment) {

@@ -338,3 +346,3 @@ this._firstFragment = false;

) {
rsv1 = buf.length >= perMessageDeflate._threshold;
rsv1 = byteLength >= perMessageDeflate._threshold;
}

@@ -351,26 +359,28 @@ this._compress = rsv1;

const opts = {
[kByteLength]: byteLength,
fin: options.fin,
rsv1,
opcode,
generateMask: this._generateMask,
mask: options.mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: toBuffer.readOnly
opcode,
readOnly,
rsv1
};
if (this._deflating) {
this.enqueue([this.dispatch, buf, this._compress, opts, cb]);
this.enqueue([this.dispatch, data, this._compress, opts, cb]);
} else {
this.dispatch(buf, this._compress, opts, cb);
this.dispatch(data, this._compress, opts, cb);
}
} else {
this.sendFrame(
Sender.frame(buf, {
Sender.frame(data, {
[kByteLength]: byteLength,
fin: options.fin,
rsv1: false,
opcode,
generateMask: this._generateMask,
mask: options.mask,
maskBuffer: this._maskBuffer,
generateMask: this._generateMask,
readOnly: toBuffer.readOnly
opcode,
readOnly,
rsv1: false
}),

@@ -383,9 +393,8 @@ cb

/**
* Dispatches a data message.
* Dispatches a message.
*
* @param {Buffer} data The message to send
* @param {(Buffer|String)} data The message to send
* @param {Boolean} [compress=false] Specifies whether or not to compress
* `data`
* @param {Object} options Options object
* @param {Number} options.opcode The opcode
* @param {Boolean} [options.fin=false] Specifies whether or not to set the

@@ -399,2 +408,3 @@ * FIN bit

* key
* @param {Number} options.opcode The opcode
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be

@@ -415,3 +425,3 @@ * modified

this._bufferedBytes += data.length;
this._bufferedBytes += options[kByteLength];
this._deflating = true;

@@ -427,3 +437,4 @@ perMessageDeflate.compress(data, options.fin, (_, buf) => {

for (let i = 0; i < this._queue.length; i++) {
const callback = this._queue[i][4];
const params = this._queue[i];
const callback = params[params.length - 1];

@@ -436,3 +447,3 @@ if (typeof callback === 'function') callback(err);

this._bufferedBytes -= data.length;
this._bufferedBytes -= options[kByteLength];
this._deflating = false;

@@ -454,3 +465,3 @@ options.readOnly = false;

this._bufferedBytes -= params[1].length;
this._bufferedBytes -= params[3][kByteLength];
Reflect.apply(params[0], this, params.slice(1));

@@ -467,3 +478,3 @@ }

enqueue(params) {
this._bufferedBytes += params[1].length;
this._bufferedBytes += params[3][kByteLength];
this._queue.push(params);

@@ -470,0 +481,0 @@ }

'use strict';
const { isUtf8 } = require('buffer');
//

@@ -108,18 +110,22 @@ // Allowed token characters:

try {
const isValidUTF8 = require('utf-8-validate');
module.exports = {
isValidStatusCode,
isValidUTF8: _isValidUTF8,
tokenChars
};
module.exports = {
isValidStatusCode,
isValidUTF8(buf) {
return buf.length < 150 ? _isValidUTF8(buf) : isValidUTF8(buf);
},
tokenChars
if (isUtf8) {
module.exports.isValidUTF8 = function (buf) {
return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf);
};
} catch (e) /* istanbul ignore next */ {
module.exports = {
isValidStatusCode,
isValidUTF8: _isValidUTF8,
tokenChars
};
} /* istanbul ignore else */ else if (!process.env.WS_NO_UTF_8_VALIDATE) {
try {
const isValidUTF8 = require('utf-8-validate');
module.exports.isValidUTF8 = function (buf) {
return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
};
} catch (e) {
// Continue regardless of the error.
}
}

@@ -1,2 +0,2 @@

/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$" }] */

@@ -7,5 +7,3 @@ 'use strict';

const http = require('http');
const https = require('https');
const net = require('net');
const tls = require('tls');
const { Duplex } = require('stream');
const { createHash } = require('crypto');

@@ -35,2 +33,7 @@

* @param {Object} options Configuration options
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
* multiple times in the same tick
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
* automatically send a pong in response to a ping
* @param {Number} [options.backlog=511] The maximum length of the queue of

@@ -54,2 +57,4 @@ * pending connections

* @param {Function} [options.verifyClient] A hook to reject connections
* @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket`
* class to use. It must be the `WebSocket` class or class that extends it
* @param {Function} [callback] A listener for the `listening` event

@@ -61,2 +66,4 @@ */

options = {
allowSynchronousEvents: false,
autoPong: true,
maxPayload: 100 * 1024 * 1024,

@@ -74,2 +81,3 @@ skipUTF8Validation: false,

port: null,
WebSocket,
...options

@@ -226,4 +234,3 @@ };

* @param {http.IncomingMessage} req The request object
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {Duplex} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream

@@ -236,19 +243,34 @@ * @param {Function} cb Callback

const key =
req.headers['sec-websocket-key'] !== undefined
? req.headers['sec-websocket-key']
: false;
const key = req.headers['sec-websocket-key'];
const version = +req.headers['sec-websocket-version'];
if (
req.method !== 'GET' ||
req.headers.upgrade.toLowerCase() !== 'websocket' ||
!key ||
!keyRegex.test(key) ||
(version !== 8 && version !== 13) ||
!this.shouldHandle(req)
) {
return abortHandshake(socket, 400);
if (req.method !== 'GET') {
const message = 'Invalid HTTP method';
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
return;
}
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
const message = 'Invalid Upgrade header';
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
return;
}
if (!key || !keyRegex.test(key)) {
const message = 'Missing or invalid Sec-WebSocket-Key header';
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
return;
}
if (version !== 8 && version !== 13) {
const message = 'Missing or invalid Sec-WebSocket-Version header';
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
return;
}
if (!this.shouldHandle(req)) {
abortHandshake(socket, 400);
return;
}
const secWebSocketProtocol = req.headers['sec-websocket-protocol'];

@@ -261,3 +283,5 @@ let protocols = new Set();

} catch (err) {
return abortHandshake(socket, 400);
const message = 'Invalid Sec-WebSocket-Protocol header';
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
return;
}

@@ -287,3 +311,6 @@ }

} catch (err) {
return abortHandshake(socket, 400);
const message =
'Invalid or unacceptable Sec-WebSocket-Extensions header';
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
return;
}

@@ -335,4 +362,3 @@ }

* @param {http.IncomingMessage} req The request object
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {Duplex} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream

@@ -369,3 +395,3 @@ * @param {Function} cb Callback

const ws = new WebSocket(null);
const ws = new this.options.WebSocket(null, undefined, this.options);

@@ -404,2 +430,3 @@ if (protocols.size) {

ws.setSocket(socket, head, {
allowSynchronousEvents: this.options.allowSynchronousEvents,
maxPayload: this.options.maxPayload,

@@ -458,3 +485,3 @@ skipUTF8Validation: this.options.skipUTF8Validation

/**
* Handle premature socket errors.
* Handle socket errors.
*

@@ -470,3 +497,3 @@ * @private

*
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
* @param {Duplex} socket The socket of the upgrade request
* @param {Number} code The HTTP response status code

@@ -478,23 +505,50 @@ * @param {String} [message] The HTTP response body

function abortHandshake(socket, code, message, headers) {
if (socket.writable) {
message = message || http.STATUS_CODES[code];
headers = {
Connection: 'close',
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(message),
...headers
};
//
// The socket is writable unless the user destroyed or ended it before calling
// `server.handleUpgrade()` or in the `verifyClient` function, which is a user
// error. Handling this does not make much sense as the worst that can happen
// is that some of the data written by the user might be discarded due to the
// call to `socket.end()` below, which triggers an `'error'` event that in
// turn causes the socket to be destroyed.
//
message = message || http.STATUS_CODES[code];
headers = {
Connection: 'close',
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(message),
...headers
};
socket.write(
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
Object.keys(headers)
.map((h) => `${h}: ${headers[h]}`)
.join('\r\n') +
'\r\n\r\n' +
message
);
}
socket.once('finish', socket.destroy);
socket.removeListener('error', socketOnError);
socket.destroy();
socket.end(
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r\n` +
Object.keys(headers)
.map((h) => `${h}: ${headers[h]}`)
.join('\r\n') +
'\r\n\r\n' +
message
);
}
/**
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
* one listener for it, otherwise call `abortHandshake()`.
*
* @param {WebSocketServer} server The WebSocket server
* @param {http.IncomingMessage} req The request object
* @param {Duplex} socket The socket of the upgrade request
* @param {Number} code The HTTP response status code
* @param {String} message The HTTP response body
* @private
*/
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) {
if (server.listenerCount('wsClientError')) {
const err = new Error(message);
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
server.emit('wsClientError', err, socket, req);
} else {
abortHandshake(socket, code, message);
}
}

@@ -1,2 +0,2 @@

/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$" }] */

@@ -11,3 +11,3 @@ 'use strict';

const { randomBytes, createHash } = require('crypto');
const { Readable } = require('stream');
const { Duplex, Readable } = require('stream');
const { URL } = require('url');

@@ -34,6 +34,7 @@

const closeTimeout = 30 * 1000;
const kAborted = Symbol('kAborted');
const protocolVersions = [8, 13];
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
const protocolVersions = [8, 13];
const closeTimeout = 30 * 1000;

@@ -88,2 +89,3 @@ /**

} else {
this._autoPong = options.autoPong;
this._isServer = true;

@@ -194,6 +196,8 @@ }

*
* @param {(net.Socket|tls.Socket)} socket The network socket between the
* server and client
* @param {Duplex} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Object} options Options object
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
* multiple times in the same tick
* @param {Function} [options.generateMask] The function used to generate the

@@ -208,2 +212,3 @@ * masking key

const receiver = new Receiver({
allowSynchronousEvents: options.allowSynchronousEvents,
binaryType: this.binaryType,

@@ -230,4 +235,7 @@ extensions: this._extensions,

socket.setTimeout(0);
socket.setNoDelay();
//
// These methods may not be available if `socket` is just a `Duplex`.
//
if (socket.setTimeout) socket.setTimeout(0);
if (socket.setNoDelay) socket.setNoDelay();

@@ -290,3 +298,4 @@ if (head.length > 0) socket.unshift(head);

const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
abortHandshake(this, this._req, msg);
return;
}

@@ -486,3 +495,4 @@

const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
abortHandshake(this, this._req, msg);
return;
}

@@ -624,2 +634,9 @@

* @param {Object} [options] Connection options
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether any
* of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple
* times in the same tick
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
* automatically send a pong in response to a ping
* @param {Function} [options.finishRequest] A function which can be used to
* customize the headers of each http request before it is sent
* @param {Boolean} [options.followRedirects=false] Whether or not to follow

@@ -647,2 +664,4 @@ * redirects

const opts = {
allowSynchronousEvents: false,
autoPong: true,
protocolVersion: protocolVersions[1],

@@ -660,3 +679,3 @@ maxPayload: 100 * 1024 * 1024,

timeout: undefined,
method: undefined,
method: 'GET',
host: undefined,

@@ -667,2 +686,4 @@ path: undefined,

websocket._autoPong = opts.autoPong;
if (!protocolVersions.includes(opts.protocolVersion)) {

@@ -679,3 +700,2 @@ throw new RangeError(

parsedUrl = address;
websocket._url = address.href;
} else {

@@ -687,21 +707,28 @@ try {

}
}
websocket._url = address;
if (parsedUrl.protocol === 'http:') {
parsedUrl.protocol = 'ws:';
} else if (parsedUrl.protocol === 'https:') {
parsedUrl.protocol = 'wss:';
}
websocket._url = parsedUrl.href;
const isSecure = parsedUrl.protocol === 'wss:';
const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
let invalidURLMessage;
const isIpcUrl = parsedUrl.protocol === 'ws+unix:';
let invalidUrlMessage;
if (parsedUrl.protocol !== 'ws:' && !isSecure && !isUnixSocket) {
invalidURLMessage =
'The URL\'s protocol must be one of "ws:", "wss:", or "ws+unix:"';
} else if (isUnixSocket && !parsedUrl.pathname) {
invalidURLMessage = "The URL's pathname is empty";
if (parsedUrl.protocol !== 'ws:' && !isSecure && !isIpcUrl) {
invalidUrlMessage =
'The URL\'s protocol must be one of "ws:", "wss:", ' +
'"http:", "https", or "ws+unix:"';
} else if (isIpcUrl && !parsedUrl.pathname) {
invalidUrlMessage = "The URL's pathname is empty";
} else if (parsedUrl.hash) {
invalidURLMessage = 'The URL contains a fragment identifier';
invalidUrlMessage = 'The URL contains a fragment identifier';
}
if (invalidURLMessage) {
const err = new SyntaxError(invalidURLMessage);
if (invalidUrlMessage) {
const err = new SyntaxError(invalidUrlMessage);

@@ -718,3 +745,3 @@ if (websocket._redirects === 0) {

const key = randomBytes(16).toString('base64');
const get = isSecure ? https.get : http.get;
const request = isSecure ? https.request : http.request;
const protocolSet = new Set();

@@ -730,7 +757,7 @@ let perMessageDeflate;

opts.headers = {
...opts.headers,
'Sec-WebSocket-Version': opts.protocolVersion,
'Sec-WebSocket-Key': key,
Connection: 'Upgrade',
Upgrade: 'websocket',
...opts.headers
Upgrade: 'websocket'
};

@@ -778,3 +805,3 @@ opts.path = parsedUrl.pathname + parsedUrl.search;

if (isUnixSocket) {
if (isIpcUrl) {
const parts = opts.path.split(':');

@@ -786,4 +813,76 @@

let req = (websocket._req = get(opts));
let req;
if (opts.followRedirects) {
if (websocket._redirects === 0) {
websocket._originalIpc = isIpcUrl;
websocket._originalSecure = isSecure;
websocket._originalHostOrSocketPath = isIpcUrl
? opts.socketPath
: parsedUrl.host;
const headers = options && options.headers;
//
// Shallow copy the user provided options so that headers can be changed
// without mutating the original object.
//
options = { ...options, headers: {} };
if (headers) {
for (const [key, value] of Object.entries(headers)) {
options.headers[key.toLowerCase()] = value;
}
}
} else if (websocket.listenerCount('redirect') === 0) {
const isSameHost = isIpcUrl
? websocket._originalIpc
? opts.socketPath === websocket._originalHostOrSocketPath
: false
: websocket._originalIpc
? false
: parsedUrl.host === websocket._originalHostOrSocketPath;
if (!isSameHost || (websocket._originalSecure && !isSecure)) {
//
// Match curl 7.77.0 behavior and drop the following headers. These
// headers are also dropped when following a redirect to a subdomain.
//
delete opts.headers.authorization;
delete opts.headers.cookie;
if (!isSameHost) delete opts.headers.host;
opts.auth = undefined;
}
}
//
// Match curl 7.77.0 behavior and make the first `Authorization` header win.
// If the `Authorization` header is set, then there is nothing to do as it
// will take precedence.
//
if (opts.auth && !options.headers.authorization) {
options.headers.authorization =
'Basic ' + Buffer.from(opts.auth).toString('base64');
}
req = websocket._req = request(opts);
if (websocket._redirects) {
//
// Unlike what is done for the `'upgrade'` event, no early exit is
// triggered here if the user calls `websocket.close()` or
// `websocket.terminate()` from a listener of the `'redirect'` event. This
// is because the user can also call `request.destroy()` with an error
// before calling `websocket.close()` or `websocket.terminate()` and this
// would result in an error being emitted on the `request` object with no
// `'error'` event listeners attached.
//
websocket.emit('redirect', websocket.url, req);
}
} else {
req = websocket._req = request(opts);
}
if (opts.timeout) {

@@ -796,3 +895,3 @@ req.on('timeout', () => {

req.on('error', (err) => {
if (req === null || req.aborted) return;
if (req === null || req[kAborted]) return;

@@ -844,4 +943,4 @@ req = websocket._req = null;

//
// The user may have closed the connection from a listener of the `upgrade`
// event.
// The user may have closed the connection from a listener of the
// `'upgrade'` event.
//

@@ -852,2 +951,7 @@ if (websocket.readyState !== WebSocket.CONNECTING) return;

if (res.headers.upgrade.toLowerCase() !== 'websocket') {
abortHandshake(websocket, socket, 'Invalid Upgrade header');
return;
}
const digest = createHash('sha1')

@@ -927,2 +1031,3 @@ .update(key + GUID)

websocket.setSocket(socket, head, {
allowSynchronousEvents: opts.allowSynchronousEvents,
generateMask: opts.generateMask,

@@ -933,6 +1038,12 @@ maxPayload: opts.maxPayload,

});
if (opts.finishRequest) {
opts.finishRequest(req, websocket);
} else {
req.end();
}
}
/**
* Emit the `'error'` and `'close'` event.
* Emit the `'error'` and `'close'` events.
*

@@ -994,2 +1105,3 @@ * @param {WebSocket} websocket The WebSocket instance

if (stream.setHeader) {
stream[kAborted] = true;
stream.abort();

@@ -1006,4 +1118,3 @@

stream.once('abort', websocket.emitClose.bind(websocket));
websocket.emit('error', err);
process.nextTick(emitErrorAndClose, websocket, err);
} else {

@@ -1044,3 +1155,3 @@ stream.destroy(err);

);
cb(err);
process.nextTick(cb, err);
}

@@ -1136,3 +1247,3 @@ }

websocket.pong(data, !websocket._isServer, NOOP);
if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);
websocket.emit('ping', data);

@@ -1162,3 +1273,3 @@ }

/**
* The listener of the `net.Socket` `'close'` event.
* The listener of the socket `'close'` event.
*

@@ -1214,3 +1325,3 @@ * @private

/**
* The listener of the `net.Socket` `'data'` event.
* The listener of the socket `'data'` event.
*

@@ -1227,3 +1338,3 @@ * @param {Buffer} chunk A chunk of data

/**
* The listener of the `net.Socket` `'end'` event.
* The listener of the socket `'end'` event.
*

@@ -1241,3 +1352,3 @@ * @private

/**
* The listener of the `net.Socket` `'error'` event.
* The listener of the socket `'error'` event.
*

@@ -1244,0 +1355,0 @@ * @private

{
"name": "ws",
"version": "8.4.0",
"version": "8.16.0",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",

@@ -15,3 +15,6 @@ "keywords": [

"bugs": "https://github.com/websockets/ws/issues",
"repository": "websockets/ws",
"repository": {
"type": "git",
"url": "git+https://github.com/websockets/ws.git"
},
"author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",

@@ -21,4 +24,8 @@ "license": "MIT",

"exports": {
"import": "./wrapper.mjs",
"require": "./index.js"
".": {
"browser": "./browser.js",
"import": "./wrapper.mjs",
"require": "./index.js"
},
"./package.json": "./package.json"
},

@@ -42,3 +49,3 @@ "browser": "browser.js",

"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},

@@ -57,9 +64,9 @@ "peerDependenciesMeta": {

"eslint": "^8.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"mocha": "^8.4.0",
"nyc": "^15.0.0",
"prettier": "^2.0.5",
"utf-8-validate": "^5.0.2"
"prettier": "^3.0.0",
"utf-8-validate": "^6.0.0"
}
}
# ws: a Node.js WebSocket library
[![Version npm](https://img.shields.io/npm/v/ws.svg?logo=npm)](https://www.npmjs.com/package/ws)
[![CI](https://img.shields.io/github/workflow/status/websockets/ws/CI/master?label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
[![CI](https://img.shields.io/github/actions/workflow/status/websockets/ws/ci.yml?branch=master&label=CI&logo=github)](https://github.com/websockets/ws/actions?query=workflow%3ACI+branch%3Amaster)
[![Coverage Status](https://img.shields.io/coveralls/websockets/ws/master.svg?logo=coveralls)](https://coveralls.io/github/websockets/ws)

@@ -26,2 +26,3 @@

- [Opt-in for performance](#opt-in-for-performance)
- [Legacy opt-in for performance](#legacy-opt-in-for-performance)
- [API docs](#api-docs)

@@ -37,3 +38,3 @@ - [WebSocket compression](#websocket-compression)

- [Server broadcast](#server-broadcast)
- [echo.websocket.org demo](#echowebsocketorg-demo)
- [Round-trip time](#round-trip-time)
- [Use the Node.js streams API](#use-the-nodejs-streams-api)

@@ -62,13 +63,34 @@ - [Other examples](#other-examples)

There are 2 optional modules that can be installed along side with the ws
module. These modules are binary addons which improve certain operations.
Prebuilt binaries are available for the most popular platforms so you don't
necessarily need to have a C++ compiler installed on your machine.
[bufferutil][] is an optional module that can be installed alongside the ws
module:
- `npm install --save-optional bufferutil`: Allows to efficiently perform
operations such as masking and unmasking the data payload of the WebSocket
frames.
- `npm install --save-optional utf-8-validate`: Allows to efficiently check if a
message contains valid UTF-8.
```
npm install --save-optional bufferutil
```
This is a binary addon that improves the performance of certain operations such
as masking and unmasking the data payload of the WebSocket frames. Prebuilt
binaries are available for the most popular platforms, so you don't necessarily
need to have a C++ compiler installed on your machine.
To force ws to not use bufferutil, use the
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This
can be useful to enhance security in systems where a user can put a package in
the package search path of an application of another user, due to how the
Node.js resolver algorithm works.
#### Legacy opt-in for performance
If you are running on an old version of Node.js (prior to v18.14.0), ws also
supports the [utf-8-validate][] module:
```
npm install --save-optional utf-8-validate
```
This contains a binary polyfill for [`buffer.isUtf8()`][].
To force ws to not use utf-8-validate, use the
[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.
## API docs

@@ -150,2 +172,4 @@

ws.on('error', console.error);
ws.on('open', function open() {

@@ -167,2 +191,4 @@ ws.send('something');

ws.on('error', console.error);
ws.on('open', function open() {

@@ -187,2 +213,4 @@ const array = new Float32Array(5);

wss.on('connection', function connection(ws) {
ws.on('error', console.error);
ws.on('message', function message(data) {

@@ -210,2 +238,4 @@ console.log('received: %s', data);

wss.on('connection', function connection(ws) {
ws.on('error', console.error);
ws.on('message', function message(data) {

@@ -233,2 +263,4 @@ console.log('received: %s', data);

wss1.on('connection', function connection(ws) {
ws.on('error', console.error);
// ...

@@ -238,2 +270,4 @@ });

wss2.on('connection', function connection(ws) {
ws.on('error', console.error);
// ...

@@ -264,5 +298,9 @@ });

```js
import WebSocket from 'ws';
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
function onSocketError(err) {
console.error(err);
}
const server = createServer();

@@ -272,2 +310,4 @@ const wss = new WebSocketServer({ noServer: true });

wss.on('connection', function connection(ws, request, client) {
ws.on('error', console.error);
ws.on('message', function message(data) {

@@ -279,2 +319,4 @@ console.log(`Received message ${data} from user ${client}`);

server.on('upgrade', function upgrade(request, socket, head) {
socket.on('error', onSocketError);
// This function is not defined on purpose. Implement it with your own logic.

@@ -288,2 +330,4 @@ authenticate(request, function next(err, client) {

socket.removeListener('error', onSocketError);
wss.handleUpgrade(request, socket, head, function done(ws) {

@@ -311,2 +355,4 @@ wss.emit('connection', ws, request, client);

wss.on('connection', function connection(ws) {
ws.on('error', console.error);
ws.on('message', function message(data, isBinary) {

@@ -331,2 +377,4 @@ wss.clients.forEach(function each(client) {

wss.on('connection', function connection(ws) {
ws.on('error', console.error);
ws.on('message', function message(data, isBinary) {

@@ -342,3 +390,3 @@ wss.clients.forEach(function each(client) {

### echo.websocket.org demo
### Round-trip time

@@ -348,6 +396,6 @@ ```js

const ws = new WebSocket('wss://echo.websocket.org/', {
origin: 'https://websocket.org'
});
const ws = new WebSocket('wss://websocket-echo.com/');
ws.on('error', console.error);
ws.on('open', function open() {

@@ -363,3 +411,3 @@ console.log('connected');

ws.on('message', function message(data) {
console.log(`Roundtrip time: ${Date.now() - data} ms`);
console.log(`Round-trip time: ${Date.now() - data} ms`);

@@ -377,8 +425,8 @@ setTimeout(function timeout() {

const ws = new WebSocket('wss://echo.websocket.org/', {
origin: 'https://websocket.org'
});
const ws = new WebSocket('wss://websocket-echo.com/');
const duplex = createWebSocketStream(ws, { encoding: 'utf8' });
duplex.on('error', console.error);
duplex.pipe(process.stdout);

@@ -408,2 +456,4 @@ process.stdin.pipe(duplex);

const ip = req.socket.remoteAddress;
ws.on('error', console.error);
});

@@ -418,2 +468,4 @@ ```

const ip = req.headers['x-forwarded-for'].split(',')[0].trim();
ws.on('error', console.error);
});

@@ -442,2 +494,3 @@ ```

ws.isAlive = true;
ws.on('error', console.error);
ws.on('pong', heartbeat);

@@ -482,4 +535,5 @@ });

const client = new WebSocket('wss://echo.websocket.org/');
const client = new WebSocket('wss://websocket-echo.com/');
client.on('error', console.error);
client.on('open', heartbeat);

@@ -505,2 +559,4 @@ client.on('ping', heartbeat);

[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input
[bufferutil]: https://github.com/websockets/bufferutil
[changelog]: https://github.com/websockets/ws/releases

@@ -516,3 +572,3 @@ [client-report]: http://websockets.github.io/ws/autobahn/clients/

[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
[ws-server-options]:
https://github.com/websockets/ws/blob/master/doc/ws.md#new-websocketserveroptions-callback
[utf-8-validate]: https://github.com/websockets/utf-8-validate
[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback

Sorry, the diff of this file is not supported yet

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