ziron-client
Version:
1 lines • 15 kB
JavaScript
"use strict";var __awaiter=this&&this.__awaiter||function(t,e,n,i){return new(n||(n=Promise))((function(s,o){function r(t){try{c(i.next(t))}catch(t){o(t)}}function h(t){try{c(i.throw(t))}catch(t){o(t)}}function c(t){var e;t.done?s(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(r,h)}c((i=i.apply(t,e||[])).next())}))};Object.defineProperty(exports,"__esModule",{value:!0});const SocketOptions_1=require("./SocketOptions"),TokenStoreEngine_1=require("../main/tokenStore/TokenStoreEngine"),WebSocket_1=require("./WebSocket"),ziron_engine_1=require("ziron-engine"),Constants_1=require("../main/utils/Constants"),Deferred_1=require("../main/utils/Deferred"),ziron_errors_1=require("ziron-errors"),Errors_1=require("../main/Errors"),emitix_1=require("emitix"),CancelablePromise_1=require("../main/utils/CancelablePromise"),AuthToken_1=require("../main/utils/AuthToken"),URL=require("url"),URL_1=require("../main/utils/URL"),isIp=require("is-ip");class Socket{setAuth(t,e,n){const i=this.authToken;this.authToken=t,this.signedAuthToken=e,this.authenticated=null!=t,i!==t&&(this._emit("authTokenChange",t,i,n),this._processPendingSubscriptions())}constructor(t,e){var n;this.autoReconnectOptions={active:!0,initialDelay:5e3,multiplier:1.5,randomness:5e3,maxDelay:6e4},this._state=2,this._channelMap={},this._chEmitter=new emitix_1.default,this.currentMaxPayloadSize=4194304,this._currentPingTimeout=2e5,this.signedAuthToken=null,this.authToken=null,this.authenticated=!1,this.reconnectAttempts=0,this.procedures={},this.onUnknownInvoke=()=>{},this.receivers={"#":Constants_1.EMPTY_HANDLER,"#1":t=>{const e=(0,AuthToken_1.extractAuthToken)(t);e?(this.setAuth(e,t,!1),this._tokenStoreEngine.saveToken(t)):0===this._state&&this.transmit("#2")},"#2":()=>{this.setAuth(null,null,!1),this._tokenStoreEngine.removeToken()},"#3":([t,e])=>{1===this._channelMap[t]&&(this._channelMap[t]=0,this._chEmitter.emit("unsubscribe/"+t,1,e),this._chEmitter.emit("unsubscribe",t,1,e))},"#0":([t,e],n)=>{this.hasSubscribed(t)&&(this._chEmitter.emit("publish/"+t,e,0!==n),this._chEmitter.emit("publish",t,e,0!==n))}},this.onUnknownTransmit=()=>{},this._localEmitter=new emitix_1.default,this.once=this._localEmitter.once.bind(this._localEmitter),this.on=this._localEmitter.on.bind(this._localEmitter),this.off=this._localEmitter.off.bind(this._localEmitter),this._emit=this._localEmitter.emit.bind(this._localEmitter),this._boundPingTimeoutReached=this._onPingTimeoutReached.bind(this),this._boundConnectTimeoutReached=this._onConnectTimeoutReached.bind(this),this._boundOnSocketOpen=this._onSocketOpen.bind(this),this._boundOnSocketClose=this._onSocketClose.bind(this),this._boundOnSocketError=this._onSocketError.bind(this),this._boundOnSocketDrain=this._onSocketDrain.bind(this);const i="string"==typeof t?Object.assign(Socket.parseOptionsFromUrl(t),e||{}):t||{};this.options={hostname:SocketOptions_1.DEFAULT_HOSTNAME,port:(0,SocketOptions_1.getDefaultPort)(null!==(n=i.secure)&&void 0!==n?n:SocketOptions_1.DEFAULT_SECURE),secure:SocketOptions_1.DEFAULT_SECURE,path:"/",responseTimeout:7e3,connectTimeout:2e4,invokeSendTimeout:3e3,transmitSendTimeout:null,autoReconnect:{},autoResubscribe:!0,handshakeAttachment:void 0,wsOptions:{},tokenStore:null,lowSendBackpressureMark:209715,maxPackageBufferSize:Number.POSITIVE_INFINITY,binaryContentPacketTimeout:1e4,streamsPerPackageLimit:20,chunksCanContainStreams:!1},Object.assign(this.options,i),this.options.path=(0,URL_1.preprocessPath)(this.options.path),Object.assign(this.autoReconnectOptions,i.autoReconnect),this._url=this._createUrl(),this._stringifiedHandshakeAttachment=null!=this.options.handshakeAttachment?JSON.stringify(this.options.handshakeAttachment):void 0,this._tokenStoreEngine=new TokenStoreEngine_1.default(i.tokenStore),this.transportOptions=ziron_engine_1.Transport.buildOptions(this.options),this._transport=new ziron_engine_1.Transport({onTransmit:this._onTransmit.bind(this),onInvoke:this._onInvoke.bind(this),onListenerError:t=>this._emit("error",t),onInvalidMessage:()=>this._destroyConnection(4400,"Bad message"),hasLowSendBackpressure:this.hasLowSendBackpressure.bind(this)},this.transportOptions,!1),this._transport.onPing=()=>{this._renewPingTimeout(),this._transport.sendPong()},this.flushBuffer=this._transport.buffer.flushBuffer.bind(this._transport.buffer),this.getBufferSize=this._transport.buffer.getBufferSize.bind(this._transport.buffer),this.sendPackage=this._transport.sendPackage.bind(this._transport),this._onMessageHandler=t=>this._transport.emitMessage(t.data)}_updateTransportOptions(t){t<=0||(this.transportOptions.limitBatchBinarySize=Math.max(Math.ceil(.7*t),200),this.transportOptions.limitBatchStringLength=Math.max(Math.ceil(t/4*.7),2e3))}static parseOptionsFromUrl(t){/^([a-zA-Z]*:\/\/)/.test(t)||(t="ws://"+t);const e=URL.parse(t),n={};if(e.port){const t=parseInt(e.port);isNaN(t)||(n.port=t)}return e.hostname&&(n.hostname=e.hostname),n.secure="wss:"===e.protocol,e.pathname&&(n.path=e.pathname),n}_onInvoke(t,e,n,i,s){if(this.procedures[t])return this.procedures[t](e,n,i,s);this.onUnknownInvoke(t,e,n,i,s)}_onTransmit(t,e,n){if(this.receivers[t])return this.receivers[t](e,n);this.onUnknownTransmit(t,e,n)}connect(t){return __awaiter(this,void 0,void 0,(function*(){if(2===this._state){this._state=1,clearTimeout(this._reconnectTimeoutTicker);const e=new Deferred_1.default;this._connectDeferred=e;try{const n=yield this._loadHandshakeAuthToken();if(e.fulfilled)return e.promise;this._handshakeAuthToken=n,this._connectTimeoutTicker=setTimeout(this._boundConnectTimeoutReached,t||this.options.connectTimeout);const i=(0,WebSocket_1.createWebSocket)(this._createHandshakeUrl(),Socket._createHandshakeProtocolHeader(null==n?void 0:n.signed),this.options.wsOptions);this._socket=i,i.binaryType="arraybuffer",i.onmessage=this._onMessageHandler,i.onclose=this._boundOnSocketClose,i.onerror=this._boundOnSocketError,i.onopen=this._boundOnSocketOpen,i.ondrain=this._boundOnSocketDrain,this._transport.send=t=>{try{i.send(t)}catch(t){this._destroyConnection(1006,(0,Errors_1.stringifyError)(t))}}}catch(t){this._destroyConnection(1e3,(0,Errors_1.stringifyError)(t))}}return this._connectDeferred.promise}))}_loadHandshakeAuthToken(){return __awaiter(this,void 0,void 0,(function*(){if(null!=this.signedAuthToken)return{signed:this.signedAuthToken,plain:this.authToken};const t=yield this._tokenStoreEngine.loadToken();if(null!=t){const e=(0,AuthToken_1.extractAuthToken)(t);return null==e?(this._tokenStoreEngine.removeToken(),null):{signed:t,plain:e}}return null}))}disconnect(t=1e3,e){2!==this._state&&this._destroyConnection(t,e),clearTimeout(this._reconnectTimeoutTicker)}reconnect(t){return __awaiter(this,void 0,void 0,(function*(){return this.disconnect(),this.connect(t)}))}_tryConnect(){this.connect().catch(Constants_1.EMPTY_HANDLER)}_destroyConnection(t,e){e=e||ziron_errors_1.socketProtocolErrorStatuses[t]||"Unknown reason";const n=this._socket;switch(n&&(n.onopen=Constants_1.EMPTY_HANDLER,n.onclose=Constants_1.EMPTY_HANDLER,n.onmessage=Constants_1.EMPTY_HANDLER,n.onerror=Constants_1.EMPTY_HANDLER,n.ondrain=Constants_1.EMPTY_HANDLER),clearTimeout(this._connectTimeoutTicker),clearTimeout(this._pingTimeoutTicker),clearTimeout(this._reconnectTimeoutTicker),n&&n.readyState<2&&n.close(t,e),this._state){case 0:this._onDisconnect(t,e);break;case 1:this._onConnectAbort(t,e);break;case 2:this._transport.emitBadConnection(ziron_engine_1.BadConnectionType.ConnectAbort)}this._suspendSubscriptions(),this.autoReconnectOptions.active&&(4e3===t||4001===t||1005===t?this._tryReconnect(0):1e3!==t&&t<4500&&this._tryReconnect())}_renewPingTimeout(){clearTimeout(this._pingTimeoutTicker),this._pingTimeoutTicker=setTimeout(this._boundPingTimeoutReached,this._currentPingTimeout)}_onPingTimeoutReached(){this._destroyConnection(4e3)}_onConnectTimeoutReached(){this._destroyConnection(4007)}_onSocketOpen(){const t=this._socket;this.receivers["#"]=([e,n,i,s])=>{1===t.readyState&&(this._currentPingTimeout=e+2e3,this.currentMaxPayloadSize=n,this._updateTransportOptions(n),this._processOpenAuthTokenState(i),this._state=0,this.reconnectAttempts=0,this.receivers["#"]=Constants_1.EMPTY_HANDLER,clearTimeout(this._connectTimeoutTicker),this._renewPingTimeout(),this._transport.emitConnection(),this._connectDeferred.resolve(s),this._emit("connect",s),this._processPendingSubscriptions())}}_processOpenAuthTokenState(t){if(0===t){const t=this._handshakeAuthToken;null!=t?this.setAuth(t.plain,t.signed,!1):this.setAuth(null,null,!1)}else(t>0||-1===t)&&(this.setAuth(null,null,!1),2===t&&this._tokenStoreEngine.removeToken())}_onSocketClose(t){this._destroyConnection(null==t.code?1005:t.code,t.reason)}_onSocketError(){1===this._state&&this._destroyConnection(1006)}_onSocketDrain(t){t<=this.options.lowSendBackpressureMark&&this._transport.emitSendBackpressureDrain()}get bufferedSendAmount(){var t;return(null===(t=this._socket)||void 0===t?void 0:t.bufferedAmount)||0}hasLowSendBackpressure(){return null!=this._socket&&this._socket.bufferedAmount<=this.options.lowSendBackpressureMark}_onConnectAbort(t,e){this._state=2,this._transport.emitBadConnection(ziron_engine_1.BadConnectionType.ConnectAbort);const n=new Errors_1.ConnectAbortError(this._url,t,e);this._connectDeferred.reject(n),this._emit("connectAbort",t,e)}_onDisconnect(t,e){this._state=2,this._transport.emitBadConnection(ziron_engine_1.BadConnectionType.Disconnect),this._emit("disconnect",t,e)}_tryReconnect(t){const e=this.reconnectAttempts++;let n=null==t||e>0?Math.round(Math.round(this.autoReconnectOptions.initialDelay+this.autoReconnectOptions.randomness*Math.random())*Math.pow(this.autoReconnectOptions.multiplier,this.reconnectAttempts)):t;null!=this.autoReconnectOptions.maxDelay&&n>this.autoReconnectOptions.maxDelay&&(n=this.autoReconnectOptions.maxDelay),clearTimeout(this._reconnectTimeoutTicker),this._reconnectTimeoutTicker=setTimeout(()=>this._tryConnect(),n)}authenticate(t,e){const n=(0,AuthToken_1.extractAuthToken)(t);if(null==n)throw new ziron_errors_1.AuthTokenError("Invalid signed auth-token: payload could not be extracted.");const i=this.invoke("#2",t,e),s=i.then(()=>{this.setAuth(n,t,!0),this._tokenStoreEngine.saveToken(t)});return e.cancelable?(0,CancelablePromise_1.toCancelablePromise)(s,t=>i.cancel(t)):s}deauthenticate(){return __awaiter(this,void 0,void 0,(function*(){0===this._state&&(yield this.transmit("#2")),this.setAuth(null,null,!0),this._tokenStoreEngine.removeToken()}))}isConnected(){return 0===this._state}isConnecting(){return 1===this._state}isClosed(){return 2===this._state}_createUrl(){let t="";this.options.port&&(this.options.secure&&443!==this.options.port||!this.options.secure&&80!==this.options.port)&&(t=":"+this.options.port);const e=isIp.v6(this.options.hostname)?`[${this.options.hostname}]`:this.options.hostname;return`${this.options.secure?"wss":"ws"}://${e}${t}${this.options.path}`}_createHandshakeUrl(){return this._stringifiedHandshakeAttachment?this._url+"?"+encodeURIComponent(this._stringifiedHandshakeAttachment):this._url}static _createHandshakeProtocolHeader(t){return t?["ziron",encodeURIComponent(t)]:"ziron"}transmit(t,e,n={}){void 0===n.sendTimeout&&(n.sendTimeout=this.options.transmitSendTimeout);const i=this._transport.prepareTransmit(t,e,n);if(0!==this._state&&this._tryConnect(),n.cancelable||null!=n.sendTimeout){const t=this._transport.sendPackageWithPromise(i,n.batch),e=(0,CancelablePromise_1.toCancelablePromise)(t,()=>this._transport.tryCancelPackage(i));if(null!=n.sendTimeout){const i=setTimeout(()=>{e.cancel(new ziron_engine_1.TimeoutError("Transmit send timeout reached.","SendTimeout"))},n.sendTimeout);t.finally(()=>clearTimeout(i))}return e}return this._transport.sendPackageWithPromise(i,n.batch)}invoke(t,e,n={}){void 0===n.sendTimeout&&(n.sendTimeout=this.options.invokeSendTimeout);const i=this._transport.prepareInvoke(t,e,n);if(0!==this._state&&this._tryConnect(),null!=n.sendTimeout){const t=this._transport.sendPackageWithPromise(i,n.batch),e=(0,CancelablePromise_1.toCancelablePromise)(i.promise,()=>this._transport.tryCancelPackage(i)),s=setTimeout(()=>{e.cancel(new ziron_engine_1.TimeoutError("Invoke send timeout reached.","SendTimeout"))},n.sendTimeout);return t.finally(()=>clearTimeout(s)),e}return this._transport.sendPackage(i,n.batch),n.cancelable?(0,CancelablePromise_1.toCancelablePromise)(i.promise,()=>this._transport.tryCancelPackage(i)):i.promise}subscribe(t){return __awaiter(this,arguments,void 0,(function*(t,e={}){1!==this._channelMap[t]&&(yield this.invoke("#1",t,e),this._channelMap[t]=1,this._chEmitter.emit("subscribe/"+t),this._chEmitter.emit("subscribe",t))}))}_trySubscribe(t,e){return __awaiter(this,void 0,void 0,(function*(){try{yield this.subscribe(t,{batch:e})}catch(t){}}))}unsubscribe(t,e){return __awaiter(this,void 0,void 0,(function*(){return"string"==typeof t?this._unsubscribe(t,e):(t=t||{},Promise.all(Object.keys(this._channelMap).map(e=>this._unsubscribe(e,t))))}))}_unsubscribe(t){return __awaiter(this,arguments,void 0,(function*(t,e={}){const n=this._channelMap[t];null!=n&&(0===this._state&&(yield this.transmit("#1",t,e)),delete this._channelMap[t],1===n&&(this._chEmitter.emit("unsubscribe/"+t,0),this._chEmitter.emit("unsubscribe",t,0)))}))}hasSubscribed(t,e=!1){return e?void 0!==this._channelMap[t]:1===this._channelMap[t]}getSubscriptions(t=!1){return t?Object.keys(this._channelMap):Object.keys(this._channelMap).filter(t=>1===this._channelMap[t])}publish(t,e,n={}){return n.ack?this.invoke("#0",[t,e],n):this.transmit("#0",[t,e],n)}_processPendingSubscriptions(){const t=Object.keys(this._channelMap);for(let e=0,n=t.length;e<n;e++)1!==this._channelMap[t[e]]&&this._trySubscribe(t[e],!0);this.flushBuffer()}_suspendSubscriptions(){const t=Object.keys(this._channelMap);for(let e,n=0,i=t.length;n<i;n++)e=t[n],1===this._channelMap[e]&&(this._chEmitter.emit("unsubscribe/"+e,2),this._chEmitter.emit("unsubscribe",e,2),this._channelMap[e]=0)}onPublish(t,e){"string"==typeof t?this._chEmitter.on("publish/"+t,e):this._chEmitter.on("publish",t)}oncePublish(t,e){return"string"==typeof t?this._chEmitter.once("publish/"+t,e):this._chEmitter.once("publish",t)}offPublish(t,e){"string"==typeof t?this._chEmitter.off("publish/"+t,e):this._chEmitter.off("publish",t)}onSubscribe(t,e){"string"==typeof t?this._chEmitter.on("subscribe/"+t,e):this._chEmitter.on("subscribe",t)}onceSubscribe(t,e){return"string"==typeof t?this._chEmitter.once("subscribe/"+t,e):this._chEmitter.once("subscribe",t)}offSubscribe(t,e){"string"==typeof t?this._chEmitter.off("subscribe/"+t,e):this._chEmitter.off("subscribe",t)}onUnsubscribe(t,e){"string"==typeof t?this._chEmitter.on("unsubscribe/"+t,e):this._chEmitter.on("unsubscribe",t)}onceUnsubscribe(t,e){return"string"==typeof t?this._chEmitter.once("unsubscribe/"+t,e):this._chEmitter.once("unsubscribe",t)}offUnsubscribe(t,e){"string"==typeof t?this._chEmitter.off("unsubscribe/"+t,e):this._chEmitter.off("unsubscribe",t)}removeAllChannelListener(){this._chEmitter.off()}}exports.default=Socket;