@webex/internal-plugin-mercury 3.10.0-next.3 → 3.10.0-wxc-disconnect.1

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.
package/src/mercury.js CHANGED
@@ -96,80 +96,6 @@ const Mercury = WebexPlugin.extend({
96
96
  });
97
97
  },
98
98
 
99
- /**
100
- * Attach event listeners to a socket.
101
- * @param {Socket} socket - The socket to attach listeners to
102
- * @returns {void}
103
- */
104
- _attachSocketEventListeners(socket) {
105
- socket.on('close', (event) => this._onclose(event, socket));
106
- socket.on('message', (...args) => this._onmessage(...args));
107
- socket.on('pong', (...args) => this._setTimeOffset(...args));
108
- socket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args));
109
- socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
110
- },
111
-
112
- /**
113
- * Handle imminent shutdown by establishing a new connection while keeping
114
- * the current one alive (make-before-break).
115
- * Idempotent: will no-op if already in progress.
116
- * @returns {void}
117
- */
118
- _handleImminentShutdown() {
119
- try {
120
- if (this._shutdownSwitchoverInProgress) {
121
- this.logger.info(`${this.namespace}: [shutdown] switchover already in progress`);
122
-
123
- return;
124
- }
125
- this._shutdownSwitchoverInProgress = true;
126
- this._shutdownSwitchoverId = `${Date.now()}`;
127
- this.logger.info(
128
- `${this.namespace}: [shutdown] switchover start, id=${this._shutdownSwitchoverId}`
129
- );
130
-
131
- this._connectWithBackoff(undefined, {
132
- isShutdownSwitchover: true,
133
- attemptOptions: {
134
- isShutdownSwitchover: true,
135
- onSuccess: (newSocket, webSocketUrl) => {
136
- this.logger.info(
137
- `${this.namespace}: [shutdown] switchover connected, url: ${webSocketUrl}`
138
- );
139
-
140
- const oldSocket = this.socket;
141
- // Atomically switch active socket reference
142
- this.socket = newSocket;
143
- this.connected = true; // remain connected throughout
144
-
145
- this._emit('event:mercury_shutdown_switchover_complete', {url: webSocketUrl});
146
-
147
- if (oldSocket) {
148
- this.logger.info(
149
- `${this.namespace}: [shutdown] old socket retained; server will close with 4001`
150
- );
151
- }
152
- },
153
- },
154
- })
155
- .then(() => {
156
- this.logger.info(`${this.namespace}: [shutdown] switchover completed successfully`);
157
- })
158
- .catch((err) => {
159
- this.logger.info(
160
- `${this.namespace}: [shutdown] switchover exhausted retries; will fall back to normal reconnection`,
161
- err
162
- );
163
- this._emit('event:mercury_shutdown_switchover_failed', {reason: err});
164
- // Old socket will eventually close with 4001, triggering normal reconnection
165
- });
166
- } catch (e) {
167
- this.logger.error(`${this.namespace}: [shutdown] error during switchover`, e);
168
- this._shutdownSwitchoverInProgress = false;
169
- this._emit('event:mercury_shutdown_switchover_failed', {reason: e});
170
- }
171
- },
172
-
173
99
  /**
174
100
  * Get the last error.
175
101
  * @returns {any} The last error.
@@ -226,11 +152,6 @@ const Mercury = WebexPlugin.extend({
226
152
  this.backoffCall.abort();
227
153
  }
228
154
 
229
- if (this._shutdownSwitchoverBackoffCall) {
230
- this.logger.info(`${this.namespace}: aborting shutdown switchover`);
231
- this._shutdownSwitchoverBackoffCall.abort();
232
- }
233
-
234
155
  if (this.socket) {
235
156
  this.socket.removeAllListeners('message');
236
157
  this.once('offline', resolve);
@@ -312,63 +233,55 @@ const Mercury = WebexPlugin.extend({
312
233
  });
313
234
  },
314
235
 
315
- _attemptConnection(socketUrl, callback, options = {}) {
316
- const {isShutdownSwitchover = false, onSuccess = null} = options;
317
-
236
+ _attemptConnection(socketUrl, callback) {
318
237
  const socket = new Socket();
319
- let newWSUrl;
320
-
321
- this._attachSocketEventListeners(socket);
238
+ let attemptWSUrl;
322
239
 
323
- // Check appropriate backoff call based on connection type
324
- if (isShutdownSwitchover && !this._shutdownSwitchoverBackoffCall) {
325
- const msg = `${this.namespace}: prevent socket open when switchover backoff call no longer defined`;
326
- const err = new Error(msg);
240
+ socket.on('close', (...args) => this._onclose(...args));
241
+ socket.on('message', (...args) => this._onmessage(...args));
242
+ socket.on('pong', (...args) => this._setTimeOffset(...args));
243
+ socket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args));
244
+ socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
327
245
 
328
- this.logger.info(msg);
246
+ Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()])
247
+ .then(([webSocketUrl, token]) => {
248
+ if (!this.backoffCall) {
249
+ const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
329
250
 
330
- // Call the callback with the error before rejecting
331
- callback(err);
251
+ this.logger.info(msg);
332
252
 
333
- return Promise.reject(err);
334
- }
253
+ return Promise.reject(new Error(msg));
254
+ }
335
255
 
336
- if (!isShutdownSwitchover && !this.backoffCall) {
337
- const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
338
- const err = new Error(msg);
256
+ attemptWSUrl = webSocketUrl;
339
257
 
340
- this.logger.info(msg);
258
+ let options = {
259
+ forceCloseDelay: this.config.forceCloseDelay,
260
+ pingInterval: this.config.pingInterval,
261
+ pongTimeout: this.config.pongTimeout,
262
+ token: token.toString(),
263
+ trackingId: `${this.webex.sessionId}_${Date.now()}`,
264
+ logger: this.logger,
265
+ };
341
266
 
342
- // Call the callback with the error before rejecting
343
- callback(err);
267
+ // if the consumer has supplied request options use them
268
+ if (this.webex.config.defaultMercuryOptions) {
269
+ this.logger.info(`${this.namespace}: setting custom options`);
270
+ options = {...options, ...this.webex.config.defaultMercuryOptions};
271
+ }
344
272
 
345
- return Promise.reject(err);
346
- }
273
+ // Set the socket before opening it. This allows a disconnect() to close
274
+ // the socket if it is in the process of being opened.
275
+ this.socket = socket;
347
276
 
348
- // For shutdown switchover, don't set socket yet (make-before-break)
349
- // For normal connection, set socket before opening to allow disconnect() to close it
350
- if (!isShutdownSwitchover) {
351
- this.socket = socket;
352
- }
277
+ this.logger.info(`${this.namespace} connection url: ${webSocketUrl}`);
353
278
 
354
- return this._prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover)
355
- .then((webSocketUrl) => {
356
- newWSUrl = webSocketUrl;
279
+ return socket.open(webSocketUrl, options);
280
+ })
281
+ .then(() => {
357
282
  this.logger.info(
358
- `${this.namespace}: ${
359
- isShutdownSwitchover ? '[shutdown] switchover' : ''
360
- } connected to mercury, success, action: connected, url: ${newWSUrl}`
283
+ `${this.namespace}: connected to mercury, success, action: connected, url: ${attemptWSUrl}`
361
284
  );
362
-
363
- // Custom success handler for shutdown switchover
364
- if (onSuccess) {
365
- onSuccess(socket, webSocketUrl);
366
- callback();
367
-
368
- return Promise.resolve();
369
- }
370
-
371
- // Default behavior for normal connection
372
285
  callback();
373
286
 
374
287
  return this.webex.internal.feature
@@ -382,14 +295,6 @@ const Mercury = WebexPlugin.extend({
382
295
  });
383
296
  })
384
297
  .catch((reason) => {
385
- // For shutdown, simpler error handling - just callback for retry
386
- if (isShutdownSwitchover) {
387
- this.logger.info(`${this.namespace}: [shutdown] switchover attempt failed`, reason);
388
-
389
- return callback(reason);
390
- }
391
-
392
- // Normal connection error handling (existing complex logic)
393
298
  this.lastError = reason; // remember the last error
394
299
 
395
300
  // Suppress connection errors that appear to be network related. This
@@ -439,10 +344,10 @@ const Mercury = WebexPlugin.extend({
439
344
  .then((haMessagingEnabled) => {
440
345
  if (haMessagingEnabled) {
441
346
  this.logger.info(
442
- `${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${newWSUrl} error: ${reason.message}`
347
+ `${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${attemptWSUrl} error: ${reason.message}`
443
348
  );
444
349
 
445
- return this.webex.internal.services.markFailedUrl(newWSUrl);
350
+ return this.webex.internal.services.markFailedUrl(attemptWSUrl);
446
351
  }
447
352
 
448
353
  return null;
@@ -458,83 +363,36 @@ const Mercury = WebexPlugin.extend({
458
363
  });
459
364
  },
460
365
 
461
- _prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover = false) {
462
- const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
463
-
464
- return Promise.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(
465
- ([webSocketUrl, token]) => {
466
- let options = {
467
- forceCloseDelay: this.config.forceCloseDelay,
468
- pingInterval: this.config.pingInterval,
469
- pongTimeout: this.config.pongTimeout,
470
- token: token.toString(),
471
- trackingId: `${this.webex.sessionId}_${Date.now()}`,
472
- logger: this.logger,
473
- };
474
-
475
- if (this.webex.config.defaultMercuryOptions) {
476
- const customOptionsMsg = isShutdownSwitchover
477
- ? 'setting custom options for switchover'
478
- : 'setting custom options';
479
-
480
- this.logger.info(`${this.namespace}: ${customOptionsMsg}`);
481
- options = {...options, ...this.webex.config.defaultMercuryOptions};
482
- }
483
-
484
- this.logger.info(`${this.namespace}: ${logPrefix} url: ${webSocketUrl}`);
485
-
486
- return socket.open(webSocketUrl, options).then(() => webSocketUrl);
487
- }
488
- );
489
- },
490
-
491
- _connectWithBackoff(webSocketUrl, context = {}) {
492
- const {isShutdownSwitchover = false, attemptOptions = {}} = context;
493
-
366
+ _connectWithBackoff(webSocketUrl) {
494
367
  return new Promise((resolve, reject) => {
495
368
  // eslint gets confused about whether or not call is actually used
496
369
  // eslint-disable-next-line prefer-const
497
370
  let call;
498
371
  const onComplete = (err) => {
499
- // Clear state flags based on connection type
500
- if (isShutdownSwitchover) {
501
- this._shutdownSwitchoverInProgress = false;
502
- this._shutdownSwitchoverBackoffCall = undefined;
503
- } else {
504
- this.connecting = false;
505
- this.backoffCall = undefined;
506
- }
372
+ this.connecting = false;
507
373
 
374
+ this.backoffCall = undefined;
508
375
  if (err) {
509
- const msg = isShutdownSwitchover
510
- ? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
511
- : `failed to connect after ${call.getNumRetries()} retries`;
512
-
513
376
  this.logger.info(
514
- `${this.namespace}: ${msg}; log statement about next retry was inaccurate; ${err}`
377
+ `${
378
+ this.namespace
379
+ }: failed to connect after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
515
380
  );
516
381
 
517
382
  return reject(err);
518
383
  }
519
-
520
- // Default success handling for normal connections
521
- if (!isShutdownSwitchover) {
522
- this.connected = true;
523
- this.hasEverConnected = true;
524
- this._emit('online');
525
- this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
526
- }
384
+ this.connected = true;
385
+ this.hasEverConnected = true;
386
+ this._emit('online');
387
+ this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
527
388
 
528
389
  return resolve();
529
390
  };
530
391
 
531
392
  // eslint-disable-next-line prefer-reflect
532
393
  call = backoff.call((callback) => {
533
- const attemptNum = call.getNumRetries();
534
- const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
535
-
536
- this.logger.info(`${this.namespace}: executing ${logPrefix} attempt ${attemptNum}`);
537
- this._attemptConnection(webSocketUrl, callback, attemptOptions);
394
+ this.logger.info(`${this.namespace}: executing connection attempt ${call.getNumRetries()}`);
395
+ this._attemptConnection(webSocketUrl, callback);
538
396
  }, onComplete);
539
397
 
540
398
  call.setStrategy(
@@ -544,33 +402,24 @@ const Mercury = WebexPlugin.extend({
544
402
  })
545
403
  );
546
404
 
547
- if (
548
- this.config.initialConnectionMaxRetries &&
549
- !this.hasEverConnected &&
550
- !isShutdownSwitchover
551
- ) {
405
+ if (this.config.initialConnectionMaxRetries && !this.hasEverConnected) {
552
406
  call.failAfter(this.config.initialConnectionMaxRetries);
553
407
  } else if (this.config.maxRetries) {
554
408
  call.failAfter(this.config.maxRetries);
555
409
  }
556
410
 
557
411
  call.on('abort', () => {
558
- const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
559
-
560
- this.logger.info(`${this.namespace}: ${msg} aborted`);
561
- reject(new Error(`Mercury ${msg} Aborted`));
412
+ this.logger.info(`${this.namespace}: connection aborted`);
413
+ reject(new Error('Mercury Connection Aborted'));
562
414
  });
563
415
 
564
416
  call.on('callback', (err) => {
565
417
  if (err) {
566
418
  const number = call.getNumRetries();
567
419
  const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
568
- const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
569
420
 
570
421
  this.logger.info(
571
- `${this.namespace}: ${logPrefix} failed to connect; attempting retry ${
572
- number + 1
573
- } in ${delay} ms`
422
+ `${this.namespace}: failed to connect; attempting retry ${number + 1} in ${delay} ms`
574
423
  );
575
424
  /* istanbul ignore if */
576
425
  if (process.env.NODE_ENV === 'development') {
@@ -582,14 +431,9 @@ const Mercury = WebexPlugin.extend({
582
431
  this.logger.info(`${this.namespace}: connected`);
583
432
  });
584
433
 
585
- // Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
586
- if (isShutdownSwitchover) {
587
- this._shutdownSwitchoverBackoffCall = call;
588
- } else {
589
- this.backoffCall = call;
590
- }
591
-
592
434
  call.start();
435
+
436
+ this.backoffCall = call;
593
437
  });
594
438
  },
595
439
 
@@ -626,42 +470,19 @@ const Mercury = WebexPlugin.extend({
626
470
  return handlers;
627
471
  },
628
472
 
629
- _onclose(event, sourceSocket) {
473
+ _onclose(event) {
630
474
  // I don't see any way to avoid the complexity or statement count in here.
631
475
  /* eslint complexity: [0] */
632
476
 
633
477
  try {
634
- const isActiveSocket = sourceSocket === this.socket;
635
478
  const reason = event.reason && event.reason.toLowerCase();
479
+ const socketUrl = this.socket.url;
636
480
 
637
- let socketUrl;
638
- if (isActiveSocket && this.socket) {
639
- // Active socket closed - get URL from current socket reference
640
- socketUrl = this.socket.url;
641
- } else if (sourceSocket) {
642
- // Old socket closed - get URL from the closed socket
643
- socketUrl = sourceSocket.url;
644
- }
645
-
646
- if (isActiveSocket) {
647
- // Only tear down state if the currently active socket closed
648
- if (this.socket) {
649
- this.socket.removeAllListeners();
650
- }
651
- this.unset('socket');
652
- this.connected = false;
653
- this._emit('offline', event);
654
- this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
655
- } else {
656
- // Old socket closed; do not flip connection state
657
- this.logger.info(
658
- `${this.namespace}: [shutdown] non-active socket closed, code=${event.code}`
659
- );
660
- // Clean up listeners from old socket now that it's closed
661
- if (sourceSocket) {
662
- sourceSocket.removeAllListeners();
663
- }
664
- }
481
+ this.socket.removeAllListeners();
482
+ this.unset('socket');
483
+ this.connected = false;
484
+ this._emit('offline', event);
485
+ this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
665
486
 
666
487
  switch (event.code) {
667
488
  case 1003:
@@ -669,42 +490,20 @@ const Mercury = WebexPlugin.extend({
669
490
  this.logger.info(
670
491
  `${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
671
492
  );
672
- if (isActiveSocket) this._emit('offline.permanent', event);
493
+ this._emit('offline.permanent', event);
673
494
  break;
674
495
  case 4000:
675
496
  // metric: disconnect
676
497
  this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
677
- if (isActiveSocket) this._emit('offline.replaced', event);
678
- // If not active, nothing to do
679
- break;
680
- case 4001:
681
- // replaced during shutdown
682
- if (isActiveSocket) {
683
- // Server closed active socket with 4001, meaning it expected this connection
684
- // to be replaced, but the switchover in _handleImminentShutdown failed.
685
- // This is a permanent failure - do not reconnect.
686
- this.logger.warn(
687
- `${this.namespace}: active socket closed with 4001; shutdown switchover failed`
688
- );
689
- this._emit('offline.permanent', event);
690
- } else {
691
- // Expected: old socket closed after successful switchover
692
- this.logger.info(
693
- `${this.namespace}: old socket closed with 4001 (replaced during shutdown); no reconnect needed`
694
- );
695
- this._emit('offline.replaced', event);
696
- }
498
+ this._emit('offline.replaced', event);
697
499
  break;
698
500
  case 1001:
699
501
  case 1005:
700
502
  case 1006:
701
503
  case 1011:
702
504
  this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
703
- if (isActiveSocket) {
704
- this._emit('offline.transient', event);
705
- this.logger.info(`${this.namespace}: [shutdown] reconnecting active socket to recover`);
706
- this._reconnect(socketUrl);
707
- }
505
+ this._emit('offline.transient', event);
506
+ this._reconnect(socketUrl);
708
507
  // metric: disconnect
709
508
  // if (code == 1011 && reason !== ping error) metric: unexpected disconnect
710
509
  break;
@@ -712,18 +511,15 @@ const Mercury = WebexPlugin.extend({
712
511
  case 3050: // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
713
512
  if (normalReconnectReasons.includes(reason)) {
714
513
  this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
715
- if (isActiveSocket) {
716
- this._emit('offline.transient', event);
717
- this.logger.info(`${this.namespace}: [shutdown] reconnecting due to normal close`);
718
- this._reconnect(socketUrl);
719
- }
514
+ this._emit('offline.transient', event);
515
+ this._reconnect(socketUrl);
720
516
  // metric: disconnect
721
517
  // if (reason === done forced) metric: force closure
722
518
  } else {
723
519
  this.logger.info(
724
520
  `${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
725
521
  );
726
- if (isActiveSocket) this._emit('offline.permanent', event);
522
+ this._emit('offline.permanent', event);
727
523
  }
728
524
  break;
729
525
  default:
@@ -731,7 +527,7 @@ const Mercury = WebexPlugin.extend({
731
527
  `${this.namespace}: socket disconnected unexpectedly; will not reconnect`
732
528
  );
733
529
  // unexpected disconnect
734
- if (isActiveSocket) this._emit('offline.permanent', event);
530
+ this._emit('offline.permanent', event);
735
531
  }
736
532
  } catch (error) {
737
533
  this.logger.error(`${this.namespace}: error occurred in close handler`, error);
@@ -746,16 +542,6 @@ const Mercury = WebexPlugin.extend({
746
542
  this.logger.debug(`${this.namespace}: message envelope: `, envelope);
747
543
  }
748
544
 
749
- // Handle shutdown message shape: { type: 'shutdown' }
750
- if (envelope && envelope.type === 'shutdown') {
751
- this.logger.info(`${this.namespace}: [shutdown] imminent shutdown message received`);
752
- this._emit('event:mercury_shutdown_imminent', envelope);
753
-
754
- this._handleImminentShutdown();
755
-
756
- return Promise.resolve();
757
- }
758
-
759
545
  const {data} = envelope;
760
546
 
761
547
  this._applyOverrides(data);