-
Notifications
You must be signed in to change notification settings - Fork 1
/
websocket.js
16 lines (16 loc) · 30.5 KB
/
websocket.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/************************************************************************
* 原项目地址https://www.npmjs.com/package/websocket
* 原项目太几把多文件了,依赖几十个是模块(((φ(◎ロ◎;)φ)))
* 抠出来给e小天做应用客户端 https://www.wxext.cn/
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***********************************************************************/
const Buffer=require("buffer").Buffer,BufferUtil={merge:function(e,t){for(var i=0,o=0,n=t.length;o<n;++o){var s=t[o];s.copy(e,i),i+=s.length}},mask:function(e,t,i,o,n){for(var s=t.readUInt32LE(0),r=0;r<n-3;r+=4){var a=s^e.readUInt32LE(r);a<0&&(a=4294967296+a),i.writeUInt32LE(a,o+r)}switch(n%4){case 3:i[o+r+2]=e[r+2]^t[2];case 2:i[o+r+1]=e[r+1]^t[1];case 1:i[o+r]=e[r]^t[0]}},unmask:function(e,t){for(var i=t.readUInt32LE(0),o=e.length,n=0;n<o-3;n+=4){var s=i^e.readUInt32LE(n);s<0&&(s=4294967296+s),e.writeUInt32LE(s,n)}switch(o%4){case 3:e[n+2]=e[n+2]^t[2];case 2:e[n+1]=e[n+1]^t[1];case 1:e[n]=e[n]^t[0]}}},utils=function(){const e={};function t(e,t,i){this.logFunction=i,this.identifier=e,this.uniqueID=t,this.buffer=[]}return e.extend=function(e,t){for(var i in t)e[i]=t[i]},e.eventEmitterListenerCount=require("events").EventEmitter.listenerCount||function(e,t){return e.listeners(t).length},e.bufferAllocUnsafe=Buffer.allocUnsafe?Buffer.allocUnsafe:function(e){return new Buffer(e)},e.bufferFromString=Buffer.from?Buffer.from:function(e,t){return new Buffer(e,t)},e.BufferingLogger=function(e,i){var o=function(){};if(o.enabled){var n=new t(e,i,o),s=n.log.bind(n);return s.printOutput=n.printOutput.bind(n),s.enabled=o.enabled,s}return o.printOutput=function(){},o},t.prototype.log=function(){return this.buffer.push([new Date,Array.prototype.slice.call(arguments)]),this},t.prototype.clear=function(){return this.buffer=[],this},t.prototype.printOutput=function(t){t||(t=this.logFunction);var i=this.uniqueID;this.buffer.forEach((function(o){var n=o[0].toLocaleString(),s=o[1].slice(),r=s[0];null!=r&&(r="%s - %s - "+r.toString(),s.splice(0,1,r,n,i),t.apply(e,s))}))},e}();var bufferAllocUnsafe=utils.bufferAllocUnsafe;const DECODE_HEADER=1,WAITING_FOR_16_BIT_LENGTH=2,WAITING_FOR_64_BIT_LENGTH=3,WAITING_FOR_MASK_KEY=4,WAITING_FOR_PAYLOAD=5,COMPLETE=6;function WebSocketFrame(e,t,i){this.maskBytes=e,this.frameHeader=t,this.config=i,this.maxReceivedFrameSize=i.maxReceivedFrameSize,this.protocolError=!1,this.frameTooLarge=!1,this.invalidCloseFrameLength=!1,this.parseState=1,this.closeStatus=-1}WebSocketFrame.prototype.addData=function(e){if(1===this.parseState&&e.length>=2){e.joinInto(this.frameHeader,0,0,2),e.advance(2);var t=this.frameHeader[0],i=this.frameHeader[1];if(this.fin=Boolean(128&t),this.rsv1=Boolean(64&t),this.rsv2=Boolean(32&t),this.rsv3=Boolean(16&t),this.mask=Boolean(128&i),this.opcode=15&t,this.length=127&i,this.opcode>=8){if(this.length>125)return this.protocolError=!0,this.dropReason="Illegal control frame longer than 125 bytes.",!0;if(!this.fin)return this.protocolError=!0,this.dropReason="Control frames must not be fragmented.",!0}126===this.length?this.parseState=2:127===this.length?this.parseState=3:this.parseState=4}if(2===this.parseState)e.length>=2&&(e.joinInto(this.frameHeader,2,0,2),e.advance(2),this.length=this.frameHeader.readUInt16BE(2),this.parseState=4);else if(3===this.parseState&&e.length>=8){e.joinInto(this.frameHeader,2,0,8),e.advance(8);var o=[this.frameHeader.readUInt32BE(2),this.frameHeader.readUInt32BE(6)];if(0!==o[0])return this.protocolError=!0,this.dropReason="Unsupported 64-bit length frame received",!0;this.length=o[1],this.parseState=4}if(4===this.parseState&&(this.mask?e.length>=4&&(e.joinInto(this.maskBytes,0,0,4),e.advance(4),this.parseState=5):this.parseState=5),5===this.parseState){if(this.length>this.maxReceivedFrameSize)return this.frameTooLarge=!0,this.dropReason="Frame size of "+this.length.toString(10)+" bytes exceeds maximum accepted frame size",!0;if(0===this.length)return this.binaryPayload=bufferAllocUnsafe(0),this.parseState=6,!0;if(e.length>=this.length)return this.binaryPayload=e.take(this.length),e.advance(this.length),this.mask&&BufferUtil.unmask(this.binaryPayload,this.maskBytes),8===this.opcode&&(1===this.length&&(this.binaryPayload=bufferAllocUnsafe(0),this.invalidCloseFrameLength=!0),this.length>=2&&(this.closeStatus=this.binaryPayload.readUInt16BE(0),this.binaryPayload=this.binaryPayload.slice(2))),this.parseState=6,!0}return!1},WebSocketFrame.prototype.throwAwayPayload=function(e){return e.length>=this.length&&(e.advance(this.length),this.parseState=6,!0)},WebSocketFrame.prototype.toBuffer=function(e){var t,i,o,n=2,s=0,r=0;this.fin&&(s|=128),this.rsv1&&(s|=64),this.rsv2&&(s|=32),this.rsv3&&(s|=16),this.mask&&(r|=128),s|=15&this.opcode,8===this.opcode?(this.length=2,this.binaryPayload&&(this.length+=this.binaryPayload.length),(i=bufferAllocUnsafe(this.length)).writeUInt16BE(this.closeStatus,0),this.length>2&&this.binaryPayload.copy(i,2)):this.binaryPayload?(i=this.binaryPayload,this.length=i.length):this.length=0,this.length<=125?r|=127&this.length:this.length>125&&this.length<=65535?(r|=126,n+=2):this.length>65535&&(r|=127,n+=8);var a=bufferAllocUnsafe(this.length+n+(this.mask?4:0));return a[0]=s,a[1]=r,o=2,this.length>125&&this.length<=65535?(a.writeUInt16BE(this.length,o),o+=2):this.length>65535&&(a.writeUInt32BE(0,o),a.writeUInt32BE(this.length,o+4),o+=8),this.mask?(t=e?0:4294967295*Math.random()>>>0,this.maskBytes.writeUInt32BE(t,0),this.maskBytes.copy(a,o),o+=4,i&&BufferUtil.mask(i,this.maskBytes,a,o,this.length)):i&&i.copy(a,o),a},WebSocketFrame.prototype.toString=function(){return"Opcode: "+this.opcode+", fin: "+this.fin+", length: "+this.length+", hasPayload: "+Boolean(this.binaryPayload)+", masked: "+this.mask};var EventEmitter=require("events").EventEmitter;bufferAllocUnsafe=utils.bufferAllocUnsafe;function BufferList(e){if(!(this instanceof BufferList))return new BufferList(e);EventEmitter.call(this);var t=this;void 0===e&&(e={}),t.encoding=e.encoding;var i={next:null,buffer:null},o={next:null,buffer:null},n=0;t.__defineGetter__("length",(function(){return n}));var s=0;t.write=function(e){return i.buffer?(o.next={next:null,buffer:e},o=o.next):(i.buffer=e,o=i),n+=e.length,t.emit("write",e),!0},t.end=function(e){Buffer.isBuffer(e)&&t.write(e)},t.push=function(){var e=[].concat.apply([],arguments);return e.forEach(t.write),t},t.forEach=function(e){if(!i.buffer)return bufferAllocUnsafe(0);if(i.buffer.length-s<=0)return t;for(var o={buffer:i.buffer.slice(s),next:i.next};o&&o.buffer;){if(e(o.buffer))break;o=o.next}return t},t.join=function(e,o){if(!i.buffer)return bufferAllocUnsafe(0);null==e&&(e=0),null==o&&(o=t.length);var n=bufferAllocUnsafe(o-e),s=0;return t.forEach((function(t){if(e<s+t.length&&s<o&&t.copy(n,Math.max(0,s-e),Math.max(0,e-s),Math.min(t.length,o-s)),(s+=t.length)>o)return!0})),n},t.joinInto=function(e,o,n,s){if(!i.buffer)return new bufferAllocUnsafe(0);null==n&&(n=0),null==s&&(s=t.length);var r=e;if(r.length-o<s-n)throw new Error("Insufficient space available in target Buffer.");var a=0;return t.forEach((function(e){if(n<a+e.length&&a<s&&e.copy(r,Math.max(o,o+a-n),Math.max(0,n-a),Math.min(e.length,s-a)),(a+=e.length)>s)return!0})),r},t.advance=function(e){for(s+=e,n-=e;i.buffer&&s>=i.buffer.length;)s-=i.buffer.length,i=i.next?i.next:{buffer:null,next:null};return null===i.buffer&&(o={next:null,buffer:null}),t.emit("advance",e),t},t.take=function(e,i){null==e?e=t.length:"number"!=typeof e&&(i=e,e=t.length);if(i||(i=t.encoding),i){var o="";return t.forEach((function(t){if(e<=0)return!0;o+=t.toString(i,0,Math.min(e,t.length)),e-=t.length})),o}return t.join(0,e)},t.toString=function(){return t.take("binary")}}BufferList.BufferList=BufferList,require("util").inherits(BufferList,EventEmitter);var util=require("util"),bufferFromString=(EventEmitter=require("events").EventEmitter,bufferAllocUnsafe=utils.bufferAllocUnsafe,utils.bufferFromString);const STATE_OPEN="open",STATE_PEER_REQUESTED_CLOSE="peer_requested_close",STATE_ENDING="ending",STATE_CLOSED="closed";var setImmediateImpl="setImmediate"in global?global.setImmediate.bind(global):process.nextTick.bind(process),idCounter=0;function WebSocketConnection(e,t,i,o,n){if(this._debug=utils.BufferingLogger("websocket:connection",++idCounter),this._debug("constructor"),this._debug.enabled&&instrumentSocketForDebugging(this,e),EventEmitter.call(this),this._pingListenerCount=0,this.on("newListener",(function(e){"ping"===e&&this._pingListenerCount++})).on("removeListener",(function(e){"ping"===e&&this._pingListenerCount--})),this.config=n,this.socket=e,this.protocol=i,this.extensions=t,this.remoteAddress=e.remoteAddress,this.closeReasonCode=-1,this.closeDescription=null,this.closeEventEmitted=!1,this.maskOutgoingPackets=o,this.maskBytes=bufferAllocUnsafe(4),this.frameHeader=bufferAllocUnsafe(10),this.bufferList=new BufferList,this.currentFrame=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config),this.fragmentationSize=0,this.frameQueue=[],this.connected=!0,this.state="open",this.waitingForCloseResponse=!1,this.receivedEnd=!1,this.closeTimeout=this.config.closeTimeout,this.assembleFragments=this.config.assembleFragments,this.maxReceivedMessageSize=this.config.maxReceivedMessageSize,this.outputBufferFull=!1,this.inputPaused=!1,this.receivedDataHandler=this.processReceivedData.bind(this),this._closeTimerHandler=this.handleCloseTimer.bind(this),this.socket.setNoDelay(this.config.disableNagleAlgorithm),this.socket.setTimeout(0),this.config.keepalive&&!this.config.useNativeKeepalive){if("number"!=typeof this.config.keepaliveInterval)throw new Error("keepaliveInterval must be specified and numeric if keepalive is true.");if(this._keepaliveTimerHandler=this.handleKeepaliveTimer.bind(this),this.setKeepaliveTimer(),this.config.dropConnectionOnKeepaliveTimeout){if("number"!=typeof this.config.keepaliveGracePeriod)throw new Error("keepaliveGracePeriod must be specified and numeric if dropConnectionOnKeepaliveTimeout is true.");this._gracePeriodTimerHandler=this.handleGracePeriodTimer.bind(this)}}else if(this.config.keepalive&&this.config.useNativeKeepalive){if(!("setKeepAlive"in this.socket))throw new Error("Unable to use native keepalive: unsupported by this version of Node.");this.socket.setKeepAlive(!0,this.config.keepaliveInterval)}this.socket.removeAllListeners("error")}function validateCloseReason(e){return!(e<1e3)&&(e>=1e3&&e<=2999?-1!==[1e3,1001,1002,1003,1007,1008,1009,1010,1011,1012,1013,1014].indexOf(e):e>=3e3&&e<=3999||(e>=4e3&&e<=4999||!(e>=5e3)&&void 0))}function instrumentSocketForDebugging(e,t){if(e._debug.enabled){var i=t.emit;for(var o in t.emit=function(t){e._debug("||| Socket Event '%s'",t),i.apply(this,arguments)},t)"function"==typeof t[o]&&-1===["emit"].indexOf(o)&&function(i){var o=t[i];t[i]="on"!==i?function(){return e._debug("||| Socket method called: %s",i),o.apply(this,arguments)}:function(){return e._debug("||| Socket method called: %s (%s)",i,arguments[0]),o.apply(this,arguments)}}(o)}}WebSocketConnection.CLOSE_REASON_NORMAL=1e3,WebSocketConnection.CLOSE_REASON_GOING_AWAY=1001,WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR=1002,WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT=1003,WebSocketConnection.CLOSE_REASON_RESERVED=1004,WebSocketConnection.CLOSE_REASON_NOT_PROVIDED=1005,WebSocketConnection.CLOSE_REASON_ABNORMAL=1006,WebSocketConnection.CLOSE_REASON_INVALID_DATA=1007,WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION=1008,WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG=1009,WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED=1010,WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR=1011,WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED=1015,WebSocketConnection.CLOSE_DESCRIPTIONS={1e3:"Normal connection closure",1001:"Remote peer is going away",1002:"Protocol error",1003:"Unprocessable input",1004:"Reserved",1005:"Reason not provided",1006:"Abnormal closure, no further detail available",1007:"Invalid data received",1008:"Policy violation",1009:"Message too big",1010:"Extension requested by client is required",1011:"Internal Server Error",1015:"TLS Handshake Failed"},util.inherits(WebSocketConnection,EventEmitter),WebSocketConnection.prototype._addSocketEventListeners=function(){this.socket.on("error",this.handleSocketError.bind(this)),this.socket.on("end",this.handleSocketEnd.bind(this)),this.socket.on("close",this.handleSocketClose.bind(this)),this.socket.on("drain",this.handleSocketDrain.bind(this)),this.socket.on("pause",this.handleSocketPause.bind(this)),this.socket.on("resume",this.handleSocketResume.bind(this)),this.socket.on("data",this.handleSocketData.bind(this))},WebSocketConnection.prototype.setKeepaliveTimer=function(){this._debug("setKeepaliveTimer"),this.config.keepalive&&!this.config.useNativeKeepalive&&(this.clearKeepaliveTimer(),this.clearGracePeriodTimer(),this._keepaliveTimeoutID=setTimeout(this._keepaliveTimerHandler,this.config.keepaliveInterval))},WebSocketConnection.prototype.clearKeepaliveTimer=function(){this._keepaliveTimeoutID&&clearTimeout(this._keepaliveTimeoutID)},WebSocketConnection.prototype.handleKeepaliveTimer=function(){this._debug("handleKeepaliveTimer"),this._keepaliveTimeoutID=null,this.ping(),this.config.dropConnectionOnKeepaliveTimeout?this.setGracePeriodTimer():this.setKeepaliveTimer()},WebSocketConnection.prototype.setGracePeriodTimer=function(){this._debug("setGracePeriodTimer"),this.clearGracePeriodTimer(),this._gracePeriodTimeoutID=setTimeout(this._gracePeriodTimerHandler,this.config.keepaliveGracePeriod)},WebSocketConnection.prototype.clearGracePeriodTimer=function(){this._gracePeriodTimeoutID&&clearTimeout(this._gracePeriodTimeoutID)},WebSocketConnection.prototype.handleGracePeriodTimer=function(){this._debug("handleGracePeriodTimer"),this._gracePeriodTimeoutID=null,this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL,"Peer not responding.",!0)},WebSocketConnection.prototype.handleSocketData=function(e){this._debug("handleSocketData"),this.setKeepaliveTimer(),this.bufferList.write(e),this.processReceivedData()},WebSocketConnection.prototype.processReceivedData=function(){if(this._debug("processReceivedData"),this.connected&&!this.inputPaused){var e=this.currentFrame;if(e.addData(this.bufferList)){var t=this;if(e.protocolError)return this._debug("-- protocol error"),void process.nextTick((function(){t.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,e.dropReason)}));if(e.frameTooLarge)return this._debug("-- frame too large"),void process.nextTick((function(){t.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,e.dropReason)}));if(e.rsv1||e.rsv2||e.rsv3)return this._debug("-- illegal rsv flag"),void process.nextTick((function(){t.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,"Unsupported usage of rsv bits without negotiated extension.")}));this.assembleFragments||(this._debug("-- emitting frame"),process.nextTick((function(){t.emit("frame",e)}))),process.nextTick((function(){t.processFrame(e)})),this.currentFrame=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config),this.bufferList.length>0&&setImmediateImpl(this.receivedDataHandler)}else this._debug("-- insufficient data for frame")}},WebSocketConnection.prototype.handleSocketError=function(e){this._debug("handleSocketError: %j",e),"closed"!==this.state?(this.closeReasonCode=WebSocketConnection.CLOSE_REASON_ABNORMAL,this.closeDescription="Socket Error: "+e.syscall+" "+e.code,this.connected=!1,this.state="closed",this.fragmentationSize=0,utils.eventEmitterListenerCount(this,"error")>0&&this.emit("error",e),this.socket.destroy(),this._debug.printOutput()):this._debug(" --- Socket 'error' after 'close'")},WebSocketConnection.prototype.handleSocketEnd=function(){this._debug("handleSocketEnd: received socket end. state = %s",this.state),this.receivedEnd=!0,"closed"!==this.state?"peer_requested_close"!==this.state&&"ending"!==this.state&&(this._debug(" --- UNEXPECTED socket end."),this.socket.end()):this._debug(" --- Socket 'end' after 'close'")},WebSocketConnection.prototype.handleSocketClose=function(e){this._debug("handleSocketClose: received socket close"),this.socketHadError=e,this.connected=!1,this.state="closed",-1===this.closeReasonCode&&(this.closeReasonCode=WebSocketConnection.CLOSE_REASON_ABNORMAL,this.closeDescription="Connection dropped by remote peer."),this.clearCloseTimer(),this.clearKeepaliveTimer(),this.clearGracePeriodTimer(),this.closeEventEmitted||(this.closeEventEmitted=!0,this._debug("-- Emitting WebSocketConnection close event"),this.emit("close",this.closeReasonCode,this.closeDescription))},WebSocketConnection.prototype.handleSocketDrain=function(){this._debug("handleSocketDrain: socket drain event"),this.outputBufferFull=!1,this.emit("drain")},WebSocketConnection.prototype.handleSocketPause=function(){this._debug("handleSocketPause: socket pause event"),this.inputPaused=!0,this.emit("pause")},WebSocketConnection.prototype.handleSocketResume=function(){this._debug("handleSocketResume: socket resume event"),this.inputPaused=!1,this.emit("resume"),this.processReceivedData()},WebSocketConnection.prototype.pause=function(){this._debug("pause: pause requested"),this.socket.pause()},WebSocketConnection.prototype.resume=function(){this._debug("resume: resume requested"),this.socket.resume()},WebSocketConnection.prototype.close=function(e,t){if(this.connected){if(this._debug("close: Initating clean WebSocket close sequence."),"number"!=typeof e&&(e=WebSocketConnection.CLOSE_REASON_NORMAL),!validateCloseReason(e))throw new Error("Close code "+e+" is not valid.");"string"!=typeof t&&(t=WebSocketConnection.CLOSE_DESCRIPTIONS[e]),this.closeReasonCode=e,this.closeDescription=t,this.setCloseTimer(),this.sendCloseFrame(this.closeReasonCode,this.closeDescription),this.state="ending",this.connected=!1}},WebSocketConnection.prototype.drop=function(e,t,i){this._debug("drop"),"number"!=typeof e&&(e=WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR),"string"!=typeof t&&(t=WebSocketConnection.CLOSE_DESCRIPTIONS[e]),this._debug("Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s",i,e,t),this.closeReasonCode=e,this.closeDescription=t,this.frameQueue=[],this.fragmentationSize=0,i||this.sendCloseFrame(e,t),this.connected=!1,this.state="closed",this.clearCloseTimer(),this.clearKeepaliveTimer(),this.clearGracePeriodTimer(),this.closeEventEmitted||(this.closeEventEmitted=!0,this._debug("Emitting WebSocketConnection close event"),this.emit("close",this.closeReasonCode,this.closeDescription)),this._debug("Drop: destroying socket"),this.socket.destroy()},WebSocketConnection.prototype.setCloseTimer=function(){this._debug("setCloseTimer"),this.clearCloseTimer(),this._debug("Setting close timer"),this.waitingForCloseResponse=!0,this.closeTimer=setTimeout(this._closeTimerHandler,this.closeTimeout)},WebSocketConnection.prototype.clearCloseTimer=function(){this._debug("clearCloseTimer"),this.closeTimer&&(this._debug("Clearing close timer"),clearTimeout(this.closeTimer),this.waitingForCloseResponse=!1,this.closeTimer=null)},WebSocketConnection.prototype.handleCloseTimer=function(){this._debug("handleCloseTimer"),this.closeTimer=null,this.waitingForCloseResponse&&(this._debug("Close response not received from client. Forcing socket end."),this.waitingForCloseResponse=!1,this.state="closed",this.socket.end())},WebSocketConnection.prototype.processFrame=function(e){if(this._debug("processFrame"),this._debug(" -- frame: %s",e),0!==this.frameQueue.length&&e.opcode>0&&e.opcode<8)this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,"Illegal frame opcode 0x"+e.opcode.toString(16)+" received in middle of fragmented message.");else switch(e.opcode){case 2:this._debug("-- Binary Frame"),this.assembleFragments&&(e.fin?(this._debug("---- Emitting 'message' event"),this.emit("message",{type:"binary",binaryData:e.binaryPayload})):(this.frameQueue.push(e),this.fragmentationSize=e.length));break;case 1:this._debug("-- Text Frame"),this.assembleFragments&&(e.fin?(this._debug("---- Emitting 'message' event"),this.emit("message",{type:"utf8",utf8Data:e.binaryPayload.toString("utf8")})):(this.frameQueue.push(e),this.fragmentationSize=e.length));break;case 0:if(this._debug("-- Continuation Frame"),this.assembleFragments){if(0===this.frameQueue.length)return void this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,"Unexpected Continuation Frame");if(this.fragmentationSize+=e.length,this.fragmentationSize>this.maxReceivedMessageSize)return void this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,"Maximum message size exceeded.");if(this.frameQueue.push(e),e.fin){var t=0,i=bufferAllocUnsafe(this.fragmentationSize),o=this.frameQueue[0].opcode;switch(this.frameQueue.forEach((function(e){e.binaryPayload.copy(i,t),t+=e.binaryPayload.length})),this.frameQueue=[],this.fragmentationSize=0,o){case 2:this.emit("message",{type:"binary",binaryData:i});break;case 1:this.emit("message",{type:"utf8",utf8Data:i.toString("utf8")});break;default:return void this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,"Unexpected first opcode in fragmentation sequence: 0x"+o.toString(16))}}}break;case 9:if(this._debug("-- Ping Frame"),this._pingListenerCount>0){var n=!1;this.emit("ping",(function(){n=!0}),e.binaryPayload),n||this.pong(e.binaryPayload)}else this.pong(e.binaryPayload);break;case 10:this._debug("-- Pong Frame"),this.emit("pong",e.binaryPayload);break;case 8:if(this._debug("-- Close Frame"),this.waitingForCloseResponse)return this._debug("---- Got close response from peer. Completing closing handshake."),this.clearCloseTimer(),this.waitingForCloseResponse=!1,this.state="closed",void this.socket.end();var s;this._debug("---- Closing handshake initiated by peer."),this.state="peer_requested_close",e.invalidCloseFrameLength?(this.closeReasonCode=1005,s=WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR):-1===e.closeStatus||validateCloseReason(e.closeStatus)?(this.closeReasonCode=e.closeStatus,s=WebSocketConnection.CLOSE_REASON_NORMAL):(this.closeReasonCode=e.closeStatus,s=WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR),e.binaryPayload.length>1?this.closeDescription=e.binaryPayload.toString("utf8"):this.closeDescription=WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode],this._debug("------ Remote peer %s - code: %d - %s - close frame payload length: %d",this.remoteAddress,this.closeReasonCode,this.closeDescription,e.length),this._debug("------ responding to remote peer's close request."),this.sendCloseFrame(s,null),this.connected=!1;break;default:this._debug("-- Unrecognized Opcode %d",e.opcode),this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,"Unrecognized Opcode: 0x"+e.opcode.toString(16))}},WebSocketConnection.prototype.send=function(e,t){if(this._debug("send"),Buffer.isBuffer(e))this.sendBytes(e,t);else{if("function"!=typeof e.toString)throw new Error("Data provided must either be a Node Buffer or implement toString()");this.sendUTF(e,t)}},WebSocketConnection.prototype.sendUTF=function(e,t){e=bufferFromString(e.toString(),"utf8"),this._debug("sendUTF: %d bytes",e.length);var i=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);i.opcode=1,i.binaryPayload=e,this.fragmentAndSend(i,t)},WebSocketConnection.prototype.sendBytes=function(e,t){if(this._debug("sendBytes"),!Buffer.isBuffer(e))throw new Error("You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()");var i=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);i.opcode=2,i.binaryPayload=e,this.fragmentAndSend(i,t)},WebSocketConnection.prototype.ping=function(e){this._debug("ping");var t=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);t.opcode=9,t.fin=!0,e&&(Buffer.isBuffer(e)||(e=bufferFromString(e.toString(),"utf8")),e.length>125&&(this._debug("WebSocket: Data for ping is longer than 125 bytes. Truncating."),e=e.slice(0,124)),t.binaryPayload=e),this.sendFrame(t)},WebSocketConnection.prototype.pong=function(e){this._debug("pong");var t=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);t.opcode=10,Buffer.isBuffer(e)&&e.length>125&&(this._debug("WebSocket: Data for pong is longer than 125 bytes. Truncating."),e=e.slice(0,124)),t.binaryPayload=e,t.fin=!0,this.sendFrame(t)},WebSocketConnection.prototype.fragmentAndSend=function(e,t){if(this._debug("fragmentAndSend"),e.opcode>7)throw new Error("You cannot fragment control frames.");var i=this.config.fragmentationThreshold,o=e.binaryPayload.length;if(!this.config.fragmentOutgoingMessages||e.binaryPayload&&o<=i)return e.fin=!0,void this.sendFrame(e,t);for(var n=Math.ceil(o/i),s=0,r=function(e){e?"function"==typeof t&&(t(e),t=null):++s===n&&"function"==typeof t&&t()},a=1;a<=n;a++){var c=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);c.opcode=1===a?e.opcode:0,c.fin=a===n;var h=a===n?o-i*(a-1):i,l=i*(a-1);c.binaryPayload=e.binaryPayload.slice(l,l+h),this.sendFrame(c,r)}},WebSocketConnection.prototype.sendCloseFrame=function(e,t,i){if("number"!=typeof e&&(e=WebSocketConnection.CLOSE_REASON_NORMAL),this._debug("sendCloseFrame state: %s, reasonCode: %d, description: %s",this.state,e,t),"open"===this.state||"peer_requested_close"===this.state){var o=new WebSocketFrame(this.maskBytes,this.frameHeader,this.config);o.fin=!0,o.opcode=8,o.closeStatus=e,"string"==typeof t&&(o.binaryPayload=bufferFromString(t,"utf8")),this.sendFrame(o,i),this.socket.end()}},WebSocketConnection.prototype.sendFrame=function(e,t){this._debug("sendFrame"),e.mask=this.maskOutgoingPackets;var i=this.socket.write(e.toBuffer(),t);return this.outputBufferFull=!i,i};var extend=utils.extend,http=(util=require("util"),EventEmitter=require("events").EventEmitter,require("http")),https=require("https"),url=require("url"),crypto=require("crypto"),protocolSeparators=(bufferAllocUnsafe=utils.bufferAllocUnsafe,["(",")","<",">","@",",",";",":","\\",'"',"/","[","]","?","=","{","}"," ",String.fromCharCode(9)]),excludedTlsOptions=["hostname","port","method","path","headers"];function WebSocketClient(e){var t;(EventEmitter.call(this),this.config={maxReceivedFrameSize:1048576,maxReceivedMessageSize:8388608,fragmentOutgoingMessages:!0,fragmentationThreshold:16384,webSocketVersion:13,assembleFragments:!0,disableNagleAlgorithm:!0,closeTimeout:5e3,tlsOptions:{}},e)&&(e.tlsOptions?(t=e.tlsOptions,delete e.tlsOptions):t={},extend(this.config,e),extend(this.config.tlsOptions,t));switch(this._req=null,this.config.webSocketVersion){case 8:case 13:break;default:throw new Error("Requested webSocketVersion is not supported. Allowed values are 8 and 13.")}}util.inherits(WebSocketClient,EventEmitter),WebSocketClient.prototype.connect=function(e,t,i,o,n){var s=this;if("string"==typeof t&&(t=t.length>0?[t]:[]),t instanceof Array||(t=[]),this.protocols=t,this.origin=i,this.url="string"==typeof e?url.parse(e):e,!this.url.protocol)throw new Error("You must specify a full WebSocket URL, including protocol.");if(!this.url.host)throw new Error("You must specify a full WebSocket URL, including hostname. Relative URLs are not supported.");this.secure="wss:"===this.url.protocol,this.protocols.forEach((function(e){for(var t=0;t<e.length;t++){var i=e.charCodeAt(t),o=e.charAt(t);if(i<33||i>126||-1!==protocolSeparators.indexOf(o))throw new Error('Protocol list contains invalid character "'+String.fromCharCode(i)+'"')}}));this.url.port||(this.url.port={"ws:":"80","wss:":"443"}[this.url.protocol]);for(var r=bufferAllocUnsafe(16),a=0;a<16;a++)r[a]=Math.round(255*Math.random());this.base64nonce=r.toString("base64");var c=this.url.hostname;("ws:"===this.url.protocol&&"80"!==this.url.port||"wss:"===this.url.protocol&&"443"!==this.url.port)&&(c+=":"+this.url.port);var h,l={};function u(e){s._req=null,s.emit("connectFailed",e)}this.secure&&this.config.tlsOptions.hasOwnProperty("headers")&&extend(l,this.config.tlsOptions.headers),o&&extend(l,o),extend(l,{Upgrade:"websocket",Connection:"Upgrade","Sec-WebSocket-Version":this.config.webSocketVersion.toString(10),"Sec-WebSocket-Key":this.base64nonce,Host:l.Host||c}),this.protocols.length>0&&(l["Sec-WebSocket-Protocol"]=this.protocols.join(", ")),this.origin&&(13===this.config.webSocketVersion?l.Origin=this.origin:8===this.config.webSocketVersion&&(l["Sec-WebSocket-Origin"]=this.origin)),h=this.url.pathname?this.url.path:this.url.path?"/"+this.url.path:"/";var d={agent:!1};if(n&&extend(d,n),extend(d,{hostname:this.url.hostname,port:this.url.port,method:"GET",path:h,headers:l}),this.secure){var f=this.config.tlsOptions;for(var p in f)f.hasOwnProperty(p)&&-1===excludedTlsOptions.indexOf(p)&&(d[p]=f[p])}var g=this._req=(this.secure?https:http).request(d);g.on("upgrade",(function(e,t,i){s._req=null,g.removeListener("error",u),s.socket=t,s.response=e,s.firstDataChunk=i,s.validateHandshake()})),g.on("error",u),g.on("response",(function(e){if(s._req=null,utils.eventEmitterListenerCount(s,"httpResponse")>0)s.emit("httpResponse",e,s),e.socket&&e.socket.end();else{var t=[];for(var i in e.headers)t.push(i+": "+e.headers[i]);s.failHandshake("Server responded with a non-101 status: "+e.statusCode+" "+e.statusMessage+"\nResponse Headers Follow:\n"+t.join("\n")+"\n")}})),g.end()},WebSocketClient.prototype.validateHandshake=function(){var e=this.response.headers;if(this.protocols.length>0){if(this.protocol=e["sec-websocket-protocol"],!this.protocol)return void this.failHandshake("Expected a Sec-WebSocket-Protocol header.");if(-1===this.protocols.indexOf(this.protocol))return void this.failHandshake("Server did not respond with a requested protocol.")}if(e.connection&&"upgrade"===e.connection.toLocaleLowerCase())if(e.upgrade&&"websocket"===e.upgrade.toLocaleLowerCase()){var t=crypto.createHash("sha1");t.update(this.base64nonce+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");var i=t.digest("base64");e["sec-websocket-accept"]?e["sec-websocket-accept"]===i?this.succeedHandshake():this.failHandshake("Sec-WebSocket-Accept header from server didn't match expected value of "+i):this.failHandshake("Expected Sec-WebSocket-Accept header from server")}else this.failHandshake("Expected an Upgrade: websocket header from the server");else this.failHandshake("Expected a Connection: Upgrade header from the server")},WebSocketClient.prototype.failHandshake=function(e){this.socket&&this.socket.writable&&this.socket.end(),this.emit("connectFailed",new Error(e))},WebSocketClient.prototype.succeedHandshake=function(){var e=new WebSocketConnection(this.socket,[],this.protocol,!0,this.config);e.webSocketVersion=this.config.webSocketVersion,e._addSocketEventListeners(),this.emit("connect",e),this.firstDataChunk.length>0&&e.handleSocketData(this.firstDataChunk),this.firstDataChunk=null},WebSocketClient.prototype.abort=function(){this._req&&this._req.abort()},module.exports=WebSocketClient;