Comparing version 8.17.1 to 8.18.0
'use strict'; | ||
const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments']; | ||
const hasBlob = typeof Blob !== 'undefined'; | ||
if (hasBlob) BINARY_TYPES.push('blob'); | ||
module.exports = { | ||
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], | ||
BINARY_TYPES, | ||
EMPTY_BUFFER: Buffer.alloc(0), | ||
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', | ||
hasBlob, | ||
kForOnEventAttribute: Symbol('kIsForOnEventAttribute'), | ||
@@ -8,0 +14,0 @@ kListener: Symbol('kListener'), |
@@ -562,2 +562,4 @@ 'use strict'; | ||
data = toArrayBuffer(concat(fragments, messageLength)); | ||
} else if (this._binaryType === 'blob') { | ||
data = new Blob(fragments); | ||
} else { | ||
@@ -564,0 +566,0 @@ data = fragments; |
@@ -9,4 +9,4 @@ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */ | ||
const PerMessageDeflate = require('./permessage-deflate'); | ||
const { EMPTY_BUFFER } = require('./constants'); | ||
const { isValidStatusCode } = require('./validation'); | ||
const { EMPTY_BUFFER, kWebSocket, NOOP } = require('./constants'); | ||
const { isBlob, isValidStatusCode } = require('./validation'); | ||
const { mask: applyMask, toBuffer } = require('./buffer-util'); | ||
@@ -20,2 +20,6 @@ | ||
const DEFAULT = 0; | ||
const DEFLATING = 1; | ||
const GET_BLOB_DATA = 2; | ||
/** | ||
@@ -47,4 +51,6 @@ * HyBi Sender implementation. | ||
this._bufferedBytes = 0; | ||
this._deflating = false; | ||
this._queue = []; | ||
this._state = DEFAULT; | ||
this.onerror = NOOP; | ||
this[kWebSocket] = undefined; | ||
} | ||
@@ -216,3 +222,3 @@ | ||
if (this._deflating) { | ||
if (this._state !== DEFAULT) { | ||
this.enqueue([this.dispatch, buf, false, options, cb]); | ||
@@ -239,2 +245,5 @@ } else { | ||
readOnly = false; | ||
} else if (isBlob(data)) { | ||
byteLength = data.size; | ||
readOnly = false; | ||
} else { | ||
@@ -261,3 +270,9 @@ data = toBuffer(data); | ||
if (this._deflating) { | ||
if (isBlob(data)) { | ||
if (this._state !== DEFAULT) { | ||
this.enqueue([this.getBlobData, data, false, options, cb]); | ||
} else { | ||
this.getBlobData(data, false, options, cb); | ||
} | ||
} else if (this._state !== DEFAULT) { | ||
this.enqueue([this.dispatch, data, false, options, cb]); | ||
@@ -284,2 +299,5 @@ } else { | ||
readOnly = false; | ||
} else if (isBlob(data)) { | ||
byteLength = data.size; | ||
readOnly = false; | ||
} else { | ||
@@ -306,3 +324,9 @@ data = toBuffer(data); | ||
if (this._deflating) { | ||
if (isBlob(data)) { | ||
if (this._state !== DEFAULT) { | ||
this.enqueue([this.getBlobData, data, false, options, cb]); | ||
} else { | ||
this.getBlobData(data, false, options, cb); | ||
} | ||
} else if (this._state !== DEFAULT) { | ||
this.enqueue([this.dispatch, data, false, options, cb]); | ||
@@ -341,2 +365,5 @@ } else { | ||
readOnly = false; | ||
} else if (isBlob(data)) { | ||
byteLength = data.size; | ||
readOnly = false; | ||
} else { | ||
@@ -369,33 +396,23 @@ data = toBuffer(data); | ||
if (perMessageDeflate) { | ||
const opts = { | ||
[kByteLength]: byteLength, | ||
fin: options.fin, | ||
generateMask: this._generateMask, | ||
mask: options.mask, | ||
maskBuffer: this._maskBuffer, | ||
opcode, | ||
readOnly, | ||
rsv1 | ||
}; | ||
const opts = { | ||
[kByteLength]: byteLength, | ||
fin: options.fin, | ||
generateMask: this._generateMask, | ||
mask: options.mask, | ||
maskBuffer: this._maskBuffer, | ||
opcode, | ||
readOnly, | ||
rsv1 | ||
}; | ||
if (this._deflating) { | ||
this.enqueue([this.dispatch, data, this._compress, opts, cb]); | ||
if (isBlob(data)) { | ||
if (this._state !== DEFAULT) { | ||
this.enqueue([this.getBlobData, data, this._compress, opts, cb]); | ||
} else { | ||
this.dispatch(data, this._compress, opts, cb); | ||
this.getBlobData(data, this._compress, opts, cb); | ||
} | ||
} else if (this._state !== DEFAULT) { | ||
this.enqueue([this.dispatch, data, this._compress, opts, cb]); | ||
} else { | ||
this.sendFrame( | ||
Sender.frame(data, { | ||
[kByteLength]: byteLength, | ||
fin: options.fin, | ||
generateMask: this._generateMask, | ||
mask: options.mask, | ||
maskBuffer: this._maskBuffer, | ||
opcode, | ||
readOnly, | ||
rsv1: false | ||
}), | ||
cb | ||
); | ||
this.dispatch(data, this._compress, opts, cb); | ||
} | ||
@@ -405,2 +422,66 @@ } | ||
/** | ||
* Gets the contents of a blob as binary data. | ||
* | ||
* @param {Blob} blob The blob | ||
* @param {Boolean} [compress=false] Specifies whether or not to compress | ||
* the data | ||
* @param {Object} options Options object | ||
* @param {Boolean} [options.fin=false] Specifies whether or not to set the | ||
* FIN bit | ||
* @param {Function} [options.generateMask] The function used to generate the | ||
* masking key | ||
* @param {Boolean} [options.mask=false] Specifies whether or not to mask | ||
* `data` | ||
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking | ||
* key | ||
* @param {Number} options.opcode The opcode | ||
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be | ||
* modified | ||
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the | ||
* RSV1 bit | ||
* @param {Function} [cb] Callback | ||
* @private | ||
*/ | ||
getBlobData(blob, compress, options, cb) { | ||
this._bufferedBytes += options[kByteLength]; | ||
this._state = GET_BLOB_DATA; | ||
blob | ||
.arrayBuffer() | ||
.then((arrayBuffer) => { | ||
if (this._socket.destroyed) { | ||
const err = new Error( | ||
'The socket was closed while the blob was being read' | ||
); | ||
// | ||
// `callCallbacks` is called in the next tick to ensure that errors | ||
// that might be thrown in the callbacks behave like errors thrown | ||
// outside the promise chain. | ||
// | ||
process.nextTick(callCallbacks, this, err, cb); | ||
return; | ||
} | ||
this._bufferedBytes -= options[kByteLength]; | ||
const data = toBuffer(arrayBuffer); | ||
if (!compress) { | ||
this._state = DEFAULT; | ||
this.sendFrame(Sender.frame(data, options), cb); | ||
this.dequeue(); | ||
} else { | ||
this.dispatch(data, compress, options, cb); | ||
} | ||
}) | ||
.catch((err) => { | ||
// | ||
// `onError` is called in the next tick for the same reason that | ||
// `callCallbacks` above is. | ||
// | ||
process.nextTick(onError, this, err, cb); | ||
}); | ||
} | ||
/** | ||
* Dispatches a message. | ||
@@ -437,3 +518,3 @@ * | ||
this._bufferedBytes += options[kByteLength]; | ||
this._deflating = true; | ||
this._state = DEFLATING; | ||
perMessageDeflate.compress(data, options.fin, (_, buf) => { | ||
@@ -445,11 +526,3 @@ if (this._socket.destroyed) { | ||
if (typeof cb === 'function') cb(err); | ||
for (let i = 0; i < this._queue.length; i++) { | ||
const params = this._queue[i]; | ||
const callback = params[params.length - 1]; | ||
if (typeof callback === 'function') callback(err); | ||
} | ||
callCallbacks(this, err, cb); | ||
return; | ||
@@ -459,3 +532,3 @@ } | ||
this._bufferedBytes -= options[kByteLength]; | ||
this._deflating = false; | ||
this._state = DEFAULT; | ||
options.readOnly = false; | ||
@@ -473,3 +546,3 @@ this.sendFrame(Sender.frame(buf, options), cb); | ||
dequeue() { | ||
while (!this._deflating && this._queue.length) { | ||
while (this._state === DEFAULT && this._queue.length) { | ||
const params = this._queue.shift(); | ||
@@ -513,1 +586,33 @@ | ||
module.exports = Sender; | ||
/** | ||
* Calls queued callbacks with an error. | ||
* | ||
* @param {Sender} sender The `Sender` instance | ||
* @param {Error} err The error to call the callbacks with | ||
* @param {Function} [cb] The first callback | ||
* @private | ||
*/ | ||
function callCallbacks(sender, err, cb) { | ||
if (typeof cb === 'function') cb(err); | ||
for (let i = 0; i < sender._queue.length; i++) { | ||
const params = sender._queue[i]; | ||
const callback = params[params.length - 1]; | ||
if (typeof callback === 'function') callback(err); | ||
} | ||
} | ||
/** | ||
* Handles a `Sender` error. | ||
* | ||
* @param {Sender} sender The `Sender` instance | ||
* @param {Error} err The error | ||
* @param {Function} [cb] The first pending callback | ||
* @private | ||
*/ | ||
function onError(sender, err, cb) { | ||
callCallbacks(sender, err, cb); | ||
sender.onerror(err); | ||
} |
@@ -5,2 +5,4 @@ 'use strict'; | ||
const { hasBlob } = require('./constants'); | ||
// | ||
@@ -111,3 +113,23 @@ // Allowed token characters: | ||
/** | ||
* Determines whether a value is a `Blob`. | ||
* | ||
* @param {*} value The value to be tested | ||
* @return {Boolean} `true` if `value` is a `Blob`, else `false` | ||
* @private | ||
*/ | ||
function isBlob(value) { | ||
return ( | ||
hasBlob && | ||
typeof value === 'object' && | ||
typeof value.arrayBuffer === 'function' && | ||
typeof value.type === 'string' && | ||
typeof value.stream === 'function' && | ||
(value[Symbol.toStringTag] === 'Blob' || | ||
value[Symbol.toStringTag] === 'File') | ||
); | ||
} | ||
module.exports = { | ||
isBlob, | ||
isValidStatusCode, | ||
@@ -114,0 +136,0 @@ isValidUTF8: _isValidUTF8, |
@@ -17,2 +17,4 @@ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */ | ||
const Sender = require('./sender'); | ||
const { isBlob } = require('./validation'); | ||
const { | ||
@@ -62,2 +64,3 @@ BINARY_TYPES, | ||
this._closeTimer = null; | ||
this._errorEmitted = false; | ||
this._extensions = {}; | ||
@@ -95,5 +98,4 @@ this._paused = false; | ||
/** | ||
* This deviates from the WHATWG interface since ws doesn't support the | ||
* required default "blob" type (instead we define a custom "nodebuffer" | ||
* type). | ||
* For historical reasons, the custom "nodebuffer" type is used by the default | ||
* instead of "blob". | ||
* | ||
@@ -219,7 +221,10 @@ * @type {String} | ||
this._sender = new Sender(socket, this._extensions, options.generateMask); | ||
const sender = new Sender(socket, this._extensions, options.generateMask); | ||
this._receiver = receiver; | ||
this._sender = sender; | ||
this._socket = socket; | ||
receiver[kWebSocket] = this; | ||
sender[kWebSocket] = this; | ||
socket[kWebSocket] = this; | ||
@@ -234,2 +239,4 @@ | ||
sender.onerror = senderOnError; | ||
// | ||
@@ -330,9 +337,3 @@ // These methods may not be available if `socket` is just a `Duplex`. | ||
// | ||
// Specify a timeout for the closing handshake to complete. | ||
// | ||
this._closeTimer = setTimeout( | ||
this._socket.destroy.bind(this._socket), | ||
closeTimeout | ||
); | ||
setCloseTimer(this); | ||
} | ||
@@ -1041,2 +1042,7 @@ | ||
websocket._readyState = WebSocket.CLOSING; | ||
// | ||
// The following assignment is practically useless and is done only for | ||
// consistency. | ||
// | ||
websocket._errorEmitted = true; | ||
websocket.emit('error', err); | ||
@@ -1122,3 +1128,3 @@ websocket.emitClose(); | ||
if (data) { | ||
const length = toBuffer(data).length; | ||
const length = isBlob(data) ? data.size : toBuffer(data).length; | ||
@@ -1199,3 +1205,6 @@ // | ||
websocket.emit('error', err); | ||
if (!websocket._errorEmitted) { | ||
websocket._errorEmitted = true; | ||
websocket.emit('error', err); | ||
} | ||
} | ||
@@ -1257,2 +1266,43 @@ | ||
/** | ||
* The `Sender` error event handler. | ||
* | ||
* @param {Error} The error | ||
* @private | ||
*/ | ||
function senderOnError(err) { | ||
const websocket = this[kWebSocket]; | ||
if (websocket.readyState === WebSocket.CLOSED) return; | ||
if (websocket.readyState === WebSocket.OPEN) { | ||
websocket._readyState = WebSocket.CLOSING; | ||
setCloseTimer(websocket); | ||
} | ||
// | ||
// `socket.end()` is used instead of `socket.destroy()` to allow the other | ||
// peer to finish sending queued data. There is no need to set a timer here | ||
// because `CLOSING` means that it is already set or not needed. | ||
// | ||
this._socket.end(); | ||
if (!websocket._errorEmitted) { | ||
websocket._errorEmitted = true; | ||
websocket.emit('error', err); | ||
} | ||
} | ||
/** | ||
* Set a timer to destroy the underlying raw socket of a WebSocket. | ||
* | ||
* @param {WebSocket} websocket The WebSocket instance | ||
* @private | ||
*/ | ||
function setCloseTimer(websocket) { | ||
websocket._closeTimer = setTimeout( | ||
websocket._socket.destroy.bind(websocket._socket), | ||
closeTimeout | ||
); | ||
} | ||
/** | ||
* The listener of the socket `'close'` event. | ||
@@ -1259,0 +1309,0 @@ * |
{ | ||
"name": "ws", | ||
"version": "8.17.1", | ||
"version": "8.18.0", | ||
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
146647
4216