├── .github └── workflows │ └── test-library.yml ├── .gitignore ├── LICENSE ├── README.md ├── asyngular-client.js ├── asyngular-client.min.js ├── browserify-build.sh ├── gulp-build.sh ├── gulpfile.js ├── index.js ├── lib ├── auth.js ├── clientsocket.js ├── factory.js ├── transport.js ├── wait.js └── ws-browser.js ├── package.json └── test └── integration.js /.github/workflows/test-library.yml: -------------------------------------------------------------------------------- 1 | name: test and build library 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | test-asyngular-client: 7 | runs-on: ${{ matrix.platform }} 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | platform: [ubuntu-latest, macos-latest, windows-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | with: 17 | fetch-depth: 3 18 | - name: test 19 | timeout-minutes: 15 20 | run: | 21 | yarn 22 | yarn test 23 | - name: build 24 | timeout-minutes: 15 25 | run: | 26 | yarn build 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | browser/ 3 | dist/ 4 | .idea 5 | package-lock.json 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013-2018 SocketCluster.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Asyngular JavaScript client 2 | ====== 3 | 4 | Client module for Asyngular. 5 | 6 | ## Setting up 7 | 8 | You will need to install both ```asyngular-client``` and ```asyngular-server``` (https://github.com/SocketCluster/asyngular-server). 9 | 10 | To install this module: 11 | ```bash 12 | npm install asyngular-client 13 | ``` 14 | 15 | ## How to use 16 | 17 | The asyngular-client script is called `asyngular-client.js` (located in the main asyngular-client directory). 18 | Embed it in your HTML page like this: 19 | ```html 20 | 21 | ``` 22 | \* Note that the src attribute may be different depending on how you setup your HTTP server. 23 | 24 | Once you have embedded the client `asyngular-client.js` into your page, you will gain access to a global `asyngularClient` object. 25 | You may also use CommonJS `require` or ES6 module imports. 26 | 27 | ### Connect to a server 28 | 29 | ```js 30 | let socket = asyngularClient.create({ 31 | hostname: 'localhost', 32 | port: 8000 33 | }); 34 | ``` 35 | 36 | ### Transmit data 37 | 38 | ```js 39 | // Transmit some data to the server. 40 | // It does not expect a response from the server. 41 | // From the server socket, it can be handled using either: 42 | // - for await (let data of socket.receiver('foo')) {} 43 | // - let data = await socket.receiver('foo').once() 44 | socket.transmit('foo', 123); 45 | ``` 46 | 47 | ### Invoke an RPC 48 | 49 | ```js 50 | (async () => { 51 | 52 | // Invoke an RPC on the server. 53 | // It expects a response from the server. 54 | // From the server socket, it can be handled using either: 55 | // - for await (let req of socket.procedure('myProc')) {} 56 | // - let req = await socket.procedure('myProc').once() 57 | let result = await socket.invoke('myProc', 123); 58 | 59 | })(); 60 | ``` 61 | 62 | ### Subscribe to a channel 63 | 64 | ```js 65 | (async () => { 66 | 67 | // Subscribe to a channel. 68 | let myChannel = socket.subscribe('myChannel'); 69 | 70 | await myChannel.listener('subscribe').once(); 71 | // myChannel.state is now 'subscribed'. 72 | 73 | })(); 74 | ``` 75 | 76 | ### Get a channel without subscribing 77 | 78 | ```js 79 | (async () => { 80 | 81 | let myChannel = socket.channel('myChannel'); 82 | 83 | // Can subscribe to the channel later as a separate step. 84 | myChannel.subscribe(); 85 | await myChannel.listener('subscribe').once(); 86 | // myChannel.state is now 'subscribed'. 87 | 88 | })(); 89 | ``` 90 | 91 | ### Publish data to a channel 92 | 93 | ```js 94 | // Publish data to the channel. 95 | myChannel.transmitPublish('This is a message'); 96 | 97 | // Publish data to the channel from the socket. 98 | socket.transmitPublish('myChannel', 'This is a message'); 99 | 100 | (async () => { 101 | // Publish data to the channel and await for the message 102 | // to reach the server. 103 | try { 104 | await myChannel.invokePublish('This is a message'); 105 | } catch (error) { 106 | // Handle error. 107 | } 108 | 109 | // Publish data to the channel from the socket and await for 110 | // the message to reach the server. 111 | try { 112 | await socket.invokePublish('myChannel', 'This is a message'); 113 | } catch (error) { 114 | // Handle error. 115 | } 116 | })(); 117 | ``` 118 | 119 | ### Consume data from a channel 120 | 121 | ```js 122 | (async () => { 123 | 124 | for await (let data of myChannel) { 125 | // ... 126 | } 127 | 128 | })(); 129 | ``` 130 | 131 | ### Connect over HTTPS: 132 | 133 | ```js 134 | let options = { 135 | hostname: 'securedomain.com', 136 | secure: true, 137 | port: 443, 138 | rejectUnauthorized: false // Only necessary during debug if using a self-signed certificate 139 | }; 140 | // Initiate the connection to the server 141 | let socket = asyngularClient.create(options); 142 | ``` 143 | 144 | For more detailed examples of how to use Asyngular, see `test/integration.js`. 145 | Also, see tests from the `asyngular-server` module. 146 | 147 | ### Connect Options 148 | 149 | See all available options: https://asyngular.io/docs/api-asyngular-client/ 150 | 151 | ```js 152 | let options = { 153 | path: '/asyngular/', 154 | port: 8000, 155 | hostname: '127.0.0.1', 156 | autoConnect: true, 157 | secure: false, 158 | rejectUnauthorized: false, 159 | connectTimeout: 10000, //milliseconds 160 | ackTimeout: 10000, //milliseconds 161 | channelPrefix: null, 162 | disconnectOnUnload: true, 163 | autoReconnectOptions: { 164 | initialDelay: 10000, //milliseconds 165 | randomness: 10000, //milliseconds 166 | multiplier: 1.5, //decimal 167 | maxDelay: 60000 //milliseconds 168 | }, 169 | authEngine: null, 170 | codecEngine: null, 171 | subscriptionRetryOptions: {}, 172 | query: { 173 | yourparam: 'hello' 174 | } 175 | }; 176 | ``` 177 | 178 | ## Running the tests 179 | 180 | - Clone this repo: `git clone git@github.com:SocketCluster/asyngular-client.git` 181 | - Navigate to project directory: `cd asyngular-client` 182 | - Install all dependencies: `npm install` 183 | - Run the tests: `npm test` 184 | 185 | ## Compatibility mode 186 | 187 | For compatibility with an existing SocketCluster server, set the `protocolVersion` to `1` and make sure that the `path` matches your old server path: 188 | 189 | ```js 190 | let socket = asyngularClient.create({ 191 | protocolVersion: 1, 192 | path: '/socketcluster/' 193 | }); 194 | ``` 195 | 196 | ## Developing 197 | 198 | ### Install all dependencies 199 | 200 | ```bash 201 | cd asyngular-client 202 | 203 | npm install -g gulp gulp-cli browserify uglify-es 204 | 205 | npm install 206 | ``` 207 | 208 | ### Building 209 | 210 | To build the Asyngular client: 211 | 212 | ```bash 213 | npm run build 214 | ``` 215 | 216 | ## Change log 217 | 218 | See the 'releases' section for changes: https://github.com/SocketCluster/asyngular-client/releases 219 | 220 | ## License 221 | 222 | (The MIT License) 223 | 224 | Copyright (c) 2013-2019 Asyngular.io 225 | 226 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 227 | 228 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 229 | 230 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 231 | -------------------------------------------------------------------------------- /asyngular-client.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Asyngular JavaScript client v6.2.4 3 | */ 4 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).asyngularClient=t()}}(function(){return function(){return function t(e,r,n){function o(s,a){if(!r[s]){if(!e[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(i)return i(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var h=r[s]={exports:{}};e[s][0].call(h.exports,function(t){return o(e[s][1][t]||t)},h,h.exports,t,e,r,n)}return r[s].exports}for(var i="function"==typeof require&&require,s=0;s{if(this[t]>i)throw new m(`The ${t} value provided exceeded the maximum amount allowed`)};if(u("connectTimeout"),u("ackTimeout"),u("pingTimeout"),this.connectAttempts=0,this.isBatching=!1,this.batchOnHandshake=e.batchOnHandshake,this.batchOnHandshakeDuration=e.batchOnHandshakeDuration,this._batchingIntervalId=null,this._outboundBuffer=new h,this._channelMap={},this._channelEventDemux=new n,this._channelDataDemux=new n,this._receiverDemux=new n,this._procedureDemux=new n,this.options=e,this._cid=1,this.options.callIdGenerator=(()=>this._cid++),this.options.autoReconnect){null==this.options.autoReconnectOptions&&(this.options.autoReconnectOptions={});let t=this.options.autoReconnectOptions;null==t.initialDelay&&(t.initialDelay=1e4),null==t.randomness&&(t.randomness=1e4),null==t.multiplier&&(t.multiplier=1.5),null==t.maxDelay&&(t.maxDelay=6e4)}if(null==this.options.subscriptionRetryOptions&&(this.options.subscriptionRetryOptions={}),this.options.authEngine?this.auth=this.options.authEngine:this.auth=new s,this.options.codecEngine?this.codec=this.options.codecEngine:this.codec=a,this.options.protocol){let t=new m('The "protocol" option does not affect asyngular-client - If you want to utilize SSL/TLS, use "secure" option instead');this._onError(t)}this.options.query=e.query||{},"string"==typeof this.options.query&&(this.options.query=c.parse(this.options.query)),_&&this.disconnectOnUnload&&r.addEventListener&&r.removeEventListener&&this._handleBrowserUnload(),this.options.autoConnect&&this.connect()}k.prototype=Object.create(o.prototype),k.CONNECTING=k.prototype.CONNECTING=u.prototype.CONNECTING,k.OPEN=k.prototype.OPEN=u.prototype.OPEN,k.CLOSED=k.prototype.CLOSED=u.prototype.CLOSED,k.AUTHENTICATED=k.prototype.AUTHENTICATED="authenticated",k.UNAUTHENTICATED=k.prototype.UNAUTHENTICATED="unauthenticated",k.SUBSCRIBED=k.prototype.SUBSCRIBED=i.SUBSCRIBED,k.PENDING=k.prototype.PENDING=i.PENDING,k.UNSUBSCRIBED=k.prototype.UNSUBSCRIBED=i.UNSUBSCRIBED,k.ignoreStatuses=d.socketProtocolIgnoreStatuses,k.errorStatuses=d.socketProtocolErrorStatuses,Object.defineProperty(k.prototype,"isBufferingBatch",{get:function(){return this.transport.isBufferingBatch}}),k.prototype.getBackpressure=function(){return Math.max(this.getAllListenersBackpressure(),this.getAllReceiversBackpressure(),this.getAllProceduresBackpressure(),this.getAllChannelsBackpressure())},k.prototype._handleBrowserUnload=async function(){let t=()=>{this.disconnect()},e=!1;(async()=>{let n=this.listener("connecting").createConsumer();for(;;){if((await n.next()).done)break;e||(e=!0,r.addEventListener("beforeunload",t,!1))}})(),(async()=>{let n=this.listener("close").createConsumer();for(;;){if((await n.next()).done)break;e&&(e=!1,r.removeEventListener("beforeunload",t,!1))}})()},k.prototype._setAuthToken=function(t){this._changeToAuthenticatedState(t.token),(async()=>{try{await this.auth.saveToken(this.authTokenName,t.token,{})}catch(t){this._onError(t)}})()},k.prototype._removeAuthToken=function(){(async()=>{let t;try{t=await this.auth.removeToken(this.authTokenName)}catch(t){return void this._onError(t)}this.emit("removeAuthToken",{oldAuthToken:t})})(),this._changeToUnauthenticatedStateAndClearTokens()},k.prototype._privateDataHandlerMap={"#publish":function(t){let e=this._undecorateChannelName(t.channel);this.isSubscribed(e,!0)&&this._channelDataDemux.write(e,t.data)},"#kickOut":function(t){let e=this._undecorateChannelName(t.channel),r=this._channelMap[e];r&&(this.emit("kickOut",{channel:e,message:t.message}),this._channelEventDemux.write(`${e}/kickOut`,{message:t.message}),this._triggerChannelUnsubscribe(r))},"#setAuthToken":function(t){t&&this._setAuthToken(t)},"#removeAuthToken":function(t){this._removeAuthToken(t)}},k.prototype._privateRPCHandlerMap={"#setAuthToken":function(t,e){t?(this._setAuthToken(t),e.end()):e.error(new y("No token data provided by #setAuthToken event"))},"#removeAuthToken":function(t,e){this._removeAuthToken(t),e.end()}},k.prototype.getState=function(){return this.state},k.prototype.getBytesReceived=function(){return this.transport.getBytesReceived()},k.prototype.deauthenticate=async function(){(async()=>{let t;try{t=await this.auth.removeToken(this.authTokenName)}catch(t){return void this._onError(t)}this.emit("removeAuthToken",{oldAuthToken:t})})(),this.state!==this.CLOSED&&this.transmit("#removeAuthToken"),this._changeToUnauthenticatedStateAndClearTokens(),await f(0)},k.prototype.connect=function(){this.state===this.CLOSED&&(this.pendingReconnect=!1,this.pendingReconnectTimeout=null,clearTimeout(this._reconnectTimeoutRef),this.state=this.CONNECTING,this.emit("connecting",{}),this.transport&&this.transport.clearAllListeners(),this.transport=new u(this.auth,this.codec,this.options,this.wsOptions,{onOpen:t=>{this.state=this.OPEN,this._onOpen(t)},onOpenAbort:t=>{this.state!==this.CLOSED&&(this.state=this.CLOSED,this._destroy(t.code,t.reason,!0))},onClose:t=>{this.state!==this.CLOSED&&(this.state=this.CLOSED,this._destroy(t.code,t.reason))},onEvent:t=>{this.emit(t.event,t.data)},onError:t=>{this._onError(t.error)},onInboundInvoke:t=>{this._onInboundInvoke(t)},onInboundTransmit:t=>{this._onInboundTransmit(t.event,t.data)}}))},k.prototype.reconnect=function(t,e){this.disconnect(t,e),this.connect()},k.prototype.disconnect=function(t,e){if("number"!=typeof(t=t||1e3))throw new m("If specified, the code argument must be a number");let r=this.state===this.CONNECTING;r||this.state===this.OPEN?(this.state=this.CLOSED,this._destroy(t,e,r),this.transport.close(t,e)):(this.pendingReconnect=!1,this.pendingReconnectTimeout=null,clearTimeout(this._reconnectTimeoutRef))},k.prototype._changeToUnauthenticatedStateAndClearTokens=function(){if(this.authState!==this.UNAUTHENTICATED){let t=this.authState,e=this.authToken,r=this.signedAuthToken;this.authState=this.UNAUTHENTICATED,this.signedAuthToken=null,this.authToken=null;let n={oldAuthState:t,newAuthState:this.authState};this.emit("authStateChange",n),this.emit("deauthenticate",{oldSignedAuthToken:r,oldAuthToken:e})}},k.prototype._changeToAuthenticatedState=function(t){if(this.signedAuthToken=t,this.authToken=this._extractAuthTokenData(t),this.authState!==this.AUTHENTICATED){let e=this.authState;this.authState=this.AUTHENTICATED;let r={oldAuthState:e,newAuthState:this.authState,signedAuthToken:t,authToken:this.authToken};this.preparingPendingSubscriptions||this.processPendingSubscriptions(),this.emit("authStateChange",r)}this.emit("authenticate",{signedAuthToken:t,authToken:this.authToken})},k.prototype.decodeBase64=function(t){return p.from(t,"base64").toString("utf8")},k.prototype.encodeBase64=function(t){return p.from(t,"utf8").toString("base64")},k.prototype._extractAuthTokenData=function(t){let e=(t||"").split(".")[1];if(null!=e){let t=e;try{return t=this.decodeBase64(t),JSON.parse(t)}catch(e){return t}}return null},k.prototype.getAuthToken=function(){return this.authToken},k.prototype.getSignedAuthToken=function(){return this.signedAuthToken},k.prototype.authenticate=async function(t){let e;try{e=await this.invoke("#authenticate",t)}catch(t){throw"BadConnectionError"!==t.name&&"TimeoutError"!==t.name&&this._changeToUnauthenticatedStateAndClearTokens(),await f(0),t}return e&&null!=e.isAuthenticated?e.authError&&(e.authError=d.hydrateError(e.authError)):e={isAuthenticated:this.authState,authError:null},e.isAuthenticated?this._changeToAuthenticatedState(t):this._changeToUnauthenticatedStateAndClearTokens(),(async()=>{try{await this.auth.saveToken(this.authTokenName,t,{})}catch(t){this._onError(t)}})(),await f(0),e},k.prototype._tryReconnect=function(t){let e,r=this.connectAttempts++,n=this.options.autoReconnectOptions;if(null==t||r>0){let t=Math.round(n.initialDelay+(n.randomness||0)*Math.random());e=Math.round(t*Math.pow(n.multiplier,r))}else e=t;e>n.maxDelay&&(e=n.maxDelay),clearTimeout(this._reconnectTimeoutRef),this.pendingReconnect=!0,this.pendingReconnectTimeout=e,this._reconnectTimeoutRef=setTimeout(()=>{this.connect()},e)},k.prototype._onOpen=function(t){this.isBatching?this._startBatching():this.batchOnHandshake&&(this._startBatching(),setTimeout(()=>{this.isBatching||this._stopBatching()},this.batchOnHandshakeDuration)),this.preparingPendingSubscriptions=!0,t?(this.id=t.id,this.pingTimeout=t.pingTimeout,t.isAuthenticated?this._changeToAuthenticatedState(t.authToken):this._changeToUnauthenticatedStateAndClearTokens()):this._changeToUnauthenticatedStateAndClearTokens(),this.connectAttempts=0,this.options.autoSubscribeOnConnect&&this.processPendingSubscriptions(),this.emit("connect",{...t,processPendingSubscriptions:()=>{this.processPendingSubscriptions()}}),this.state===this.OPEN&&this._flushOutboundBuffer()},k.prototype._onError=function(t){this.emit("error",{error:t})},k.prototype._suspendSubscriptions=function(){Object.keys(this._channelMap).forEach(t=>{let e=this._channelMap[t];this._triggerChannelUnsubscribe(e,!0)})},k.prototype._abortAllPendingEventsDueToBadConnection=function(t){let e,r=this._outboundBuffer.head;for(;r;){e=r.next;let n=r.data;clearTimeout(n.timeout),delete n.timeout,r.detach(),r=e;let o=n.callback;if(o){delete n.callback;let e=`Event "${n.event}" was aborted due to a bad connection`,r=new v(e,t);o.call(n,r,n)}n.cid&&this.transport.cancelPendingResponse(n.cid)}},k.prototype._destroy=function(t,e,r){if(this.id=null,this._cancelBatching(),this.transport&&this.transport.clearAllListeners(),this.pendingReconnect=!1,this.pendingReconnectTimeout=null,clearTimeout(this._reconnectTimeoutRef),this._suspendSubscriptions(),r?this.emit("connectAbort",{code:t,reason:e}):this.emit("disconnect",{code:t,reason:e}),this.emit("close",{code:t,reason:e}),!k.ignoreStatuses[t]){let r;r=e?"Socket connection closed with status code "+t+" and reason: "+e:"Socket connection closed with status code "+t;let n=new g(k.errorStatuses[t]||r,t);this._onError(n)}this._abortAllPendingEventsDueToBadConnection(r?"connectAbort":"disconnect"),this.options.autoReconnect&&(4e3===t||4001===t||1005===t?this._tryReconnect(0):1e3!==t&&t<4500&&this._tryReconnect())},k.prototype._onInboundTransmit=function(t,e){let r=this._privateDataHandlerMap[t];r?r.call(this,e):this._receiverDemux.write(t,e)},k.prototype._onInboundInvoke=function(t){let{procedure:e,data:r}=t,n=this._privateRPCHandlerMap[e];n?n.call(this,r,t):this._procedureDemux.write(e,t)},k.prototype.decode=function(t){return this.transport.decode(t)},k.prototype.encode=function(t){return this.transport.encode(t)},k.prototype._flushOutboundBuffer=function(){let t,e=this._outboundBuffer.head;for(;e;){t=e.next;let r=e.data;e.detach(),this.transport.transmitObject(r),e=t}},k.prototype._handleEventAckTimeout=function(t,e){e&&e.detach(),delete t.timeout;let r=t.callback;if(r){delete t.callback;let e=new b(`Event response for "${t.event}" timed out`);r.call(t,e,t)}t.cid&&this.transport.cancelPendingResponse(t.cid)},k.prototype._processOutboundEvent=function(t,e,r,n){r=r||{},this.state===this.CLOSED&&this.connect();let o,i={event:t};o=n?new Promise((t,e)=>{i.callback=((r,n)=>{r?e(r):t(n)})}):Promise.resolve();let s=new h.Item;this.options.cloneData?i.data=l(e):i.data=e,s.data=i;let a=null==r.ackTimeout?this.ackTimeout:r.ackTimeout;return i.timeout=setTimeout(()=>{this._handleEventAckTimeout(i,s)},a),this._outboundBuffer.append(s),this.state===this.OPEN&&this._flushOutboundBuffer(),o},k.prototype.send=function(t){this.transport.send(t)},k.prototype.transmit=function(t,e,r){return this._processOutboundEvent(t,e,r)},k.prototype.invoke=function(t,e,r){return this._processOutboundEvent(t,e,r,!0)},k.prototype.transmitPublish=function(t,e){let r={channel:this._decorateChannelName(t),data:e};return this.transmit("#publish",r)},k.prototype.invokePublish=function(t,e){let r={channel:this._decorateChannelName(t),data:e};return this.invoke("#publish",r)},k.prototype._triggerChannelSubscribe=function(t,e){let r=t.name;if(t.state!==i.SUBSCRIBED){let n=t.state;t.state=i.SUBSCRIBED;let o={oldChannelState:n,newChannelState:t.state,subscriptionOptions:e};this._channelEventDemux.write(`${r}/subscribeStateChange`,o),this._channelEventDemux.write(`${r}/subscribe`,{subscriptionOptions:e}),this.emit("subscribeStateChange",{channel:r,...o}),this.emit("subscribe",{channel:r,subscriptionOptions:e})}},k.prototype._triggerChannelSubscribeFail=function(t,e,r){let n=e.name,o=!e.options.waitForAuth||this.authState===this.AUTHENTICATED;!!this._channelMap[n]&&o&&(delete this._channelMap[n],this._channelEventDemux.write(`${n}/subscribeFail`,{error:t,subscriptionOptions:r}),this.emit("subscribeFail",{error:t,channel:n,subscriptionOptions:r}))},k.prototype._cancelPendingSubscribeCallback=function(t){null!=t._pendingSubscriptionCid&&(this.transport.cancelPendingResponse(t._pendingSubscriptionCid),delete t._pendingSubscriptionCid)},k.prototype._decorateChannelName=function(t){return this.channelPrefix&&(t=this.channelPrefix+t),t},k.prototype._undecorateChannelName=function(t){return this.channelPrefix&&0===t.indexOf(this.channelPrefix)?t.replace(this.channelPrefix,""):t},k.prototype.startBatch=function(){this.transport.startBatch()},k.prototype.flushBatch=function(){this.transport.flushBatch()},k.prototype.cancelBatch=function(){this.transport.cancelBatch()},k.prototype._startBatching=function(){null==this._batchingIntervalId&&(this.startBatch(),this._batchingIntervalId=setInterval(()=>{this.flushBatch(),this.startBatch()},this.options.batchInterval))},k.prototype.startBatching=function(){this.isBatching=!0,this._startBatching()},k.prototype._stopBatching=function(){null!=this._batchingIntervalId&&clearInterval(this._batchingIntervalId),this._batchingIntervalId=null,this.flushBatch()},k.prototype.stopBatching=function(){this.isBatching=!1,this._stopBatching()},k.prototype._cancelBatching=function(){null!=this._batchingIntervalId&&clearInterval(this._batchingIntervalId),this._batchingIntervalId=null,this.cancelBatch()},k.prototype.cancelBatching=function(){this.isBatching=!1,this._cancelBatching()},k.prototype._trySubscribe=function(t){let e=!t.options.waitForAuth||this.authState===this.AUTHENTICATED;if(this.state===this.OPEN&&!this.preparingPendingSubscriptions&&null==t._pendingSubscriptionCid&&e){let e={noTimeout:!0},r={};t.options.waitForAuth&&(e.waitForAuth=!0,r.waitForAuth=e.waitForAuth),t.options.data&&(r.data=t.options.data),t._pendingSubscriptionCid=this.transport.invokeRaw("#subscribe",{channel:this._decorateChannelName(t.name),...r},e,e=>{if(e){if("BadConnectionError"===e.name)return;delete t._pendingSubscriptionCid,this._triggerChannelSubscribeFail(e,t,r)}else delete t._pendingSubscriptionCid,this._triggerChannelSubscribe(t,r)}),this.emit("subscribeRequest",{channel:t.name,subscriptionOptions:r})}},k.prototype.subscribe=function(t,e){e=e||{};let r=this._channelMap[t],n={waitForAuth:!!e.waitForAuth};return null!=e.priority&&(n.priority=e.priority),void 0!==e.data&&(n.data=e.data),r?e&&(r.options=n):(r={name:t,state:i.PENDING,options:n},this._channelMap[t]=r,this._trySubscribe(r)),new i(t,this,this._channelEventDemux,this._channelDataDemux)},k.prototype._triggerChannelUnsubscribe=function(t,e){let r=t.name;if(this._cancelPendingSubscribeCallback(t),t.state===i.SUBSCRIBED){let n={oldChannelState:t.state,newChannelState:e?i.PENDING:i.UNSUBSCRIBED};this._channelEventDemux.write(`${r}/subscribeStateChange`,n),this._channelEventDemux.write(`${r}/unsubscribe`,{}),this.emit("subscribeStateChange",{channel:r,...n}),this.emit("unsubscribe",{channel:r})}e?t.state=i.PENDING:delete this._channelMap[r]},k.prototype._tryUnsubscribe=function(t){if(this.state===this.OPEN){this._cancelPendingSubscribeCallback(t);let e=this._decorateChannelName(t.name);this.transport.transmit("#unsubscribe",e,{noTimeout:!0})}},k.prototype.unsubscribe=function(t){let e=this._channelMap[t];e&&(this._triggerChannelUnsubscribe(e),this._tryUnsubscribe(e))},k.prototype.receiver=function(t){return this._receiverDemux.stream(t)},k.prototype.closeReceiver=function(t){this._receiverDemux.close(t)},k.prototype.closeAllReceivers=function(){this._receiverDemux.closeAll()},k.prototype.killReceiver=function(t){this._receiverDemux.kill(t)},k.prototype.killAllReceivers=function(){this._receiverDemux.killAll()},k.prototype.killReceiverConsumer=function(t){this._receiverDemux.killConsumer(t)},k.prototype.getReceiverConsumerStats=function(t){return this._receiverDemux.getConsumerStats(t)},k.prototype.getReceiverConsumerStatsList=function(t){return this._receiverDemux.getConsumerStatsList(t)},k.prototype.getAllReceiversConsumerStatsList=function(){return this._receiverDemux.getConsumerStatsListAll()},k.prototype.getReceiverBackpressure=function(t){return this._receiverDemux.getBackpressure(t)},k.prototype.getAllReceiversBackpressure=function(){return this._receiverDemux.getBackpressureAll()},k.prototype.getReceiverConsumerBackpressure=function(t){return this._receiverDemux.getConsumerBackpressure(t)},k.prototype.hasReceiverConsumer=function(t,e){return this._receiverDemux.hasConsumer(t,e)},k.prototype.hasAnyReceiverConsumer=function(t){return this._receiverDemux.hasConsumerAll(t)},k.prototype.procedure=function(t){return this._procedureDemux.stream(t)},k.prototype.closeProcedure=function(t){this._procedureDemux.close(t)},k.prototype.closeAllProcedures=function(){this._procedureDemux.closeAll()},k.prototype.killProcedure=function(t){this._procedureDemux.kill(t)},k.prototype.killAllProcedures=function(){this._procedureDemux.killAll()},k.prototype.killProcedureConsumer=function(t){this._procedureDemux.killConsumer(t)},k.prototype.getProcedureConsumerStats=function(t){return this._procedureDemux.getConsumerStats(t)},k.prototype.getProcedureConsumerStatsList=function(t){return this._procedureDemux.getConsumerStatsList(t)},k.prototype.getAllProceduresConsumerStatsList=function(){return this._procedureDemux.getConsumerStatsListAll()},k.prototype.getProcedureBackpressure=function(t){return this._procedureDemux.getBackpressure(t)},k.prototype.getAllProceduresBackpressure=function(){return this._procedureDemux.getBackpressureAll()},k.prototype.getProcedureConsumerBackpressure=function(t){return this._procedureDemux.getConsumerBackpressure(t)},k.prototype.hasProcedureConsumer=function(t,e){return this._procedureDemux.hasConsumer(t,e)},k.prototype.hasAnyProcedureConsumer=function(t){return this._procedureDemux.hasConsumerAll(t)},k.prototype.channel=function(t){return this._channelMap[t],new i(t,this,this._channelEventDemux,this._channelDataDemux)},k.prototype.closeChannel=function(t){this.channelCloseOutput(t),this.channelCloseAllListeners(t)},k.prototype.closeAllChannelOutputs=function(){this._channelDataDemux.closeAll()},k.prototype.closeAllChannelListeners=function(){this._channelEventDemux.closeAll()},k.prototype.closeAllChannels=function(){this.closeAllChannelOutputs(),this.closeAllChannelListeners()},k.prototype.killChannel=function(t){this.channelKillOutput(t),this.channelKillAllListeners(t)},k.prototype.killAllChannelOutputs=function(){this._channelDataDemux.killAll()},k.prototype.killAllChannelListeners=function(){this._channelEventDemux.killAll()},k.prototype.killAllChannels=function(){this.killAllChannelOutputs(),this.killAllChannelListeners()},k.prototype.killChannelOutputConsumer=function(t){this._channelDataDemux.killConsumer(t)},k.prototype.killChannelListenerConsumer=function(t){this._channelEventDemux.killConsumer(t)},k.prototype.getChannelOutputConsumerStats=function(t){return this._channelDataDemux.getConsumerStats(t)},k.prototype.getChannelListenerConsumerStats=function(t){return this._channelEventDemux.getConsumerStats(t)},k.prototype.getAllChannelOutputsConsumerStatsList=function(){return this._channelDataDemux.getConsumerStatsListAll()},k.prototype.getAllChannelListenersConsumerStatsList=function(){return this._channelEventDemux.getConsumerStatsListAll()},k.prototype.getChannelBackpressure=function(t){return Math.max(this.channelGetOutputBackpressure(t),this.channelGetAllListenersBackpressure(t))},k.prototype.getAllChannelOutputsBackpressure=function(){return this._channelDataDemux.getBackpressureAll()},k.prototype.getAllChannelListenersBackpressure=function(){return this._channelEventDemux.getBackpressureAll()},k.prototype.getAllChannelsBackpressure=function(){return Math.max(this.getAllChannelOutputsBackpressure(),this.getAllChannelListenersBackpressure())},k.prototype.getChannelListenerConsumerBackpressure=function(t){return this._channelEventDemux.getConsumerBackpressure(t)},k.prototype.getChannelOutputConsumerBackpressure=function(t){return this._channelDataDemux.getConsumerBackpressure(t)},k.prototype.hasAnyChannelOutputConsumer=function(t){return this._channelDataDemux.hasConsumerAll(t)},k.prototype.hasAnyChannelListenerConsumer=function(t){return this._channelEventDemux.hasConsumerAll(t)},k.prototype.getChannelState=function(t){let e=this._channelMap[t];return e?e.state:i.UNSUBSCRIBED},k.prototype.getChannelOptions=function(t){let e=this._channelMap[t];return e?{...e.options}:{}},k.prototype._getAllChannelStreamNames=function(t){let e=this._channelEventDemux.getConsumerStatsListAll().filter(e=>0===e.stream.indexOf(`${t}/`)).reduce((t,e)=>(t[e.stream]=!0,t),{});return Object.keys(e)},k.prototype.channelCloseOutput=function(t){this._channelDataDemux.close(t)},k.prototype.channelCloseListener=function(t,e){this._channelEventDemux.close(`${t}/${e}`)},k.prototype.channelCloseAllListeners=function(t){this._getAllChannelStreamNames(t).forEach(t=>{this._channelEventDemux.close(t)})},k.prototype.channelKillOutput=function(t){this._channelDataDemux.kill(t)},k.prototype.channelKillListener=function(t,e){this._channelEventDemux.kill(`${t}/${e}`)},k.prototype.channelKillAllListeners=function(t){this._getAllChannelStreamNames(t).forEach(t=>{this._channelEventDemux.kill(t)})},k.prototype.channelGetOutputConsumerStatsList=function(t){return this._channelDataDemux.getConsumerStatsList(t)},k.prototype.channelGetListenerConsumerStatsList=function(t,e){return this._channelEventDemux.getConsumerStatsList(`${t}/${e}`)},k.prototype.channelGetAllListenersConsumerStatsList=function(t){return this._getAllChannelStreamNames(t).map(t=>this._channelEventDemux.getConsumerStatsList(t)).reduce((t,e)=>(e.forEach(e=>{t.push(e)}),t),[])},k.prototype.channelGetOutputBackpressure=function(t){return this._channelDataDemux.getBackpressure(t)},k.prototype.channelGetListenerBackpressure=function(t,e){return this._channelEventDemux.getBackpressure(`${t}/${e}`)},k.prototype.channelGetAllListenersBackpressure=function(t){let e=this._getAllChannelStreamNames(t).map(t=>this._channelEventDemux.getBackpressure(t));return Math.max(...e.concat(0))},k.prototype.channelHasOutputConsumer=function(t,e){return this._channelDataDemux.hasConsumer(t,e)},k.prototype.channelHasListenerConsumer=function(t,e,r){return this._channelEventDemux.hasConsumer(`${t}/${e}`,r)},k.prototype.channelHasAnyListenerConsumer=function(t,e){return this._getAllChannelStreamNames(t).some(t=>this._channelEventDemux.hasConsumer(t,e))},k.prototype.subscriptions=function(t){let e=[];return Object.keys(this._channelMap).forEach(r=>{(t||this._channelMap[r].state===i.SUBSCRIBED)&&e.push(r)}),e},k.prototype.isSubscribed=function(t,e){let r=this._channelMap[t];return e?!!r:!!r&&r.state===i.SUBSCRIBED},k.prototype.processPendingSubscriptions=function(){this.preparingPendingSubscriptions=!1;let t=[];Object.keys(this._channelMap).forEach(e=>{let r=this._channelMap[e];r.state===i.PENDING&&t.push(r)}),t.sort((t,e)=>{let r=t.options.priority||0,n=e.options.priority||0;return r>n?-1:r{this._trySubscribe(t)})},e.exports=k}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./auth":1,"./transport":4,"./wait":5,"ag-channel":7,"async-stream-emitter":9,"buffer/":11,"linked-list":15,"lodash.clonedeep":16,querystring:19,"sc-errors":21,"sc-formatter":22,"stream-demux":24}],3:[function(t,e){(function(r){const n=t("./clientsocket"),o=t("uuid"),i=t("sc-errors").InvalidArgumentsError;e.exports={create:function(t){if((t=t||{}).host&&!t.host.match(/[^:]+:\d{2,5}/))throw new i('The host option should include both the hostname and the port number in the format "hostname:port"');if(t.host&&t.hostname)throw new i('The host option should already include the hostname and the port number in the format "hostname:port" - Because of this, you should never use host and hostname options together');if(t.host&&t.port)throw new i('The host option should already include the hostname and the port number in the format "hostname:port" - Because of this, you should never use host and port options together');let e=r.location&&"https:"===location.protocol,s={clientId:o.v4(),port:function(t,e){let n=null==t.secure?e:t.secure;return t.port||(r.location&&location.port?location.port:n?443:80)}(t,e),hostname:r.location&&location.hostname||"localhost",secure:e};return Object.assign(s,t),new n(s)}}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"./clientsocket":2,"sc-errors":21,uuid:25}],4:[function(t,e){(function(r){const n=t("ag-request"),o=t("querystring");let i,s;r.WebSocket?(i=r.WebSocket,s=function(t){return new i(t)}):(i=t("ws"),s=function(t,e){return new i(t,null,e)});const a=t("sc-errors"),u=a.TimeoutError,c=a.BadConnectionError;function h(t,e,r,n,o){this.state=this.CLOSED,this.auth=t,this.codec=e,this.options=r,this.wsOptions=n,this.protocolVersion=r.protocolVersion,this.connectTimeout=r.connectTimeout,this.pingTimeout=r.pingTimeout,this.pingTimeoutDisabled=!!r.pingTimeoutDisabled,this.callIdGenerator=r.callIdGenerator,this.authTokenName=r.authTokenName,this.isBufferingBatch=!1,this._pingTimeoutTicker=null,this._callbackMap={},this._batchBuffer=[],o||(o={}),this._onOpenHandler=o.onOpen||function(){},this._onOpenAbortHandler=o.onOpenAbort||function(){},this._onCloseHandler=o.onClose||function(){},this._onEventHandler=o.onEvent||function(){},this._onErrorHandler=o.onError||function(){},this._onInboundInvokeHandler=o.onInboundInvoke||function(){},this._onInboundTransmitHandler=o.onInboundTransmit||function(){},this.state=this.CONNECTING;let i=this.uri(),a=s(i,n);a.binaryType=this.options.binaryType,this.socket=a,a.onopen=(()=>{this._onOpen()}),a.onclose=(async t=>{let e;e=null==t.code?1005:t.code,this._destroy(e,t.reason)}),a.onmessage=(t=>{this._onMessage(t.data)}),a.onerror=(()=>{this.state===this.CONNECTING&&this._destroy(1006)}),this._connectTimeoutRef=setTimeout(()=>{this._destroy(4007),this.socket.close(4007)},this.connectTimeout),1===this.protocolVersion?this._handlePing=(t=>"#1"===t&&(this._resetPingTimeout(),this.socket.readyState===this.socket.OPEN&&this.send("#2"),!0)):this._handlePing=(t=>""===t&&(this._resetPingTimeout(),this.socket.readyState===this.socket.OPEN&&this.send(""),!0))}h.CONNECTING=h.prototype.CONNECTING="connecting",h.OPEN=h.prototype.OPEN="open",h.CLOSED=h.prototype.CLOSED="closed",h.prototype.uri=function(){let t,e=this.options.query||{},r=this.options.secure?"wss":"ws";if(this.options.timestampRequests&&(e[this.options.timestampParam]=(new Date).getTime()),(e=o.encode(e)).length&&(e="?"+e),this.options.host)t=this.options.host;else{let e="";this.options.port&&("wss"===r&&443!==this.options.port||"ws"===r&&80!==this.options.port)&&(e=":"+this.options.port),t=this.options.hostname+e}return r+"://"+t+this.options.path+e},h.prototype._onOpen=async function(){let t;clearTimeout(this._connectTimeoutRef),this._resetPingTimeout();try{t=await this._handshake()}catch(t){return null==t.statusCode&&(t.statusCode=4003),this._onError(t),this._destroy(t.statusCode,t.toString()),void this.socket.close(t.statusCode)}this.state=this.OPEN,t&&(this.pingTimeout=t.pingTimeout),this._resetPingTimeout(),this._onOpenHandler(t)},h.prototype._handshake=async function(){let t=await this.auth.loadToken(this.authTokenName),e=await this.invoke("#handshake",{authToken:t},{force:!0});return e&&(e.authToken=t,e.authError&&(e.authError=a.hydrateError(e.authError))),e},h.prototype._abortAllPendingEventsDueToBadConnection=function(t){Object.keys(this._callbackMap||{}).forEach(e=>{let r=this._callbackMap[e];delete this._callbackMap[e],clearTimeout(r.timeout),delete r.timeout;let n=`Event "${r.event}" was aborted due to a bad connection`,o=new c(n,t),i=r.callback;delete r.callback,i.call(r,o,r)})},h.prototype._destroy=function(t,e){a.socketProtocolErrorStatuses[t],!e&&a.socketProtocolErrorStatuses[t]&&(e=a.socketProtocolErrorStatuses[t]),delete this.socket.onopen,delete this.socket.onclose,delete this.socket.onmessage,delete this.socket.onerror,clearTimeout(this._connectTimeoutRef),clearTimeout(this._pingTimeoutTicker),this.state===this.OPEN?(this.state=this.CLOSED,this._abortAllPendingEventsDueToBadConnection("disconnect"),this._onCloseHandler({code:t,reason:e})):this.state===this.CONNECTING?(this.state=this.CLOSED,this._abortAllPendingEventsDueToBadConnection("connectAbort"),this._onOpenAbortHandler({code:t,reason:e})):this.state===this.CLOSED&&this._abortAllPendingEventsDueToBadConnection("connectAbort")},h.prototype._processInboundPacket=function(t,e){if(t&&null!=t.event)if(null==t.cid)this._onInboundTransmitHandler({...t});else{let e=new n(this,t.cid,t.event,t.data);this._onInboundInvokeHandler(e)}else if(t&&null!=t.rid){let e=this._callbackMap[t.rid];if(e&&(clearTimeout(e.timeout),delete e.timeout,delete this._callbackMap[t.rid],e.callback)){let r=a.hydrateError(t.error);e.callback(r,t.data)}}else this._onEventHandler({event:"raw",data:{message:e}})},h.prototype._onMessage=function(t){if(this._onEventHandler({event:"message",data:{message:t}}),this._handlePing(t))return;let e=this.decode(t);if(Array.isArray(e)){let r=e.length;for(let n=0;n{this._destroy(4e3),this.socket.close(4e3)},this.pingTimeout))},h.prototype.clearAllListeners=function(){this._onOpenHandler=function(){},this._onOpenAbortHandler=function(){},this._onCloseHandler=function(){},this._onEventHandler=function(){},this._onErrorHandler=function(){},this._onInboundInvokeHandler=function(){},this._onInboundTransmitHandler=function(){}},h.prototype.startBatch=function(){this.isBufferingBatch=!0,this._batchBuffer=[]},h.prototype.flushBatch=function(){if(this.isBufferingBatch=!1,!this._batchBuffer.length)return;let t=this.serializeObject(this._batchBuffer);this._batchBuffer=[],this.send(t)},h.prototype.cancelBatch=function(){this.isBufferingBatch=!1,this._batchBuffer=[]},h.prototype.getBytesReceived=function(){return this.socket.bytesReceived},h.prototype.close=function(t,e){this.state!==this.OPEN&&this.state!==this.CONNECTING||(t=t||1e3,this._destroy(t,e),this.socket.close(t,e))},h.prototype.transmitObject=function(t){let e={event:t.event,data:t.data};return t.callback&&(e.cid=t.cid=this.callIdGenerator(),this._callbackMap[t.cid]=t),this.sendObject(e),t.cid||null},h.prototype._handleEventAckTimeout=function(t){t.cid&&delete this._callbackMap[t.cid],delete t.timeout;let e=t.callback;if(e){delete t.callback;let r=new u(`Event response for "${t.event}" timed out`);e.call(t,r,t)}},h.prototype.transmit=function(t,e,r){return(this.state===this.OPEN||r.force)&&this.transmitObject({event:t,data:e}),Promise.resolve()},h.prototype.invokeRaw=function(t,e,r,n){let o={event:t,data:e,callback:n};r.noTimeout||(o.timeout=setTimeout(()=>{this._handleEventAckTimeout(o)},this.options.ackTimeout));let i=null;return(this.state===this.OPEN||r.force)&&(i=this.transmitObject(o)),i},h.prototype.invoke=function(t,e,r){return new Promise((n,o)=>{this.invokeRaw(t,e,r,(t,e)=>{t?o(t):n(e)})})},h.prototype.cancelPendingResponse=function(t){delete this._callbackMap[t]},h.prototype.decode=function(t){return this.codec.decode(t)},h.prototype.encode=function(t){return this.codec.encode(t)},h.prototype.send=function(t){this.socket.readyState!==this.socket.OPEN?this._destroy(1005):this.socket.send(t)},h.prototype.serializeObject=function(t){let e;try{e=this.encode(t)}catch(t){return this._onError(t),null}return e},h.prototype.sendObject=function(t){if(this.isBufferingBatch)return void this._batchBuffer.push(t);let e=this.serializeObject(t);null!=e&&this.send(e)},e.exports=h}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"ag-request":8,querystring:19,"sc-errors":21,ws:6}],5:[function(t,e){e.exports=function(t){return new Promise(e=>{setTimeout(()=>{e()},t)})}},{}],6:[function(t,e){let r;const n=(r="undefined"!=typeof WorkerGlobalScope?self:"undefined"!=typeof window&&window||function(){return this}()).WebSocket||r.MozWebSocket;function o(t,e){let r;return r=e?new n(t,e):new n(t)}n&&(o.prototype=n.prototype),e.exports=n?o:null},{}],7:[function(t,e){const r=t("consumable-stream");class n extends r{constructor(t,e,r,o){super(),this.PENDING=n.PENDING,this.SUBSCRIBED=n.SUBSCRIBED,this.UNSUBSCRIBED=n.UNSUBSCRIBED,this.name=t,this.client=e,this._eventDemux=r,this._dataStream=o.stream(this.name)}createConsumer(t){return this._dataStream.createConsumer(t)}listener(t){return this._eventDemux.stream(`${this.name}/${t}`)}close(){this.client.closeChannel(this.name)}kill(){this.client.killChannel(this.name)}killOutputConsumer(t){this.hasOutputConsumer(t)&&this.client.killChannelOutputConsumer(t)}killListenerConsumer(t){this.hasAnyListenerConsumer(t)&&this.client.killChannelListenerConsumer(t)}getOutputConsumerStats(t){if(this.hasOutputConsumer(t))return this.client.getChannelOutputConsumerStats(t)}getListenerConsumerStats(t){if(this.hasAnyListenerConsumer(t))return this.client.getChannelListenerConsumerStats(t)}getBackpressure(){return this.client.getChannelBackpressure(this.name)}getListenerConsumerBackpressure(t){return this.hasAnyListenerConsumer(t)?this.client.getChannelListenerConsumerBackpressure(t):0}getOutputConsumerBackpressure(t){return this.hasOutputConsumer(t)?this.client.getChannelOutputConsumerBackpressure(t):0}closeOutput(){this.client.channelCloseOutput(this.name)}closeListener(t){this.client.channelCloseListener(this.name,t)}closeAllListeners(){this.client.channelCloseAllListeners(this.name)}killOutput(){this.client.channelKillOutput(this.name)}killListener(t){this.client.channelKillListener(this.name,t)}killAllListeners(){this.client.channelKillAllListeners(this.name)}getOutputConsumerStatsList(){return this.client.channelGetOutputConsumerStatsList(this.name)}getListenerConsumerStatsList(t){return this.client.channelGetListenerConsumerStatsList(this.name,t)}getAllListenersConsumerStatsList(){return this.client.channelGetAllListenersConsumerStatsList(this.name)}getOutputBackpressure(){return this.client.channelGetOutputBackpressure(this.name)}getListenerBackpressure(t){return this.client.channelGetListenerBackpressure(this.name,t)}getAllListenersBackpressure(){return this.client.channelGetAllListenersBackpressure(this.name)}hasOutputConsumer(t){return this.client.channelHasOutputConsumer(this.name,t)}hasListenerConsumer(t,e){return this.client.channelHasListenerConsumer(this.name,t,e)}hasAnyListenerConsumer(t){return this.client.channelHasAnyListenerConsumer(this.name,t)}get state(){return this.client.getChannelState(this.name)}set state(t){throw new Error("Cannot directly set channel state")}get options(){return this.client.getChannelOptions(this.name)}set options(t){throw new Error("Cannot directly set channel options")}subscribe(t){this.client.subscribe(this.name,t)}unsubscribe(){this.client.unsubscribe(this.name)}isSubscribed(t){return this.client.isSubscribed(this.name,t)}transmitPublish(t){return this.client.transmitPublish(this.name,t)}invokePublish(t){return this.client.invokePublish(this.name,t)}}n.PENDING="pending",n.SUBSCRIBED="subscribed",n.UNSUBSCRIBED="unsubscribed",e.exports=n},{"consumable-stream":12}],8:[function(t,e){const r=t("sc-errors"),n=r.InvalidActionError;e.exports=function(t,e,o,i){this.socket=t,this.id=e,this.procedure=o,this.data=i,this.sent=!1,this._respond=((t,e)=>{if(this.sent)throw new n(`Response to request ${this.id} has already been sent`);this.sent=!0,this.socket.sendObject(t,e)}),this.end=((t,e)=>{let r={rid:this.id};void 0!==t&&(r.data=t),this._respond(r,e)}),this.error=((t,e)=>{let n={rid:this.id,error:r.dehydrateError(t)};this._respond(n,e)})}},{"sc-errors":21}],9:[function(t,e){const r=t("stream-demux");function n(){this._listenerDemux=new r}n.prototype.emit=function(t,e){this._listenerDemux.write(t,e)},n.prototype.listener=function(t){return this._listenerDemux.stream(t)},n.prototype.closeListener=function(t){this._listenerDemux.close(t)},n.prototype.closeAllListeners=function(){this._listenerDemux.closeAll()},n.prototype.getListenerConsumerStats=function(t){return this._listenerDemux.getConsumerStats(t)},n.prototype.getListenerConsumerStatsList=function(t){return this._listenerDemux.getConsumerStatsList(t)},n.prototype.getAllListenersConsumerStatsList=function(){return this._listenerDemux.getConsumerStatsListAll()},n.prototype.killListener=function(t){this._listenerDemux.kill(t)},n.prototype.killAllListeners=function(){this._listenerDemux.killAll()},n.prototype.killListenerConsumer=function(t){this._listenerDemux.killConsumer(t)},n.prototype.getListenerBackpressure=function(t){return this._listenerDemux.getBackpressure(t)},n.prototype.getAllListenersBackpressure=function(){return this._listenerDemux.getBackpressureAll()},n.prototype.getListenerConsumerBackpressure=function(t){return this._listenerDemux.getConsumerBackpressure(t)},n.prototype.hasListenerConsumer=function(t,e){return this._listenerDemux.hasConsumer(t,e)},n.prototype.hasAnyListenerConsumer=function(t){return this._listenerDemux.hasConsumerAll(t)},e.exports=n},{"stream-demux":24}],10:[function(t,e,r){"use strict";r.byteLength=function(t){var e=c(t),r=e[0],n=e[1];return 3*(r+n)/4-n},r.toByteArray=function(t){var e,r,n=c(t),s=n[0],a=n[1],u=new i(function(t,e,r){return 3*(e+r)/4-r}(0,s,a)),h=0,l=a>0?s-4:s;for(r=0;r>16&255,u[h++]=e>>8&255,u[h++]=255&e;2===a&&(e=o[t.charCodeAt(r)]<<2|o[t.charCodeAt(r+1)]>>4,u[h++]=255&e);1===a&&(e=o[t.charCodeAt(r)]<<10|o[t.charCodeAt(r+1)]<<4|o[t.charCodeAt(r+2)]>>2,u[h++]=e>>8&255,u[h++]=255&e);return u},r.fromByteArray=function(t){for(var e,r=t.length,o=r%3,i=[],s=0,a=r-o;sa?a:s+16383));1===o?(e=t[r-1],i.push(n[e>>2]+n[e<<4&63]+"==")):2===o&&(e=(t[r-2]<<8)+t[r-1],i.push(n[e>>10]+n[e>>4&63]+n[e<<2&63]+"="));return i.join("")};for(var n=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",a=0,u=s.length;a0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function h(t,e,r){for(var o,i,s=[],a=e;a>18&63]+n[i>>12&63]+n[i>>6&63]+n[63&i]);return s.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},{}],11:[function(t,e,r){(function(e){"use strict";var n=t("base64-js"),o=t("ieee754"),i="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;r.Buffer=e,r.SlowBuffer=function(t){+t!=t&&(t=0);return e.alloc(+t)},r.INSPECT_MAX_BYTES=50;var s=2147483647;function a(t){if(t>s)throw new RangeError('The value "'+t+'" is invalid for option "size"');var r=new Uint8Array(t);return Object.setPrototypeOf(r,e.prototype),r}function e(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return h(t)}return u(t,e,r)}function u(t,r,n){if("string"==typeof t)return function(t,r){"string"==typeof r&&""!==r||(r="utf8");if(!e.isEncoding(r))throw new TypeError("Unknown encoding: "+r);var n=0|f(t,r),o=a(n),i=o.write(t,r);i!==n&&(o=o.slice(0,i));return o}(t,r);if(ArrayBuffer.isView(t))return l(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(H(t,ArrayBuffer)||t&&H(t.buffer,ArrayBuffer))return function(t,r,n){if(r<0||t.byteLength=s)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+s.toString(16)+" bytes");return 0|t}function f(t,r){if(e.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||H(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);var n=t.length,o=arguments.length>2&&!0===arguments[2];if(!o&&0===n)return 0;for(var i=!1;;)switch(r){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return U(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return R(t).length;default:if(i)return o?-1:U(t).length;r=(""+r).toLowerCase(),i=!0}}function d(t,e,r){var n=t[e];t[e]=t[r],t[r]=n}function m(t,r,n,o,i){if(0===t.length)return-1;if("string"==typeof n?(o=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),G(n=+n)&&(n=i?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(i)return-1;n=t.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof r&&(r=e.from(r,o)),e.isBuffer(r))return 0===r.length?-1:y(t,r,n,o,i);if("number"==typeof r)return r&=255,"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,r,n):Uint8Array.prototype.lastIndexOf.call(t,r,n):y(t,[r],n,o,i);throw new TypeError("val must be string, number or Buffer")}function y(t,e,r,n,o){var i,s=1,a=t.length,u=e.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||e.length<2)return-1;s=2,a/=2,u/=2,r/=2}function c(t,e){return 1===s?t[e]:t.readUInt16BE(e*s)}if(o){var h=-1;for(i=r;ia&&(r=a-u),i=r;i>=0;i--){for(var l=!0,p=0;po&&(n=o):n=o;var i=e.length;n>i/2&&(n=i/2);for(var s=0;s>8,o=r%256,i.push(o),i.push(n);return i}(e,t.length-r),t,r,n)}function w(t,e,r){return 0===e&&r===t.length?n.fromByteArray(t):n.fromByteArray(t.slice(e,r))}function C(t,e,r){r=Math.min(t.length,r);for(var n=[],o=e;o239?4:c>223?3:c>191?2:1;if(o+l<=r)switch(l){case 1:c<128&&(h=c);break;case 2:128==(192&(i=t[o+1]))&&(u=(31&c)<<6|63&i)>127&&(h=u);break;case 3:i=t[o+1],s=t[o+2],128==(192&i)&&128==(192&s)&&(u=(15&c)<<12|(63&i)<<6|63&s)>2047&&(u<55296||u>57343)&&(h=u);break;case 4:i=t[o+1],s=t[o+2],a=t[o+3],128==(192&i)&&128==(192&s)&&128==(192&a)&&(u=(15&c)<<18|(63&i)<<12|(63&s)<<6|63&a)>65535&&u<1114112&&(h=u)}null===h?(h=65533,l=1):h>65535&&(h-=65536,n.push(h>>>10&1023|55296),h=56320|1023&h),n.push(h),o+=l}return function(t){var e=t.length;if(e<=S)return String.fromCharCode.apply(String,t);var r="",n=0;for(;nthis.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return B(this,e,r);case"utf8":case"utf-8":return C(this,e,r);case"ascii":return T(this,e,r);case"latin1":case"binary":return A(this,e,r);case"base64":return w(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}.apply(this,arguments)},e.prototype.toLocaleString=e.prototype.toString,e.prototype.equals=function(t){if(!e.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===e.compare(this,t)},e.prototype.inspect=function(){var t="",e=r.INSPECT_MAX_BYTES;return t=this.toString("hex",0,e).replace(/(.{2})/g,"$1 ").trim(),this.length>e&&(t+=" ... "),""},i&&(e.prototype[i]=e.prototype.inspect),e.prototype.compare=function(t,r,n,o,i){if(H(t,Uint8Array)&&(t=e.from(t,t.offset,t.byteLength)),!e.isBuffer(t))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof t);if(void 0===r&&(r=0),void 0===n&&(n=t?t.length:0),void 0===o&&(o=0),void 0===i&&(i=this.length),r<0||n>t.length||o<0||i>this.length)throw new RangeError("out of range index");if(o>=i&&r>=n)return 0;if(o>=i)return-1;if(r>=n)return 1;if(r>>>=0,n>>>=0,o>>>=0,i>>>=0,this===t)return 0;for(var s=i-o,a=n-r,u=Math.min(s,a),c=this.slice(o,i),h=t.slice(r,n),l=0;l>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}var o=this.length-e;if((void 0===r||r>o)&&(r=o),t.length>0&&(r<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");for(var i=!1;;)switch(n){case"hex":return g(this,t,e,r);case"utf8":case"utf-8":return b(this,t,e,r);case"ascii":return v(this,t,e,r);case"latin1":case"binary":return _(this,t,e,r);case"base64":return k(this,t,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,t,e,r);default:if(i)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),i=!0}},e.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var S=4096;function T(t,e,r){var n="";r=Math.min(t.length,r);for(var o=e;on)&&(r=n);for(var o="",i=e;ir)throw new RangeError("Trying to access beyond buffer length")}function I(t,r,n,o,i,s){if(!e.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(r>i||rt.length)throw new RangeError("Index out of range")}function D(t,e,r,n){if(r+n>t.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function L(t,e,r,n,i){return e=+e,r>>>=0,i||D(t,0,r,4),o.write(t,e,r,n,23,4),r+4}function N(t,e,r,n,i){return e=+e,r>>>=0,i||D(t,0,r,8),o.write(t,e,r,n,52,8),r+8}e.prototype.slice=function(t,r){var n=this.length;t=~~t,r=void 0===r?n:~~r,t<0?(t+=n)<0&&(t=0):t>n&&(t=n),r<0?(r+=n)<0&&(r=0):r>n&&(r=n),r>>=0,e>>>=0,r||x(t,e,this.length);for(var n=this[t],o=1,i=0;++i>>=0,e>>>=0,r||x(t,e,this.length);for(var n=this[t+--e],o=1;e>0&&(o*=256);)n+=this[t+--e]*o;return n},e.prototype.readUInt8=function(t,e){return t>>>=0,e||x(t,1,this.length),this[t]},e.prototype.readUInt16LE=function(t,e){return t>>>=0,e||x(t,2,this.length),this[t]|this[t+1]<<8},e.prototype.readUInt16BE=function(t,e){return t>>>=0,e||x(t,2,this.length),this[t]<<8|this[t+1]},e.prototype.readUInt32LE=function(t,e){return t>>>=0,e||x(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},e.prototype.readUInt32BE=function(t,e){return t>>>=0,e||x(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},e.prototype.readIntLE=function(t,e,r){t>>>=0,e>>>=0,r||x(t,e,this.length);for(var n=this[t],o=1,i=0;++i=(o*=128)&&(n-=Math.pow(2,8*e)),n},e.prototype.readIntBE=function(t,e,r){t>>>=0,e>>>=0,r||x(t,e,this.length);for(var n=e,o=1,i=this[t+--n];n>0&&(o*=256);)i+=this[t+--n]*o;return i>=(o*=128)&&(i-=Math.pow(2,8*e)),i},e.prototype.readInt8=function(t,e){return t>>>=0,e||x(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},e.prototype.readInt16LE=function(t,e){t>>>=0,e||x(t,2,this.length);var r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},e.prototype.readInt16BE=function(t,e){t>>>=0,e||x(t,2,this.length);var r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},e.prototype.readInt32LE=function(t,e){return t>>>=0,e||x(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},e.prototype.readInt32BE=function(t,e){return t>>>=0,e||x(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},e.prototype.readFloatLE=function(t,e){return t>>>=0,e||x(t,4,this.length),o.read(this,t,!0,23,4)},e.prototype.readFloatBE=function(t,e){return t>>>=0,e||x(t,4,this.length),o.read(this,t,!1,23,4)},e.prototype.readDoubleLE=function(t,e){return t>>>=0,e||x(t,8,this.length),o.read(this,t,!0,52,8)},e.prototype.readDoubleBE=function(t,e){return t>>>=0,e||x(t,8,this.length),o.read(this,t,!1,52,8)},e.prototype.writeUIntLE=function(t,e,r,n){(t=+t,e>>>=0,r>>>=0,n)||I(this,t,e,r,Math.pow(2,8*r)-1,0);var o=1,i=0;for(this[e]=255&t;++i>>=0,r>>>=0,n)||I(this,t,e,r,Math.pow(2,8*r)-1,0);var o=r-1,i=1;for(this[e+o]=255&t;--o>=0&&(i*=256);)this[e+o]=t/i&255;return e+r},e.prototype.writeUInt8=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,1,255,0),this[e]=255&t,e+1},e.prototype.writeUInt16LE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,2,65535,0),this[e]=255&t,this[e+1]=t>>>8,e+2},e.prototype.writeUInt16BE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,2,65535,0),this[e]=t>>>8,this[e+1]=255&t,e+2},e.prototype.writeUInt32LE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,4,4294967295,0),this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t,e+4},e.prototype.writeUInt32BE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,4,4294967295,0),this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t,e+4},e.prototype.writeIntLE=function(t,e,r,n){if(t=+t,e>>>=0,!n){var o=Math.pow(2,8*r-1);I(this,t,e,r,o-1,-o)}var i=0,s=1,a=0;for(this[e]=255&t;++i>0)-a&255;return e+r},e.prototype.writeIntBE=function(t,e,r,n){if(t=+t,e>>>=0,!n){var o=Math.pow(2,8*r-1);I(this,t,e,r,o-1,-o)}var i=r-1,s=1,a=0;for(this[e+i]=255&t;--i>=0&&(s*=256);)t<0&&0===a&&0!==this[e+i+1]&&(a=1),this[e+i]=(t/s>>0)-a&255;return e+r},e.prototype.writeInt8=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,1,127,-128),t<0&&(t=255+t+1),this[e]=255&t,e+1},e.prototype.writeInt16LE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,2,32767,-32768),this[e]=255&t,this[e+1]=t>>>8,e+2},e.prototype.writeInt16BE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,2,32767,-32768),this[e]=t>>>8,this[e+1]=255&t,e+2},e.prototype.writeInt32LE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,4,2147483647,-2147483648),this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24,e+4},e.prototype.writeInt32BE=function(t,e,r){return t=+t,e>>>=0,r||I(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t,e+4},e.prototype.writeFloatLE=function(t,e,r){return L(this,t,e,!0,r)},e.prototype.writeFloatBE=function(t,e,r){return L(this,t,e,!1,r)},e.prototype.writeDoubleLE=function(t,e,r){return N(this,t,e,!0,r)},e.prototype.writeDoubleBE=function(t,e,r){return N(this,t,e,!1,r)},e.prototype.copy=function(t,r,n,o){if(!e.isBuffer(t))throw new TypeError("argument should be a Buffer");if(n||(n=0),o||0===o||(o=this.length),r>=t.length&&(r=t.length),r||(r=0),o>0&&o=this.length)throw new RangeError("Index out of range");if(o<0)throw new RangeError("sourceEnd out of bounds");o>this.length&&(o=this.length),t.length-r=0;--s)t[s+r]=this[s+n];else Uint8Array.prototype.set.call(t,this.subarray(n,o),r);return i},e.prototype.fill=function(t,r,n,o){if("string"==typeof t){if("string"==typeof r?(o=r,r=0,n=this.length):"string"==typeof n&&(o=n,n=this.length),void 0!==o&&"string"!=typeof o)throw new TypeError("encoding must be a string");if("string"==typeof o&&!e.isEncoding(o))throw new TypeError("Unknown encoding: "+o);if(1===t.length){var i=t.charCodeAt(0);("utf8"===o&&i<128||"latin1"===o)&&(t=i)}}else"number"==typeof t?t&=255:"boolean"==typeof t&&(t=Number(t));if(r<0||this.length>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(s=r;s55295&&r<57344){if(!o){if(r>56319){(e-=3)>-1&&i.push(239,191,189);continue}if(s+1===n){(e-=3)>-1&&i.push(239,191,189);continue}o=r;continue}if(r<56320){(e-=3)>-1&&i.push(239,191,189),o=r;continue}r=65536+(o-55296<<10|r-56320)}else o&&(e-=3)>-1&&i.push(239,191,189);if(o=null,r<128){if((e-=1)<0)break;i.push(r)}else if(r<2048){if((e-=2)<0)break;i.push(r>>6|192,63&r|128)}else if(r<65536){if((e-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function R(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0]).trim().replace(P,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function M(t,e,r,n){for(var o=0;o=e.length||o>=t.length);++o)e[o+r]=t[o];return o}function H(t,e){return t instanceof e||null!=t&&null!=t.constructor&&null!=t.constructor.name&&t.constructor.name===e.name}function G(t){return t!=t}}).call(this,t("buffer").Buffer)},{"base64-js":10,buffer:11,ieee754:13}],12:[function(t,e){e.exports=class{async next(t){let e=this.createConsumer(t),r=await e.next();return e.return(),r}async once(t){let e=await this.next(t);return e.done&&await new Promise(()=>{}),e.value}createConsumer(){throw new TypeError("Method must be overriden by subclass")}createConsumable(t){let e=this.createConsumer(t);return{[Symbol.asyncIterator]:()=>e}}[Symbol.asyncIterator](){return this.createConsumer()}}},{}],13:[function(t,e,r){r.read=function(t,e,r,n,o){var i,s,a=8*o-n-1,u=(1<>1,h=-7,l=r?o-1:0,p=r?-1:1,f=t[e+l];for(l+=p,i=f&(1<<-h)-1,f>>=-h,h+=a;h>0;i=256*i+t[e+l],l+=p,h-=8);for(s=i&(1<<-h)-1,i>>=-h,h+=n;h>0;s=256*s+t[e+l],l+=p,h-=8);if(0===i)i=1-c;else{if(i===u)return s?NaN:1/0*(f?-1:1);s+=Math.pow(2,n),i-=c}return(f?-1:1)*s*Math.pow(2,i-n)},r.write=function(t,e,r,n,o,i){var s,a,u,c=8*i-o-1,h=(1<>1,p=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=n?0:i-1,d=n?1:-1,m=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(a=isNaN(e)?1:0,s=h):(s=Math.floor(Math.log(e)/Math.LN2),e*(u=Math.pow(2,-s))<1&&(s--,u*=2),(e+=s+l>=1?p/u:p*Math.pow(2,1-l))*u>=2&&(s++,u/=2),s+l>=h?(a=0,s=h):s+l>=1?(a=(e*u-1)*Math.pow(2,o),s+=l):(a=e*Math.pow(2,l-1)*Math.pow(2,o),s=0));o>=8;t[r+f]=255&a,f+=d,a/=256,o-=8);for(s=s<0;t[r+f]=255&s,f+=d,s/=256,c-=8);t[r+f-d]|=128*m}},{}],14:[function(t,e){"use strict";var r="An argument without append, prepend, or detach methods was given to `List";function n(){if(arguments.length)return n.from(arguments)}var o=n.prototype;function i(){}n.of=function(){return n.from.call(this,arguments)},n.from=function(t){var e,r,n,o=new this;if(t&&(e=t.length))for(r=-1;++r-1},Ct.prototype.set=function(t,e){var r=this.__data__,n=Ot(r,t);return n<0?r.push([t,e]):r[n][1]=e,this},St.prototype.clear=function(){this.__data__={hash:new wt,map:new(lt||Ct),string:new wt}},St.prototype.delete=function(t){return Nt(this,t).delete(t)},St.prototype.get=function(t){return Nt(this,t).get(t)},St.prototype.has=function(t){return Nt(this,t).has(t)},St.prototype.set=function(t,e){return Nt(this,t).set(t,e),this},Tt.prototype.clear=function(){this.__data__=new Ct},Tt.prototype.delete=function(t){return this.__data__.delete(t)},Tt.prototype.get=function(t){return this.__data__.get(t)},Tt.prototype.has=function(t){return this.__data__.has(t)},Tt.prototype.set=function(t,e){var r=this.__data__;if(r instanceof Ct){var n=r.__data__;if(!lt||n.length<199)return n.push([t,e]),this;r=this.__data__=new St(n)}return r.set(t,e),this};var jt=at?$(at,Object):function(){return[]},Ut=function(t){return Z.call(t)};function Rt(t,e){return!!(e=null==e?n:e)&&("number"==typeof t||O.test(t))&&t>-1&&t%1==0&&t-1&&t%1==0&&t<=n}(t.length)&&!zt(t)}var qt=ut||function(){return!1};function zt(t){var e=Vt(t)?Z.call(t):"";return e==a||e==u}function Vt(t){var e=typeof t;return!!t&&("object"==e||"function"==e)}function Wt(t){return Ft(t)?At(t):function(t){if(!Mt(t))return ct(t);var e=[];for(var r in Object(t))X.call(t,r)&&"constructor"!=r&&e.push(r);return e}(t)}e.exports=function(t){return xt(t,!0,!0)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],17:[function(t,e){"use strict";function r(t,e){return Object.prototype.hasOwnProperty.call(t,e)}e.exports=function(t,e,o,i){e=e||"&",o=o||"=";var s={};if("string"!=typeof t||0===t.length)return s;var a=/\+/g;t=t.split(e);var u=1e3;i&&"number"==typeof i.maxKeys&&(u=i.maxKeys);var c=t.length;u>0&&c>u&&(c=u);for(var h=0;h=0?(l=m.substr(0,y),p=m.substr(y+1)):(l=m,p=""),f=decodeURIComponent(l),d=decodeURIComponent(p),r(s,f)?n(s[f])?s[f].push(d):s[f]=[s[f],d]:s[f]=d}return s};var n=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)}},{}],18:[function(t,e){"use strict";var r=function(t){switch(typeof t){case"string":return t;case"boolean":return t?"true":"false";case"number":return isFinite(t)?t:"";default:return""}};e.exports=function(t,e,s,a){return e=e||"&",s=s||"=",null===t&&(t=void 0),"object"==typeof t?o(i(t),function(i){var a=encodeURIComponent(r(i))+s;return n(t[i])?o(t[i],function(t){return a+encodeURIComponent(r(t))}).join(e):a+encodeURIComponent(r(t[i]))}).join(e):a?encodeURIComponent(r(a))+s+encodeURIComponent(r(t)):""};var n=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function o(t,e){if(t.map)return t.map(e);for(var r=[],n=0;n>2],o+=r[(3&e[i])<<4|e[i+1]>>4],o+=r[(15&e[i+1])<<2|e[i+2]>>6],o+=r[63&e[i+2]];return n%3==2?o=o.substring(0,o.length-1)+"=":n%3==1&&(o=o.substring(0,o.length-2)+"=="),o}(n)};if(t.Buffer){if(n instanceof t.Buffer)return{base64:!0,data:n.toString("base64")};if(n&&"Buffer"===n.type&&Array.isArray(n.data))return{base64:!0,data:(t.Buffer.from?t.Buffer.from(n.data):new t.Buffer(n.data)).toString("base64")}}return n};e.exports.decode=function(t){if(null==t)return null;if("#1"===t||"#2"===t)return t;var e=t.toString();if(!n.test(e))return e;try{return JSON.parse(e)}catch(t){}return e},e.exports.encode=function(t){return"#1"===t||"#2"===t?t:JSON.stringify(t,o)}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],23:[function(t,e){const r=t("consumable-stream");e.exports=class extends r{constructor(t,e){super(),this.name=e,this._streamDemux=t}createConsumer(t){return this._streamDemux.createConsumer(this.name,t)}}},{"consumable-stream":12}],24:[function(t,e){const r=t("writable-consumable-stream"),n=t("./demuxed-consumable-stream");e.exports=class{constructor(){this._mainStream=new r}write(t,e){this._mainStream.write({stream:t,data:{value:e,done:!1}})}close(t,e){this._mainStream.write({stream:t,data:{value:e,done:!0}})}closeAll(t){this._mainStream.close(t)}writeToConsumer(t,e){this._mainStream.writeToConsumer(t,{consumerId:t,data:{value:e,done:!1}})}closeConsumer(t,e){this._mainStream.closeConsumer(t,{consumerId:t,data:{value:e,done:!0}})}getConsumerStats(t){return this._mainStream.getConsumerStats(t)}getConsumerStatsList(t){return this._mainStream.getConsumerStatsList().filter(e=>e.stream===t)}getConsumerStatsListAll(){return this._mainStream.getConsumerStatsList()}kill(t,e){let r=this.getConsumerStatsList(t),n=r.length;for(let t=0;tn&&(n=r.backpressure)}return n}getBackpressureAll(){return this._mainStream.getBackpressure()}getConsumerBackpressure(t){return this._mainStream.getConsumerBackpressure(t)}hasConsumer(t,e){let r=this._mainStream.getConsumerStats(e);return!!r&&r.stream===t}hasConsumerAll(t){return this._mainStream.hasConsumer(t)}createConsumer(t,e){let r=this._mainStream.createConsumer(e),n=r.next;r.next=async function(){for(;;){let e=await n.apply(this,arguments);if(e.value&&(e.value.stream===t||e.value.consumerId===this.id))return e.value.data.done&&this.return(),e.value.data;if(e.done)return e}};let o=r.getStats;r.getStats=function(){let e=o.apply(this,arguments);return e.stream=t,e};let i=r.applyBackpressure;r.applyBackpressure=function(e){!e.value||e.value.stream!==t&&e.value.consumerId!==this.id?e.done&&i.apply(this,arguments):i.apply(this,arguments)};let s=r.releaseBackpressure;return r.releaseBackpressure=function(e){!e.value||e.value.stream!==t&&e.value.consumerId!==this.id?e.done&&s.apply(this,arguments):s.apply(this,arguments)},r}stream(t){return new n(this,t)}}},{"./demuxed-consumable-stream":23,"writable-consumable-stream":31}],25:[function(t,e){var r=t("./v1"),n=t("./v4"),o=n;o.v1=r,o.v4=n,e.exports=o},{"./v1":28,"./v4":29}],26:[function(t,e){for(var r=[],n=0;n<256;++n)r[n]=(n+256).toString(16).substr(1);e.exports=function(t,e){var n=e||0,o=r;return[o[t[n++]],o[t[n++]],o[t[n++]],o[t[n++]],"-",o[t[n++]],o[t[n++]],"-",o[t[n++]],o[t[n++]],"-",o[t[n++]],o[t[n++]],"-",o[t[n++]],o[t[n++]],o[t[n++]],o[t[n++]],o[t[n++]],o[t[n++]]].join("")}},{}],27:[function(t,e){var r="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof window.msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto);if(r){var n=new Uint8Array(16);e.exports=function(){return r(n),n}}else{var o=new Array(16);e.exports=function(){for(var t,e=0;e<16;e++)0==(3&e)&&(t=4294967296*Math.random()),o[e]=t>>>((3&e)<<3)&255;return o}}},{}],28:[function(t,e){var r,n,o=t("./lib/rng"),i=t("./lib/bytesToUuid"),s=0,a=0;e.exports=function(t,e,u){var c=e&&u||0,h=e||[],l=(t=t||{}).node||r,p=void 0!==t.clockseq?t.clockseq:n;if(null==l||null==p){var f=o();null==l&&(l=r=[1|f[0],f[1],f[2],f[3],f[4],f[5]]),null==p&&(p=n=16383&(f[6]<<8|f[7]))}var d=void 0!==t.msecs?t.msecs:(new Date).getTime(),m=void 0!==t.nsecs?t.nsecs:a+1,y=d-s+(m-a)/1e4;if(y<0&&void 0===t.clockseq&&(p=p+1&16383),(y<0||d>s)&&void 0===t.nsecs&&(m=0),m>=1e4)throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");s=d,a=m,n=p;var g=(1e4*(268435455&(d+=122192928e5))+m)%4294967296;h[c++]=g>>>24&255,h[c++]=g>>>16&255,h[c++]=g>>>8&255,h[c++]=255&g;var b=d/4294967296*1e4&268435455;h[c++]=b>>>8&255,h[c++]=255&b,h[c++]=b>>>24&15|16,h[c++]=b>>>16&255,h[c++]=p>>>8|128,h[c++]=255&p;for(var v=0;v<6;++v)h[c+v]=l[v];return e||i(h)}},{"./lib/bytesToUuid":26,"./lib/rng":27}],29:[function(t,e){var r=t("./lib/rng"),n=t("./lib/bytesToUuid");e.exports=function(t,e,o){var i=e&&o||0;"string"==typeof t&&(e="binary"===t?new Array(16):null,t=null);var s=(t=t||{}).random||(t.rng||r)();if(s[6]=15&s[6]|64,s[8]=63&s[8]|128,e)for(var a=0;a<16;++a)e[i+a]=s[a];return e||n(s)}},{"./lib/bytesToUuid":26,"./lib/rng":27}],30:[function(t,e){e.exports=class{constructor(t,e,r,n){this.id=e,this._backpressure=0,this.stream=t,this.currentNode=r,this.timeout=n,this._isIterating=!1,this.stream.setConsumer(this.id,this)}getStats(){let t={id:this.id,backpressure:this._backpressure};return null!=this.timeout&&(t.timeout=this.timeout),t}resetBackpressure(){this._backpressure=0}applyBackpressure(t){this._backpressure++}releaseBackpressure(t){this._backpressure--}getBackpressure(){return this._backpressure}write(t){void 0!==this._timeoutId&&(clearTimeout(this._timeoutId),delete this._timeoutId),this.applyBackpressure(t),this._resolve&&(this._resolve(),delete this._resolve)}kill(t){void 0!==this._timeoutId&&(clearTimeout(this._timeoutId),delete this._timeoutId),this._isIterating?(this._killPacket={value:t,done:!0},this.applyBackpressure(this._killPacket)):(this.stream.removeConsumer(this.id),this.resetBackpressure()),this._resolve&&(this._resolve(),delete this._resolve)}async _waitForNextItem(t){return new Promise((e,r)=>{let n;if(this._resolve=e,void 0!==t){let e=new Error("Stream consumer iteration timed out");(async()=>{let o=function(t){let e,r=new Promise(r=>{e=setTimeout(r,t)});return{timeoutId:e,promise:r}}(t);n=o.timeoutId,await o.promise,e.name="TimeoutError",delete this._resolve,r(e)})()}this._timeoutId=n})}async next(){for(this._isIterating=!0,this.stream.setConsumer(this.id,this);;){if(!this.currentNode.next)try{await this._waitForNextItem(this.timeout)}catch(t){throw this._isIterating=!1,this.stream.removeConsumer(this.id),t}if(this._killPacket){this._isIterating=!1,this.stream.removeConsumer(this.id),this.resetBackpressure();let t=this._killPacket;return delete this._killPacket,t}if(this.currentNode=this.currentNode.next,this.releaseBackpressure(this.currentNode.data),!this.currentNode.consumerId||this.currentNode.consumerId===this.id)return this.currentNode.data.done&&(this._isIterating=!1,this.stream.removeConsumer(this.id)),this.currentNode.data}}return(){return delete this.currentNode,this._isIterating=!1,this.stream.removeConsumer(this.id),this.resetBackpressure(),{}}}},{}],31:[function(t,e){const r=t("consumable-stream"),n=t("./consumer");e.exports=class extends r{constructor(){super(),this.nextConsumerId=1,this._consumers={},this._tailNode={next:null,data:{value:void 0,done:!1}}}_write(t,e,r){let n={data:{value:t,done:e},next:null};r&&(n.consumerId=r),this._tailNode.next=n,this._tailNode=n;let o=Object.values(this._consumers),i=o.length;for(let t=0;tr&&(r=e)}return r}getConsumerBackpressure(t){let e=this._consumers[t];return e?e.getBackpressure():0}hasConsumer(t){return!!this._consumers[t]}setConsumer(t,e){this._consumers[t]=e,e.currentNode||(e.currentNode=this._tailNode)}removeConsumer(t){delete this._consumers[t]}getConsumerStats(t){let e=this._consumers[t];if(e)return e.getStats()}getConsumerStatsList(){let t=[],e=Object.values(this._consumers),r=e.length;for(let n=0;n asyngular-client.js && uglifyjs asyngular-client.js -o asyngular-client.min.js 6 | -------------------------------------------------------------------------------- /gulp-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | SDK_VERSION=$(cat package.json | sed -n -e '/version/ s/.*: *"\([^"]*\).*/\1/p') 4 | echo "Building JavaScript SDK v$SDK_VERSION...\n" 5 | echo "Cleaning up old builds...\n" 6 | rm -rf asyngular-client.js asyngular-client.min.js 7 | echo "Browser Release:" 8 | gulp browserify 9 | gulp minify 10 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var browserify = require('browserify'); 4 | var babel = require('gulp-babel'); 5 | var insert = require('gulp-insert'); 6 | var rename = require('gulp-rename'); 7 | var source = require('vinyl-source-stream'); 8 | var uglify = require('uglify-es'); 9 | var uglifyComposer = require('gulp-uglify/composer'); 10 | var minify = uglifyComposer(uglify, console); 11 | var convertNewline = require('gulp-convert-newline'); 12 | 13 | var BUILD = 'browser'; 14 | var DIST = './'; 15 | var VERSION = require('./package.json').version; 16 | 17 | // Add comment. Also, prepend the var keyword to the require variable 18 | // declaration added by browserify. 19 | var FULL_HEADER = ( 20 | '/**\n' + 21 | ' * Asyngular JavaScript client v' + VERSION + '\n' + 22 | ' */\n ' 23 | ); 24 | 25 | gulp.task('browserify', function (done) { 26 | var stream = browserify({ 27 | builtins: ['_process', 'events', 'buffer', 'querystring'], 28 | entries: 'index.js', 29 | standalone: 'asyngularClient' 30 | }) 31 | .ignore('_process') 32 | .require('./index.js', { 33 | expose: 'asyngular-client' 34 | }) 35 | .bundle(); 36 | return stream.pipe(source('asyngular-client.js')) 37 | .pipe(insert.prepend(FULL_HEADER)) 38 | .pipe(convertNewline({ 39 | newline: 'lf', 40 | encoding: 'utf8' 41 | })) 42 | .pipe(gulp.dest(DIST)); 43 | }); 44 | 45 | gulp.task('minify', function () { 46 | return gulp.src(DIST + 'asyngular-client.js') 47 | .pipe(babel({ 48 | comments: false 49 | })) 50 | .pipe(babel({ 51 | plugins: ['minify-dead-code-elimination'] 52 | })) 53 | .pipe(minify()) 54 | .pipe(insert.prepend(FULL_HEADER)) 55 | .pipe(rename({ 56 | extname: '.min.js' 57 | })) 58 | .pipe(gulp.dest(DIST)); 59 | }); 60 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const AGClientSocket = require('./lib/clientsocket'); 2 | const factory = require('./lib/factory'); 3 | const version = '6.2.4'; 4 | 5 | module.exports.factory = factory; 6 | module.exports.AGClientSocket = AGClientSocket; 7 | 8 | module.exports.create = function (options) { 9 | return factory.create({...options, version}); 10 | }; 11 | 12 | module.exports.version = version; 13 | -------------------------------------------------------------------------------- /lib/auth.js: -------------------------------------------------------------------------------- 1 | function AuthEngine() { 2 | this._internalStorage = {}; 3 | this.isLocalStorageEnabled = this._checkLocalStorageEnabled(); 4 | } 5 | 6 | AuthEngine.prototype._checkLocalStorageEnabled = function () { 7 | let err; 8 | try { 9 | // Some browsers will throw an error here if localStorage is disabled. 10 | global.localStorage; 11 | 12 | // Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem 13 | // throw QuotaExceededError. We're going to detect this and avoid hard to debug edge cases. 14 | global.localStorage.setItem('__scLocalStorageTest', 1); 15 | global.localStorage.removeItem('__scLocalStorageTest'); 16 | } catch (e) { 17 | err = e; 18 | } 19 | return !err; 20 | }; 21 | 22 | AuthEngine.prototype.saveToken = function (name, token, options) { 23 | if (this.isLocalStorageEnabled && global.localStorage) { 24 | global.localStorage.setItem(name, token); 25 | } else { 26 | this._internalStorage[name] = token; 27 | } 28 | return Promise.resolve(token); 29 | }; 30 | 31 | AuthEngine.prototype.removeToken = function (name) { 32 | let loadPromise = this.loadToken(name); 33 | 34 | if (this.isLocalStorageEnabled && global.localStorage) { 35 | global.localStorage.removeItem(name); 36 | } else { 37 | delete this._internalStorage[name]; 38 | } 39 | 40 | return loadPromise; 41 | }; 42 | 43 | AuthEngine.prototype.loadToken = function (name) { 44 | let token; 45 | 46 | if (this.isLocalStorageEnabled && global.localStorage) { 47 | token = global.localStorage.getItem(name); 48 | } else { 49 | token = this._internalStorage[name] || null; 50 | } 51 | 52 | return Promise.resolve(token); 53 | }; 54 | 55 | module.exports = AuthEngine; 56 | -------------------------------------------------------------------------------- /lib/clientsocket.js: -------------------------------------------------------------------------------- 1 | const StreamDemux = require('stream-demux'); 2 | const AsyncStreamEmitter = require('async-stream-emitter'); 3 | const AGChannel = require('ag-channel'); 4 | const AuthEngine = require('./auth'); 5 | const formatter = require('sc-formatter'); 6 | const AGTransport = require('./transport'); 7 | const querystring = require('querystring'); 8 | const LinkedList = require('linked-list'); 9 | const cloneDeep = require('lodash.clonedeep'); 10 | const Buffer = require('buffer/').Buffer; 11 | const wait = require('./wait'); 12 | 13 | const scErrors = require('sc-errors'); 14 | const InvalidArgumentsError = scErrors.InvalidArgumentsError; 15 | const InvalidMessageError = scErrors.InvalidMessageError; 16 | const InvalidActionError = scErrors.InvalidActionError; 17 | const SocketProtocolError = scErrors.SocketProtocolError; 18 | const TimeoutError = scErrors.TimeoutError; 19 | const BadConnectionError = scErrors.BadConnectionError; 20 | 21 | const isBrowser = typeof window !== 'undefined'; 22 | 23 | function AGClientSocket(socketOptions) { 24 | AsyncStreamEmitter.call(this); 25 | 26 | let defaultOptions = { 27 | path: '/asyngular/', 28 | secure: false, 29 | autoConnect: true, 30 | autoReconnect: true, 31 | autoSubscribeOnConnect: true, 32 | connectTimeout: 20000, 33 | ackTimeout: 10000, 34 | timestampRequests: false, 35 | timestampParam: 't', 36 | authTokenName: 'asyngular.authToken', 37 | binaryType: 'arraybuffer', 38 | batchOnHandshake: false, 39 | batchOnHandshakeDuration: 100, 40 | batchInterval: 50, 41 | protocolVersion: 2, 42 | wsOptions: {}, 43 | cloneData: false 44 | }; 45 | let opts = Object.assign(defaultOptions, socketOptions); 46 | 47 | this.id = null; 48 | this.version = opts.version || null; 49 | this.protocolVersion = opts.protocolVersion; 50 | this.state = this.CLOSED; 51 | this.authState = this.UNAUTHENTICATED; 52 | this.signedAuthToken = null; 53 | this.authToken = null; 54 | this.pendingReconnect = false; 55 | this.pendingReconnectTimeout = null; 56 | this.preparingPendingSubscriptions = false; 57 | this.clientId = opts.clientId; 58 | this.wsOptions = opts.wsOptions; 59 | 60 | this.connectTimeout = opts.connectTimeout; 61 | this.ackTimeout = opts.ackTimeout; 62 | this.channelPrefix = opts.channelPrefix || null; 63 | this.disconnectOnUnload = opts.disconnectOnUnload == null ? true : opts.disconnectOnUnload; 64 | this.authTokenName = opts.authTokenName; 65 | 66 | // pingTimeout will be connectTimeout at the start, but it will 67 | // be updated with values provided by the 'connect' event 68 | opts.pingTimeout = opts.connectTimeout; 69 | this.pingTimeout = opts.pingTimeout; 70 | this.pingTimeoutDisabled = !!opts.pingTimeoutDisabled; 71 | 72 | let maxTimeout = Math.pow(2, 31) - 1; 73 | 74 | let verifyDuration = (propertyName) => { 75 | if (this[propertyName] > maxTimeout) { 76 | throw new InvalidArgumentsError( 77 | `The ${propertyName} value provided exceeded the maximum amount allowed` 78 | ); 79 | } 80 | }; 81 | 82 | verifyDuration('connectTimeout'); 83 | verifyDuration('ackTimeout'); 84 | verifyDuration('pingTimeout'); 85 | 86 | this.connectAttempts = 0; 87 | 88 | this.isBatching = false; 89 | this.batchOnHandshake = opts.batchOnHandshake; 90 | this.batchOnHandshakeDuration = opts.batchOnHandshakeDuration; 91 | 92 | this._batchingIntervalId = null; 93 | this._outboundBuffer = new LinkedList(); 94 | this._channelMap = {}; 95 | 96 | this._channelEventDemux = new StreamDemux(); 97 | this._channelDataDemux = new StreamDemux(); 98 | 99 | this._receiverDemux = new StreamDemux(); 100 | this._procedureDemux = new StreamDemux(); 101 | 102 | this.options = opts; 103 | 104 | this._cid = 1; 105 | 106 | this.options.callIdGenerator = () => { 107 | return this._cid++; 108 | }; 109 | 110 | if (this.options.autoReconnect) { 111 | if (this.options.autoReconnectOptions == null) { 112 | this.options.autoReconnectOptions = {}; 113 | } 114 | 115 | // Add properties to the this.options.autoReconnectOptions object. 116 | // We assign the reference to a reconnectOptions variable to avoid repetition. 117 | let reconnectOptions = this.options.autoReconnectOptions; 118 | if (reconnectOptions.initialDelay == null) { 119 | reconnectOptions.initialDelay = 10000; 120 | } 121 | if (reconnectOptions.randomness == null) { 122 | reconnectOptions.randomness = 10000; 123 | } 124 | if (reconnectOptions.multiplier == null) { 125 | reconnectOptions.multiplier = 1.5; 126 | } 127 | if (reconnectOptions.maxDelay == null) { 128 | reconnectOptions.maxDelay = 60000; 129 | } 130 | } 131 | 132 | if (this.options.subscriptionRetryOptions == null) { 133 | this.options.subscriptionRetryOptions = {}; 134 | } 135 | 136 | if (this.options.authEngine) { 137 | this.auth = this.options.authEngine; 138 | } else { 139 | this.auth = new AuthEngine(); 140 | } 141 | 142 | if (this.options.codecEngine) { 143 | this.codec = this.options.codecEngine; 144 | } else { 145 | // Default codec engine 146 | this.codec = formatter; 147 | } 148 | 149 | if (this.options.protocol) { 150 | let protocolOptionError = new InvalidArgumentsError( 151 | 'The "protocol" option does not affect asyngular-client - ' + 152 | 'If you want to utilize SSL/TLS, use "secure" option instead' 153 | ); 154 | this._onError(protocolOptionError); 155 | } 156 | 157 | this.options.query = opts.query || {}; 158 | if (typeof this.options.query === 'string') { 159 | this.options.query = querystring.parse(this.options.query); 160 | } 161 | 162 | if (isBrowser && this.disconnectOnUnload && global.addEventListener && global.removeEventListener) { 163 | this._handleBrowserUnload(); 164 | } 165 | 166 | if (this.options.autoConnect) { 167 | this.connect(); 168 | } 169 | } 170 | 171 | AGClientSocket.prototype = Object.create(AsyncStreamEmitter.prototype); 172 | 173 | AGClientSocket.CONNECTING = AGClientSocket.prototype.CONNECTING = AGTransport.prototype.CONNECTING; 174 | AGClientSocket.OPEN = AGClientSocket.prototype.OPEN = AGTransport.prototype.OPEN; 175 | AGClientSocket.CLOSED = AGClientSocket.prototype.CLOSED = AGTransport.prototype.CLOSED; 176 | 177 | AGClientSocket.AUTHENTICATED = AGClientSocket.prototype.AUTHENTICATED = 'authenticated'; 178 | AGClientSocket.UNAUTHENTICATED = AGClientSocket.prototype.UNAUTHENTICATED = 'unauthenticated'; 179 | 180 | AGClientSocket.SUBSCRIBED = AGClientSocket.prototype.SUBSCRIBED = AGChannel.SUBSCRIBED; 181 | AGClientSocket.PENDING = AGClientSocket.prototype.PENDING = AGChannel.PENDING; 182 | AGClientSocket.UNSUBSCRIBED = AGClientSocket.prototype.UNSUBSCRIBED = AGChannel.UNSUBSCRIBED; 183 | 184 | AGClientSocket.ignoreStatuses = scErrors.socketProtocolIgnoreStatuses; 185 | AGClientSocket.errorStatuses = scErrors.socketProtocolErrorStatuses; 186 | 187 | Object.defineProperty(AGClientSocket.prototype, 'isBufferingBatch', { 188 | get: function () { 189 | return this.transport.isBufferingBatch; 190 | } 191 | }); 192 | 193 | AGClientSocket.prototype.getBackpressure = function () { 194 | return Math.max( 195 | this.getAllListenersBackpressure(), 196 | this.getAllReceiversBackpressure(), 197 | this.getAllProceduresBackpressure(), 198 | this.getAllChannelsBackpressure() 199 | ); 200 | }; 201 | 202 | AGClientSocket.prototype._handleBrowserUnload = async function () { 203 | let unloadHandler = () => { 204 | this.disconnect(); 205 | }; 206 | let isUnloadHandlerAttached = false; 207 | 208 | let attachUnloadHandler = () => { 209 | if (!isUnloadHandlerAttached) { 210 | isUnloadHandlerAttached = true; 211 | global.addEventListener('beforeunload', unloadHandler, false); 212 | } 213 | }; 214 | 215 | let detachUnloadHandler = () => { 216 | if (isUnloadHandlerAttached) { 217 | isUnloadHandlerAttached = false; 218 | global.removeEventListener('beforeunload', unloadHandler, false); 219 | } 220 | }; 221 | 222 | (async () => { 223 | let consumer = this.listener('connecting').createConsumer(); 224 | while (true) { 225 | let packet = await consumer.next(); 226 | if (packet.done) break; 227 | attachUnloadHandler(); 228 | } 229 | })(); 230 | 231 | (async () => { 232 | let consumer = this.listener('close').createConsumer(); 233 | while (true) { 234 | let packet = await consumer.next(); 235 | if (packet.done) break; 236 | detachUnloadHandler(); 237 | } 238 | })(); 239 | }; 240 | 241 | AGClientSocket.prototype._setAuthToken = function (data) { 242 | this._changeToAuthenticatedState(data.token); 243 | 244 | (async () => { 245 | try { 246 | await this.auth.saveToken(this.authTokenName, data.token, {}); 247 | } catch (err) { 248 | this._onError(err); 249 | } 250 | })(); 251 | }; 252 | 253 | AGClientSocket.prototype._removeAuthToken = function (data) { 254 | (async () => { 255 | let oldAuthToken; 256 | try { 257 | oldAuthToken = await this.auth.removeToken(this.authTokenName); 258 | } catch (err) { 259 | // Non-fatal error - Do not close the connection 260 | this._onError(err); 261 | return; 262 | } 263 | this.emit('removeAuthToken', {oldAuthToken}); 264 | })(); 265 | 266 | this._changeToUnauthenticatedStateAndClearTokens(); 267 | }; 268 | 269 | AGClientSocket.prototype._privateDataHandlerMap = { 270 | '#publish': function (data) { 271 | let undecoratedChannelName = this._undecorateChannelName(data.channel); 272 | let isSubscribed = this.isSubscribed(undecoratedChannelName, true); 273 | 274 | if (isSubscribed) { 275 | this._channelDataDemux.write(undecoratedChannelName, data.data); 276 | } 277 | }, 278 | '#kickOut': function (data) { 279 | let undecoratedChannelName = this._undecorateChannelName(data.channel); 280 | let channel = this._channelMap[undecoratedChannelName]; 281 | if (channel) { 282 | this.emit('kickOut', { 283 | channel: undecoratedChannelName, 284 | message: data.message 285 | }); 286 | this._channelEventDemux.write(`${undecoratedChannelName}/kickOut`, {message: data.message}); 287 | this._triggerChannelUnsubscribe(channel); 288 | } 289 | }, 290 | '#setAuthToken': function (data) { 291 | if (data) { 292 | this._setAuthToken(data); 293 | } 294 | }, 295 | '#removeAuthToken': function (data) { 296 | this._removeAuthToken(data); 297 | } 298 | }; 299 | 300 | AGClientSocket.prototype._privateRPCHandlerMap = { 301 | '#setAuthToken': function (data, request) { 302 | if (data) { 303 | this._setAuthToken(data); 304 | 305 | request.end(); 306 | } else { 307 | request.error(new InvalidMessageError('No token data provided by #setAuthToken event')); 308 | } 309 | }, 310 | '#removeAuthToken': function (data, request) { 311 | this._removeAuthToken(data); 312 | request.end(); 313 | } 314 | }; 315 | 316 | AGClientSocket.prototype.getState = function () { 317 | return this.state; 318 | }; 319 | 320 | AGClientSocket.prototype.getBytesReceived = function () { 321 | return this.transport.getBytesReceived(); 322 | }; 323 | 324 | AGClientSocket.prototype.deauthenticate = async function () { 325 | (async () => { 326 | let oldAuthToken; 327 | try { 328 | oldAuthToken = await this.auth.removeToken(this.authTokenName); 329 | } catch (err) { 330 | this._onError(err); 331 | return; 332 | } 333 | this.emit('removeAuthToken', {oldAuthToken}); 334 | })(); 335 | 336 | if (this.state !== this.CLOSED) { 337 | this.transmit('#removeAuthToken'); 338 | } 339 | this._changeToUnauthenticatedStateAndClearTokens(); 340 | await wait(0); 341 | }; 342 | 343 | AGClientSocket.prototype.connect = function () { 344 | if (this.state === this.CLOSED) { 345 | this.pendingReconnect = false; 346 | this.pendingReconnectTimeout = null; 347 | clearTimeout(this._reconnectTimeoutRef); 348 | 349 | this.state = this.CONNECTING; 350 | this.emit('connecting', {}); 351 | 352 | if (this.transport) { 353 | this.transport.clearAllListeners(); 354 | } 355 | 356 | let transportHandlers = { 357 | onOpen: (value) => { 358 | this.state = this.OPEN; 359 | this._onOpen(value); 360 | }, 361 | onOpenAbort: (value) => { 362 | if (this.state !== this.CLOSED) { 363 | this.state = this.CLOSED; 364 | this._destroy(value.code, value.reason, true); 365 | } 366 | }, 367 | onClose: (value) => { 368 | if (this.state !== this.CLOSED) { 369 | this.state = this.CLOSED; 370 | this._destroy(value.code, value.reason); 371 | } 372 | }, 373 | onEvent: (value) => { 374 | this.emit(value.event, value.data); 375 | }, 376 | onError: (value) => { 377 | this._onError(value.error); 378 | }, 379 | onInboundInvoke: (value) => { 380 | this._onInboundInvoke(value); 381 | }, 382 | onInboundTransmit: (value) => { 383 | this._onInboundTransmit(value.event, value.data); 384 | } 385 | }; 386 | 387 | this.transport = new AGTransport(this.auth, this.codec, this.options, this.wsOptions, transportHandlers); 388 | } 389 | }; 390 | 391 | AGClientSocket.prototype.reconnect = function (code, reason) { 392 | this.disconnect(code, reason); 393 | this.connect(); 394 | }; 395 | 396 | AGClientSocket.prototype.disconnect = function (code, reason) { 397 | code = code || 1000; 398 | 399 | if (typeof code !== 'number') { 400 | throw new InvalidArgumentsError('If specified, the code argument must be a number'); 401 | } 402 | 403 | let isConnecting = this.state === this.CONNECTING; 404 | if (isConnecting || this.state === this.OPEN) { 405 | this.state = this.CLOSED; 406 | this._destroy(code, reason, isConnecting); 407 | this.transport.close(code, reason); 408 | } else { 409 | this.pendingReconnect = false; 410 | this.pendingReconnectTimeout = null; 411 | clearTimeout(this._reconnectTimeoutRef); 412 | } 413 | }; 414 | 415 | AGClientSocket.prototype._changeToUnauthenticatedStateAndClearTokens = function () { 416 | if (this.authState !== this.UNAUTHENTICATED) { 417 | let oldAuthState = this.authState; 418 | let oldAuthToken = this.authToken; 419 | let oldSignedAuthToken = this.signedAuthToken; 420 | this.authState = this.UNAUTHENTICATED; 421 | this.signedAuthToken = null; 422 | this.authToken = null; 423 | 424 | let stateChangeData = { 425 | oldAuthState, 426 | newAuthState: this.authState 427 | }; 428 | this.emit('authStateChange', stateChangeData); 429 | this.emit('deauthenticate', {oldSignedAuthToken, oldAuthToken}); 430 | } 431 | }; 432 | 433 | AGClientSocket.prototype._changeToAuthenticatedState = function (signedAuthToken) { 434 | this.signedAuthToken = signedAuthToken; 435 | this.authToken = this._extractAuthTokenData(signedAuthToken); 436 | 437 | if (this.authState !== this.AUTHENTICATED) { 438 | let oldAuthState = this.authState; 439 | this.authState = this.AUTHENTICATED; 440 | let stateChangeData = { 441 | oldAuthState, 442 | newAuthState: this.authState, 443 | signedAuthToken: signedAuthToken, 444 | authToken: this.authToken 445 | }; 446 | if (!this.preparingPendingSubscriptions) { 447 | this.processPendingSubscriptions(); 448 | } 449 | 450 | this.emit('authStateChange', stateChangeData); 451 | } 452 | this.emit('authenticate', {signedAuthToken, authToken: this.authToken}); 453 | }; 454 | 455 | AGClientSocket.prototype.decodeBase64 = function (encodedString) { 456 | return Buffer.from(encodedString, 'base64').toString('utf8'); 457 | }; 458 | 459 | AGClientSocket.prototype.encodeBase64 = function (decodedString) { 460 | return Buffer.from(decodedString, 'utf8').toString('base64'); 461 | }; 462 | 463 | AGClientSocket.prototype._extractAuthTokenData = function (signedAuthToken) { 464 | let tokenParts = (signedAuthToken || '').split('.'); 465 | let encodedTokenData = tokenParts[1]; 466 | if (encodedTokenData != null) { 467 | let tokenData = encodedTokenData; 468 | try { 469 | tokenData = this.decodeBase64(tokenData); 470 | return JSON.parse(tokenData); 471 | } catch (e) { 472 | return tokenData; 473 | } 474 | } 475 | return null; 476 | }; 477 | 478 | AGClientSocket.prototype.getAuthToken = function () { 479 | return this.authToken; 480 | }; 481 | 482 | AGClientSocket.prototype.getSignedAuthToken = function () { 483 | return this.signedAuthToken; 484 | }; 485 | 486 | // Perform client-initiated authentication by providing an encrypted token string. 487 | AGClientSocket.prototype.authenticate = async function (signedAuthToken) { 488 | let authStatus; 489 | 490 | try { 491 | authStatus = await this.invoke('#authenticate', signedAuthToken); 492 | } catch (err) { 493 | if (err.name !== 'BadConnectionError' && err.name !== 'TimeoutError') { 494 | // In case of a bad/closed connection or a timeout, we maintain the last 495 | // known auth state since those errors don't mean that the token is invalid. 496 | this._changeToUnauthenticatedStateAndClearTokens(); 497 | } 498 | await wait(0); 499 | throw err; 500 | } 501 | 502 | if (authStatus && authStatus.isAuthenticated != null) { 503 | // If authStatus is correctly formatted (has an isAuthenticated property), 504 | // then we will rehydrate the authError. 505 | if (authStatus.authError) { 506 | authStatus.authError = scErrors.hydrateError(authStatus.authError); 507 | } 508 | } else { 509 | // Some errors like BadConnectionError and TimeoutError will not pass a valid 510 | // authStatus object to the current function, so we need to create it ourselves. 511 | authStatus = { 512 | isAuthenticated: this.authState, 513 | authError: null 514 | }; 515 | } 516 | 517 | if (authStatus.isAuthenticated) { 518 | this._changeToAuthenticatedState(signedAuthToken); 519 | } else { 520 | this._changeToUnauthenticatedStateAndClearTokens(); 521 | } 522 | 523 | (async () => { 524 | try { 525 | await this.auth.saveToken(this.authTokenName, signedAuthToken, {}); 526 | } catch (err) { 527 | this._onError(err); 528 | } 529 | })(); 530 | 531 | await wait(0); 532 | return authStatus; 533 | }; 534 | 535 | AGClientSocket.prototype._tryReconnect = function (initialDelay) { 536 | let exponent = this.connectAttempts++; 537 | let reconnectOptions = this.options.autoReconnectOptions; 538 | let timeout; 539 | 540 | if (initialDelay == null || exponent > 0) { 541 | let initialTimeout = Math.round(reconnectOptions.initialDelay + (reconnectOptions.randomness || 0) * Math.random()); 542 | 543 | timeout = Math.round(initialTimeout * Math.pow(reconnectOptions.multiplier, exponent)); 544 | } else { 545 | timeout = initialDelay; 546 | } 547 | 548 | if (timeout > reconnectOptions.maxDelay) { 549 | timeout = reconnectOptions.maxDelay; 550 | } 551 | 552 | clearTimeout(this._reconnectTimeoutRef); 553 | 554 | this.pendingReconnect = true; 555 | this.pendingReconnectTimeout = timeout; 556 | this._reconnectTimeoutRef = setTimeout(() => { 557 | this.connect(); 558 | }, timeout); 559 | }; 560 | 561 | AGClientSocket.prototype._onOpen = function (status) { 562 | if (this.isBatching) { 563 | this._startBatching(); 564 | } else if (this.batchOnHandshake) { 565 | this._startBatching(); 566 | setTimeout(() => { 567 | if (!this.isBatching) { 568 | this._stopBatching(); 569 | } 570 | }, this.batchOnHandshakeDuration); 571 | } 572 | this.preparingPendingSubscriptions = true; 573 | 574 | if (status) { 575 | this.id = status.id; 576 | this.pingTimeout = status.pingTimeout; 577 | if (status.isAuthenticated) { 578 | this._changeToAuthenticatedState(status.authToken); 579 | } else { 580 | this._changeToUnauthenticatedStateAndClearTokens(); 581 | } 582 | } else { 583 | // This can happen if auth.loadToken (in transport.js) fails with 584 | // an error - This means that the signedAuthToken cannot be loaded by 585 | // the auth engine and therefore, we need to unauthenticate the client. 586 | this._changeToUnauthenticatedStateAndClearTokens(); 587 | } 588 | 589 | this.connectAttempts = 0; 590 | 591 | if (this.options.autoSubscribeOnConnect) { 592 | this.processPendingSubscriptions(); 593 | } 594 | 595 | // If the user invokes the callback while in autoSubscribeOnConnect mode, it 596 | // won't break anything. 597 | this.emit('connect', { 598 | ...status, 599 | processPendingSubscriptions: () => { 600 | this.processPendingSubscriptions(); 601 | } 602 | }); 603 | 604 | if (this.state === this.OPEN) { 605 | this._flushOutboundBuffer(); 606 | } 607 | }; 608 | 609 | AGClientSocket.prototype._onError = function (error) { 610 | this.emit('error', {error}); 611 | }; 612 | 613 | AGClientSocket.prototype._suspendSubscriptions = function () { 614 | Object.keys(this._channelMap).forEach((channelName) => { 615 | let channel = this._channelMap[channelName]; 616 | this._triggerChannelUnsubscribe(channel, true); 617 | }); 618 | }; 619 | 620 | AGClientSocket.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { 621 | let currentNode = this._outboundBuffer.head; 622 | let nextNode; 623 | 624 | while (currentNode) { 625 | nextNode = currentNode.next; 626 | let eventObject = currentNode.data; 627 | clearTimeout(eventObject.timeout); 628 | delete eventObject.timeout; 629 | currentNode.detach(); 630 | currentNode = nextNode; 631 | 632 | let callback = eventObject.callback; 633 | 634 | if (callback) { 635 | delete eventObject.callback; 636 | let errorMessage = `Event "${eventObject.event}" was aborted due to a bad connection`; 637 | let error = new BadConnectionError(errorMessage, failureType); 638 | 639 | callback.call(eventObject, error, eventObject); 640 | } 641 | // Cleanup any pending response callback in the transport layer too. 642 | if (eventObject.cid) { 643 | this.transport.cancelPendingResponse(eventObject.cid); 644 | } 645 | } 646 | }; 647 | 648 | AGClientSocket.prototype._destroy = function (code, reason, openAbort) { 649 | this.id = null; 650 | this._cancelBatching(); 651 | 652 | if (this.transport) { 653 | this.transport.clearAllListeners(); 654 | } 655 | 656 | this.pendingReconnect = false; 657 | this.pendingReconnectTimeout = null; 658 | clearTimeout(this._reconnectTimeoutRef); 659 | 660 | this._suspendSubscriptions(); 661 | 662 | if (openAbort) { 663 | this.emit('connectAbort', {code, reason}); 664 | } else { 665 | this.emit('disconnect', {code, reason}); 666 | } 667 | this.emit('close', {code, reason}); 668 | 669 | if (!AGClientSocket.ignoreStatuses[code]) { 670 | let closeMessage; 671 | if (reason) { 672 | closeMessage = 'Socket connection closed with status code ' + code + ' and reason: ' + reason; 673 | } else { 674 | closeMessage = 'Socket connection closed with status code ' + code; 675 | } 676 | let err = new SocketProtocolError(AGClientSocket.errorStatuses[code] || closeMessage, code); 677 | this._onError(err); 678 | } 679 | 680 | this._abortAllPendingEventsDueToBadConnection(openAbort ? 'connectAbort' : 'disconnect'); 681 | 682 | // Try to reconnect 683 | // on server ping timeout (4000) 684 | // or on client pong timeout (4001) 685 | // or on close without status (1005) 686 | // or on handshake failure (4003) 687 | // or on handshake rejection (4008) 688 | // or on socket hung up (1006) 689 | if (this.options.autoReconnect) { 690 | if (code === 4000 || code === 4001 || code === 1005) { 691 | // If there is a ping or pong timeout or socket closes without 692 | // status, don't wait before trying to reconnect - These could happen 693 | // if the client wakes up after a period of inactivity and in this case we 694 | // want to re-establish the connection as soon as possible. 695 | this._tryReconnect(0); 696 | 697 | // Codes 4500 and above will be treated as permanent disconnects. 698 | // Socket will not try to auto-reconnect. 699 | } else if (code !== 1000 && code < 4500) { 700 | this._tryReconnect(); 701 | } 702 | } 703 | }; 704 | 705 | AGClientSocket.prototype._onInboundTransmit = function (event, data) { 706 | let handler = this._privateDataHandlerMap[event]; 707 | if (handler) { 708 | handler.call(this, data); 709 | } else { 710 | this._receiverDemux.write(event, data); 711 | } 712 | }; 713 | 714 | AGClientSocket.prototype._onInboundInvoke = function (request) { 715 | let {procedure, data} = request; 716 | let handler = this._privateRPCHandlerMap[procedure]; 717 | if (handler) { 718 | handler.call(this, data, request); 719 | } else { 720 | this._procedureDemux.write(procedure, request); 721 | } 722 | }; 723 | 724 | AGClientSocket.prototype.decode = function (message) { 725 | return this.transport.decode(message); 726 | }; 727 | 728 | AGClientSocket.prototype.encode = function (object) { 729 | return this.transport.encode(object); 730 | }; 731 | 732 | AGClientSocket.prototype._flushOutboundBuffer = function () { 733 | let currentNode = this._outboundBuffer.head; 734 | let nextNode; 735 | 736 | while (currentNode) { 737 | nextNode = currentNode.next; 738 | let eventObject = currentNode.data; 739 | currentNode.detach(); 740 | this.transport.transmitObject(eventObject); 741 | currentNode = nextNode; 742 | } 743 | }; 744 | 745 | AGClientSocket.prototype._handleEventAckTimeout = function (eventObject, eventNode) { 746 | if (eventNode) { 747 | eventNode.detach(); 748 | } 749 | delete eventObject.timeout; 750 | 751 | let callback = eventObject.callback; 752 | if (callback) { 753 | delete eventObject.callback; 754 | let error = new TimeoutError(`Event response for "${eventObject.event}" timed out`); 755 | callback.call(eventObject, error, eventObject); 756 | } 757 | // Cleanup any pending response callback in the transport layer too. 758 | if (eventObject.cid) { 759 | this.transport.cancelPendingResponse(eventObject.cid); 760 | } 761 | }; 762 | 763 | AGClientSocket.prototype._processOutboundEvent = function (event, data, options, expectResponse) { 764 | options = options || {}; 765 | 766 | if (this.state === this.CLOSED) { 767 | this.connect(); 768 | } 769 | let eventObject = { 770 | event 771 | }; 772 | 773 | let promise; 774 | 775 | if (expectResponse) { 776 | promise = new Promise((resolve, reject) => { 777 | eventObject.callback = (err, data) => { 778 | if (err) { 779 | reject(err); 780 | return; 781 | } 782 | resolve(data); 783 | }; 784 | }); 785 | } else { 786 | promise = Promise.resolve(); 787 | } 788 | 789 | let eventNode = new LinkedList.Item(); 790 | 791 | if (this.options.cloneData) { 792 | eventObject.data = cloneDeep(data); 793 | } else { 794 | eventObject.data = data; 795 | } 796 | eventNode.data = eventObject; 797 | 798 | let ackTimeout = options.ackTimeout == null ? this.ackTimeout : options.ackTimeout; 799 | 800 | eventObject.timeout = setTimeout(() => { 801 | this._handleEventAckTimeout(eventObject, eventNode); 802 | }, ackTimeout); 803 | 804 | this._outboundBuffer.append(eventNode); 805 | if (this.state === this.OPEN) { 806 | this._flushOutboundBuffer(); 807 | } 808 | return promise; 809 | }; 810 | 811 | AGClientSocket.prototype.send = function (data) { 812 | this.transport.send(data); 813 | }; 814 | 815 | AGClientSocket.prototype.transmit = function (event, data, options) { 816 | return this._processOutboundEvent(event, data, options); 817 | }; 818 | 819 | AGClientSocket.prototype.invoke = function (event, data, options) { 820 | return this._processOutboundEvent(event, data, options, true); 821 | }; 822 | 823 | AGClientSocket.prototype.transmitPublish = function (channelName, data) { 824 | let pubData = { 825 | channel: this._decorateChannelName(channelName), 826 | data 827 | }; 828 | return this.transmit('#publish', pubData); 829 | }; 830 | 831 | AGClientSocket.prototype.invokePublish = function (channelName, data) { 832 | let pubData = { 833 | channel: this._decorateChannelName(channelName), 834 | data 835 | }; 836 | return this.invoke('#publish', pubData); 837 | }; 838 | 839 | AGClientSocket.prototype._triggerChannelSubscribe = function (channel, subscriptionOptions) { 840 | let channelName = channel.name; 841 | 842 | if (channel.state !== AGChannel.SUBSCRIBED) { 843 | let oldChannelState = channel.state; 844 | channel.state = AGChannel.SUBSCRIBED; 845 | 846 | let stateChangeData = { 847 | oldChannelState, 848 | newChannelState: channel.state, 849 | subscriptionOptions 850 | }; 851 | this._channelEventDemux.write(`${channelName}/subscribeStateChange`, stateChangeData); 852 | this._channelEventDemux.write(`${channelName}/subscribe`, { 853 | subscriptionOptions 854 | }); 855 | this.emit('subscribeStateChange', { 856 | channel: channelName, 857 | ...stateChangeData 858 | }); 859 | this.emit('subscribe', { 860 | channel: channelName, 861 | subscriptionOptions 862 | }); 863 | } 864 | }; 865 | 866 | AGClientSocket.prototype._triggerChannelSubscribeFail = function (err, channel, subscriptionOptions) { 867 | let channelName = channel.name; 868 | let meetsAuthRequirements = !channel.options.waitForAuth || this.authState === this.AUTHENTICATED; 869 | let hasChannel = !!this._channelMap[channelName]; 870 | 871 | if (hasChannel && meetsAuthRequirements) { 872 | delete this._channelMap[channelName]; 873 | 874 | this._channelEventDemux.write(`${channelName}/subscribeFail`, { 875 | error: err, 876 | subscriptionOptions 877 | }); 878 | this.emit('subscribeFail', { 879 | error: err, 880 | channel: channelName, 881 | subscriptionOptions: subscriptionOptions 882 | }); 883 | } 884 | }; 885 | 886 | // Cancel any pending subscribe callback 887 | AGClientSocket.prototype._cancelPendingSubscribeCallback = function (channel) { 888 | if (channel._pendingSubscriptionCid != null) { 889 | this.transport.cancelPendingResponse(channel._pendingSubscriptionCid); 890 | delete channel._pendingSubscriptionCid; 891 | } 892 | }; 893 | 894 | AGClientSocket.prototype._decorateChannelName = function (channelName) { 895 | if (this.channelPrefix) { 896 | channelName = this.channelPrefix + channelName; 897 | } 898 | return channelName; 899 | }; 900 | 901 | AGClientSocket.prototype._undecorateChannelName = function (decoratedChannelName) { 902 | if (this.channelPrefix && decoratedChannelName.indexOf(this.channelPrefix) === 0) { 903 | return decoratedChannelName.replace(this.channelPrefix, ''); 904 | } 905 | return decoratedChannelName; 906 | }; 907 | 908 | AGClientSocket.prototype.startBatch = function () { 909 | this.transport.startBatch(); 910 | }; 911 | 912 | AGClientSocket.prototype.flushBatch = function () { 913 | this.transport.flushBatch(); 914 | }; 915 | 916 | AGClientSocket.prototype.cancelBatch = function () { 917 | this.transport.cancelBatch(); 918 | }; 919 | 920 | AGClientSocket.prototype._startBatching = function () { 921 | if (this._batchingIntervalId != null) { 922 | return; 923 | } 924 | this.startBatch(); 925 | this._batchingIntervalId = setInterval(() => { 926 | this.flushBatch(); 927 | this.startBatch(); 928 | }, this.options.batchInterval); 929 | }; 930 | 931 | AGClientSocket.prototype.startBatching = function () { 932 | this.isBatching = true; 933 | this._startBatching(); 934 | }; 935 | 936 | AGClientSocket.prototype._stopBatching = function () { 937 | if (this._batchingIntervalId != null) { 938 | clearInterval(this._batchingIntervalId); 939 | } 940 | this._batchingIntervalId = null; 941 | this.flushBatch(); 942 | }; 943 | 944 | AGClientSocket.prototype.stopBatching = function () { 945 | this.isBatching = false; 946 | this._stopBatching(); 947 | }; 948 | 949 | AGClientSocket.prototype._cancelBatching = function () { 950 | if (this._batchingIntervalId != null) { 951 | clearInterval(this._batchingIntervalId); 952 | } 953 | this._batchingIntervalId = null; 954 | this.cancelBatch(); 955 | }; 956 | 957 | AGClientSocket.prototype.cancelBatching = function () { 958 | this.isBatching = false; 959 | this._cancelBatching(); 960 | }; 961 | 962 | AGClientSocket.prototype._trySubscribe = function (channel) { 963 | let meetsAuthRequirements = !channel.options.waitForAuth || this.authState === this.AUTHENTICATED; 964 | 965 | // We can only ever have one pending subscribe action at any given time on a channel 966 | if ( 967 | this.state === this.OPEN && 968 | !this.preparingPendingSubscriptions && 969 | channel._pendingSubscriptionCid == null && 970 | meetsAuthRequirements 971 | ) { 972 | 973 | let options = { 974 | noTimeout: true 975 | }; 976 | 977 | let subscriptionOptions = {}; 978 | if (channel.options.waitForAuth) { 979 | options.waitForAuth = true; 980 | subscriptionOptions.waitForAuth = options.waitForAuth; 981 | } 982 | if (channel.options.data) { 983 | subscriptionOptions.data = channel.options.data; 984 | } 985 | 986 | channel._pendingSubscriptionCid = this.transport.invokeRaw( 987 | '#subscribe', 988 | { 989 | channel: this._decorateChannelName(channel.name), 990 | ...subscriptionOptions 991 | }, 992 | options, 993 | (err) => { 994 | if (err) { 995 | if (err.name === 'BadConnectionError') { 996 | // In case of a failed connection, keep the subscription 997 | // as pending; it will try again on reconnect. 998 | return; 999 | } 1000 | delete channel._pendingSubscriptionCid; 1001 | this._triggerChannelSubscribeFail(err, channel, subscriptionOptions); 1002 | } else { 1003 | delete channel._pendingSubscriptionCid; 1004 | this._triggerChannelSubscribe(channel, subscriptionOptions); 1005 | } 1006 | } 1007 | ); 1008 | this.emit('subscribeRequest', { 1009 | channel: channel.name, 1010 | subscriptionOptions 1011 | }); 1012 | } 1013 | }; 1014 | 1015 | AGClientSocket.prototype.subscribe = function (channelName, options) { 1016 | options = options || {}; 1017 | let channel = this._channelMap[channelName]; 1018 | 1019 | let sanitizedOptions = { 1020 | waitForAuth: !!options.waitForAuth 1021 | }; 1022 | 1023 | if (options.priority != null) { 1024 | sanitizedOptions.priority = options.priority; 1025 | } 1026 | if (options.data !== undefined) { 1027 | sanitizedOptions.data = options.data; 1028 | } 1029 | 1030 | if (!channel) { 1031 | channel = { 1032 | name: channelName, 1033 | state: AGChannel.PENDING, 1034 | options: sanitizedOptions 1035 | }; 1036 | this._channelMap[channelName] = channel; 1037 | this._trySubscribe(channel); 1038 | } else if (options) { 1039 | channel.options = sanitizedOptions; 1040 | } 1041 | 1042 | let channelIterable = new AGChannel( 1043 | channelName, 1044 | this, 1045 | this._channelEventDemux, 1046 | this._channelDataDemux 1047 | ); 1048 | 1049 | return channelIterable; 1050 | }; 1051 | 1052 | AGClientSocket.prototype._triggerChannelUnsubscribe = function (channel, setAsPending) { 1053 | let channelName = channel.name; 1054 | 1055 | this._cancelPendingSubscribeCallback(channel); 1056 | 1057 | if (channel.state === AGChannel.SUBSCRIBED) { 1058 | let stateChangeData = { 1059 | oldChannelState: channel.state, 1060 | newChannelState: setAsPending ? AGChannel.PENDING : AGChannel.UNSUBSCRIBED 1061 | }; 1062 | this._channelEventDemux.write(`${channelName}/subscribeStateChange`, stateChangeData); 1063 | this._channelEventDemux.write(`${channelName}/unsubscribe`, {}); 1064 | this.emit('subscribeStateChange', { 1065 | channel: channelName, 1066 | ...stateChangeData 1067 | }); 1068 | this.emit('unsubscribe', {channel: channelName}); 1069 | } 1070 | 1071 | if (setAsPending) { 1072 | channel.state = AGChannel.PENDING; 1073 | } else { 1074 | delete this._channelMap[channelName]; 1075 | } 1076 | }; 1077 | 1078 | AGClientSocket.prototype._tryUnsubscribe = function (channel) { 1079 | if (this.state === this.OPEN) { 1080 | let options = { 1081 | noTimeout: true 1082 | }; 1083 | // If there is a pending subscribe action, cancel the callback 1084 | this._cancelPendingSubscribeCallback(channel); 1085 | 1086 | // This operation cannot fail because the TCP protocol guarantees delivery 1087 | // so long as the connection remains open. If the connection closes, 1088 | // the server will automatically unsubscribe the client and thus complete 1089 | // the operation on the server side. 1090 | let decoratedChannelName = this._decorateChannelName(channel.name); 1091 | this.transport.transmit('#unsubscribe', decoratedChannelName, options); 1092 | } 1093 | }; 1094 | 1095 | AGClientSocket.prototype.unsubscribe = function (channelName) { 1096 | let channel = this._channelMap[channelName]; 1097 | 1098 | if (channel) { 1099 | this._triggerChannelUnsubscribe(channel); 1100 | this._tryUnsubscribe(channel); 1101 | } 1102 | }; 1103 | 1104 | // ---- Receiver logic ---- 1105 | 1106 | AGClientSocket.prototype.receiver = function (receiverName) { 1107 | return this._receiverDemux.stream(receiverName); 1108 | }; 1109 | 1110 | AGClientSocket.prototype.closeReceiver = function (receiverName) { 1111 | this._receiverDemux.close(receiverName); 1112 | }; 1113 | 1114 | AGClientSocket.prototype.closeAllReceivers = function () { 1115 | this._receiverDemux.closeAll(); 1116 | }; 1117 | 1118 | AGClientSocket.prototype.killReceiver = function (receiverName) { 1119 | this._receiverDemux.kill(receiverName); 1120 | }; 1121 | 1122 | AGClientSocket.prototype.killAllReceivers = function () { 1123 | this._receiverDemux.killAll(); 1124 | }; 1125 | 1126 | AGClientSocket.prototype.killReceiverConsumer = function (consumerId) { 1127 | this._receiverDemux.killConsumer(consumerId); 1128 | }; 1129 | 1130 | AGClientSocket.prototype.getReceiverConsumerStats = function (consumerId) { 1131 | return this._receiverDemux.getConsumerStats(consumerId); 1132 | }; 1133 | 1134 | AGClientSocket.prototype.getReceiverConsumerStatsList = function (receiverName) { 1135 | return this._receiverDemux.getConsumerStatsList(receiverName); 1136 | }; 1137 | 1138 | AGClientSocket.prototype.getAllReceiversConsumerStatsList = function () { 1139 | return this._receiverDemux.getConsumerStatsListAll(); 1140 | }; 1141 | 1142 | AGClientSocket.prototype.getReceiverBackpressure = function (receiverName) { 1143 | return this._receiverDemux.getBackpressure(receiverName); 1144 | }; 1145 | 1146 | AGClientSocket.prototype.getAllReceiversBackpressure = function () { 1147 | return this._receiverDemux.getBackpressureAll(); 1148 | }; 1149 | 1150 | AGClientSocket.prototype.getReceiverConsumerBackpressure = function (consumerId) { 1151 | return this._receiverDemux.getConsumerBackpressure(consumerId); 1152 | }; 1153 | 1154 | AGClientSocket.prototype.hasReceiverConsumer = function (receiverName, consumerId) { 1155 | return this._receiverDemux.hasConsumer(receiverName, consumerId); 1156 | }; 1157 | 1158 | AGClientSocket.prototype.hasAnyReceiverConsumer = function (consumerId) { 1159 | return this._receiverDemux.hasConsumerAll(consumerId); 1160 | }; 1161 | 1162 | // ---- Procedure logic ---- 1163 | 1164 | AGClientSocket.prototype.procedure = function (procedureName) { 1165 | return this._procedureDemux.stream(procedureName); 1166 | }; 1167 | 1168 | AGClientSocket.prototype.closeProcedure = function (procedureName) { 1169 | this._procedureDemux.close(procedureName); 1170 | }; 1171 | 1172 | AGClientSocket.prototype.closeAllProcedures = function () { 1173 | this._procedureDemux.closeAll(); 1174 | }; 1175 | 1176 | AGClientSocket.prototype.killProcedure = function (procedureName) { 1177 | this._procedureDemux.kill(procedureName); 1178 | }; 1179 | 1180 | AGClientSocket.prototype.killAllProcedures = function () { 1181 | this._procedureDemux.killAll(); 1182 | }; 1183 | 1184 | AGClientSocket.prototype.killProcedureConsumer = function (consumerId) { 1185 | this._procedureDemux.killConsumer(consumerId); 1186 | }; 1187 | 1188 | AGClientSocket.prototype.getProcedureConsumerStats = function (consumerId) { 1189 | return this._procedureDemux.getConsumerStats(consumerId); 1190 | }; 1191 | 1192 | AGClientSocket.prototype.getProcedureConsumerStatsList = function (procedureName) { 1193 | return this._procedureDemux.getConsumerStatsList(procedureName); 1194 | }; 1195 | 1196 | AGClientSocket.prototype.getAllProceduresConsumerStatsList = function () { 1197 | return this._procedureDemux.getConsumerStatsListAll(); 1198 | }; 1199 | 1200 | AGClientSocket.prototype.getProcedureBackpressure = function (procedureName) { 1201 | return this._procedureDemux.getBackpressure(procedureName); 1202 | }; 1203 | 1204 | AGClientSocket.prototype.getAllProceduresBackpressure = function () { 1205 | return this._procedureDemux.getBackpressureAll(); 1206 | }; 1207 | 1208 | AGClientSocket.prototype.getProcedureConsumerBackpressure = function (consumerId) { 1209 | return this._procedureDemux.getConsumerBackpressure(consumerId); 1210 | }; 1211 | 1212 | AGClientSocket.prototype.hasProcedureConsumer = function (procedureName, consumerId) { 1213 | return this._procedureDemux.hasConsumer(procedureName, consumerId); 1214 | }; 1215 | 1216 | AGClientSocket.prototype.hasAnyProcedureConsumer = function (consumerId) { 1217 | return this._procedureDemux.hasConsumerAll(consumerId); 1218 | }; 1219 | 1220 | // ---- Channel logic ---- 1221 | 1222 | AGClientSocket.prototype.channel = function (channelName) { 1223 | let currentChannel = this._channelMap[channelName]; 1224 | 1225 | let channelIterable = new AGChannel( 1226 | channelName, 1227 | this, 1228 | this._channelEventDemux, 1229 | this._channelDataDemux 1230 | ); 1231 | 1232 | return channelIterable; 1233 | }; 1234 | 1235 | AGClientSocket.prototype.closeChannel = function (channelName) { 1236 | this.channelCloseOutput(channelName); 1237 | this.channelCloseAllListeners(channelName); 1238 | }; 1239 | 1240 | AGClientSocket.prototype.closeAllChannelOutputs = function () { 1241 | this._channelDataDemux.closeAll(); 1242 | }; 1243 | 1244 | AGClientSocket.prototype.closeAllChannelListeners = function () { 1245 | this._channelEventDemux.closeAll(); 1246 | }; 1247 | 1248 | AGClientSocket.prototype.closeAllChannels = function () { 1249 | this.closeAllChannelOutputs(); 1250 | this.closeAllChannelListeners(); 1251 | }; 1252 | 1253 | AGClientSocket.prototype.killChannel = function (channelName) { 1254 | this.channelKillOutput(channelName); 1255 | this.channelKillAllListeners(channelName); 1256 | }; 1257 | 1258 | AGClientSocket.prototype.killAllChannelOutputs = function () { 1259 | this._channelDataDemux.killAll(); 1260 | }; 1261 | 1262 | AGClientSocket.prototype.killAllChannelListeners = function () { 1263 | this._channelEventDemux.killAll(); 1264 | }; 1265 | 1266 | AGClientSocket.prototype.killAllChannels = function () { 1267 | this.killAllChannelOutputs(); 1268 | this.killAllChannelListeners(); 1269 | }; 1270 | 1271 | AGClientSocket.prototype.killChannelOutputConsumer = function (consumerId) { 1272 | this._channelDataDemux.killConsumer(consumerId); 1273 | }; 1274 | 1275 | AGClientSocket.prototype.killChannelListenerConsumer = function (consumerId) { 1276 | this._channelEventDemux.killConsumer(consumerId); 1277 | }; 1278 | 1279 | AGClientSocket.prototype.getChannelOutputConsumerStats = function (consumerId) { 1280 | return this._channelDataDemux.getConsumerStats(consumerId); 1281 | }; 1282 | 1283 | AGClientSocket.prototype.getChannelListenerConsumerStats = function (consumerId) { 1284 | return this._channelEventDemux.getConsumerStats(consumerId); 1285 | }; 1286 | 1287 | AGClientSocket.prototype.getAllChannelOutputsConsumerStatsList = function () { 1288 | return this._channelDataDemux.getConsumerStatsListAll(); 1289 | }; 1290 | 1291 | AGClientSocket.prototype.getAllChannelListenersConsumerStatsList = function () { 1292 | return this._channelEventDemux.getConsumerStatsListAll(); 1293 | }; 1294 | 1295 | AGClientSocket.prototype.getChannelBackpressure = function (channelName) { 1296 | return Math.max( 1297 | this.channelGetOutputBackpressure(channelName), 1298 | this.channelGetAllListenersBackpressure(channelName) 1299 | ); 1300 | }; 1301 | 1302 | AGClientSocket.prototype.getAllChannelOutputsBackpressure = function () { 1303 | return this._channelDataDemux.getBackpressureAll(); 1304 | }; 1305 | 1306 | AGClientSocket.prototype.getAllChannelListenersBackpressure = function () { 1307 | return this._channelEventDemux.getBackpressureAll(); 1308 | }; 1309 | 1310 | AGClientSocket.prototype.getAllChannelsBackpressure = function () { 1311 | return Math.max( 1312 | this.getAllChannelOutputsBackpressure(), 1313 | this.getAllChannelListenersBackpressure() 1314 | ); 1315 | }; 1316 | 1317 | AGClientSocket.prototype.getChannelListenerConsumerBackpressure = function (consumerId) { 1318 | return this._channelEventDemux.getConsumerBackpressure(consumerId); 1319 | }; 1320 | 1321 | AGClientSocket.prototype.getChannelOutputConsumerBackpressure = function (consumerId) { 1322 | return this._channelDataDemux.getConsumerBackpressure(consumerId); 1323 | }; 1324 | 1325 | AGClientSocket.prototype.hasAnyChannelOutputConsumer = function (consumerId) { 1326 | return this._channelDataDemux.hasConsumerAll(consumerId); 1327 | }; 1328 | 1329 | AGClientSocket.prototype.hasAnyChannelListenerConsumer = function (consumerId) { 1330 | return this._channelEventDemux.hasConsumerAll(consumerId); 1331 | }; 1332 | 1333 | AGClientSocket.prototype.getChannelState = function (channelName) { 1334 | let channel = this._channelMap[channelName]; 1335 | if (channel) { 1336 | return channel.state; 1337 | } 1338 | return AGChannel.UNSUBSCRIBED; 1339 | }; 1340 | 1341 | AGClientSocket.prototype.getChannelOptions = function (channelName) { 1342 | let channel = this._channelMap[channelName]; 1343 | if (channel) { 1344 | return {...channel.options}; 1345 | } 1346 | return {}; 1347 | }; 1348 | 1349 | AGClientSocket.prototype._getAllChannelStreamNames = function (channelName) { 1350 | let streamNamesLookup = this._channelEventDemux.getConsumerStatsListAll() 1351 | .filter((stats) => { 1352 | return stats.stream.indexOf(`${channelName}/`) === 0; 1353 | }) 1354 | .reduce((accumulator, stats) => { 1355 | accumulator[stats.stream] = true; 1356 | return accumulator; 1357 | }, {}); 1358 | return Object.keys(streamNamesLookup); 1359 | }; 1360 | 1361 | AGClientSocket.prototype.channelCloseOutput = function (channelName) { 1362 | this._channelDataDemux.close(channelName); 1363 | }; 1364 | 1365 | AGClientSocket.prototype.channelCloseListener = function (channelName, eventName) { 1366 | this._channelEventDemux.close(`${channelName}/${eventName}`); 1367 | }; 1368 | 1369 | AGClientSocket.prototype.channelCloseAllListeners = function (channelName) { 1370 | let listenerStreams = this._getAllChannelStreamNames(channelName) 1371 | .forEach((streamName) => { 1372 | this._channelEventDemux.close(streamName); 1373 | }); 1374 | }; 1375 | 1376 | AGClientSocket.prototype.channelKillOutput = function (channelName) { 1377 | this._channelDataDemux.kill(channelName); 1378 | }; 1379 | 1380 | AGClientSocket.prototype.channelKillListener = function (channelName, eventName) { 1381 | this._channelEventDemux.kill(`${channelName}/${eventName}`); 1382 | }; 1383 | 1384 | AGClientSocket.prototype.channelKillAllListeners = function (channelName) { 1385 | let listenerStreams = this._getAllChannelStreamNames(channelName) 1386 | .forEach((streamName) => { 1387 | this._channelEventDemux.kill(streamName); 1388 | }); 1389 | }; 1390 | 1391 | AGClientSocket.prototype.channelGetOutputConsumerStatsList = function (channelName) { 1392 | return this._channelDataDemux.getConsumerStatsList(channelName); 1393 | }; 1394 | 1395 | AGClientSocket.prototype.channelGetListenerConsumerStatsList = function (channelName, eventName) { 1396 | return this._channelEventDemux.getConsumerStatsList(`${channelName}/${eventName}`); 1397 | }; 1398 | 1399 | AGClientSocket.prototype.channelGetAllListenersConsumerStatsList = function (channelName) { 1400 | return this._getAllChannelStreamNames(channelName) 1401 | .map((streamName) => { 1402 | return this._channelEventDemux.getConsumerStatsList(streamName); 1403 | }) 1404 | .reduce((accumulator, statsList) => { 1405 | statsList.forEach((stats) => { 1406 | accumulator.push(stats); 1407 | }); 1408 | return accumulator; 1409 | }, []); 1410 | }; 1411 | 1412 | AGClientSocket.prototype.channelGetOutputBackpressure = function (channelName) { 1413 | return this._channelDataDemux.getBackpressure(channelName); 1414 | }; 1415 | 1416 | AGClientSocket.prototype.channelGetListenerBackpressure = function (channelName, eventName) { 1417 | return this._channelEventDemux.getBackpressure(`${channelName}/${eventName}`); 1418 | }; 1419 | 1420 | AGClientSocket.prototype.channelGetAllListenersBackpressure = function (channelName) { 1421 | let listenerStreamBackpressures = this._getAllChannelStreamNames(channelName) 1422 | .map((streamName) => { 1423 | return this._channelEventDemux.getBackpressure(streamName); 1424 | }); 1425 | return Math.max(...listenerStreamBackpressures.concat(0)); 1426 | }; 1427 | 1428 | AGClientSocket.prototype.channelHasOutputConsumer = function (channelName, consumerId) { 1429 | return this._channelDataDemux.hasConsumer(channelName, consumerId); 1430 | }; 1431 | 1432 | AGClientSocket.prototype.channelHasListenerConsumer = function (channelName, eventName, consumerId) { 1433 | return this._channelEventDemux.hasConsumer(`${channelName}/${eventName}`, consumerId); 1434 | }; 1435 | 1436 | AGClientSocket.prototype.channelHasAnyListenerConsumer = function (channelName, consumerId) { 1437 | return this._getAllChannelStreamNames(channelName) 1438 | .some((streamName) => { 1439 | return this._channelEventDemux.hasConsumer(streamName, consumerId); 1440 | }); 1441 | }; 1442 | 1443 | AGClientSocket.prototype.subscriptions = function (includePending) { 1444 | let subs = []; 1445 | Object.keys(this._channelMap).forEach((channelName) => { 1446 | if (includePending || this._channelMap[channelName].state === AGChannel.SUBSCRIBED) { 1447 | subs.push(channelName); 1448 | } 1449 | }); 1450 | return subs; 1451 | }; 1452 | 1453 | AGClientSocket.prototype.isSubscribed = function (channelName, includePending) { 1454 | let channel = this._channelMap[channelName]; 1455 | if (includePending) { 1456 | return !!channel; 1457 | } 1458 | return !!channel && channel.state === AGChannel.SUBSCRIBED; 1459 | }; 1460 | 1461 | AGClientSocket.prototype.processPendingSubscriptions = function () { 1462 | this.preparingPendingSubscriptions = false; 1463 | let pendingChannels = []; 1464 | 1465 | Object.keys(this._channelMap).forEach((channelName) => { 1466 | let channel = this._channelMap[channelName]; 1467 | if (channel.state === AGChannel.PENDING) { 1468 | pendingChannels.push(channel); 1469 | } 1470 | }); 1471 | 1472 | pendingChannels.sort((a, b) => { 1473 | let ap = a.options.priority || 0; 1474 | let bp = b.options.priority || 0; 1475 | if (ap > bp) { 1476 | return -1; 1477 | } 1478 | if (ap < bp) { 1479 | return 1; 1480 | } 1481 | return 0; 1482 | }); 1483 | 1484 | pendingChannels.forEach((channel) => { 1485 | this._trySubscribe(channel); 1486 | }); 1487 | }; 1488 | 1489 | module.exports = AGClientSocket; 1490 | -------------------------------------------------------------------------------- /lib/factory.js: -------------------------------------------------------------------------------- 1 | const AGClientSocket = require('./clientsocket'); 2 | const uuid = require('uuid'); 3 | const scErrors = require('sc-errors'); 4 | const InvalidArgumentsError = scErrors.InvalidArgumentsError; 5 | 6 | function isUrlSecure() { 7 | return global.location && location.protocol === 'https:'; 8 | } 9 | 10 | function getPort(options, isSecureDefault) { 11 | let isSecure = options.secure == null ? isSecureDefault : options.secure; 12 | return options.port || (global.location && location.port ? location.port : isSecure ? 443 : 80); 13 | } 14 | 15 | function create(options) { 16 | options = options || {}; 17 | 18 | if (options.host && !options.host.match(/[^:]+:\d{2,5}/)) { 19 | throw new InvalidArgumentsError( 20 | 'The host option should include both' + 21 | ' the hostname and the port number in the format "hostname:port"' 22 | ); 23 | } 24 | 25 | if (options.host && options.hostname) { 26 | throw new InvalidArgumentsError( 27 | 'The host option should already include' + 28 | ' the hostname and the port number in the format "hostname:port"' + 29 | ' - Because of this, you should never use host and hostname options together' 30 | ); 31 | } 32 | 33 | if (options.host && options.port) { 34 | throw new InvalidArgumentsError( 35 | 'The host option should already include' + 36 | ' the hostname and the port number in the format "hostname:port"' + 37 | ' - Because of this, you should never use host and port options together' 38 | ); 39 | } 40 | 41 | let isSecureDefault = isUrlSecure(); 42 | 43 | let opts = { 44 | clientId: uuid.v4(), 45 | port: getPort(options, isSecureDefault), 46 | hostname: global.location && location.hostname || 'localhost', 47 | secure: isSecureDefault 48 | }; 49 | 50 | Object.assign(opts, options); 51 | 52 | return new AGClientSocket(opts); 53 | } 54 | 55 | module.exports = { 56 | create 57 | }; 58 | -------------------------------------------------------------------------------- /lib/transport.js: -------------------------------------------------------------------------------- 1 | const AGRequest = require('ag-request'); 2 | const querystring = require('querystring'); 3 | 4 | let WebSocket; 5 | let createWebSocket; 6 | 7 | if (global.WebSocket) { 8 | WebSocket = global.WebSocket; 9 | createWebSocket = function (uri, options) { 10 | return new WebSocket(uri); 11 | }; 12 | } else { 13 | WebSocket = require('ws'); 14 | createWebSocket = function (uri, options) { 15 | return new WebSocket(uri, null, options); 16 | }; 17 | } 18 | 19 | const scErrors = require('sc-errors'); 20 | const TimeoutError = scErrors.TimeoutError; 21 | const BadConnectionError = scErrors.BadConnectionError; 22 | 23 | function AGTransport(authEngine, codecEngine, options, wsOptions, handlers) { 24 | this.state = this.CLOSED; 25 | this.auth = authEngine; 26 | this.codec = codecEngine; 27 | this.options = options; 28 | this.wsOptions = wsOptions; 29 | this.protocolVersion = options.protocolVersion; 30 | this.connectTimeout = options.connectTimeout; 31 | this.pingTimeout = options.pingTimeout; 32 | this.pingTimeoutDisabled = !!options.pingTimeoutDisabled; 33 | this.callIdGenerator = options.callIdGenerator; 34 | this.authTokenName = options.authTokenName; 35 | this.isBufferingBatch = false; 36 | 37 | this._pingTimeoutTicker = null; 38 | this._callbackMap = {}; 39 | this._batchBuffer = []; 40 | 41 | if (!handlers) { 42 | handlers = {}; 43 | } 44 | 45 | this._onOpenHandler = handlers.onOpen || function () {}; 46 | this._onOpenAbortHandler = handlers.onOpenAbort || function () {}; 47 | this._onCloseHandler = handlers.onClose || function () {}; 48 | this._onEventHandler = handlers.onEvent || function () {}; 49 | this._onErrorHandler = handlers.onError || function () {}; 50 | this._onInboundInvokeHandler = handlers.onInboundInvoke || function () {}; 51 | this._onInboundTransmitHandler = handlers.onInboundTransmit || function () {}; 52 | 53 | // Open the connection. 54 | 55 | this.state = this.CONNECTING; 56 | let uri = this.uri(); 57 | 58 | let wsSocket = createWebSocket(uri, wsOptions); 59 | wsSocket.binaryType = this.options.binaryType; 60 | 61 | this.socket = wsSocket; 62 | 63 | wsSocket.onopen = () => { 64 | this._onOpen(); 65 | }; 66 | 67 | wsSocket.onclose = async (event) => { 68 | let code; 69 | if (event.code == null) { 70 | // This is to handle an edge case in React Native whereby 71 | // event.code is undefined when the mobile device is locked. 72 | // TODO: This is not ideal since this condition could also apply to 73 | // an abnormal close (no close control frame) which would be a 1006. 74 | code = 1005; 75 | } else { 76 | code = event.code; 77 | } 78 | this._destroy(code, event.reason); 79 | }; 80 | 81 | wsSocket.onmessage = (message, flags) => { 82 | this._onMessage(message.data); 83 | }; 84 | 85 | wsSocket.onerror = (error) => { 86 | // The onclose event will be called automatically after the onerror event 87 | // if the socket is connected - Otherwise, if it's in the middle of 88 | // connecting, we want to close it manually with a 1006 - This is necessary 89 | // to prevent inconsistent behavior when running the client in Node.js 90 | // vs in a browser. 91 | if (this.state === this.CONNECTING) { 92 | this._destroy(1006); 93 | } 94 | }; 95 | 96 | this._connectTimeoutRef = setTimeout(() => { 97 | this._destroy(4007); 98 | this.socket.close(4007); 99 | }, this.connectTimeout); 100 | 101 | if (this.protocolVersion === 1) { 102 | this._handlePing = (message) => { 103 | if (message === '#1') { 104 | this._resetPingTimeout(); 105 | if (this.socket.readyState === this.socket.OPEN) { 106 | this.send('#2'); 107 | } 108 | return true; 109 | } 110 | return false; 111 | }; 112 | } else { 113 | this._handlePing = (message) => { 114 | if (message === '') { 115 | this._resetPingTimeout(); 116 | if (this.socket.readyState === this.socket.OPEN) { 117 | this.send(''); 118 | } 119 | return true; 120 | } 121 | return false; 122 | }; 123 | } 124 | } 125 | 126 | AGTransport.CONNECTING = AGTransport.prototype.CONNECTING = 'connecting'; 127 | AGTransport.OPEN = AGTransport.prototype.OPEN = 'open'; 128 | AGTransport.CLOSED = AGTransport.prototype.CLOSED = 'closed'; 129 | 130 | AGTransport.prototype.uri = function () { 131 | let query = this.options.query || {}; 132 | let schema = this.options.secure ? 'wss' : 'ws'; 133 | 134 | if (this.options.timestampRequests) { 135 | query[this.options.timestampParam] = (new Date()).getTime(); 136 | } 137 | 138 | query = querystring.encode(query); 139 | 140 | if (query.length) { 141 | query = '?' + query; 142 | } 143 | 144 | let host; 145 | if (this.options.host) { 146 | host = this.options.host; 147 | } else { 148 | let port = ''; 149 | 150 | if (this.options.port && ((schema === 'wss' && this.options.port !== 443) 151 | || (schema === 'ws' && this.options.port !== 80))) { 152 | port = ':' + this.options.port; 153 | } 154 | host = this.options.hostname + port; 155 | } 156 | 157 | return schema + '://' + host + this.options.path + query; 158 | }; 159 | 160 | AGTransport.prototype._onOpen = async function () { 161 | clearTimeout(this._connectTimeoutRef); 162 | this._resetPingTimeout(); 163 | 164 | let status; 165 | 166 | try { 167 | status = await this._handshake(); 168 | } catch (err) { 169 | if (err.statusCode == null) { 170 | err.statusCode = 4003; 171 | } 172 | this._onError(err); 173 | this._destroy(err.statusCode, err.toString()); 174 | this.socket.close(err.statusCode); 175 | return; 176 | } 177 | 178 | this.state = this.OPEN; 179 | if (status) { 180 | this.pingTimeout = status.pingTimeout; 181 | } 182 | this._resetPingTimeout(); 183 | this._onOpenHandler(status); 184 | }; 185 | 186 | AGTransport.prototype._handshake = async function () { 187 | let token = await this.auth.loadToken(this.authTokenName); 188 | // Don't wait for this.state to be 'open'. 189 | // The underlying WebSocket (this.socket) is already open. 190 | let options = { 191 | force: true 192 | }; 193 | let status = await this.invoke('#handshake', {authToken: token}, options); 194 | if (status) { 195 | // Add the token which was used as part of authentication attempt 196 | // to the status object. 197 | status.authToken = token; 198 | if (status.authError) { 199 | status.authError = scErrors.hydrateError(status.authError); 200 | } 201 | } 202 | return status; 203 | }; 204 | 205 | AGTransport.prototype._abortAllPendingEventsDueToBadConnection = function (failureType) { 206 | Object.keys(this._callbackMap || {}).forEach((i) => { 207 | let eventObject = this._callbackMap[i]; 208 | delete this._callbackMap[i]; 209 | 210 | clearTimeout(eventObject.timeout); 211 | delete eventObject.timeout; 212 | 213 | let errorMessage = `Event "${eventObject.event}" was aborted due to a bad connection`; 214 | let badConnectionError = new BadConnectionError(errorMessage, failureType); 215 | 216 | let callback = eventObject.callback; 217 | delete eventObject.callback; 218 | 219 | callback.call(eventObject, badConnectionError, eventObject); 220 | }); 221 | }; 222 | 223 | AGTransport.prototype._destroy = function (code, reason) { 224 | let protocolReason = scErrors.socketProtocolErrorStatuses[code]; 225 | if (!reason && scErrors.socketProtocolErrorStatuses[code]) { 226 | reason = scErrors.socketProtocolErrorStatuses[code]; 227 | } 228 | delete this.socket.onopen; 229 | delete this.socket.onclose; 230 | delete this.socket.onmessage; 231 | delete this.socket.onerror; 232 | 233 | clearTimeout(this._connectTimeoutRef); 234 | clearTimeout(this._pingTimeoutTicker); 235 | 236 | if (this.state === this.OPEN) { 237 | this.state = this.CLOSED; 238 | this._abortAllPendingEventsDueToBadConnection('disconnect'); 239 | this._onCloseHandler({code, reason}); 240 | } else if (this.state === this.CONNECTING) { 241 | this.state = this.CLOSED; 242 | this._abortAllPendingEventsDueToBadConnection('connectAbort'); 243 | this._onOpenAbortHandler({code, reason}); 244 | } else if (this.state === this.CLOSED) { 245 | this._abortAllPendingEventsDueToBadConnection('connectAbort'); 246 | } 247 | }; 248 | 249 | AGTransport.prototype._processInboundPacket = function (packet, message) { 250 | if (packet && packet.event != null) { 251 | if (packet.cid == null) { 252 | this._onInboundTransmitHandler({...packet}); 253 | } else { 254 | let request = new AGRequest(this, packet.cid, packet.event, packet.data); 255 | this._onInboundInvokeHandler(request); 256 | } 257 | } else if (packet && packet.rid != null) { 258 | let eventObject = this._callbackMap[packet.rid]; 259 | if (eventObject) { 260 | clearTimeout(eventObject.timeout); 261 | delete eventObject.timeout; 262 | delete this._callbackMap[packet.rid]; 263 | 264 | if (eventObject.callback) { 265 | let rehydratedError = scErrors.hydrateError(packet.error); 266 | eventObject.callback(rehydratedError, packet.data); 267 | } 268 | } 269 | } else { 270 | this._onEventHandler({event: 'raw', data: {message}}); 271 | } 272 | }; 273 | 274 | AGTransport.prototype._onMessage = function (message) { 275 | this._onEventHandler({event: 'message', data: {message}}); 276 | 277 | if (this._handlePing(message)) { 278 | return; 279 | } 280 | 281 | let packet = this.decode(message); 282 | 283 | if (Array.isArray(packet)) { 284 | let len = packet.length; 285 | for (let i = 0; i < len; i++) { 286 | this._processInboundPacket(packet[i], message); 287 | } 288 | } else { 289 | this._processInboundPacket(packet, message); 290 | } 291 | }; 292 | 293 | AGTransport.prototype._onError = function (error) { 294 | this._onErrorHandler({error}); 295 | }; 296 | 297 | AGTransport.prototype._resetPingTimeout = function () { 298 | if (this.pingTimeoutDisabled) { 299 | return; 300 | } 301 | 302 | let now = (new Date()).getTime(); 303 | clearTimeout(this._pingTimeoutTicker); 304 | this._pingTimeoutTicker = setTimeout(() => { 305 | this._destroy(4000); 306 | this.socket.close(4000); 307 | }, this.pingTimeout); 308 | }; 309 | 310 | AGTransport.prototype.clearAllListeners = function () { 311 | this._onOpenHandler = function () {}; 312 | this._onOpenAbortHandler = function () {}; 313 | this._onCloseHandler = function () {}; 314 | this._onEventHandler = function () {}; 315 | this._onErrorHandler = function () {}; 316 | this._onInboundInvokeHandler = function () {}; 317 | this._onInboundTransmitHandler = function () {}; 318 | }; 319 | 320 | AGTransport.prototype.startBatch = function () { 321 | this.isBufferingBatch = true; 322 | this._batchBuffer = []; 323 | }; 324 | 325 | AGTransport.prototype.flushBatch = function () { 326 | this.isBufferingBatch = false; 327 | if (!this._batchBuffer.length) { 328 | return; 329 | } 330 | let serializedBatch = this.serializeObject(this._batchBuffer); 331 | this._batchBuffer = []; 332 | this.send(serializedBatch); 333 | }; 334 | 335 | AGTransport.prototype.cancelBatch = function () { 336 | this.isBufferingBatch = false; 337 | this._batchBuffer = []; 338 | }; 339 | 340 | AGTransport.prototype.getBytesReceived = function () { 341 | return this.socket.bytesReceived; 342 | }; 343 | 344 | AGTransport.prototype.close = function (code, reason) { 345 | if (this.state === this.OPEN || this.state === this.CONNECTING) { 346 | code = code || 1000; 347 | this._destroy(code, reason); 348 | this.socket.close(code, reason); 349 | } 350 | }; 351 | 352 | AGTransport.prototype.transmitObject = function (eventObject) { 353 | let simpleEventObject = { 354 | event: eventObject.event, 355 | data: eventObject.data 356 | }; 357 | 358 | if (eventObject.callback) { 359 | simpleEventObject.cid = eventObject.cid = this.callIdGenerator(); 360 | this._callbackMap[eventObject.cid] = eventObject; 361 | } 362 | 363 | this.sendObject(simpleEventObject); 364 | 365 | return eventObject.cid || null; 366 | }; 367 | 368 | AGTransport.prototype._handleEventAckTimeout = function (eventObject) { 369 | if (eventObject.cid) { 370 | delete this._callbackMap[eventObject.cid]; 371 | } 372 | delete eventObject.timeout; 373 | 374 | let callback = eventObject.callback; 375 | if (callback) { 376 | delete eventObject.callback; 377 | let error = new TimeoutError(`Event response for "${eventObject.event}" timed out`); 378 | callback.call(eventObject, error, eventObject); 379 | } 380 | }; 381 | 382 | AGTransport.prototype.transmit = function (event, data, options) { 383 | let eventObject = { 384 | event, 385 | data 386 | }; 387 | 388 | if (this.state === this.OPEN || options.force) { 389 | this.transmitObject(eventObject); 390 | } 391 | return Promise.resolve(); 392 | }; 393 | 394 | AGTransport.prototype.invokeRaw = function (event, data, options, callback) { 395 | let eventObject = { 396 | event, 397 | data, 398 | callback 399 | }; 400 | 401 | if (!options.noTimeout) { 402 | eventObject.timeout = setTimeout(() => { 403 | this._handleEventAckTimeout(eventObject); 404 | }, this.options.ackTimeout); 405 | } 406 | let cid = null; 407 | if (this.state === this.OPEN || options.force) { 408 | cid = this.transmitObject(eventObject); 409 | } 410 | return cid; 411 | }; 412 | 413 | AGTransport.prototype.invoke = function (event, data, options) { 414 | return new Promise((resolve, reject) => { 415 | this.invokeRaw(event, data, options, (err, data) => { 416 | if (err) { 417 | reject(err); 418 | return; 419 | } 420 | resolve(data); 421 | }); 422 | }); 423 | }; 424 | 425 | AGTransport.prototype.cancelPendingResponse = function (cid) { 426 | delete this._callbackMap[cid]; 427 | }; 428 | 429 | AGTransport.prototype.decode = function (message) { 430 | return this.codec.decode(message); 431 | }; 432 | 433 | AGTransport.prototype.encode = function (object) { 434 | return this.codec.encode(object); 435 | }; 436 | 437 | AGTransport.prototype.send = function (data) { 438 | if (this.socket.readyState !== this.socket.OPEN) { 439 | this._destroy(1005); 440 | } else { 441 | this.socket.send(data); 442 | } 443 | }; 444 | 445 | AGTransport.prototype.serializeObject = function (object) { 446 | let str; 447 | try { 448 | str = this.encode(object); 449 | } catch (error) { 450 | this._onError(error); 451 | return null; 452 | } 453 | return str; 454 | }; 455 | 456 | AGTransport.prototype.sendObject = function (object) { 457 | if (this.isBufferingBatch) { 458 | this._batchBuffer.push(object); 459 | return; 460 | } 461 | let str = this.serializeObject(object); 462 | if (str != null) { 463 | this.send(str); 464 | } 465 | }; 466 | 467 | module.exports = AGTransport; 468 | -------------------------------------------------------------------------------- /lib/wait.js: -------------------------------------------------------------------------------- 1 | function wait(duration) { 2 | return new Promise((resolve) => { 3 | setTimeout(() => { 4 | resolve(); 5 | }, duration); 6 | }); 7 | } 8 | 9 | module.exports = wait; 10 | -------------------------------------------------------------------------------- /lib/ws-browser.js: -------------------------------------------------------------------------------- 1 | let global; 2 | if (typeof WorkerGlobalScope !== 'undefined') { 3 | global = self; 4 | } else { 5 | global = typeof window !== 'undefined' && window || (function() { return this; })(); 6 | } 7 | 8 | const WebSocket = global.WebSocket || global.MozWebSocket; 9 | 10 | /** 11 | * WebSocket constructor. 12 | * 13 | * The third `opts` options object gets ignored in web browsers, since it's 14 | * non-standard, and throws a TypeError if passed to the constructor. 15 | * See: https://github.com/einaros/ws/issues/227 16 | * 17 | * @param {String} uri 18 | * @param {Array} protocols (optional) 19 | * @param {Object} opts (optional) 20 | * @api public 21 | */ 22 | 23 | function ws(uri, protocols, opts) { 24 | let instance; 25 | if (protocols) { 26 | instance = new WebSocket(uri, protocols); 27 | } else { 28 | instance = new WebSocket(uri); 29 | } 30 | return instance; 31 | } 32 | 33 | if (WebSocket) ws.prototype = WebSocket.prototype; 34 | 35 | module.exports = WebSocket ? ws : null; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "asyngular-client", 3 | "description": "Asyngular JavaScript client", 4 | "version": "6.2.4", 5 | "homepage": "https://asyngular.io/", 6 | "contributors": [ 7 | { 8 | "name": "Jonathan Gros-Dubois", 9 | "email": "grosjona@yahoo.com.au" 10 | } 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/SocketCluster/asyngular-client.git" 15 | }, 16 | "scripts": { 17 | "build": "gulp browserify; gulp minify", 18 | "test": "mocha --reporter spec --timeout 3000 --slow 3000" 19 | }, 20 | "dependencies": { 21 | "ag-channel": "^4.0.2", 22 | "ag-request": "^1.0.0", 23 | "async-stream-emitter": "^3.0.2", 24 | "buffer": "^5.2.1", 25 | "linked-list": "^0.1.0", 26 | "lodash.clonedeep": "^4.5.0", 27 | "querystring": "^0.2.0", 28 | "sc-errors": "^2.0.1", 29 | "sc-formatter": "^3.0.2", 30 | "stream-demux": "^7.0.1", 31 | "uuid": "^3.2.1", 32 | "ws": "^7.1.0" 33 | }, 34 | "browser": { 35 | "ws": "./lib/ws-browser.js" 36 | }, 37 | "readmeFilename": "README.md", 38 | "license": "MIT", 39 | "devDependencies": { 40 | "@babel/core": "^7.7.4", 41 | "@babel/preset-env": "^7.7.4", 42 | "asyngular-server": "^6.2.0", 43 | "babel-plugin-minify-dead-code-elimination": "^0.5.0", 44 | "browserify": "^16.2.3", 45 | "es-check": "^5.1.0", 46 | "gulp": "^4.0.0", 47 | "gulp-babel": "^8.0.0", 48 | "gulp-cli": "^2.0.1", 49 | "gulp-convert-newline": "^0.0.3", 50 | "gulp-insert": "^0.5.0", 51 | "gulp-rename": "^1.4.0", 52 | "gulp-uglify": "^3.0.1", 53 | "localStorage": "^1.0.3", 54 | "mocha": "^5.2.0", 55 | "uglify-es": "^3.3.9", 56 | "vinyl-source-stream": "^2.0.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/integration.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const asyngularServer = require('asyngular-server'); 3 | const AGAction = require('asyngular-server/action'); 4 | const asyngularClient = require('../'); 5 | const localStorage = require('localStorage'); 6 | 7 | // Add to the global scope like in browser. 8 | global.localStorage = localStorage; 9 | 10 | const PORT_NUMBER = 8009; 11 | 12 | let clientOptions; 13 | let serverOptions; 14 | 15 | let allowedUsers = { 16 | bob: true, 17 | kate: true, 18 | alice: true 19 | }; 20 | 21 | let server, client; 22 | let validSignedAuthTokenBob = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImV4cCI6MzE2Mzc1ODk3ODIxNTQ4NywiaWF0IjoxNTAyNzQ3NzQ2fQ.GLf_jqi_qUSCRahxe2D2I9kD8iVIs0d4xTbiZMRiQq4'; 23 | let validSignedAuthTokenKate = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImthdGUiLCJleHAiOjMxNjM3NTg5NzgyMTU0ODcsImlhdCI6MTUwMjc0Nzc5NX0.Yfb63XvDt9Wk0wHSDJ3t7Qb1F0oUVUaM5_JKxIE2kyw'; 24 | let invalidSignedAuthToken = 'fakebGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.fakec2VybmFtZSI6ImJvYiIsImlhdCI6MTUwMjYyNTIxMywiZXhwIjoxNTAyNzExNjEzfQ.fakemYcOOjM9bzmS4UYRvlWSk_lm3WGHvclmFjLbyOk'; 25 | 26 | const TOKEN_EXPIRY_IN_SECONDS = 60 * 60 * 24 * 366 * 5000; 27 | 28 | function wait(duration) { 29 | return new Promise((resolve) => { 30 | setTimeout(() => { 31 | resolve(); 32 | }, duration); 33 | }); 34 | }; 35 | 36 | function connectionHandler(socket) { 37 | async function handleLogin() { 38 | let rpc = await socket.procedure('login').once(); 39 | if (allowedUsers[rpc.data.username]) { 40 | rpc.data.exp = Math.round(Date.now() / 1000) + TOKEN_EXPIRY_IN_SECONDS; 41 | socket.setAuthToken(rpc.data); 42 | rpc.end(); 43 | } else { 44 | let err = new Error('Failed to login'); 45 | err.name = 'FailedLoginError'; 46 | rpc.error(err); 47 | } 48 | } 49 | handleLogin(); 50 | 51 | async function handleSetAuthKey() { 52 | let rpc = await socket.procedure('setAuthKey').once(); 53 | server.signatureKey = rpc.data; 54 | server.verificationKey = rpc.data; 55 | rpc.end(); 56 | } 57 | handleSetAuthKey(); 58 | 59 | async function handlePerformTask() { 60 | for await (let rpc of socket.procedure('performTask')) { 61 | setTimeout(function () { 62 | rpc.end(); 63 | }, 1000); 64 | } 65 | } 66 | handlePerformTask(); 67 | }; 68 | 69 | describe('Integration tests', function () { 70 | beforeEach('Run the server before start', async function () { 71 | serverOptions = { 72 | authKey: 'testkey', 73 | ackTimeout: 200 74 | }; 75 | 76 | server = asyngularServer.listen(PORT_NUMBER, serverOptions); 77 | async function handleServerConnection() { 78 | for await (let {socket} of server.listener('connection')) { 79 | connectionHandler(socket); 80 | } 81 | } 82 | handleServerConnection(); 83 | 84 | clientOptions = { 85 | hostname: '127.0.0.1', 86 | port: PORT_NUMBER, 87 | ackTimeout: 200 88 | }; 89 | 90 | await server.listener('ready').once(); 91 | }); 92 | 93 | afterEach('Shut down server and clients afterwards', async function () { 94 | let cleanupTasks = []; 95 | global.localStorage.removeItem('asyngular.authToken'); 96 | if (client) { 97 | if (client.state !== client.CLOSED) { 98 | cleanupTasks.push( 99 | Promise.race([ 100 | client.listener('disconnect').once(), 101 | client.listener('connectAbort').once() 102 | ]) 103 | ); 104 | client.disconnect(); 105 | } else { 106 | client.disconnect(); 107 | } 108 | } 109 | cleanupTasks.push( 110 | (async () => { 111 | server.httpServer.close(); 112 | await server.close(); 113 | })() 114 | ); 115 | await Promise.all(cleanupTasks); 116 | }); 117 | 118 | describe('Creation', function () { 119 | it('Should automatically connect socket on creation by default', async function () { 120 | clientOptions = { 121 | hostname: '127.0.0.1', 122 | port: PORT_NUMBER 123 | }; 124 | 125 | client = asyngularClient.create(clientOptions); 126 | 127 | assert.equal(client.state, client.CONNECTING); 128 | }); 129 | 130 | it('Should not automatically connect socket if autoConnect is set to false', async function () { 131 | clientOptions = { 132 | hostname: '127.0.0.1', 133 | port: PORT_NUMBER, 134 | autoConnect: false 135 | }; 136 | 137 | client = asyngularClient.create(clientOptions); 138 | 139 | assert.equal(client.state, client.CLOSED); 140 | }); 141 | }); 142 | 143 | describe('Errors', function () { 144 | it('Should be able to emit the error event locally on the socket', async function () { 145 | client = asyngularClient.create(clientOptions); 146 | let err = null; 147 | 148 | (async () => { 149 | for await (let {error} of client.listener('error')) { 150 | err = error; 151 | } 152 | })(); 153 | 154 | (async () => { 155 | for await (let status of client.listener('connect')) { 156 | let error = new Error('Custom error'); 157 | error.name = 'CustomError'; 158 | client.emit('error', {error}); 159 | } 160 | })(); 161 | 162 | await wait(100); 163 | 164 | assert.notEqual(err, null); 165 | assert.equal(err.name, 'CustomError'); 166 | }); 167 | }); 168 | 169 | describe('Authentication', function () { 170 | it('Should not send back error if JWT is not provided in handshake', async function () { 171 | client = asyngularClient.create(clientOptions); 172 | let event = await client.listener('connect').once(); 173 | assert.equal(event.authError === undefined, true); 174 | }); 175 | 176 | it('Should be authenticated on connect if previous JWT token is present', async function () { 177 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 178 | client = asyngularClient.create(clientOptions); 179 | 180 | let event = await client.listener('connect').once(); 181 | assert.equal(client.authState, 'authenticated'); 182 | assert.equal(event.isAuthenticated, true); 183 | assert.equal(event.authError === undefined, true); 184 | }); 185 | 186 | it('Should send back error if JWT is invalid during handshake', async function () { 187 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 188 | client = asyngularClient.create(clientOptions); 189 | 190 | let event = await client.listener('connect').once(); 191 | assert.notEqual(event, null); 192 | assert.equal(event.isAuthenticated, true); 193 | assert.equal(event.authError, null); 194 | 195 | assert.notEqual(client.signedAuthToken, null); 196 | assert.notEqual(client.authToken, null); 197 | 198 | // Change the setAuthKey to invalidate the current token. 199 | await client.invoke('setAuthKey', 'differentAuthKey'); 200 | 201 | client.disconnect(); 202 | client.connect(); 203 | 204 | event = await client.listener('connect').once(); 205 | 206 | assert.equal(event.isAuthenticated, false); 207 | assert.notEqual(event.authError, null); 208 | assert.equal(event.authError.name, 'AuthTokenInvalidError'); 209 | 210 | // When authentication fails, the auth token properties on the client 211 | // socket should be set to null; that way it's not going to keep 212 | // throwing the same error every time the socket tries to connect. 213 | assert.equal(client.signedAuthToken, null); 214 | assert.equal(client.authToken, null); 215 | 216 | // Set authKey back to what it was. 217 | await client.invoke('setAuthKey', serverOptions.authKey); 218 | }); 219 | 220 | it('Should allow switching between users', async function () { 221 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 222 | client = asyngularClient.create(clientOptions); 223 | let authenticateTriggered = false; 224 | let authStateChangeTriggered = false; 225 | 226 | await client.listener('connect').once(); 227 | 228 | assert.notEqual(client.authToken, null); 229 | assert.equal(client.authToken.username, 'bob'); 230 | 231 | client.invoke('login', {username: 'alice'}); 232 | 233 | (async () => { 234 | await client.listener('authenticate').once(); 235 | authenticateTriggered = true; 236 | assert.equal(client.authState, 'authenticated'); 237 | assert.notEqual(client.authToken, null); 238 | assert.equal(client.authToken.username, 'alice'); 239 | })(); 240 | 241 | (async () => { 242 | await client.listener('authStateChange').once(); 243 | authStateChangeTriggered = true; 244 | })(); 245 | 246 | await wait(100); 247 | 248 | assert.equal(authenticateTriggered, true); 249 | assert.equal(authStateChangeTriggered, false); 250 | }); 251 | 252 | it('If token engine signing is synchronous, authentication can be captured using the authenticate event', async function () { 253 | let port = 8509; 254 | let customServer = asyngularServer.listen(port, { 255 | authKey: serverOptions.authKey, 256 | authSignAsync: false 257 | }); 258 | 259 | (async () => { 260 | let {socket} = await customServer.listener('connection').once(); 261 | connectionHandler(socket); 262 | })(); 263 | 264 | await customServer.listener('ready').once(); 265 | 266 | client = asyngularClient.create({ 267 | hostname: clientOptions.hostname, 268 | port: port 269 | }); 270 | 271 | await client.listener('connect').once(); 272 | 273 | await Promise.all([ 274 | client.invoke('login', {username: 'bob'}), 275 | client.listener('authenticate').once() 276 | ]); 277 | 278 | assert.equal(client.authState, 'authenticated'); 279 | assert.notEqual(client.authToken, null); 280 | assert.equal(client.authToken.username, 'bob'); 281 | 282 | customServer.httpServer.close(); 283 | await customServer.close(); 284 | }); 285 | 286 | it('If token engine signing is asynchronous, authentication can be captured using the authenticate event', async function () { 287 | let port = 8510; 288 | let customServer = asyngularServer.listen(port, { 289 | authKey: serverOptions.authKey, 290 | authSignAsync: true 291 | }); 292 | 293 | (async () => { 294 | let {socket} = await customServer.listener('connection').once(); 295 | connectionHandler(socket); 296 | })(); 297 | 298 | await customServer.listener('ready').once(); 299 | 300 | client = asyngularClient.create({ 301 | hostname: clientOptions.hostname, 302 | port: port 303 | }); 304 | 305 | await client.listener('connect').once(); 306 | 307 | await Promise.all([ 308 | client.invoke('login', {username: 'bob'}), 309 | client.listener('authenticate').once() 310 | ]); 311 | 312 | assert.equal(client.authState, 'authenticated'); 313 | assert.notEqual(client.authToken, null); 314 | assert.equal(client.authToken.username, 'bob'); 315 | 316 | customServer.httpServer.close(); 317 | await customServer.close(); 318 | }); 319 | 320 | it('If token verification is synchronous, authentication can be captured using the authenticate event', async function () { 321 | let port = 8511; 322 | customServer = asyngularServer.listen(port, { 323 | authKey: serverOptions.authKey, 324 | authVerifyAsync: false 325 | }); 326 | 327 | (async () => { 328 | let {socket} = await customServer.listener('connection').once(); 329 | connectionHandler(socket); 330 | })(); 331 | 332 | await customServer.listener('ready').once(); 333 | 334 | client = asyngularClient.create({ 335 | hostname: clientOptions.hostname, 336 | port: port 337 | }); 338 | 339 | await client.listener('connect').once(); 340 | 341 | await Promise.all([ 342 | (async () => { 343 | await client.listener('authenticate').once(); 344 | await client.listener('disconnect').once(); 345 | client.connect(); 346 | let event = await client.listener('connect').once(); 347 | assert.equal(event.isAuthenticated, true); 348 | assert.notEqual(client.authToken, null); 349 | assert.equal(client.authToken.username, 'bob'); 350 | })(), 351 | (async () => { 352 | await Promise.all([ 353 | client.invoke('login', {username: 'bob'}), 354 | client.listener('authenticate').once() 355 | ]); 356 | client.disconnect(); 357 | })() 358 | ]); 359 | 360 | customServer.httpServer.close(); 361 | await customServer.close(); 362 | }); 363 | 364 | it('Should start out in pending authState and switch to unauthenticated if no token exists', async function () { 365 | client = asyngularClient.create(clientOptions); 366 | assert.equal(client.authState, 'unauthenticated'); 367 | 368 | (async () => { 369 | let status = await client.listener('authStateChange').once(); 370 | throw new Error('authState should not change after connecting without a token'); 371 | })(); 372 | 373 | await wait(1000); 374 | }); 375 | 376 | it('Should deal with auth engine errors related to saveToken function', async function () { 377 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 378 | client = asyngularClient.create(clientOptions); 379 | 380 | let caughtError; 381 | 382 | (async () => { 383 | for await (let {error} of client.listener('error')) { 384 | caughtError = error; 385 | } 386 | })(); 387 | 388 | await client.listener('connect').once(); 389 | 390 | let oldSaveTokenFunction = client.auth.saveToken; 391 | client.auth.saveToken = function (tokenName, tokenValue, options) { 392 | let err = new Error('Failed to save token'); 393 | err.name = 'FailedToSaveTokenError'; 394 | return Promise.reject(err); 395 | }; 396 | assert.notEqual(client.authToken, null); 397 | assert.equal(client.authToken.username, 'bob'); 398 | 399 | let authStatus = await client.authenticate(validSignedAuthTokenKate); 400 | 401 | assert.notEqual(authStatus, null); 402 | // The error here comes from the client auth engine and does not prevent the 403 | // authentication from taking place, it only prevents the token from being 404 | // stored correctly on the client. 405 | assert.equal(authStatus.isAuthenticated, true); 406 | // authError should be null because the error comes from the client-side auth engine 407 | // whereas authError is for server-side errors (e.g. JWT errors). 408 | assert.equal(authStatus.authError, null); 409 | 410 | assert.notEqual(client.authToken, null); 411 | assert.equal(client.authToken.username, 'kate'); 412 | 413 | await wait(10); 414 | 415 | assert.notEqual(caughtError, null); 416 | assert.equal(caughtError.name, 'FailedToSaveTokenError'); 417 | client.auth.saveToken = oldSaveTokenFunction; 418 | }); 419 | 420 | it('Should gracefully handle authenticate abortion due to disconnection', async function () { 421 | client = asyngularClient.create(clientOptions); 422 | 423 | await client.listener('connect').once(); 424 | 425 | let authenticatePromise = await client.authenticate(validSignedAuthTokenBob); 426 | client.disconnect(); 427 | 428 | try { 429 | await authenticatePromise; 430 | } catch (err) { 431 | assert.notEqual(err, null); 432 | assert.equal(err.name, 'BadConnectionError'); 433 | assert.equal(client.authState, 'unauthenticated'); 434 | } 435 | }); 436 | 437 | it('Should go through the correct sequence of authentication state changes when dealing with disconnections; part 1', async function () { 438 | client = asyngularClient.create(clientOptions); 439 | 440 | let expectedAuthStateChanges = [ 441 | 'unauthenticated->authenticated' 442 | ]; 443 | let authStateChanges = []; 444 | 445 | (async () => { 446 | for await (status of client.listener('authStateChange')) { 447 | authStateChanges.push(status.oldAuthState + '->' + status.newAuthState); 448 | } 449 | })(); 450 | 451 | assert.equal(client.authState, 'unauthenticated'); 452 | 453 | await client.listener('connect').once(); 454 | assert.equal(client.authState, 'unauthenticated'); 455 | 456 | (async () => { 457 | await Promise.all([ 458 | client.invoke('login', {username: 'bob'}), 459 | client.listener('authenticate').once() 460 | ]); 461 | client.disconnect(); 462 | })(); 463 | 464 | assert.equal(client.authState, 'unauthenticated'); 465 | 466 | let {signedAuthToken, authToken} = await client.listener('authenticate').once(); 467 | 468 | assert.notEqual(signedAuthToken, null); 469 | assert.notEqual(authToken, null); 470 | 471 | assert.equal(client.authState, 'authenticated'); 472 | 473 | await client.listener('disconnect').once(); 474 | 475 | // In case of disconnection, the socket maintains the last known auth state. 476 | assert.equal(client.authState, 'authenticated'); 477 | 478 | await client.authenticate(signedAuthToken); 479 | 480 | assert.equal(client.authState, 'authenticated'); 481 | assert.equal(JSON.stringify(authStateChanges), JSON.stringify(expectedAuthStateChanges)); 482 | client.closeListener('authStateChange'); 483 | }); 484 | 485 | it('Should go through the correct sequence of authentication state changes when dealing with disconnections; part 2', async function () { 486 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 487 | client = asyngularClient.create(clientOptions); 488 | 489 | let expectedAuthStateChanges = [ 490 | 'unauthenticated->authenticated', 491 | 'authenticated->unauthenticated', 492 | 'unauthenticated->authenticated', 493 | 'authenticated->unauthenticated' 494 | ]; 495 | let authStateChanges = []; 496 | 497 | (async () => { 498 | for await (status of client.listener('authStateChange')) { 499 | authStateChanges.push(status.oldAuthState + '->' + status.newAuthState); 500 | } 501 | })(); 502 | 503 | assert.equal(client.authState, 'unauthenticated'); 504 | 505 | await client.listener('connect').once(); 506 | 507 | assert.equal(client.authState, 'authenticated'); 508 | client.deauthenticate(); 509 | assert.equal(client.authState, 'unauthenticated'); 510 | let authenticatePromise = client.authenticate(validSignedAuthTokenBob); 511 | assert.equal(client.authState, 'unauthenticated'); 512 | 513 | await authenticatePromise; 514 | 515 | assert.equal(client.authState, 'authenticated'); 516 | 517 | client.disconnect(); 518 | 519 | assert.equal(client.authState, 'authenticated'); 520 | await client.deauthenticate(); 521 | assert.equal(client.authState, 'unauthenticated'); 522 | 523 | assert.equal(JSON.stringify(authStateChanges), JSON.stringify(expectedAuthStateChanges)); 524 | }); 525 | 526 | it('Should go through the correct sequence of authentication state changes when dealing with disconnections; part 3', async function () { 527 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 528 | client = asyngularClient.create(clientOptions); 529 | 530 | let expectedAuthStateChanges = [ 531 | 'unauthenticated->authenticated', 532 | 'authenticated->unauthenticated' 533 | ]; 534 | let authStateChanges = []; 535 | 536 | (async () => { 537 | for await (let status of client.listener('authStateChange')) { 538 | authStateChanges.push(status.oldAuthState + '->' + status.newAuthState); 539 | } 540 | })(); 541 | 542 | assert.equal(client.authState, 'unauthenticated'); 543 | 544 | await client.listener('connect').once(); 545 | 546 | assert.equal(client.authState, 'authenticated'); 547 | let authenticatePromise = client.authenticate(invalidSignedAuthToken); 548 | assert.equal(client.authState, 'authenticated'); 549 | 550 | try { 551 | await authenticatePromise; 552 | } catch (err) { 553 | assert.notEqual(err, null); 554 | assert.equal(err.name, 'AuthTokenInvalidError'); 555 | assert.equal(client.authState, 'unauthenticated'); 556 | assert.equal(JSON.stringify(authStateChanges), JSON.stringify(expectedAuthStateChanges)); 557 | } 558 | }); 559 | 560 | it('Should go through the correct sequence of authentication state changes when authenticating as a user while already authenticated as another user', async function () { 561 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 562 | client = asyngularClient.create(clientOptions); 563 | 564 | let expectedAuthStateChanges = [ 565 | 'unauthenticated->authenticated' 566 | ]; 567 | let authStateChanges = []; 568 | 569 | (async () => { 570 | for await (let status of client.listener('authStateChange')) { 571 | authStateChanges.push(status.oldAuthState + '->' + status.newAuthState); 572 | } 573 | })(); 574 | 575 | let expectedAuthTokenChanges = [ 576 | validSignedAuthTokenBob, 577 | validSignedAuthTokenKate 578 | ]; 579 | let authTokenChanges = []; 580 | 581 | (async () => { 582 | for await (let event of client.listener('authenticate')) { 583 | authTokenChanges.push(client.signedAuthToken); 584 | } 585 | })(); 586 | 587 | (async () => { 588 | for await (let event of client.listener('deauthenticate')) { 589 | authTokenChanges.push(client.signedAuthToken); 590 | } 591 | })(); 592 | 593 | assert.equal(client.authState, 'unauthenticated'); 594 | 595 | await client.listener('connect').once(); 596 | 597 | assert.equal(client.authState, 'authenticated'); 598 | assert.equal(client.authToken.username, 'bob'); 599 | let authenticatePromise = client.authenticate(validSignedAuthTokenKate); 600 | 601 | assert.equal(client.authState, 'authenticated'); 602 | 603 | await authenticatePromise; 604 | 605 | assert.equal(client.authState, 'authenticated'); 606 | assert.equal(client.authToken.username, 'kate'); 607 | assert.equal(JSON.stringify(authStateChanges), JSON.stringify(expectedAuthStateChanges)); 608 | assert.equal(JSON.stringify(authTokenChanges), JSON.stringify(expectedAuthTokenChanges)); 609 | }); 610 | 611 | it('Should wait for socket to be authenticated before subscribing to waitForAuth channel', async function () { 612 | client = asyngularClient.create(clientOptions); 613 | 614 | let privateChannel = client.subscribe('priv', {waitForAuth: true}); 615 | assert.equal(privateChannel.state, 'pending'); 616 | 617 | await client.listener('connect').once(); 618 | assert.equal(privateChannel.state, 'pending'); 619 | 620 | let authState = null; 621 | 622 | (async () => { 623 | await client.invoke('login', {username: 'bob'}); 624 | authState = client.authState; 625 | })(); 626 | 627 | await client.listener('subscribe').once(); 628 | assert.equal(privateChannel.state, 'subscribed'); 629 | 630 | client.disconnect(); 631 | assert.equal(privateChannel.state, 'pending'); 632 | 633 | client.authenticate(validSignedAuthTokenBob); 634 | await client.listener('subscribe').once(); 635 | assert.equal(privateChannel.state, 'subscribed'); 636 | 637 | assert.equal(authState, 'authenticated'); 638 | }); 639 | 640 | it('Subscriptions (including those with waitForAuth option) should have priority over the authenticate action', async function () { 641 | global.localStorage.setItem('asyngular.authToken', validSignedAuthTokenBob); 642 | client = asyngularClient.create(clientOptions); 643 | 644 | let expectedAuthStateChanges = [ 645 | 'unauthenticated->authenticated', 646 | 'authenticated->unauthenticated' 647 | ]; 648 | let initialSignedAuthToken; 649 | let authStateChanges = []; 650 | 651 | (async () => { 652 | for await (let status of client.listener('authStateChange')) { 653 | authStateChanges.push(status.oldAuthState + '->' + status.newAuthState); 654 | } 655 | })(); 656 | 657 | (async () => { 658 | let error = null; 659 | try { 660 | await client.authenticate(invalidSignedAuthToken); 661 | } catch (err) { 662 | error = err; 663 | } 664 | assert.notEqual(error, null); 665 | assert.equal(error.name, 'AuthTokenInvalidError'); 666 | })(); 667 | 668 | let privateChannel = client.subscribe('priv', {waitForAuth: true}); 669 | assert.equal(privateChannel.state, 'pending'); 670 | 671 | (async () => { 672 | let event = await client.listener('connect').once(); 673 | initialSignedAuthToken = client.signedAuthToken; 674 | assert.equal(event.isAuthenticated, true); 675 | assert.equal(privateChannel.state, 'pending'); 676 | 677 | await Promise.race([ 678 | (async () => { 679 | let err = await privateChannel.listener('subscribeFail').once(); 680 | // This shouldn't happen because the subscription should be 681 | // processed before the authenticate() call with the invalid token fails. 682 | throw new Error('Failed to subscribe to channel: ' + err.message); 683 | })(), 684 | (async () => { 685 | await privateChannel.listener('subscribe').once(); 686 | assert.equal(privateChannel.state, 'subscribed'); 687 | })() 688 | ]); 689 | })(); 690 | 691 | (async () => { 692 | // The subscription already went through so it should still be subscribed. 693 | let {oldSignedAuthToken, oldAuthToken} = await client.listener('deauthenticate').once(); 694 | // The subscription already went through so it should still be subscribed. 695 | assert.equal(privateChannel.state, 'subscribed'); 696 | assert.equal(client.authState, 'unauthenticated'); 697 | assert.equal(client.authToken, null); 698 | 699 | assert.notEqual(oldAuthToken, null); 700 | assert.equal(oldAuthToken.username, 'bob'); 701 | assert.equal(oldSignedAuthToken, initialSignedAuthToken); 702 | 703 | let privateChannel2 = client.subscribe('priv2', {waitForAuth: true}); 704 | 705 | await privateChannel2.listener('subscribe').once(); 706 | 707 | // This line should not execute. 708 | throw new Error('Should not subscribe because the socket is not authenticated'); 709 | })(); 710 | 711 | await wait(1000); 712 | client.closeListener('authStateChange'); 713 | assert.equal(JSON.stringify(authStateChanges), JSON.stringify(expectedAuthStateChanges)); 714 | }); 715 | 716 | it('Should trigger the close event if the socket disconnects in the middle of the handshake phase', async function () { 717 | client = asyngularClient.create(clientOptions); 718 | let aborted = false; 719 | let diconnected = false; 720 | let closed = false; 721 | 722 | (async () => { 723 | await client.listener('connectAbort').once(); 724 | aborted = true; 725 | })(); 726 | 727 | (async () => { 728 | await client.listener('disconnect').once(); 729 | diconnected = true; 730 | })(); 731 | 732 | (async () => { 733 | await client.listener('close').once(); 734 | closed = true; 735 | })(); 736 | 737 | client.disconnect(); 738 | 739 | await wait(300); 740 | 741 | assert.equal(aborted, true); 742 | assert.equal(diconnected, false); 743 | assert.equal(closed, true); 744 | }); 745 | 746 | it('Should trigger the close event if the socket disconnects after the handshake phase', async function () { 747 | client = asyngularClient.create(clientOptions); 748 | let aborted = false; 749 | let diconnected = false; 750 | let closed = false; 751 | 752 | (async () => { 753 | await client.listener('connectAbort').once(); 754 | aborted = true; 755 | })(); 756 | 757 | (async () => { 758 | await client.listener('disconnect').once(); 759 | diconnected = true; 760 | })(); 761 | 762 | (async () => { 763 | await client.listener('close').once(); 764 | closed = true; 765 | })(); 766 | 767 | (async () => { 768 | for await (let event of client.listener('connect')) { 769 | client.disconnect(); 770 | } 771 | })(); 772 | 773 | await wait(300); 774 | 775 | assert.equal(aborted, false); 776 | assert.equal(diconnected, true); 777 | assert.equal(closed, true); 778 | }); 779 | }); 780 | 781 | describe('Transmitting remote events', function () { 782 | it('Should not throw error on socket if ackTimeout elapses before response to event is sent back', async function () { 783 | client = asyngularClient.create(clientOptions); 784 | 785 | let caughtError; 786 | let clientError; 787 | 788 | (async () => { 789 | for await (let {error} of client.listener('error')) { 790 | clientError = error; 791 | } 792 | })(); 793 | 794 | let responseError; 795 | 796 | for await (let event of client.listener('connect')) { 797 | try { 798 | await client.invoke('performTask', 123); 799 | } catch (err) { 800 | responseError = err; 801 | } 802 | await wait(250); 803 | try { 804 | client.disconnect(); 805 | } catch (err) { 806 | caughtError = err; 807 | } 808 | break; 809 | } 810 | 811 | assert.notEqual(responseError, null); 812 | assert.equal(caughtError, null); 813 | }); 814 | }); 815 | 816 | describe('Pub/sub', function () { 817 | let publisherClient; 818 | let lastServerMessage = null; 819 | 820 | beforeEach('Setup publisher client', async function () { 821 | publisherClient = asyngularClient.create(clientOptions); 822 | 823 | server.removeMiddleware(server.MIDDLEWARE_INBOUND); 824 | server.setMiddleware(server.MIDDLEWARE_INBOUND, async (middlewareStream) => { 825 | for await (let action of middlewareStream) { 826 | if (action.type === AGAction.PUBLISH_IN) { 827 | lastServerMessage = action.data; 828 | } 829 | action.allow(); 830 | } 831 | }); 832 | }); 833 | 834 | afterEach('Destroy publisher client', async function () { 835 | publisherClient.disconnect(); 836 | }); 837 | 838 | it('Should receive transmitted publish messages if subscribed to channel', async function () { 839 | client = asyngularClient.create(clientOptions); 840 | 841 | let channel = client.subscribe('foo'); 842 | await channel.listener('subscribe').once(); 843 | 844 | (async () => { 845 | await wait(10); 846 | publisherClient.transmitPublish('foo', 'hello'); 847 | await wait(20); 848 | publisherClient.transmitPublish('foo', 'world'); 849 | publisherClient.transmitPublish('foo', {abc: 123}); 850 | await wait(10); 851 | channel.close(); 852 | })(); 853 | 854 | let receivedMessages = []; 855 | 856 | for await (let message of channel) { 857 | receivedMessages.push(message); 858 | } 859 | 860 | assert.equal(receivedMessages.length, 3); 861 | assert.equal(receivedMessages[0], 'hello'); 862 | assert.equal(receivedMessages[1], 'world'); 863 | assert.equal(JSON.stringify(receivedMessages[2]), JSON.stringify({abc: 123})); 864 | }); 865 | 866 | it('Should receive invoked publish messages if subscribed to channel', async function () { 867 | client = asyngularClient.create(clientOptions); 868 | 869 | let channel = client.subscribe('bar'); 870 | await channel.listener('subscribe').once(); 871 | 872 | (async () => { 873 | await wait(10); 874 | await publisherClient.transmitPublish('bar', 'hi'); 875 | // assert.equal(lastServerMessage, 'hi'); 876 | await wait(20); 877 | await publisherClient.transmitPublish('bar', 'world'); 878 | // assert.equal(lastServerMessage, 'world'); 879 | await publisherClient.transmitPublish('bar', {def: 123}); 880 | // assert.equal(JSON.stringify(clientReceivedMessages[2]), JSON.stringify({def: 123})); 881 | await wait(10); 882 | channel.close(); 883 | })(); 884 | 885 | let clientReceivedMessages = []; 886 | 887 | for await (let message of channel) { 888 | clientReceivedMessages.push(message); 889 | } 890 | 891 | assert.equal(clientReceivedMessages.length, 3); 892 | assert.equal(clientReceivedMessages[0], 'hi'); 893 | assert.equal(clientReceivedMessages[1], 'world'); 894 | assert.equal(JSON.stringify(clientReceivedMessages[2]), JSON.stringify({def: 123})); 895 | }); 896 | }); 897 | 898 | describe('Reconnecting socket', function () { 899 | it('Should disconnect socket with code 1000 and reconnect', async function () { 900 | client = asyngularClient.create(clientOptions); 901 | 902 | await client.listener('connect').once(); 903 | 904 | let disconnectCode; 905 | let disconnectReason; 906 | 907 | (async () => { 908 | for await (let event of client.listener('disconnect')) { 909 | disconnectCode = event.code; 910 | disconnectReason = event.reason; 911 | } 912 | })(); 913 | 914 | client.reconnect(); 915 | await client.listener('connect').once(); 916 | 917 | assert.equal(disconnectCode, 1000); 918 | assert.equal(disconnectReason, undefined); 919 | }); 920 | 921 | it('Should disconnect socket with custom code and data when socket.reconnect() is called with arguments', async function () { 922 | client = asyngularClient.create(clientOptions); 923 | 924 | await client.listener('connect').once(); 925 | 926 | let disconnectCode; 927 | let disconnectReason; 928 | 929 | (async () => { 930 | let event = await client.listener('disconnect').once(); 931 | disconnectCode = event.code; 932 | disconnectReason = event.reason; 933 | })(); 934 | 935 | client.reconnect(1000, 'About to reconnect'); 936 | await client.listener('connect').once(); 937 | 938 | assert.equal(disconnectCode, 1000); 939 | assert.equal(disconnectReason, 'About to reconnect'); 940 | }); 941 | }); 942 | 943 | describe('Order of events', function () { 944 | it('Should trigger unsubscribe event on channel before disconnect event', async function () { 945 | client = asyngularClient.create(clientOptions); 946 | let hasUnsubscribed = false; 947 | 948 | let fooChannel = client.subscribe('foo'); 949 | 950 | (async () => { 951 | for await (let event of fooChannel.listener('subscribe')) { 952 | await wait(100); 953 | client.disconnect(); 954 | } 955 | })(); 956 | 957 | (async () => { 958 | for await (let event of fooChannel.listener('unsubscribe')) { 959 | hasUnsubscribed = true; 960 | } 961 | })(); 962 | 963 | await client.listener('disconnect').once(); 964 | assert.equal(hasUnsubscribed, true); 965 | }); 966 | 967 | it('Should not invoke subscribeFail event if connection is aborted', async function () { 968 | client = asyngularClient.create(clientOptions); 969 | let hasSubscribeFailed = false; 970 | let gotBadConnectionError = false; 971 | let wasConnected = false; 972 | 973 | (async () => { 974 | for await (let event of client.listener('connect')) { 975 | wasConnected = true; 976 | (async () => { 977 | try { 978 | await client.invoke('someEvent', 123); 979 | } catch (err) { 980 | if (err.name === 'BadConnectionError') { 981 | gotBadConnectionError = true; 982 | } 983 | } 984 | })(); 985 | 986 | let fooChannel = client.subscribe('foo'); 987 | (async () => { 988 | for await (let event of fooChannel.listener('subscribeFail')) { 989 | hasSubscribeFailed = true; 990 | } 991 | })(); 992 | 993 | (async () => { 994 | await wait(0); 995 | client.disconnect(); 996 | })(); 997 | } 998 | })(); 999 | 1000 | await client.listener('close').once(); 1001 | await wait(100); 1002 | assert.equal(wasConnected, true); 1003 | assert.equal(gotBadConnectionError, true); 1004 | assert.equal(hasSubscribeFailed, false); 1005 | }); 1006 | 1007 | it('Should resolve invoke Promise with BadConnectionError before triggering the disconnect event', async function () { 1008 | client = asyngularClient.create(clientOptions); 1009 | let messageList = []; 1010 | let clientState = client.state; 1011 | 1012 | (async () => { 1013 | for await (let event of client.listener('disconnect')) { 1014 | messageList.push({ 1015 | type: 'disconnect', 1016 | code: event.code, 1017 | reason: event.reason 1018 | }); 1019 | } 1020 | })(); 1021 | 1022 | (async () => { 1023 | try { 1024 | await client.invoke('someEvent', 123); 1025 | } catch (err) { 1026 | clientState = client.state; 1027 | messageList.push({ 1028 | type: 'error', 1029 | error: err 1030 | }); 1031 | } 1032 | })(); 1033 | 1034 | await client.listener('connect').once(); 1035 | client.disconnect(); 1036 | await wait(200); 1037 | assert.equal(messageList.length, 2); 1038 | assert.equal(clientState, client.CLOSED); 1039 | assert.equal(messageList[0].error.name, 'BadConnectionError'); 1040 | assert.equal(messageList[0].type, 'error'); 1041 | assert.equal(messageList[1].type, 'disconnect'); 1042 | }); 1043 | 1044 | it('Should reconnect if transmit is called on a disconnected socket', async function () { 1045 | let fooReceiverTriggered = false; 1046 | 1047 | (async () => { 1048 | for await (let {socket} of server.listener('connection')) { 1049 | (async () => { 1050 | for await (let data of socket.receiver('foo')) { 1051 | fooReceiverTriggered = true; 1052 | } 1053 | })(); 1054 | } 1055 | })(); 1056 | 1057 | client = asyngularClient.create(clientOptions); 1058 | 1059 | let clientError; 1060 | 1061 | (async () => { 1062 | for await (let {error} of client.listener('error')) { 1063 | clientError = error; 1064 | } 1065 | })(); 1066 | 1067 | let eventList = []; 1068 | 1069 | (async () => { 1070 | for await (let event of client.listener('connecting')) { 1071 | eventList.push('connecting'); 1072 | } 1073 | })(); 1074 | 1075 | (async () => { 1076 | for await (let event of client.listener('connect')) { 1077 | eventList.push('connect'); 1078 | } 1079 | })(); 1080 | 1081 | (async () => { 1082 | for await (let event of client.listener('disconnect')) { 1083 | eventList.push('disconnect'); 1084 | } 1085 | })(); 1086 | 1087 | (async () => { 1088 | for await (let event of client.listener('close')) { 1089 | eventList.push('close'); 1090 | } 1091 | })(); 1092 | 1093 | (async () => { 1094 | for await (let event of client.listener('connectAbort')) { 1095 | eventList.push('connectAbort'); 1096 | } 1097 | })(); 1098 | 1099 | (async () => { 1100 | await client.listener('connect').once(); 1101 | client.disconnect(); 1102 | client.transmit('foo', 123); 1103 | })(); 1104 | 1105 | await wait(1000); 1106 | 1107 | let expectedEventList = ['connect', 'disconnect', 'close', 'connecting', 'connect']; 1108 | assert.equal(JSON.stringify(eventList), JSON.stringify(expectedEventList)); 1109 | assert.equal(fooReceiverTriggered, true); 1110 | }); 1111 | 1112 | it('Should correctly handle multiple successive connect and disconnect calls', async function () { 1113 | client = asyngularClient.create(clientOptions); 1114 | 1115 | let eventList = []; 1116 | 1117 | let clientError; 1118 | (async () => { 1119 | for await (let {error} of client.listener('error')) { 1120 | clientError = error; 1121 | } 1122 | })(); 1123 | 1124 | (async () => { 1125 | for await (let event of client.listener('connecting')) { 1126 | eventList.push({ 1127 | event: 'connecting' 1128 | }); 1129 | } 1130 | })(); 1131 | 1132 | (async () => { 1133 | for await (let event of client.listener('connect')) { 1134 | eventList.push({ 1135 | event: 'connect' 1136 | }); 1137 | } 1138 | })(); 1139 | 1140 | (async () => { 1141 | for await (let event of client.listener('connectAbort')) { 1142 | eventList.push({ 1143 | event: 'connectAbort', 1144 | code: event.code, 1145 | reason: event.reason 1146 | }); 1147 | } 1148 | })(); 1149 | 1150 | (async () => { 1151 | for await (let event of client.listener('disconnect')) { 1152 | eventList.push({ 1153 | event: 'disconnect', 1154 | code: event.code, 1155 | reason: event.reason 1156 | }); 1157 | } 1158 | })(); 1159 | 1160 | (async () => { 1161 | for await (let event of client.listener('close')) { 1162 | eventList.push({ 1163 | event: 'close', 1164 | code: event.code, 1165 | reason: event.reason 1166 | }); 1167 | } 1168 | })(); 1169 | 1170 | client.disconnect(1000, 'One'); 1171 | client.connect(); 1172 | client.disconnect(4444, 'Two'); 1173 | 1174 | (async () => { 1175 | await client.listener('connect').once(); 1176 | client.disconnect(4455, 'Three'); 1177 | })(); 1178 | 1179 | client.connect(); 1180 | 1181 | await wait(200); 1182 | 1183 | let expectedEventList = [ 1184 | { 1185 | event: 'connectAbort', 1186 | code: 1000, 1187 | reason: 'One' 1188 | }, 1189 | { 1190 | event: 'close', 1191 | code: 1000, 1192 | reason: 'One' 1193 | }, 1194 | { 1195 | event: 'connecting' 1196 | }, 1197 | { 1198 | event: 'connectAbort', 1199 | code: 4444, 1200 | reason: 'Two' 1201 | }, 1202 | { 1203 | event: 'close', 1204 | code: 4444, 1205 | reason: 'Two' 1206 | }, 1207 | { 1208 | event: 'connecting' 1209 | }, 1210 | { 1211 | event: 'connect' 1212 | }, 1213 | { 1214 | event: 'disconnect', 1215 | code: 4455, 1216 | reason: 'Three' 1217 | }, 1218 | { 1219 | event: 'close', 1220 | code: 4455, 1221 | reason: 'Three' 1222 | }, 1223 | ]; 1224 | assert.equal(JSON.stringify(eventList), JSON.stringify(expectedEventList)); 1225 | }); 1226 | }); 1227 | 1228 | describe('Ping/pong', function () { 1229 | it('Should disconnect if ping is not received before timeout', async function () { 1230 | clientOptions.connectTimeout = 500; 1231 | client = asyngularClient.create(clientOptions); 1232 | 1233 | assert.equal(client.pingTimeout, 500); 1234 | 1235 | (async () => { 1236 | for await (let event of client.listener('connect')) { 1237 | assert.equal(client.transport.pingTimeout, server.options.pingTimeout); 1238 | // Hack to make the client ping independent from the server ping. 1239 | client.transport.pingTimeout = 500; 1240 | client.transport._resetPingTimeout(); 1241 | } 1242 | })(); 1243 | 1244 | let disconnectEvent = null; 1245 | let clientError = null; 1246 | 1247 | (async () => { 1248 | for await (let {error} of client.listener('error')) { 1249 | clientError = error; 1250 | } 1251 | })(); 1252 | 1253 | (async () => { 1254 | for await (let event of client.listener('disconnect')) { 1255 | disconnectEvent = event; 1256 | } 1257 | })(); 1258 | 1259 | await wait(1000); 1260 | 1261 | assert.equal(disconnectEvent.code, 4000); 1262 | assert.equal(disconnectEvent.reason, 'Server ping timed out'); 1263 | assert.notEqual(clientError, null); 1264 | assert.equal(clientError.name, 'SocketProtocolError'); 1265 | }); 1266 | 1267 | it('Should not disconnect if ping is not received before timeout when pingTimeoutDisabled is true', async function () { 1268 | clientOptions.connectTimeout = 500; 1269 | clientOptions.pingTimeoutDisabled = true; 1270 | client = asyngularClient.create(clientOptions); 1271 | 1272 | assert.equal(client.pingTimeout, 500); 1273 | 1274 | let clientError = null; 1275 | (async () => { 1276 | for await (let {error} of client.listener('error')) { 1277 | clientError = error; 1278 | } 1279 | })(); 1280 | 1281 | await wait(1000); 1282 | assert.equal(clientError, null); 1283 | }); 1284 | }); 1285 | 1286 | describe('Consumable streams', function () { 1287 | it('Should be able to get the stats list of consumers and check if consumers exist on specific channels', async function () { 1288 | client = asyngularClient.create(clientOptions); 1289 | 1290 | let fooChannel = client.channel('foo'); 1291 | (async () => { 1292 | for await (let data of fooChannel.listener('subscribe')) {} 1293 | })(); 1294 | (async () => { 1295 | for await (let data of fooChannel.listener('subscribe')) {} 1296 | })(); 1297 | (async () => { 1298 | for await (let data of fooChannel.listener('subscribeFail')) {} 1299 | })(); 1300 | (async () => { 1301 | for await (let data of fooChannel.listener('customEvent')) {} 1302 | })(); 1303 | 1304 | (async () => { 1305 | for await (let data of client.channel('bar').listener('subscribe')) {} 1306 | })(); 1307 | 1308 | let fooStatsList = client.channelGetAllListenersConsumerStatsList('foo'); 1309 | let barStatsList = client.channelGetAllListenersConsumerStatsList('bar'); 1310 | 1311 | assert.equal(fooStatsList.length, 4); 1312 | assert.equal(fooStatsList[0].id, 1); 1313 | assert.equal(fooStatsList[0].stream, 'foo/subscribe'); 1314 | assert.equal(fooStatsList[1].id, 2); 1315 | assert.equal(fooStatsList[2].id, 3); 1316 | assert.equal(fooStatsList[3].id, 4); 1317 | assert.equal(fooStatsList[3].stream, 'foo/customEvent'); 1318 | 1319 | assert.equal(barStatsList.length, 1); 1320 | assert.equal(barStatsList[0].id, 5); 1321 | assert.equal(barStatsList[0].stream, 'bar/subscribe'); 1322 | 1323 | assert.equal(client.channelHasAnyListenerConsumer('foo', 1), true); 1324 | assert.equal(client.channelHasAnyListenerConsumer('foo', 4), true); 1325 | assert.equal(client.channelHasAnyListenerConsumer('foo', 5), false); 1326 | assert.equal(client.channelHasAnyListenerConsumer('bar', 5), true); 1327 | }); 1328 | 1329 | it('Should be able to check the listener backpressure for specific channels', async function () { 1330 | client = asyngularClient.create(clientOptions); 1331 | 1332 | let fooChannel = client.channel('foo'); 1333 | let barChannel = client.channel('bar'); 1334 | let fooBackpressures = []; 1335 | let barBackpressures = []; 1336 | 1337 | await Promise.all([ 1338 | (async () => { 1339 | for await (let data of fooChannel.listener('customEvent')) { 1340 | fooBackpressures.push(client.channelGetAllListenersBackpressure('foo')); 1341 | await wait(50); 1342 | } 1343 | })(), 1344 | (async () => { 1345 | for await (let data of barChannel.listener('customEvent')) { 1346 | barBackpressures.push(client.channelGetAllListenersBackpressure('bar')); 1347 | await wait(20); 1348 | } 1349 | })(), 1350 | (async () => { 1351 | for (let i = 0; i < 20; i++) { 1352 | fooChannel._eventDemux.write('foo/customEvent', `message${i}`); 1353 | } 1354 | barChannel._eventDemux.write('bar/customEvent', `hi0`); 1355 | barChannel._eventDemux.write('bar/customEvent', `hi1`); 1356 | barChannel._eventDemux.write('bar/anotherEvent', `hi2`); 1357 | barChannel._eventDemux.close('bar/customEvent'); 1358 | barChannel._eventDemux.close('bar/anotherEvent'); 1359 | fooChannel._eventDemux.close('foo/customEvent'); 1360 | })() 1361 | ]); 1362 | 1363 | assert.equal(fooBackpressures.length, 20); 1364 | assert.equal(fooBackpressures[0], 20); 1365 | assert.equal(fooBackpressures[1], 19); 1366 | assert.equal(fooBackpressures[19], 1); 1367 | 1368 | assert.equal(barBackpressures.length, 2); 1369 | assert.equal(barBackpressures[0], 2); 1370 | assert.equal(barBackpressures[1], 1); 1371 | 1372 | assert.equal(client.channelGetAllListenersBackpressure('foo'), 0); 1373 | assert.equal(client.channelGetAllListenersBackpressure('bar'), 0); 1374 | }); 1375 | 1376 | it('Should be able to kill and close channels and backpressure should update accordingly', async function () { 1377 | client = asyngularClient.create(clientOptions); 1378 | 1379 | await client.listener('connect').once(); 1380 | 1381 | let fooChannel = client.channel('foo'); 1382 | let barChannel = client.subscribe('bar'); 1383 | 1384 | await barChannel.listener('subscribe').once(); 1385 | 1386 | let fooEvents = []; 1387 | let barEvents = []; 1388 | let barMessages = []; 1389 | let barBackpressures = []; 1390 | let allBackpressures = []; 1391 | 1392 | await Promise.all([ 1393 | (async () => { 1394 | for await (let data of barChannel) { 1395 | await wait(10); 1396 | assert.equal(client.getChannelBackpressure('bar'), barChannel.getBackpressure()); 1397 | barBackpressures.push(client.getChannelBackpressure('bar')); 1398 | allBackpressures.push(client.getAllChannelsBackpressure()); 1399 | barMessages.push(data); 1400 | } 1401 | })(), 1402 | (async () => { 1403 | for await (let data of fooChannel.listener('customEvent')) { 1404 | fooEvents.push(data); 1405 | await wait(50); 1406 | } 1407 | })(), 1408 | (async () => { 1409 | for await (let data of barChannel.listener('customEvent')) { 1410 | barEvents.push(data); 1411 | await wait(20); 1412 | } 1413 | })(), 1414 | (async () => { 1415 | for (let i = 0; i < 20; i++) { 1416 | fooChannel._eventDemux.write('foo/customEvent', `message${i}`); 1417 | } 1418 | for (let i = 0; i < 50; i++) { 1419 | barChannel.transmitPublish(`hello${i}`); 1420 | } 1421 | 1422 | barChannel._eventDemux.write('bar/customEvent', `hi0`); 1423 | barChannel._eventDemux.write('bar/customEvent', `hi1`); 1424 | barChannel._eventDemux.write('bar/customEvent', `hi2`); 1425 | barChannel._eventDemux.write('bar/customEvent', `hi3`); 1426 | barChannel._eventDemux.write('bar/customEvent', `hi4`); 1427 | assert.equal(client.getChannelBackpressure('bar'), 5); 1428 | fooChannel._eventDemux.close('foo/customEvent'); 1429 | client.killChannel('foo'); 1430 | 1431 | 1432 | await wait(600); 1433 | assert.equal(client.getChannelBackpressure('bar'), 0); 1434 | client.closeChannel('bar'); 1435 | assert.equal(client.getChannelBackpressure('bar'), 1); 1436 | })() 1437 | ]); 1438 | 1439 | assert.equal(fooEvents.length, 0); 1440 | 1441 | assert.equal(barEvents.length, 5); 1442 | assert.equal(barEvents[0], 'hi0'); 1443 | assert.equal(barEvents[1], 'hi1'); 1444 | assert.equal(barEvents[4], 'hi4'); 1445 | 1446 | assert.equal(barMessages.length, 50); 1447 | assert.equal(barMessages[0], 'hello0'); 1448 | assert.equal(barMessages[49], 'hello49'); 1449 | 1450 | assert.equal(client.channelGetAllListenersBackpressure('foo'), 0); 1451 | assert.equal(client.channelGetAllListenersConsumerStatsList('bar').length, 0); 1452 | assert.equal(client.channelGetAllListenersBackpressure('bar'), 0); 1453 | 1454 | assert.equal(barBackpressures.length, 50); 1455 | assert.equal(barBackpressures[0], 49); 1456 | assert.equal(barBackpressures[49], 0); 1457 | 1458 | assert.equal(allBackpressures.length, 50); 1459 | assert.equal(allBackpressures[0], 49); 1460 | assert.equal(allBackpressures[49], 0); 1461 | }); 1462 | }); 1463 | 1464 | describe('Utilities', function () { 1465 | it('Can encode a string to base64 and then decode it back to utf8', async function () { 1466 | client = asyngularClient.create(clientOptions); 1467 | let encodedString = client.encodeBase64('This is a string'); 1468 | assert.equal(encodedString, 'VGhpcyBpcyBhIHN0cmluZw=='); 1469 | let decodedString = client.decodeBase64(encodedString); 1470 | assert.equal(decodedString, 'This is a string'); 1471 | }); 1472 | }); 1473 | }); 1474 | --------------------------------------------------------------------------------