@webex/internal-plugin-mercury 3.9.0-webinar5k.1 → 3.10.0-next.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/dist/mercury.js +341 -134
- package/dist/mercury.js.map +1 -1
- package/package.json +18 -18
- package/src/mercury.js +311 -71
- package/test/unit/spec/mercury.js +684 -1
- package/test/unit/spec/socket.js +6 -6
package/src/mercury.js
CHANGED
|
@@ -68,6 +68,106 @@ const Mercury = WebexPlugin.extend({
|
|
|
68
68
|
this.webex.internal.feature.updateFeature(envelope.data.featureToggle);
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
|
+
/*
|
|
72
|
+
* When Cluster Migrations, notify clients using ActiveClusterStatusEvent via mercury
|
|
73
|
+
* https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-002.html#wip-notifying-clients-of-cluster-migrations
|
|
74
|
+
* */
|
|
75
|
+
this.on('event:ActiveClusterStatusEvent', (envelope) => {
|
|
76
|
+
if (
|
|
77
|
+
typeof this.webex.internal.services?.switchActiveClusterIds === 'function' &&
|
|
78
|
+
envelope &&
|
|
79
|
+
envelope.data
|
|
80
|
+
) {
|
|
81
|
+
this.webex.internal.services.switchActiveClusterIds(envelope.data?.activeClusters);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
/*
|
|
85
|
+
* Using cache-invalidation via mercury to instead the method of polling via the new /timestamp endpoint from u2c
|
|
86
|
+
* https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-005.html#websocket-notifications
|
|
87
|
+
* */
|
|
88
|
+
this.on('event:u2c.cache-invalidation', (envelope) => {
|
|
89
|
+
if (
|
|
90
|
+
typeof this.webex.internal.services?.invalidateCache === 'function' &&
|
|
91
|
+
envelope &&
|
|
92
|
+
envelope.data
|
|
93
|
+
) {
|
|
94
|
+
this.webex.internal.services.invalidateCache(envelope.data?.timestamp);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
},
|
|
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
|
+
}
|
|
71
171
|
},
|
|
72
172
|
|
|
73
173
|
/**
|
|
@@ -126,6 +226,11 @@ const Mercury = WebexPlugin.extend({
|
|
|
126
226
|
this.backoffCall.abort();
|
|
127
227
|
}
|
|
128
228
|
|
|
229
|
+
if (this._shutdownSwitchoverBackoffCall) {
|
|
230
|
+
this.logger.info(`${this.namespace}: aborting shutdown switchover`);
|
|
231
|
+
this._shutdownSwitchoverBackoffCall.abort();
|
|
232
|
+
}
|
|
233
|
+
|
|
129
234
|
if (this.socket) {
|
|
130
235
|
this.socket.removeAllListeners('message');
|
|
131
236
|
this.once('offline', resolve);
|
|
@@ -207,55 +312,63 @@ const Mercury = WebexPlugin.extend({
|
|
|
207
312
|
});
|
|
208
313
|
},
|
|
209
314
|
|
|
210
|
-
_attemptConnection(socketUrl, callback) {
|
|
315
|
+
_attemptConnection(socketUrl, callback, options = {}) {
|
|
316
|
+
const {isShutdownSwitchover = false, onSuccess = null} = options;
|
|
317
|
+
|
|
211
318
|
const socket = new Socket();
|
|
212
|
-
let
|
|
319
|
+
let newWSUrl;
|
|
213
320
|
|
|
214
|
-
|
|
215
|
-
socket.on('message', (...args) => this._onmessage(...args));
|
|
216
|
-
socket.on('pong', (...args) => this._setTimeOffset(...args));
|
|
217
|
-
socket.on('sequence-mismatch', (...args) => this._emit('sequence-mismatch', ...args));
|
|
218
|
-
socket.on('ping-pong-latency', (...args) => this._emit('ping-pong-latency', ...args));
|
|
321
|
+
this._attachSocketEventListeners(socket);
|
|
219
322
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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);
|
|
224
327
|
|
|
225
|
-
|
|
328
|
+
this.logger.info(msg);
|
|
226
329
|
|
|
227
|
-
|
|
228
|
-
|
|
330
|
+
// Call the callback with the error before rejecting
|
|
331
|
+
callback(err);
|
|
229
332
|
|
|
230
|
-
|
|
333
|
+
return Promise.reject(err);
|
|
334
|
+
}
|
|
231
335
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
pongTimeout: this.config.pongTimeout,
|
|
236
|
-
token: token.toString(),
|
|
237
|
-
trackingId: `${this.webex.sessionId}_${Date.now()}`,
|
|
238
|
-
logger: this.logger,
|
|
239
|
-
};
|
|
336
|
+
if (!isShutdownSwitchover && !this.backoffCall) {
|
|
337
|
+
const msg = `${this.namespace}: prevent socket open when backoffCall no longer defined`;
|
|
338
|
+
const err = new Error(msg);
|
|
240
339
|
|
|
241
|
-
|
|
242
|
-
if (this.webex.config.defaultMercuryOptions) {
|
|
243
|
-
this.logger.info(`${this.namespace}: setting custom options`);
|
|
244
|
-
options = {...options, ...this.webex.config.defaultMercuryOptions};
|
|
245
|
-
}
|
|
340
|
+
this.logger.info(msg);
|
|
246
341
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
this.socket = socket;
|
|
342
|
+
// Call the callback with the error before rejecting
|
|
343
|
+
callback(err);
|
|
250
344
|
|
|
251
|
-
|
|
345
|
+
return Promise.reject(err);
|
|
346
|
+
}
|
|
252
347
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
+
}
|
|
353
|
+
|
|
354
|
+
return this._prepareAndOpenSocket(socket, socketUrl, isShutdownSwitchover)
|
|
355
|
+
.then((webSocketUrl) => {
|
|
356
|
+
newWSUrl = webSocketUrl;
|
|
256
357
|
this.logger.info(
|
|
257
|
-
`${this.namespace}:
|
|
358
|
+
`${this.namespace}: ${
|
|
359
|
+
isShutdownSwitchover ? '[shutdown] switchover' : ''
|
|
360
|
+
} connected to mercury, success, action: connected, url: ${newWSUrl}`
|
|
258
361
|
);
|
|
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
|
|
259
372
|
callback();
|
|
260
373
|
|
|
261
374
|
return this.webex.internal.feature
|
|
@@ -269,6 +382,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
269
382
|
});
|
|
270
383
|
})
|
|
271
384
|
.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)
|
|
272
393
|
this.lastError = reason; // remember the last error
|
|
273
394
|
|
|
274
395
|
// Suppress connection errors that appear to be network related. This
|
|
@@ -318,10 +439,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
318
439
|
.then((haMessagingEnabled) => {
|
|
319
440
|
if (haMessagingEnabled) {
|
|
320
441
|
this.logger.info(
|
|
321
|
-
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${
|
|
442
|
+
`${this.namespace}: received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ${newWSUrl} error: ${reason.message}`
|
|
322
443
|
);
|
|
323
444
|
|
|
324
|
-
return this.webex.internal.services.markFailedUrl(
|
|
445
|
+
return this.webex.internal.services.markFailedUrl(newWSUrl);
|
|
325
446
|
}
|
|
326
447
|
|
|
327
448
|
return null;
|
|
@@ -337,36 +458,83 @@ const Mercury = WebexPlugin.extend({
|
|
|
337
458
|
});
|
|
338
459
|
},
|
|
339
460
|
|
|
340
|
-
|
|
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
|
+
|
|
341
494
|
return new Promise((resolve, reject) => {
|
|
342
495
|
// eslint gets confused about whether or not call is actually used
|
|
343
496
|
// eslint-disable-next-line prefer-const
|
|
344
497
|
let call;
|
|
345
498
|
const onComplete = (err) => {
|
|
346
|
-
|
|
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
|
+
}
|
|
347
507
|
|
|
348
|
-
this.backoffCall = undefined;
|
|
349
508
|
if (err) {
|
|
509
|
+
const msg = isShutdownSwitchover
|
|
510
|
+
? `[shutdown] switchover failed after ${call.getNumRetries()} retries`
|
|
511
|
+
: `failed to connect after ${call.getNumRetries()} retries`;
|
|
512
|
+
|
|
350
513
|
this.logger.info(
|
|
351
|
-
`${
|
|
352
|
-
this.namespace
|
|
353
|
-
}: failed to connect after ${call.getNumRetries()} retries; log statement about next retry was inaccurate; ${err}`
|
|
514
|
+
`${this.namespace}: ${msg}; log statement about next retry was inaccurate; ${err}`
|
|
354
515
|
);
|
|
355
516
|
|
|
356
517
|
return reject(err);
|
|
357
518
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
+
}
|
|
362
527
|
|
|
363
528
|
return resolve();
|
|
364
529
|
};
|
|
365
530
|
|
|
366
531
|
// eslint-disable-next-line prefer-reflect
|
|
367
532
|
call = backoff.call((callback) => {
|
|
368
|
-
|
|
369
|
-
|
|
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);
|
|
370
538
|
}, onComplete);
|
|
371
539
|
|
|
372
540
|
call.setStrategy(
|
|
@@ -376,24 +544,33 @@ const Mercury = WebexPlugin.extend({
|
|
|
376
544
|
})
|
|
377
545
|
);
|
|
378
546
|
|
|
379
|
-
if (
|
|
547
|
+
if (
|
|
548
|
+
this.config.initialConnectionMaxRetries &&
|
|
549
|
+
!this.hasEverConnected &&
|
|
550
|
+
!isShutdownSwitchover
|
|
551
|
+
) {
|
|
380
552
|
call.failAfter(this.config.initialConnectionMaxRetries);
|
|
381
553
|
} else if (this.config.maxRetries) {
|
|
382
554
|
call.failAfter(this.config.maxRetries);
|
|
383
555
|
}
|
|
384
556
|
|
|
385
557
|
call.on('abort', () => {
|
|
386
|
-
|
|
387
|
-
|
|
558
|
+
const msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
|
|
559
|
+
|
|
560
|
+
this.logger.info(`${this.namespace}: ${msg} aborted`);
|
|
561
|
+
reject(new Error(`Mercury ${msg} Aborted`));
|
|
388
562
|
});
|
|
389
563
|
|
|
390
564
|
call.on('callback', (err) => {
|
|
391
565
|
if (err) {
|
|
392
566
|
const number = call.getNumRetries();
|
|
393
567
|
const delay = Math.min(call.strategy_.nextBackoffDelay_, this.config.backoffTimeMax);
|
|
568
|
+
const logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
|
|
394
569
|
|
|
395
570
|
this.logger.info(
|
|
396
|
-
`${this.namespace}: failed to connect; attempting retry ${
|
|
571
|
+
`${this.namespace}: ${logPrefix} failed to connect; attempting retry ${
|
|
572
|
+
number + 1
|
|
573
|
+
} in ${delay} ms`
|
|
397
574
|
);
|
|
398
575
|
/* istanbul ignore if */
|
|
399
576
|
if (process.env.NODE_ENV === 'development') {
|
|
@@ -405,9 +582,14 @@ const Mercury = WebexPlugin.extend({
|
|
|
405
582
|
this.logger.info(`${this.namespace}: connected`);
|
|
406
583
|
});
|
|
407
584
|
|
|
408
|
-
call
|
|
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
|
+
}
|
|
409
591
|
|
|
410
|
-
|
|
592
|
+
call.start();
|
|
411
593
|
});
|
|
412
594
|
},
|
|
413
595
|
|
|
@@ -444,19 +626,42 @@ const Mercury = WebexPlugin.extend({
|
|
|
444
626
|
return handlers;
|
|
445
627
|
},
|
|
446
628
|
|
|
447
|
-
_onclose(event) {
|
|
629
|
+
_onclose(event, sourceSocket) {
|
|
448
630
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
449
631
|
/* eslint complexity: [0] */
|
|
450
632
|
|
|
451
633
|
try {
|
|
634
|
+
const isActiveSocket = sourceSocket === this.socket;
|
|
452
635
|
const reason = event.reason && event.reason.toLowerCase();
|
|
453
|
-
const socketUrl = this.socket.url;
|
|
454
636
|
|
|
455
|
-
|
|
456
|
-
this.
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
}
|
|
460
665
|
|
|
461
666
|
switch (event.code) {
|
|
462
667
|
case 1003:
|
|
@@ -464,20 +669,42 @@ const Mercury = WebexPlugin.extend({
|
|
|
464
669
|
this.logger.info(
|
|
465
670
|
`${this.namespace}: Mercury service rejected last message; will not reconnect: ${event.reason}`
|
|
466
671
|
);
|
|
467
|
-
this._emit('offline.permanent', event);
|
|
672
|
+
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
468
673
|
break;
|
|
469
674
|
case 4000:
|
|
470
675
|
// metric: disconnect
|
|
471
676
|
this.logger.info(`${this.namespace}: socket replaced; will not reconnect`);
|
|
472
|
-
this._emit('offline.replaced', event);
|
|
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
|
+
}
|
|
473
697
|
break;
|
|
474
698
|
case 1001:
|
|
475
699
|
case 1005:
|
|
476
700
|
case 1006:
|
|
477
701
|
case 1011:
|
|
478
702
|
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
479
|
-
|
|
480
|
-
|
|
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
|
+
}
|
|
481
708
|
// metric: disconnect
|
|
482
709
|
// if (code == 1011 && reason !== ping error) metric: unexpected disconnect
|
|
483
710
|
break;
|
|
@@ -485,15 +712,18 @@ const Mercury = WebexPlugin.extend({
|
|
|
485
712
|
case 3050: // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
|
|
486
713
|
if (normalReconnectReasons.includes(reason)) {
|
|
487
714
|
this.logger.info(`${this.namespace}: socket disconnected; reconnecting`);
|
|
488
|
-
|
|
489
|
-
|
|
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
|
+
}
|
|
490
720
|
// metric: disconnect
|
|
491
721
|
// if (reason === done forced) metric: force closure
|
|
492
722
|
} else {
|
|
493
723
|
this.logger.info(
|
|
494
724
|
`${this.namespace}: socket disconnected; will not reconnect: ${event.reason}`
|
|
495
725
|
);
|
|
496
|
-
this._emit('offline.permanent', event);
|
|
726
|
+
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
497
727
|
}
|
|
498
728
|
break;
|
|
499
729
|
default:
|
|
@@ -501,7 +731,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
501
731
|
`${this.namespace}: socket disconnected unexpectedly; will not reconnect`
|
|
502
732
|
);
|
|
503
733
|
// unexpected disconnect
|
|
504
|
-
this._emit('offline.permanent', event);
|
|
734
|
+
if (isActiveSocket) this._emit('offline.permanent', event);
|
|
505
735
|
}
|
|
506
736
|
} catch (error) {
|
|
507
737
|
this.logger.error(`${this.namespace}: error occurred in close handler`, error);
|
|
@@ -516,6 +746,16 @@ const Mercury = WebexPlugin.extend({
|
|
|
516
746
|
this.logger.debug(`${this.namespace}: message envelope: `, envelope);
|
|
517
747
|
}
|
|
518
748
|
|
|
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
|
+
|
|
519
759
|
const {data} = envelope;
|
|
520
760
|
|
|
521
761
|
this._applyOverrides(data);
|