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