genesys-cloud-streaming-client 17.0.2-develop.85 → 17.0.2-develop.88

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.
@@ -24,6 +24,8 @@ export declare class Client extends EventEmitter {
24
24
  private extensions;
25
25
  private connectionManager;
26
26
  private channelReuses;
27
+ private backoffReductionTimer;
28
+ private hasMadeInitialAttempt;
27
29
  private boundStanzaDisconnect?;
28
30
  private boundStanzaNoLongerSubscribed?;
29
31
  private boundStanzaDuplicateId?;
@@ -45,6 +47,11 @@ export declare class Client extends EventEmitter {
45
47
  private handleNoLongerSubscribed;
46
48
  private handleDuplicateId;
47
49
  disconnect(): Promise<any>;
50
+ private getConnectionData;
51
+ private setConnectionData;
52
+ private increaseBackoff;
53
+ private decreaseBackoff;
54
+ private getStartingDelay;
48
55
  connect(connectOpts?: StreamingClientConnectOptions): Promise<void>;
49
56
  private backoffConnectRetryHandler;
50
57
  private makeConnectionAttempt;
@@ -26,6 +26,9 @@ const STANZA_DISCONNECTED = 'stanzaDisconnected';
26
26
  const NO_LONGER_SUBSCRIBED = 'notify:no_longer_subscribed';
27
27
  const DUPLICATE_ID = 'notify:duplicate_id';
28
28
  const MAX_CHANNEL_REUSES = 10;
29
+ const SESSION_STORE_KEY = 'sc_connectionData';
30
+ const BACKOFF_DECREASE_DELAY_MULTIPLIER = 5;
31
+ const INITIAL_DELAY = 2000;
29
32
  class Client extends events_1.default {
30
33
  constructor(options) {
31
34
  super();
@@ -37,6 +40,7 @@ class Client extends events_1.default {
37
40
  this.autoReconnect = true;
38
41
  this.extensions = [];
39
42
  this.channelReuses = 0;
43
+ this.hasMadeInitialAttempt = false;
40
44
  this.http = new http_client_1.HttpClient();
41
45
  this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
42
46
  this.config = {
@@ -213,6 +217,64 @@ class Client extends events_1.default {
213
217
  .then(resolve);
214
218
  }, 5000, 'disconnecting streaming service');
215
219
  }
220
+ getConnectionData() {
221
+ const connectionDataStr = sessionStorage.getItem(SESSION_STORE_KEY);
222
+ const defaultValue = {
223
+ currentDelayMs: 0,
224
+ };
225
+ if (connectionDataStr) {
226
+ try {
227
+ return JSON.parse(connectionDataStr);
228
+ }
229
+ catch (e) {
230
+ this.logger.warn('failed to parse streaming client connection data');
231
+ return defaultValue;
232
+ }
233
+ }
234
+ return defaultValue;
235
+ }
236
+ setConnectionData(data) {
237
+ sessionStorage.setItem(SESSION_STORE_KEY, JSON.stringify(data));
238
+ }
239
+ increaseBackoff() {
240
+ const connectionData = this.getConnectionData();
241
+ const currentDelay = Math.max(connectionData.currentDelayMs * 2, INITIAL_DELAY);
242
+ this.setConnectionData({
243
+ currentDelayMs: currentDelay,
244
+ delayMsAfterNextReduction: currentDelay / 2,
245
+ nextDelayReductionTime: new Date().getTime() + (currentDelay * BACKOFF_DECREASE_DELAY_MULTIPLIER),
246
+ timeOfTotalReset: new Date().getTime() + 1000 * 60 * 60 // one hour in the future
247
+ });
248
+ }
249
+ decreaseBackoff(newAmountMs) {
250
+ const data = this.getConnectionData();
251
+ const msUntilNextReduction = newAmountMs * BACKOFF_DECREASE_DELAY_MULTIPLIER;
252
+ const newConnectionData = {
253
+ currentDelayMs: newAmountMs,
254
+ delayMsAfterNextReduction: newAmountMs / 2,
255
+ nextDelayReductionTime: new Date().getTime() + (msUntilNextReduction),
256
+ timeOfTotalReset: data.timeOfTotalReset
257
+ };
258
+ // if we are past the total reset time, do that instead
259
+ if (data.timeOfTotalReset && data.timeOfTotalReset < new Date().getTime() || newAmountMs < INITIAL_DELAY) {
260
+ this.logger.debug('decreaseBackoff() called, but timeOfTotalReset has elasped or next delay is below 2s. Resetting backoff');
261
+ return this.setConnectionData({
262
+ currentDelayMs: 0
263
+ });
264
+ }
265
+ this.setConnectionData(newConnectionData);
266
+ clearTimeout(this.backoffReductionTimer);
267
+ this.logger.debug('Setting timer for next backoff reduction since we haven\'t reached total reset', { msUntilReduction: msUntilNextReduction, delayMsAfterNextReduction: newConnectionData.delayMsAfterNextReduction });
268
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(newConnectionData.delayMsAfterNextReduction), msUntilNextReduction);
269
+ }
270
+ getStartingDelay(connectionData, maxDelay) {
271
+ // we don't want the delay to ever be less than 2 seconds
272
+ const minDelay = Math.max(connectionData.currentDelayMs, INITIAL_DELAY);
273
+ if (connectionData.timeOfTotalReset && connectionData.timeOfTotalReset < new Date().getTime()) {
274
+ return INITIAL_DELAY;
275
+ }
276
+ return Math.min(minDelay, maxDelay);
277
+ }
216
278
  async connect(connectOpts) {
217
279
  var _a;
218
280
  if (this.connecting) {
@@ -227,13 +289,29 @@ class Client extends events_1.default {
227
289
  // this maintains the previous functionality
228
290
  maxAttempts = Infinity;
229
291
  }
292
+ clearTimeout(this.backoffReductionTimer);
293
+ const connectionData = this.getConnectionData();
294
+ const startingDelay = this.getStartingDelay(connectionData, maxDelay);
295
+ const delayFirstAttempt = this.hasMadeInitialAttempt;
296
+ this.hasMadeInitialAttempt = true;
230
297
  try {
231
- await exponential_backoff_1.backOff(() => this.makeConnectionAttempt(), {
232
- jitter: 'full',
298
+ await exponential_backoff_1.backOff(async () => {
299
+ const connectionData = this.getConnectionData();
300
+ await this.makeConnectionAttempt();
301
+ if (connectionData.nextDelayReductionTime) {
302
+ const msUntilReduction = connectionData.nextDelayReductionTime - new Date().getTime();
303
+ this.logger.debug('Setting timer for next backoff reduction', { msUntilReduction, delayMsAfterNextReduction: connectionData.delayMsAfterNextReduction });
304
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(connectionData.delayMsAfterNextReduction || 0), msUntilReduction);
305
+ }
306
+ }, {
307
+ jitter: 'none',
233
308
  maxDelay,
234
309
  numOfAttempts: maxAttempts,
235
- startingDelay: 2000,
236
- retry: this.backoffConnectRetryHandler.bind(this, { maxConnectionAttempts: maxAttempts })
310
+ startingDelay,
311
+ delayFirstAttempt,
312
+ retry: this.backoffConnectRetryHandler.bind(this, {
313
+ maxConnectionAttempts: maxAttempts,
314
+ }),
237
315
  });
238
316
  }
239
317
  catch (err) {
@@ -317,14 +395,13 @@ class Client extends events_1.default {
317
395
  let retryDelay = parseInt(retryAfter, 10) * 1000;
318
396
  additionalErrorDetails.retryDelay = retryDelay;
319
397
  this.logger.error('Failed streaming client connection attempt, respecting retry-after header and will retry afterwards.', additionalErrorDetails, { skipServer: err instanceof offline_error_1.default });
320
- await new Promise((resolve) => {
321
- setTimeout(resolve, retryDelay);
322
- });
398
+ await utils_1.delay(retryDelay);
323
399
  this.logger.debug('finished waiting for retry-after');
324
400
  return true;
325
401
  }
326
402
  }
327
403
  this.logger.error('Failed streaming client connection attempt, retrying', additionalErrorDetails, { skipServer: err instanceof offline_error_1.default });
404
+ this.increaseBackoff();
328
405
  return true;
329
406
  }
330
407
  async makeConnectionAttempt() {
@@ -373,7 +450,7 @@ class Client extends events_1.default {
373
450
  }
374
451
  if (!this.hardReconnectRequired) {
375
452
  this.channelReuses++;
376
- if (this.channelReuses > MAX_CHANNEL_REUSES) {
453
+ if (this.channelReuses >= MAX_CHANNEL_REUSES) {
377
454
  this.logger.warn('Forcing a hard reconnect due to max channel reuses', { channelId: this.config.channelId, channelReuses: this.channelReuses });
378
455
  this.channelReuses = 0;
379
456
  this.hardReconnectRequired = true;
@@ -247,3 +247,9 @@ export declare type MediaStat = InsightAction<{
247
247
  elapsedMsFromInitialRequest?: number;
248
248
  }>;
249
249
  export declare type NRProxyStat = FirstAlertingConversationStat | MediaStat;
250
+ export declare type SCConnectionData = {
251
+ currentDelayMs: number;
252
+ delayMsAfterNextReduction?: number;
253
+ nextDelayReductionTime?: number;
254
+ timeOfTotalReset?: number;
255
+ };
@@ -1,4 +1,5 @@
1
1
  export declare function timeoutPromise(fn: Function, timeoutMs: number, msg: string, details?: any): Promise<any>;
2
+ export declare function delay(ms: number): Promise<void>;
2
3
  export declare function splitIntoIndividualTopics(topicString: string): string[];
3
4
  export declare const isAcdJid: (jid: string) => boolean;
4
5
  export declare const isScreenRecordingJid: (jid: string) => boolean;
package/dist/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculatePayloadSize = exports.parseJwt = exports.retryPromise = exports.isVideoJid = exports.isSoftphoneJid = exports.isScreenRecordingJid = exports.isAcdJid = exports.splitIntoIndividualTopics = exports.timeoutPromise = void 0;
3
+ exports.calculatePayloadSize = exports.parseJwt = exports.retryPromise = exports.isVideoJid = exports.isSoftphoneJid = exports.isScreenRecordingJid = exports.isAcdJid = exports.splitIntoIndividualTopics = exports.delay = exports.timeoutPromise = void 0;
4
4
  const uuid_1 = require("uuid");
5
5
  const timeout_error_1 = require("./types/timeout-error");
6
6
  /* istanbul ignore next */
@@ -19,6 +19,12 @@ function timeoutPromise(fn, timeoutMs, msg, details) {
19
19
  });
20
20
  }
21
21
  exports.timeoutPromise = timeoutPromise;
22
+ function delay(ms) {
23
+ return new Promise((resolve) => {
24
+ setTimeout(resolve, ms);
25
+ });
26
+ }
27
+ exports.delay = delay;
22
28
  function splitIntoIndividualTopics(topicString) {
23
29
  const topics = [];
24
30
  if (topicString.includes('?')) {
@@ -38,11 +44,11 @@ function splitIntoIndividualTopics(topicString) {
38
44
  }
39
45
  exports.splitIntoIndividualTopics = splitIntoIndividualTopics;
40
46
  const isAcdJid = function (jid) {
41
- return jid.startsWith('acd-');
47
+ return jid.startsWith('acd-') && !exports.isSoftphoneJid(jid);
42
48
  };
43
49
  exports.isAcdJid = isAcdJid;
44
50
  const isScreenRecordingJid = function (jid) {
45
- return jid.startsWith('screenrecording-');
51
+ return jid.startsWith('screenrecording-') && !exports.isSoftphoneJid(jid);
46
52
  };
47
53
  exports.isScreenRecordingJid = isScreenRecordingJid;
48
54
  const isSoftphoneJid = function (jid) {
@@ -17,6 +17,6 @@
17
17
  "file": "v17/streaming-client.browser.js"
18
18
  }
19
19
  ],
20
- "build": "85",
21
- "buildDate": "2024-02-14T00:16:33.645243Z"
20
+ "build": "88",
21
+ "buildDate": "2024-02-16T18:31:18.700405Z"
22
22
  }
@@ -24,6 +24,8 @@ export declare class Client extends EventEmitter {
24
24
  private extensions;
25
25
  private connectionManager;
26
26
  private channelReuses;
27
+ private backoffReductionTimer;
28
+ private hasMadeInitialAttempt;
27
29
  private boundStanzaDisconnect?;
28
30
  private boundStanzaNoLongerSubscribed?;
29
31
  private boundStanzaDuplicateId?;
@@ -45,6 +47,11 @@ export declare class Client extends EventEmitter {
45
47
  private handleNoLongerSubscribed;
46
48
  private handleDuplicateId;
47
49
  disconnect(): Promise<any>;
50
+ private getConnectionData;
51
+ private setConnectionData;
52
+ private increaseBackoff;
53
+ private decreaseBackoff;
54
+ private getStartingDelay;
48
55
  connect(connectOpts?: StreamingClientConnectOptions): Promise<void>;
49
56
  private backoffConnectRetryHandler;
50
57
  private makeConnectionAttempt;
package/dist/es/client.js CHANGED
@@ -6,7 +6,7 @@ import './polyfills';
6
6
  import { Notifications } from './notifications';
7
7
  import { WebrtcExtension } from './webrtc';
8
8
  import { Ping } from './ping';
9
- import { parseJwt, timeoutPromise } from './utils';
9
+ import { delay, parseJwt, timeoutPromise } from './utils';
10
10
  import { HttpClient } from './http-client';
11
11
  import EventEmitter from 'events';
12
12
  import { ConnectionManager } from './connection-manager';
@@ -24,6 +24,9 @@ const STANZA_DISCONNECTED = 'stanzaDisconnected';
24
24
  const NO_LONGER_SUBSCRIBED = 'notify:no_longer_subscribed';
25
25
  const DUPLICATE_ID = 'notify:duplicate_id';
26
26
  const MAX_CHANNEL_REUSES = 10;
27
+ const SESSION_STORE_KEY = 'sc_connectionData';
28
+ const BACKOFF_DECREASE_DELAY_MULTIPLIER = 5;
29
+ const INITIAL_DELAY = 2000;
27
30
  export class Client extends EventEmitter {
28
31
  constructor(options) {
29
32
  super();
@@ -35,6 +38,7 @@ export class Client extends EventEmitter {
35
38
  this.autoReconnect = true;
36
39
  this.extensions = [];
37
40
  this.channelReuses = 0;
41
+ this.hasMadeInitialAttempt = false;
38
42
  this.http = new HttpClient();
39
43
  this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
40
44
  this.config = {
@@ -215,6 +219,64 @@ export class Client extends EventEmitter {
215
219
  }, 5000, 'disconnecting streaming service');
216
220
  });
217
221
  }
222
+ getConnectionData() {
223
+ const connectionDataStr = sessionStorage.getItem(SESSION_STORE_KEY);
224
+ const defaultValue = {
225
+ currentDelayMs: 0,
226
+ };
227
+ if (connectionDataStr) {
228
+ try {
229
+ return JSON.parse(connectionDataStr);
230
+ }
231
+ catch (e) {
232
+ this.logger.warn('failed to parse streaming client connection data');
233
+ return defaultValue;
234
+ }
235
+ }
236
+ return defaultValue;
237
+ }
238
+ setConnectionData(data) {
239
+ sessionStorage.setItem(SESSION_STORE_KEY, JSON.stringify(data));
240
+ }
241
+ increaseBackoff() {
242
+ const connectionData = this.getConnectionData();
243
+ const currentDelay = Math.max(connectionData.currentDelayMs * 2, INITIAL_DELAY);
244
+ this.setConnectionData({
245
+ currentDelayMs: currentDelay,
246
+ delayMsAfterNextReduction: currentDelay / 2,
247
+ nextDelayReductionTime: new Date().getTime() + (currentDelay * BACKOFF_DECREASE_DELAY_MULTIPLIER),
248
+ timeOfTotalReset: new Date().getTime() + 1000 * 60 * 60 // one hour in the future
249
+ });
250
+ }
251
+ decreaseBackoff(newAmountMs) {
252
+ const data = this.getConnectionData();
253
+ const msUntilNextReduction = newAmountMs * BACKOFF_DECREASE_DELAY_MULTIPLIER;
254
+ const newConnectionData = {
255
+ currentDelayMs: newAmountMs,
256
+ delayMsAfterNextReduction: newAmountMs / 2,
257
+ nextDelayReductionTime: new Date().getTime() + (msUntilNextReduction),
258
+ timeOfTotalReset: data.timeOfTotalReset
259
+ };
260
+ // if we are past the total reset time, do that instead
261
+ if (data.timeOfTotalReset && data.timeOfTotalReset < new Date().getTime() || newAmountMs < INITIAL_DELAY) {
262
+ this.logger.debug('decreaseBackoff() called, but timeOfTotalReset has elasped or next delay is below 2s. Resetting backoff');
263
+ return this.setConnectionData({
264
+ currentDelayMs: 0
265
+ });
266
+ }
267
+ this.setConnectionData(newConnectionData);
268
+ clearTimeout(this.backoffReductionTimer);
269
+ this.logger.debug('Setting timer for next backoff reduction since we haven\'t reached total reset', { msUntilReduction: msUntilNextReduction, delayMsAfterNextReduction: newConnectionData.delayMsAfterNextReduction });
270
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(newConnectionData.delayMsAfterNextReduction), msUntilNextReduction);
271
+ }
272
+ getStartingDelay(connectionData, maxDelay) {
273
+ // we don't want the delay to ever be less than 2 seconds
274
+ const minDelay = Math.max(connectionData.currentDelayMs, INITIAL_DELAY);
275
+ if (connectionData.timeOfTotalReset && connectionData.timeOfTotalReset < new Date().getTime()) {
276
+ return INITIAL_DELAY;
277
+ }
278
+ return Math.min(minDelay, maxDelay);
279
+ }
218
280
  connect(connectOpts) {
219
281
  var _a;
220
282
  return __awaiter(this, void 0, void 0, function* () {
@@ -230,13 +292,29 @@ export class Client extends EventEmitter {
230
292
  // this maintains the previous functionality
231
293
  maxAttempts = Infinity;
232
294
  }
295
+ clearTimeout(this.backoffReductionTimer);
296
+ const connectionData = this.getConnectionData();
297
+ const startingDelay = this.getStartingDelay(connectionData, maxDelay);
298
+ const delayFirstAttempt = this.hasMadeInitialAttempt;
299
+ this.hasMadeInitialAttempt = true;
233
300
  try {
234
- yield backOff(() => this.makeConnectionAttempt(), {
235
- jitter: 'full',
301
+ yield backOff(() => __awaiter(this, void 0, void 0, function* () {
302
+ const connectionData = this.getConnectionData();
303
+ yield this.makeConnectionAttempt();
304
+ if (connectionData.nextDelayReductionTime) {
305
+ const msUntilReduction = connectionData.nextDelayReductionTime - new Date().getTime();
306
+ this.logger.debug('Setting timer for next backoff reduction', { msUntilReduction, delayMsAfterNextReduction: connectionData.delayMsAfterNextReduction });
307
+ this.backoffReductionTimer = setTimeout(() => this.decreaseBackoff(connectionData.delayMsAfterNextReduction || 0), msUntilReduction);
308
+ }
309
+ }), {
310
+ jitter: 'none',
236
311
  maxDelay,
237
312
  numOfAttempts: maxAttempts,
238
- startingDelay: 2000,
239
- retry: this.backoffConnectRetryHandler.bind(this, { maxConnectionAttempts: maxAttempts })
313
+ startingDelay,
314
+ delayFirstAttempt,
315
+ retry: this.backoffConnectRetryHandler.bind(this, {
316
+ maxConnectionAttempts: maxAttempts,
317
+ }),
240
318
  });
241
319
  }
242
320
  catch (err) {
@@ -322,14 +400,13 @@ export class Client extends EventEmitter {
322
400
  let retryDelay = parseInt(retryAfter, 10) * 1000;
323
401
  additionalErrorDetails.retryDelay = retryDelay;
324
402
  this.logger.error('Failed streaming client connection attempt, respecting retry-after header and will retry afterwards.', additionalErrorDetails, { skipServer: err instanceof OfflineError });
325
- yield new Promise((resolve) => {
326
- setTimeout(resolve, retryDelay);
327
- });
403
+ yield delay(retryDelay);
328
404
  this.logger.debug('finished waiting for retry-after');
329
405
  return true;
330
406
  }
331
407
  }
332
408
  this.logger.error('Failed streaming client connection attempt, retrying', additionalErrorDetails, { skipServer: err instanceof OfflineError });
409
+ this.increaseBackoff();
333
410
  return true;
334
411
  });
335
412
  }
@@ -382,7 +459,7 @@ export class Client extends EventEmitter {
382
459
  }
383
460
  if (!this.hardReconnectRequired) {
384
461
  this.channelReuses++;
385
- if (this.channelReuses > MAX_CHANNEL_REUSES) {
462
+ if (this.channelReuses >= MAX_CHANNEL_REUSES) {
386
463
  this.logger.warn('Forcing a hard reconnect due to max channel reuses', { channelId: this.config.channelId, channelReuses: this.channelReuses });
387
464
  this.channelReuses = 0;
388
465
  this.hardReconnectRequired = true;