| 'use strict'; | 
|   | 
| const { randomBytes } = require('crypto'); | 
|   | 
| const PerMessageDeflate = require('./permessage-deflate'); | 
| const { EMPTY_BUFFER } = require('./constants'); | 
| const { isValidStatusCode } = require('./validation'); | 
| const { mask: applyMask, toBuffer } = require('./buffer-util'); | 
|   | 
| /** | 
|  * HyBi Sender implementation. | 
|  */ | 
| class Sender { | 
|   /** | 
|    * Creates a Sender instance. | 
|    * | 
|    * @param {net.Socket} socket The connection socket | 
|    * @param {Object} extensions An object containing the negotiated extensions | 
|    */ | 
|   constructor(socket, extensions) { | 
|     this._extensions = extensions || {}; | 
|     this._socket = socket; | 
|   | 
|     this._firstFragment = true; | 
|     this._compress = false; | 
|   | 
|     this._bufferedBytes = 0; | 
|     this._deflating = false; | 
|     this._queue = []; | 
|   } | 
|   | 
|   /** | 
|    * 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 = 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; | 
|   | 
|     target[1] = payloadLength; | 
|   | 
|     if (payloadLength === 126) { | 
|       target.writeUInt16BE(data.length, 2); | 
|     } else if (payloadLength === 127) { | 
|       target.writeUInt32BE(0, 2); | 
|       target.writeUInt32BE(data.length, 6); | 
|     } | 
|   | 
|     if (!options.mask) return [target, data]; | 
|   | 
|     const mask = randomBytes(4); | 
|   | 
|     target[1] |= 0x80; | 
|     target[offset - 4] = mask[0]; | 
|     target[offset - 3] = mask[1]; | 
|     target[offset - 2] = mask[2]; | 
|     target[offset - 1] = mask[3]; | 
|   | 
|     if (merge) { | 
|       applyMask(data, mask, target, offset, data.length); | 
|       return [target]; | 
|     } | 
|   | 
|     applyMask(data, mask, data, 0, data.length); | 
|     return [target, data]; | 
|   } | 
|   | 
|   /** | 
|    * Sends a close message to the other peer. | 
|    * | 
|    * @param {(Number|undefined)} code The status code component of the body | 
|    * @param {String} data The message component of the body | 
|    * @param {Boolean} mask Specifies whether or not to mask the message | 
|    * @param {Function} cb Callback | 
|    * @public | 
|    */ | 
|   close(code, data, mask, cb) { | 
|     var buf; | 
|   | 
|     if (code === undefined) { | 
|       buf = EMPTY_BUFFER; | 
|     } else if (typeof code !== 'number' || !isValidStatusCode(code)) { | 
|       throw new TypeError('First argument must be a valid error code number'); | 
|     } else if (data === undefined || data === '') { | 
|       buf = Buffer.allocUnsafe(2); | 
|       buf.writeUInt16BE(code, 0); | 
|     } else { | 
|       buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); | 
|       buf.writeUInt16BE(code, 0); | 
|       buf.write(data, 2); | 
|     } | 
|   | 
|     if (this._deflating) { | 
|       this.enqueue([this.doClose, buf, mask, cb]); | 
|     } else { | 
|       this.doClose(buf, mask, cb); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Frames and sends a close message. | 
|    * | 
|    * @param {Buffer} data The message to send | 
|    * @param {Boolean} mask Specifies whether or not to mask `data` | 
|    * @param {Function} cb Callback | 
|    * @private | 
|    */ | 
|   doClose(data, mask, cb) { | 
|     this.sendFrame( | 
|       Sender.frame(data, { | 
|         fin: true, | 
|         rsv1: false, | 
|         opcode: 0x08, | 
|         mask, | 
|         readOnly: false | 
|       }), | 
|       cb | 
|     ); | 
|   } | 
|   | 
|   /** | 
|    * Sends a ping message to the other peer. | 
|    * | 
|    * @param {*} data The message to send | 
|    * @param {Boolean} mask Specifies whether or not to mask `data` | 
|    * @param {Function} cb Callback | 
|    * @public | 
|    */ | 
|   ping(data, mask, cb) { | 
|     const buf = toBuffer(data); | 
|   | 
|     if (this._deflating) { | 
|       this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]); | 
|     } else { | 
|       this.doPing(buf, mask, toBuffer.readOnly, cb); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Frames and sends a ping message. | 
|    * | 
|    * @param {*} data The message to send | 
|    * @param {Boolean} mask Specifies whether or not to mask `data` | 
|    * @param {Boolean} readOnly Specifies whether `data` can be modified | 
|    * @param {Function} cb Callback | 
|    * @private | 
|    */ | 
|   doPing(data, mask, readOnly, cb) { | 
|     this.sendFrame( | 
|       Sender.frame(data, { | 
|         fin: true, | 
|         rsv1: false, | 
|         opcode: 0x09, | 
|         mask, | 
|         readOnly | 
|       }), | 
|       cb | 
|     ); | 
|   } | 
|   | 
|   /** | 
|    * Sends a pong message to the other peer. | 
|    * | 
|    * @param {*} data The message to send | 
|    * @param {Boolean} mask Specifies whether or not to mask `data` | 
|    * @param {Function} cb Callback | 
|    * @public | 
|    */ | 
|   pong(data, mask, cb) { | 
|     const buf = toBuffer(data); | 
|   | 
|     if (this._deflating) { | 
|       this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]); | 
|     } else { | 
|       this.doPong(buf, mask, toBuffer.readOnly, cb); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Frames and sends a pong message. | 
|    * | 
|    * @param {*} data The message to send | 
|    * @param {Boolean} mask Specifies whether or not to mask `data` | 
|    * @param {Boolean} readOnly Specifies whether `data` can be modified | 
|    * @param {Function} cb Callback | 
|    * @private | 
|    */ | 
|   doPong(data, mask, readOnly, cb) { | 
|     this.sendFrame( | 
|       Sender.frame(data, { | 
|         fin: true, | 
|         rsv1: false, | 
|         opcode: 0x0a, | 
|         mask, | 
|         readOnly | 
|       }), | 
|       cb | 
|     ); | 
|   } | 
|   | 
|   /** | 
|    * Sends a data message to the other peer. | 
|    * | 
|    * @param {*} data The message to send | 
|    * @param {Object} options Options object | 
|    * @param {Boolean} options.compress Specifies whether or not to compress `data` | 
|    * @param {Boolean} options.binary Specifies whether `data` is binary or text | 
|    * @param {Boolean} options.fin Specifies whether the fragment is the last one | 
|    * @param {Boolean} options.mask Specifies whether or not to mask `data` | 
|    * @param {Function} cb Callback | 
|    * @public | 
|    */ | 
|   send(data, options, cb) { | 
|     const buf = toBuffer(data); | 
|     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; | 
|     var opcode = options.binary ? 2 : 1; | 
|     var rsv1 = options.compress; | 
|   | 
|     if (this._firstFragment) { | 
|       this._firstFragment = false; | 
|       if (rsv1 && perMessageDeflate) { | 
|         rsv1 = buf.length >= perMessageDeflate._threshold; | 
|       } | 
|       this._compress = rsv1; | 
|     } else { | 
|       rsv1 = false; | 
|       opcode = 0; | 
|     } | 
|   | 
|     if (options.fin) this._firstFragment = true; | 
|   | 
|     if (perMessageDeflate) { | 
|       const opts = { | 
|         fin: options.fin, | 
|         rsv1, | 
|         opcode, | 
|         mask: options.mask, | 
|         readOnly: toBuffer.readOnly | 
|       }; | 
|   | 
|       if (this._deflating) { | 
|         this.enqueue([this.dispatch, buf, this._compress, opts, cb]); | 
|       } else { | 
|         this.dispatch(buf, this._compress, opts, cb); | 
|       } | 
|     } else { | 
|       this.sendFrame( | 
|         Sender.frame(buf, { | 
|           fin: options.fin, | 
|           rsv1: false, | 
|           opcode, | 
|           mask: options.mask, | 
|           readOnly: toBuffer.readOnly | 
|         }), | 
|         cb | 
|       ); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Dispatches a data message. | 
|    * | 
|    * @param {Buffer} data The message to send | 
|    * @param {Boolean} compress Specifies whether or not to compress `data` | 
|    * @param {Object} options Options object | 
|    * @param {Number} options.opcode The opcode | 
|    * @param {Boolean} options.readOnly Specifies whether `data` can be modified | 
|    * @param {Boolean} options.fin Specifies whether or not to set the FIN bit | 
|    * @param {Boolean} options.mask Specifies whether or not to mask `data` | 
|    * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit | 
|    * @param {Function} cb Callback | 
|    * @private | 
|    */ | 
|   dispatch(data, compress, options, cb) { | 
|     if (!compress) { | 
|       this.sendFrame(Sender.frame(data, options), cb); | 
|       return; | 
|     } | 
|   | 
|     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; | 
|   | 
|     this._deflating = true; | 
|     perMessageDeflate.compress(data, options.fin, (_, buf) => { | 
|       this._deflating = false; | 
|       options.readOnly = false; | 
|       this.sendFrame(Sender.frame(buf, options), cb); | 
|       this.dequeue(); | 
|     }); | 
|   } | 
|   | 
|   /** | 
|    * Executes queued send operations. | 
|    * | 
|    * @private | 
|    */ | 
|   dequeue() { | 
|     while (!this._deflating && this._queue.length) { | 
|       const params = this._queue.shift(); | 
|   | 
|       this._bufferedBytes -= params[1].length; | 
|       params[0].apply(this, params.slice(1)); | 
|     } | 
|   } | 
|   | 
|   /** | 
|    * Enqueues a send operation. | 
|    * | 
|    * @param {Array} params Send operation parameters. | 
|    * @private | 
|    */ | 
|   enqueue(params) { | 
|     this._bufferedBytes += params[1].length; | 
|     this._queue.push(params); | 
|   } | 
|   | 
|   /** | 
|    * Sends a frame. | 
|    * | 
|    * @param {Buffer[]} list The frame to send | 
|    * @param {Function} cb Callback | 
|    * @private | 
|    */ | 
|   sendFrame(list, cb) { | 
|     if (list.length === 2) { | 
|       this._socket.cork(); | 
|       this._socket.write(list[0]); | 
|       this._socket.write(list[1], cb); | 
|       this._socket.uncork(); | 
|     } else { | 
|       this._socket.write(list[0], cb); | 
|     } | 
|   } | 
| } | 
|   | 
| module.exports = Sender; |