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