genesys-cloud-streaming-client 17.1.2-develop.96 → 17.1.2-develop.97

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.
@@ -16,6 +16,7 @@ export declare class Client extends EventEmitter {
16
16
  connecting: boolean;
17
17
  hardReconnectRequired: boolean;
18
18
  reconnectOnNoLongerSubscribed: boolean;
19
+ useServerSidePings: boolean;
19
20
  logger: Logger;
20
21
  config: IClientConfig;
21
22
  isGuest: boolean;
@@ -55,6 +56,7 @@ export declare class Client extends EventEmitter {
55
56
  connect(connectOpts?: StreamingClientConnectOptions): Promise<void>;
56
57
  private backoffConnectRetryHandler;
57
58
  private makeConnectionAttempt;
59
+ private setupConnectionMonitoring;
58
60
  private prepareForConnect;
59
61
  stopServerLogging(): void;
60
62
  startServerLogging(): void;
@@ -8,6 +8,7 @@ require("./polyfills");
8
8
  const notifications_1 = require("./notifications");
9
9
  const webrtc_1 = require("./webrtc");
10
10
  const ping_1 = require("./ping");
11
+ const server_monitor_1 = require("./server-monitor");
11
12
  const utils_1 = require("./utils");
12
13
  const http_client_1 = require("./http-client");
13
14
  const events_1 = tslib_1.__importDefault(require("events"));
@@ -43,6 +44,7 @@ class Client extends events_1.default {
43
44
  this.hasMadeInitialAttempt = false;
44
45
  this.http = new http_client_1.HttpClient();
45
46
  this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
47
+ this.useServerSidePings = options.useServerSidePings !== false;
46
48
  this.config = {
47
49
  host: options.host,
48
50
  apiHost: options.apiHost || options.host.replace('wss://streaming.', ''),
@@ -175,11 +177,12 @@ class Client extends events_1.default {
175
177
  };
176
178
  }
177
179
  async handleStanzaDisconnectedEvent(disconnectedInstance) {
178
- var _a;
180
+ var _a, _b;
179
181
  this.logger.info('stanzaDisconnected event received', { stanzaInstanceId: disconnectedInstance.id, channelId: disconnectedInstance.channelId });
180
182
  this.connected = false;
181
183
  this.connecting = false;
182
184
  (_a = disconnectedInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
185
+ (_b = disconnectedInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
183
186
  this.removeAllListeners(STANZA_DISCONNECTED);
184
187
  this.removeAllListeners(NO_LONGER_SUBSCRIBED);
185
188
  // unproxy events
@@ -193,18 +196,20 @@ class Client extends events_1.default {
193
196
  }
194
197
  }
195
198
  handleNoLongerSubscribed(stanzaInstance) {
196
- var _a;
199
+ var _a, _b;
197
200
  this.logger.warn('noLongerSubscribed event received', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
198
201
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
202
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
199
203
  this.hardReconnectRequired = true;
200
204
  if (!this.reconnectOnNoLongerSubscribed) {
201
205
  this.autoReconnect = false;
202
206
  }
203
207
  }
204
208
  handleDuplicateId(stanzaInstance) {
205
- var _a;
209
+ var _a, _b;
206
210
  this.logger.warn('duplicate_id event received, forcing hard reconnect', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
207
211
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
212
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
208
213
  this.hardReconnectRequired = true;
209
214
  }
210
215
  async disconnect() {
@@ -407,7 +412,7 @@ class Client extends events_1.default {
407
412
  return true;
408
413
  }
409
414
  async makeConnectionAttempt() {
410
- var _a;
415
+ var _a, _b;
411
416
  if (!navigator.onLine) {
412
417
  throw new offline_error_1.default('Browser is offline, skipping connection attempt');
413
418
  }
@@ -430,7 +435,7 @@ class Client extends events_1.default {
430
435
  extension.handleStanzaInstanceChange(stanzaInstance);
431
436
  }
432
437
  this.activeStanzaInstance = stanzaInstance;
433
- stanzaInstance.pinger = new ping_1.Ping(this, stanzaInstance);
438
+ await this.setupConnectionMonitoring(stanzaInstance);
434
439
  this.emit('connected');
435
440
  }
436
441
  catch (err) {
@@ -438,6 +443,7 @@ class Client extends events_1.default {
438
443
  this.logger.error('Error occurred in connection attempt, but after websocket connected. Cleaning up connection so backoff is respected', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
439
444
  this.removeStanzaBoundEventHandlers();
440
445
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
446
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
441
447
  await stanzaInstance.disconnect();
442
448
  this.connected = false;
443
449
  this.connecting = previousConnectingState;
@@ -445,6 +451,26 @@ class Client extends events_1.default {
445
451
  throw err;
446
452
  }
447
453
  }
454
+ async setupConnectionMonitoring(stanzaInstance) {
455
+ const setupClientPinger = (message) => {
456
+ const logMessage = `$(message), falling back to client-side pinging`;
457
+ this.logger.warn(logMessage, { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
458
+ stanzaInstance.pinger = new ping_1.Ping(this, stanzaInstance);
459
+ };
460
+ if (this.useServerSidePings) {
461
+ try {
462
+ // if this fails, then hawk doesn't support serverside pinging and we need to do client side pings
463
+ await stanzaInstance.subscribeToNode(this._notifications.pubsubHost, 'enable.server.side.pings');
464
+ stanzaInstance.serverMonitor = new server_monitor_1.ServerMonitor(this, stanzaInstance);
465
+ }
466
+ catch (err) {
467
+ setupClientPinger('failed to establish server-side pinging');
468
+ }
469
+ }
470
+ else {
471
+ setupClientPinger('client configured to not use server-side pinging');
472
+ }
473
+ }
448
474
  async prepareForConnect() {
449
475
  if (this.config.jwt) {
450
476
  this.hardReconnectRequired = false;
@@ -0,0 +1,16 @@
1
+ import { Client } from './client';
2
+ import { NamedAgent } from './types/named-agent';
3
+ export interface ServerMonitorOptions {
4
+ stanzaTimeout?: number;
5
+ }
6
+ export declare class ServerMonitor {
7
+ private client;
8
+ private stanzaInstance;
9
+ private stanzaTimeout;
10
+ private timeoutId?;
11
+ private boundSetupStanzaTimeout?;
12
+ constructor(client: Client, stanzaInstance: NamedAgent, options?: ServerMonitorOptions);
13
+ private start;
14
+ stop(): void;
15
+ private setupStanzaTimeout;
16
+ }
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ServerMonitor = void 0;
4
+ const DEFAULT_STANZA_TIMEOUT = 70 * 1000;
5
+ class ServerMonitor {
6
+ constructor(client, stanzaInstance, options = {}) {
7
+ this.client = client;
8
+ this.stanzaInstance = stanzaInstance;
9
+ this.stanzaTimeout = options.stanzaTimeout || DEFAULT_STANZA_TIMEOUT;
10
+ this.timeoutId = undefined;
11
+ this.start();
12
+ }
13
+ start() {
14
+ this.boundSetupStanzaTimeout = this.setupStanzaTimeout.bind(this);
15
+ this.client.on('connected', this.boundSetupStanzaTimeout);
16
+ this.stanzaInstance.on('raw:incoming', this.boundSetupStanzaTimeout);
17
+ }
18
+ stop() {
19
+ clearTimeout(this.timeoutId);
20
+ this.timeoutId = undefined;
21
+ if (this.boundSetupStanzaTimeout) {
22
+ this.client.off('connected', this.boundSetupStanzaTimeout);
23
+ this.stanzaInstance.off('raw:incoming', this.boundSetupStanzaTimeout);
24
+ this.boundSetupStanzaTimeout = undefined;
25
+ }
26
+ }
27
+ setupStanzaTimeout() {
28
+ clearTimeout(this.timeoutId);
29
+ this.timeoutId = setTimeout(() => {
30
+ const info = {
31
+ channelId: this.client.config.channelId,
32
+ jid: this.stanzaInstance.jid,
33
+ stanzaInstanceId: this.stanzaInstance.id,
34
+ timeout: this.stanzaTimeout
35
+ };
36
+ this.client.logger.error('Time between XMPP stanzas exceeded timeout, disconnecting', info);
37
+ this.stanzaInstance.sendStreamError({ text: 'time between stanzas exceeded timeout', condition: 'connection-timeout' });
38
+ this.stop();
39
+ }, this.stanzaTimeout);
40
+ }
41
+ }
42
+ exports.ServerMonitor = ServerMonitor;
@@ -17,6 +17,7 @@ export interface IClientOptions {
17
17
  logLevel?: LogLevel;
18
18
  logFormatters?: LogFormatterFn[];
19
19
  signalIceConnected?: boolean;
20
+ useServerSidePings?: boolean;
20
21
  appName?: string;
21
22
  appVersion?: string;
22
23
  appId?: string;
@@ -1,9 +1,11 @@
1
1
  import { Agent } from 'stanza';
2
2
  import { Ping } from '../ping';
3
+ import { ServerMonitor } from '../server-monitor';
3
4
  export interface NamedAgent extends Omit<Agent, 'disconnect'> {
4
5
  id: string;
5
6
  channelId?: string;
6
7
  originalEmitter?: Function;
7
8
  pinger?: Ping;
9
+ serverMonitor?: ServerMonitor;
8
10
  disconnect: () => Promise<void>;
9
11
  }
@@ -17,6 +17,6 @@
17
17
  "file": "v17/streaming-client.browser.js"
18
18
  }
19
19
  ],
20
- "build": "96",
21
- "buildDate": "2024-04-22T20:44:52.500279Z"
20
+ "build": "97",
21
+ "buildDate": "2024-05-02T22:19:19.442066Z"
22
22
  }
@@ -16,6 +16,7 @@ export declare class Client extends EventEmitter {
16
16
  connecting: boolean;
17
17
  hardReconnectRequired: boolean;
18
18
  reconnectOnNoLongerSubscribed: boolean;
19
+ useServerSidePings: boolean;
19
20
  logger: Logger;
20
21
  config: IClientConfig;
21
22
  isGuest: boolean;
@@ -55,6 +56,7 @@ export declare class Client extends EventEmitter {
55
56
  connect(connectOpts?: StreamingClientConnectOptions): Promise<void>;
56
57
  private backoffConnectRetryHandler;
57
58
  private makeConnectionAttempt;
59
+ private setupConnectionMonitoring;
58
60
  private prepareForConnect;
59
61
  stopServerLogging(): void;
60
62
  startServerLogging(): void;
package/dist/es/client.js CHANGED
@@ -6,6 +6,7 @@ import './polyfills';
6
6
  import { Notifications } from './notifications';
7
7
  import { WebrtcExtension } from './webrtc';
8
8
  import { Ping } from './ping';
9
+ import { ServerMonitor } from './server-monitor';
9
10
  import { delay, parseJwt, timeoutPromise } from './utils';
10
11
  import { HttpClient } from './http-client';
11
12
  import EventEmitter from 'events';
@@ -41,6 +42,7 @@ export class Client extends EventEmitter {
41
42
  this.hasMadeInitialAttempt = false;
42
43
  this.http = new HttpClient();
43
44
  this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
45
+ this.useServerSidePings = options.useServerSidePings !== false;
44
46
  this.config = {
45
47
  host: options.host,
46
48
  apiHost: options.apiHost || options.host.replace('wss://streaming.', ''),
@@ -173,12 +175,13 @@ export class Client extends EventEmitter {
173
175
  };
174
176
  }
175
177
  handleStanzaDisconnectedEvent(disconnectedInstance) {
176
- var _a;
178
+ var _a, _b;
177
179
  return __awaiter(this, void 0, void 0, function* () {
178
180
  this.logger.info('stanzaDisconnected event received', { stanzaInstanceId: disconnectedInstance.id, channelId: disconnectedInstance.channelId });
179
181
  this.connected = false;
180
182
  this.connecting = false;
181
183
  (_a = disconnectedInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
184
+ (_b = disconnectedInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
182
185
  this.removeAllListeners(STANZA_DISCONNECTED);
183
186
  this.removeAllListeners(NO_LONGER_SUBSCRIBED);
184
187
  // unproxy events
@@ -193,18 +196,20 @@ export class Client extends EventEmitter {
193
196
  });
194
197
  }
195
198
  handleNoLongerSubscribed(stanzaInstance) {
196
- var _a;
199
+ var _a, _b;
197
200
  this.logger.warn('noLongerSubscribed event received', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
198
201
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
202
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
199
203
  this.hardReconnectRequired = true;
200
204
  if (!this.reconnectOnNoLongerSubscribed) {
201
205
  this.autoReconnect = false;
202
206
  }
203
207
  }
204
208
  handleDuplicateId(stanzaInstance) {
205
- var _a;
209
+ var _a, _b;
206
210
  this.logger.warn('duplicate_id event received, forcing hard reconnect', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
207
211
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
212
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
208
213
  this.hardReconnectRequired = true;
209
214
  }
210
215
  disconnect() {
@@ -413,7 +418,7 @@ export class Client extends EventEmitter {
413
418
  });
414
419
  }
415
420
  makeConnectionAttempt() {
416
- var _a;
421
+ var _a, _b;
417
422
  return __awaiter(this, void 0, void 0, function* () {
418
423
  if (!navigator.onLine) {
419
424
  throw new OfflineError('Browser is offline, skipping connection attempt');
@@ -437,7 +442,7 @@ export class Client extends EventEmitter {
437
442
  extension.handleStanzaInstanceChange(stanzaInstance);
438
443
  }
439
444
  this.activeStanzaInstance = stanzaInstance;
440
- stanzaInstance.pinger = new Ping(this, stanzaInstance);
445
+ yield this.setupConnectionMonitoring(stanzaInstance);
441
446
  this.emit('connected');
442
447
  }
443
448
  catch (err) {
@@ -445,6 +450,7 @@ export class Client extends EventEmitter {
445
450
  this.logger.error('Error occurred in connection attempt, but after websocket connected. Cleaning up connection so backoff is respected', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
446
451
  this.removeStanzaBoundEventHandlers();
447
452
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
453
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
448
454
  yield stanzaInstance.disconnect();
449
455
  this.connected = false;
450
456
  this.connecting = previousConnectingState;
@@ -453,6 +459,28 @@ export class Client extends EventEmitter {
453
459
  }
454
460
  });
455
461
  }
462
+ setupConnectionMonitoring(stanzaInstance) {
463
+ return __awaiter(this, void 0, void 0, function* () {
464
+ const setupClientPinger = (message) => {
465
+ const logMessage = `$(message), falling back to client-side pinging`;
466
+ this.logger.warn(logMessage, { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
467
+ stanzaInstance.pinger = new Ping(this, stanzaInstance);
468
+ };
469
+ if (this.useServerSidePings) {
470
+ try {
471
+ // if this fails, then hawk doesn't support serverside pinging and we need to do client side pings
472
+ yield stanzaInstance.subscribeToNode(this._notifications.pubsubHost, 'enable.server.side.pings');
473
+ stanzaInstance.serverMonitor = new ServerMonitor(this, stanzaInstance);
474
+ }
475
+ catch (err) {
476
+ setupClientPinger('failed to establish server-side pinging');
477
+ }
478
+ }
479
+ else {
480
+ setupClientPinger('client configured to not use server-side pinging');
481
+ }
482
+ });
483
+ }
456
484
  prepareForConnect() {
457
485
  return __awaiter(this, void 0, void 0, function* () {
458
486
  if (this.config.jwt) {
@@ -31444,6 +31444,45 @@ class Ping {
31444
31444
  }
31445
31445
  }
31446
31446
 
31447
+ const DEFAULT_STANZA_TIMEOUT = 70 * 1000;
31448
+ class ServerMonitor {
31449
+ constructor(client, stanzaInstance, options = {}) {
31450
+ this.client = client;
31451
+ this.stanzaInstance = stanzaInstance;
31452
+ this.stanzaTimeout = options.stanzaTimeout || DEFAULT_STANZA_TIMEOUT;
31453
+ this.timeoutId = undefined;
31454
+ this.start();
31455
+ }
31456
+ start() {
31457
+ this.boundSetupStanzaTimeout = this.setupStanzaTimeout.bind(this);
31458
+ this.client.on('connected', this.boundSetupStanzaTimeout);
31459
+ this.stanzaInstance.on('raw:incoming', this.boundSetupStanzaTimeout);
31460
+ }
31461
+ stop() {
31462
+ clearTimeout(this.timeoutId);
31463
+ this.timeoutId = undefined;
31464
+ if (this.boundSetupStanzaTimeout) {
31465
+ this.client.off('connected', this.boundSetupStanzaTimeout);
31466
+ this.stanzaInstance.off('raw:incoming', this.boundSetupStanzaTimeout);
31467
+ this.boundSetupStanzaTimeout = undefined;
31468
+ }
31469
+ }
31470
+ setupStanzaTimeout() {
31471
+ clearTimeout(this.timeoutId);
31472
+ this.timeoutId = setTimeout(() => {
31473
+ const info = {
31474
+ channelId: this.client.config.channelId,
31475
+ jid: this.stanzaInstance.jid,
31476
+ stanzaInstanceId: this.stanzaInstance.id,
31477
+ timeout: this.stanzaTimeout
31478
+ };
31479
+ this.client.logger.error('Time between XMPP stanzas exceeded timeout, disconnecting', info);
31480
+ this.stanzaInstance.sendStreamError({ text: 'time between stanzas exceeded timeout', condition: 'connection-timeout' });
31481
+ this.stop();
31482
+ }, this.stanzaTimeout);
31483
+ }
31484
+ }
31485
+
31447
31486
  var stanza = {};
31448
31487
 
31449
31488
  var Client$2 = {};
@@ -41444,6 +41483,7 @@ class Client extends EventEmitter {
41444
41483
  this.hasMadeInitialAttempt = false;
41445
41484
  this.http = new HttpClient();
41446
41485
  this.reconnectOnNoLongerSubscribed = options.reconnectOnNoLongerSubscribed !== false;
41486
+ this.useServerSidePings = options.useServerSidePings !== false;
41447
41487
  this.config = {
41448
41488
  host: options.host,
41449
41489
  apiHost: options.apiHost || options.host.replace('wss://streaming.', ''),
@@ -41576,12 +41616,13 @@ class Client extends EventEmitter {
41576
41616
  };
41577
41617
  }
41578
41618
  handleStanzaDisconnectedEvent(disconnectedInstance) {
41579
- var _a;
41619
+ var _a, _b;
41580
41620
  return __awaiter$5(this, void 0, void 0, function* () {
41581
41621
  this.logger.info('stanzaDisconnected event received', { stanzaInstanceId: disconnectedInstance.id, channelId: disconnectedInstance.channelId });
41582
41622
  this.connected = false;
41583
41623
  this.connecting = false;
41584
41624
  (_a = disconnectedInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
41625
+ (_b = disconnectedInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
41585
41626
  this.removeAllListeners(STANZA_DISCONNECTED);
41586
41627
  this.removeAllListeners(NO_LONGER_SUBSCRIBED);
41587
41628
  // unproxy events
@@ -41596,18 +41637,20 @@ class Client extends EventEmitter {
41596
41637
  });
41597
41638
  }
41598
41639
  handleNoLongerSubscribed(stanzaInstance) {
41599
- var _a;
41640
+ var _a, _b;
41600
41641
  this.logger.warn('noLongerSubscribed event received', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
41601
41642
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
41643
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
41602
41644
  this.hardReconnectRequired = true;
41603
41645
  if (!this.reconnectOnNoLongerSubscribed) {
41604
41646
  this.autoReconnect = false;
41605
41647
  }
41606
41648
  }
41607
41649
  handleDuplicateId(stanzaInstance) {
41608
- var _a;
41650
+ var _a, _b;
41609
41651
  this.logger.warn('duplicate_id event received, forcing hard reconnect', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
41610
41652
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
41653
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
41611
41654
  this.hardReconnectRequired = true;
41612
41655
  }
41613
41656
  disconnect() {
@@ -41816,7 +41859,7 @@ class Client extends EventEmitter {
41816
41859
  });
41817
41860
  }
41818
41861
  makeConnectionAttempt() {
41819
- var _a;
41862
+ var _a, _b;
41820
41863
  return __awaiter$5(this, void 0, void 0, function* () {
41821
41864
  if (!navigator.onLine) {
41822
41865
  throw new OfflineError('Browser is offline, skipping connection attempt');
@@ -41840,7 +41883,7 @@ class Client extends EventEmitter {
41840
41883
  extension.handleStanzaInstanceChange(stanzaInstance);
41841
41884
  }
41842
41885
  this.activeStanzaInstance = stanzaInstance;
41843
- stanzaInstance.pinger = new Ping(this, stanzaInstance);
41886
+ yield this.setupConnectionMonitoring(stanzaInstance);
41844
41887
  this.emit('connected');
41845
41888
  }
41846
41889
  catch (err) {
@@ -41848,6 +41891,7 @@ class Client extends EventEmitter {
41848
41891
  this.logger.error('Error occurred in connection attempt, but after websocket connected. Cleaning up connection so backoff is respected', { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
41849
41892
  this.removeStanzaBoundEventHandlers();
41850
41893
  (_a = stanzaInstance.pinger) === null || _a === void 0 ? void 0 : _a.stop();
41894
+ (_b = stanzaInstance.serverMonitor) === null || _b === void 0 ? void 0 : _b.stop();
41851
41895
  yield stanzaInstance.disconnect();
41852
41896
  this.connected = false;
41853
41897
  this.connecting = previousConnectingState;
@@ -41856,6 +41900,28 @@ class Client extends EventEmitter {
41856
41900
  }
41857
41901
  });
41858
41902
  }
41903
+ setupConnectionMonitoring(stanzaInstance) {
41904
+ return __awaiter$5(this, void 0, void 0, function* () {
41905
+ const setupClientPinger = (message) => {
41906
+ const logMessage = `$(message), falling back to client-side pinging`;
41907
+ this.logger.warn(logMessage, { stanzaInstanceId: stanzaInstance.id, channelId: stanzaInstance.channelId });
41908
+ stanzaInstance.pinger = new Ping(this, stanzaInstance);
41909
+ };
41910
+ if (this.useServerSidePings) {
41911
+ try {
41912
+ // if this fails, then hawk doesn't support serverside pinging and we need to do client side pings
41913
+ yield stanzaInstance.subscribeToNode(this._notifications.pubsubHost, 'enable.server.side.pings');
41914
+ stanzaInstance.serverMonitor = new ServerMonitor(this, stanzaInstance);
41915
+ }
41916
+ catch (err) {
41917
+ setupClientPinger();
41918
+ }
41919
+ }
41920
+ else {
41921
+ setupClientPinger();
41922
+ }
41923
+ });
41924
+ }
41859
41925
  prepareForConnect() {
41860
41926
  return __awaiter$5(this, void 0, void 0, function* () {
41861
41927
  if (this.config.jwt) {
@@ -0,0 +1,16 @@
1
+ import { Client } from './client';
2
+ import { NamedAgent } from './types/named-agent';
3
+ export interface ServerMonitorOptions {
4
+ stanzaTimeout?: number;
5
+ }
6
+ export declare class ServerMonitor {
7
+ private client;
8
+ private stanzaInstance;
9
+ private stanzaTimeout;
10
+ private timeoutId?;
11
+ private boundSetupStanzaTimeout?;
12
+ constructor(client: Client, stanzaInstance: NamedAgent, options?: ServerMonitorOptions);
13
+ private start;
14
+ stop(): void;
15
+ private setupStanzaTimeout;
16
+ }
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+ const DEFAULT_STANZA_TIMEOUT = 70 * 1000;
3
+ export class ServerMonitor {
4
+ constructor(client, stanzaInstance, options = {}) {
5
+ this.client = client;
6
+ this.stanzaInstance = stanzaInstance;
7
+ this.stanzaTimeout = options.stanzaTimeout || DEFAULT_STANZA_TIMEOUT;
8
+ this.timeoutId = undefined;
9
+ this.start();
10
+ }
11
+ start() {
12
+ this.boundSetupStanzaTimeout = this.setupStanzaTimeout.bind(this);
13
+ this.client.on('connected', this.boundSetupStanzaTimeout);
14
+ this.stanzaInstance.on('raw:incoming', this.boundSetupStanzaTimeout);
15
+ }
16
+ stop() {
17
+ clearTimeout(this.timeoutId);
18
+ this.timeoutId = undefined;
19
+ if (this.boundSetupStanzaTimeout) {
20
+ this.client.off('connected', this.boundSetupStanzaTimeout);
21
+ this.stanzaInstance.off('raw:incoming', this.boundSetupStanzaTimeout);
22
+ this.boundSetupStanzaTimeout = undefined;
23
+ }
24
+ }
25
+ setupStanzaTimeout() {
26
+ clearTimeout(this.timeoutId);
27
+ this.timeoutId = setTimeout(() => {
28
+ const info = {
29
+ channelId: this.client.config.channelId,
30
+ jid: this.stanzaInstance.jid,
31
+ stanzaInstanceId: this.stanzaInstance.id,
32
+ timeout: this.stanzaTimeout
33
+ };
34
+ this.client.logger.error('Time between XMPP stanzas exceeded timeout, disconnecting', info);
35
+ this.stanzaInstance.sendStreamError({ text: 'time between stanzas exceeded timeout', condition: 'connection-timeout' });
36
+ this.stop();
37
+ }, this.stanzaTimeout);
38
+ }
39
+ }
@@ -17,6 +17,7 @@ export interface IClientOptions {
17
17
  logLevel?: LogLevel;
18
18
  logFormatters?: LogFormatterFn[];
19
19
  signalIceConnected?: boolean;
20
+ useServerSidePings?: boolean;
20
21
  appName?: string;
21
22
  appVersion?: string;
22
23
  appId?: string;
@@ -1,9 +1,11 @@
1
1
  import { Agent } from 'stanza';
2
2
  import { Ping } from '../ping';
3
+ import { ServerMonitor } from '../server-monitor';
3
4
  export interface NamedAgent extends Omit<Agent, 'disconnect'> {
4
5
  id: string;
5
6
  channelId?: string;
6
7
  originalEmitter?: Function;
7
8
  pinger?: Ping;
9
+ serverMonitor?: ServerMonitor;
8
10
  disconnect: () => Promise<void>;
9
11
  }
@@ -5,6 +5,8 @@ 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.1.2...HEAD)
8
+ ### Changed
9
+ * [PCM-2352](https://inindca.atlassian.net/browse/PCM-2352) - Use a timeout when using server-side pinging to detect when a connection has dropped
8
10
 
9
11
  # [v17.1.2](https://github.com/purecloudlabs/genesys-cloud-streaming-client/compare/v17.1.1...v17.1.2)
10
12
  * [NO-JIRA] - Removed Spigot testing from Jenkins build step.
@@ -16,6 +16,7 @@ export declare class Client extends EventEmitter {
16
16
  connecting: boolean;
17
17
  hardReconnectRequired: boolean;
18
18
  reconnectOnNoLongerSubscribed: boolean;
19
+ useServerSidePings: boolean;
19
20
  logger: Logger;
20
21
  config: IClientConfig;
21
22
  isGuest: boolean;
@@ -55,6 +56,7 @@ export declare class Client extends EventEmitter {
55
56
  connect(connectOpts?: StreamingClientConnectOptions): Promise<void>;
56
57
  private backoffConnectRetryHandler;
57
58
  private makeConnectionAttempt;
59
+ private setupConnectionMonitoring;
58
60
  private prepareForConnect;
59
61
  stopServerLogging(): void;
60
62
  startServerLogging(): void;