ws-client-js
Version:
WebSocket browser client implementation with reconnect behavior.
2 lines (1 loc) • 5.18 kB
JavaScript
class e{serialize(e){return JSON.stringify(e)}deserialize(e){if("string"==typeof e)return JSON.parse(e);throw new Error(`Deserialize failed for data: ${e}`)}}const t=Object.freeze({CONNECTING:"connecting",OPEN:"open",CLOSED:"closed"}),s=Object.freeze({NORMAL:1e3,GOING_AWAY:1001,PROTOCOL_ERROR:1002,UNSUPPORTED_DATA:1003,RESERVED:1004,NO_STATUS_RESERVED:1005,ABNORMAL_CLOSURE_RESERVED:1006,INVALID_FRAME_PAYLOAD_DATA:1007,POLICY_VIOLATION:1008,MESSAGE_TOO_BIG:1009,MISSING_EXTENSION:1010,INTERNAL_ERROR:1011,SERVICE_RESTART:1012,TRY_AGAIN_LATER:1013,BAD_GATEWAY:1014,TLS_HANDSHAKE_RESERVED:1015});class n{constructor(e,t){this.attemptsMax=0,this.skipCloseEventCodes=[],this.initialState={delay:e.delay,attempts:0},this.state={delay:this.initialState.delay,attempts:this.initialState.attempts},this.attemptsMax=e.attempts,this.delayIncreaseType=e.delayIncreaseType,this.skipCloseEventCodes=e.skipCloseEventCodes,this.callbacks=t,this.perform=this.perform.bind(this)}get isStarted(){return this.state.attempts>this.initialState.attempts}canApply(e){return-1===this.skipCloseEventCodes.indexOf(e)}start(){this.state.attempts+=1,this.state.attempts>=this.attemptsMax?(this.resetState(),this.callbacks.onEnd()):(this.clearTimer(),this.timeoutId=window.setTimeout(this.perform,this.state.delay))}stop(){this.resetState(),this.clearTimer()}perform(){switch(this.delayIncreaseType){case"twice":this.state.delay=2*this.state.delay}this.callbacks.onNext()}resetState(){this.state.attempts=this.initialState.attempts,this.state.delay=this.initialState.delay}clearTimer(){this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=void 0)}}class i{constructor(e,s){if(this.status=t.CONNECTING,this.options={url:"",debug:!1},this.connection=null,this.error=null,this.resendQueue=[],this.listeners={onOpen:[],onReopen:[],onMessage:[],onClose:[],onError:[]},this.serializer=e,this.options={...this.options,...s},-1===this.options.url.search(/(wss?|https?)/))throw new Error(`Not valid webSocket URL: ${this.options.url}`);this.handleMessage=this.handleMessage.bind(this),this.handleOpen=this.handleOpen.bind(this),this.handleError=this.handleError.bind(this),this.handleClose=this.handleClose.bind(this),this.handleReconnectNext=this.handleReconnectNext.bind(this),this.handleReconnectEnd=this.handleReconnectEnd.bind(this),s.reconnect&&(this.reconnect=new n(s.reconnect,{onNext:this.handleReconnectNext,onEnd:this.handleReconnectEnd}))}onOpen(e){return this.status===t.OPEN?e():this.listeners.onOpen.push(e),this.getEventListenerDisposer("onOpen",e)}onReopen(e){return this.listeners.onReopen.push(e),this.getEventListenerDisposer("onReopen",e)}onMessage(e){return this.listeners.onMessage.push(e),this.getEventListenerDisposer("onMessage",e)}onClose(e){return this.listeners.onClose.push(e),this.getEventListenerDisposer("onClose",e)}onError(e){return this.listeners.onError.push(e),this.getEventListenerDisposer("onError",e)}connect(e){null===this.connection&&(this.protocol=e,this.status=t.CONNECTING,this.connection=new WebSocket(this.options.url,e),this.connection.onmessage=this.handleMessage,this.connection.onopen=this.handleOpen,this.connection.onerror=this.handleError,this.connection.onclose=this.handleClose)}disconnect(e){null!==this.connection&&(this.status=t.CLOSED,this.notifyListeners("onClose",e||{code:s.NORMAL,reason:"Closed by client"}),this.connection.close(),this.connection=null)}send(e){return this.status===t.CONNECTING?(this.resendQueue.push(e),!0):this.status===t.OPEN&&this.connection?(this.connection.send(this.serializer.serialize(e)),!0):(this.notifyListeners("onError"),!1)}resend(){if(0!==this.resendQueue.length&&this.connection){for(let e=0;e<this.resendQueue.length;e+=1)this.connection.send(this.serializer.serialize(this.resendQueue[e]));this.resendQueue=[]}}handleMessage(e){this.notifyListeners("onMessage",this.serializer.deserialize(e.data))}handleOpen(e){this.status=t.OPEN,this.reconnect&&this.reconnect.isStarted?(this.reconnect.stop(),this.error=null,this.notifyListeners("onReopen"),this.log("socket reopened",e)):(this.notifyListeners("onOpen"),this.log("socket opened",e)),this.resend()}handleError(e){this.notifyListeners("onError"),this.error=e,this.log("socket error",e)}handleReconnectNext(){this.status=t.CONNECTING,this.connection&&(this.connection=null),this.connect(this.protocol)}handleReconnectEnd(){this.status=t.CLOSED,this.disconnect({code:s.NORMAL,reason:"Reconnect failed"})}handleClose(e){this.reconnect&&(this.error||this.reconnect.canApply(e.code))?this.reconnect.start():(this.log("socket stopped",e),this.disconnect({code:e.code,reason:e.reason}))}notifyListeners(e,t){switch(e){case"onOpen":case"onError":case"onReopen":for(let t=0;t<this.listeners[e].length;t+=1)this.listeners[e][t]();break;case"onClose":case"onMessage":for(let s=0;s<this.listeners[e].length;s+=1)this.listeners[e][s](t)}}getEventListenerDisposer(e,t){return()=>{this.listeners[e]=this.listeners[e].filter(e=>e!==t)}}log(...e){this.options.debug&&(console.group("WebSocket Connection log"),e.forEach(e=>{"object"==typeof e?console.dir(e):console.info(e)}),console.groupEnd())}}export{s as CLOSE_EVENT_CODE,i as Connection,e as JsonSerializer,t as STATUS};