├── .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 |
--------------------------------------------------------------------------------