@webex/internal-plugin-mercury 3.9.0 → 3.10.0-multi-llms.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
 
3
+ var _Array$from = require("@babel/runtime-corejs2/core-js/array/from");
4
+ var _Symbol = require("@babel/runtime-corejs2/core-js/symbol");
5
+ var _Symbol$iterator = require("@babel/runtime-corejs2/core-js/symbol/iterator");
6
+ var _Array$isArray = require("@babel/runtime-corejs2/core-js/array/is-array");
3
7
  var _Object$keys2 = require("@babel/runtime-corejs2/core-js/object/keys");
4
8
  var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs2/core-js/object/get-own-property-symbols");
5
9
  var _Object$getOwnPropertyDescriptor2 = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
@@ -11,12 +15,14 @@ _Object$defineProperty(exports, "__esModule", {
11
15
  value: true
12
16
  });
13
17
  exports.default = void 0;
18
+ var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));
19
+ var _now = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/date/now"));
14
20
  var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
15
21
  var _keys = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/keys"));
16
22
  var _assign = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/assign"));
17
23
  var _deleteProperty = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/delete-property"));
18
- var _now = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/date/now"));
19
24
  var _getOwnPropertyDescriptor = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor"));
25
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/toConsumableArray"));
20
26
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
21
27
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
22
28
  var _applyDecoratedDescriptor2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/applyDecoratedDescriptor"));
@@ -34,10 +40,14 @@ var _dec, _dec2, _obj;
34
40
  */
35
41
  function ownKeys(e, r) { var t = _Object$keys2(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return _Object$getOwnPropertyDescriptor2(e, r).enumerable; })), t.push.apply(t, o); } return t; }
36
42
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor2(t, r)); }); } return e; }
43
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && o[_Symbol$iterator] || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
44
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
45
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
37
46
  var normalReconnectReasons = ['idle', 'done (forced)', 'pong not received', 'pong mismatch'];
38
47
  var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mercury#listen(): Use Mercury#connect() instead'), _dec2 = (0, _common.deprecated)('Mercury#stopListening(): Use Mercury#disconnect() instead'), (_obj = {
39
48
  namespace: 'Mercury',
40
49
  lastError: undefined,
50
+ defaultSessionId: 'mercury-default-session',
41
51
  session: {
42
52
  connected: {
43
53
  default: false,
@@ -51,7 +61,24 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
51
61
  default: false,
52
62
  type: 'boolean'
53
63
  },
54
- socket: 'object',
64
+ sockets: {
65
+ default: function _default() {
66
+ return new _map.default();
67
+ },
68
+ type: 'object'
69
+ },
70
+ backoffCalls: {
71
+ default: function _default() {
72
+ return new _map.default();
73
+ },
74
+ type: 'object'
75
+ },
76
+ _shutdownSwitchoverBackoffCalls: {
77
+ default: function _default() {
78
+ return new _map.default();
79
+ },
80
+ type: 'object'
81
+ },
55
82
  localClusterServiceUrls: 'object',
56
83
  mercuryTimeOffset: {
57
84
  default: undefined,
@@ -79,6 +106,117 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
79
106
  _this.webex.internal.feature.updateFeature(envelope.data.featureToggle);
80
107
  }
81
108
  });
109
+ /*
110
+ * When Cluster Migrations, notify clients using ActiveClusterStatusEvent via mercury
111
+ * https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-002.html#wip-notifying-clients-of-cluster-migrations
112
+ * */
113
+ this.on('event:ActiveClusterStatusEvent', function (envelope) {
114
+ var _this$webex$internal$;
115
+ if (typeof ((_this$webex$internal$ = _this.webex.internal.services) === null || _this$webex$internal$ === void 0 ? void 0 : _this$webex$internal$.switchActiveClusterIds) === 'function' && envelope && envelope.data) {
116
+ var _envelope$data;
117
+ _this.webex.internal.services.switchActiveClusterIds((_envelope$data = envelope.data) === null || _envelope$data === void 0 ? void 0 : _envelope$data.activeClusters);
118
+ }
119
+ });
120
+ /*
121
+ * Using cache-invalidation via mercury to instead the method of polling via the new /timestamp endpoint from u2c
122
+ * https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-005.html#websocket-notifications
123
+ * */
124
+ this.on('event:u2c.cache-invalidation', function (envelope) {
125
+ var _this$webex$internal$2;
126
+ if (typeof ((_this$webex$internal$2 = _this.webex.internal.services) === null || _this$webex$internal$2 === void 0 ? void 0 : _this$webex$internal$2.invalidateCache) === 'function' && envelope && envelope.data) {
127
+ var _envelope$data2;
128
+ _this.webex.internal.services.invalidateCache((_envelope$data2 = envelope.data) === null || _envelope$data2 === void 0 ? void 0 : _envelope$data2.timestamp);
129
+ }
130
+ });
131
+ },
132
+ /**
133
+ * Attach event listeners to a socket.
134
+ * @param {Socket} socket - The socket to attach listeners to
135
+ * @param {sessionId} sessionId - The socket related session ID
136
+ * @returns {void}
137
+ */
138
+ _attachSocketEventListeners: function _attachSocketEventListeners(socket, sessionId) {
139
+ var _this2 = this;
140
+ socket.on('close', function (event) {
141
+ return _this2._onclose(sessionId, event, socket);
142
+ });
143
+ socket.on('message', function () {
144
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
145
+ args[_key] = arguments[_key];
146
+ }
147
+ return _this2._onmessage.apply(_this2, [sessionId].concat(args));
148
+ });
149
+ socket.on('pong', function () {
150
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
151
+ args[_key2] = arguments[_key2];
152
+ }
153
+ return _this2._setTimeOffset.apply(_this2, [sessionId].concat(args));
154
+ });
155
+ socket.on('sequence-mismatch', function () {
156
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
157
+ args[_key3] = arguments[_key3];
158
+ }
159
+ return _this2._emit.apply(_this2, [sessionId, 'sequence-mismatch'].concat(args));
160
+ });
161
+ socket.on('ping-pong-latency', function () {
162
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
163
+ args[_key4] = arguments[_key4];
164
+ }
165
+ return _this2._emit.apply(_this2, [sessionId, 'ping-pong-latency'].concat(args));
166
+ });
167
+ },
168
+ /**
169
+ * Handle imminent shutdown by establishing a new connection while keeping
170
+ * the current one alive (make-before-break).
171
+ * Idempotent: will no-op if already in progress.
172
+ * @param {string} sessionId - The session ID for which the shutdown is imminent
173
+ * @returns {void}
174
+ */
175
+ _handleImminentShutdown: function _handleImminentShutdown(sessionId) {
176
+ var _this3 = this;
177
+ var oldSocket = this.sockets.get(sessionId);
178
+ try {
179
+ if (this._shutdownSwitchoverBackoffCalls.get(sessionId)) {
180
+ this.logger.info("".concat(this.namespace, ": [shutdown] switchover already in progress for ").concat(sessionId));
181
+ return;
182
+ }
183
+ this._shutdownSwitchoverId = "".concat((0, _now.default)());
184
+ this.logger.info("".concat(this.namespace, ": [shutdown] switchover start, id=").concat(this._shutdownSwitchoverId, " for ").concat(sessionId));
185
+ this._connectWithBackoff(undefined, sessionId, {
186
+ isShutdownSwitchover: true,
187
+ attemptOptions: {
188
+ isShutdownSwitchover: true,
189
+ onSuccess: function onSuccess(newSocket, webSocketUrl) {
190
+ _this3.logger.info("".concat(_this3.namespace, ": [shutdown] switchover connected, url: ").concat(webSocketUrl, " for ").concat(sessionId));
191
+
192
+ // Atomically switch active socket reference
193
+ _this3.socket = _this3.sockets.get(_this3.defaultSessionId) || newSocket;
194
+ _this3.connected = _this3.hasConnectedSockets(); // remain connected throughout
195
+
196
+ _this3._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
197
+ url: webSocketUrl
198
+ });
199
+ if (oldSocket) {
200
+ _this3.logger.info("".concat(_this3.namespace, ": [shutdown] old socket retained; server will close with 4001"));
201
+ }
202
+ }
203
+ }
204
+ }).then(function () {
205
+ _this3.logger.info("".concat(_this3.namespace, ": [shutdown] switchover completed successfully for ").concat(sessionId));
206
+ }).catch(function (err) {
207
+ _this3.logger.info("".concat(_this3.namespace, ": [shutdown] switchover exhausted retries; will fall back to normal reconnection for ").concat(sessionId, ": "), err);
208
+ _this3._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {
209
+ reason: err
210
+ });
211
+ // Old socket will eventually close with 4001, triggering normal reconnection
212
+ });
213
+ } catch (e) {
214
+ this.logger.error("".concat(this.namespace, ": [shutdown] error during switchover for ").concat(sessionId), e);
215
+ this._shutdownSwitchoverBackoffCalls.delete(sessionId);
216
+ this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {
217
+ reason: e
218
+ });
219
+ }
82
220
  },
83
221
  /**
84
222
  * Get the last error.
@@ -87,41 +225,164 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
87
225
  getLastError: function getLastError() {
88
226
  return this.lastError;
89
227
  },
228
+ /**
229
+ * Get all active socket connections
230
+ * @returns {Map} Map of sessionId to socket instances
231
+ */
232
+ getSockets: function getSockets() {
233
+ return this.sockets;
234
+ },
235
+ /**
236
+ * Get a specific socket by connection ID
237
+ * @param {string} sessionId - The connection identifier
238
+ * @returns {Socket|undefined} The socket instance or undefined if not found
239
+ */
240
+ getSocket: function getSocket() {
241
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultSessionId;
242
+ return this.sockets.get(sessionId);
243
+ },
244
+ /**
245
+ * Check if any sockets are connected
246
+ * @returns {boolean} True if at least one socket is connected
247
+ */
248
+ hasConnectedSockets: function hasConnectedSockets() {
249
+ var _iterator = _createForOfIteratorHelper(this.sockets.values()),
250
+ _step;
251
+ try {
252
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
253
+ var socket = _step.value;
254
+ if (socket && socket.connected) {
255
+ return true;
256
+ }
257
+ }
258
+ } catch (err) {
259
+ _iterator.e(err);
260
+ } finally {
261
+ _iterator.f();
262
+ }
263
+ return false;
264
+ },
265
+ /**
266
+ * Check if any sockets are connecting
267
+ * @returns {boolean} True if at least one socket is connected
268
+ */
269
+ hasConnectingSockets: function hasConnectingSockets() {
270
+ var _iterator2 = _createForOfIteratorHelper(this.sockets.values()),
271
+ _step2;
272
+ try {
273
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
274
+ var socket = _step2.value;
275
+ if (socket && socket.connecting) {
276
+ return true;
277
+ }
278
+ }
279
+ } catch (err) {
280
+ _iterator2.e(err);
281
+ } finally {
282
+ _iterator2.f();
283
+ }
284
+ return false;
285
+ },
286
+ // @oneFlight
90
287
  connect: function connect(webSocketUrl) {
91
- var _this2 = this;
92
- if (this.connected) {
93
- this.logger.info("".concat(this.namespace, ": already connected, will not connect again"));
288
+ var _this4 = this;
289
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
290
+ if (!this._connectPromises) this._connectPromises = new _map.default();
291
+
292
+ // First check if there's already a connection promise for this session
293
+ if (this._connectPromises.has(sessionId)) {
294
+ this.logger.info("".concat(this.namespace, ": connection ").concat(sessionId, " already in progress, returning existing promise"));
295
+ return this._connectPromises.get(sessionId);
296
+ }
297
+ var sessionSocket = this.sockets.get(sessionId);
298
+ if (sessionSocket !== null && sessionSocket !== void 0 && sessionSocket.connected || sessionSocket !== null && sessionSocket !== void 0 && sessionSocket.connecting) {
299
+ this.logger.info("".concat(this.namespace, ": connection ").concat(sessionId, " already connected, will not connect again"));
94
300
  return _promise.default.resolve();
95
301
  }
96
302
  this.connecting = true;
97
- this.logger.info("".concat(this.namespace, ": starting connection attempt"));
303
+ this.logger.info("".concat(this.namespace, ": starting connection attempt for ").concat(sessionId));
98
304
  this.logger.info("".concat(this.namespace, ": debug_mercury_logging stack: "), new Error('debug_mercury_logging').stack);
99
- return _promise.default.resolve(this.webex.internal.device.registered || this.webex.internal.device.register()).then(function () {
100
- _this2.logger.info("".concat(_this2.namespace, ": connecting"));
101
- return _this2._connectWithBackoff(webSocketUrl);
305
+ var connectPromise = _promise.default.resolve(this.webex.internal.device.registered || this.webex.internal.device.register()).then(function () {
306
+ _this4.logger.info("".concat(_this4.namespace, ": connecting ").concat(sessionId));
307
+ return _this4._connectWithBackoff(webSocketUrl, sessionId);
308
+ }).finally(function () {
309
+ _this4._connectPromises.delete(sessionId);
102
310
  });
311
+ this._connectPromises.set(sessionId, connectPromise);
312
+ return connectPromise;
103
313
  },
104
314
  logout: function logout() {
105
315
  this.logger.info("".concat(this.namespace, ": logout() called"));
106
316
  this.logger.info("".concat(this.namespace, ": debug_mercury_logging stack: "), new Error('debug_mercury_logging').stack);
107
- return this.disconnect(this.config.beforeLogoutOptionsCloseReason && !normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason) ? {
317
+ return this.disconnectAll(this.config.beforeLogoutOptionsCloseReason && !normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason) ? {
108
318
  code: 3050,
109
319
  reason: this.config.beforeLogoutOptionsCloseReason
110
320
  } : undefined);
111
321
  },
322
+ // @oneFlight
112
323
  disconnect: function disconnect(options) {
113
- var _this3 = this;
324
+ var _this5 = this;
325
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
114
326
  return new _promise.default(function (resolve) {
115
- if (_this3.backoffCall) {
116
- _this3.logger.info("".concat(_this3.namespace, ": aborting connection"));
117
- _this3.backoffCall.abort();
327
+ var backoffCall = _this5.backoffCalls.get(sessionId);
328
+ if (backoffCall) {
329
+ _this5.logger.info("".concat(_this5.namespace, ": aborting connection ").concat(sessionId));
330
+ backoffCall.abort();
331
+ _this5.backoffCalls.delete(sessionId);
118
332
  }
119
- if (_this3.socket) {
120
- _this3.socket.removeAllListeners('message');
121
- _this3.once('offline', resolve);
122
- resolve(_this3.socket.close(options || undefined));
333
+ var shutdownSwitchoverBackoffCalls = _this5._shutdownSwitchoverBackoffCalls.get(sessionId);
334
+ if (shutdownSwitchoverBackoffCalls) {
335
+ _this5.logger.info("".concat(_this5.namespace, ": aborting shutdown switchover connection ").concat(sessionId));
336
+ shutdownSwitchoverBackoffCalls.abort();
337
+ _this5._shutdownSwitchoverBackoffCalls.delete(sessionId);
338
+ }
339
+ // Clean up any pending connection promises
340
+ if (_this5._connectPromises) {
341
+ _this5._connectPromises.delete(sessionId);
342
+ }
343
+ var sessionSocket = _this5.sockets.get(sessionId);
344
+ var suffix = sessionId === _this5.defaultSessionId ? '' : ":".concat(sessionId);
345
+ if (sessionSocket) {
346
+ sessionSocket.removeAllListeners('message');
347
+ sessionSocket.connecting = false;
348
+ sessionSocket.connected = false;
349
+ _this5.once(sessionId === _this5.defaultSessionId ? 'offline' : "offline".concat(suffix), resolve);
350
+ resolve(sessionSocket.close(options || undefined));
123
351
  }
124
352
  resolve();
353
+
354
+ // Update overall connected status
355
+ _this5.connected = _this5.hasConnectedSockets();
356
+ });
357
+ },
358
+ /**
359
+ * Disconnect all socket connections
360
+ * @param {object} options - Close options
361
+ * @returns {Promise} Promise that resolves when all connections are closed
362
+ */
363
+ disconnectAll: function disconnectAll(options) {
364
+ var _this6 = this;
365
+ var disconnectPromises = [];
366
+ var _iterator3 = _createForOfIteratorHelper(this.sockets.keys()),
367
+ _step3;
368
+ try {
369
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
370
+ var sessionId = _step3.value;
371
+ disconnectPromises.push(this.disconnect(options, sessionId));
372
+ }
373
+ } catch (err) {
374
+ _iterator3.e(err);
375
+ } finally {
376
+ _iterator3.f();
377
+ }
378
+ return _promise.default.all(disconnectPromises).then(function () {
379
+ _this6.connected = false;
380
+ _this6.sockets.clear();
381
+ _this6.backoffCalls.clear();
382
+ // Clear connection promises to prevent stale promises
383
+ if (_this6._connectPromises) {
384
+ _this6._connectPromises.clear();
385
+ }
125
386
  });
126
387
  },
127
388
  listen: function listen() {
@@ -145,19 +406,19 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
145
406
  });
146
407
  },
147
408
  _prepareUrl: function _prepareUrl(webSocketUrl) {
148
- var _this4 = this;
409
+ var _this7 = this;
149
410
  if (!webSocketUrl) {
150
411
  webSocketUrl = this.webex.internal.device.webSocketUrl;
151
412
  }
152
413
  return this.webex.internal.feature.getFeature('developer', 'web-high-availability').then(function (haMessagingEnabled) {
153
414
  if (haMessagingEnabled) {
154
- return _this4.webex.internal.services.convertUrlToPriorityHostUrl(webSocketUrl);
415
+ return _this7.webex.internal.services.convertUrlToPriorityHostUrl(webSocketUrl);
155
416
  }
156
417
  return webSocketUrl;
157
418
  }).then(function (wsUrl) {
158
419
  webSocketUrl = wsUrl;
159
420
  }).then(function () {
160
- return _this4.webex.internal.feature.getFeature('developer', 'web-shared-mercury');
421
+ return _this7.webex.internal.feature.getFeature('developer', 'web-shared-mercury');
161
422
  }).then(function (webSharedMercury) {
162
423
  webSocketUrl = _url.default.parse(webSocketUrl, true);
163
424
  (0, _assign.default)(webSocketUrl.query, {
@@ -172,104 +433,104 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
172
433
  });
173
434
  (0, _deleteProperty.default)(webSocketUrl.query, 'bufferStates');
174
435
  }
175
- if ((0, _lodash.get)(_this4, 'webex.config.device.ephemeral', false)) {
436
+ if ((0, _lodash.get)(_this7, 'webex.config.device.ephemeral', false)) {
176
437
  webSocketUrl.query.multipleConnections = true;
177
438
  }
178
439
  webSocketUrl.query.clientTimestamp = (0, _now.default)();
179
440
  return _url.default.format(webSocketUrl);
180
441
  });
181
442
  },
182
- _attemptConnection: function _attemptConnection(socketUrl, callback) {
183
- var _this5 = this;
443
+ _attemptConnection: function _attemptConnection(socketUrl, sessionId, callback) {
444
+ var _this8 = this;
445
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
446
+ var _options$isShutdownSw = options.isShutdownSwitchover,
447
+ isShutdownSwitchover = _options$isShutdownSw === void 0 ? false : _options$isShutdownSw,
448
+ _options$onSuccess = options.onSuccess,
449
+ onSuccess = _options$onSuccess === void 0 ? null : _options$onSuccess;
184
450
  var socket = new _socket.default();
185
- var attemptWSUrl;
186
- socket.on('close', function () {
187
- return _this5._onclose.apply(_this5, arguments);
188
- });
189
- socket.on('message', function () {
190
- return _this5._onmessage.apply(_this5, arguments);
191
- });
192
- socket.on('pong', function () {
193
- return _this5._setTimeOffset.apply(_this5, arguments);
194
- });
195
- socket.on('sequence-mismatch', function () {
196
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
197
- args[_key] = arguments[_key];
198
- }
199
- return _this5._emit.apply(_this5, ['sequence-mismatch'].concat(args));
200
- });
201
- socket.on('ping-pong-latency', function () {
202
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
203
- args[_key2] = arguments[_key2];
204
- }
205
- return _this5._emit.apply(_this5, ['ping-pong-latency'].concat(args));
206
- });
207
- _promise.default.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(function (_ref) {
208
- var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
209
- webSocketUrl = _ref2[0],
210
- token = _ref2[1];
211
- if (!_this5.backoffCall) {
212
- var msg = "".concat(_this5.namespace, ": prevent socket open when backoffCall no longer defined");
213
- _this5.logger.info(msg);
214
- return _promise.default.reject(new Error(msg));
215
- }
216
- attemptWSUrl = webSocketUrl;
217
- var options = {
218
- forceCloseDelay: _this5.config.forceCloseDelay,
219
- pingInterval: _this5.config.pingInterval,
220
- pongTimeout: _this5.config.pongTimeout,
221
- token: token.toString(),
222
- trackingId: "".concat(_this5.webex.sessionId, "_").concat((0, _now.default)()),
223
- logger: _this5.logger
224
- };
451
+ socket.connecting = true;
452
+ var newWSUrl;
453
+ this._attachSocketEventListeners(socket, sessionId);
454
+ var backoffCall = isShutdownSwitchover ? this._shutdownSwitchoverBackoffCalls.get(sessionId) : this.backoffCalls.get(sessionId);
455
+
456
+ // Check appropriate backoff call based on connection type
457
+ if (isShutdownSwitchover && !backoffCall) {
458
+ var msg = "".concat(this.namespace, ": prevent socket open when switchover backoff call no longer defined for ").concat(sessionId);
459
+ var err = new Error(msg);
460
+ this.logger.info(msg);
461
+
462
+ // Call the callback with the error before rejecting
463
+ callback(err);
464
+ return _promise.default.reject(err);
465
+ }
466
+ if (!isShutdownSwitchover && !backoffCall) {
467
+ var _msg = "".concat(this.namespace, ": prevent socket open when backoffCall no longer defined for ").concat(sessionId);
468
+ var _err = new Error(_msg);
469
+ this.logger.info(_msg);
470
+
471
+ // Call the callback with the error before rejecting
472
+ callback(_err);
473
+ return _promise.default.reject(_err);
474
+ }
475
+
476
+ // For shutdown switchover, don't set socket yet (make-before-break)
477
+ // For normal connection, set socket before opening to allow disconnect() to close it
478
+ if (!isShutdownSwitchover) {
479
+ this.sockets.set(sessionId, socket);
480
+ }
481
+ return this._prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover).then(function (webSocketUrl) {
482
+ newWSUrl = webSocketUrl;
483
+ _this8.logger.info("".concat(_this8.namespace, ": ").concat(isShutdownSwitchover ? '[shutdown] switchover' : '', " connected to mercury, success, action: connected for ").concat(sessionId, ", url: ").concat(newWSUrl));
225
484
 
226
- // if the consumer has supplied request options use them
227
- if (_this5.webex.config.defaultMercuryOptions) {
228
- _this5.logger.info("".concat(_this5.namespace, ": setting custom options"));
229
- options = _objectSpread(_objectSpread({}, options), _this5.webex.config.defaultMercuryOptions);
485
+ // Custom success handler for shutdown switchover
486
+ if (onSuccess) {
487
+ onSuccess(socket, webSocketUrl);
488
+ callback();
489
+ return _promise.default.resolve();
230
490
  }
231
491
 
232
- // Set the socket before opening it. This allows a disconnect() to close
233
- // the socket if it is in the process of being opened.
234
- _this5.socket = socket;
235
- _this5.logger.info("".concat(_this5.namespace, " connection url: ").concat(webSocketUrl));
236
- return socket.open(webSocketUrl, options);
237
- }).then(function () {
238
- _this5.logger.info("".concat(_this5.namespace, ": connected to mercury, success, action: connected, url: ").concat(attemptWSUrl));
492
+ // Default behavior for normal connection
239
493
  callback();
240
- return _this5.webex.internal.feature.getFeature('developer', 'web-high-availability').then(function (haMessagingEnabled) {
494
+ return _this8.webex.internal.feature.getFeature('developer', 'web-high-availability').then(function (haMessagingEnabled) {
241
495
  if (haMessagingEnabled) {
242
- return _this5.webex.internal.device.refresh();
496
+ return _this8.webex.internal.device.refresh();
243
497
  }
244
498
  return _promise.default.resolve();
245
499
  });
246
500
  }).catch(function (reason) {
247
- var _this5$backoffCall, _this5$backoffCall3;
248
- _this5.lastError = reason; // remember the last error
501
+ // For shutdown, simpler error handling - just callback for retry
502
+ if (isShutdownSwitchover) {
503
+ _this8.logger.info("".concat(_this8.namespace, ": [shutdown] switchover attempt failed for ").concat(sessionId), reason);
504
+ return callback(reason);
505
+ }
506
+
507
+ // Normal connection error handling (existing complex logic)
508
+ _this8.lastError = reason; // remember the last error
249
509
 
510
+ var backoffCall = _this8.backoffCalls.get(sessionId);
250
511
  // Suppress connection errors that appear to be network related. This
251
512
  // may end up suppressing metrics during outages, but we might not care
252
513
  // (especially since many of our outages happen in a way that client
253
514
  // metrics can't be trusted).
254
- if (reason.code !== 1006 && _this5.backoffCall && ((_this5$backoffCall = _this5.backoffCall) === null || _this5$backoffCall === void 0 ? void 0 : _this5$backoffCall.getNumRetries()) > 0) {
255
- var _this5$backoffCall2;
256
- _this5._emit('connection_failed', reason, {
257
- retries: (_this5$backoffCall2 = _this5.backoffCall) === null || _this5$backoffCall2 === void 0 ? void 0 : _this5$backoffCall2.getNumRetries()
515
+ if (reason.code !== 1006 && backoffCall && (backoffCall === null || backoffCall === void 0 ? void 0 : backoffCall.getNumRetries()) > 0) {
516
+ _this8._emit(sessionId, 'connection_failed', reason, {
517
+ sessionId: sessionId,
518
+ retries: backoffCall === null || backoffCall === void 0 ? void 0 : backoffCall.getNumRetries()
258
519
  });
259
520
  }
260
- _this5.logger.info("".concat(_this5.namespace, ": connection attempt failed"), reason, ((_this5$backoffCall3 = _this5.backoffCall) === null || _this5$backoffCall3 === void 0 ? void 0 : _this5$backoffCall3.getNumRetries()) === 0 ? reason.stack : '');
521
+ _this8.logger.info("".concat(_this8.namespace, ": connection attempt failed for ").concat(sessionId), reason, (backoffCall === null || backoffCall === void 0 ? void 0 : backoffCall.getNumRetries()) === 0 ? reason.stack : '');
261
522
  // UnknownResponse is produced by IE for any 4XXX; treated it like a bad
262
523
  // web socket url and let WDM handle the token checking
263
524
  if (reason instanceof _errors.UnknownResponse) {
264
- _this5.logger.info("".concat(_this5.namespace, ": received unknown response code, refreshing device registration"));
265
- return _this5.webex.internal.device.refresh().then(function () {
525
+ _this8.logger.info("".concat(_this8.namespace, ": received unknown response code for ").concat(sessionId, ", refreshing device registration"));
526
+ return _this8.webex.internal.device.refresh().then(function () {
266
527
  return callback(reason);
267
528
  });
268
529
  }
269
530
  // NotAuthorized implies expired token
270
531
  if (reason instanceof _errors.NotAuthorized) {
271
- _this5.logger.info("".concat(_this5.namespace, ": received authorization error, reauthorizing"));
272
- return _this5.webex.credentials.refresh({
532
+ _this8.logger.info("".concat(_this8.namespace, ": received authorization error for ").concat(sessionId, ", reauthorizing"));
533
+ return _this8.webex.credentials.refresh({
273
534
  force: true
274
535
  }).then(function () {
275
536
  return callback(reason);
@@ -284,15 +545,15 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
284
545
  // BadRequest implies current credentials are for a Service Account
285
546
  // Forbidden implies current user is not entitle for Webex
286
547
  if (reason instanceof _errors.BadRequest || reason instanceof _errors.Forbidden) {
287
- _this5.logger.warn("".concat(_this5.namespace, ": received unrecoverable response from mercury"));
288
- _this5.backoffCall.abort();
548
+ _this8.logger.warn("".concat(_this8.namespace, ": received unrecoverable response from mercury for ").concat(sessionId));
549
+ backoffCall === null || backoffCall === void 0 ? void 0 : backoffCall.abort();
289
550
  return callback(reason);
290
551
  }
291
552
  if (reason instanceof _errors.ConnectionError) {
292
- return _this5.webex.internal.feature.getFeature('developer', 'web-high-availability').then(function (haMessagingEnabled) {
553
+ return _this8.webex.internal.feature.getFeature('developer', 'web-high-availability').then(function (haMessagingEnabled) {
293
554
  if (haMessagingEnabled) {
294
- _this5.logger.info("".concat(_this5.namespace, ": received a generic connection error, will try to connect to another datacenter. failed, action: 'failed', url: ").concat(attemptWSUrl, " error: ").concat(reason.message));
295
- return _this5.webex.internal.services.markFailedUrl(attemptWSUrl);
555
+ _this8.logger.info("".concat(_this8.namespace, ": received a generic connection error for ").concat(sessionId, ", will try to connect to another datacenter. failed, action: 'failed', url: ").concat(newWSUrl, " error: ").concat(reason.message));
556
+ return _this8.webex.internal.services.markFailedUrl(newWSUrl);
296
557
  }
297
558
  return null;
298
559
  }).then(function () {
@@ -301,73 +562,164 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
301
562
  }
302
563
  return callback(reason);
303
564
  }).catch(function (reason) {
304
- _this5.logger.error("".concat(_this5.namespace, ": failed to handle connection failure"), reason);
565
+ _this8.logger.error("".concat(_this8.namespace, ": failed to handle connection failure for ").concat(sessionId), reason);
305
566
  callback(reason);
306
567
  });
307
568
  },
308
- _connectWithBackoff: function _connectWithBackoff(webSocketUrl) {
309
- var _this6 = this;
569
+ _prepareAndOpenSocket: function _prepareAndOpenSocket(socket, socketUrl, sessionId) {
570
+ var _this9 = this;
571
+ var isShutdownSwitchover = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
572
+ var logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
573
+ return _promise.default.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(function (_ref) {
574
+ var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
575
+ webSocketUrl = _ref2[0],
576
+ token = _ref2[1];
577
+ var options = {
578
+ forceCloseDelay: _this9.config.forceCloseDelay,
579
+ pingInterval: _this9.config.pingInterval,
580
+ pongTimeout: _this9.config.pongTimeout,
581
+ token: token.toString(),
582
+ trackingId: "".concat(_this9.webex.sessionId, "_").concat((0, _now.default)()),
583
+ logger: _this9.logger
584
+ };
585
+ if (_this9.webex.config.defaultMercuryOptions) {
586
+ var customOptionsMsg = isShutdownSwitchover ? 'setting custom options for switchover' : 'setting custom options';
587
+ _this9.logger.info("".concat(_this9.namespace, ": ").concat(customOptionsMsg));
588
+ options = _objectSpread(_objectSpread({}, options), _this9.webex.config.defaultMercuryOptions);
589
+ }
590
+
591
+ // Set the socket before opening it. This allows a disconnect() to close
592
+ // the socket if it is in the process of being opened.
593
+ _this9.sockets.set(sessionId, socket);
594
+ _this9.socket = _this9.sockets.get(_this9.defaultSessionId) || socket;
595
+ _this9.logger.info("".concat(_this9.namespace, " ").concat(logPrefix, " url for ").concat(sessionId, ": ").concat(webSocketUrl));
596
+ return socket.open(webSocketUrl, options).then(function () {
597
+ return webSocketUrl;
598
+ });
599
+ });
600
+ },
601
+ _connectWithBackoff: function _connectWithBackoff(webSocketUrl, sessionId) {
602
+ var _this10 = this;
603
+ var context = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
604
+ var _context$isShutdownSw = context.isShutdownSwitchover,
605
+ isShutdownSwitchover = _context$isShutdownSw === void 0 ? false : _context$isShutdownSw,
606
+ _context$attemptOptio = context.attemptOptions,
607
+ attemptOptions = _context$attemptOptio === void 0 ? {} : _context$attemptOptio;
310
608
  return new _promise.default(function (resolve, reject) {
311
- // eslint gets confused about whether or not call is actually used
609
+ // eslint gets confused about whether call is actually used
312
610
  // eslint-disable-next-line prefer-const
313
611
  var call;
314
612
  var onComplete = function onComplete(err) {
315
- _this6.connecting = false;
316
- _this6.backoffCall = undefined;
613
+ var sid = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : sessionId;
614
+ if (isShutdownSwitchover) {
615
+ _this10._shutdownSwitchoverBackoffCalls.delete(sid);
616
+ } else {
617
+ _this10.backoffCalls.delete(sid);
618
+ }
317
619
  if (err) {
318
- _this6.logger.info("".concat(_this6.namespace, ": failed to connect after ").concat(call.getNumRetries(), " retries; log statement about next retry was inaccurate; ").concat(err));
620
+ var msg = isShutdownSwitchover ? "[shutdown] switchover failed after ".concat(call.getNumRetries(), " retries") : "failed to connect after ".concat(call.getNumRetries(), " retries");
621
+ _this10.logger.info("".concat(_this10.namespace, ": ").concat(msg, "; log statement about next retry was inaccurate; ").concat(err));
319
622
  return reject(err);
320
623
  }
321
- _this6.connected = true;
322
- _this6.hasEverConnected = true;
323
- _this6._emit('online');
324
- _this6.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
624
+ // Update overall connected status
625
+ var sessionSocket = _this10.sockets.get(sid);
626
+ if (sessionSocket) {
627
+ sessionSocket.connecting = false;
628
+ sessionSocket.connected = true;
629
+ }
630
+ // Default success handling for normal connections
631
+ if (!isShutdownSwitchover) {
632
+ _this10.connecting = _this10.hasConnectingSockets();
633
+ _this10.connected = _this10.hasConnectedSockets();
634
+ _this10.hasEverConnected = true;
635
+ _this10._emit(sid, 'online', {
636
+ sessionId: sid
637
+ });
638
+ _this10.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(true);
639
+ }
325
640
  return resolve();
326
641
  };
327
-
328
642
  // eslint-disable-next-line prefer-reflect
329
643
  call = _backoff.default.call(function (callback) {
330
- _this6.logger.info("".concat(_this6.namespace, ": executing connection attempt ").concat(call.getNumRetries()));
331
- _this6._attemptConnection(webSocketUrl, callback);
332
- }, onComplete);
644
+ var attemptNum = call.getNumRetries();
645
+ var logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
646
+ _this10.logger.info("".concat(_this10.namespace, ": executing ").concat(logPrefix, " attempt ").concat(attemptNum, " for ").concat(sessionId));
647
+ _this10._attemptConnection(webSocketUrl, sessionId, callback, attemptOptions);
648
+ }, function (err) {
649
+ return onComplete(err, sessionId);
650
+ });
333
651
  call.setStrategy(new _backoff.default.ExponentialStrategy({
334
- initialDelay: _this6.config.backoffTimeReset,
335
- maxDelay: _this6.config.backoffTimeMax
652
+ initialDelay: _this10.config.backoffTimeReset,
653
+ maxDelay: _this10.config.backoffTimeMax
336
654
  }));
337
- if (_this6.config.initialConnectionMaxRetries && !_this6.hasEverConnected) {
338
- call.failAfter(_this6.config.initialConnectionMaxRetries);
339
- } else if (_this6.config.maxRetries) {
340
- call.failAfter(_this6.config.maxRetries);
655
+ if (_this10.config.initialConnectionMaxRetries && !_this10.hasEverConnected && !isShutdownSwitchover) {
656
+ call.failAfter(_this10.config.initialConnectionMaxRetries);
657
+ } else if (_this10.config.maxRetries) {
658
+ call.failAfter(_this10.config.maxRetries);
659
+ }
660
+
661
+ // Store the call BEFORE setting up event handlers to prevent race conditions
662
+ // Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
663
+ if (isShutdownSwitchover) {
664
+ _this10._shutdownSwitchoverBackoffCalls.set(sessionId, call);
665
+ } else {
666
+ _this10.backoffCalls.set(sessionId, call);
341
667
  }
342
668
  call.on('abort', function () {
343
- _this6.logger.info("".concat(_this6.namespace, ": connection aborted"));
344
- reject(new Error('Mercury Connection Aborted'));
669
+ var msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
670
+ _this10.logger.info("".concat(_this10.namespace, ": ").concat(msg, " aborted for ").concat(sessionId));
671
+ reject(new Error("Mercury ".concat(msg, " Aborted for ").concat(sessionId)));
345
672
  });
346
673
  call.on('callback', function (err) {
347
674
  if (err) {
348
675
  var number = call.getNumRetries();
349
- var delay = Math.min(call.strategy_.nextBackoffDelay_, _this6.config.backoffTimeMax);
350
- _this6.logger.info("".concat(_this6.namespace, ": failed to connect; attempting retry ").concat(number + 1, " in ").concat(delay, " ms"));
676
+ var delay = Math.min(call.strategy_.nextBackoffDelay_, _this10.config.backoffTimeMax);
677
+ var logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
678
+ _this10.logger.info("".concat(_this10.namespace, ": ").concat(logPrefix, " failed to connect; attempting retry ").concat(number + 1, " in ").concat(delay, " ms for ").concat(sessionId));
351
679
  /* istanbul ignore if */
352
680
  if (process.env.NODE_ENV === 'development') {
353
- _this6.logger.debug("".concat(_this6.namespace, ": "), err, err.stack);
681
+ _this10.logger.debug("".concat(_this10.namespace, ": "), err, err.stack);
354
682
  }
355
683
  return;
356
684
  }
357
- _this6.logger.info("".concat(_this6.namespace, ": connected"));
685
+ _this10.logger.info("".concat(_this10.namespace, ": connected ").concat(sessionId));
358
686
  });
359
687
  call.start();
360
- _this6.backoffCall = call;
361
688
  });
362
689
  },
363
690
  _emit: function _emit() {
364
- for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
365
- args[_key3] = arguments[_key3];
691
+ for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
692
+ args[_key5] = arguments[_key5];
366
693
  }
367
694
  try {
368
- this.trigger.apply(this, args);
695
+ if (!args || args.length === 0) {
696
+ return;
697
+ }
698
+
699
+ // New signature: _emit(sessionId, eventName, ...rest)
700
+ // Backwards compatibility: if the first arg isn't a known sessionId (or defaultSessionId),
701
+ // treat the call as the old signature and forward directly to trigger(...)
702
+ var first = args[0],
703
+ second = args[1],
704
+ rest = args.slice(2);
705
+ if (typeof first === 'string' && (this.sockets.has(first) || first === this.defaultSessionId) && typeof second === 'string') {
706
+ var sessionId = first;
707
+ var eventName = second;
708
+ var suffix = sessionId === this.defaultSessionId ? '' : ":".concat(sessionId);
709
+ this.trigger.apply(this, ["".concat(eventName).concat(suffix)].concat((0, _toConsumableArray2.default)(rest)));
710
+ } else {
711
+ // Old usage: _emit(eventName, ...args)
712
+ this.trigger.apply(this, args);
713
+ }
369
714
  } catch (error) {
370
- this.logger.error("".concat(this.namespace, ": error occurred in event handler:"), error, ' with args: ', args);
715
+ // Safely handle errors without causing additional issues during cleanup
716
+ try {
717
+ this.logger.error("".concat(this.namespace, ": error occurred in event handler:"), error, ' with args: ', args);
718
+ } catch (logError) {
719
+ // If even logging fails, just ignore to prevent cascading errors during cleanup
720
+ // eslint-disable-next-line no-console
721
+ console.error('Mercury _emit error handling failed:', logError);
722
+ }
371
723
  }
372
724
  },
373
725
  _getEventHandlers: function _getEventHandlers(eventType) {
@@ -388,36 +740,77 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
388
740
  }
389
741
  return handlers;
390
742
  },
391
- _onclose: function _onclose(event) {
743
+ _onclose: function _onclose(sessionId, event, sourceSocket) {
392
744
  // I don't see any way to avoid the complexity or statement count in here.
393
745
  /* eslint complexity: [0] */
394
746
 
395
747
  try {
396
748
  var reason = event.reason && event.reason.toLowerCase();
397
- var socketUrl = this.socket.url;
398
- this.socket.removeAllListeners();
399
- this.unset('socket');
400
- this.connected = false;
401
- this._emit('offline', event);
402
- this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
749
+ var sessionSocket = this.sockets.get(sessionId);
750
+ var socketUrl;
751
+ event.sessionId = sessionId;
752
+ var isActiveSocket = sourceSocket === sessionSocket;
753
+ if (sourceSocket) {
754
+ socketUrl = sourceSocket.url;
755
+ }
756
+ this.sockets.delete(sessionId);
757
+ if (isActiveSocket) {
758
+ // Only tear down state if the currently active socket closed
759
+ if (sessionSocket) {
760
+ sessionSocket.removeAllListeners();
761
+ sessionSocket = null;
762
+ this._emit(sessionId, 'offline', event);
763
+ }
764
+ // Update overall connected status
765
+ this.connecting = this.hasConnectingSockets();
766
+ this.connected = this.hasConnectedSockets();
767
+ if (!this.connected) {
768
+ this.webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus(false);
769
+ }
770
+ } else {
771
+ // Old socket closed; do not flip connection state
772
+ this.logger.info("".concat(this.namespace, ": [shutdown] non-active socket closed, code=").concat(event.code, " for ").concat(sessionId));
773
+ // Clean up listeners from old socket now that it's closed
774
+ if (sourceSocket) {
775
+ sourceSocket.removeAllListeners();
776
+ }
777
+ }
403
778
  switch (event.code) {
404
779
  case 1003:
405
780
  // metric: disconnect
406
- this.logger.info("".concat(this.namespace, ": Mercury service rejected last message; will not reconnect: ").concat(event.reason));
407
- this._emit('offline.permanent', event);
781
+ this.logger.info("".concat(this.namespace, ": Mercury service rejected last message for ").concat(sessionId, "; will not reconnect: ").concat(event.reason));
782
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
408
783
  break;
409
784
  case 4000:
410
785
  // metric: disconnect
411
- this.logger.info("".concat(this.namespace, ": socket replaced; will not reconnect"));
412
- this._emit('offline.replaced', event);
786
+ this.logger.info("".concat(this.namespace, ": socket ").concat(sessionId, " replaced; will not reconnect"));
787
+ if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
788
+ // If not active, nothing to do
789
+ break;
790
+ case 4001:
791
+ // replaced during shutdown
792
+ if (isActiveSocket) {
793
+ // Server closed active socket with 4001, meaning it expected this connection
794
+ // to be replaced, but the switchover in _handleImminentShutdown failed.
795
+ // This is a permanent failure - do not reconnect.
796
+ this.logger.warn("".concat(this.namespace, ": active socket closed with 4001; shutdown switchover failed for ").concat(sessionId));
797
+ this._emit(sessionId, 'offline.permanent', event);
798
+ } else {
799
+ // Expected: old socket closed after successful switchover
800
+ this.logger.info("".concat(this.namespace, ": old socket closed with 4001 (replaced during shutdown); no reconnect needed for ").concat(sessionId));
801
+ this._emit(sessionId, 'offline.replaced', event);
802
+ }
413
803
  break;
414
804
  case 1001:
415
805
  case 1005:
416
806
  case 1006:
417
807
  case 1011:
418
- this.logger.info("".concat(this.namespace, ": socket disconnected; reconnecting"));
419
- this._emit('offline.transient', event);
420
- this._reconnect(socketUrl);
808
+ this.logger.info("".concat(this.namespace, ": socket ").concat(sessionId, " disconnected; reconnecting"));
809
+ if (isActiveSocket) {
810
+ this._emit(sessionId, 'offline.transient', event);
811
+ this.logger.info("".concat(this.namespace, ": [shutdown] reconnecting active socket to recover for ").concat(sessionId));
812
+ this._reconnect(socketUrl, sessionId);
813
+ }
421
814
  // metric: disconnect
422
815
  // if (code == 1011 && reason !== ping error) metric: unexpected disconnect
423
816
  break;
@@ -425,32 +818,45 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
425
818
  case 3050:
426
819
  // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
427
820
  if (normalReconnectReasons.includes(reason)) {
428
- this.logger.info("".concat(this.namespace, ": socket disconnected; reconnecting"));
429
- this._emit('offline.transient', event);
430
- this._reconnect(socketUrl);
821
+ this.logger.info("".concat(this.namespace, ": socket ").concat(sessionId, " disconnected; reconnecting"));
822
+ if (isActiveSocket) {
823
+ this._emit(sessionId, 'offline.transient', event);
824
+ this.logger.info("".concat(this.namespace, ": [shutdown] reconnecting due to normal close for ").concat(sessionId));
825
+ this._reconnect(socketUrl, sessionId);
826
+ }
431
827
  // metric: disconnect
432
828
  // if (reason === done forced) metric: force closure
433
829
  } else {
434
- this.logger.info("".concat(this.namespace, ": socket disconnected; will not reconnect: ").concat(event.reason));
435
- this._emit('offline.permanent', event);
830
+ this.logger.info("".concat(this.namespace, ": socket ").concat(sessionId, " disconnected; will not reconnect: ").concat(event.reason));
831
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
436
832
  }
437
833
  break;
438
834
  default:
439
- this.logger.info("".concat(this.namespace, ": socket disconnected unexpectedly; will not reconnect"));
835
+ this.logger.info("".concat(this.namespace, ": socket ").concat(sessionId, " disconnected unexpectedly; will not reconnect"));
440
836
  // unexpected disconnect
441
- this._emit('offline.permanent', event);
837
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
442
838
  }
443
839
  } catch (error) {
444
- this.logger.error("".concat(this.namespace, ": error occurred in close handler"), error);
840
+ this.logger.error("".concat(this.namespace, ": error occurred in close handler for ").concat(sessionId), error);
445
841
  }
446
842
  },
447
- _onmessage: function _onmessage(event) {
448
- var _this7 = this;
449
- this._setTimeOffset(event);
843
+ _onmessage: function _onmessage(sessionId, event) {
844
+ var _this11 = this;
845
+ this._setTimeOffset(sessionId, event);
450
846
  var envelope = event.data;
451
847
  if (process.env.ENABLE_MERCURY_LOGGING) {
452
- this.logger.debug("".concat(this.namespace, ": message envelope: "), envelope);
848
+ this.logger.debug("".concat(this.namespace, ": message envelope from ").concat(sessionId, ": "), envelope);
849
+ }
850
+ envelope.sessionId = sessionId;
851
+
852
+ // Handle shutdown message shape: { type: 'shutdown' }
853
+ if (envelope && envelope.type === 'shutdown') {
854
+ this.logger.info("".concat(this.namespace, ": [shutdown] imminent shutdown message received for ").concat(sessionId));
855
+ this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
856
+ this._handleImminentShutdown(sessionId);
857
+ return _promise.default.resolve();
453
858
  }
859
+ envelope.sessionId = sessionId;
454
860
  var data = envelope.data;
455
861
  this._applyOverrides(data);
456
862
  return this._getEventHandlers(data.eventType).reduce(function (promise, handler) {
@@ -458,37 +864,38 @@ var Mercury = _webexCore.WebexPlugin.extend((_dec = (0, _common.deprecated)('Mer
458
864
  var namespace = handler.namespace,
459
865
  name = handler.name;
460
866
  return new _promise.default(function (resolve) {
461
- return resolve((_this7.webex[namespace] || _this7.webex.internal[namespace])[name](data));
867
+ return resolve((_this11.webex[namespace] || _this11.webex.internal[namespace])[name](data));
462
868
  }).catch(function (reason) {
463
- return _this7.logger.error("".concat(_this7.namespace, ": error occurred in autowired event handler for ").concat(data.eventType), reason);
869
+ return _this11.logger.error("".concat(_this11.namespace, ": error occurred in autowired event handler for ").concat(data.eventType, " from ").concat(sessionId), reason);
464
870
  });
465
871
  });
466
872
  }, _promise.default.resolve()).then(function () {
467
- _this7._emit('event', event.data);
873
+ _this11._emit(sessionId, 'event', envelope);
468
874
  var _data$eventType$split = data.eventType.split('.'),
469
875
  _data$eventType$split2 = (0, _slicedToArray2.default)(_data$eventType$split, 1),
470
876
  namespace = _data$eventType$split2[0];
471
877
  if (namespace === data.eventType) {
472
- _this7._emit("event:".concat(namespace), envelope);
878
+ _this11._emit(sessionId, "event:".concat(namespace), envelope);
473
879
  } else {
474
- _this7._emit("event:".concat(namespace), envelope);
475
- _this7._emit("event:".concat(data.eventType), envelope);
880
+ _this11._emit(sessionId, "event:".concat(namespace), envelope);
881
+ _this11._emit(sessionId, "event:".concat(data.eventType), envelope);
476
882
  }
477
883
  }).catch(function (reason) {
478
- _this7.logger.error("".concat(_this7.namespace, ": error occurred processing socket message"), reason);
884
+ _this11.logger.error("".concat(_this11.namespace, ": error occurred processing socket message from ").concat(sessionId), reason);
479
885
  });
480
886
  },
481
- _setTimeOffset: function _setTimeOffset(event) {
887
+ _setTimeOffset: function _setTimeOffset(sessionId, event) {
482
888
  var wsWriteTimestamp = event.data.wsWriteTimestamp;
483
889
  if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
484
890
  this.mercuryTimeOffset = (0, _now.default)() - wsWriteTimestamp;
485
891
  }
486
892
  },
487
893
  _reconnect: function _reconnect(webSocketUrl) {
488
- this.logger.info("".concat(this.namespace, ": reconnecting"));
489
- return this.connect(webSocketUrl);
894
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
895
+ this.logger.info("".concat(this.namespace, ": reconnecting ").concat(sessionId));
896
+ return this.connect(webSocketUrl, sessionId);
490
897
  },
491
- version: "3.9.0"
492
- }, ((0, _applyDecoratedDescriptor2.default)(_obj, "connect", [_common.oneFlight], (0, _getOwnPropertyDescriptor.default)(_obj, "connect"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "disconnect", [_common.oneFlight], (0, _getOwnPropertyDescriptor.default)(_obj, "disconnect"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "listen", [_dec], (0, _getOwnPropertyDescriptor.default)(_obj, "listen"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "stopListening", [_dec2], (0, _getOwnPropertyDescriptor.default)(_obj, "stopListening"), _obj)), _obj)));
493
- var _default = exports.default = Mercury;
898
+ version: "3.10.0-multi-llms.1"
899
+ }, ((0, _applyDecoratedDescriptor2.default)(_obj, "listen", [_dec], (0, _getOwnPropertyDescriptor.default)(_obj, "listen"), _obj), (0, _applyDecoratedDescriptor2.default)(_obj, "stopListening", [_dec2], (0, _getOwnPropertyDescriptor.default)(_obj, "stopListening"), _obj)), _obj)));
900
+ var _default2 = exports.default = Mercury;
494
901
  //# sourceMappingURL=mercury.js.map