@socket-mesh/client 17.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,916 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { StreamDemux, StreamDemuxWrapper } from "@socket-mesh/stream-demux";
11
+ import { LocalStorageAuthEngine } from "./auth";
12
+ import { AuthState } from "@socket-mesh/auth";
13
+ import formatter from "@socket-mesh/formatter";
14
+ import { Transport } from "./transport";
15
+ import { List, Item } from "linked-list";
16
+ import cloneDeep from "clone-deep";
17
+ import { Buffer } from "buffer";
18
+ import { wait } from "./wait";
19
+ import { ChannelState, Client } from "@socket-mesh/channel";
20
+ import { SocketState } from "./socket-state";
21
+ import { hydrateError, InvalidArgumentsError, InvalidMessageError, SocketProtocolError, TimeoutError, BadConnectionError, socketProtocolIgnoreStatuses, socketProtocolErrorStatuses } from "@socket-mesh/errors";
22
+ const isBrowser = typeof window !== 'undefined';
23
+ class EventNode extends Item {
24
+ constructor() {
25
+ super();
26
+ }
27
+ }
28
+ export class ClientSocket extends Client {
29
+ constructor(socketOptions) {
30
+ super();
31
+ this.authState = AuthState.UNAUTHENTICATED;
32
+ this.id = null;
33
+ this.state = SocketState.CLOSED;
34
+ let opts = Object.assign({
35
+ path: '/socketcluster/',
36
+ secure: false,
37
+ protocolScheme: null,
38
+ socketPath: null,
39
+ autoConnect: true,
40
+ autoReconnect: true,
41
+ autoSubscribeOnConnect: true,
42
+ connectTimeout: 20000,
43
+ ackTimeout: 10000,
44
+ timestampRequests: false,
45
+ timestampParam: 't',
46
+ authTokenName: 'socketcluster.authToken',
47
+ binaryType: 'arraybuffer',
48
+ batchOnHandshake: false,
49
+ batchOnHandshakeDuration: 100,
50
+ batchInterval: 50,
51
+ protocolVersion: 2,
52
+ wsOptions: {},
53
+ cloneData: false
54
+ }, socketOptions);
55
+ this.version = opts.version || null;
56
+ this.protocolVersion = opts.protocolVersion;
57
+ this.signedAuthToken = null;
58
+ this.authToken = null;
59
+ this.pendingReconnect = false;
60
+ this.pendingReconnectTimeout = null;
61
+ this.preparingPendingSubscriptions = false;
62
+ this.clientId = opts.clientId;
63
+ this.wsOptions = opts.wsOptions;
64
+ this.connectTimeout = opts.connectTimeout;
65
+ this.ackTimeout = opts.ackTimeout;
66
+ this.channelPrefix = opts.channelPrefix || null;
67
+ this.disconnectOnUnload = opts.disconnectOnUnload == null ? true : opts.disconnectOnUnload;
68
+ this.authTokenName = opts.authTokenName;
69
+ // pingTimeout will be connectTimeout at the start, but it will
70
+ // be updated with values provided by the 'connect' event
71
+ opts.pingTimeout = opts.connectTimeout;
72
+ this.pingTimeout = opts.pingTimeout;
73
+ this.pingTimeoutDisabled = !!opts.pingTimeoutDisabled;
74
+ const maxTimeout = Math.pow(2, 31) - 1;
75
+ if (this.connectTimeout > maxTimeout) {
76
+ throw new InvalidArgumentsError('The connectTimeout value provided exceeded the maximum amount allowed');
77
+ }
78
+ if (this.ackTimeout > maxTimeout) {
79
+ throw new InvalidArgumentsError('The ackTimeout value provided exceeded the maximum amount allowed');
80
+ }
81
+ if (this.pingTimeout > maxTimeout) {
82
+ throw new InvalidArgumentsError('The pingTimeout value provided exceeded the maximum amount allowed');
83
+ }
84
+ this.connectAttempts = 0;
85
+ this.isBatching = false;
86
+ this.batchOnHandshake = opts.batchOnHandshake;
87
+ this.batchOnHandshakeDuration = opts.batchOnHandshakeDuration;
88
+ this._batchingIntervalId = null;
89
+ this._outboundBuffer = new List();
90
+ this._receiverDemux = new StreamDemux();
91
+ this.receiver = new StreamDemuxWrapper(this._receiverDemux);
92
+ this._procedureDemux = new StreamDemux();
93
+ this.procedure = new StreamDemuxWrapper(this._procedureDemux);
94
+ this.options = opts;
95
+ this._cid = 1;
96
+ this.options.callIdGenerator = () => {
97
+ return this._cid++;
98
+ };
99
+ if (this.options.autoReconnect) {
100
+ if (this.options.autoReconnectOptions == null) {
101
+ this.options.autoReconnectOptions = {};
102
+ }
103
+ // Add properties to the this.options.autoReconnectOptions object.
104
+ // We assign the reference to a reconnectOptions variable to avoid repetition.
105
+ let reconnectOptions = this.options.autoReconnectOptions;
106
+ if (reconnectOptions.initialDelay == null) {
107
+ reconnectOptions.initialDelay = 10000;
108
+ }
109
+ if (reconnectOptions.randomness == null) {
110
+ reconnectOptions.randomness = 10000;
111
+ }
112
+ if (reconnectOptions.multiplier == null) {
113
+ reconnectOptions.multiplier = 1.5;
114
+ }
115
+ if (reconnectOptions.maxDelay == null) {
116
+ reconnectOptions.maxDelay = 60000;
117
+ }
118
+ }
119
+ if (this.options.subscriptionRetryOptions == null) {
120
+ this.options.subscriptionRetryOptions = {};
121
+ }
122
+ if (this.options.authEngine) {
123
+ this.auth = this.options.authEngine;
124
+ }
125
+ else {
126
+ this.auth = new LocalStorageAuthEngine();
127
+ }
128
+ if (this.options.codecEngine) {
129
+ this.codec = this.options.codecEngine;
130
+ }
131
+ else {
132
+ // Default codec engine
133
+ this.codec = formatter;
134
+ }
135
+ if (this.options.protocol) {
136
+ let protocolOptionError = new InvalidArgumentsError('The "protocol" option does not affect socketcluster-client - ' +
137
+ 'If you want to utilize SSL/TLS, use "secure" option instead');
138
+ this._onError(protocolOptionError);
139
+ }
140
+ this.options.query = opts.query || {};
141
+ if (isBrowser && this.disconnectOnUnload && global.addEventListener && global.removeEventListener) {
142
+ this._handleBrowserUnload();
143
+ }
144
+ if (this.options.autoConnect) {
145
+ this.connect();
146
+ }
147
+ }
148
+ getBackpressure() {
149
+ return Math.max(this.getListenerBackpressure(), this.receiver.getBackpressure(), this.procedure.getBackpressure(), this.getChannelBackpressure());
150
+ }
151
+ _handleBrowserUnload() {
152
+ return __awaiter(this, void 0, void 0, function* () {
153
+ let unloadHandler = () => {
154
+ this.disconnect();
155
+ };
156
+ let isUnloadHandlerAttached = false;
157
+ let attachUnloadHandler = () => {
158
+ if (!isUnloadHandlerAttached) {
159
+ isUnloadHandlerAttached = true;
160
+ global.addEventListener('beforeunload', unloadHandler, false);
161
+ }
162
+ };
163
+ let detachUnloadHandler = () => {
164
+ if (isUnloadHandlerAttached) {
165
+ isUnloadHandlerAttached = false;
166
+ global.removeEventListener('beforeunload', unloadHandler, false);
167
+ }
168
+ };
169
+ (() => __awaiter(this, void 0, void 0, function* () {
170
+ let consumer = this.listen('connecting').createConsumer();
171
+ while (true) {
172
+ let packet = yield consumer.next();
173
+ if (packet.done)
174
+ break;
175
+ attachUnloadHandler();
176
+ }
177
+ }))();
178
+ (() => __awaiter(this, void 0, void 0, function* () {
179
+ let consumer = this.listen('close').createConsumer();
180
+ while (true) {
181
+ let packet = yield consumer.next();
182
+ if (packet.done)
183
+ break;
184
+ detachUnloadHandler();
185
+ }
186
+ }))();
187
+ });
188
+ }
189
+ _setAuthToken(data) {
190
+ this._changeToAuthenticatedState(data.token);
191
+ (() => __awaiter(this, void 0, void 0, function* () {
192
+ try {
193
+ yield this.auth.saveToken(this.authTokenName, data.token, {});
194
+ }
195
+ catch (err) {
196
+ this._onError(err);
197
+ }
198
+ }))();
199
+ }
200
+ _removeAuthToken() {
201
+ (() => __awaiter(this, void 0, void 0, function* () {
202
+ let oldAuthToken;
203
+ try {
204
+ oldAuthToken = yield this.auth.removeToken(this.authTokenName);
205
+ }
206
+ catch (err) {
207
+ // Non-fatal error - Do not close the connection
208
+ this._onError(err);
209
+ return;
210
+ }
211
+ this.emit('removeAuthToken', { oldAuthToken });
212
+ }))();
213
+ this._changeToUnauthenticatedStateAndClearTokens();
214
+ }
215
+ getState() {
216
+ return this.state;
217
+ }
218
+ getBytesReceived() {
219
+ return this.transport.getBytesReceived();
220
+ }
221
+ deauthenticate() {
222
+ return __awaiter(this, void 0, void 0, function* () {
223
+ (() => __awaiter(this, void 0, void 0, function* () {
224
+ let oldAuthToken;
225
+ try {
226
+ oldAuthToken = yield this.auth.removeToken(this.authTokenName);
227
+ }
228
+ catch (err) {
229
+ this._onError(err);
230
+ return;
231
+ }
232
+ this.emit('removeAuthToken', { oldAuthToken });
233
+ }))();
234
+ if (this.state !== SocketState.CLOSED) {
235
+ this.transmit('#removeAuthToken');
236
+ }
237
+ this._changeToUnauthenticatedStateAndClearTokens();
238
+ yield wait(0);
239
+ });
240
+ }
241
+ connect() {
242
+ if (this.state === SocketState.CLOSED) {
243
+ this.pendingReconnect = false;
244
+ this.pendingReconnectTimeout = null;
245
+ clearTimeout(this._reconnectTimeoutRef);
246
+ this.state = SocketState.CONNECTING;
247
+ this.emit('connecting', {});
248
+ if (this.transport) {
249
+ this.transport.clearAllListeners();
250
+ }
251
+ const transportHandlers = {
252
+ onOpen: (value) => {
253
+ this.state = SocketState.OPEN;
254
+ this._onOpen(value);
255
+ },
256
+ onOpenAbort: (value) => {
257
+ if (this.state !== SocketState.CLOSED) {
258
+ this.state = SocketState.CLOSED;
259
+ this._destroy(value.code, value.reason, true);
260
+ }
261
+ },
262
+ onClose: (value) => {
263
+ if (this.state !== SocketState.CLOSED) {
264
+ this.state = SocketState.CLOSED;
265
+ this._destroy(value.code, value.reason);
266
+ }
267
+ },
268
+ onEvent: (value) => {
269
+ this.emit(value.event, value.data);
270
+ },
271
+ onError: (value) => {
272
+ this._onError(value.error);
273
+ },
274
+ onInboundInvoke: (value) => {
275
+ this._onInboundInvoke(value);
276
+ },
277
+ onInboundTransmit: (value) => {
278
+ this._onInboundTransmit(value.event, value.data);
279
+ }
280
+ };
281
+ this.transport = new Transport(this.auth, this.codec, this.options, this.wsOptions, transportHandlers);
282
+ }
283
+ }
284
+ reconnect(code, reason) {
285
+ this.disconnect(code, reason);
286
+ this.connect();
287
+ }
288
+ disconnect(code = 1000, reason) {
289
+ if (typeof code !== 'number') {
290
+ throw new InvalidArgumentsError('If specified, the code argument must be a number');
291
+ }
292
+ let isConnecting = this.state === SocketState.CONNECTING;
293
+ if (isConnecting || this.state === SocketState.OPEN) {
294
+ this.state = SocketState.CLOSED;
295
+ this._destroy(code, reason, isConnecting);
296
+ this.transport.close(code, reason);
297
+ }
298
+ else {
299
+ this.pendingReconnect = false;
300
+ this.pendingReconnectTimeout = null;
301
+ clearTimeout(this._reconnectTimeoutRef);
302
+ }
303
+ }
304
+ _changeToUnauthenticatedStateAndClearTokens() {
305
+ if (this.authState !== AuthState.UNAUTHENTICATED) {
306
+ let oldAuthState = this.authState;
307
+ let oldAuthToken = this.authToken;
308
+ let oldSignedAuthToken = this.signedAuthToken;
309
+ this.authState = AuthState.UNAUTHENTICATED;
310
+ this.signedAuthToken = null;
311
+ this.authToken = null;
312
+ let stateChangeData = {
313
+ oldAuthState,
314
+ newAuthState: this.authState
315
+ };
316
+ this.emit('authStateChange', stateChangeData);
317
+ this.emit('deauthenticate', { oldSignedAuthToken, oldAuthToken });
318
+ }
319
+ }
320
+ _changeToAuthenticatedState(signedAuthToken) {
321
+ this.signedAuthToken = signedAuthToken;
322
+ this.authToken = this._extractAuthTokenData(signedAuthToken);
323
+ if (this.authState !== AuthState.AUTHENTICATED) {
324
+ let oldAuthState = this.authState;
325
+ this.authState = AuthState.AUTHENTICATED;
326
+ let stateChangeData = {
327
+ oldAuthState,
328
+ newAuthState: this.authState,
329
+ signedAuthToken: signedAuthToken,
330
+ authToken: this.authToken
331
+ };
332
+ if (!this.preparingPendingSubscriptions) {
333
+ this.processPendingSubscriptions();
334
+ }
335
+ this.emit('authStateChange', stateChangeData);
336
+ }
337
+ this.emit('authenticate', { signedAuthToken, authToken: this.authToken });
338
+ }
339
+ decodeBase64(encodedString) {
340
+ return Buffer.from(encodedString, 'base64').toString('utf8');
341
+ }
342
+ encodeBase64(decodedString) {
343
+ return Buffer.from(decodedString, 'utf8').toString('base64');
344
+ }
345
+ _extractAuthTokenData(signedAuthToken) {
346
+ let tokenParts = (signedAuthToken || '').split('.');
347
+ let encodedTokenData = tokenParts[1];
348
+ if (encodedTokenData != null) {
349
+ let tokenData = encodedTokenData;
350
+ try {
351
+ tokenData = this.decodeBase64(tokenData);
352
+ return JSON.parse(tokenData);
353
+ }
354
+ catch (e) {
355
+ return tokenData;
356
+ }
357
+ }
358
+ return null;
359
+ }
360
+ getAuthToken() {
361
+ return this.authToken;
362
+ }
363
+ getSignedAuthToken() {
364
+ return this.signedAuthToken;
365
+ }
366
+ // Perform client-initiated authentication by providing an encrypted token string.
367
+ authenticate(signedAuthToken) {
368
+ return __awaiter(this, void 0, void 0, function* () {
369
+ let authStatus;
370
+ try {
371
+ authStatus = yield this.invoke('#authenticate', signedAuthToken);
372
+ }
373
+ catch (err) {
374
+ if (err.name !== 'BadConnectionError' && err.name !== 'TimeoutError') {
375
+ // In case of a bad/closed connection or a timeout, we maintain the last
376
+ // known auth state since those errors don't mean that the token is invalid.
377
+ this._changeToUnauthenticatedStateAndClearTokens();
378
+ }
379
+ yield wait(0);
380
+ throw err;
381
+ }
382
+ if ((authStatus === null || authStatus === void 0 ? void 0 : authStatus.isAuthenticated) != null) {
383
+ // If authStatus is correctly formatted (has an isAuthenticated property),
384
+ // then we will rehydrate the authError.
385
+ if (authStatus.authError) {
386
+ authStatus.authError = hydrateError(authStatus.authError);
387
+ }
388
+ }
389
+ else {
390
+ // Some errors like BadConnectionError and TimeoutError will not pass a valid
391
+ // authStatus object to the current function, so we need to create it ourselves.
392
+ authStatus = {
393
+ isAuthenticated: this.authState,
394
+ authError: null
395
+ };
396
+ }
397
+ if (authStatus.isAuthenticated) {
398
+ this._changeToAuthenticatedState(signedAuthToken);
399
+ }
400
+ else {
401
+ this._changeToUnauthenticatedStateAndClearTokens();
402
+ }
403
+ (() => __awaiter(this, void 0, void 0, function* () {
404
+ try {
405
+ yield this.auth.saveToken(this.authTokenName, signedAuthToken, {});
406
+ }
407
+ catch (err) {
408
+ this._onError(err);
409
+ }
410
+ }))();
411
+ yield wait(0);
412
+ return authStatus;
413
+ });
414
+ }
415
+ _tryReconnect(initialDelay) {
416
+ let exponent = this.connectAttempts++;
417
+ let reconnectOptions = this.options.autoReconnectOptions;
418
+ let timeout;
419
+ if (initialDelay == null || exponent > 0) {
420
+ let initialTimeout = Math.round(reconnectOptions.initialDelay + (reconnectOptions.randomness || 0) * Math.random());
421
+ timeout = Math.round(initialTimeout * Math.pow(reconnectOptions.multiplier, exponent));
422
+ }
423
+ else {
424
+ timeout = initialDelay;
425
+ }
426
+ if (timeout > reconnectOptions.maxDelay) {
427
+ timeout = reconnectOptions.maxDelay;
428
+ }
429
+ clearTimeout(this._reconnectTimeoutRef);
430
+ this.pendingReconnect = true;
431
+ this.pendingReconnectTimeout = timeout;
432
+ this._reconnectTimeoutRef = setTimeout(() => {
433
+ this.connect();
434
+ }, timeout);
435
+ }
436
+ _onOpen(status) {
437
+ if (this.isBatching) {
438
+ this._startBatching();
439
+ }
440
+ else if (this.batchOnHandshake) {
441
+ this._startBatching();
442
+ setTimeout(() => {
443
+ if (!this.isBatching) {
444
+ this._stopBatching();
445
+ }
446
+ }, this.batchOnHandshakeDuration);
447
+ }
448
+ this.preparingPendingSubscriptions = true;
449
+ if (status) {
450
+ this.id = status.id;
451
+ this.pingTimeout = status.pingTimeout;
452
+ if (status.isAuthenticated) {
453
+ this._changeToAuthenticatedState(status.authToken);
454
+ }
455
+ else {
456
+ this._changeToUnauthenticatedStateAndClearTokens();
457
+ }
458
+ }
459
+ else {
460
+ // This can happen if auth.loadToken (in transport.js) fails with
461
+ // an error - This means that the signedAuthToken cannot be loaded by
462
+ // the auth engine and therefore, we need to unauthenticate the client.
463
+ this._changeToUnauthenticatedStateAndClearTokens();
464
+ }
465
+ this.connectAttempts = 0;
466
+ if (this.options.autoSubscribeOnConnect) {
467
+ this.processPendingSubscriptions();
468
+ }
469
+ // If the user invokes the callback while in autoSubscribeOnConnect mode, it
470
+ // won't break anything.
471
+ this.emit('connect', Object.assign(Object.assign({}, status), { processPendingSubscriptions: () => {
472
+ this.processPendingSubscriptions();
473
+ } }));
474
+ if (this.state === SocketState.OPEN) {
475
+ this._flushOutboundBuffer();
476
+ }
477
+ }
478
+ _onError(error) {
479
+ this.emit('error', { error });
480
+ }
481
+ _suspendSubscriptions() {
482
+ Object.keys(this._channelMap).forEach((channelName) => {
483
+ let channel = this._channelMap[channelName];
484
+ this._triggerChannelUnsubscribe(channel, true);
485
+ });
486
+ }
487
+ _abortAllPendingEventsDueToBadConnection(failureType) {
488
+ let currentNode = this._outboundBuffer.head;
489
+ let nextNode;
490
+ while (currentNode) {
491
+ nextNode = currentNode.next;
492
+ let eventObject = currentNode.data;
493
+ clearTimeout(eventObject.timeout);
494
+ delete eventObject.timeout;
495
+ currentNode.detach();
496
+ currentNode = nextNode;
497
+ let callback = eventObject.callback;
498
+ if (callback) {
499
+ delete eventObject.callback;
500
+ let errorMessage = `Event "${eventObject.event}" was aborted due to a bad connection`;
501
+ let error = new BadConnectionError(errorMessage, failureType);
502
+ callback.call(eventObject, error, eventObject);
503
+ }
504
+ // Cleanup any pending response callback in the transport layer too.
505
+ if (eventObject.cid) {
506
+ this.transport.cancelPendingResponse(eventObject.cid);
507
+ }
508
+ }
509
+ }
510
+ _destroy(code, reason, openAbort) {
511
+ this.id = null;
512
+ this._cancelBatching();
513
+ if (this.transport) {
514
+ this.transport.clearAllListeners();
515
+ }
516
+ this.pendingReconnect = false;
517
+ this.pendingReconnectTimeout = null;
518
+ clearTimeout(this._reconnectTimeoutRef);
519
+ this._suspendSubscriptions();
520
+ if (openAbort) {
521
+ this.emit('connectAbort', { code, reason });
522
+ }
523
+ else {
524
+ this.emit('disconnect', { code, reason });
525
+ }
526
+ this.emit('close', { code, reason });
527
+ if (!socketProtocolIgnoreStatuses[code]) {
528
+ let closeMessage;
529
+ if (reason) {
530
+ closeMessage = 'Socket connection closed with status code ' + code + ' and reason: ' + reason;
531
+ }
532
+ else {
533
+ closeMessage = 'Socket connection closed with status code ' + code;
534
+ }
535
+ let err = new SocketProtocolError(socketProtocolErrorStatuses[code] || closeMessage, code);
536
+ this._onError(err);
537
+ }
538
+ this._abortAllPendingEventsDueToBadConnection(openAbort ? 'connectAbort' : 'disconnect');
539
+ // Try to reconnect
540
+ // on server ping timeout (4000)
541
+ // or on client pong timeout (4001)
542
+ // or on close without status (1005)
543
+ // or on handshake failure (4003)
544
+ // or on handshake rejection (4008)
545
+ // or on socket hung up (1006)
546
+ if (this.options.autoReconnect) {
547
+ if (code === 4000 || code === 4001 || code === 1005) {
548
+ // If there is a ping or pong timeout or socket closes without
549
+ // status, don't wait before trying to reconnect - These could happen
550
+ // if the client wakes up after a period of inactivity and in this case we
551
+ // want to re-establish the connection as soon as possible.
552
+ this._tryReconnect(0);
553
+ // Codes 4500 and above will be treated as permanent disconnects.
554
+ // Socket will not try to auto-reconnect.
555
+ }
556
+ else if (code !== 1000 && code < 4500) {
557
+ this._tryReconnect();
558
+ }
559
+ }
560
+ }
561
+ _onInboundTransmit(event, data) {
562
+ if (event === '#publish') {
563
+ let undecoratedChannelName = this._undecorateChannelName(data.channel);
564
+ let isSubscribed = this.isSubscribed(undecoratedChannelName, true);
565
+ if (isSubscribed) {
566
+ this._channelDataDemux.write(undecoratedChannelName, data.data);
567
+ }
568
+ }
569
+ else if (event === '#kickOut') {
570
+ let undecoratedChannelName = this._undecorateChannelName(data.channel);
571
+ let channel = this._channelMap[undecoratedChannelName];
572
+ if (channel) {
573
+ this.emit('kickOut', {
574
+ channel: undecoratedChannelName,
575
+ message: data.message
576
+ });
577
+ this._channelEventDemux.write(`${undecoratedChannelName}/kickOut`, { message: data.message });
578
+ this._triggerChannelUnsubscribe(channel);
579
+ }
580
+ }
581
+ else if (event === '#setAuthToken') {
582
+ if (data) {
583
+ this._setAuthToken(data);
584
+ }
585
+ }
586
+ else if (event === '#removeAuthToken') {
587
+ this._removeAuthToken();
588
+ }
589
+ else {
590
+ this._receiverDemux.write(event, data);
591
+ }
592
+ }
593
+ _onInboundInvoke(request) {
594
+ let { procedure, data } = request;
595
+ if (procedure === '#setAuthToken') {
596
+ if (data) {
597
+ this._setAuthToken(data);
598
+ request.end();
599
+ }
600
+ else {
601
+ request.error(new InvalidMessageError('No token data provided by #setAuthToken event'));
602
+ }
603
+ }
604
+ else if (procedure === '#removeAuthToken') {
605
+ this._removeAuthToken();
606
+ request.end();
607
+ }
608
+ else {
609
+ this._procedureDemux.write(procedure, request);
610
+ }
611
+ }
612
+ decode(message) {
613
+ return this.transport.decode(message);
614
+ }
615
+ encode(object) {
616
+ return this.transport.encode(object);
617
+ }
618
+ _flushOutboundBuffer() {
619
+ let currentNode = this._outboundBuffer.head;
620
+ let nextNode;
621
+ while (currentNode) {
622
+ nextNode = currentNode.next;
623
+ let eventObject = currentNode.data;
624
+ currentNode.detach();
625
+ this.transport.transmitObject(eventObject);
626
+ currentNode = nextNode;
627
+ }
628
+ }
629
+ _handleEventAckTimeout(eventObject, eventNode) {
630
+ if (eventNode) {
631
+ eventNode.detach();
632
+ }
633
+ delete eventObject.timeout;
634
+ let callback = eventObject.callback;
635
+ if (callback) {
636
+ delete eventObject.callback;
637
+ let error = new TimeoutError(`Event response for "${eventObject.event}" timed out`);
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
+ _processOutboundEvent(event, data, options, expectResponse) {
646
+ options = options || {};
647
+ if (this.state === SocketState.CLOSED) {
648
+ this.connect();
649
+ }
650
+ let eventObject = {
651
+ event,
652
+ data: this.options.cloneData ? cloneDeep(data) : data
653
+ };
654
+ let promise;
655
+ if (expectResponse) {
656
+ promise = new Promise((resolve, reject) => {
657
+ eventObject.callback = (err, data) => {
658
+ if (err) {
659
+ reject(err);
660
+ return;
661
+ }
662
+ resolve(data);
663
+ };
664
+ });
665
+ }
666
+ else {
667
+ promise = Promise.resolve();
668
+ }
669
+ let eventNode = new EventNode();
670
+ eventNode.data = eventObject;
671
+ let ackTimeout = options.ackTimeout == null ? this.ackTimeout : options.ackTimeout;
672
+ eventObject.timeout = setTimeout(() => {
673
+ this._handleEventAckTimeout(eventObject, eventNode);
674
+ }, ackTimeout);
675
+ this._outboundBuffer.append(eventNode);
676
+ if (this.state === SocketState.OPEN) {
677
+ this._flushOutboundBuffer();
678
+ }
679
+ return promise;
680
+ }
681
+ send(data) {
682
+ this.transport.send(data);
683
+ }
684
+ ;
685
+ transmit(event, data, options) {
686
+ return this._processOutboundEvent(event, data, options);
687
+ }
688
+ invoke(event, data, options) {
689
+ return this._processOutboundEvent(event, data, options, true);
690
+ }
691
+ transmitPublish(channelName, data) {
692
+ let pubData = {
693
+ channel: this._decorateChannelName(channelName),
694
+ data
695
+ };
696
+ return this.transmit('#publish', pubData);
697
+ }
698
+ invokePublish(channelName, data) {
699
+ let pubData = {
700
+ channel: this._decorateChannelName(channelName),
701
+ data
702
+ };
703
+ return this.invoke('#publish', pubData);
704
+ }
705
+ _triggerChannelSubscribe(channel, subscriptionOptions) {
706
+ let channelName = channel.name;
707
+ if (channel.state !== ChannelState.SUBSCRIBED) {
708
+ let oldChannelState = channel.state;
709
+ channel.state = ChannelState.SUBSCRIBED;
710
+ let stateChangeData = {
711
+ oldChannelState,
712
+ newChannelState: channel.state,
713
+ subscriptionOptions
714
+ };
715
+ this._channelEventDemux.write(`${channelName}/subscribeStateChange`, stateChangeData);
716
+ this._channelEventDemux.write(`${channelName}/subscribe`, {
717
+ subscriptionOptions
718
+ });
719
+ this.emit('subscribeStateChange', Object.assign({ channel: channelName }, stateChangeData));
720
+ this.emit('subscribe', {
721
+ channel: channelName,
722
+ subscriptionOptions
723
+ });
724
+ }
725
+ }
726
+ _triggerChannelSubscribeFail(err, channel, subscriptionOptions) {
727
+ let channelName = channel.name;
728
+ let meetsAuthRequirements = !channel.options.waitForAuth || this.authState === AuthState.AUTHENTICATED;
729
+ let hasChannel = !!this._channelMap[channelName];
730
+ if (hasChannel && meetsAuthRequirements) {
731
+ delete this._channelMap[channelName];
732
+ this._channelEventDemux.write(`${channelName}/subscribeFail`, {
733
+ error: err,
734
+ subscriptionOptions
735
+ });
736
+ this.emit('subscribeFail', {
737
+ error: err,
738
+ channel: channelName,
739
+ subscriptionOptions: subscriptionOptions
740
+ });
741
+ }
742
+ }
743
+ // Cancel any pending subscribe callback
744
+ _cancelPendingSubscribeCallback(channel) {
745
+ if (channel._pendingSubscriptionCid != null) {
746
+ this.transport.cancelPendingResponse(channel._pendingSubscriptionCid);
747
+ delete channel._pendingSubscriptionCid;
748
+ }
749
+ }
750
+ _decorateChannelName(channelName) {
751
+ if (this.channelPrefix) {
752
+ channelName = this.channelPrefix + channelName;
753
+ }
754
+ return channelName;
755
+ }
756
+ _undecorateChannelName(decoratedChannelName) {
757
+ if (this.channelPrefix && decoratedChannelName.indexOf(this.channelPrefix) === 0) {
758
+ return decoratedChannelName.replace(this.channelPrefix, '');
759
+ }
760
+ return decoratedChannelName;
761
+ }
762
+ startBatch() {
763
+ this.transport.startBatch();
764
+ }
765
+ flushBatch() {
766
+ this.transport.flushBatch();
767
+ }
768
+ cancelBatch() {
769
+ this.transport.cancelBatch();
770
+ }
771
+ _startBatching() {
772
+ if (this._batchingIntervalId != null) {
773
+ return;
774
+ }
775
+ this.startBatch();
776
+ this._batchingIntervalId = setInterval(() => {
777
+ this.flushBatch();
778
+ this.startBatch();
779
+ }, this.options.batchInterval);
780
+ }
781
+ startBatching() {
782
+ this.isBatching = true;
783
+ this._startBatching();
784
+ }
785
+ _stopBatching() {
786
+ if (this._batchingIntervalId != null) {
787
+ clearInterval(this._batchingIntervalId);
788
+ }
789
+ this._batchingIntervalId = null;
790
+ this.flushBatch();
791
+ }
792
+ stopBatching() {
793
+ this.isBatching = false;
794
+ this._stopBatching();
795
+ }
796
+ _cancelBatching() {
797
+ if (this._batchingIntervalId != null) {
798
+ clearInterval(this._batchingIntervalId);
799
+ }
800
+ this._batchingIntervalId = null;
801
+ this.cancelBatch();
802
+ }
803
+ cancelBatching() {
804
+ this.isBatching = false;
805
+ this._cancelBatching();
806
+ }
807
+ _trySubscribe(channel) {
808
+ let meetsAuthRequirements = !channel.options.waitForAuth || this.authState === AuthState.AUTHENTICATED;
809
+ // We can only ever have one pending subscribe action at any given time on a channel
810
+ if (this.state === SocketState.OPEN &&
811
+ !this.preparingPendingSubscriptions &&
812
+ channel._pendingSubscriptionCid == null &&
813
+ meetsAuthRequirements) {
814
+ let options = {
815
+ noTimeout: true
816
+ };
817
+ let subscriptionOptions = {};
818
+ if (channel.options.waitForAuth) {
819
+ subscriptionOptions.waitForAuth = true;
820
+ }
821
+ if (channel.options.data) {
822
+ subscriptionOptions.data = channel.options.data;
823
+ }
824
+ channel._pendingSubscriptionCid = this.transport.invokeRaw('#subscribe', Object.assign({ channel: this._decorateChannelName(channel.name) }, subscriptionOptions), options, (err) => {
825
+ if (err) {
826
+ if (err.name === 'BadConnectionError') {
827
+ // In case of a failed connection, keep the subscription
828
+ // as pending; it will try again on reconnect.
829
+ return;
830
+ }
831
+ delete channel._pendingSubscriptionCid;
832
+ this._triggerChannelSubscribeFail(err, channel, subscriptionOptions);
833
+ }
834
+ else {
835
+ delete channel._pendingSubscriptionCid;
836
+ this._triggerChannelSubscribe(channel, subscriptionOptions);
837
+ }
838
+ });
839
+ this.emit('subscribeRequest', {
840
+ channel: channel.name,
841
+ subscriptionOptions
842
+ });
843
+ }
844
+ }
845
+ emit(eventName, data) {
846
+ super.emit(eventName, data);
847
+ }
848
+ listen(eventName) {
849
+ return super.listen(eventName);
850
+ }
851
+ _triggerChannelUnsubscribe(channel, setAsPending) {
852
+ let channelName = channel.name;
853
+ this._cancelPendingSubscribeCallback(channel);
854
+ if (channel.state === ChannelState.SUBSCRIBED) {
855
+ let stateChangeData = {
856
+ oldChannelState: channel.state,
857
+ newChannelState: setAsPending ? ChannelState.PENDING : ChannelState.UNSUBSCRIBED
858
+ };
859
+ this._channelEventDemux.write(`${channelName}/subscribeStateChange`, stateChangeData);
860
+ this._channelEventDemux.write(`${channelName}/unsubscribe`, {});
861
+ this.emit('subscribeStateChange', Object.assign({ channel: channelName }, stateChangeData));
862
+ this.emit('unsubscribe', { channel: channelName });
863
+ }
864
+ if (setAsPending) {
865
+ channel.state = ChannelState.PENDING;
866
+ }
867
+ else {
868
+ delete this._channelMap[channelName];
869
+ }
870
+ }
871
+ _tryUnsubscribe(channel) {
872
+ this._triggerChannelUnsubscribe(channel);
873
+ if (this.state === SocketState.OPEN) {
874
+ let options = {
875
+ noTimeout: true
876
+ };
877
+ // If there is a pending subscribe action, cancel the callback
878
+ this._cancelPendingSubscribeCallback(channel);
879
+ // This operation cannot fail because the TCP protocol guarantees delivery
880
+ // so long as the connection remains open. If the connection closes,
881
+ // the server will automatically unsubscribe the client and thus complete
882
+ // the operation on the server side.
883
+ let decoratedChannelName = this._decorateChannelName(channel.name);
884
+ this.transport.transmit('#unsubscribe', decoratedChannelName, options);
885
+ }
886
+ }
887
+ // ---- Channel logic ----
888
+ processPendingSubscriptions() {
889
+ this.preparingPendingSubscriptions = false;
890
+ let pendingChannels = [];
891
+ Object.keys(this._channelMap).forEach((channelName) => {
892
+ let channel = this._channelMap[channelName];
893
+ if (channel.state === ChannelState.PENDING) {
894
+ pendingChannels.push(channel);
895
+ }
896
+ });
897
+ pendingChannels.sort((a, b) => {
898
+ let ap = a.options.priority || 0;
899
+ let bp = b.options.priority || 0;
900
+ if (ap > bp) {
901
+ return -1;
902
+ }
903
+ if (ap < bp) {
904
+ return 1;
905
+ }
906
+ return 0;
907
+ });
908
+ pendingChannels.forEach((channel) => {
909
+ this._trySubscribe(channel);
910
+ });
911
+ }
912
+ get isBufferingBatch() {
913
+ return this.transport.isBufferingBatch;
914
+ }
915
+ }
916
+ //# sourceMappingURL=clientsocket.js.map