Comparing version 2.1.0 to 2.2.0
@@ -14,3 +14,50 @@ 'use strict'; | ||
} catch (e) { | ||
module.exports = require('./BufferUtil.fallback'); | ||
/** | ||
* Merges an array of buffers into a target buffer. | ||
* | ||
* @param {Buffer} target The target buffer | ||
* @param {Buffer[]} buffers The array of buffers to merge | ||
* @public | ||
*/ | ||
const merge = (target, buffers) => { | ||
var offset = 0; | ||
for (var i = 0; i < buffers.length; i++) { | ||
const buf = buffers[i]; | ||
buf.copy(target, offset); | ||
offset += buf.length; | ||
} | ||
}; | ||
/** | ||
* Masks a buffer using the given mask. | ||
* | ||
* @param {Buffer} source The buffer to mask | ||
* @param {Buffer} mask The mask to use | ||
* @param {Buffer} output The buffer where to store the result | ||
* @param {Number} offset The offset at which to start writing | ||
* @param {Number} length The number of bytes to mask. | ||
* @public | ||
*/ | ||
const mask = (source, mask, output, offset, length) => { | ||
for (var i = 0; i < length; i++) { | ||
output[offset + i] = source[i] ^ mask[i & 3]; | ||
} | ||
}; | ||
/** | ||
* Unmasks a buffer using the given mask. | ||
* | ||
* @param {Buffer} buffer The buffer to unmask | ||
* @param {Buffer} mask The mask to use | ||
* @public | ||
*/ | ||
const unmask = (buffer, mask) => { | ||
// Required until https://github.com/nodejs/node/issues/9006 is resolved. | ||
const length = buffer.length; | ||
for (var i = 0; i < length; i++) { | ||
buffer[i] ^= mask[i & 3]; | ||
} | ||
}; | ||
module.exports = { merge, mask, unmask }; | ||
} |
@@ -31,3 +31,3 @@ 'use strict'; | ||
* | ||
* @param {(String|Buffer|ArrayBuffer)} data The received data | ||
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data | ||
* @param {Boolean} isBinary Specifies if `data` is binary | ||
@@ -104,5 +104,2 @@ * @param {WebSocket} target A reference to the target to which the event was dispatched | ||
function onMessage (data, flags) { | ||
if (flags.binary && this.binaryType === 'arraybuffer') { | ||
data = new Uint8Array(data).buffer; | ||
} | ||
listener.call(this, new MessageEvent(data, !!flags.binary, this)); | ||
@@ -109,0 +106,0 @@ } |
@@ -13,5 +13,4 @@ /*! | ||
const ErrorCodes = require('./ErrorCodes'); | ||
const constants = require('./Constants'); | ||
const EMPTY_BUFFER = Buffer.alloc(0); | ||
const GET_INFO = 0; | ||
@@ -33,4 +32,6 @@ const GET_PAYLOAD_LENGTH_16 = 1; | ||
* @param {Number} maxPayload The maximum allowed message length | ||
* @param {String} binaryType The type for binary data | ||
*/ | ||
constructor (extensions, maxPayload) { | ||
constructor (extensions, maxPayload, binaryType) { | ||
this.binaryType = binaryType || constants.BINARY_TYPES[0]; | ||
this.extensions = extensions || {}; | ||
@@ -90,3 +91,3 @@ this.maxPayload = maxPayload | 0; | ||
dst = new Buffer(bytes); | ||
dst = Buffer.allocUnsafe(bytes); | ||
@@ -313,3 +314,3 @@ while (bytes > 0) { | ||
getData () { | ||
var data = EMPTY_BUFFER; | ||
var data = constants.EMPTY_BUFFER; | ||
@@ -360,16 +361,25 @@ if (this.payloadLength) { | ||
if (this.fin) { | ||
const buf = this.fragments.length > 1 | ||
? Buffer.concat(this.fragments, this.messageLength) | ||
: this.fragments.length === 1 | ||
? this.fragments[0] | ||
: EMPTY_BUFFER; | ||
const messageLength = this.messageLength; | ||
const fragments = this.fragments; | ||
this.totalPayloadLength = 0; | ||
this.fragments.length = 0; | ||
this.messageLength = 0; | ||
this.fragmented = 0; | ||
this.fragments = []; | ||
if (this.opcode === 2) { | ||
this.onmessage(buf, { masked: this.masked, binary: true }); | ||
var data; | ||
if (this.binaryType === 'nodebuffer') { | ||
data = toBuffer(fragments, messageLength); | ||
} else if (this.binaryType === 'arraybuffer') { | ||
data = toArrayBuffer(toBuffer(fragments, messageLength)); | ||
} else { | ||
data = fragments; | ||
} | ||
this.onmessage(data, { masked: this.masked, binary: true }); | ||
} else { | ||
const buf = toBuffer(fragments, messageLength); | ||
if (!isValidUTF8(buf)) { | ||
@@ -519,1 +529,29 @@ this.error(new Error('invalid utf8 sequence'), 1007); | ||
module.exports = Receiver; | ||
/** | ||
* Makes a buffer from a list of fragments. | ||
* | ||
* @param {Buffer[]} fragments The list of fragments composing the message | ||
* @param {Number} messageLength The length of the message | ||
* @return {Buffer} | ||
* @private | ||
*/ | ||
function toBuffer (fragments, messageLength) { | ||
if (fragments.length === 1) return fragments[0]; | ||
if (fragments.length > 1) return Buffer.concat(fragments, messageLength); | ||
return constants.EMPTY_BUFFER; | ||
} | ||
/** | ||
* Converts a buffer to an `ArrayBuffer`. | ||
* | ||
* @param {Buffer} The buffer to convert | ||
* @return {ArrayBuffer} Converted buffer | ||
*/ | ||
function toArrayBuffer (buf) { | ||
if (buf.byteOffset === 0 && buf.byteLength === buf.buffer.byteLength) { | ||
return buf.buffer; | ||
} | ||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
} |
@@ -40,2 +40,67 @@ /*! | ||
/** | ||
* Frames a piece of data according to the HyBi WebSocket protocol. | ||
* | ||
* @param {Buffer} data The data to frame | ||
* @param {Object} options Options object | ||
* @param {Number} options.opcode The opcode | ||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified | ||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit | ||
* @param {Boolean} options.mask Specifies whether or not to mask `data` | ||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit | ||
* @return {Buffer[]} The framed data as a list of `Buffer` instances | ||
* @public | ||
*/ | ||
static frame (data, options) { | ||
const merge = data.length < 1024 || options.mask && options.readOnly; | ||
var offset = options.mask ? 6 : 2; | ||
var payloadLength = data.length; | ||
if (data.length >= 65536) { | ||
offset += 8; | ||
payloadLength = 127; | ||
} else if (data.length > 125) { | ||
offset += 2; | ||
payloadLength = 126; | ||
} | ||
const target = Buffer.allocUnsafe(merge ? data.length + offset : offset); | ||
target[0] = options.fin ? options.opcode | 0x80 : options.opcode; | ||
if (options.rsv1) target[0] |= 0x40; | ||
if (payloadLength === 126) { | ||
target.writeUInt16BE(data.length, 2, true); | ||
} else if (payloadLength === 127) { | ||
target.writeUInt32BE(0, 2, true); | ||
target.writeUInt32BE(data.length, 6, true); | ||
} | ||
if (!options.mask) { | ||
target[1] = payloadLength; | ||
if (merge) { | ||
data.copy(target, offset); | ||
return [target]; | ||
} | ||
return [target, data]; | ||
} | ||
const mask = crypto.randomBytes(4); | ||
target[1] = payloadLength | 0x80; | ||
target[offset - 4] = mask[0]; | ||
target[offset - 3] = mask[1]; | ||
target[offset - 2] = mask[2]; | ||
target[offset - 1] = mask[3]; | ||
if (merge) { | ||
bufferUtil.mask(data, mask, target, offset, data.length); | ||
return [target]; | ||
} | ||
bufferUtil.mask(data, mask, data, 0, data.length); | ||
return [target, data]; | ||
} | ||
/** | ||
* Sends a close message to the other peer. | ||
@@ -75,3 +140,3 @@ * | ||
doClose (data, mask, cb) { | ||
this.frameAndSend(data, { | ||
this.sendFrame(Sender.frame(data, { | ||
readOnly: false, | ||
@@ -82,3 +147,3 @@ opcode: 0x08, | ||
mask | ||
}, cb); | ||
}), cb); | ||
} | ||
@@ -96,3 +161,3 @@ | ||
if (data && !Buffer.isBuffer(data)) { | ||
if (!Buffer.isBuffer(data)) { | ||
if (data instanceof ArrayBuffer) { | ||
@@ -124,3 +189,3 @@ data = Buffer.from(data); | ||
doPing (data, mask, readOnly) { | ||
this.frameAndSend(data, { | ||
this.sendFrame(Sender.frame(data, { | ||
opcode: 0x09, | ||
@@ -131,3 +196,3 @@ rsv1: false, | ||
mask | ||
}); | ||
})); | ||
} | ||
@@ -145,3 +210,3 @@ | ||
if (data && !Buffer.isBuffer(data)) { | ||
if (!Buffer.isBuffer(data)) { | ||
if (data instanceof ArrayBuffer) { | ||
@@ -173,3 +238,3 @@ data = Buffer.from(data); | ||
doPong (data, mask, readOnly) { | ||
this.frameAndSend(data, { | ||
this.sendFrame(Sender.frame(data, { | ||
opcode: 0x0a, | ||
@@ -180,3 +245,3 @@ rsv1: false, | ||
mask | ||
}); | ||
})); | ||
} | ||
@@ -201,3 +266,3 @@ | ||
if (data && !Buffer.isBuffer(data)) { | ||
if (!Buffer.isBuffer(data)) { | ||
if (data instanceof ArrayBuffer) { | ||
@@ -215,3 +280,3 @@ data = Buffer.from(data); | ||
this.firstFragment = false; | ||
if (rsv1 && data && this.perMessageDeflate) { | ||
if (rsv1 && this.perMessageDeflate) { | ||
rsv1 = data.length >= this.perMessageDeflate.threshold; | ||
@@ -243,3 +308,3 @@ } | ||
} else { | ||
this.frameAndSend(data, { | ||
this.sendFrame(Sender.frame(data, { | ||
mask: options.mask, | ||
@@ -250,3 +315,3 @@ fin: options.fin, | ||
opcode | ||
}, cb); | ||
}), cb); | ||
} | ||
@@ -271,3 +336,3 @@ } | ||
if (!options.compress) { | ||
this.frameAndSend(data, options, cb); | ||
this.sendFrame(Sender.frame(data, options), cb); | ||
return; | ||
@@ -285,3 +350,3 @@ } | ||
options.readOnly = false; | ||
this.frameAndSend(buf, options, cb); | ||
this.sendFrame(Sender.frame(buf, options), cb); | ||
this.deflating = false; | ||
@@ -293,77 +358,2 @@ this.dequeue(); | ||
/** | ||
* Frames and sends a piece of data according to the HyBi WebSocket protocol. | ||
* | ||
* @param {Buffer} data The data to send | ||
* @param {Object} options Options object | ||
* @param {Number} options.opcode The opcode | ||
* @param {Boolean} options.readOnly Specifies whether `data` can be modified | ||
* @param {Boolean} options.fin Specifies whether or not to set the FIN bit | ||
* @param {Boolean} options.mask Specifies whether or not to mask `data` | ||
* @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit | ||
* @param {Function} cb Callback | ||
* @private | ||
*/ | ||
frameAndSend (data, options, cb) { | ||
if (!data) { | ||
const bytes = [options.opcode, 0]; | ||
if (options.fin) bytes[0] |= 0x80; | ||
if (options.mask) { | ||
bytes[1] |= 0x80; | ||
bytes.push(0, 0, 0, 0); | ||
} | ||
sendFramedData(this, Buffer.from(bytes), null, cb); | ||
return; | ||
} | ||
const mergeBuffers = data.length < 1024 || options.mask && options.readOnly; | ||
var dataOffset = options.mask ? 6 : 2; | ||
var payloadLength = data.length; | ||
if (data.length >= 65536) { | ||
dataOffset += 8; | ||
payloadLength = 127; | ||
} else if (data.length > 125) { | ||
dataOffset += 2; | ||
payloadLength = 126; | ||
} | ||
const outputBuffer = Buffer.allocUnsafe( | ||
mergeBuffers ? data.length + dataOffset : dataOffset | ||
); | ||
outputBuffer[0] = options.fin ? options.opcode | 0x80 : options.opcode; | ||
if (options.rsv1) outputBuffer[0] |= 0x40; | ||
if (payloadLength === 126) { | ||
outputBuffer.writeUInt16BE(data.length, 2, true); | ||
} else if (payloadLength === 127) { | ||
outputBuffer.writeUInt32BE(0, 2, true); | ||
outputBuffer.writeUInt32BE(data.length, 6, true); | ||
} | ||
if (options.mask) { | ||
const mask = getRandomMask(); | ||
outputBuffer[1] = payloadLength | 0x80; | ||
outputBuffer[dataOffset - 4] = mask[0]; | ||
outputBuffer[dataOffset - 3] = mask[1]; | ||
outputBuffer[dataOffset - 2] = mask[2]; | ||
outputBuffer[dataOffset - 1] = mask[3]; | ||
if (mergeBuffers) { | ||
bufferUtil.mask(data, mask, outputBuffer, dataOffset, data.length); | ||
} else { | ||
bufferUtil.mask(data, mask, data, 0, data.length); | ||
} | ||
} else { | ||
outputBuffer[1] = payloadLength; | ||
if (mergeBuffers) data.copy(outputBuffer, dataOffset); | ||
} | ||
sendFramedData(this, outputBuffer, mergeBuffers ? null : data, cb); | ||
} | ||
/** | ||
* Executes queued send operations. | ||
@@ -377,3 +367,3 @@ * | ||
if (params[1]) this.bufferedBytes -= params[1].length; | ||
this.bufferedBytes -= params[1].length; | ||
params[0].apply(this, params.slice(1)); | ||
@@ -390,5 +380,21 @@ } | ||
enqueue (params) { | ||
if (params[1]) this.bufferedBytes += params[1].length; | ||
this.bufferedBytes += params[1].length; | ||
this.queue.push(params); | ||
} | ||
/** | ||
* Sends a frame. | ||
* | ||
* @param {Buffer[]} list The frame to send | ||
* @param {Function} cb Callback | ||
* @private | ||
*/ | ||
sendFrame (list, cb) { | ||
if (list.length === 2) { | ||
this._socket.write(list[0]); | ||
this._socket.write(list[1], cb); | ||
} else { | ||
this._socket.write(list[0], cb); | ||
} | ||
} | ||
} | ||
@@ -414,29 +420,1 @@ | ||
} | ||
/** | ||
* Generates a random mask. | ||
* | ||
* @return {Buffer} The mask | ||
* @private | ||
*/ | ||
function getRandomMask () { | ||
return crypto.randomBytes(4); | ||
} | ||
/** | ||
* Sends a frame. | ||
* | ||
* @param {Sender} sender Sender instance | ||
* @param {Buffer} outputBuffer The data to send | ||
* @param {Buffer} data Additional data to send if frame is split into two buffers | ||
* @param {Function} cb Callback | ||
* @private | ||
*/ | ||
function sendFramedData (sender, outputBuffer, data, cb) { | ||
if (data) { | ||
sender._socket.write(outputBuffer); | ||
sender._socket.write(data, cb); | ||
} else { | ||
sender._socket.write(outputBuffer, cb); | ||
} | ||
} |
@@ -16,3 +16,3 @@ /*! | ||
} catch (e) { | ||
module.exports = require('./Validation.fallback'); | ||
module.exports = () => true; | ||
} |
@@ -19,9 +19,8 @@ /*! | ||
const Extensions = require('./Extensions'); | ||
const constants = require('./Constants'); | ||
const Receiver = require('./Receiver'); | ||
const Sender = require('./Sender'); | ||
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; | ||
const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly. | ||
const protocolVersion = 13; | ||
const noop = () => {}; | ||
@@ -57,4 +56,4 @@ /** | ||
this._binaryType = constants.BINARY_TYPES[0]; | ||
this._finalize = this.finalize.bind(this); | ||
this._binaryType = 'nodebuffer'; | ||
this._finalizeCalled = false; | ||
@@ -104,7 +103,10 @@ this._closeMessage = null; | ||
set binaryType (type) { | ||
if (type === 'arraybuffer' || type === 'nodebuffer') { | ||
this._binaryType = type; | ||
} else { | ||
throw new SyntaxError('unsupported binaryType: must be either "nodebuffer" or "arraybuffer"'); | ||
} | ||
if (constants.BINARY_TYPES.indexOf(type) < 0) return; | ||
this._binaryType = type; | ||
// | ||
// Allow to change `binaryType` on the fly. | ||
// | ||
if (this._receiver) this._receiver.binaryType = type; | ||
} | ||
@@ -123,3 +125,3 @@ | ||
this._receiver = new Receiver(this.extensions, this.maxPayload); | ||
this._receiver = new Receiver(this.extensions, this.maxPayload, this.binaryType); | ||
this._sender = new Sender(socket, this.extensions); | ||
@@ -237,3 +239,3 @@ this._ultron = new Ultron(socket); | ||
this.removeAllListeners(); | ||
this.on('error', noop); // Catch all errors after this. | ||
this.on('error', constants.NOOP); // Catch all errors after this. | ||
} | ||
@@ -319,3 +321,3 @@ | ||
if (mask === undefined) mask = !this._isServer; | ||
this._sender.ping(data, mask); | ||
this._sender.ping(data || constants.EMPTY_BUFFER, mask); | ||
} | ||
@@ -339,3 +341,3 @@ | ||
if (mask === undefined) mask = !this._isServer; | ||
this._sender.pong(data, mask); | ||
this._sender.pong(data || constants.EMPTY_BUFFER, mask); | ||
} | ||
@@ -368,9 +370,8 @@ | ||
if (typeof data === 'number') data = data.toString(); | ||
else if (!data) data = ''; | ||
const opts = Object.assign({ | ||
fin: true, | ||
binary: typeof data !== 'string', | ||
mask: !this._isServer, | ||
compress: true | ||
compress: true, | ||
fin: true | ||
}, options); | ||
@@ -382,3 +383,3 @@ | ||
this._sender.send(data, opts, cb); | ||
this._sender.send(data || constants.EMPTY_BUFFER, opts, cb); | ||
} | ||
@@ -674,3 +675,3 @@ | ||
const digest = crypto.createHash('sha1') | ||
.update(key + GUID, 'binary') | ||
.update(key + constants.GUID, 'binary') | ||
.digest('base64'); | ||
@@ -677,0 +678,0 @@ |
@@ -17,6 +17,5 @@ /*! | ||
const Extensions = require('./Extensions'); | ||
const constants = require('./Constants'); | ||
const WebSocket = require('./WebSocket'); | ||
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; | ||
/** | ||
@@ -219,3 +218,3 @@ * Class representing a WebSocket server. | ||
const key = crypto.createHash('sha1') | ||
.update(req.headers['sec-websocket-key'] + GUID, 'binary') | ||
.update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') | ||
.digest('base64'); | ||
@@ -222,0 +221,0 @@ |
@@ -5,3 +5,3 @@ { | ||
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"license": "MIT", | ||
@@ -34,3 +34,3 @@ "main": "index.js", | ||
"bufferutil": "~2.0.0", | ||
"eslint": "~3.15.0", | ||
"eslint": "~3.16.0", | ||
"eslint-config-semistandard": "~7.0.0", | ||
@@ -37,0 +37,0 @@ "eslint-config-standard": "~6.2.1", |
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
82963
16