stream-chat 4.4.3 → 4.5.0-beta.0

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.
Files changed (53) hide show
  1. package/README.md +4 -13
  2. package/dist/browser.es.js +1571 -1071
  3. package/dist/browser.es.js.map +1 -1
  4. package/dist/browser.full-bundle.min.js +1 -1
  5. package/dist/browser.full-bundle.min.js.map +1 -1
  6. package/dist/browser.js +1571 -1071
  7. package/dist/browser.js.map +1 -1
  8. package/dist/index.es.js +1571 -1071
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.js +1571 -1071
  11. package/dist/index.js.map +1 -1
  12. package/dist/types/base64.d.ts.map +1 -1
  13. package/dist/types/channel.d.ts +19 -15
  14. package/dist/types/channel.d.ts.map +1 -1
  15. package/dist/types/channel_state.d.ts +2 -2
  16. package/dist/types/channel_state.d.ts.map +1 -1
  17. package/dist/types/client.d.ts +18 -39
  18. package/dist/types/client.d.ts.map +1 -1
  19. package/dist/types/client_state.d.ts +2 -2
  20. package/dist/types/client_state.d.ts.map +1 -1
  21. package/dist/types/connection.d.ts +14 -49
  22. package/dist/types/connection.d.ts.map +1 -1
  23. package/dist/types/connection_fallback.d.ts +42 -0
  24. package/dist/types/connection_fallback.d.ts.map +1 -0
  25. package/dist/types/errors.d.ts +14 -0
  26. package/dist/types/errors.d.ts.map +1 -0
  27. package/dist/types/insights.d.ts +6 -6
  28. package/dist/types/insights.d.ts.map +1 -1
  29. package/dist/types/permissions.d.ts.map +1 -1
  30. package/dist/types/signing.d.ts +3 -3
  31. package/dist/types/signing.d.ts.map +1 -1
  32. package/dist/types/token_manager.d.ts +2 -2
  33. package/dist/types/token_manager.d.ts.map +1 -1
  34. package/dist/types/types.d.ts +94 -80
  35. package/dist/types/types.d.ts.map +1 -1
  36. package/dist/types/utils.d.ts +12 -2
  37. package/dist/types/utils.d.ts.map +1 -1
  38. package/package.json +3 -3
  39. package/src/base64.ts +1 -4
  40. package/src/channel.ts +133 -461
  41. package/src/channel_state.ts +31 -158
  42. package/src/client.ts +277 -674
  43. package/src/client_state.ts +2 -2
  44. package/src/connection.ts +143 -394
  45. package/src/connection_fallback.ts +205 -0
  46. package/src/errors.ts +58 -0
  47. package/src/insights.ts +15 -23
  48. package/src/permissions.ts +3 -24
  49. package/src/signing.ts +6 -17
  50. package/src/token_manager.ts +6 -18
  51. package/src/types.ts +268 -504
  52. package/src/utils.ts +39 -19
  53. package/CHANGELOG.md +0 -844
package/src/connection.ts CHANGED
@@ -1,55 +1,23 @@
1
1
  import WebSocket from 'isomorphic-ws';
2
- import { chatCodes, convertErrorToJson, sleep, retryInterval, randomId } from './utils';
3
- import { TokenManager } from './token_manager';
4
2
  import {
5
- buildWsFatalInsight,
6
- buildWsSuccessAfterFailureInsight,
7
- InsightMetrics,
8
- postInsights,
9
- } from './insights';
10
- import {
11
- BaseDeviceFields,
12
- ConnectAPIResponse,
13
- ConnectionChangeEvent,
14
- ConnectionOpen,
15
- LiteralStringForUnion,
16
- Logger,
17
- UnknownType,
18
- UserResponse,
19
- } from './types';
3
+ chatCodes,
4
+ convertErrorToJson,
5
+ sleep,
6
+ retryInterval,
7
+ randomId,
8
+ removeConnectionEventListeners,
9
+ addConnectionEventListeners,
10
+ } from './utils';
11
+ import { buildWsFatalInsight, buildWsSuccessAfterFailureInsight, postInsights } from './insights';
12
+ import { ConnectAPIResponse, ConnectionOpen, LiteralStringForUnion, UR, LogLevel } from './types';
13
+ import { StreamChat } from './client';
20
14
 
21
15
  // Type guards to check WebSocket error type
22
- const isCloseEvent = (
23
- res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
24
- ): res is WebSocket.CloseEvent => (res as WebSocket.CloseEvent).code !== undefined;
25
-
26
- const isErrorEvent = (
27
- res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
28
- ): res is WebSocket.ErrorEvent => (res as WebSocket.ErrorEvent).error !== undefined;
16
+ const isCloseEvent = (res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent): res is WebSocket.CloseEvent =>
17
+ (res as WebSocket.CloseEvent).code !== undefined;
29
18
 
30
- type Constructor<
31
- ChannelType extends UnknownType = UnknownType,
32
- CommandType extends string = LiteralStringForUnion,
33
- UserType extends UnknownType = UnknownType
34
- > = {
35
- apiKey: string;
36
- authType: 'anonymous' | 'jwt';
37
- clientID: string;
38
- eventCallback: (event: ConnectionChangeEvent) => void;
39
- insightMetrics: InsightMetrics;
40
- logger: Logger | (() => void);
41
- messageCallback: (messageEvent: WebSocket.MessageEvent) => void;
42
- recoverCallback: (
43
- open?: ConnectionOpen<ChannelType, CommandType, UserType>,
44
- ) => Promise<void>;
45
- tokenManager: TokenManager<UserType>;
46
- user: UserResponse<UserType>;
47
- userAgent: string;
48
- userID: string;
49
- wsBaseURL: string;
50
- device?: BaseDeviceFields;
51
- enableInsights?: boolean;
52
- };
19
+ const isErrorEvent = (res: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent): res is WebSocket.ErrorEvent =>
20
+ (res as WebSocket.ErrorEvent).error !== undefined;
53
21
 
54
22
  /**
55
23
  * StableWSConnection - A WS connection that reconnects upon failure.
@@ -69,90 +37,59 @@ type Constructor<
69
37
  * - if the servers fails to publish a message to the client, the WS connection is destroyed
70
38
  */
71
39
  export class StableWSConnection<
72
- ChannelType extends UnknownType = UnknownType,
40
+ // CAUTION: generics are out of usual order here
41
+ ChannelType extends UR = UR,
73
42
  CommandType extends string = LiteralStringForUnion,
74
- UserType extends UnknownType = UnknownType
43
+ UserType extends UR = UR,
44
+ AttachmentType extends UR = UR,
45
+ EventType extends UR = UR,
46
+ MessageType extends UR = UR,
47
+ ReactionType extends UR = UR
75
48
  > {
76
- apiKey: Constructor<ChannelType, CommandType, UserType>['apiKey'];
77
- authType: Constructor<ChannelType, CommandType, UserType>['authType'];
78
- clientID: Constructor<ChannelType, CommandType, UserType>['clientID'];
79
- eventCallback: Constructor<ChannelType, CommandType, UserType>['eventCallback'];
80
- logger: Constructor<ChannelType, CommandType, UserType>['logger'];
81
- messageCallback: Constructor<ChannelType, CommandType, UserType>['messageCallback'];
82
- recoverCallback: Constructor<ChannelType, CommandType, UserType>['recoverCallback'];
83
- tokenManager: Constructor<ChannelType, CommandType, UserType>['tokenManager'];
84
- user: Constructor<ChannelType, CommandType, UserType>['user'];
85
- userAgent: Constructor<ChannelType, CommandType, UserType>['userAgent'];
86
- userID: Constructor<ChannelType, CommandType, UserType>['userID'];
87
- wsBaseURL: Constructor<ChannelType, CommandType, UserType>['wsBaseURL'];
88
- device: Constructor<ChannelType, CommandType, UserType>['device'];
49
+ // global from constructor
50
+ client: StreamChat<AttachmentType, ChannelType, CommandType, EventType, MessageType, ReactionType, UserType>;
51
+
52
+ // local vars
89
53
  connectionID?: string;
90
54
  connectionOpen?: ConnectAPIResponse<ChannelType, CommandType, UserType>;
91
55
  consecutiveFailures: number;
92
56
  pingInterval: number;
93
57
  healthCheckTimeoutRef?: NodeJS.Timeout;
94
58
  isConnecting: boolean;
59
+ isDisconnected: boolean;
95
60
  isHealthy: boolean;
96
61
  isResolved?: boolean;
97
62
  lastEvent: Date | null;
98
63
  connectionCheckTimeout: number;
99
64
  connectionCheckTimeoutRef?: NodeJS.Timeout;
100
65
  rejectPromise?: (
101
- reason?: Error & {
102
- code?: string | number;
103
- isWSFailure?: boolean;
104
- StatusCode?: string | number;
105
- },
66
+ reason?: Error & { code?: string | number; isWSFailure?: boolean; StatusCode?: string | number },
106
67
  ) => void;
107
68
  requestID: string | undefined;
108
- connectionStartTimestamp: number | undefined;
109
- resolvePromise?: (value: WebSocket.MessageEvent) => void;
69
+ resolvePromise?: (value: ConnectionOpen<ChannelType, CommandType, UserType>) => void;
110
70
  totalFailures: number;
111
71
  ws?: WebSocket;
112
72
  wsID: number;
113
- enableInsights?: boolean;
114
- insightMetrics: InsightMetrics;
73
+
115
74
  constructor({
116
- apiKey,
117
- authType,
118
- clientID,
119
- eventCallback,
120
- logger,
121
- messageCallback,
122
- recoverCallback,
123
- tokenManager,
124
- user,
125
- userAgent,
126
- userID,
127
- wsBaseURL,
128
- device,
129
- enableInsights,
130
- insightMetrics,
131
- }: Constructor<ChannelType, CommandType, UserType>) {
132
- this.wsBaseURL = wsBaseURL;
133
- this.clientID = clientID;
134
- this.userID = userID;
135
- this.user = user;
136
- this.authType = authType;
137
- this.userAgent = userAgent;
138
- this.apiKey = apiKey;
139
- this.tokenManager = tokenManager;
140
- this.device = device;
75
+ client,
76
+ }: {
77
+ client: StreamChat<AttachmentType, ChannelType, CommandType, EventType, MessageType, ReactionType, UserType>;
78
+ }) {
79
+ /** StreamChat client */
80
+ this.client = client;
141
81
  /** consecutive failures influence the duration of the timeout */
142
82
  this.consecutiveFailures = 0;
143
83
  /** keep track of the total number of failures */
144
84
  this.totalFailures = 0;
145
85
  /** We only make 1 attempt to reconnect at the same time.. */
146
86
  this.isConnecting = false;
87
+ /** To avoid reconnect if client is disconnected */
88
+ this.isDisconnected = false;
147
89
  /** Boolean that indicates if the connection promise is resolved */
148
90
  this.isResolved = false;
149
91
  /** Boolean that indicates if we have a working connection to the server */
150
92
  this.isHealthy = false;
151
- /** Callback when the connection fails and recovers */
152
- this.recoverCallback = recoverCallback;
153
- this.messageCallback = messageCallback;
154
- this.eventCallback = eventCallback;
155
- this.logger = logger;
156
93
  /** Incremented when a new WS connection is made */
157
94
  this.wsID = 1;
158
95
  /** Store the last event time for health checks */
@@ -160,46 +97,37 @@ export class StableWSConnection<
160
97
  /** Send a health check message every 25 seconds */
161
98
  this.pingInterval = 25 * 1000;
162
99
  this.connectionCheckTimeout = this.pingInterval + 10 * 1000;
163
- this._listenForConnectionChanges();
164
- this.enableInsights = enableInsights;
165
- this.insightMetrics = insightMetrics;
100
+
101
+ addConnectionEventListeners(this.onlineStatusChanged);
102
+ }
103
+
104
+ _log(msg: string, extra: UR = {}, level: LogLevel = 'info') {
105
+ this.client.logger(level, 'connection:' + msg, { tags: ['connection'], ...extra });
166
106
  }
167
107
 
168
108
  /**
169
109
  * connect - Connect to the WS URL
170
- *
110
+ * the default 15s timeout allows between 2~3 tries
171
111
  * @return {ConnectAPIResponse<ChannelType, CommandType, UserType>} Promise that completes once the first health check message is received
172
112
  */
173
- async connect() {
113
+ async connect(timeout = 15000) {
174
114
  if (this.isConnecting) {
175
- throw Error(
176
- `You've called connect twice, can only attempt 1 connection at the time`,
177
- );
115
+ throw Error(`You've called connect twice, can only attempt 1 connection at the time`);
178
116
  }
179
117
 
118
+ this.isDisconnected = false;
119
+
180
120
  try {
181
121
  const healthCheck = await this._connect();
182
122
  this.consecutiveFailures = 0;
183
123
 
184
- this.logger(
185
- 'info',
186
- `connection:connect() - Established ws connection with healthcheck: ${healthCheck}`,
187
- {
188
- tags: ['connection'],
189
- },
190
- );
124
+ this._log(`connect() - Established ws connection with healthcheck: ${healthCheck}`);
191
125
  } catch (error) {
192
126
  this.isHealthy = false;
193
127
  this.consecutiveFailures += 1;
194
128
 
195
- if (error.code === chatCodes.TOKEN_EXPIRED && !this.tokenManager.isStatic()) {
196
- this.logger(
197
- 'info',
198
- 'connection:connect() - WS failure due to expired token, so going to try to reload token and reconnect',
199
- {
200
- tags: ['connection'],
201
- },
202
- );
129
+ if (error.code === chatCodes.TOKEN_EXPIRED && !this.client.tokenManager.isStatic()) {
130
+ this._log('connect() - WS failure due to expired token, so going to try to reload token and reconnect');
203
131
  this._reconnect({ refreshToken: true });
204
132
  } else if (!error.isWSFailure) {
205
133
  // API rejected the connection and we should not retry
@@ -214,7 +142,7 @@ export class StableWSConnection<
214
142
  }
215
143
  }
216
144
 
217
- return await this._waitForHealthy();
145
+ return await this._waitForHealthy(timeout);
218
146
  }
219
147
 
220
148
  /**
@@ -246,6 +174,7 @@ export class StableWSConnection<
246
174
  })(),
247
175
  (async () => {
248
176
  await sleep(timeout);
177
+ this.isConnecting = false;
249
178
  throw new Error(
250
179
  JSON.stringify({
251
180
  code: '',
@@ -260,21 +189,16 @@ export class StableWSConnection<
260
189
 
261
190
  /**
262
191
  * Builds and returns the url for websocket.
263
- * @param reqID Unique identifier generated on client side, to help tracking apis on backend.
192
+ * @private
264
193
  * @returns url string
265
194
  */
266
- _buildUrl = (reqID?: string) => {
267
- const params = {
268
- user_id: this.user.id,
269
- user_details: this.user,
270
- user_token: this.tokenManager.getToken(),
271
- server_determines_connection_id: true,
272
- device: this.device,
273
- client_request_id: reqID,
274
- };
275
- const qs = encodeURIComponent(JSON.stringify(params));
276
- const token = this.tokenManager.getToken();
277
- return `${this.wsBaseURL}/connect?json=${qs}&api_key=${this.apiKey}&authorization=${token}&stream-auth-type=${this.authType}&X-Stream-Client=${this.userAgent}`;
195
+ _buildUrl = () => {
196
+ const qs = encodeURIComponent(this.client._buildWSPayload(this.requestID));
197
+ const token = this.client.tokenManager.getToken();
198
+
199
+ return `${this.client.wsBaseURL}/connect?json=${qs}&api_key=${
200
+ this.client.key
201
+ }&authorization=${token}&stream-auth-type=${this.client.getAuthType()}&X-Stream-Client=${this.client.getUserAgent()}`;
278
202
  };
279
203
 
280
204
  /**
@@ -282,15 +206,11 @@ export class StableWSConnection<
282
206
  *
283
207
  */
284
208
  disconnect(timeout?: number) {
285
- this.logger(
286
- 'info',
287
- `connection:disconnect() - Closing the websocket connection for wsID ${this.wsID}`,
288
- {
289
- tags: ['connection'],
290
- },
291
- );
209
+ this._log(`disconnect() - Closing the websocket connection for wsID ${this.wsID}`);
292
210
 
293
211
  this.wsID += 1;
212
+ this.isConnecting = false;
213
+ this.isDisconnected = true;
294
214
 
295
215
  // start by removing all the listeners
296
216
  if (this.healthCheckTimeoutRef) {
@@ -300,7 +220,7 @@ export class StableWSConnection<
300
220
  clearInterval(this.connectionCheckTimeoutRef);
301
221
  }
302
222
 
303
- this._removeConnectionListeners();
223
+ removeConnectionEventListeners(this.onlineStatusChanged);
304
224
 
305
225
  this.isHealthy = false;
306
226
 
@@ -317,16 +237,7 @@ export class StableWSConnection<
317
237
  if (ws && ws.close && ws.readyState === ws.OPEN) {
318
238
  isClosedPromise = new Promise((resolve) => {
319
239
  const onclose = (event: WebSocket.CloseEvent) => {
320
- this.logger(
321
- 'info',
322
- `connection:disconnect() - resolving isClosedPromise ${
323
- event ? 'with' : 'without'
324
- } close frame`,
325
- {
326
- tags: ['connection'],
327
- event,
328
- },
329
- );
240
+ this._log(`disconnect() - resolving isClosedPromise ${event ? 'with' : 'without'} close frame`, { event });
330
241
  resolve();
331
242
  };
332
243
 
@@ -336,26 +247,11 @@ export class StableWSConnection<
336
247
  setTimeout(onclose, timeout != null ? timeout : 1000);
337
248
  });
338
249
 
339
- this.logger(
340
- 'info',
341
- `connection:disconnect() - Manually closed connection by calling client.disconnect()`,
342
- {
343
- tags: ['connection'],
344
- },
345
- );
346
-
347
- ws.close(
348
- chatCodes.WS_CLOSED_SUCCESS,
349
- 'Manually closed connection by calling client.disconnect()',
350
- );
250
+ this._log(`disconnect() - Manually closed connection by calling client.disconnect()`);
251
+
252
+ ws.close(chatCodes.WS_CLOSED_SUCCESS, 'Manually closed connection by calling client.disconnect()');
351
253
  } else {
352
- this.logger(
353
- 'info',
354
- `connection:disconnect() - ws connection doesn't exist or it is already closed.`,
355
- {
356
- tags: ['connection'],
357
- },
358
- );
254
+ this._log(`disconnect() - ws connection doesn't exist or it is already closed.`);
359
255
  isClosedPromise = Promise.resolve();
360
256
  }
361
257
 
@@ -370,14 +266,14 @@ export class StableWSConnection<
370
266
  * @return {ConnectAPIResponse<ChannelType, CommandType, UserType>} Promise that completes once the first health check message is received
371
267
  */
372
268
  async _connect() {
373
- if (this.isConnecting) return; // simply ignore _connect if it's currently trying to connect
269
+ if (this.isConnecting || this.isDisconnected) return; // simply ignore _connect if it's currently trying to connect
374
270
  this.isConnecting = true;
375
271
  this.requestID = randomId();
376
- this.insightMetrics.connectionStartTimestamp = new Date().getTime();
272
+ this.client.insightMetrics.connectionStartTimestamp = new Date().getTime();
377
273
  try {
378
- await this.tokenManager.tokenReady();
274
+ await this.client.tokenManager.tokenReady();
379
275
  this._setupConnectionPromise();
380
- const wsURL = this._buildUrl(this.requestID);
276
+ const wsURL = this._buildUrl();
381
277
  this.ws = new WebSocket(wsURL);
382
278
  this.ws.onopen = this.onopen.bind(this, this.wsID);
383
279
  this.ws.onclose = this.onclose.bind(this, this.wsID);
@@ -388,26 +284,23 @@ export class StableWSConnection<
388
284
 
389
285
  if (response) {
390
286
  this.connectionID = response.connection_id;
391
- if (this.insightMetrics.wsConsecutiveFailures > 0 && this.enableInsights) {
287
+ if (this.client.insightMetrics.wsConsecutiveFailures > 0 && this.client.options.enableInsights) {
392
288
  postInsights(
393
289
  'ws_success_after_failure',
394
290
  buildWsSuccessAfterFailureInsight((this as unknown) as StableWSConnection),
395
291
  );
396
- this.insightMetrics.wsConsecutiveFailures = 0;
292
+ this.client.insightMetrics.wsConsecutiveFailures = 0;
397
293
  }
398
294
  return response;
399
295
  }
400
296
  } catch (err) {
401
297
  this.isConnecting = false;
402
298
 
403
- if (this.enableInsights) {
404
- this.insightMetrics.wsConsecutiveFailures++;
405
- this.insightMetrics.wsTotalFailures++;
299
+ if (this.client.options.enableInsights) {
300
+ this.client.insightMetrics.wsConsecutiveFailures++;
301
+ this.client.insightMetrics.wsTotalFailures++;
406
302
 
407
- const insights = buildWsFatalInsight(
408
- (this as unknown) as StableWSConnection,
409
- convertErrorToJson(err as Error),
410
- );
303
+ const insights = buildWsFatalInsight((this as unknown) as StableWSConnection, convertErrorToJson(err as Error));
411
304
  postInsights?.('ws_fatal', insights);
412
305
  }
413
306
  throw err;
@@ -422,21 +315,12 @@ export class StableWSConnection<
422
315
  * - `interval` {int} number of ms that function should wait before reconnecting
423
316
  * - `refreshToken` {boolean} reload/refresh user token be refreshed before attempting reconnection.
424
317
  */
425
- async _reconnect(
426
- options: { interval?: number; refreshToken?: boolean } = {},
427
- ): Promise<void> {
428
- this.logger('info', 'connection:_reconnect() - Initiating the reconnect', {
429
- tags: ['connection'],
430
- });
318
+ async _reconnect(options: { interval?: number; refreshToken?: boolean } = {}): Promise<void> {
319
+ this._log('_reconnect() - Initiating the reconnect');
320
+
431
321
  // only allow 1 connection at the time
432
322
  if (this.isConnecting || this.isHealthy) {
433
- this.logger(
434
- 'info',
435
- 'connection:_reconnect() - Abort (1) since already connecting or healthy',
436
- {
437
- tags: ['connection'],
438
- },
439
- );
323
+ this._log('_reconnect() - Abort (1) since already connecting or healthy');
440
324
  return;
441
325
  }
442
326
 
@@ -452,70 +336,48 @@ export class StableWSConnection<
452
336
  // Check once again if by some other call to _reconnect is active or connection is
453
337
  // already restored, then no need to proceed.
454
338
  if (this.isConnecting || this.isHealthy) {
455
- this.logger(
456
- 'info',
457
- 'connection:_reconnect() - Abort (2) since already connecting or healthy',
458
- {
459
- tags: ['connection'],
460
- },
461
- );
339
+ this._log('_reconnect() - Abort (2) since already connecting or healthy');
462
340
  return;
463
341
  }
464
342
 
465
- // cleanup the old connection
466
- this.logger('info', 'connection:_reconnect() - Destroying current WS connection', {
467
- tags: ['connection'],
468
- });
343
+ if (this.isDisconnected) {
344
+ this._log('_reconnect() - Abort (3) since disconnect() is called');
345
+ return;
346
+ }
347
+
348
+ this._log('_reconnect() - Destroying current WS connection');
469
349
 
350
+ // cleanup the old connection
470
351
  this._destroyCurrentWSConnection();
471
352
 
472
353
  if (options.refreshToken) {
473
- await this.tokenManager.loadToken();
354
+ await this.client.tokenManager.loadToken();
474
355
  }
475
356
 
476
357
  try {
477
- const open = await this._connect();
478
- if (this.recoverCallback) {
479
- this.logger('info', 'connection:_reconnect() - Waiting for recoverCallBack', {
480
- tags: ['connection'],
481
- });
482
- await this.recoverCallback(open);
483
- this.logger('info', 'connection:_reconnect() - Finished recoverCallBack', {
484
- tags: ['connection'],
485
- });
486
- }
358
+ await this._connect();
359
+ this._log('_reconnect() - Waiting for recoverCallBack');
360
+ await this.client.recoverState();
361
+ this._log('_reconnect() - Finished recoverCallBack');
362
+
487
363
  this.consecutiveFailures = 0;
488
364
  } catch (error) {
489
365
  this.isHealthy = false;
490
366
  this.consecutiveFailures += 1;
491
- if (error.code === chatCodes.TOKEN_EXPIRED && !this.tokenManager.isStatic()) {
492
- this.logger(
493
- 'info',
494
- 'connection:_reconnect() - WS failure due to expired token, so going to try to reload token and reconnect',
495
- {
496
- tags: ['connection'],
497
- },
498
- );
367
+ if (error.code === chatCodes.TOKEN_EXPIRED && !this.client.tokenManager.isStatic()) {
368
+ this._log('_reconnect() - WS failure due to expired token, so going to try to reload token and reconnect');
499
369
 
500
370
  return this._reconnect({ refreshToken: true });
501
371
  }
502
372
 
503
373
  // reconnect on WS failures, don't reconnect if there is a code bug
504
374
  if (error.isWSFailure) {
505
- this.logger(
506
- 'info',
507
- 'connection:_reconnect() - WS failure, so going to try to reconnect',
508
- {
509
- tags: ['connection'],
510
- },
511
- );
375
+ this._log('_reconnect() - WS failure, so going to try to reconnect');
512
376
 
513
377
  this._reconnect();
514
378
  }
515
379
  }
516
- this.logger('info', 'connection:_reconnect() - == END ==', {
517
- tags: ['connection'],
518
- });
380
+ this._log('_reconnect() - == END ==');
519
381
  }
520
382
 
521
383
  /**
@@ -527,26 +389,14 @@ export class StableWSConnection<
527
389
  onlineStatusChanged = (event: Event) => {
528
390
  if (event.type === 'offline') {
529
391
  // mark the connection as down
530
- this.logger(
531
- 'info',
532
- 'connection:onlineStatusChanged() - Status changing to offline',
533
- {
534
- tags: ['connection'],
535
- },
536
- );
392
+ this._log('onlineStatusChanged() - Status changing to offline');
537
393
  this._setHealth(false);
538
394
  } else if (event.type === 'online') {
539
395
  // retry right now...
540
396
  // We check this.isHealthy, not sure if it's always
541
397
  // smart to create a new WS connection if the old one is still up and running.
542
398
  // it's possible we didn't miss any messages, so this process is just expensive and not needed.
543
- this.logger(
544
- 'info',
545
- `connection:onlineStatusChanged() - Status changing to online. isHealthy: ${this.isHealthy}`,
546
- {
547
- tags: ['connection'],
548
- },
549
- );
399
+ this._log(`onlineStatusChanged() - Status changing to online. isHealthy: ${this.isHealthy}`);
550
400
  if (!this.isHealthy) {
551
401
  this._reconnect({ interval: 10 });
552
402
  }
@@ -556,10 +406,7 @@ export class StableWSConnection<
556
406
  onopen = (wsID: number) => {
557
407
  if (this.wsID !== wsID) return;
558
408
 
559
- this.logger('info', 'connection:onopen() - onopen callback', {
560
- tags: ['connection'],
561
- wsID,
562
- });
409
+ this._log('onopen() - onopen callback', { wsID });
563
410
  };
564
411
 
565
412
  onmessage = (wsID: number, event: WebSocket.MessageEvent) => {
@@ -572,46 +419,36 @@ export class StableWSConnection<
572
419
  // after that a ws.onclose..
573
420
  if (!this.isResolved && data) {
574
421
  this.isResolved = true;
575
- if (data.error != null) {
422
+ if (data.error) {
576
423
  this.rejectPromise?.(this._errorFromWSEvent(data, false));
577
424
  return;
578
- } else {
579
- this.resolvePromise?.(event);
580
- this._setHealth(true);
581
425
  }
426
+
427
+ this.resolvePromise?.(data);
428
+ this._setHealth(true);
582
429
  }
583
430
 
584
431
  // trigger the event..
585
432
  this.lastEvent = new Date();
586
- this.logger('info', 'connection:onmessage() - onmessage callback', {
587
- tags: ['connection'],
588
- event,
589
- wsID,
590
- });
433
+ this._log('onmessage() - onmessage callback', { event, wsID });
591
434
 
592
435
  if (data && data.type === 'health.check') {
593
436
  this.scheduleNextPing();
594
437
  }
595
438
 
596
- this.messageCallback(event);
439
+ this.client.handleEvent(event);
597
440
  this.scheduleConnectionCheck();
598
441
  };
599
442
 
600
443
  onclose = (wsID: number, event: WebSocket.CloseEvent) => {
601
444
  if (this.wsID !== wsID) return;
602
445
 
603
- this.logger('info', 'connection:onclose() - onclose callback - ' + event.code, {
604
- tags: ['connection'],
605
- event,
606
- wsID,
607
- });
446
+ this._log('onclose() - onclose callback - ' + event.code, { event, wsID });
608
447
 
609
448
  if (event.code === chatCodes.WS_CLOSED_SUCCESS) {
610
449
  // this is a permanent error raised by stream..
611
450
  // usually caused by invalid auth details
612
- const error = new Error(
613
- `WS connection reject with error ${event.reason}`,
614
- ) as Error & WebSocket.CloseEvent;
451
+ const error = new Error(`WS connection reject with error ${event.reason}`) as Error & WebSocket.CloseEvent;
615
452
 
616
453
  error.reason = event.reason;
617
454
  error.code = event.code;
@@ -619,14 +456,7 @@ export class StableWSConnection<
619
456
  error.target = event.target;
620
457
 
621
458
  this.rejectPromise?.(error);
622
- this.logger(
623
- 'info',
624
- `connection:onclose() - WS connection reject with error ${event.reason}`,
625
- {
626
- tags: ['connection'],
627
- event,
628
- },
629
- );
459
+ this._log(`onclose() - WS connection reject with error ${event.reason}`, { event });
630
460
  } else {
631
461
  this.consecutiveFailures += 1;
632
462
  this.totalFailures += 1;
@@ -635,14 +465,7 @@ export class StableWSConnection<
635
465
 
636
466
  this.rejectPromise?.(this._errorFromWSEvent(event));
637
467
 
638
- this.logger(
639
- 'info',
640
- `connection:onclose() - WS connection closed. Calling reconnect ...`,
641
- {
642
- tags: ['connection'],
643
- event,
644
- },
645
- );
468
+ this._log(`onclose() - WS connection closed. Calling reconnect ...`, { event });
646
469
 
647
470
  // reconnect if its an abnormal failure
648
471
  this._reconnect();
@@ -658,10 +481,7 @@ export class StableWSConnection<
658
481
  this.isConnecting = false;
659
482
 
660
483
  this.rejectPromise?.(this._errorFromWSEvent(event));
661
- this.logger('info', `connection:onerror() - WS connection resulted into error`, {
662
- tags: ['connection'],
663
- event,
664
- });
484
+ this._log(`onerror() - WS connection resulted into error`, { event });
665
485
 
666
486
  this._reconnect();
667
487
  };
@@ -674,37 +494,29 @@ export class StableWSConnection<
674
494
  *
675
495
  */
676
496
  _setHealth = (healthy: boolean) => {
677
- if (healthy && !this.isHealthy) {
678
- // yes we are online:
679
- this.isHealthy = true;
680
- this.eventCallback({
681
- type: 'connection.changed',
682
- online: true,
683
- });
684
- }
497
+ if (healthy === this.isHealthy) return;
685
498
 
686
- if (!healthy && this.isHealthy) {
687
- // bummer we are offline
688
- this.isHealthy = false;
689
- setTimeout(() => {
690
- if (!this.isHealthy) {
691
- this.eventCallback({
692
- type: 'connection.changed',
693
- online: false,
694
- });
695
- }
696
- }, 5000);
499
+ this.isHealthy = healthy;
500
+
501
+ if (this.isHealthy) {
502
+ //@ts-expect-error
503
+ this.client.dispatchEvent({ type: 'connection.changed', online: this.isHealthy });
504
+ return;
697
505
  }
506
+
507
+ // we're offline, wait few seconds and fire and event if still offline
508
+ setTimeout(() => {
509
+ if (this.isHealthy) return;
510
+ //@ts-expect-error
511
+ this.client.dispatchEvent({ type: 'connection.changed', online: this.isHealthy });
512
+ }, 5000);
698
513
  };
699
514
 
700
515
  /**
701
516
  * _errorFromWSEvent - Creates an error object for the WS event
702
517
  *
703
518
  */
704
- _errorFromWSEvent = (
705
- event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
706
- isWSFailure = true,
707
- ) => {
519
+ _errorFromWSEvent = (event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent, isWSFailure = true) => {
708
520
  let code;
709
521
  let statusCode;
710
522
  let message;
@@ -721,14 +533,9 @@ export class StableWSConnection<
721
533
  }
722
534
 
723
535
  // Keeping this `warn` level log, to avoid cluttering of error logs from ws failures.
724
- this.logger('warn', `connection:_errorFromWSEvent() - WS failed with code ${code}`, {
725
- tags: ['connection'],
726
- event,
727
- });
536
+ this._log(`_errorFromWSEvent() - WS failed with code ${code}`, { event }, 'warn');
728
537
 
729
- const error = new Error(
730
- `WS failed with code ${code} and reason - ${message}`,
731
- ) as Error & {
538
+ const error = new Error(`WS failed with code ${code} and reason - ${message}`) as Error & {
732
539
  code?: string | number;
733
540
  isWSFailure?: boolean;
734
541
  StatusCode?: string | number;
@@ -743,27 +550,6 @@ export class StableWSConnection<
743
550
  return error;
744
551
  };
745
552
 
746
- /**
747
- * _listenForConnectionChanges - Adds an event listener for the browser going online or offline
748
- */
749
- _listenForConnectionChanges = () => {
750
- // (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
751
- // and thus (window === undefined) will result in ReferenceError.
752
- if (typeof window !== 'undefined' && window?.addEventListener) {
753
- window.addEventListener('offline', this.onlineStatusChanged);
754
- window.addEventListener('online', this.onlineStatusChanged);
755
- }
756
- };
757
-
758
- _removeConnectionListeners = () => {
759
- // (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
760
- // and thus (window === undefined) will result in ReferenceError.
761
- if (typeof window !== 'undefined' && window?.removeEventListener) {
762
- window.removeEventListener('offline', this.onlineStatusChanged);
763
- window.removeEventListener('online', this.onlineStatusChanged);
764
- }
765
- };
766
-
767
553
  /**
768
554
  * _destroyCurrentWSConnection - Removes the current WS connection
769
555
  *
@@ -774,13 +560,8 @@ export class StableWSConnection<
774
560
  this.wsID += 1;
775
561
 
776
562
  try {
777
- if (this.ws && this.ws.removeAllListeners) {
778
- this.ws.removeAllListeners();
779
- }
780
-
781
- if (this.ws && this.ws.close) {
782
- this.ws.close();
783
- }
563
+ this?.ws?.removeAllListeners();
564
+ this?.ws?.close();
784
565
  } catch (e) {
785
566
  // we don't care
786
567
  }
@@ -790,34 +571,12 @@ export class StableWSConnection<
790
571
  * _setupPromise - sets up the this.connectOpen promise
791
572
  */
792
573
  _setupConnectionPromise = () => {
793
- const that = this;
794
574
  this.isResolved = false;
795
575
  /** a promise that is resolved once ws.open is called */
796
- this.connectionOpen = new Promise<WebSocket.MessageEvent>(function (resolve, reject) {
797
- that.resolvePromise = resolve;
798
- that.rejectPromise = reject;
799
- }).then(
800
- (e) => {
801
- if (e.data && typeof e.data === 'string') {
802
- const data = JSON.parse(e.data) as ConnectionOpen<
803
- ChannelType,
804
- CommandType,
805
- UserType
806
- > & {
807
- error?: unknown;
808
- };
809
- if (data && data.error != null) {
810
- throw new Error(JSON.stringify(data.error));
811
- }
812
- return data;
813
- } else {
814
- return undefined;
815
- }
816
- },
817
- (error) => {
818
- throw error;
819
- },
820
- );
576
+ this.connectionOpen = new Promise<ConnectionOpen<ChannelType, CommandType, UserType>>((resolve, reject) => {
577
+ this.resolvePromise = resolve;
578
+ this.rejectPromise = reject;
579
+ });
821
580
  };
822
581
 
823
582
  /**
@@ -831,12 +590,7 @@ export class StableWSConnection<
831
590
  // 30 seconds is the recommended interval (messenger uses this)
832
591
  this.healthCheckTimeoutRef = setTimeout(() => {
833
592
  // send the healthcheck.., server replies with a health check event
834
- const data = [
835
- {
836
- type: 'health.check',
837
- client_id: this.clientID,
838
- },
839
- ];
593
+ const data = [{ type: 'health.check', client_id: this.client.clientID }];
840
594
  // try to send on the connection
841
595
  try {
842
596
  this.ws?.send(JSON.stringify(data));
@@ -858,13 +612,8 @@ export class StableWSConnection<
858
612
 
859
613
  this.connectionCheckTimeoutRef = setTimeout(() => {
860
614
  const now = new Date();
861
- if (
862
- this.lastEvent &&
863
- now.getTime() - this.lastEvent.getTime() > this.connectionCheckTimeout
864
- ) {
865
- this.logger('info', 'connection:scheduleConnectionCheck - going to reconnect', {
866
- tags: ['connection'],
867
- });
615
+ if (this.lastEvent && now.getTime() - this.lastEvent.getTime() > this.connectionCheckTimeout) {
616
+ this._log('scheduleConnectionCheck - going to reconnect');
868
617
  this._setHealth(false);
869
618
  this._reconnect();
870
619
  }