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