@webex/calling 3.12.0-mobius-socket.9 → 3.12.0-mobius-socket.11

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.
Files changed (69) hide show
  1. package/dist/CallingClient/CallingClient.test.js +2 -2
  2. package/dist/CallingClient/CallingClient.test.js.map +1 -1
  3. package/dist/CallingClient/calling/call.test.js +1 -1
  4. package/dist/CallingClient/calling/call.test.js.map +1 -1
  5. package/dist/CallingClient/line/line.test.js +1 -1
  6. package/dist/CallingClient/line/line.test.js.map +1 -1
  7. package/dist/CallingClient/registration/register.test.js +1 -1
  8. package/dist/CallingClient/registration/register.test.js.map +1 -1
  9. package/dist/CallingClient/utils/request.js +2 -2
  10. package/dist/CallingClient/utils/request.js.map +1 -1
  11. package/dist/mobius-socket/config.js +61 -0
  12. package/dist/mobius-socket/config.js.map +1 -0
  13. package/dist/mobius-socket/errors.js +106 -0
  14. package/dist/mobius-socket/errors.js.map +1 -0
  15. package/dist/mobius-socket/index.js +101 -0
  16. package/dist/mobius-socket/index.js.map +1 -0
  17. package/dist/mobius-socket/mobius-socket-events.test.js +511 -0
  18. package/dist/mobius-socket/mobius-socket-events.test.js.map +1 -0
  19. package/dist/mobius-socket/mobius-socket.js +1191 -0
  20. package/dist/mobius-socket/mobius-socket.js.map +1 -0
  21. package/dist/mobius-socket/mobius-socket.test.js +2107 -0
  22. package/dist/mobius-socket/mobius-socket.test.js.map +1 -0
  23. package/dist/mobius-socket/socket/constants.js +20 -0
  24. package/dist/mobius-socket/socket/constants.js.map +1 -0
  25. package/dist/mobius-socket/socket/index.js +15 -0
  26. package/dist/mobius-socket/socket/index.js.map +1 -0
  27. package/dist/mobius-socket/socket/socket-base.js +632 -0
  28. package/dist/mobius-socket/socket/socket-base.js.map +1 -0
  29. package/dist/mobius-socket/socket/socket.js +19 -0
  30. package/dist/mobius-socket/socket/socket.js.map +1 -0
  31. package/dist/mobius-socket/socket/socket.shim.js +36 -0
  32. package/dist/mobius-socket/socket/socket.shim.js.map +1 -0
  33. package/dist/mobius-socket/socket.test.js +752 -0
  34. package/dist/mobius-socket/socket.test.js.map +1 -0
  35. package/dist/mobius-socket/test/mocha-helpers.js +23 -0
  36. package/dist/mobius-socket/test/mocha-helpers.js.map +1 -0
  37. package/dist/mobius-socket/test/promise-tick.js +29 -0
  38. package/dist/mobius-socket/test/promise-tick.js.map +1 -0
  39. package/dist/module/CallingClient/utils/request.js +1 -1
  40. package/dist/module/mobius-socket/config.js +18 -0
  41. package/dist/module/mobius-socket/errors.js +30 -0
  42. package/dist/module/mobius-socket/index.js +24 -0
  43. package/dist/module/mobius-socket/mobius-socket.js +761 -0
  44. package/dist/module/mobius-socket/socket/constants.js +10 -0
  45. package/dist/module/mobius-socket/socket/index.js +4 -0
  46. package/dist/module/mobius-socket/socket/socket-base.js +374 -0
  47. package/dist/module/mobius-socket/socket/socket.js +9 -0
  48. package/dist/module/mobius-socket/socket/socket.shim.js +24 -0
  49. package/dist/types/mobius-socket/config.d.ts +15 -0
  50. package/dist/types/mobius-socket/config.d.ts.map +1 -0
  51. package/dist/types/mobius-socket/errors.d.ts +13 -0
  52. package/dist/types/mobius-socket/errors.d.ts.map +1 -0
  53. package/dist/types/mobius-socket/index.d.ts +9 -0
  54. package/dist/types/mobius-socket/index.d.ts.map +1 -0
  55. package/dist/types/mobius-socket/mobius-socket.d.ts +62 -0
  56. package/dist/types/mobius-socket/mobius-socket.d.ts.map +1 -0
  57. package/dist/types/mobius-socket/socket/constants.d.ts +11 -0
  58. package/dist/types/mobius-socket/socket/constants.d.ts.map +1 -0
  59. package/dist/types/mobius-socket/socket/index.d.ts +2 -0
  60. package/dist/types/mobius-socket/socket/index.d.ts.map +1 -0
  61. package/dist/types/mobius-socket/socket/socket-base.d.ts +38 -0
  62. package/dist/types/mobius-socket/socket/socket-base.d.ts.map +1 -0
  63. package/dist/types/mobius-socket/socket/socket.d.ts +3 -0
  64. package/dist/types/mobius-socket/socket/socket.d.ts.map +1 -0
  65. package/dist/types/mobius-socket/socket/socket.shim.d.ts +3 -0
  66. package/dist/types/mobius-socket/socket/socket.shim.d.ts.map +1 -0
  67. package/package.json +17 -5
  68. package/src/mobius-socket/socket/socket.js +13 -0
  69. package/src/mobius-socket/socket/socket.shim.js +31 -0
@@ -0,0 +1,1191 @@
1
+ "use strict";
2
+
3
+ var _Reflect$construct = require("@babel/runtime-corejs2/core-js/reflect/construct");
4
+ var _Array$from = require("@babel/runtime-corejs2/core-js/array/from");
5
+ var _Symbol = require("@babel/runtime-corejs2/core-js/symbol");
6
+ var _Symbol$iterator = require("@babel/runtime-corejs2/core-js/symbol/iterator");
7
+ var _Array$isArray2 = require("@babel/runtime-corejs2/core-js/array/is-array");
8
+ var _Object$keys2 = require("@babel/runtime-corejs2/core-js/object/keys");
9
+ var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs2/core-js/object/get-own-property-symbols");
10
+ var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
11
+ var _Object$getOwnPropertyDescriptors = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptors");
12
+ var _Object$defineProperties = require("@babel/runtime-corejs2/core-js/object/define-properties");
13
+ var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
14
+ var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
15
+ _Object$defineProperty(exports, "__esModule", {
16
+ value: true
17
+ });
18
+ exports.default = void 0;
19
+ var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));
20
+ var _now = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/date/now"));
21
+ var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
22
+ var _isArray = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/array/is-array"));
23
+ var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
24
+ var _keys = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/keys"));
25
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
26
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/slicedToArray"));
27
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/typeof"));
28
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
29
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
30
+ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/possibleConstructorReturn"));
31
+ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/getPrototypeOf"));
32
+ var _get2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/get"));
33
+ var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/inherits"));
34
+ var _set2 = _interopRequireDefault(require("lodash/set"));
35
+ var _camelCase2 = _interopRequireDefault(require("lodash/camelCase"));
36
+ var _events = require("events");
37
+ var _backoff = _interopRequireDefault(require("backoff"));
38
+ var _socket = _interopRequireDefault(require("./socket"));
39
+ var _errors = require("./errors");
40
+ 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$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
41
+ 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$getOwnPropertyDescriptor(t, r)); }); } return e; }
42
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof _Symbol && r[_Symbol$iterator] || r["@@iterator"]; if (!t) { if (_Array$isArray2(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; } } }; }
43
+ 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; } }
44
+ 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; }
45
+ function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? _Reflect$construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
46
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
47
+ function _superPropGet(t, o, e, r) { var p = (0, _get2.default)((0, _getPrototypeOf2.default)(1 & r ? t.prototype : t), o, e); return 2 & r && "function" == typeof p ? function (t) { return p.apply(e, t); } : p; } /* eslint-disable require-jsdoc */ /*!
48
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file
49
+ */
50
+ var normalReconnectReasons = ['idle', 'done (forced)'];
51
+ var DEFAULT_MOBIUS_WEBSOCKET_SESSION = 'mobius-websocket-session';
52
+ var MOBIUS_SOCKET_NAMESPACE = 'MobiusSocket';
53
+ var TOKEN_REFRESH_INTERVAL_MS = 1 * 60 * 60 * 1000; // 1 hour
54
+
55
+ function normalizeMobiusAuthToken(token) {
56
+ if (typeof token !== 'string') {
57
+ return token;
58
+ }
59
+ return token.replace(/^Bearer\s+/i, '');
60
+ }
61
+ var MobiusSocket = /*#__PURE__*/function (_EventEmitter) {
62
+ function MobiusSocket(webex) {
63
+ var _this;
64
+ var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
65
+ (0, _classCallCheck2.default)(this, MobiusSocket);
66
+ _this = _callSuper(this, MobiusSocket);
67
+ if (!webex) {
68
+ throw new Error('A Webex instance is required when initializing MobiusSocket');
69
+ }
70
+ _this.webex = webex;
71
+ _this.config = config;
72
+ _this.logger = webex.logger || console;
73
+ _this.defaultSessionId = DEFAULT_MOBIUS_WEBSOCKET_SESSION;
74
+ _this.connected = false;
75
+ _this.connecting = false;
76
+ _this.hasEverConnected = false;
77
+ _this.socket = undefined;
78
+ _this.sockets = new _map.default();
79
+ _this.backoffCalls = new _map.default();
80
+ _this._shutdownSwitchoverBackoffCalls = new _map.default();
81
+ _this._seenAsyncEventIdsBySession = new _map.default();
82
+ _this._connectPromises = new _map.default();
83
+ _this.mercuryTimeOffset = undefined;
84
+ _this._tokenRefreshTimer = undefined;
85
+ _this._tokenRefreshInFlight = undefined;
86
+ _this._bindInternalEvents();
87
+ return _this;
88
+ }
89
+ (0, _inherits2.default)(MobiusSocket, _EventEmitter);
90
+ return (0, _createClass2.default)(MobiusSocket, [{
91
+ key: "off",
92
+ value: function off(eventName, listener) {
93
+ if (listener) {
94
+ return _superPropGet(MobiusSocket, "off", this, 3)([eventName, listener]);
95
+ }
96
+ this.removeAllListeners(eventName);
97
+ return this;
98
+ }
99
+ }, {
100
+ key: "_bindInternalEvents",
101
+ value: function _bindInternalEvents() {
102
+ var _this2 = this;
103
+ /*
104
+ When one of these legacy feature gets updated, this event would be triggered
105
+ * group-message-notifications
106
+ * mention-notifications
107
+ * thread-notifications
108
+ */
109
+ this.on('event:featureToggle_update', function (envelope) {
110
+ if (envelope && envelope.data) {
111
+ _this2.webex.internal.feature.updateFeature(envelope.data.featureToggle);
112
+ }
113
+ });
114
+ /*
115
+ * When Cluster Migrations, notify clients using ActiveClusterStatusEvent via mercury
116
+ * https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-002.html#wip-notifying-clients-of-cluster-migrations
117
+ * */
118
+ this.on('event:ActiveClusterStatusEvent', function (envelope) {
119
+ var _this2$webex$internal;
120
+ if (typeof ((_this2$webex$internal = _this2.webex.internal.services) === null || _this2$webex$internal === void 0 ? void 0 : _this2$webex$internal.switchActiveClusterIds) === 'function' && envelope && envelope.data) {
121
+ var _envelope$data;
122
+ _this2.webex.internal.services.switchActiveClusterIds((_envelope$data = envelope.data) === null || _envelope$data === void 0 ? void 0 : _envelope$data.activeClusters);
123
+ }
124
+ });
125
+ /*
126
+ * Using cache-invalidation via mercury to instead the method of polling via the new /timestamp endpoint from u2c
127
+ * https://wwwin-github.cisco.com/pages/Webex/crr-docs/techdocs/rr-005.html#websocket-notifications
128
+ * */
129
+ this.on('event:u2c.cache-invalidation', function (envelope) {
130
+ var _this2$webex$internal2;
131
+ if (typeof ((_this2$webex$internal2 = _this2.webex.internal.services) === null || _this2$webex$internal2 === void 0 ? void 0 : _this2$webex$internal2.invalidateCache) === 'function' && envelope && envelope.data) {
132
+ var _envelope$data2;
133
+ _this2.webex.internal.services.invalidateCache((_envelope$data2 = envelope.data) === null || _envelope$data2 === void 0 ? void 0 : _envelope$data2.timestamp);
134
+ }
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Attach event listeners to a socket.
140
+ * @param {Socket} socket - The socket to attach listeners to
141
+ * @param {sessionId} sessionId - The socket related session ID
142
+ * @returns {void}
143
+ */
144
+ }, {
145
+ key: "_attachSocketEventListeners",
146
+ value: function _attachSocketEventListeners(socket, sessionId) {
147
+ var _this3 = this;
148
+ socket.on('close', function (event) {
149
+ return _this3._onclose(sessionId, event, socket);
150
+ });
151
+ socket.on('message', function () {
152
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
153
+ args[_key] = arguments[_key];
154
+ }
155
+ return _this3._onmessage.apply(_this3, [sessionId].concat(args));
156
+ });
157
+ socket.on('pong', function () {
158
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
159
+ args[_key2] = arguments[_key2];
160
+ }
161
+ return _this3._setTimeOffset.apply(_this3, [sessionId].concat(args));
162
+ });
163
+ socket.on('sequence-mismatch', function () {
164
+ for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
165
+ args[_key3] = arguments[_key3];
166
+ }
167
+ return _this3._emit.apply(_this3, [sessionId, 'sequence-mismatch'].concat(args));
168
+ });
169
+ socket.on('ping-pong-latency', function () {
170
+ for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
171
+ args[_key4] = arguments[_key4];
172
+ }
173
+ return _this3._emit.apply(_this3, [sessionId, 'ping-pong-latency'].concat(args));
174
+ });
175
+ }
176
+
177
+ /**
178
+ * Returns the per-session cache of seen async_event IDs, creating it on first access.
179
+ * @param {string} sessionId - The session identifier.
180
+ * @returns {Map<string, boolean>} Ordered cache of seen event IDs for the session.
181
+ */
182
+ }, {
183
+ key: "_getSeenAsyncEventIds",
184
+ value: function _getSeenAsyncEventIds(sessionId) {
185
+ var seenAsyncEventIds = this._seenAsyncEventIdsBySession.get(sessionId);
186
+ if (!seenAsyncEventIds) {
187
+ seenAsyncEventIds = new _map.default();
188
+ this._seenAsyncEventIdsBySession.set(sessionId, seenAsyncEventIds);
189
+ }
190
+ return seenAsyncEventIds;
191
+ }
192
+
193
+ /**
194
+ * Clears the dedup cache for one session or for all sessions when omitted.
195
+ * @param {string} [sessionId] - Optional session identifier.
196
+ * @returns {void}
197
+ */
198
+ }, {
199
+ key: "_clearSeenAsyncEventIds",
200
+ value: function _clearSeenAsyncEventIds(sessionId) {
201
+ if (sessionId) {
202
+ this._seenAsyncEventIdsBySession.delete(sessionId);
203
+ return;
204
+ }
205
+ this._seenAsyncEventIdsBySession.clear();
206
+ }
207
+
208
+ /**
209
+ * Tracks a newly seen async_event ID and reports whether a duplicate should be suppressed.
210
+ * @param {string} sessionId - The session identifier.
211
+ * @param {object} envelope - Parsed websocket message envelope.
212
+ * @returns {boolean} True when the event has already been seen for this session.
213
+ */
214
+ }, {
215
+ key: "_trackAsyncEventAndShouldSuppressDuplicate",
216
+ value: function _trackAsyncEventAndShouldSuppressDuplicate(sessionId, envelope) {
217
+ if ((envelope === null || envelope === void 0 ? void 0 : envelope.type) !== 'async_event' || !envelope.eventId) {
218
+ return false;
219
+ }
220
+ var seenAsyncEventIds = this._getSeenAsyncEventIds(sessionId);
221
+ if (seenAsyncEventIds.has(envelope.eventId)) {
222
+ var previousValue = seenAsyncEventIds.get(envelope.eventId);
223
+
224
+ // Refresh recency so frequently retransmitted eventIds stay protected longer.
225
+ seenAsyncEventIds.delete(envelope.eventId);
226
+ seenAsyncEventIds.set(envelope.eventId, previousValue);
227
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": duplicate async_event suppressed for ").concat(sessionId, ", eventId=").concat(envelope.eventId));
228
+ return true;
229
+ }
230
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": tracking async_event for ").concat(sessionId, ", eventId=").concat(envelope.eventId));
231
+ seenAsyncEventIds.set(envelope.eventId, true);
232
+ if (seenAsyncEventIds.size > this.config.dedupCacheMaxSize) {
233
+ var oldestEventId = seenAsyncEventIds.keys().next().value;
234
+ seenAsyncEventIds.delete(oldestEventId);
235
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": evicted oldest async_event from dedup cache for ").concat(sessionId, ", eventId=").concat(oldestEventId));
236
+ }
237
+ return false;
238
+ }
239
+
240
+ /**
241
+ * Handle imminent shutdown by establishing a new connection while keeping
242
+ * the current one alive (make-before-break).
243
+ * Idempotent: will no-op if already in progress.
244
+ * @param {string} sessionId - The session ID for which the shutdown is imminent
245
+ * @returns {void}
246
+ */
247
+ }, {
248
+ key: "_handleImminentShutdown",
249
+ value: function _handleImminentShutdown(sessionId) {
250
+ var _this4 = this;
251
+ var oldSocket = this.sockets.get(sessionId);
252
+ try {
253
+ // Idempotent: if we already have a switchover backoff call for this session,
254
+ // a switchover is in progress – do nothing.
255
+ if (this._shutdownSwitchoverBackoffCalls.get(sessionId)) {
256
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover already in progress for ").concat(sessionId));
257
+ return;
258
+ }
259
+ var switchoverId = "".concat((0, _now.default)());
260
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover start, id=").concat(switchoverId, " for ").concat(sessionId));
261
+ this._connectWithBackoff(undefined, sessionId, {
262
+ isShutdownSwitchover: true,
263
+ attemptOptions: {
264
+ isShutdownSwitchover: true,
265
+ onSuccess: function onSuccess(newSocket, webSocketUrl) {
266
+ _this4.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover connected, url: ").concat(webSocketUrl, " for ").concat(sessionId));
267
+
268
+ // Atomically switch active socket reference
269
+ _this4.socket = _this4.sockets.get(_this4.defaultSessionId);
270
+ _this4.connected = _this4.hasConnectedSockets(); // remain connected throughout
271
+
272
+ _this4._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
273
+ url: webSocketUrl
274
+ });
275
+ if (oldSocket) {
276
+ _this4.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] old socket retained; server will close with 4001"));
277
+ }
278
+ }
279
+ }
280
+ }).then(function () {
281
+ _this4.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover completed successfully for ").concat(sessionId));
282
+ }).catch(function (err) {
283
+ _this4.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover exhausted retries; will fall back to normal reconnection for ").concat(sessionId, ": "), err);
284
+ _this4._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {
285
+ reason: err
286
+ });
287
+ // Old socket will eventually close with 4001, triggering normal reconnection
288
+ });
289
+ } catch (e) {
290
+ this.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] error during switchover for ").concat(sessionId), e);
291
+ this._shutdownSwitchoverBackoffCalls.delete(sessionId);
292
+ this._emit(sessionId, 'event:mercury_shutdown_switchover_failed', {
293
+ reason: e
294
+ });
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get the last error.
300
+ * @returns {any} The last error.
301
+ */
302
+ }, {
303
+ key: "getLastError",
304
+ value: function getLastError() {
305
+ return this.lastError;
306
+ }
307
+
308
+ /**
309
+ * Get all active socket connections
310
+ * @returns {Map} Map of sessionId to socket instances
311
+ */
312
+ }, {
313
+ key: "getSockets",
314
+ value: function getSockets() {
315
+ return this.sockets;
316
+ }
317
+
318
+ /**
319
+ * Get a specific socket by connection ID
320
+ * @param {string} sessionId - The connection identifier
321
+ * @returns {Socket|undefined} The socket instance or undefined if not found
322
+ */
323
+ }, {
324
+ key: "getSocket",
325
+ value: function getSocket() {
326
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultSessionId;
327
+ return this.sockets.get(sessionId);
328
+ }
329
+
330
+ /**
331
+ * Get the websocket URL for a currently connected session.
332
+ * @param {string} [sessionId=this.defaultSessionId] - The session identifier.
333
+ * @returns {string|undefined} The connected websocket URL, or undefined when not connected.
334
+ */
335
+ }, {
336
+ key: "getConnectedWebSocketUrl",
337
+ value: function getConnectedWebSocketUrl() {
338
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultSessionId;
339
+ var socket = this.getSocket(sessionId);
340
+ if (!(socket !== null && socket !== void 0 && socket.connected)) {
341
+ return undefined;
342
+ }
343
+ return socket.url;
344
+ }
345
+
346
+ /**
347
+ * Sends a payload on the active connected socket
348
+ * @param {Object} payload - The data to send
349
+ * @param {string} [sessionId=this.defaultSessionId] - The session identifier
350
+ * @returns {Promise}
351
+ */
352
+ }, {
353
+ key: "send",
354
+ value: function send(payload) {
355
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
356
+ var socket = this.getSocket(sessionId);
357
+ if (!socket || !socket.connected) {
358
+ return _promise.default.reject(new Error("Mobius socket is not connected for session ".concat(sessionId)));
359
+ }
360
+ return socket.send(payload);
361
+ }
362
+
363
+ /**
364
+ * Sends a websocket request and resolves when the matching response arrives.
365
+ * @param {Object} payload - The websocket request payload.
366
+ * @param {string|Object} [sessionIdOrOptions=this.defaultSessionId] - Session ID or request options.
367
+ * @param {Object} [options={}] - Additional request options.
368
+ * @returns {Promise<Object>}
369
+ */
370
+ }, {
371
+ key: "sendWssRequest",
372
+ value: function sendWssRequest(payload) {
373
+ var _this5 = this;
374
+ var sessionIdOrOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
375
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
376
+ if (!payload || (0, _typeof2.default)(payload) !== 'object' || (0, _isArray.default)(payload)) {
377
+ return _promise.default.reject(new Error('`payload` is required'));
378
+ }
379
+ var sessionId = this.defaultSessionId;
380
+ var requestOptions = options;
381
+ if (typeof sessionIdOrOptions === 'string') {
382
+ sessionId = sessionIdOrOptions;
383
+ } else if (sessionIdOrOptions && (0, _typeof2.default)(sessionIdOrOptions) === 'object') {
384
+ requestOptions = sessionIdOrOptions;
385
+ }
386
+ var socket = this.getSocket(sessionId);
387
+ if (!socket || !socket.connected) {
388
+ return _promise.default.reject(new Error("Mobius socket is not connected for session ".concat(sessionId)));
389
+ }
390
+ return socket.sendRequest(payload, {
391
+ timeout: requestOptions.timeout,
392
+ matchesResponse: function matchesResponse(response, request) {
393
+ return (response === null || response === void 0 ? void 0 : response.type) === 'response_event' && (response === null || response === void 0 ? void 0 : response.subtype) === request.type && (response === null || response === void 0 ? void 0 : response.trackingId) === request.trackingId;
394
+ },
395
+ getStatusCode: function getStatusCode(response) {
396
+ return response === null || response === void 0 ? void 0 : response.statusCode;
397
+ },
398
+ getStatusMessage: function getStatusMessage(response) {
399
+ return response === null || response === void 0 ? void 0 : response.statusMessage;
400
+ },
401
+ createError: function createError(response, statusCode, statusMessage) {
402
+ return _this5._createWssResponseError(response, statusCode, statusMessage);
403
+ },
404
+ createTimeoutError: function createTimeoutError(request) {
405
+ return _this5._createWssResponseError({
406
+ type: 'response_event',
407
+ subtype: request.type,
408
+ trackingId: request.trackingId
409
+ }, 408, 'Mobius websocket response timed out');
410
+ }
411
+ });
412
+ }
413
+
414
+ /**
415
+ * Check if the plugin is connected
416
+ * @returns {boolean} True if connected
417
+ */
418
+ }, {
419
+ key: "isConnected",
420
+ value: function isConnected() {
421
+ return this.connected;
422
+ }
423
+
424
+ /**
425
+ * Check if a socket is connected
426
+ * @param {string} [sessionId] - Optional session identifier
427
+ * @returns {boolean|undefined} True if the socket is connected
428
+ */
429
+ }, {
430
+ key: "hasConnectedSockets",
431
+ value: function hasConnectedSockets(sessionId) {
432
+ if (sessionId) {
433
+ var _this$sockets$get;
434
+ return Boolean((_this$sockets$get = this.sockets.get(sessionId)) === null || _this$sockets$get === void 0 ? void 0 : _this$sockets$get.connected);
435
+ }
436
+ var _iterator = _createForOfIteratorHelper(this.sockets.values()),
437
+ _step;
438
+ try {
439
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
440
+ var socket = _step.value;
441
+ if (socket !== null && socket !== void 0 && socket.connected) {
442
+ return true;
443
+ }
444
+ }
445
+ } catch (err) {
446
+ _iterator.e(err);
447
+ } finally {
448
+ _iterator.f();
449
+ }
450
+ return false;
451
+ }
452
+
453
+ /**
454
+ * Check if any sockets are connecting
455
+ * @param {string} [sessionId=this.defaultSessionId] - The session identifier
456
+ * @returns {boolean|undefined} True if the socket is connecting
457
+ */
458
+ }, {
459
+ key: "hasConnectingSockets",
460
+ value: function hasConnectingSockets() {
461
+ var sessionId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.defaultSessionId;
462
+ var socket = this.sockets.get(sessionId || this.defaultSessionId);
463
+ return Boolean(socket === null || socket === void 0 ? void 0 : socket.connecting);
464
+ }
465
+
466
+ /**
467
+ * Connect to Mobius for a specific session.
468
+ * @param {string} [webSocketUrl] - Optional websocket URL override. Falls back to the device websocket URL.
469
+ * @param {string} [sessionId=this.defaultSessionId] - The session identifier for this connection.
470
+ * @returns {Promise<void>} Resolves when connection flow completes for the session.
471
+ */
472
+ }, {
473
+ key: "connect",
474
+ value: function connect(webSocketUrl) {
475
+ var _this6 = this;
476
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
477
+ // First check if there's already a connection promise for this session
478
+ if (this._connectPromises.has(sessionId)) {
479
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": connection ").concat(sessionId, " already in progress, returning existing promise"));
480
+ return this._connectPromises.get(sessionId);
481
+ }
482
+ var sessionSocket = this.sockets.get(sessionId);
483
+ if (sessionSocket !== null && sessionSocket !== void 0 && sessionSocket.connected || sessionSocket !== null && sessionSocket !== void 0 && sessionSocket.connecting) {
484
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": connection ").concat(sessionId, " already connected, will not connect again"));
485
+ return _promise.default.resolve();
486
+ }
487
+
488
+ // Treat a connect() call that targets a different Mobius websocket URL
489
+ // as a fresh initial connection for retry purposes.
490
+ if (webSocketUrl && this.socketUrl && webSocketUrl !== this.socketUrl) {
491
+ this.hasEverConnected = false;
492
+ }
493
+
494
+ // Cache the caller-provided URL for reconnect
495
+ var resolvedUrl = webSocketUrl || this.socketUrl;
496
+ if (webSocketUrl) {
497
+ this.socketUrl = webSocketUrl;
498
+ }
499
+ this.connecting = true;
500
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": starting connection attempt for ").concat(sessionId).concat(Number(this.config.initialConnectionMaxRetries) === 0 && !this.hasEverConnected ? ' (initial retries disabled)' : ''));
501
+ var connectPromise = _promise.default.resolve(this.webex.internal.device.registered || this.webex.internal.device.register()).then(function () {
502
+ _this6.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": connecting ").concat(sessionId));
503
+ return _this6._connectWithBackoff(resolvedUrl, sessionId);
504
+ }).finally(function () {
505
+ _this6._connectPromises.delete(sessionId);
506
+ });
507
+ this._connectPromises.set(sessionId, connectPromise);
508
+ return connectPromise;
509
+ }
510
+ }, {
511
+ key: "logout",
512
+ value: function logout() {
513
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": logout() called"));
514
+ return this.disconnectAll(this.config.beforeLogoutOptionsCloseReason && !normalReconnectReasons.includes(this.config.beforeLogoutOptionsCloseReason) ? {
515
+ code: 3050,
516
+ reason: this.config.beforeLogoutOptionsCloseReason
517
+ } : undefined);
518
+ }
519
+
520
+ /**
521
+ * Disconnect a Mobius socket for a specific session.
522
+ * @param {object} [options] - Optional websocket close options (for example: `{code, reason}`).
523
+ * @param {string} [sessionId=this.defaultSessionId] - The session identifier to disconnect.
524
+ * @returns {Promise<void>} Resolves after disconnect cleanup and close handling are initiated/completed.
525
+ */
526
+ }, {
527
+ key: "disconnect",
528
+ value: function disconnect(options) {
529
+ var _this7 = this;
530
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
531
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, "#disconnect: connecting state: ").concat(this.connecting, ", connected state: ").concat(this.connected, ", socket exists: ").concat(!!this.socket, ", options: ").concat((0, _stringify.default)(options)));
532
+ var backoffCall = this.backoffCalls.get(sessionId);
533
+ if (backoffCall) {
534
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": aborting connection ").concat(sessionId));
535
+ backoffCall.abort();
536
+ this.backoffCalls.delete(sessionId);
537
+ }
538
+ var shutdownSwitchoverBackoffCall = this._shutdownSwitchoverBackoffCalls.get(sessionId);
539
+ if (shutdownSwitchoverBackoffCall) {
540
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": aborting shutdown switchover connection ").concat(sessionId));
541
+ shutdownSwitchoverBackoffCall.abort();
542
+ this._shutdownSwitchoverBackoffCalls.delete(sessionId);
543
+ }
544
+ // Clean up any pending connection promises
545
+ this._connectPromises.delete(sessionId);
546
+ var sessionSocket = this.sockets.get(sessionId);
547
+ this._clearSeenAsyncEventIds(sessionId);
548
+ if (!sessionSocket) {
549
+ this.connected = this.hasConnectedSockets();
550
+ if (!this.hasConnectedSockets()) {
551
+ this._stopTokenRefreshTimer();
552
+ }
553
+ return _promise.default.resolve();
554
+ }
555
+ sessionSocket.removeAllListeners('message');
556
+ sessionSocket.connecting = false;
557
+ sessionSocket.connected = false;
558
+ return _promise.default.resolve(sessionSocket.close(options || undefined)).finally(function () {
559
+ _this7.connected = _this7.hasConnectedSockets();
560
+ if (!_this7.hasConnectedSockets()) {
561
+ _this7._stopTokenRefreshTimer();
562
+ }
563
+ });
564
+ }
565
+
566
+ /**
567
+ * Disconnect all socket connections
568
+ * @param {object} options - Close options
569
+ * @returns {Promise} Promise that resolves when all connections are closed
570
+ */
571
+ }, {
572
+ key: "disconnectAll",
573
+ value: function disconnectAll(options) {
574
+ var _this8 = this;
575
+ var disconnectPromises = [];
576
+ var _iterator2 = _createForOfIteratorHelper(this.sockets.keys()),
577
+ _step2;
578
+ try {
579
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
580
+ var sessionId = _step2.value;
581
+ disconnectPromises.push(this.disconnect(options, sessionId));
582
+ }
583
+ } catch (err) {
584
+ _iterator2.e(err);
585
+ } finally {
586
+ _iterator2.f();
587
+ }
588
+ return _promise.default.all(disconnectPromises).then(function () {
589
+ _this8.connected = false;
590
+ _this8.socket = undefined;
591
+ _this8.sockets.clear();
592
+ _this8.backoffCalls.clear();
593
+ _this8._shutdownSwitchoverBackoffCalls.clear();
594
+ _this8._clearSeenAsyncEventIds();
595
+ _this8._stopTokenRefreshTimer();
596
+ _this8._connectPromises.clear();
597
+ });
598
+ }
599
+ }, {
600
+ key: "processRegistrationStatusEvent",
601
+ value: function processRegistrationStatusEvent(message) {
602
+ this.localClusterServiceUrls = message.localClusterServiceUrls;
603
+ }
604
+ }, {
605
+ key: "_createWssResponseError",
606
+ value: function _createWssResponseError(response, statusCode, statusMessage) {
607
+ var error = new Error(statusMessage || "Mobius websocket request failed with status ".concat(statusCode || 'unknown'));
608
+ error.name = 'MobiusSocketResponseError';
609
+ error.statusCode = statusCode;
610
+ error.statusMessage = statusMessage;
611
+ error.response = response;
612
+ error.trackingId = response === null || response === void 0 ? void 0 : response.trackingId;
613
+ return error;
614
+ }
615
+ }, {
616
+ key: "_applyOverrides",
617
+ value: function _applyOverrides(event) {
618
+ if (!event || !event.headers) {
619
+ return;
620
+ }
621
+ var headerKeys = (0, _keys.default)(event.headers);
622
+ headerKeys.forEach(function (keyPath) {
623
+ (0, _set2.default)(event, keyPath, event.headers[keyPath]);
624
+ });
625
+ }
626
+ }, {
627
+ key: "_prepareUrl",
628
+ value: function _prepareUrl(webSocketUrl) {
629
+ if (!webSocketUrl) {
630
+ webSocketUrl = this.webex.internal.device.webSocketUrl;
631
+ }
632
+
633
+ // TODO: Validate the host against the service catalog
634
+ // const hostFromUrl = url.parse(webSocketUrl, true)?.host;
635
+ // const isValidHost = this.webex.internal.services.isValidHost(hostFromUrl);
636
+ // if (!isValidHost) {
637
+ // this.logger.error(
638
+ // `${MOBIUS_SOCKET_NAMESPACE}: host ${hostFromUrl} is not a valid host from host catalog`
639
+ // );
640
+ // return Promise.resolve('');
641
+ // }
642
+
643
+ return _promise.default.resolve(webSocketUrl);
644
+ }
645
+ }, {
646
+ key: "_attemptConnection",
647
+ value: function _attemptConnection(socketUrl, sessionId, callback) {
648
+ var _this9 = this;
649
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
650
+ var _options$isShutdownSw = options.isShutdownSwitchover,
651
+ isShutdownSwitchover = _options$isShutdownSw === void 0 ? false : _options$isShutdownSw,
652
+ _options$onSuccess = options.onSuccess,
653
+ onSuccess = _options$onSuccess === void 0 ? null : _options$onSuccess;
654
+ var socket = new _socket.default();
655
+ socket.connecting = true;
656
+ var newWSUrl;
657
+ this._attachSocketEventListeners(socket, sessionId);
658
+ var backoffCall = isShutdownSwitchover ? this._shutdownSwitchoverBackoffCalls.get(sessionId) : this.backoffCalls.get(sessionId);
659
+
660
+ // Check appropriate backoff call based on connection type
661
+ if (!backoffCall) {
662
+ var mode = isShutdownSwitchover ? 'switchover backoff call' : 'backoffCall';
663
+ var msg = "".concat(MOBIUS_SOCKET_NAMESPACE, ": prevent socket open when ").concat(mode, " no longer defined for ").concat(sessionId);
664
+ var err = new Error(msg);
665
+ this.logger.info(msg);
666
+
667
+ // Call the callback with the error before rejecting
668
+ callback(err);
669
+ return _promise.default.reject(err);
670
+ }
671
+
672
+ // For shutdown switchover, don't set socket yet (make-before-break)
673
+ // For normal connection, set socket before opening to allow disconnect() to close it
674
+ if (!isShutdownSwitchover) {
675
+ this.sockets.set(sessionId, socket);
676
+ }
677
+ return this._prepareAndOpenSocket(socket, socketUrl, sessionId, isShutdownSwitchover).then(function (webSocketUrl) {
678
+ newWSUrl = webSocketUrl;
679
+ _this9.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": ").concat(isShutdownSwitchover ? '[shutdown] switchover' : '', " connected to mobius socket, success, action: connected for ").concat(sessionId, ", url: ").concat(newWSUrl));
680
+
681
+ // Custom success handler for shutdown switchover
682
+ if (onSuccess) {
683
+ onSuccess(socket, webSocketUrl);
684
+ callback();
685
+ return _promise.default.resolve();
686
+ }
687
+
688
+ // Default behavior for normal connection
689
+ callback();
690
+ return _promise.default.resolve();
691
+ }).catch(function (reason) {
692
+ // For shutdown, simpler error handling - just callback for retry
693
+ if (isShutdownSwitchover) {
694
+ _this9.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] switchover attempt failed for ").concat(sessionId), reason);
695
+ return callback(reason);
696
+ }
697
+
698
+ // Normal connection error handling (existing complex logic)
699
+ _this9.lastError = reason; // remember the last error
700
+
701
+ var backoffCallNormal = _this9.backoffCalls.get(sessionId);
702
+ // Suppress connection errors that appear to be network related. This
703
+ // may end up suppressing metrics during outages, but we might not care
704
+ // (especially since many of our outages happen in a way that client
705
+ // metrics can't be trusted).
706
+ if (reason.code !== 1006 && backoffCallNormal && (backoffCallNormal === null || backoffCallNormal === void 0 ? void 0 : backoffCallNormal.getNumRetries()) > 0) {
707
+ _this9._emit(sessionId, 'connection_failed', reason, {
708
+ sessionId: sessionId,
709
+ retries: backoffCallNormal === null || backoffCallNormal === void 0 ? void 0 : backoffCallNormal.getNumRetries()
710
+ });
711
+ }
712
+ _this9.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": connection attempt failed for ").concat(sessionId), reason, (backoffCallNormal === null || backoffCallNormal === void 0 ? void 0 : backoffCallNormal.getNumRetries()) === 0 ? reason.stack : '');
713
+ // UnknownResponse is produced by IE for any 4XXX; treated it like a bad
714
+ // web socket url and let WDM handle the token checking
715
+ if (reason instanceof _errors.UnknownResponse) {
716
+ _this9.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": received unknown response code for ").concat(sessionId, ", refreshing device registration"));
717
+ return _this9.webex.internal.device.refresh().then(function () {
718
+ return callback(reason);
719
+ });
720
+ }
721
+ // NotAuthorized implies expired token
722
+ if (reason instanceof _errors.NotAuthorized) {
723
+ _this9.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": received authorization error for ").concat(sessionId, ", reauthorizing"));
724
+ return _this9.webex.credentials.refresh({
725
+ force: true
726
+ }).then(function () {
727
+ return callback(reason);
728
+ });
729
+ }
730
+ // // NotFound implies expired web socket url
731
+ // else if (reason instanceof NotFound) {
732
+ // this.logger.info(`mercury: received not found error, refreshing device registration`);
733
+ // return this.webex.internal.device.refresh()
734
+ // .then(() => callback(reason));
735
+ // }
736
+ // BadRequest implies current credentials are for a Service Account
737
+ // Forbidden implies current user is not entitled for Webex
738
+ if (reason instanceof _errors.BadRequest || reason instanceof _errors.Forbidden) {
739
+ _this9.logger.warn("".concat(MOBIUS_SOCKET_NAMESPACE, ": received unrecoverable response from ").concat(MOBIUS_SOCKET_NAMESPACE, " for ").concat(sessionId));
740
+ backoffCallNormal === null || backoffCallNormal === void 0 ? void 0 : backoffCallNormal.abort();
741
+ return callback(reason);
742
+ }
743
+ return callback(reason);
744
+ }).catch(function (reason) {
745
+ _this9.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": failed to handle connection failure for ").concat(sessionId), reason);
746
+ callback(reason);
747
+ });
748
+ }
749
+ }, {
750
+ key: "_prepareAndOpenSocket",
751
+ value: function _prepareAndOpenSocket(socket, socketUrl, sessionId) {
752
+ var _this0 = this;
753
+ var isShutdownSwitchover = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
754
+ var logPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
755
+ return _promise.default.all([this._prepareUrl(socketUrl), this.webex.credentials.getUserToken()]).then(function (_ref) {
756
+ var _ref2 = (0, _slicedToArray2.default)(_ref, 2),
757
+ webSocketUrl = _ref2[0],
758
+ token = _ref2[1];
759
+ var options = {
760
+ forceCloseDelay: _this0.config.forceCloseDelay,
761
+ wssResponseTimeout: _this0.config.wssResponseTimeout,
762
+ skipAckEventId: _this0.config.skipAckEventId,
763
+ skipAckEventType: _this0.config.skipAckEventType,
764
+ token: normalizeMobiusAuthToken(token.toString()),
765
+ refreshToken: function refreshToken() {
766
+ return _this0._refreshToken();
767
+ },
768
+ trackingId: "".concat(_this0.webex.sessionId, "_").concat((0, _now.default)()),
769
+ logger: _this0.logger
770
+ };
771
+ if (_this0.webex.config.defaultMobiusSocketOptions) {
772
+ var customOptionsMsg = isShutdownSwitchover ? 'setting custom options for switchover' : 'setting custom options';
773
+ _this0.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": ").concat(customOptionsMsg));
774
+ options = _objectSpread(_objectSpread({}, options), _this0.webex.config.defaultMobiusSocketOptions);
775
+ }
776
+
777
+ // Set the socket before opening it. This allows a disconnect() to close
778
+ // the socket if it is in the process of being opened.
779
+ _this0.sockets.set(sessionId, socket);
780
+ _this0.socket = _this0.sockets.get(_this0.defaultSessionId);
781
+ _this0.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, " ").concat(logPrefix, " url for ").concat(sessionId, ": ").concat(webSocketUrl));
782
+ return socket.open(webSocketUrl, options).then(function () {
783
+ return webSocketUrl;
784
+ });
785
+ });
786
+ }
787
+ }, {
788
+ key: "_connectWithBackoff",
789
+ value: function _connectWithBackoff(webSocketUrl, sessionId) {
790
+ var _this1 = this;
791
+ var context = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
792
+ var _context$isShutdownSw = context.isShutdownSwitchover,
793
+ isShutdownSwitchover = _context$isShutdownSw === void 0 ? false : _context$isShutdownSw,
794
+ _context$attemptOptio = context.attemptOptions,
795
+ attemptOptions = _context$attemptOptio === void 0 ? {} : _context$attemptOptio;
796
+ return new _promise.default(function (resolve, reject) {
797
+ // eslint gets confused about whether call is actually used
798
+ // eslint-disable-next-line prefer-const
799
+ var call;
800
+ var isInitialConnect = !isShutdownSwitchover && !_this1.hasEverConnected;
801
+ var initialRetryLimit = _this1.config.initialConnectionMaxRetries == null ? null : Number(_this1.config.initialConnectionMaxRetries);
802
+ var isInitialConnectWithoutRetries = isInitialConnect && initialRetryLimit === 0;
803
+ var onComplete = function onComplete(err) {
804
+ var sid = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : sessionId;
805
+ if (isShutdownSwitchover) {
806
+ _this1._shutdownSwitchoverBackoffCalls.delete(sid);
807
+ } else {
808
+ _this1.backoffCalls.delete(sid);
809
+ }
810
+ var sessionSocket = _this1.sockets.get(sid);
811
+ if (err) {
812
+ var msg = isShutdownSwitchover ? "[shutdown] switchover failed after ".concat(call.getNumRetries(), " retries") : "failed to connect after ".concat(call.getNumRetries(), " retries");
813
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": ").concat(msg, "; log statement about next retry was inaccurate; ").concat(err));
814
+ if (sessionSocket) {
815
+ sessionSocket.connecting = false;
816
+ sessionSocket.connected = false;
817
+ }
818
+ return reject(err);
819
+ }
820
+
821
+ // Update overall connected status
822
+ if (sessionSocket) {
823
+ sessionSocket.connecting = false;
824
+ sessionSocket.connected = true;
825
+ }
826
+ // Default success handling for normal connections
827
+ if (!isShutdownSwitchover) {
828
+ _this1.connecting = _this1.hasConnectingSockets();
829
+ _this1.connected = _this1.hasConnectedSockets();
830
+ _this1.hasEverConnected = true;
831
+ _this1._startTokenRefreshTimer();
832
+ _this1._emit(sid, 'online');
833
+ }
834
+ return resolve();
835
+ };
836
+ // eslint-disable-next-line prefer-reflect
837
+ call = _backoff.default.call(function (callback) {
838
+ var attemptNum = call.getNumRetries();
839
+ var attemptLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : 'connection';
840
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": executing ").concat(attemptLogPrefix, " attempt ").concat(attemptNum, " for ").concat(sessionId));
841
+ _this1._attemptConnection(webSocketUrl, sessionId, callback, attemptOptions);
842
+ }, function (err) {
843
+ return onComplete(err, sessionId);
844
+ });
845
+ call.setStrategy(new _backoff.default.ExponentialStrategy({
846
+ initialDelay: _this1.config.backoffTimeReset,
847
+ maxDelay: _this1.config.backoffTimeMax
848
+ }));
849
+ if (isInitialConnectWithoutRetries) {
850
+ call.retryIf(function () {
851
+ return false;
852
+ });
853
+ } else if (isInitialConnect && initialRetryLimit > 0) {
854
+ call.failAfter(initialRetryLimit);
855
+ } else if (_this1.config.maxRetries) {
856
+ call.failAfter(_this1.config.maxRetries);
857
+ }
858
+
859
+ // Store the call BEFORE setting up event handlers to prevent race conditions
860
+ // Store backoff call reference BEFORE starting (so it's available in _attemptConnection)
861
+ if (isShutdownSwitchover) {
862
+ _this1._shutdownSwitchoverBackoffCalls.set(sessionId, call);
863
+ } else {
864
+ _this1.backoffCalls.set(sessionId, call);
865
+ }
866
+ call.on('abort', function () {
867
+ var msg = isShutdownSwitchover ? 'Shutdown Switchover' : 'Connection';
868
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": ").concat(msg, " aborted for ").concat(sessionId));
869
+ reject(new Error("MobiusSocket ".concat(msg, " Aborted for ").concat(sessionId)));
870
+ });
871
+ call.on('callback', function (err) {
872
+ if (err) {
873
+ if (isInitialConnectWithoutRetries) {
874
+ // retryIf(() => false) already disabled retries for this initial connect;
875
+ // this branch only avoids logging the generic "attempting retry" message.
876
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": initial connect failed for ").concat(sessionId, "; retries already disabled"));
877
+ return;
878
+ }
879
+ var number = call.getNumRetries();
880
+ var delay = Math.min(call.strategy_.nextBackoffDelay_, _this1.config.backoffTimeMax);
881
+ var callbackLogPrefix = isShutdownSwitchover ? '[shutdown] switchover' : '';
882
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": ").concat(callbackLogPrefix, " failed to connect; attempting retry ").concat(number + 1, " in ").concat(delay, " ms for ").concat(sessionId));
883
+ /* istanbul ignore if */
884
+ if (process.env.NODE_ENV === 'development') {
885
+ _this1.logger.debug("".concat(MOBIUS_SOCKET_NAMESPACE, ": "), err, err.stack);
886
+ }
887
+ return;
888
+ }
889
+ _this1.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": connected ").concat(sessionId));
890
+ });
891
+ call.start();
892
+ });
893
+ }
894
+ }, {
895
+ key: "_emit",
896
+ value: function _emit(sessionId, eventName) {
897
+ for (var _len5 = arguments.length, args = new Array(_len5 > 2 ? _len5 - 2 : 0), _key5 = 2; _key5 < _len5; _key5++) {
898
+ args[_key5 - 2] = arguments[_key5];
899
+ }
900
+ try {
901
+ if (!sessionId || !eventName) {
902
+ return;
903
+ }
904
+ var suffix = sessionId === this.defaultSessionId ? '' : ":".concat(sessionId);
905
+ this.emit.apply(this, ["".concat(eventName).concat(suffix)].concat(args));
906
+ } catch (error) {
907
+ // Safely handle errors without causing additional issues during cleanup
908
+ try {
909
+ this.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": error occurred in event handler:"), error, ' with args: ', [sessionId, eventName].concat(args));
910
+ } catch (logError) {
911
+ // If even logging fails, just ignore to prevent cascading errors during cleanup
912
+ // eslint-disable-next-line no-console
913
+ console.error('MobiusSocket _emit error handling failed:', logError);
914
+ }
915
+ }
916
+ }
917
+ }, {
918
+ key: "_getEventHandlers",
919
+ value: function _getEventHandlers(eventType) {
920
+ if (!eventType) {
921
+ return [];
922
+ }
923
+ var _eventType$split = eventType.split('.'),
924
+ _eventType$split2 = (0, _slicedToArray2.default)(_eventType$split, 2),
925
+ namespace = _eventType$split2[0],
926
+ name = _eventType$split2[1];
927
+ var handlers = [];
928
+ if (!this.webex[namespace] && !this.webex.internal[namespace]) {
929
+ return handlers;
930
+ }
931
+ var handlerName = (0, _camelCase2.default)("process_".concat(name, "_event"));
932
+ if ((this.webex[namespace] || this.webex.internal[namespace])[handlerName]) {
933
+ handlers.push({
934
+ name: handlerName,
935
+ namespace: namespace
936
+ });
937
+ }
938
+ return handlers;
939
+ }
940
+ }, {
941
+ key: "_startTokenRefreshTimer",
942
+ value: function _startTokenRefreshTimer() {
943
+ var _this10 = this;
944
+ if (this._tokenRefreshTimer || !this.hasConnectedSockets()) {
945
+ return;
946
+ }
947
+ this._tokenRefreshTimer = setInterval(function () {
948
+ _this10._refreshToken().catch(function (error) {
949
+ _this10.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": periodic token refresh failed"), error);
950
+ });
951
+ }, TOKEN_REFRESH_INTERVAL_MS);
952
+ }
953
+ }, {
954
+ key: "_stopTokenRefreshTimer",
955
+ value: function _stopTokenRefreshTimer() {
956
+ if (!this._tokenRefreshTimer) {
957
+ return;
958
+ }
959
+ clearInterval(this._tokenRefreshTimer);
960
+ this._tokenRefreshTimer = undefined;
961
+ }
962
+ }, {
963
+ key: "_refreshToken",
964
+ value: function _refreshToken() {
965
+ var _this11 = this;
966
+ if (this._tokenRefreshInFlight) {
967
+ return this._tokenRefreshInFlight;
968
+ }
969
+ if (!this.hasConnectedSockets()) {
970
+ this._stopTokenRefreshTimer();
971
+ return _promise.default.resolve();
972
+ }
973
+ var tokenPromise = this.webex.credentials.canRefresh ? this.webex.credentials.refresh({
974
+ force: true
975
+ }).then(function () {
976
+ return _this11.webex.credentials.getUserToken();
977
+ }) : this.webex.credentials.getUserToken();
978
+ this._tokenRefreshInFlight = tokenPromise.then(function (token) {
979
+ if (!token) {
980
+ throw new Error('Mobius token refresh did not return a token');
981
+ }
982
+ var refreshedToken = normalizeMobiusAuthToken(token.toString());
983
+ var authPayloadPromises = [];
984
+ var _iterator3 = _createForOfIteratorHelper(_this11.sockets.values()),
985
+ _step3;
986
+ try {
987
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
988
+ var socket = _step3.value;
989
+ if (socket !== null && socket !== void 0 && socket.connected) {
990
+ authPayloadPromises.push(socket.refresh(refreshedToken));
991
+ }
992
+ }
993
+ } catch (err) {
994
+ _iterator3.e(err);
995
+ } finally {
996
+ _iterator3.f();
997
+ }
998
+ return _promise.default.all(authPayloadPromises);
999
+ }).catch(function (error) {
1000
+ _this11.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": failed to refresh/re-auth Mobius sockets"), error);
1001
+ throw error;
1002
+ }).finally(function () {
1003
+ _this11._tokenRefreshInFlight = undefined;
1004
+ });
1005
+ return this._tokenRefreshInFlight;
1006
+ }
1007
+ }, {
1008
+ key: "_onclose",
1009
+ value: function _onclose(sessionId, event, sourceSocket) {
1010
+ // I don't see any way to avoid the complexity or statement count in here.
1011
+ /* eslint complexity: [0] */
1012
+
1013
+ try {
1014
+ var reason = event.reason && event.reason.toLowerCase();
1015
+ var sessionSocket = this.sockets.get(sessionId);
1016
+ var socketUrl;
1017
+ event.sessionId = sessionId;
1018
+ var isActiveSocket = sourceSocket === sessionSocket;
1019
+ if (sourceSocket) {
1020
+ socketUrl = sourceSocket.url;
1021
+ }
1022
+ this.sockets.delete(sessionId);
1023
+ if (isActiveSocket) {
1024
+ // Only tear down state if the currently active socket closed
1025
+ if (sessionSocket) {
1026
+ sessionSocket.removeAllListeners();
1027
+ if (sessionId === this.defaultSessionId) {
1028
+ this.socket = undefined;
1029
+ }
1030
+ this._emit(sessionId, 'offline', event);
1031
+ }
1032
+ // Update overall connected status
1033
+ this.connecting = this.hasConnectingSockets();
1034
+ this.connected = this.hasConnectedSockets();
1035
+ if (!this.hasConnectedSockets()) {
1036
+ this._stopTokenRefreshTimer();
1037
+ }
1038
+ } else {
1039
+ // Old socket closed; do not flip connection state
1040
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] non-active socket closed, code=").concat(event.code, " for ").concat(sessionId));
1041
+ // Clean up listeners from old socket now that it's closed
1042
+ if (sourceSocket) {
1043
+ sourceSocket.removeAllListeners();
1044
+ }
1045
+ }
1046
+ switch (event.code) {
1047
+ case 1003:
1048
+ // metric: disconnect
1049
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": service rejected last message for ").concat(sessionId, "; will not reconnect: ").concat(event.reason));
1050
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
1051
+ break;
1052
+ case 4000:
1053
+ // metric: disconnect
1054
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": socket ").concat(sessionId, " replaced; will not reconnect"));
1055
+ if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
1056
+ // If not active, nothing to do
1057
+ break;
1058
+ case 4001:
1059
+ // replaced during shutdown
1060
+ if (isActiveSocket) {
1061
+ // Server closed active socket with 4001, meaning it expected this connection
1062
+ // to be replaced, but the switchover in _handleImminentShutdown failed.
1063
+ // This is a permanent failure - do not reconnect.
1064
+ this.logger.warn("".concat(MOBIUS_SOCKET_NAMESPACE, ": active socket closed with 4001; shutdown switchover failed for ").concat(sessionId));
1065
+ this._emit(sessionId, 'offline.permanent', event);
1066
+ } else {
1067
+ // Expected: old socket closed after successful switchover
1068
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": old socket closed with 4001 (replaced during shutdown); no reconnect needed for ").concat(sessionId));
1069
+ this._emit(sessionId, 'offline.replaced', event);
1070
+ }
1071
+ break;
1072
+ case 1001:
1073
+ case 1005:
1074
+ case 1006:
1075
+ case 1011:
1076
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": socket ").concat(sessionId, " disconnected; reconnecting"));
1077
+ if (isActiveSocket) {
1078
+ this._emit(sessionId, 'offline.transient', event);
1079
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] reconnecting active socket to recover for ").concat(sessionId));
1080
+ this._reconnect(socketUrl, sessionId);
1081
+ }
1082
+ // metric: disconnect
1083
+ // if (code == 1011 && reason !== ping error) metric: unexpected disconnect
1084
+ break;
1085
+ case 1000:
1086
+ case 3050:
1087
+ // 3050 indicates logout form of closure, default to old behavior, use config reason defined by consumer to proceed with the permanent block
1088
+ if (normalReconnectReasons.includes(reason)) {
1089
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": socket ").concat(sessionId, " disconnected; reconnecting"));
1090
+ if (isActiveSocket) {
1091
+ this._emit(sessionId, 'offline.transient', event);
1092
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] reconnecting due to normal close for ").concat(sessionId));
1093
+ this._reconnect(socketUrl, sessionId);
1094
+ }
1095
+ // metric: disconnect
1096
+ // if (reason === done forced) metric: force closure
1097
+ } else {
1098
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": socket ").concat(sessionId, " disconnected; will not reconnect: ").concat(event.reason));
1099
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
1100
+ }
1101
+ break;
1102
+ default:
1103
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": socket ").concat(sessionId, " disconnected unexpectedly; will not reconnect"));
1104
+ // unexpected disconnect
1105
+ if (isActiveSocket) this._emit(sessionId, 'offline.permanent', event);
1106
+ }
1107
+ } catch (error) {
1108
+ this.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": error occurred in close handler for ").concat(sessionId), error);
1109
+ }
1110
+ }
1111
+ }, {
1112
+ key: "_onmessage",
1113
+ value: function _onmessage(sessionId, event) {
1114
+ var _this12 = this;
1115
+ this._setTimeOffset(sessionId, event);
1116
+ var envelope = event.data;
1117
+ if (process.env.ENABLE_MERCURY_LOGGING) {
1118
+ this.logger.debug("".concat(MOBIUS_SOCKET_NAMESPACE, ": message envelope from ").concat(sessionId, ": "), envelope);
1119
+ }
1120
+ envelope.sessionId = sessionId;
1121
+
1122
+ // Handle shutdown message shape: { type: 'shutdown' }
1123
+ if (envelope && envelope.type === 'shutdown') {
1124
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": [shutdown] imminent shutdown message received for ").concat(sessionId));
1125
+ this._emit(sessionId, 'event:mercury_shutdown_imminent', envelope);
1126
+ this._handleImminentShutdown(sessionId);
1127
+ return _promise.default.resolve();
1128
+ }
1129
+ if (this._trackAsyncEventAndShouldSuppressDuplicate(sessionId, envelope)) {
1130
+ return _promise.default.resolve();
1131
+ }
1132
+
1133
+ // Mobius: emit event:<type> for typed messages (e.g., register.response)
1134
+ if (envelope.type) {
1135
+ this._emit(sessionId, "event:".concat(envelope.type), envelope);
1136
+ }
1137
+ envelope.sessionId = sessionId;
1138
+ // Use data/payload if present, otherwise treat the envelope itself as the data (flat format)
1139
+ var data = envelope.data || envelope;
1140
+ this._applyOverrides(data);
1141
+
1142
+ // Support both Mercury-enveloped (data.eventType) and flat (eventType) formats
1143
+ var eventType = (data === null || data === void 0 ? void 0 : data.eventType) || envelope.eventType;
1144
+ if (!eventType) {
1145
+ this._emit(sessionId, 'event', envelope);
1146
+ return _promise.default.resolve();
1147
+ }
1148
+ return this._getEventHandlers(eventType).reduce(function (promise, handler) {
1149
+ return promise.then(function () {
1150
+ var namespace = handler.namespace,
1151
+ name = handler.name;
1152
+ return new _promise.default(function (resolve) {
1153
+ return resolve((_this12.webex[namespace] || _this12.webex.internal[namespace])[name](data));
1154
+ }).catch(function (reason) {
1155
+ return _this12.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": error occurred in autowired event handler for ").concat(eventType, " from ").concat(sessionId), reason);
1156
+ });
1157
+ });
1158
+ }, _promise.default.resolve()).then(function () {
1159
+ _this12._emit(sessionId, 'event', envelope);
1160
+ var _eventType$split3 = eventType.split('.'),
1161
+ _eventType$split4 = (0, _slicedToArray2.default)(_eventType$split3, 1),
1162
+ namespace = _eventType$split4[0];
1163
+ if (namespace === eventType) {
1164
+ _this12._emit(sessionId, "event:".concat(namespace), envelope);
1165
+ } else {
1166
+ _this12._emit(sessionId, "event:".concat(namespace), envelope);
1167
+ _this12._emit(sessionId, "event:".concat(eventType), envelope);
1168
+ }
1169
+ }).catch(function (reason) {
1170
+ _this12.logger.error("".concat(MOBIUS_SOCKET_NAMESPACE, ": error occurred processing socket message from ").concat(sessionId), reason);
1171
+ });
1172
+ }
1173
+ }, {
1174
+ key: "_setTimeOffset",
1175
+ value: function _setTimeOffset(sessionId, event) {
1176
+ var wsWriteTimestamp = event.data.wsWriteTimestamp;
1177
+ if (typeof wsWriteTimestamp === 'number' && wsWriteTimestamp > 0) {
1178
+ this.mercuryTimeOffset = (0, _now.default)() - wsWriteTimestamp;
1179
+ }
1180
+ }
1181
+ }, {
1182
+ key: "_reconnect",
1183
+ value: function _reconnect(webSocketUrl) {
1184
+ var sessionId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.defaultSessionId;
1185
+ this.logger.info("".concat(MOBIUS_SOCKET_NAMESPACE, ": reconnecting ").concat(sessionId));
1186
+ return this.connect(webSocketUrl || this.socketUrl, sessionId);
1187
+ }
1188
+ }]);
1189
+ }(_events.EventEmitter);
1190
+ var _default = exports.default = MobiusSocket;
1191
+ //# sourceMappingURL=mobius-socket.js.map