stream-chat 4.4.3-dev.2 → 5.0.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 +1229 -720
  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 +1229 -719
  7. package/dist/browser.js.map +1 -1
  8. package/dist/index.es.js +1229 -720
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.js +1229 -719
  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 +25 -42
  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 +41 -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 +17 -10
  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 -88
  35. package/dist/types/types.d.ts.map +1 -1
  36. package/dist/types/utils.d.ts +13 -3
  37. package/dist/types/utils.d.ts.map +1 -1
  38. package/package.json +4 -4
  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 +291 -712
  43. package/src/client_state.ts +2 -2
  44. package/src/connection.ts +146 -395
  45. package/src/connection_fallback.ts +205 -0
  46. package/src/errors.ts +58 -0
  47. package/src/insights.ts +38 -32
  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 -512
  52. package/src/utils.ts +58 -24
  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
- InsightTypes,
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
- postInsights?: (eventType: InsightTypes, event: Record<string, unknown>) => void;
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
- postInsights?: Constructor<ChannelType, CommandType, UserType>['postInsights'];
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
- postInsights,
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.postInsights = postInsights;
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,25 +284,24 @@ export class StableWSConnection<
388
284
 
389
285
  if (response) {
390
286
  this.connectionID = response.connection_id;
391
- if (this.insightMetrics.wsConsecutiveFailures > 0 && this.postInsights) {
392
- this.postInsights(
287
+ if (this.client.insightMetrics.wsConsecutiveFailures > 0 && this.client.options.enableInsights) {
288
+ postInsights(
393
289
  'ws_success_after_failure',
394
- buildWsSuccessAfterFailureInsight(this),
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.postInsights) {
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
- // @ts-ignore
408
- const insights = buildWsFatalInsight(this, convertErrorToJson(err));
409
- this.postInsights?.('ws_fatal', insights);
303
+ const insights = buildWsFatalInsight((this as unknown) as StableWSConnection, convertErrorToJson(err as Error));
304
+ postInsights?.('ws_fatal', insights);
410
305
  }
411
306
  throw err;
412
307
  }
@@ -420,21 +315,12 @@ export class StableWSConnection<
420
315
  * - `interval` {int} number of ms that function should wait before reconnecting
421
316
  * - `refreshToken` {boolean} reload/refresh user token be refreshed before attempting reconnection.
422
317
  */
423
- async _reconnect(
424
- options: { interval?: number; refreshToken?: boolean } = {},
425
- ): Promise<void> {
426
- this.logger('info', 'connection:_reconnect() - Initiating the reconnect', {
427
- tags: ['connection'],
428
- });
318
+ async _reconnect(options: { interval?: number; refreshToken?: boolean } = {}): Promise<void> {
319
+ this._log('_reconnect() - Initiating the reconnect');
320
+
429
321
  // only allow 1 connection at the time
430
322
  if (this.isConnecting || this.isHealthy) {
431
- this.logger(
432
- 'info',
433
- 'connection:_reconnect() - Abort (1) since already connecting or healthy',
434
- {
435
- tags: ['connection'],
436
- },
437
- );
323
+ this._log('_reconnect() - Abort (1) since already connecting or healthy');
438
324
  return;
439
325
  }
440
326
 
@@ -450,70 +336,48 @@ export class StableWSConnection<
450
336
  // Check once again if by some other call to _reconnect is active or connection is
451
337
  // already restored, then no need to proceed.
452
338
  if (this.isConnecting || this.isHealthy) {
453
- this.logger(
454
- 'info',
455
- 'connection:_reconnect() - Abort (2) since already connecting or healthy',
456
- {
457
- tags: ['connection'],
458
- },
459
- );
339
+ this._log('_reconnect() - Abort (2) since already connecting or healthy');
460
340
  return;
461
341
  }
462
342
 
463
- // cleanup the old connection
464
- this.logger('info', 'connection:_reconnect() - Destroying current WS connection', {
465
- tags: ['connection'],
466
- });
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');
467
349
 
350
+ // cleanup the old connection
468
351
  this._destroyCurrentWSConnection();
469
352
 
470
353
  if (options.refreshToken) {
471
- await this.tokenManager.loadToken();
354
+ await this.client.tokenManager.loadToken();
472
355
  }
473
356
 
474
357
  try {
475
- const open = await this._connect();
476
- if (this.recoverCallback) {
477
- this.logger('info', 'connection:_reconnect() - Waiting for recoverCallBack', {
478
- tags: ['connection'],
479
- });
480
- await this.recoverCallback(open);
481
- this.logger('info', 'connection:_reconnect() - Finished recoverCallBack', {
482
- tags: ['connection'],
483
- });
484
- }
358
+ await this._connect();
359
+ this._log('_reconnect() - Waiting for recoverCallBack');
360
+ await this.client.recoverState();
361
+ this._log('_reconnect() - Finished recoverCallBack');
362
+
485
363
  this.consecutiveFailures = 0;
486
364
  } catch (error) {
487
365
  this.isHealthy = false;
488
366
  this.consecutiveFailures += 1;
489
- if (error.code === chatCodes.TOKEN_EXPIRED && !this.tokenManager.isStatic()) {
490
- this.logger(
491
- 'info',
492
- 'connection:_reconnect() - WS failure due to expired token, so going to try to reload token and reconnect',
493
- {
494
- tags: ['connection'],
495
- },
496
- );
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');
497
369
 
498
370
  return this._reconnect({ refreshToken: true });
499
371
  }
500
372
 
501
373
  // reconnect on WS failures, don't reconnect if there is a code bug
502
374
  if (error.isWSFailure) {
503
- this.logger(
504
- 'info',
505
- 'connection:_reconnect() - WS failure, so going to try to reconnect',
506
- {
507
- tags: ['connection'],
508
- },
509
- );
375
+ this._log('_reconnect() - WS failure, so going to try to reconnect');
510
376
 
511
377
  this._reconnect();
512
378
  }
513
379
  }
514
- this.logger('info', 'connection:_reconnect() - == END ==', {
515
- tags: ['connection'],
516
- });
380
+ this._log('_reconnect() - == END ==');
517
381
  }
518
382
 
519
383
  /**
@@ -525,26 +389,14 @@ export class StableWSConnection<
525
389
  onlineStatusChanged = (event: Event) => {
526
390
  if (event.type === 'offline') {
527
391
  // mark the connection as down
528
- this.logger(
529
- 'info',
530
- 'connection:onlineStatusChanged() - Status changing to offline',
531
- {
532
- tags: ['connection'],
533
- },
534
- );
392
+ this._log('onlineStatusChanged() - Status changing to offline');
535
393
  this._setHealth(false);
536
394
  } else if (event.type === 'online') {
537
395
  // retry right now...
538
396
  // We check this.isHealthy, not sure if it's always
539
397
  // smart to create a new WS connection if the old one is still up and running.
540
398
  // it's possible we didn't miss any messages, so this process is just expensive and not needed.
541
- this.logger(
542
- 'info',
543
- `connection:onlineStatusChanged() - Status changing to online. isHealthy: ${this.isHealthy}`,
544
- {
545
- tags: ['connection'],
546
- },
547
- );
399
+ this._log(`onlineStatusChanged() - Status changing to online. isHealthy: ${this.isHealthy}`);
548
400
  if (!this.isHealthy) {
549
401
  this._reconnect({ interval: 10 });
550
402
  }
@@ -554,10 +406,7 @@ export class StableWSConnection<
554
406
  onopen = (wsID: number) => {
555
407
  if (this.wsID !== wsID) return;
556
408
 
557
- this.logger('info', 'connection:onopen() - onopen callback', {
558
- tags: ['connection'],
559
- wsID,
560
- });
409
+ this._log('onopen() - onopen callback', { wsID });
561
410
  };
562
411
 
563
412
  onmessage = (wsID: number, event: WebSocket.MessageEvent) => {
@@ -570,46 +419,36 @@ export class StableWSConnection<
570
419
  // after that a ws.onclose..
571
420
  if (!this.isResolved && data) {
572
421
  this.isResolved = true;
573
- if (data.error != null) {
422
+ if (data.error) {
574
423
  this.rejectPromise?.(this._errorFromWSEvent(data, false));
575
424
  return;
576
- } else {
577
- this.resolvePromise?.(event);
578
- this._setHealth(true);
579
425
  }
426
+
427
+ this.resolvePromise?.(data);
428
+ this._setHealth(true);
580
429
  }
581
430
 
582
431
  // trigger the event..
583
432
  this.lastEvent = new Date();
584
- this.logger('info', 'connection:onmessage() - onmessage callback', {
585
- tags: ['connection'],
586
- event,
587
- wsID,
588
- });
433
+ this._log('onmessage() - onmessage callback', { event, wsID });
589
434
 
590
435
  if (data && data.type === 'health.check') {
591
436
  this.scheduleNextPing();
592
437
  }
593
438
 
594
- this.messageCallback(event);
439
+ this.client.handleEvent(event);
595
440
  this.scheduleConnectionCheck();
596
441
  };
597
442
 
598
443
  onclose = (wsID: number, event: WebSocket.CloseEvent) => {
599
444
  if (this.wsID !== wsID) return;
600
445
 
601
- this.logger('info', 'connection:onclose() - onclose callback - ' + event.code, {
602
- tags: ['connection'],
603
- event,
604
- wsID,
605
- });
446
+ this._log('onclose() - onclose callback - ' + event.code, { event, wsID });
606
447
 
607
448
  if (event.code === chatCodes.WS_CLOSED_SUCCESS) {
608
449
  // this is a permanent error raised by stream..
609
450
  // usually caused by invalid auth details
610
- const error = new Error(
611
- `WS connection reject with error ${event.reason}`,
612
- ) as Error & WebSocket.CloseEvent;
451
+ const error = new Error(`WS connection reject with error ${event.reason}`) as Error & WebSocket.CloseEvent;
613
452
 
614
453
  error.reason = event.reason;
615
454
  error.code = event.code;
@@ -617,14 +456,7 @@ export class StableWSConnection<
617
456
  error.target = event.target;
618
457
 
619
458
  this.rejectPromise?.(error);
620
- this.logger(
621
- 'info',
622
- `connection:onclose() - WS connection reject with error ${event.reason}`,
623
- {
624
- tags: ['connection'],
625
- event,
626
- },
627
- );
459
+ this._log(`onclose() - WS connection reject with error ${event.reason}`, { event });
628
460
  } else {
629
461
  this.consecutiveFailures += 1;
630
462
  this.totalFailures += 1;
@@ -633,14 +465,7 @@ export class StableWSConnection<
633
465
 
634
466
  this.rejectPromise?.(this._errorFromWSEvent(event));
635
467
 
636
- this.logger(
637
- 'info',
638
- `connection:onclose() - WS connection closed. Calling reconnect ...`,
639
- {
640
- tags: ['connection'],
641
- event,
642
- },
643
- );
468
+ this._log(`onclose() - WS connection closed. Calling reconnect ...`, { event });
644
469
 
645
470
  // reconnect if its an abnormal failure
646
471
  this._reconnect();
@@ -656,10 +481,7 @@ export class StableWSConnection<
656
481
  this.isConnecting = false;
657
482
 
658
483
  this.rejectPromise?.(this._errorFromWSEvent(event));
659
- this.logger('info', `connection:onerror() - WS connection resulted into error`, {
660
- tags: ['connection'],
661
- event,
662
- });
484
+ this._log(`onerror() - WS connection resulted into error`, { event });
663
485
 
664
486
  this._reconnect();
665
487
  };
@@ -672,37 +494,29 @@ export class StableWSConnection<
672
494
  *
673
495
  */
674
496
  _setHealth = (healthy: boolean) => {
675
- if (healthy && !this.isHealthy) {
676
- // yes we are online:
677
- this.isHealthy = true;
678
- this.eventCallback({
679
- type: 'connection.changed',
680
- online: true,
681
- });
682
- }
497
+ if (healthy === this.isHealthy) return;
683
498
 
684
- if (!healthy && this.isHealthy) {
685
- // bummer we are offline
686
- this.isHealthy = false;
687
- setTimeout(() => {
688
- if (!this.isHealthy) {
689
- this.eventCallback({
690
- type: 'connection.changed',
691
- online: false,
692
- });
693
- }
694
- }, 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;
695
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);
696
513
  };
697
514
 
698
515
  /**
699
516
  * _errorFromWSEvent - Creates an error object for the WS event
700
517
  *
701
518
  */
702
- _errorFromWSEvent = (
703
- event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent,
704
- isWSFailure = true,
705
- ) => {
519
+ _errorFromWSEvent = (event: WebSocket.CloseEvent | WebSocket.Data | WebSocket.ErrorEvent, isWSFailure = true) => {
706
520
  let code;
707
521
  let statusCode;
708
522
  let message;
@@ -719,14 +533,9 @@ export class StableWSConnection<
719
533
  }
720
534
 
721
535
  // Keeping this `warn` level log, to avoid cluttering of error logs from ws failures.
722
- this.logger('warn', `connection:_errorFromWSEvent() - WS failed with code ${code}`, {
723
- tags: ['connection'],
724
- event,
725
- });
536
+ this._log(`_errorFromWSEvent() - WS failed with code ${code}`, { event }, 'warn');
726
537
 
727
- const error = new Error(
728
- `WS failed with code ${code} and reason - ${message}`,
729
- ) as Error & {
538
+ const error = new Error(`WS failed with code ${code} and reason - ${message}`) as Error & {
730
539
  code?: string | number;
731
540
  isWSFailure?: boolean;
732
541
  StatusCode?: string | number;
@@ -741,27 +550,6 @@ export class StableWSConnection<
741
550
  return error;
742
551
  };
743
552
 
744
- /**
745
- * _listenForConnectionChanges - Adds an event listener for the browser going online or offline
746
- */
747
- _listenForConnectionChanges = () => {
748
- // (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
749
- // and thus (window === undefined) will result in ReferenceError.
750
- if (typeof window !== 'undefined' && window?.addEventListener) {
751
- window.addEventListener('offline', this.onlineStatusChanged);
752
- window.addEventListener('online', this.onlineStatusChanged);
753
- }
754
- };
755
-
756
- _removeConnectionListeners = () => {
757
- // (typeof window !== 'undefined') check is for environments where window is not defined, such as nextjs environment,
758
- // and thus (window === undefined) will result in ReferenceError.
759
- if (typeof window !== 'undefined' && window?.removeEventListener) {
760
- window.removeEventListener('offline', this.onlineStatusChanged);
761
- window.removeEventListener('online', this.onlineStatusChanged);
762
- }
763
- };
764
-
765
553
  /**
766
554
  * _destroyCurrentWSConnection - Removes the current WS connection
767
555
  *
@@ -772,13 +560,8 @@ export class StableWSConnection<
772
560
  this.wsID += 1;
773
561
 
774
562
  try {
775
- if (this.ws && this.ws.removeAllListeners) {
776
- this.ws.removeAllListeners();
777
- }
778
-
779
- if (this.ws && this.ws.close) {
780
- this.ws.close();
781
- }
563
+ this?.ws?.removeAllListeners();
564
+ this?.ws?.close();
782
565
  } catch (e) {
783
566
  // we don't care
784
567
  }
@@ -788,34 +571,12 @@ export class StableWSConnection<
788
571
  * _setupPromise - sets up the this.connectOpen promise
789
572
  */
790
573
  _setupConnectionPromise = () => {
791
- const that = this;
792
574
  this.isResolved = false;
793
575
  /** a promise that is resolved once ws.open is called */
794
- this.connectionOpen = new Promise<WebSocket.MessageEvent>(function (resolve, reject) {
795
- that.resolvePromise = resolve;
796
- that.rejectPromise = reject;
797
- }).then(
798
- (e) => {
799
- if (e.data && typeof e.data === 'string') {
800
- const data = JSON.parse(e.data) as ConnectionOpen<
801
- ChannelType,
802
- CommandType,
803
- UserType
804
- > & {
805
- error?: unknown;
806
- };
807
- if (data && data.error != null) {
808
- throw new Error(JSON.stringify(data.error));
809
- }
810
- return data;
811
- } else {
812
- return undefined;
813
- }
814
- },
815
- (error) => {
816
- throw error;
817
- },
818
- );
576
+ this.connectionOpen = new Promise<ConnectionOpen<ChannelType, CommandType, UserType>>((resolve, reject) => {
577
+ this.resolvePromise = resolve;
578
+ this.rejectPromise = reject;
579
+ });
819
580
  };
820
581
 
821
582
  /**
@@ -829,12 +590,7 @@ export class StableWSConnection<
829
590
  // 30 seconds is the recommended interval (messenger uses this)
830
591
  this.healthCheckTimeoutRef = setTimeout(() => {
831
592
  // send the healthcheck.., server replies with a health check event
832
- const data = [
833
- {
834
- type: 'health.check',
835
- client_id: this.clientID,
836
- },
837
- ];
593
+ const data = [{ type: 'health.check', client_id: this.client.clientID }];
838
594
  // try to send on the connection
839
595
  try {
840
596
  this.ws?.send(JSON.stringify(data));
@@ -856,13 +612,8 @@ export class StableWSConnection<
856
612
 
857
613
  this.connectionCheckTimeoutRef = setTimeout(() => {
858
614
  const now = new Date();
859
- if (
860
- this.lastEvent &&
861
- now.getTime() - this.lastEvent.getTime() > this.connectionCheckTimeout
862
- ) {
863
- this.logger('info', 'connection:scheduleConnectionCheck - going to reconnect', {
864
- tags: ['connection'],
865
- });
615
+ if (this.lastEvent && now.getTime() - this.lastEvent.getTime() > this.connectionCheckTimeout) {
616
+ this._log('scheduleConnectionCheck - going to reconnect');
866
617
  this._setHealth(false);
867
618
  this._reconnect();
868
619
  }