@webex/internal-plugin-mercury 3.11.0 → 3.12.0

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