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