Add files via upload
This commit is contained in:
		
							
								
								
									
										131
									
								
								node_modules/ws/lib/buffer-util.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								node_modules/ws/lib/buffer-util.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,131 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { EMPTY_BUFFER } = require('./constants'); | ||||
|  | ||||
| const FastBuffer = Buffer[Symbol.species]; | ||||
|  | ||||
| /** | ||||
|  * Merges an array of buffers into a new buffer. | ||||
|  * | ||||
|  * @param {Buffer[]} list The array of buffers to concat | ||||
|  * @param {Number} totalLength The total length of buffers in the list | ||||
|  * @return {Buffer} The resulting buffer | ||||
|  * @public | ||||
|  */ | ||||
| function concat(list, totalLength) { | ||||
|   if (list.length === 0) return EMPTY_BUFFER; | ||||
|   if (list.length === 1) return list[0]; | ||||
|  | ||||
|   const target = Buffer.allocUnsafe(totalLength); | ||||
|   let offset = 0; | ||||
|  | ||||
|   for (let i = 0; i < list.length; i++) { | ||||
|     const buf = list[i]; | ||||
|     target.set(buf, offset); | ||||
|     offset += buf.length; | ||||
|   } | ||||
|  | ||||
|   if (offset < totalLength) { | ||||
|     return new FastBuffer(target.buffer, target.byteOffset, offset); | ||||
|   } | ||||
|  | ||||
|   return target; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| function _mask(source, mask, output, offset, length) { | ||||
|   for (let 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 | ||||
|  */ | ||||
| function _unmask(buffer, mask) { | ||||
|   for (let i = 0; i < buffer.length; i++) { | ||||
|     buffer[i] ^= mask[i & 3]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Converts a buffer to an `ArrayBuffer`. | ||||
|  * | ||||
|  * @param {Buffer} buf The buffer to convert | ||||
|  * @return {ArrayBuffer} Converted buffer | ||||
|  * @public | ||||
|  */ | ||||
| function toArrayBuffer(buf) { | ||||
|   if (buf.length === buf.buffer.byteLength) { | ||||
|     return buf.buffer; | ||||
|   } | ||||
|  | ||||
|   return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Converts `data` to a `Buffer`. | ||||
|  * | ||||
|  * @param {*} data The data to convert | ||||
|  * @return {Buffer} The buffer | ||||
|  * @throws {TypeError} | ||||
|  * @public | ||||
|  */ | ||||
| function toBuffer(data) { | ||||
|   toBuffer.readOnly = true; | ||||
|  | ||||
|   if (Buffer.isBuffer(data)) return data; | ||||
|  | ||||
|   let buf; | ||||
|  | ||||
|   if (data instanceof ArrayBuffer) { | ||||
|     buf = new FastBuffer(data); | ||||
|   } else if (ArrayBuffer.isView(data)) { | ||||
|     buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength); | ||||
|   } else { | ||||
|     buf = Buffer.from(data); | ||||
|     toBuffer.readOnly = false; | ||||
|   } | ||||
|  | ||||
|   return buf; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   concat, | ||||
|   mask: _mask, | ||||
|   toArrayBuffer, | ||||
|   toBuffer, | ||||
|   unmask: _unmask | ||||
| }; | ||||
|  | ||||
| /* 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); | ||||
|     }; | ||||
|  | ||||
|     module.exports.unmask = function (buffer, mask) { | ||||
|       if (buffer.length < 32) _unmask(buffer, mask); | ||||
|       else bufferUtil.unmask(buffer, mask); | ||||
|     }; | ||||
|   } catch (e) { | ||||
|     // Continue regardless of the error. | ||||
|   } | ||||
| } | ||||
							
								
								
									
										12
									
								
								node_modules/ws/lib/constants.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								node_modules/ws/lib/constants.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| module.exports = { | ||||
|   BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], | ||||
|   EMPTY_BUFFER: Buffer.alloc(0), | ||||
|   GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', | ||||
|   kForOnEventAttribute: Symbol('kIsForOnEventAttribute'), | ||||
|   kListener: Symbol('kListener'), | ||||
|   kStatusCode: Symbol('status-code'), | ||||
|   kWebSocket: Symbol('websocket'), | ||||
|   NOOP: () => {} | ||||
| }; | ||||
							
								
								
									
										292
									
								
								node_modules/ws/lib/event-target.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								node_modules/ws/lib/event-target.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,292 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { kForOnEventAttribute, kListener } = require('./constants'); | ||||
|  | ||||
| const kCode = Symbol('kCode'); | ||||
| const kData = Symbol('kData'); | ||||
| const kError = Symbol('kError'); | ||||
| const kMessage = Symbol('kMessage'); | ||||
| const kReason = Symbol('kReason'); | ||||
| const kTarget = Symbol('kTarget'); | ||||
| const kType = Symbol('kType'); | ||||
| const kWasClean = Symbol('kWasClean'); | ||||
|  | ||||
| /** | ||||
|  * Class representing an event. | ||||
|  */ | ||||
| class Event { | ||||
|   /** | ||||
|    * Create a new `Event`. | ||||
|    * | ||||
|    * @param {String} type The name of the event | ||||
|    * @throws {TypeError} If the `type` argument is not specified | ||||
|    */ | ||||
|   constructor(type) { | ||||
|     this[kTarget] = null; | ||||
|     this[kType] = type; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {*} | ||||
|    */ | ||||
|   get target() { | ||||
|     return this[kTarget]; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {String} | ||||
|    */ | ||||
|   get type() { | ||||
|     return this[kType]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Object.defineProperty(Event.prototype, 'target', { enumerable: true }); | ||||
| Object.defineProperty(Event.prototype, 'type', { enumerable: true }); | ||||
|  | ||||
| /** | ||||
|  * Class representing a close event. | ||||
|  * | ||||
|  * @extends Event | ||||
|  */ | ||||
| class CloseEvent extends Event { | ||||
|   /** | ||||
|    * Create a new `CloseEvent`. | ||||
|    * | ||||
|    * @param {String} type The name of the event | ||||
|    * @param {Object} [options] A dictionary object that allows for setting | ||||
|    *     attributes via object members of the same name | ||||
|    * @param {Number} [options.code=0] The status code explaining why the | ||||
|    *     connection was closed | ||||
|    * @param {String} [options.reason=''] A human-readable string explaining why | ||||
|    *     the connection was closed | ||||
|    * @param {Boolean} [options.wasClean=false] Indicates whether or not the | ||||
|    *     connection was cleanly closed | ||||
|    */ | ||||
|   constructor(type, options = {}) { | ||||
|     super(type); | ||||
|  | ||||
|     this[kCode] = options.code === undefined ? 0 : options.code; | ||||
|     this[kReason] = options.reason === undefined ? '' : options.reason; | ||||
|     this[kWasClean] = options.wasClean === undefined ? false : options.wasClean; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {Number} | ||||
|    */ | ||||
|   get code() { | ||||
|     return this[kCode]; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {String} | ||||
|    */ | ||||
|   get reason() { | ||||
|     return this[kReason]; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {Boolean} | ||||
|    */ | ||||
|   get wasClean() { | ||||
|     return this[kWasClean]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Object.defineProperty(CloseEvent.prototype, 'code', { enumerable: true }); | ||||
| Object.defineProperty(CloseEvent.prototype, 'reason', { enumerable: true }); | ||||
| Object.defineProperty(CloseEvent.prototype, 'wasClean', { enumerable: true }); | ||||
|  | ||||
| /** | ||||
|  * Class representing an error event. | ||||
|  * | ||||
|  * @extends Event | ||||
|  */ | ||||
| class ErrorEvent extends Event { | ||||
|   /** | ||||
|    * Create a new `ErrorEvent`. | ||||
|    * | ||||
|    * @param {String} type The name of the event | ||||
|    * @param {Object} [options] A dictionary object that allows for setting | ||||
|    *     attributes via object members of the same name | ||||
|    * @param {*} [options.error=null] The error that generated this event | ||||
|    * @param {String} [options.message=''] The error message | ||||
|    */ | ||||
|   constructor(type, options = {}) { | ||||
|     super(type); | ||||
|  | ||||
|     this[kError] = options.error === undefined ? null : options.error; | ||||
|     this[kMessage] = options.message === undefined ? '' : options.message; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {*} | ||||
|    */ | ||||
|   get error() { | ||||
|     return this[kError]; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {String} | ||||
|    */ | ||||
|   get message() { | ||||
|     return this[kMessage]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Object.defineProperty(ErrorEvent.prototype, 'error', { enumerable: true }); | ||||
| Object.defineProperty(ErrorEvent.prototype, 'message', { enumerable: true }); | ||||
|  | ||||
| /** | ||||
|  * Class representing a message event. | ||||
|  * | ||||
|  * @extends Event | ||||
|  */ | ||||
| class MessageEvent extends Event { | ||||
|   /** | ||||
|    * Create a new `MessageEvent`. | ||||
|    * | ||||
|    * @param {String} type The name of the event | ||||
|    * @param {Object} [options] A dictionary object that allows for setting | ||||
|    *     attributes via object members of the same name | ||||
|    * @param {*} [options.data=null] The message content | ||||
|    */ | ||||
|   constructor(type, options = {}) { | ||||
|     super(type); | ||||
|  | ||||
|     this[kData] = options.data === undefined ? null : options.data; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {*} | ||||
|    */ | ||||
|   get data() { | ||||
|     return this[kData]; | ||||
|   } | ||||
| } | ||||
|  | ||||
| Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true }); | ||||
|  | ||||
| /** | ||||
|  * This provides methods for emulating the `EventTarget` interface. It's not | ||||
|  * meant to be used directly. | ||||
|  * | ||||
|  * @mixin | ||||
|  */ | ||||
| const EventTarget = { | ||||
|   /** | ||||
|    * Register an event listener. | ||||
|    * | ||||
|    * @param {String} type A string representing the event type to listen for | ||||
|    * @param {(Function|Object)} handler The listener to add | ||||
|    * @param {Object} [options] An options object specifies characteristics about | ||||
|    *     the event listener | ||||
|    * @param {Boolean} [options.once=false] A `Boolean` indicating that the | ||||
|    *     listener should be invoked at most once after being added. If `true`, | ||||
|    *     the listener would be automatically removed when invoked. | ||||
|    * @public | ||||
|    */ | ||||
|   addEventListener(type, handler, options = {}) { | ||||
|     for (const listener of this.listeners(type)) { | ||||
|       if ( | ||||
|         !options[kForOnEventAttribute] && | ||||
|         listener[kListener] === handler && | ||||
|         !listener[kForOnEventAttribute] | ||||
|       ) { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     let wrapper; | ||||
|  | ||||
|     if (type === 'message') { | ||||
|       wrapper = function onMessage(data, isBinary) { | ||||
|         const event = new MessageEvent('message', { | ||||
|           data: isBinary ? data : data.toString() | ||||
|         }); | ||||
|  | ||||
|         event[kTarget] = this; | ||||
|         callListener(handler, this, event); | ||||
|       }; | ||||
|     } else if (type === 'close') { | ||||
|       wrapper = function onClose(code, message) { | ||||
|         const event = new CloseEvent('close', { | ||||
|           code, | ||||
|           reason: message.toString(), | ||||
|           wasClean: this._closeFrameReceived && this._closeFrameSent | ||||
|         }); | ||||
|  | ||||
|         event[kTarget] = this; | ||||
|         callListener(handler, this, event); | ||||
|       }; | ||||
|     } else if (type === 'error') { | ||||
|       wrapper = function onError(error) { | ||||
|         const event = new ErrorEvent('error', { | ||||
|           error, | ||||
|           message: error.message | ||||
|         }); | ||||
|  | ||||
|         event[kTarget] = this; | ||||
|         callListener(handler, this, event); | ||||
|       }; | ||||
|     } else if (type === 'open') { | ||||
|       wrapper = function onOpen() { | ||||
|         const event = new Event('open'); | ||||
|  | ||||
|         event[kTarget] = this; | ||||
|         callListener(handler, this, event); | ||||
|       }; | ||||
|     } else { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     wrapper[kForOnEventAttribute] = !!options[kForOnEventAttribute]; | ||||
|     wrapper[kListener] = handler; | ||||
|  | ||||
|     if (options.once) { | ||||
|       this.once(type, wrapper); | ||||
|     } else { | ||||
|       this.on(type, wrapper); | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   /** | ||||
|    * Remove an event listener. | ||||
|    * | ||||
|    * @param {String} type A string representing the event type to remove | ||||
|    * @param {(Function|Object)} handler The listener to remove | ||||
|    * @public | ||||
|    */ | ||||
|   removeEventListener(type, handler) { | ||||
|     for (const listener of this.listeners(type)) { | ||||
|       if (listener[kListener] === handler && !listener[kForOnEventAttribute]) { | ||||
|         this.removeListener(type, listener); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
|   CloseEvent, | ||||
|   ErrorEvent, | ||||
|   Event, | ||||
|   EventTarget, | ||||
|   MessageEvent | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * 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); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										203
									
								
								node_modules/ws/lib/extension.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								node_modules/ws/lib/extension.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { tokenChars } = require('./validation'); | ||||
|  | ||||
| /** | ||||
|  * Adds an offer to the map of extension offers or a parameter to the map of | ||||
|  * parameters. | ||||
|  * | ||||
|  * @param {Object} dest The map of extension offers or parameters | ||||
|  * @param {String} name The extension or parameter name | ||||
|  * @param {(Object|Boolean|String)} elem The extension parameters or the | ||||
|  *     parameter value | ||||
|  * @private | ||||
|  */ | ||||
| function push(dest, name, elem) { | ||||
|   if (dest[name] === undefined) dest[name] = [elem]; | ||||
|   else dest[name].push(elem); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Parses the `Sec-WebSocket-Extensions` header into an object. | ||||
|  * | ||||
|  * @param {String} header The field value of the header | ||||
|  * @return {Object} The parsed object | ||||
|  * @public | ||||
|  */ | ||||
| function parse(header) { | ||||
|   const offers = Object.create(null); | ||||
|   let params = Object.create(null); | ||||
|   let mustUnescape = false; | ||||
|   let isEscaping = false; | ||||
|   let inQuotes = false; | ||||
|   let extensionName; | ||||
|   let paramName; | ||||
|   let start = -1; | ||||
|   let code = -1; | ||||
|   let end = -1; | ||||
|   let i = 0; | ||||
|  | ||||
|   for (; i < header.length; i++) { | ||||
|     code = header.charCodeAt(i); | ||||
|  | ||||
|     if (extensionName === undefined) { | ||||
|       if (end === -1 && tokenChars[code] === 1) { | ||||
|         if (start === -1) start = i; | ||||
|       } else if ( | ||||
|         i !== 0 && | ||||
|         (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */ | ||||
|       ) { | ||||
|         if (end === -1 && start !== -1) end = i; | ||||
|       } else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) { | ||||
|         if (start === -1) { | ||||
|           throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|         } | ||||
|  | ||||
|         if (end === -1) end = i; | ||||
|         const name = header.slice(start, end); | ||||
|         if (code === 0x2c) { | ||||
|           push(offers, name, params); | ||||
|           params = Object.create(null); | ||||
|         } else { | ||||
|           extensionName = name; | ||||
|         } | ||||
|  | ||||
|         start = end = -1; | ||||
|       } else { | ||||
|         throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|       } | ||||
|     } else if (paramName === undefined) { | ||||
|       if (end === -1 && tokenChars[code] === 1) { | ||||
|         if (start === -1) start = i; | ||||
|       } else if (code === 0x20 || code === 0x09) { | ||||
|         if (end === -1 && start !== -1) end = i; | ||||
|       } else if (code === 0x3b || code === 0x2c) { | ||||
|         if (start === -1) { | ||||
|           throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|         } | ||||
|  | ||||
|         if (end === -1) end = i; | ||||
|         push(params, header.slice(start, end), true); | ||||
|         if (code === 0x2c) { | ||||
|           push(offers, extensionName, params); | ||||
|           params = Object.create(null); | ||||
|           extensionName = undefined; | ||||
|         } | ||||
|  | ||||
|         start = end = -1; | ||||
|       } else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) { | ||||
|         paramName = header.slice(start, i); | ||||
|         start = end = -1; | ||||
|       } else { | ||||
|         throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|       } | ||||
|     } else { | ||||
|       // | ||||
|       // The value of a quoted-string after unescaping must conform to the | ||||
|       // token ABNF, so only token characters are valid. | ||||
|       // Ref: https://tools.ietf.org/html/rfc6455#section-9.1 | ||||
|       // | ||||
|       if (isEscaping) { | ||||
|         if (tokenChars[code] !== 1) { | ||||
|           throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|         } | ||||
|         if (start === -1) start = i; | ||||
|         else if (!mustUnescape) mustUnescape = true; | ||||
|         isEscaping = false; | ||||
|       } else if (inQuotes) { | ||||
|         if (tokenChars[code] === 1) { | ||||
|           if (start === -1) start = i; | ||||
|         } else if (code === 0x22 /* '"' */ && start !== -1) { | ||||
|           inQuotes = false; | ||||
|           end = i; | ||||
|         } else if (code === 0x5c /* '\' */) { | ||||
|           isEscaping = true; | ||||
|         } else { | ||||
|           throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|         } | ||||
|       } else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { | ||||
|         inQuotes = true; | ||||
|       } else if (end === -1 && tokenChars[code] === 1) { | ||||
|         if (start === -1) start = i; | ||||
|       } else if (start !== -1 && (code === 0x20 || code === 0x09)) { | ||||
|         if (end === -1) end = i; | ||||
|       } else if (code === 0x3b || code === 0x2c) { | ||||
|         if (start === -1) { | ||||
|           throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|         } | ||||
|  | ||||
|         if (end === -1) end = i; | ||||
|         let value = header.slice(start, end); | ||||
|         if (mustUnescape) { | ||||
|           value = value.replace(/\\/g, ''); | ||||
|           mustUnescape = false; | ||||
|         } | ||||
|         push(params, paramName, value); | ||||
|         if (code === 0x2c) { | ||||
|           push(offers, extensionName, params); | ||||
|           params = Object.create(null); | ||||
|           extensionName = undefined; | ||||
|         } | ||||
|  | ||||
|         paramName = undefined; | ||||
|         start = end = -1; | ||||
|       } else { | ||||
|         throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (start === -1 || inQuotes || code === 0x20 || code === 0x09) { | ||||
|     throw new SyntaxError('Unexpected end of input'); | ||||
|   } | ||||
|  | ||||
|   if (end === -1) end = i; | ||||
|   const token = header.slice(start, end); | ||||
|   if (extensionName === undefined) { | ||||
|     push(offers, token, params); | ||||
|   } else { | ||||
|     if (paramName === undefined) { | ||||
|       push(params, token, true); | ||||
|     } else if (mustUnescape) { | ||||
|       push(params, paramName, token.replace(/\\/g, '')); | ||||
|     } else { | ||||
|       push(params, paramName, token); | ||||
|     } | ||||
|     push(offers, extensionName, params); | ||||
|   } | ||||
|  | ||||
|   return offers; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Builds the `Sec-WebSocket-Extensions` header field value. | ||||
|  * | ||||
|  * @param {Object} extensions The map of extensions and parameters to format | ||||
|  * @return {String} A string representing the given object | ||||
|  * @public | ||||
|  */ | ||||
| function format(extensions) { | ||||
|   return Object.keys(extensions) | ||||
|     .map((extension) => { | ||||
|       let configurations = extensions[extension]; | ||||
|       if (!Array.isArray(configurations)) configurations = [configurations]; | ||||
|       return configurations | ||||
|         .map((params) => { | ||||
|           return [extension] | ||||
|             .concat( | ||||
|               Object.keys(params).map((k) => { | ||||
|                 let values = params[k]; | ||||
|                 if (!Array.isArray(values)) values = [values]; | ||||
|                 return values | ||||
|                   .map((v) => (v === true ? k : `${k}=${v}`)) | ||||
|                   .join('; '); | ||||
|               }) | ||||
|             ) | ||||
|             .join('; '); | ||||
|         }) | ||||
|         .join(', '); | ||||
|     }) | ||||
|     .join(', '); | ||||
| } | ||||
|  | ||||
| module.exports = { format, parse }; | ||||
							
								
								
									
										55
									
								
								node_modules/ws/lib/limiter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								node_modules/ws/lib/limiter.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const kDone = Symbol('kDone'); | ||||
| const kRun = Symbol('kRun'); | ||||
|  | ||||
| /** | ||||
|  * A very simple job queue with adjustable concurrency. Adapted from | ||||
|  * https://github.com/STRML/async-limiter | ||||
|  */ | ||||
| class Limiter { | ||||
|   /** | ||||
|    * Creates a new `Limiter`. | ||||
|    * | ||||
|    * @param {Number} [concurrency=Infinity] The maximum number of jobs allowed | ||||
|    *     to run concurrently | ||||
|    */ | ||||
|   constructor(concurrency) { | ||||
|     this[kDone] = () => { | ||||
|       this.pending--; | ||||
|       this[kRun](); | ||||
|     }; | ||||
|     this.concurrency = concurrency || Infinity; | ||||
|     this.jobs = []; | ||||
|     this.pending = 0; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Adds a job to the queue. | ||||
|    * | ||||
|    * @param {Function} job The job to run | ||||
|    * @public | ||||
|    */ | ||||
|   add(job) { | ||||
|     this.jobs.push(job); | ||||
|     this[kRun](); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Removes a job from the queue and runs it if possible. | ||||
|    * | ||||
|    * @private | ||||
|    */ | ||||
|   [kRun]() { | ||||
|     if (this.pending === this.concurrency) return; | ||||
|  | ||||
|     if (this.jobs.length) { | ||||
|       const job = this.jobs.shift(); | ||||
|  | ||||
|       this.pending++; | ||||
|       job(this[kDone]); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = Limiter; | ||||
							
								
								
									
										514
									
								
								node_modules/ws/lib/permessage-deflate.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								node_modules/ws/lib/permessage-deflate.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,514 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const zlib = require('zlib'); | ||||
|  | ||||
| const bufferUtil = require('./buffer-util'); | ||||
| const Limiter = require('./limiter'); | ||||
| const { kStatusCode } = require('./constants'); | ||||
|  | ||||
| const FastBuffer = Buffer[Symbol.species]; | ||||
| const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]); | ||||
| const kPerMessageDeflate = Symbol('permessage-deflate'); | ||||
| const kTotalLength = Symbol('total-length'); | ||||
| const kCallback = Symbol('callback'); | ||||
| const kBuffers = Symbol('buffers'); | ||||
| const kError = Symbol('error'); | ||||
|  | ||||
| // | ||||
| // We limit zlib concurrency, which prevents severe memory fragmentation | ||||
| // as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913 | ||||
| // and https://github.com/websockets/ws/issues/1202 | ||||
| // | ||||
| // Intentionally global; it's the global thread pool that's an issue. | ||||
| // | ||||
| let zlibLimiter; | ||||
|  | ||||
| /** | ||||
|  * permessage-deflate implementation. | ||||
|  */ | ||||
| class PerMessageDeflate { | ||||
|   /** | ||||
|    * Creates a PerMessageDeflate instance. | ||||
|    * | ||||
|    * @param {Object} [options] Configuration options | ||||
|    * @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support | ||||
|    *     for, or request, a custom client window size | ||||
|    * @param {Boolean} [options.clientNoContextTakeover=false] Advertise/ | ||||
|    *     acknowledge disabling of client context takeover | ||||
|    * @param {Number} [options.concurrencyLimit=10] The number of concurrent | ||||
|    *     calls to zlib | ||||
|    * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the | ||||
|    *     use of a custom server window size | ||||
|    * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept | ||||
|    *     disabling of server context takeover | ||||
|    * @param {Number} [options.threshold=1024] Size (in bytes) below which | ||||
|    *     messages should not be compressed if context takeover is disabled | ||||
|    * @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on | ||||
|    *     deflate | ||||
|    * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on | ||||
|    *     inflate | ||||
|    * @param {Boolean} [isServer=false] Create the instance in either server or | ||||
|    *     client mode | ||||
|    * @param {Number} [maxPayload=0] The maximum allowed message length | ||||
|    */ | ||||
|   constructor(options, isServer, maxPayload) { | ||||
|     this._maxPayload = maxPayload | 0; | ||||
|     this._options = options || {}; | ||||
|     this._threshold = | ||||
|       this._options.threshold !== undefined ? this._options.threshold : 1024; | ||||
|     this._isServer = !!isServer; | ||||
|     this._deflate = null; | ||||
|     this._inflate = null; | ||||
|  | ||||
|     this.params = null; | ||||
|  | ||||
|     if (!zlibLimiter) { | ||||
|       const concurrency = | ||||
|         this._options.concurrencyLimit !== undefined | ||||
|           ? this._options.concurrencyLimit | ||||
|           : 10; | ||||
|       zlibLimiter = new Limiter(concurrency); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @type {String} | ||||
|    */ | ||||
|   static get extensionName() { | ||||
|     return 'permessage-deflate'; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Create an extension negotiation offer. | ||||
|    * | ||||
|    * @return {Object} Extension parameters | ||||
|    * @public | ||||
|    */ | ||||
|   offer() { | ||||
|     const params = {}; | ||||
|  | ||||
|     if (this._options.serverNoContextTakeover) { | ||||
|       params.server_no_context_takeover = true; | ||||
|     } | ||||
|     if (this._options.clientNoContextTakeover) { | ||||
|       params.client_no_context_takeover = true; | ||||
|     } | ||||
|     if (this._options.serverMaxWindowBits) { | ||||
|       params.server_max_window_bits = this._options.serverMaxWindowBits; | ||||
|     } | ||||
|     if (this._options.clientMaxWindowBits) { | ||||
|       params.client_max_window_bits = this._options.clientMaxWindowBits; | ||||
|     } else if (this._options.clientMaxWindowBits == null) { | ||||
|       params.client_max_window_bits = true; | ||||
|     } | ||||
|  | ||||
|     return params; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Accept an extension negotiation offer/response. | ||||
|    * | ||||
|    * @param {Array} configurations The extension negotiation offers/reponse | ||||
|    * @return {Object} Accepted configuration | ||||
|    * @public | ||||
|    */ | ||||
|   accept(configurations) { | ||||
|     configurations = this.normalizeParams(configurations); | ||||
|  | ||||
|     this.params = this._isServer | ||||
|       ? this.acceptAsServer(configurations) | ||||
|       : this.acceptAsClient(configurations); | ||||
|  | ||||
|     return this.params; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Releases all resources used by the extension. | ||||
|    * | ||||
|    * @public | ||||
|    */ | ||||
|   cleanup() { | ||||
|     if (this._inflate) { | ||||
|       this._inflate.close(); | ||||
|       this._inflate = null; | ||||
|     } | ||||
|  | ||||
|     if (this._deflate) { | ||||
|       const callback = this._deflate[kCallback]; | ||||
|  | ||||
|       this._deflate.close(); | ||||
|       this._deflate = null; | ||||
|  | ||||
|       if (callback) { | ||||
|         callback( | ||||
|           new Error( | ||||
|             'The deflate stream was closed while data was being processed' | ||||
|           ) | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    *  Accept an extension negotiation offer. | ||||
|    * | ||||
|    * @param {Array} offers The extension negotiation offers | ||||
|    * @return {Object} Accepted configuration | ||||
|    * @private | ||||
|    */ | ||||
|   acceptAsServer(offers) { | ||||
|     const opts = this._options; | ||||
|     const accepted = offers.find((params) => { | ||||
|       if ( | ||||
|         (opts.serverNoContextTakeover === false && | ||||
|           params.server_no_context_takeover) || | ||||
|         (params.server_max_window_bits && | ||||
|           (opts.serverMaxWindowBits === false || | ||||
|             (typeof opts.serverMaxWindowBits === 'number' && | ||||
|               opts.serverMaxWindowBits > params.server_max_window_bits))) || | ||||
|         (typeof opts.clientMaxWindowBits === 'number' && | ||||
|           !params.client_max_window_bits) | ||||
|       ) { | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       return true; | ||||
|     }); | ||||
|  | ||||
|     if (!accepted) { | ||||
|       throw new Error('None of the extension offers can be accepted'); | ||||
|     } | ||||
|  | ||||
|     if (opts.serverNoContextTakeover) { | ||||
|       accepted.server_no_context_takeover = true; | ||||
|     } | ||||
|     if (opts.clientNoContextTakeover) { | ||||
|       accepted.client_no_context_takeover = true; | ||||
|     } | ||||
|     if (typeof opts.serverMaxWindowBits === 'number') { | ||||
|       accepted.server_max_window_bits = opts.serverMaxWindowBits; | ||||
|     } | ||||
|     if (typeof opts.clientMaxWindowBits === 'number') { | ||||
|       accepted.client_max_window_bits = opts.clientMaxWindowBits; | ||||
|     } else if ( | ||||
|       accepted.client_max_window_bits === true || | ||||
|       opts.clientMaxWindowBits === false | ||||
|     ) { | ||||
|       delete accepted.client_max_window_bits; | ||||
|     } | ||||
|  | ||||
|     return accepted; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Accept the extension negotiation response. | ||||
|    * | ||||
|    * @param {Array} response The extension negotiation response | ||||
|    * @return {Object} Accepted configuration | ||||
|    * @private | ||||
|    */ | ||||
|   acceptAsClient(response) { | ||||
|     const params = response[0]; | ||||
|  | ||||
|     if ( | ||||
|       this._options.clientNoContextTakeover === false && | ||||
|       params.client_no_context_takeover | ||||
|     ) { | ||||
|       throw new Error('Unexpected parameter "client_no_context_takeover"'); | ||||
|     } | ||||
|  | ||||
|     if (!params.client_max_window_bits) { | ||||
|       if (typeof this._options.clientMaxWindowBits === 'number') { | ||||
|         params.client_max_window_bits = this._options.clientMaxWindowBits; | ||||
|       } | ||||
|     } else if ( | ||||
|       this._options.clientMaxWindowBits === false || | ||||
|       (typeof this._options.clientMaxWindowBits === 'number' && | ||||
|         params.client_max_window_bits > this._options.clientMaxWindowBits) | ||||
|     ) { | ||||
|       throw new Error( | ||||
|         'Unexpected or invalid parameter "client_max_window_bits"' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return params; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Normalize parameters. | ||||
|    * | ||||
|    * @param {Array} configurations The extension negotiation offers/reponse | ||||
|    * @return {Array} The offers/response with normalized parameters | ||||
|    * @private | ||||
|    */ | ||||
|   normalizeParams(configurations) { | ||||
|     configurations.forEach((params) => { | ||||
|       Object.keys(params).forEach((key) => { | ||||
|         let value = params[key]; | ||||
|  | ||||
|         if (value.length > 1) { | ||||
|           throw new Error(`Parameter "${key}" must have only a single value`); | ||||
|         } | ||||
|  | ||||
|         value = value[0]; | ||||
|  | ||||
|         if (key === 'client_max_window_bits') { | ||||
|           if (value !== true) { | ||||
|             const num = +value; | ||||
|             if (!Number.isInteger(num) || num < 8 || num > 15) { | ||||
|               throw new TypeError( | ||||
|                 `Invalid value for parameter "${key}": ${value}` | ||||
|               ); | ||||
|             } | ||||
|             value = num; | ||||
|           } else if (!this._isServer) { | ||||
|             throw new TypeError( | ||||
|               `Invalid value for parameter "${key}": ${value}` | ||||
|             ); | ||||
|           } | ||||
|         } else if (key === 'server_max_window_bits') { | ||||
|           const num = +value; | ||||
|           if (!Number.isInteger(num) || num < 8 || num > 15) { | ||||
|             throw new TypeError( | ||||
|               `Invalid value for parameter "${key}": ${value}` | ||||
|             ); | ||||
|           } | ||||
|           value = num; | ||||
|         } else if ( | ||||
|           key === 'client_no_context_takeover' || | ||||
|           key === 'server_no_context_takeover' | ||||
|         ) { | ||||
|           if (value !== true) { | ||||
|             throw new TypeError( | ||||
|               `Invalid value for parameter "${key}": ${value}` | ||||
|             ); | ||||
|           } | ||||
|         } else { | ||||
|           throw new Error(`Unknown parameter "${key}"`); | ||||
|         } | ||||
|  | ||||
|         params[key] = value; | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     return configurations; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Decompress data. Concurrency limited. | ||||
|    * | ||||
|    * @param {Buffer} data Compressed data | ||||
|    * @param {Boolean} fin Specifies whether or not this is the last fragment | ||||
|    * @param {Function} callback Callback | ||||
|    * @public | ||||
|    */ | ||||
|   decompress(data, fin, callback) { | ||||
|     zlibLimiter.add((done) => { | ||||
|       this._decompress(data, fin, (err, result) => { | ||||
|         done(); | ||||
|         callback(err, result); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Compress data. Concurrency limited. | ||||
|    * | ||||
|    * @param {(Buffer|String)} data Data to compress | ||||
|    * @param {Boolean} fin Specifies whether or not this is the last fragment | ||||
|    * @param {Function} callback Callback | ||||
|    * @public | ||||
|    */ | ||||
|   compress(data, fin, callback) { | ||||
|     zlibLimiter.add((done) => { | ||||
|       this._compress(data, fin, (err, result) => { | ||||
|         done(); | ||||
|         callback(err, result); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Decompress data. | ||||
|    * | ||||
|    * @param {Buffer} data Compressed data | ||||
|    * @param {Boolean} fin Specifies whether or not this is the last fragment | ||||
|    * @param {Function} callback Callback | ||||
|    * @private | ||||
|    */ | ||||
|   _decompress(data, fin, callback) { | ||||
|     const endpoint = this._isServer ? 'client' : 'server'; | ||||
|  | ||||
|     if (!this._inflate) { | ||||
|       const key = `${endpoint}_max_window_bits`; | ||||
|       const windowBits = | ||||
|         typeof this.params[key] !== 'number' | ||||
|           ? zlib.Z_DEFAULT_WINDOWBITS | ||||
|           : this.params[key]; | ||||
|  | ||||
|       this._inflate = zlib.createInflateRaw({ | ||||
|         ...this._options.zlibInflateOptions, | ||||
|         windowBits | ||||
|       }); | ||||
|       this._inflate[kPerMessageDeflate] = this; | ||||
|       this._inflate[kTotalLength] = 0; | ||||
|       this._inflate[kBuffers] = []; | ||||
|       this._inflate.on('error', inflateOnError); | ||||
|       this._inflate.on('data', inflateOnData); | ||||
|     } | ||||
|  | ||||
|     this._inflate[kCallback] = callback; | ||||
|  | ||||
|     this._inflate.write(data); | ||||
|     if (fin) this._inflate.write(TRAILER); | ||||
|  | ||||
|     this._inflate.flush(() => { | ||||
|       const err = this._inflate[kError]; | ||||
|  | ||||
|       if (err) { | ||||
|         this._inflate.close(); | ||||
|         this._inflate = null; | ||||
|         callback(err); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       const data = bufferUtil.concat( | ||||
|         this._inflate[kBuffers], | ||||
|         this._inflate[kTotalLength] | ||||
|       ); | ||||
|  | ||||
|       if (this._inflate._readableState.endEmitted) { | ||||
|         this._inflate.close(); | ||||
|         this._inflate = null; | ||||
|       } else { | ||||
|         this._inflate[kTotalLength] = 0; | ||||
|         this._inflate[kBuffers] = []; | ||||
|  | ||||
|         if (fin && this.params[`${endpoint}_no_context_takeover`]) { | ||||
|           this._inflate.reset(); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       callback(null, data); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Compress data. | ||||
|    * | ||||
|    * @param {(Buffer|String)} data Data to compress | ||||
|    * @param {Boolean} fin Specifies whether or not this is the last fragment | ||||
|    * @param {Function} callback Callback | ||||
|    * @private | ||||
|    */ | ||||
|   _compress(data, fin, callback) { | ||||
|     const endpoint = this._isServer ? 'server' : 'client'; | ||||
|  | ||||
|     if (!this._deflate) { | ||||
|       const key = `${endpoint}_max_window_bits`; | ||||
|       const windowBits = | ||||
|         typeof this.params[key] !== 'number' | ||||
|           ? zlib.Z_DEFAULT_WINDOWBITS | ||||
|           : this.params[key]; | ||||
|  | ||||
|       this._deflate = zlib.createDeflateRaw({ | ||||
|         ...this._options.zlibDeflateOptions, | ||||
|         windowBits | ||||
|       }); | ||||
|  | ||||
|       this._deflate[kTotalLength] = 0; | ||||
|       this._deflate[kBuffers] = []; | ||||
|  | ||||
|       this._deflate.on('data', deflateOnData); | ||||
|     } | ||||
|  | ||||
|     this._deflate[kCallback] = callback; | ||||
|  | ||||
|     this._deflate.write(data); | ||||
|     this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { | ||||
|       if (!this._deflate) { | ||||
|         // | ||||
|         // The deflate stream was closed while data was being processed. | ||||
|         // | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       let data = bufferUtil.concat( | ||||
|         this._deflate[kBuffers], | ||||
|         this._deflate[kTotalLength] | ||||
|       ); | ||||
|  | ||||
|       if (fin) { | ||||
|         data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4); | ||||
|       } | ||||
|  | ||||
|       // | ||||
|       // Ensure that the callback will not be called again in | ||||
|       // `PerMessageDeflate#cleanup()`. | ||||
|       // | ||||
|       this._deflate[kCallback] = null; | ||||
|  | ||||
|       this._deflate[kTotalLength] = 0; | ||||
|       this._deflate[kBuffers] = []; | ||||
|  | ||||
|       if (fin && this.params[`${endpoint}_no_context_takeover`]) { | ||||
|         this._deflate.reset(); | ||||
|       } | ||||
|  | ||||
|       callback(null, data); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = PerMessageDeflate; | ||||
|  | ||||
| /** | ||||
|  * The listener of the `zlib.DeflateRaw` stream `'data'` event. | ||||
|  * | ||||
|  * @param {Buffer} chunk A chunk of data | ||||
|  * @private | ||||
|  */ | ||||
| function deflateOnData(chunk) { | ||||
|   this[kBuffers].push(chunk); | ||||
|   this[kTotalLength] += chunk.length; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The listener of the `zlib.InflateRaw` stream `'data'` event. | ||||
|  * | ||||
|  * @param {Buffer} chunk A chunk of data | ||||
|  * @private | ||||
|  */ | ||||
| function inflateOnData(chunk) { | ||||
|   this[kTotalLength] += chunk.length; | ||||
|  | ||||
|   if ( | ||||
|     this[kPerMessageDeflate]._maxPayload < 1 || | ||||
|     this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload | ||||
|   ) { | ||||
|     this[kBuffers].push(chunk); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this[kError] = new RangeError('Max payload size exceeded'); | ||||
|   this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH'; | ||||
|   this[kError][kStatusCode] = 1009; | ||||
|   this.removeListener('data', inflateOnData); | ||||
|   this.reset(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The listener of the `zlib.InflateRaw` stream `'error'` event. | ||||
|  * | ||||
|  * @param {Error} err The emitted error | ||||
|  * @private | ||||
|  */ | ||||
| function inflateOnError(err) { | ||||
|   // | ||||
|   // There is no need to call `Zlib#close()` as the handle is automatically | ||||
|   // closed when an error is emitted. | ||||
|   // | ||||
|   this[kPerMessageDeflate]._inflate = null; | ||||
|   err[kStatusCode] = 1007; | ||||
|   this[kCallback](err); | ||||
| } | ||||
							
								
								
									
										679
									
								
								node_modules/ws/lib/receiver.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										679
									
								
								node_modules/ws/lib/receiver.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,679 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { Writable } = require('stream'); | ||||
|  | ||||
| const PerMessageDeflate = require('./permessage-deflate'); | ||||
| const { | ||||
|   BINARY_TYPES, | ||||
|   EMPTY_BUFFER, | ||||
|   kStatusCode, | ||||
|   kWebSocket | ||||
| } = require('./constants'); | ||||
| const { concat, toArrayBuffer, unmask } = require('./buffer-util'); | ||||
| const { isValidStatusCode, isValidUTF8 } = require('./validation'); | ||||
|  | ||||
| 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; | ||||
| const GET_PAYLOAD_LENGTH_16 = 1; | ||||
| const GET_PAYLOAD_LENGTH_64 = 2; | ||||
| const GET_MASK = 3; | ||||
| const GET_DATA = 4; | ||||
| const INFLATING = 5; | ||||
| const WAIT_MICROTASK = 6; | ||||
|  | ||||
| /** | ||||
|  * HyBi Receiver implementation. | ||||
|  * | ||||
|  * @extends Writable | ||||
|  */ | ||||
| class Receiver extends Writable { | ||||
|   /** | ||||
|    * Creates a Receiver instance. | ||||
|    * | ||||
|    * @param {Object} [options] Options object | ||||
|    * @param {String} [options.binaryType=nodebuffer] The type for binary data | ||||
|    * @param {Object} [options.extensions] An object containing the negotiated | ||||
|    *     extensions | ||||
|    * @param {Boolean} [options.isServer=false] Specifies whether to operate in | ||||
|    *     client or server mode | ||||
|    * @param {Number} [options.maxPayload=0] The maximum allowed message length | ||||
|    * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or | ||||
|    *     not to skip UTF-8 validation for text and close messages | ||||
|    */ | ||||
|   constructor(options = {}) { | ||||
|     super(); | ||||
|  | ||||
|     this._binaryType = options.binaryType || BINARY_TYPES[0]; | ||||
|     this._extensions = options.extensions || {}; | ||||
|     this._isServer = !!options.isServer; | ||||
|     this._maxPayload = options.maxPayload | 0; | ||||
|     this._skipUTF8Validation = !!options.skipUTF8Validation; | ||||
|     this[kWebSocket] = undefined; | ||||
|  | ||||
|     this._bufferedBytes = 0; | ||||
|     this._buffers = []; | ||||
|  | ||||
|     this._compressed = false; | ||||
|     this._payloadLength = 0; | ||||
|     this._mask = undefined; | ||||
|     this._fragmented = 0; | ||||
|     this._masked = false; | ||||
|     this._fin = false; | ||||
|     this._opcode = 0; | ||||
|  | ||||
|     this._totalPayloadLength = 0; | ||||
|     this._messageLength = 0; | ||||
|     this._fragments = []; | ||||
|  | ||||
|     this._state = GET_INFO; | ||||
|     this._loop = false; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Implements `Writable.prototype._write()`. | ||||
|    * | ||||
|    * @param {Buffer} chunk The chunk of data to write | ||||
|    * @param {String} encoding The character encoding of `chunk` | ||||
|    * @param {Function} cb Callback | ||||
|    * @private | ||||
|    */ | ||||
|   _write(chunk, encoding, cb) { | ||||
|     if (this._opcode === 0x08 && this._state == GET_INFO) return cb(); | ||||
|  | ||||
|     this._bufferedBytes += chunk.length; | ||||
|     this._buffers.push(chunk); | ||||
|     this.startLoop(cb); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Consumes `n` bytes from the buffered data. | ||||
|    * | ||||
|    * @param {Number} n The number of bytes to consume | ||||
|    * @return {Buffer} The consumed bytes | ||||
|    * @private | ||||
|    */ | ||||
|   consume(n) { | ||||
|     this._bufferedBytes -= n; | ||||
|  | ||||
|     if (n === this._buffers[0].length) return this._buffers.shift(); | ||||
|  | ||||
|     if (n < this._buffers[0].length) { | ||||
|       const buf = this._buffers[0]; | ||||
|       this._buffers[0] = new FastBuffer( | ||||
|         buf.buffer, | ||||
|         buf.byteOffset + n, | ||||
|         buf.length - n | ||||
|       ); | ||||
|  | ||||
|       return new FastBuffer(buf.buffer, buf.byteOffset, n); | ||||
|     } | ||||
|  | ||||
|     const dst = Buffer.allocUnsafe(n); | ||||
|  | ||||
|     do { | ||||
|       const buf = this._buffers[0]; | ||||
|       const offset = dst.length - n; | ||||
|  | ||||
|       if (n >= buf.length) { | ||||
|         dst.set(this._buffers.shift(), offset); | ||||
|       } else { | ||||
|         dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset); | ||||
|         this._buffers[0] = new FastBuffer( | ||||
|           buf.buffer, | ||||
|           buf.byteOffset + n, | ||||
|           buf.length - n | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       n -= buf.length; | ||||
|     } while (n > 0); | ||||
|  | ||||
|     return dst; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Starts the parsing loop. | ||||
|    * | ||||
|    * @param {Function} cb Callback | ||||
|    * @private | ||||
|    */ | ||||
|   startLoop(cb) { | ||||
|     let err; | ||||
|     this._loop = true; | ||||
|  | ||||
|     do { | ||||
|       switch (this._state) { | ||||
|         case GET_INFO: | ||||
|           err = this.getInfo(); | ||||
|           break; | ||||
|         case GET_PAYLOAD_LENGTH_16: | ||||
|           err = this.getPayloadLength16(); | ||||
|           break; | ||||
|         case GET_PAYLOAD_LENGTH_64: | ||||
|           err = this.getPayloadLength64(); | ||||
|           break; | ||||
|         case GET_MASK: | ||||
|           this.getMask(); | ||||
|           break; | ||||
|         case GET_DATA: | ||||
|           err = this.getData(cb); | ||||
|           break; | ||||
|         case INFLATING: | ||||
|           this._loop = false; | ||||
|           return; | ||||
|         default: | ||||
|           // | ||||
|           // `WAIT_MICROTASK`. | ||||
|           // | ||||
|           this._loop = false; | ||||
|  | ||||
|           queueTask(() => { | ||||
|             this._state = GET_INFO; | ||||
|             this.startLoop(cb); | ||||
|           }); | ||||
|           return; | ||||
|       } | ||||
|     } while (this._loop); | ||||
|  | ||||
|     cb(err); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Reads the first two bytes of a frame. | ||||
|    * | ||||
|    * @return {(RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   getInfo() { | ||||
|     if (this._bufferedBytes < 2) { | ||||
|       this._loop = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const buf = this.consume(2); | ||||
|  | ||||
|     if ((buf[0] & 0x30) !== 0x00) { | ||||
|       this._loop = false; | ||||
|       return error( | ||||
|         RangeError, | ||||
|         'RSV2 and RSV3 must be clear', | ||||
|         true, | ||||
|         1002, | ||||
|         'WS_ERR_UNEXPECTED_RSV_2_3' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     const compressed = (buf[0] & 0x40) === 0x40; | ||||
|  | ||||
|     if (compressed && !this._extensions[PerMessageDeflate.extensionName]) { | ||||
|       this._loop = false; | ||||
|       return error( | ||||
|         RangeError, | ||||
|         'RSV1 must be clear', | ||||
|         true, | ||||
|         1002, | ||||
|         'WS_ERR_UNEXPECTED_RSV_1' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     this._fin = (buf[0] & 0x80) === 0x80; | ||||
|     this._opcode = buf[0] & 0x0f; | ||||
|     this._payloadLength = buf[1] & 0x7f; | ||||
|  | ||||
|     if (this._opcode === 0x00) { | ||||
|       if (compressed) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'RSV1 must be clear', | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_UNEXPECTED_RSV_1' | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       if (!this._fragmented) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'invalid opcode 0', | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_INVALID_OPCODE' | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       this._opcode = this._fragmented; | ||||
|     } else if (this._opcode === 0x01 || this._opcode === 0x02) { | ||||
|       if (this._fragmented) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           `invalid opcode ${this._opcode}`, | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_INVALID_OPCODE' | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       this._compressed = compressed; | ||||
|     } else if (this._opcode > 0x07 && this._opcode < 0x0b) { | ||||
|       if (!this._fin) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'FIN must be set', | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_EXPECTED_FIN' | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       if (compressed) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'RSV1 must be clear', | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_UNEXPECTED_RSV_1' | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       if ( | ||||
|         this._payloadLength > 0x7d || | ||||
|         (this._opcode === 0x08 && this._payloadLength === 1) | ||||
|       ) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           `invalid payload length ${this._payloadLength}`, | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH' | ||||
|         ); | ||||
|       } | ||||
|     } else { | ||||
|       this._loop = false; | ||||
|       return error( | ||||
|         RangeError, | ||||
|         `invalid opcode ${this._opcode}`, | ||||
|         true, | ||||
|         1002, | ||||
|         'WS_ERR_INVALID_OPCODE' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (!this._fin && !this._fragmented) this._fragmented = this._opcode; | ||||
|     this._masked = (buf[1] & 0x80) === 0x80; | ||||
|  | ||||
|     if (this._isServer) { | ||||
|       if (!this._masked) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'MASK must be set', | ||||
|           true, | ||||
|           1002, | ||||
|           'WS_ERR_EXPECTED_MASK' | ||||
|         ); | ||||
|       } | ||||
|     } else if (this._masked) { | ||||
|       this._loop = false; | ||||
|       return error( | ||||
|         RangeError, | ||||
|         'MASK must be clear', | ||||
|         true, | ||||
|         1002, | ||||
|         'WS_ERR_UNEXPECTED_MASK' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (this._payloadLength === 126) this._state = GET_PAYLOAD_LENGTH_16; | ||||
|     else if (this._payloadLength === 127) this._state = GET_PAYLOAD_LENGTH_64; | ||||
|     else return this.haveLength(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Gets extended payload length (7+16). | ||||
|    * | ||||
|    * @return {(RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   getPayloadLength16() { | ||||
|     if (this._bufferedBytes < 2) { | ||||
|       this._loop = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this._payloadLength = this.consume(2).readUInt16BE(0); | ||||
|     return this.haveLength(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Gets extended payload length (7+64). | ||||
|    * | ||||
|    * @return {(RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   getPayloadLength64() { | ||||
|     if (this._bufferedBytes < 8) { | ||||
|       this._loop = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const buf = this.consume(8); | ||||
|     const num = buf.readUInt32BE(0); | ||||
|  | ||||
|     // | ||||
|     // The maximum safe integer in JavaScript is 2^53 - 1. An error is returned | ||||
|     // if payload length is greater than this number. | ||||
|     // | ||||
|     if (num > Math.pow(2, 53 - 32) - 1) { | ||||
|       this._loop = false; | ||||
|       return error( | ||||
|         RangeError, | ||||
|         'Unsupported WebSocket frame: payload length > 2^53 - 1', | ||||
|         false, | ||||
|         1009, | ||||
|         'WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4); | ||||
|     return this.haveLength(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Payload length has been read. | ||||
|    * | ||||
|    * @return {(RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   haveLength() { | ||||
|     if (this._payloadLength && this._opcode < 0x08) { | ||||
|       this._totalPayloadLength += this._payloadLength; | ||||
|       if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { | ||||
|         this._loop = false; | ||||
|         return error( | ||||
|           RangeError, | ||||
|           'Max payload size exceeded', | ||||
|           false, | ||||
|           1009, | ||||
|           'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (this._masked) this._state = GET_MASK; | ||||
|     else this._state = GET_DATA; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Reads mask bytes. | ||||
|    * | ||||
|    * @private | ||||
|    */ | ||||
|   getMask() { | ||||
|     if (this._bufferedBytes < 4) { | ||||
|       this._loop = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this._mask = this.consume(4); | ||||
|     this._state = GET_DATA; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Reads data bytes. | ||||
|    * | ||||
|    * @param {Function} cb Callback | ||||
|    * @return {(Error|RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   getData(cb) { | ||||
|     let data = EMPTY_BUFFER; | ||||
|  | ||||
|     if (this._payloadLength) { | ||||
|       if (this._bufferedBytes < this._payloadLength) { | ||||
|         this._loop = false; | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       data = this.consume(this._payloadLength); | ||||
|  | ||||
|       if ( | ||||
|         this._masked && | ||||
|         (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0 | ||||
|       ) { | ||||
|         unmask(data, this._mask); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (this._opcode > 0x07) return this.controlMessage(data); | ||||
|  | ||||
|     if (this._compressed) { | ||||
|       this._state = INFLATING; | ||||
|       this.decompress(data, cb); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (data.length) { | ||||
|       // | ||||
|       // This message is not compressed so its length is the sum of the payload | ||||
|       // length of all fragments. | ||||
|       // | ||||
|       this._messageLength = this._totalPayloadLength; | ||||
|       this._fragments.push(data); | ||||
|     } | ||||
|  | ||||
|     return this.dataMessage(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Decompresses data. | ||||
|    * | ||||
|    * @param {Buffer} data Compressed data | ||||
|    * @param {Function} cb Callback | ||||
|    * @private | ||||
|    */ | ||||
|   decompress(data, cb) { | ||||
|     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; | ||||
|  | ||||
|     perMessageDeflate.decompress(data, this._fin, (err, buf) => { | ||||
|       if (err) return cb(err); | ||||
|  | ||||
|       if (buf.length) { | ||||
|         this._messageLength += buf.length; | ||||
|         if (this._messageLength > this._maxPayload && this._maxPayload > 0) { | ||||
|           return cb( | ||||
|             error( | ||||
|               RangeError, | ||||
|               'Max payload size exceeded', | ||||
|               false, | ||||
|               1009, | ||||
|               'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH' | ||||
|             ) | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         this._fragments.push(buf); | ||||
|       } | ||||
|  | ||||
|       const er = this.dataMessage(); | ||||
|       if (er) return cb(er); | ||||
|  | ||||
|       this.startLoop(cb); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handles a data message. | ||||
|    * | ||||
|    * @return {(Error|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   dataMessage() { | ||||
|     if (this._fin) { | ||||
|       const messageLength = this._messageLength; | ||||
|       const fragments = this._fragments; | ||||
|  | ||||
|       this._totalPayloadLength = 0; | ||||
|       this._messageLength = 0; | ||||
|       this._fragmented = 0; | ||||
|       this._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; | ||||
|         } | ||||
|  | ||||
|         this.emit('message', data, true); | ||||
|       } 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' | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         this.emit('message', buf, false); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     this._state = WAIT_MICROTASK; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handles a control message. | ||||
|    * | ||||
|    * @param {Buffer} data Data to handle | ||||
|    * @return {(Error|RangeError|undefined)} A possible error | ||||
|    * @private | ||||
|    */ | ||||
|   controlMessage(data) { | ||||
|     if (this._opcode === 0x08) { | ||||
|       this._loop = false; | ||||
|  | ||||
|       if (data.length === 0) { | ||||
|         this.emit('conclude', 1005, EMPTY_BUFFER); | ||||
|         this.end(); | ||||
|  | ||||
|         this._state = GET_INFO; | ||||
|       } else { | ||||
|         const code = data.readUInt16BE(0); | ||||
|  | ||||
|         if (!isValidStatusCode(code)) { | ||||
|           return error( | ||||
|             RangeError, | ||||
|             `invalid status code ${code}`, | ||||
|             true, | ||||
|             1002, | ||||
|             'WS_ERR_INVALID_CLOSE_CODE' | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         const buf = new FastBuffer( | ||||
|           data.buffer, | ||||
|           data.byteOffset + 2, | ||||
|           data.length - 2 | ||||
|         ); | ||||
|  | ||||
|         if (!this._skipUTF8Validation && !isValidUTF8(buf)) { | ||||
|           return error( | ||||
|             Error, | ||||
|             'invalid UTF-8 sequence', | ||||
|             true, | ||||
|             1007, | ||||
|             'WS_ERR_INVALID_UTF8' | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         this.emit('conclude', code, buf); | ||||
|         this.end(); | ||||
|  | ||||
|         this._state = GET_INFO; | ||||
|       } | ||||
|     } else if (this._opcode === 0x09) { | ||||
|       this.emit('ping', data); | ||||
|       this._state = WAIT_MICROTASK; | ||||
|     } else { | ||||
|       this.emit('pong', data); | ||||
|       this._state = WAIT_MICROTASK; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = Receiver; | ||||
|  | ||||
| /** | ||||
|  * 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 | ||||
|  */ | ||||
| function error(ErrorCtor, message, prefix, statusCode, errorCode) { | ||||
|   const err = new ErrorCtor( | ||||
|     prefix ? `Invalid WebSocket frame: ${message}` : message | ||||
|   ); | ||||
|  | ||||
|   Error.captureStackTrace(err, error); | ||||
|   err.code = errorCode; | ||||
|   err[kStatusCode] = statusCode; | ||||
|   return err; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * A shim for `queueMicrotask()`. | ||||
|  * | ||||
|  * @param {Function} cb Callback | ||||
|  */ | ||||
| function queueMicrotaskShim(cb) { | ||||
|   promise.then(cb).catch(throwErrorNextTick); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Throws an error. | ||||
|  * | ||||
|  * @param {Error} err The error to throw | ||||
|  * @private | ||||
|  */ | ||||
| function throwError(err) { | ||||
|   throw err; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Throws an error in the next tick. | ||||
|  * | ||||
|  * @param {Error} err The error to throw | ||||
|  * @private | ||||
|  */ | ||||
| function throwErrorNextTick(err) { | ||||
|   process.nextTick(throwError, err); | ||||
| } | ||||
							
								
								
									
										477
									
								
								node_modules/ws/lib/sender.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								node_modules/ws/lib/sender.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,477 @@ | ||||
| /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex" }] */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| const { Duplex } = require('stream'); | ||||
| const { randomFillSync } = require('crypto'); | ||||
|  | ||||
| const PerMessageDeflate = require('./permessage-deflate'); | ||||
| const { EMPTY_BUFFER } = require('./constants'); | ||||
| const { isValidStatusCode } = require('./validation'); | ||||
| const { mask: applyMask, toBuffer } = require('./buffer-util'); | ||||
|  | ||||
| const kByteLength = Symbol('kByteLength'); | ||||
| const maskBuffer = Buffer.alloc(4); | ||||
|  | ||||
| /** | ||||
|  * HyBi Sender implementation. | ||||
|  */ | ||||
| class Sender { | ||||
|   /** | ||||
|    * Creates a Sender instance. | ||||
|    * | ||||
|    * @param {Duplex} socket The connection socket | ||||
|    * @param {Object} [extensions] An object containing the negotiated extensions | ||||
|    * @param {Function} [generateMask] The function used to generate the masking | ||||
|    *     key | ||||
|    */ | ||||
|   constructor(socket, extensions, generateMask) { | ||||
|     this._extensions = extensions || {}; | ||||
|  | ||||
|     if (generateMask) { | ||||
|       this._generateMask = generateMask; | ||||
|       this._maskBuffer = Buffer.alloc(4); | ||||
|     } | ||||
|  | ||||
|     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|String)} data The data to frame | ||||
|    * @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 | ||||
|    * @return {(Buffer|String)[]} The framed data | ||||
|    * @public | ||||
|    */ | ||||
|   static frame(data, options) { | ||||
|     let mask; | ||||
|     let merge = false; | ||||
|     let offset = 2; | ||||
|     let skipMasking = false; | ||||
|  | ||||
|     if (options.mask) { | ||||
|       mask = options.maskBuffer || maskBuffer; | ||||
|  | ||||
|       if (options.generateMask) { | ||||
|         options.generateMask(mask); | ||||
|       } else { | ||||
|         randomFillSync(mask, 0, 4); | ||||
|       } | ||||
|  | ||||
|       skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0; | ||||
|       offset = 6; | ||||
|     } | ||||
|  | ||||
|     let dataLength; | ||||
|  | ||||
|     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 (dataLength > 125) { | ||||
|       offset += 2; | ||||
|       payloadLength = 126; | ||||
|     } | ||||
|  | ||||
|     const target = Buffer.allocUnsafe(merge ? dataLength + 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(dataLength, 2); | ||||
|     } else if (payloadLength === 127) { | ||||
|       target[2] = target[3] = 0; | ||||
|       target.writeUIntBE(dataLength, 4, 6); | ||||
|     } | ||||
|  | ||||
|     if (!options.mask) return [target, data]; | ||||
|  | ||||
|     target[1] |= 0x80; | ||||
|     target[offset - 4] = mask[0]; | ||||
|     target[offset - 3] = mask[1]; | ||||
|     target[offset - 2] = mask[2]; | ||||
|     target[offset - 1] = mask[3]; | ||||
|  | ||||
|     if (skipMasking) return [target, data]; | ||||
|  | ||||
|     if (merge) { | ||||
|       applyMask(data, mask, target, offset, dataLength); | ||||
|       return [target]; | ||||
|     } | ||||
|  | ||||
|     applyMask(data, mask, data, 0, dataLength); | ||||
|     return [target, data]; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Sends a close message to the other peer. | ||||
|    * | ||||
|    * @param {Number} [code] The status code component of the body | ||||
|    * @param {(String|Buffer)} [data] The message component of the body | ||||
|    * @param {Boolean} [mask=false] Specifies whether or not to mask the message | ||||
|    * @param {Function} [cb] Callback | ||||
|    * @public | ||||
|    */ | ||||
|   close(code, data, mask, cb) { | ||||
|     let 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.length) { | ||||
|       buf = Buffer.allocUnsafe(2); | ||||
|       buf.writeUInt16BE(code, 0); | ||||
|     } else { | ||||
|       const length = Buffer.byteLength(data); | ||||
|  | ||||
|       if (length > 123) { | ||||
|         throw new RangeError('The message must not be greater than 123 bytes'); | ||||
|       } | ||||
|  | ||||
|       buf = Buffer.allocUnsafe(2 + length); | ||||
|       buf.writeUInt16BE(code, 0); | ||||
|  | ||||
|       if (typeof data === 'string') { | ||||
|         buf.write(data, 2); | ||||
|       } else { | ||||
|         buf.set(data, 2); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     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.dispatch, buf, false, options, cb]); | ||||
|     } else { | ||||
|       this.sendFrame(Sender.frame(buf, options), cb); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Sends a ping message to the other peer. | ||||
|    * | ||||
|    * @param {*} data The message to send | ||||
|    * @param {Boolean} [mask=false] Specifies whether or not to mask `data` | ||||
|    * @param {Function} [cb] Callback | ||||
|    * @public | ||||
|    */ | ||||
|   ping(data, mask, cb) { | ||||
|     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 (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.dispatch, data, false, options, cb]); | ||||
|     } else { | ||||
|       this.sendFrame(Sender.frame(data, options), cb); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Sends a pong message to the other peer. | ||||
|    * | ||||
|    * @param {*} data The message to send | ||||
|    * @param {Boolean} [mask=false] Specifies whether or not to mask `data` | ||||
|    * @param {Function} [cb] Callback | ||||
|    * @public | ||||
|    */ | ||||
|   pong(data, mask, cb) { | ||||
|     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 (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.dispatch, data, false, options, cb]); | ||||
|     } else { | ||||
|       this.sendFrame(Sender.frame(data, options), cb); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Sends a data message to the other peer. | ||||
|    * | ||||
|    * @param {*} data The message to send | ||||
|    * @param {Object} options Options object | ||||
|    * @param {Boolean} [options.binary=false] Specifies whether `data` is binary | ||||
|    *     or text | ||||
|    * @param {Boolean} [options.compress=false] Specifies whether or not to | ||||
|    *     compress `data` | ||||
|    * @param {Boolean} [options.fin=false] Specifies whether the fragment is the | ||||
|    *     last one | ||||
|    * @param {Boolean} [options.mask=false] Specifies whether or not to mask | ||||
|    *     `data` | ||||
|    * @param {Function} [cb] Callback | ||||
|    * @public | ||||
|    */ | ||||
|   send(data, options, cb) { | ||||
|     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; | ||||
|     let opcode = options.binary ? 2 : 1; | ||||
|     let rsv1 = options.compress; | ||||
|  | ||||
|     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) { | ||||
|       this._firstFragment = false; | ||||
|       if ( | ||||
|         rsv1 && | ||||
|         perMessageDeflate && | ||||
|         perMessageDeflate.params[ | ||||
|           perMessageDeflate._isServer | ||||
|             ? 'server_no_context_takeover' | ||||
|             : 'client_no_context_takeover' | ||||
|         ] | ||||
|       ) { | ||||
|         rsv1 = byteLength >= perMessageDeflate._threshold; | ||||
|       } | ||||
|       this._compress = rsv1; | ||||
|     } else { | ||||
|       rsv1 = false; | ||||
|       opcode = 0; | ||||
|     } | ||||
|  | ||||
|     if (options.fin) this._firstFragment = true; | ||||
|  | ||||
|     if (perMessageDeflate) { | ||||
|       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]); | ||||
|       } else { | ||||
|         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 | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Dispatches a message. | ||||
|    * | ||||
|    * @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 {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 | ||||
|    */ | ||||
|   dispatch(data, compress, options, cb) { | ||||
|     if (!compress) { | ||||
|       this.sendFrame(Sender.frame(data, options), cb); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; | ||||
|  | ||||
|     this._bufferedBytes += options[kByteLength]; | ||||
|     this._deflating = true; | ||||
|     perMessageDeflate.compress(data, options.fin, (_, buf) => { | ||||
|       if (this._socket.destroyed) { | ||||
|         const err = new Error( | ||||
|           'The socket was closed while data was being compressed' | ||||
|         ); | ||||
|  | ||||
|         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); | ||||
|         } | ||||
|  | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       this._bufferedBytes -= options[kByteLength]; | ||||
|       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[3][kByteLength]; | ||||
|       Reflect.apply(params[0], this, params.slice(1)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Enqueues a send operation. | ||||
|    * | ||||
|    * @param {Array} params Send operation parameters. | ||||
|    * @private | ||||
|    */ | ||||
|   enqueue(params) { | ||||
|     this._bufferedBytes += params[3][kByteLength]; | ||||
|     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; | ||||
							
								
								
									
										159
									
								
								node_modules/ws/lib/stream.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								node_modules/ws/lib/stream.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { Duplex } = require('stream'); | ||||
|  | ||||
| /** | ||||
|  * Emits the `'close'` event on a stream. | ||||
|  * | ||||
|  * @param {Duplex} stream The stream. | ||||
|  * @private | ||||
|  */ | ||||
| function emitClose(stream) { | ||||
|   stream.emit('close'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The listener of the `'end'` event. | ||||
|  * | ||||
|  * @private | ||||
|  */ | ||||
| function duplexOnEnd() { | ||||
|   if (!this.destroyed && this._writableState.finished) { | ||||
|     this.destroy(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * The listener of the `'error'` event. | ||||
|  * | ||||
|  * @param {Error} err The error | ||||
|  * @private | ||||
|  */ | ||||
| function duplexOnError(err) { | ||||
|   this.removeListener('error', duplexOnError); | ||||
|   this.destroy(); | ||||
|   if (this.listenerCount('error') === 0) { | ||||
|     // Do not suppress the throwing behavior. | ||||
|     this.emit('error', err); | ||||
|   } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Wraps a `WebSocket` in a duplex stream. | ||||
|  * | ||||
|  * @param {WebSocket} ws The `WebSocket` to wrap | ||||
|  * @param {Object} [options] The options for the `Duplex` constructor | ||||
|  * @return {Duplex} The duplex stream | ||||
|  * @public | ||||
|  */ | ||||
| function createWebSocketStream(ws, options) { | ||||
|   let terminateOnDestroy = true; | ||||
|  | ||||
|   const duplex = new Duplex({ | ||||
|     ...options, | ||||
|     autoDestroy: false, | ||||
|     emitClose: false, | ||||
|     objectMode: false, | ||||
|     writableObjectMode: false | ||||
|   }); | ||||
|  | ||||
|   ws.on('message', function message(msg, isBinary) { | ||||
|     const data = | ||||
|       !isBinary && duplex._readableState.objectMode ? msg.toString() : msg; | ||||
|  | ||||
|     if (!duplex.push(data)) ws.pause(); | ||||
|   }); | ||||
|  | ||||
|   ws.once('error', function error(err) { | ||||
|     if (duplex.destroyed) return; | ||||
|  | ||||
|     // Prevent `ws.terminate()` from being called by `duplex._destroy()`. | ||||
|     // | ||||
|     // - If the `'error'` event is emitted before the `'open'` event, then | ||||
|     //   `ws.terminate()` is a noop as no socket is assigned. | ||||
|     // - Otherwise, the error is re-emitted by the listener of the `'error'` | ||||
|     //   event of the `Receiver` object. The listener already closes the | ||||
|     //   connection by calling `ws.close()`. This allows a close frame to be | ||||
|     //   sent to the other peer. If `ws.terminate()` is called right after this, | ||||
|     //   then the close frame might not be sent. | ||||
|     terminateOnDestroy = false; | ||||
|     duplex.destroy(err); | ||||
|   }); | ||||
|  | ||||
|   ws.once('close', function close() { | ||||
|     if (duplex.destroyed) return; | ||||
|  | ||||
|     duplex.push(null); | ||||
|   }); | ||||
|  | ||||
|   duplex._destroy = function (err, callback) { | ||||
|     if (ws.readyState === ws.CLOSED) { | ||||
|       callback(err); | ||||
|       process.nextTick(emitClose, duplex); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let called = false; | ||||
|  | ||||
|     ws.once('error', function error(err) { | ||||
|       called = true; | ||||
|       callback(err); | ||||
|     }); | ||||
|  | ||||
|     ws.once('close', function close() { | ||||
|       if (!called) callback(err); | ||||
|       process.nextTick(emitClose, duplex); | ||||
|     }); | ||||
|  | ||||
|     if (terminateOnDestroy) ws.terminate(); | ||||
|   }; | ||||
|  | ||||
|   duplex._final = function (callback) { | ||||
|     if (ws.readyState === ws.CONNECTING) { | ||||
|       ws.once('open', function open() { | ||||
|         duplex._final(callback); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     // If the value of the `_socket` property is `null` it means that `ws` is a | ||||
|     // client websocket and the handshake failed. In fact, when this happens, a | ||||
|     // socket is never assigned to the websocket. Wait for the `'error'` event | ||||
|     // that will be emitted by the websocket. | ||||
|     if (ws._socket === null) return; | ||||
|  | ||||
|     if (ws._socket._writableState.finished) { | ||||
|       callback(); | ||||
|       if (duplex._readableState.endEmitted) duplex.destroy(); | ||||
|     } else { | ||||
|       ws._socket.once('finish', function finish() { | ||||
|         // `duplex` is not destroyed here because the `'end'` event will be | ||||
|         // emitted on `duplex` after this `'finish'` event. The EOF signaling | ||||
|         // `null` chunk is, in fact, pushed when the websocket emits `'close'`. | ||||
|         callback(); | ||||
|       }); | ||||
|       ws.close(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   duplex._read = function () { | ||||
|     if (ws.isPaused) ws.resume(); | ||||
|   }; | ||||
|  | ||||
|   duplex._write = function (chunk, encoding, callback) { | ||||
|     if (ws.readyState === ws.CONNECTING) { | ||||
|       ws.once('open', function open() { | ||||
|         duplex._write(chunk, encoding, callback); | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     ws.send(chunk, callback); | ||||
|   }; | ||||
|  | ||||
|   duplex.on('end', duplexOnEnd); | ||||
|   duplex.on('error', duplexOnError); | ||||
|   return duplex; | ||||
| } | ||||
|  | ||||
| module.exports = createWebSocketStream; | ||||
							
								
								
									
										62
									
								
								node_modules/ws/lib/subprotocol.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								node_modules/ws/lib/subprotocol.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { tokenChars } = require('./validation'); | ||||
|  | ||||
| /** | ||||
|  * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names. | ||||
|  * | ||||
|  * @param {String} header The field value of the header | ||||
|  * @return {Set} The subprotocol names | ||||
|  * @public | ||||
|  */ | ||||
| function parse(header) { | ||||
|   const protocols = new Set(); | ||||
|   let start = -1; | ||||
|   let end = -1; | ||||
|   let i = 0; | ||||
|  | ||||
|   for (i; i < header.length; i++) { | ||||
|     const code = header.charCodeAt(i); | ||||
|  | ||||
|     if (end === -1 && tokenChars[code] === 1) { | ||||
|       if (start === -1) start = i; | ||||
|     } else if ( | ||||
|       i !== 0 && | ||||
|       (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */ | ||||
|     ) { | ||||
|       if (end === -1 && start !== -1) end = i; | ||||
|     } else if (code === 0x2c /* ',' */) { | ||||
|       if (start === -1) { | ||||
|         throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|       } | ||||
|  | ||||
|       if (end === -1) end = i; | ||||
|  | ||||
|       const protocol = header.slice(start, end); | ||||
|  | ||||
|       if (protocols.has(protocol)) { | ||||
|         throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); | ||||
|       } | ||||
|  | ||||
|       protocols.add(protocol); | ||||
|       start = end = -1; | ||||
|     } else { | ||||
|       throw new SyntaxError(`Unexpected character at index ${i}`); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (start === -1 || end !== -1) { | ||||
|     throw new SyntaxError('Unexpected end of input'); | ||||
|   } | ||||
|  | ||||
|   const protocol = header.slice(start, i); | ||||
|  | ||||
|   if (protocols.has(protocol)) { | ||||
|     throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); | ||||
|   } | ||||
|  | ||||
|   protocols.add(protocol); | ||||
|   return protocols; | ||||
| } | ||||
|  | ||||
| module.exports = { parse }; | ||||
							
								
								
									
										130
									
								
								node_modules/ws/lib/validation.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								node_modules/ws/lib/validation.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| 'use strict'; | ||||
|  | ||||
| const { isUtf8 } = require('buffer'); | ||||
|  | ||||
| // | ||||
| // Allowed token characters: | ||||
| // | ||||
| // '!', '#', '$', '%', '&', ''', '*', '+', '-', | ||||
| // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~' | ||||
| // | ||||
| // tokenChars[32] === 0 // ' ' | ||||
| // tokenChars[33] === 1 // '!' | ||||
| // tokenChars[34] === 0 // '"' | ||||
| // ... | ||||
| // | ||||
| // prettier-ignore | ||||
| const tokenChars = [ | ||||
|   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 | ||||
|   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 | ||||
|   0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, // 32 - 47 | ||||
|   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 | ||||
|   0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 | ||||
|   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, // 80 - 95 | ||||
|   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 | ||||
|   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127 | ||||
| ]; | ||||
|  | ||||
| /** | ||||
|  * Checks if a status code is allowed in a close frame. | ||||
|  * | ||||
|  * @param {Number} code The status code | ||||
|  * @return {Boolean} `true` if the status code is valid, else `false` | ||||
|  * @public | ||||
|  */ | ||||
| function isValidStatusCode(code) { | ||||
|   return ( | ||||
|     (code >= 1000 && | ||||
|       code <= 1014 && | ||||
|       code !== 1004 && | ||||
|       code !== 1005 && | ||||
|       code !== 1006) || | ||||
|     (code >= 3000 && code <= 4999) | ||||
|   ); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Checks if a given buffer contains only correct UTF-8. | ||||
|  * Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by | ||||
|  * Markus Kuhn. | ||||
|  * | ||||
|  * @param {Buffer} buf The buffer to check | ||||
|  * @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false` | ||||
|  * @public | ||||
|  */ | ||||
| function _isValidUTF8(buf) { | ||||
|   const len = buf.length; | ||||
|   let i = 0; | ||||
|  | ||||
|   while (i < len) { | ||||
|     if ((buf[i] & 0x80) === 0) { | ||||
|       // 0xxxxxxx | ||||
|       i++; | ||||
|     } else if ((buf[i] & 0xe0) === 0xc0) { | ||||
|       // 110xxxxx 10xxxxxx | ||||
|       if ( | ||||
|         i + 1 === len || | ||||
|         (buf[i + 1] & 0xc0) !== 0x80 || | ||||
|         (buf[i] & 0xfe) === 0xc0 // Overlong | ||||
|       ) { | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       i += 2; | ||||
|     } else if ((buf[i] & 0xf0) === 0xe0) { | ||||
|       // 1110xxxx 10xxxxxx 10xxxxxx | ||||
|       if ( | ||||
|         i + 2 >= len || | ||||
|         (buf[i + 1] & 0xc0) !== 0x80 || | ||||
|         (buf[i + 2] & 0xc0) !== 0x80 || | ||||
|         (buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong | ||||
|         (buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF) | ||||
|       ) { | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       i += 3; | ||||
|     } else if ((buf[i] & 0xf8) === 0xf0) { | ||||
|       // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | ||||
|       if ( | ||||
|         i + 3 >= len || | ||||
|         (buf[i + 1] & 0xc0) !== 0x80 || | ||||
|         (buf[i + 2] & 0xc0) !== 0x80 || | ||||
|         (buf[i + 3] & 0xc0) !== 0x80 || | ||||
|         (buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong | ||||
|         (buf[i] === 0xf4 && buf[i + 1] > 0x8f) || | ||||
|         buf[i] > 0xf4 // > U+10FFFF | ||||
|       ) { | ||||
|         return false; | ||||
|       } | ||||
|  | ||||
|       i += 4; | ||||
|     } else { | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   isValidStatusCode, | ||||
|   isValidUTF8: _isValidUTF8, | ||||
|   tokenChars | ||||
| }; | ||||
|  | ||||
| if (isUtf8) { | ||||
|   module.exports.isValidUTF8 = function (buf) { | ||||
|     return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf); | ||||
|   }; | ||||
| } /* 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. | ||||
|   } | ||||
| } | ||||
							
								
								
									
										531
									
								
								node_modules/ws/lib/websocket-server.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										531
									
								
								node_modules/ws/lib/websocket-server.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,531 @@ | ||||
| /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$" }] */ | ||||
|  | ||||
| 'use strict'; | ||||
|  | ||||
| const EventEmitter = require('events'); | ||||
| const http = require('http'); | ||||
| const { Duplex } = require('stream'); | ||||
| const { createHash } = require('crypto'); | ||||
|  | ||||
| const extension = require('./extension'); | ||||
| const PerMessageDeflate = require('./permessage-deflate'); | ||||
| const subprotocol = require('./subprotocol'); | ||||
| const WebSocket = require('./websocket'); | ||||
| const { GUID, kWebSocket } = require('./constants'); | ||||
|  | ||||
| const keyRegex = /^[+/0-9A-Za-z]{22}==$/; | ||||
|  | ||||
| const RUNNING = 0; | ||||
| const CLOSING = 1; | ||||
| const CLOSED = 2; | ||||
|  | ||||
| /** | ||||
|  * Class representing a WebSocket server. | ||||
|  * | ||||
|  * @extends EventEmitter | ||||
|  */ | ||||
| class WebSocketServer extends EventEmitter { | ||||
|   /** | ||||
|    * Create a `WebSocketServer` instance. | ||||
|    * | ||||
|    * @param {Object} options Configuration options | ||||
|    * @param {Number} [options.backlog=511] The maximum length of the queue of | ||||
|    *     pending connections | ||||
|    * @param {Boolean} [options.clientTracking=true] Specifies whether or not to | ||||
|    *     track clients | ||||
|    * @param {Function} [options.handleProtocols] A hook to handle protocols | ||||
|    * @param {String} [options.host] The hostname where to bind the server | ||||
|    * @param {Number} [options.maxPayload=104857600] The maximum allowed message | ||||
|    *     size | ||||
|    * @param {Boolean} [options.noServer=false] Enable no server mode | ||||
|    * @param {String} [options.path] Accept only connections matching this path | ||||
|    * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable | ||||
|    *     permessage-deflate | ||||
|    * @param {Number} [options.port] The port where to bind the server | ||||
|    * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S | ||||
|    *     server to use | ||||
|    * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or | ||||
|    *     not to skip UTF-8 validation for text and close messages | ||||
|    * @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 | ||||
|    */ | ||||
|   constructor(options, callback) { | ||||
|     super(); | ||||
|  | ||||
|     options = { | ||||
|       maxPayload: 100 * 1024 * 1024, | ||||
|       skipUTF8Validation: false, | ||||
|       perMessageDeflate: false, | ||||
|       handleProtocols: null, | ||||
|       clientTracking: true, | ||||
|       verifyClient: null, | ||||
|       noServer: false, | ||||
|       backlog: null, // use default (511 as implemented in net.js) | ||||
|       server: null, | ||||
|       host: null, | ||||
|       path: null, | ||||
|       port: null, | ||||
|       WebSocket, | ||||
|       ...options | ||||
|     }; | ||||
|  | ||||
|     if ( | ||||
|       (options.port == null && !options.server && !options.noServer) || | ||||
|       (options.port != null && (options.server || options.noServer)) || | ||||
|       (options.server && options.noServer) | ||||
|     ) { | ||||
|       throw new TypeError( | ||||
|         'One and only one of the "port", "server", or "noServer" options ' + | ||||
|           'must be specified' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (options.port != null) { | ||||
|       this._server = http.createServer((req, res) => { | ||||
|         const body = http.STATUS_CODES[426]; | ||||
|  | ||||
|         res.writeHead(426, { | ||||
|           'Content-Length': body.length, | ||||
|           'Content-Type': 'text/plain' | ||||
|         }); | ||||
|         res.end(body); | ||||
|       }); | ||||
|       this._server.listen( | ||||
|         options.port, | ||||
|         options.host, | ||||
|         options.backlog, | ||||
|         callback | ||||
|       ); | ||||
|     } else if (options.server) { | ||||
|       this._server = options.server; | ||||
|     } | ||||
|  | ||||
|     if (this._server) { | ||||
|       const emitConnection = this.emit.bind(this, 'connection'); | ||||
|  | ||||
|       this._removeListeners = addListeners(this._server, { | ||||
|         listening: this.emit.bind(this, 'listening'), | ||||
|         error: this.emit.bind(this, 'error'), | ||||
|         upgrade: (req, socket, head) => { | ||||
|           this.handleUpgrade(req, socket, head, emitConnection); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     if (options.perMessageDeflate === true) options.perMessageDeflate = {}; | ||||
|     if (options.clientTracking) { | ||||
|       this.clients = new Set(); | ||||
|       this._shouldEmitClose = false; | ||||
|     } | ||||
|  | ||||
|     this.options = options; | ||||
|     this._state = RUNNING; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the bound address, the address family name, and port of the server | ||||
|    * as reported by the operating system if listening on an IP socket. | ||||
|    * If the server is listening on a pipe or UNIX domain socket, the name is | ||||
|    * returned as a string. | ||||
|    * | ||||
|    * @return {(Object|String|null)} The address of the server | ||||
|    * @public | ||||
|    */ | ||||
|   address() { | ||||
|     if (this.options.noServer) { | ||||
|       throw new Error('The server is operating in "noServer" mode'); | ||||
|     } | ||||
|  | ||||
|     if (!this._server) return null; | ||||
|     return this._server.address(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Stop the server from accepting new connections and emit the `'close'` event | ||||
|    * when all existing connections are closed. | ||||
|    * | ||||
|    * @param {Function} [cb] A one-time listener for the `'close'` event | ||||
|    * @public | ||||
|    */ | ||||
|   close(cb) { | ||||
|     if (this._state === CLOSED) { | ||||
|       if (cb) { | ||||
|         this.once('close', () => { | ||||
|           cb(new Error('The server is not running')); | ||||
|         }); | ||||
|       } | ||||
|  | ||||
|       process.nextTick(emitClose, this); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (cb) this.once('close', cb); | ||||
|  | ||||
|     if (this._state === CLOSING) return; | ||||
|     this._state = CLOSING; | ||||
|  | ||||
|     if (this.options.noServer || this.options.server) { | ||||
|       if (this._server) { | ||||
|         this._removeListeners(); | ||||
|         this._removeListeners = this._server = null; | ||||
|       } | ||||
|  | ||||
|       if (this.clients) { | ||||
|         if (!this.clients.size) { | ||||
|           process.nextTick(emitClose, this); | ||||
|         } else { | ||||
|           this._shouldEmitClose = true; | ||||
|         } | ||||
|       } else { | ||||
|         process.nextTick(emitClose, this); | ||||
|       } | ||||
|     } else { | ||||
|       const server = this._server; | ||||
|  | ||||
|       this._removeListeners(); | ||||
|       this._removeListeners = this._server = null; | ||||
|  | ||||
|       // | ||||
|       // The HTTP/S server was created internally. Close it, and rely on its | ||||
|       // `'close'` event. | ||||
|       // | ||||
|       server.close(() => { | ||||
|         emitClose(this); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * See if a given request should be handled by this server instance. | ||||
|    * | ||||
|    * @param {http.IncomingMessage} req Request object to inspect | ||||
|    * @return {Boolean} `true` if the request is valid, else `false` | ||||
|    * @public | ||||
|    */ | ||||
|   shouldHandle(req) { | ||||
|     if (this.options.path) { | ||||
|       const index = req.url.indexOf('?'); | ||||
|       const pathname = index !== -1 ? req.url.slice(0, index) : req.url; | ||||
|  | ||||
|       if (pathname !== this.options.path) return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handle a HTTP Upgrade request. | ||||
|    * | ||||
|    * @param {http.IncomingMessage} req The request object | ||||
|    * @param {Duplex} socket The network socket between the server and client | ||||
|    * @param {Buffer} head The first packet of the upgraded stream | ||||
|    * @param {Function} cb Callback | ||||
|    * @public | ||||
|    */ | ||||
|   handleUpgrade(req, socket, head, cb) { | ||||
|     socket.on('error', socketOnError); | ||||
|  | ||||
|     const key = req.headers['sec-websocket-key']; | ||||
|     const version = +req.headers['sec-websocket-version']; | ||||
|  | ||||
|     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']; | ||||
|     let protocols = new Set(); | ||||
|  | ||||
|     if (secWebSocketProtocol !== undefined) { | ||||
|       try { | ||||
|         protocols = subprotocol.parse(secWebSocketProtocol); | ||||
|       } catch (err) { | ||||
|         const message = 'Invalid Sec-WebSocket-Protocol header'; | ||||
|         abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const secWebSocketExtensions = req.headers['sec-websocket-extensions']; | ||||
|     const extensions = {}; | ||||
|  | ||||
|     if ( | ||||
|       this.options.perMessageDeflate && | ||||
|       secWebSocketExtensions !== undefined | ||||
|     ) { | ||||
|       const perMessageDeflate = new PerMessageDeflate( | ||||
|         this.options.perMessageDeflate, | ||||
|         true, | ||||
|         this.options.maxPayload | ||||
|       ); | ||||
|  | ||||
|       try { | ||||
|         const offers = extension.parse(secWebSocketExtensions); | ||||
|  | ||||
|         if (offers[PerMessageDeflate.extensionName]) { | ||||
|           perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); | ||||
|           extensions[PerMessageDeflate.extensionName] = perMessageDeflate; | ||||
|         } | ||||
|       } catch (err) { | ||||
|         const message = | ||||
|           'Invalid or unacceptable Sec-WebSocket-Extensions header'; | ||||
|         abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // Optionally call external client verification handler. | ||||
|     // | ||||
|     if (this.options.verifyClient) { | ||||
|       const info = { | ||||
|         origin: | ||||
|           req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`], | ||||
|         secure: !!(req.socket.authorized || req.socket.encrypted), | ||||
|         req | ||||
|       }; | ||||
|  | ||||
|       if (this.options.verifyClient.length === 2) { | ||||
|         this.options.verifyClient(info, (verified, code, message, headers) => { | ||||
|           if (!verified) { | ||||
|             return abortHandshake(socket, code || 401, message, headers); | ||||
|           } | ||||
|  | ||||
|           this.completeUpgrade( | ||||
|             extensions, | ||||
|             key, | ||||
|             protocols, | ||||
|             req, | ||||
|             socket, | ||||
|             head, | ||||
|             cb | ||||
|           ); | ||||
|         }); | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (!this.options.verifyClient(info)) return abortHandshake(socket, 401); | ||||
|     } | ||||
|  | ||||
|     this.completeUpgrade(extensions, key, protocols, req, socket, head, cb); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Upgrade the connection to WebSocket. | ||||
|    * | ||||
|    * @param {Object} extensions The accepted extensions | ||||
|    * @param {String} key The value of the `Sec-WebSocket-Key` header | ||||
|    * @param {Set} protocols The subprotocols | ||||
|    * @param {http.IncomingMessage} req The request object | ||||
|    * @param {Duplex} socket The network socket between the server and client | ||||
|    * @param {Buffer} head The first packet of the upgraded stream | ||||
|    * @param {Function} cb Callback | ||||
|    * @throws {Error} If called more than once with the same socket | ||||
|    * @private | ||||
|    */ | ||||
|   completeUpgrade(extensions, key, protocols, req, socket, head, cb) { | ||||
|     // | ||||
|     // Destroy the socket if the client has already sent a FIN packet. | ||||
|     // | ||||
|     if (!socket.readable || !socket.writable) return socket.destroy(); | ||||
|  | ||||
|     if (socket[kWebSocket]) { | ||||
|       throw new Error( | ||||
|         'server.handleUpgrade() was called more than once with the same ' + | ||||
|           'socket, possibly due to a misconfiguration' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     if (this._state > RUNNING) return abortHandshake(socket, 503); | ||||
|  | ||||
|     const digest = createHash('sha1') | ||||
|       .update(key + GUID) | ||||
|       .digest('base64'); | ||||
|  | ||||
|     const headers = [ | ||||
|       'HTTP/1.1 101 Switching Protocols', | ||||
|       'Upgrade: websocket', | ||||
|       'Connection: Upgrade', | ||||
|       `Sec-WebSocket-Accept: ${digest}` | ||||
|     ]; | ||||
|  | ||||
|     const ws = new this.options.WebSocket(null); | ||||
|  | ||||
|     if (protocols.size) { | ||||
|       // | ||||
|       // Optionally call external protocol selection handler. | ||||
|       // | ||||
|       const protocol = this.options.handleProtocols | ||||
|         ? this.options.handleProtocols(protocols, req) | ||||
|         : protocols.values().next().value; | ||||
|  | ||||
|       if (protocol) { | ||||
|         headers.push(`Sec-WebSocket-Protocol: ${protocol}`); | ||||
|         ws._protocol = protocol; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     if (extensions[PerMessageDeflate.extensionName]) { | ||||
|       const params = extensions[PerMessageDeflate.extensionName].params; | ||||
|       const value = extension.format({ | ||||
|         [PerMessageDeflate.extensionName]: [params] | ||||
|       }); | ||||
|       headers.push(`Sec-WebSocket-Extensions: ${value}`); | ||||
|       ws._extensions = extensions; | ||||
|     } | ||||
|  | ||||
|     // | ||||
|     // Allow external modification/inspection of handshake headers. | ||||
|     // | ||||
|     this.emit('headers', headers, req); | ||||
|  | ||||
|     socket.write(headers.concat('\r\n').join('\r\n')); | ||||
|     socket.removeListener('error', socketOnError); | ||||
|  | ||||
|     ws.setSocket(socket, head, { | ||||
|       maxPayload: this.options.maxPayload, | ||||
|       skipUTF8Validation: this.options.skipUTF8Validation | ||||
|     }); | ||||
|  | ||||
|     if (this.clients) { | ||||
|       this.clients.add(ws); | ||||
|       ws.on('close', () => { | ||||
|         this.clients.delete(ws); | ||||
|  | ||||
|         if (this._shouldEmitClose && !this.clients.size) { | ||||
|           process.nextTick(emitClose, this); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     cb(ws, req); | ||||
|   } | ||||
| } | ||||
|  | ||||
| module.exports = WebSocketServer; | ||||
|  | ||||
| /** | ||||
|  * Add event listeners on an `EventEmitter` using a map of <event, listener> | ||||
|  * pairs. | ||||
|  * | ||||
|  * @param {EventEmitter} server The event emitter | ||||
|  * @param {Object.<String, Function>} map The listeners to add | ||||
|  * @return {Function} A function that will remove the added listeners when | ||||
|  *     called | ||||
|  * @private | ||||
|  */ | ||||
| function addListeners(server, map) { | ||||
|   for (const event of Object.keys(map)) server.on(event, map[event]); | ||||
|  | ||||
|   return function removeListeners() { | ||||
|     for (const event of Object.keys(map)) { | ||||
|       server.removeListener(event, map[event]); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Emit a `'close'` event on an `EventEmitter`. | ||||
|  * | ||||
|  * @param {EventEmitter} server The event emitter | ||||
|  * @private | ||||
|  */ | ||||
| function emitClose(server) { | ||||
|   server._state = CLOSED; | ||||
|   server.emit('close'); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Handle socket errors. | ||||
|  * | ||||
|  * @private | ||||
|  */ | ||||
| function socketOnError() { | ||||
|   this.destroy(); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Close the connection when preconditions are not fulfilled. | ||||
|  * | ||||
|  * @param {Duplex} socket The socket of the upgrade request | ||||
|  * @param {Number} code The HTTP response status code | ||||
|  * @param {String} [message] The HTTP response body | ||||
|  * @param {Object} [headers] Additional HTTP response headers | ||||
|  * @private | ||||
|  */ | ||||
| function abortHandshake(socket, code, 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.once('finish', 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); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1319
									
								
								node_modules/ws/lib/websocket.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1319
									
								
								node_modules/ws/lib/websocket.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user