genesys-cloud-streaming-client 17.0.2-develop.83 → 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.
@@ -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/es/utils.js CHANGED
@@ -16,6 +16,11 @@ export function timeoutPromise(fn, timeoutMs, msg, details) {
16
16
  fn(done, reject);
17
17
  });
18
18
  }
19
+ export function delay(ms) {
20
+ return new Promise((resolve) => {
21
+ setTimeout(resolve, ms);
22
+ });
23
+ }
19
24
  export function splitIntoIndividualTopics(topicString) {
20
25
  const topics = [];
21
26
  if (topicString.includes('?')) {
@@ -34,10 +39,10 @@ export function splitIntoIndividualTopics(topicString) {
34
39
  return topics;
35
40
  }
36
41
  export const isAcdJid = function (jid) {
37
- return jid.startsWith('acd-');
42
+ return jid.startsWith('acd-') && !isSoftphoneJid(jid);
38
43
  };
39
44
  export const isScreenRecordingJid = function (jid) {
40
- return jid.startsWith('screenrecording-');
45
+ return jid.startsWith('screenrecording-') && !isSoftphoneJid(jid);
41
46
  };
42
47
  export const isSoftphoneJid = function (jid) {
43
48
  if (!jid) {
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  # [Unreleased](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v17.0.1...HEAD)
8
+ ### Changed
9
+ * [PCM-2312](https://inindca.atlassian.net/browse/PCM-2312) bump logger version
10
+
11
+ ### Fixed
12
+ * [PCM-2304](https://inindca.atlassian.net/browse/PCM-2304) Made connection backoff semi-persistent through the sessionStore. Updated stanza to circumvent browser intensive throttling on connection attempts.
13
+ * [PCM-2314](https://inindca.atlassian.net/browse/PCM-2314) Fix softphone calls with users' emails beginning with "acd-"
8
14
 
9
15
  # [v17.0.1](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v17.0.0...v17.0.1)
10
16
  ### Added
@@ -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/npm/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) {