@webex/calling 3.12.0-mobius-socket.10 → 3.12.0-mobius-socket.12

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 (81) 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 +2 -2
  6. package/dist/CallingClient/line/line.test.js.map +1 -1
  7. package/dist/CallingClient/registration/register.js +76 -43
  8. package/dist/CallingClient/registration/register.js.map +1 -1
  9. package/dist/CallingClient/registration/register.test.js +1 -1
  10. package/dist/CallingClient/registration/register.test.js.map +1 -1
  11. package/dist/CallingClient/registration/types.js.map +1 -1
  12. package/dist/CallingClient/utils/request.js +2 -2
  13. package/dist/CallingClient/utils/request.js.map +1 -1
  14. package/dist/common/Utils.js +70 -30
  15. package/dist/common/Utils.js.map +1 -1
  16. package/dist/mobius-socket/config.js +61 -0
  17. package/dist/mobius-socket/config.js.map +1 -0
  18. package/dist/mobius-socket/errors.js +106 -0
  19. package/dist/mobius-socket/errors.js.map +1 -0
  20. package/dist/mobius-socket/index.js +101 -0
  21. package/dist/mobius-socket/index.js.map +1 -0
  22. package/dist/mobius-socket/mobius-socket-events.test.js +511 -0
  23. package/dist/mobius-socket/mobius-socket-events.test.js.map +1 -0
  24. package/dist/mobius-socket/mobius-socket.js +1191 -0
  25. package/dist/mobius-socket/mobius-socket.js.map +1 -0
  26. package/dist/mobius-socket/mobius-socket.test.js +2107 -0
  27. package/dist/mobius-socket/mobius-socket.test.js.map +1 -0
  28. package/dist/mobius-socket/socket/constants.js +20 -0
  29. package/dist/mobius-socket/socket/constants.js.map +1 -0
  30. package/dist/mobius-socket/socket/index.js +15 -0
  31. package/dist/mobius-socket/socket/index.js.map +1 -0
  32. package/dist/mobius-socket/socket/socket-base.js +632 -0
  33. package/dist/mobius-socket/socket/socket-base.js.map +1 -0
  34. package/dist/mobius-socket/socket/socket.js +19 -0
  35. package/dist/mobius-socket/socket/socket.js.map +1 -0
  36. package/dist/mobius-socket/socket/socket.shim.js +36 -0
  37. package/dist/mobius-socket/socket/socket.shim.js.map +1 -0
  38. package/dist/mobius-socket/socket.test.js +752 -0
  39. package/dist/mobius-socket/socket.test.js.map +1 -0
  40. package/dist/mobius-socket/test/mocha-helpers.js +23 -0
  41. package/dist/mobius-socket/test/mocha-helpers.js.map +1 -0
  42. package/dist/mobius-socket/test/promise-tick.js +29 -0
  43. package/dist/mobius-socket/test/promise-tick.js.map +1 -0
  44. package/dist/module/CallingClient/registration/register.js +30 -7
  45. package/dist/module/CallingClient/utils/request.js +1 -1
  46. package/dist/module/common/Utils.js +17 -4
  47. package/dist/module/mobius-socket/config.js +18 -0
  48. package/dist/module/mobius-socket/errors.js +30 -0
  49. package/dist/module/mobius-socket/index.js +24 -0
  50. package/dist/module/mobius-socket/mobius-socket.js +761 -0
  51. package/dist/module/mobius-socket/socket/constants.js +10 -0
  52. package/dist/module/mobius-socket/socket/index.js +4 -0
  53. package/dist/module/mobius-socket/socket/socket-base.js +374 -0
  54. package/dist/module/mobius-socket/socket/socket.js +9 -0
  55. package/dist/module/mobius-socket/socket/socket.shim.js +24 -0
  56. package/dist/types/CallingClient/registration/register.d.ts.map +1 -1
  57. package/dist/types/CallingClient/registration/types.d.ts +1 -1
  58. package/dist/types/CallingClient/registration/types.d.ts.map +1 -1
  59. package/dist/types/common/Utils.d.ts +4 -1
  60. package/dist/types/common/Utils.d.ts.map +1 -1
  61. package/dist/types/mobius-socket/config.d.ts +15 -0
  62. package/dist/types/mobius-socket/config.d.ts.map +1 -0
  63. package/dist/types/mobius-socket/errors.d.ts +13 -0
  64. package/dist/types/mobius-socket/errors.d.ts.map +1 -0
  65. package/dist/types/mobius-socket/index.d.ts +9 -0
  66. package/dist/types/mobius-socket/index.d.ts.map +1 -0
  67. package/dist/types/mobius-socket/mobius-socket.d.ts +62 -0
  68. package/dist/types/mobius-socket/mobius-socket.d.ts.map +1 -0
  69. package/dist/types/mobius-socket/socket/constants.d.ts +11 -0
  70. package/dist/types/mobius-socket/socket/constants.d.ts.map +1 -0
  71. package/dist/types/mobius-socket/socket/index.d.ts +2 -0
  72. package/dist/types/mobius-socket/socket/index.d.ts.map +1 -0
  73. package/dist/types/mobius-socket/socket/socket-base.d.ts +38 -0
  74. package/dist/types/mobius-socket/socket/socket-base.d.ts.map +1 -0
  75. package/dist/types/mobius-socket/socket/socket.d.ts +3 -0
  76. package/dist/types/mobius-socket/socket/socket.d.ts.map +1 -0
  77. package/dist/types/mobius-socket/socket/socket.shim.d.ts +3 -0
  78. package/dist/types/mobius-socket/socket/socket.shim.d.ts.map +1 -0
  79. package/package.json +16 -3
  80. package/src/mobius-socket/socket/socket.js +13 -0
  81. package/src/mobius-socket/socket/socket.shim.js +31 -0
@@ -0,0 +1,2107 @@
1
+ "use strict";
2
+
3
+ var _typeof = require("@babel/runtime-corejs2/helpers/typeof");
4
+ var _Object$keys = require("@babel/runtime-corejs2/core-js/object/keys");
5
+ var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs2/core-js/object/get-own-property-symbols");
6
+ var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
7
+ var _Object$getOwnPropertyDescriptors = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptors");
8
+ var _Object$defineProperties = require("@babel/runtime-corejs2/core-js/object/define-properties");
9
+ var _Object$defineProperty2 = require("@babel/runtime-corejs2/core-js/object/define-property");
10
+ var _WeakMap = require("@babel/runtime-corejs2/core-js/weak-map");
11
+ var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
12
+ var _regenerator = _interopRequireDefault(require("@babel/runtime-corejs2/regenerator"));
13
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/asyncToGenerator"));
14
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
15
+ var _construct2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/construct"));
16
+ var _defineProperty3 = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/object/define-property"));
17
+ var _stringify = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/json/stringify"));
18
+ var _now = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/date/now"));
19
+ var _promise = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/promise"));
20
+ var _apply = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/reflect/apply"));
21
+ var _crypto = require("crypto");
22
+ var _sinon = _interopRequireDefault(require("sinon"));
23
+ var _testHelperChai = require("@webex/test-helper-chai");
24
+ var _testHelperMockWebex = _interopRequireDefault(require("@webex/test-helper-mock-webex"));
25
+ var _testHelperMockWebSocket = _interopRequireDefault(require("@webex/test-helper-mock-web-socket"));
26
+ var _index = _interopRequireWildcard(require("./index"));
27
+ var _mochaHelpers = require("./test/mocha-helpers");
28
+ var _constants = require("./socket/constants");
29
+ var _promiseTick = _interopRequireDefault(require("./test/promise-tick"));
30
+ function _interopRequireWildcard(e, t) { if ("function" == typeof _WeakMap) var r = new _WeakMap(), n = new _WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t3 in e) "default" !== _t3 && {}.hasOwnProperty.call(e, _t3) && ((i = (o = _Object$defineProperty2) && _Object$getOwnPropertyDescriptor(e, _t3)) && (i.get || i.set) ? o(f, _t3, i) : f[_t3] = e[_t3]); return f; })(e, t); }
31
+ function ownKeys(e, r) { var t = _Object$keys(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; }
32
+ 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$defineProperty2(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; } /*!
33
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
34
+ */
35
+ if (!crypto.randomUUID) {
36
+ (0, _defineProperty3.default)(crypto, 'randomUUID', {
37
+ value: _crypto.randomUUID,
38
+ configurable: true
39
+ });
40
+ }
41
+ describe('plugin-mobius-socket', function () {
42
+ var createUuid = function createUuid() {
43
+ return crypto.randomUUID();
44
+ };
45
+ describe('getMobiusSocketInstance', function () {
46
+ afterEach(function () {
47
+ (0, _index.resetMobiusSocketInstance)();
48
+ });
49
+ it('uses package config when consumer config is not provided', function () {
50
+ var configuredWebex = new _testHelperMockWebex.default();
51
+ var instance = (0, _index.getMobiusSocketInstance)(configuredWebex);
52
+ _testHelperChai.assert.instanceOf(instance, _index.default);
53
+ _testHelperChai.assert.equal(instance.config, _index.config.mobiusSocket);
54
+ });
55
+ it('uses consumer config when it is provided', function () {
56
+ var configuredWebex = new _testHelperMockWebex.default();
57
+ var consumerConfig = {
58
+ initialConnectionMaxRetries: 0,
59
+ backoffTimeReset: 1234
60
+ };
61
+ var instance = (0, _index.getMobiusSocketInstance)(configuredWebex, consumerConfig);
62
+ _testHelperChai.assert.instanceOf(instance, _index.default);
63
+ _testHelperChai.assert.equal(instance.config, consumerConfig);
64
+ });
65
+ });
66
+ describe('MobiusSocket', function () {
67
+ var mobiusSocket;
68
+ var mockWebSocket;
69
+ var socketOpenStub;
70
+ var usingFakeTimers;
71
+ var webex;
72
+ var statusStartTypingMessage = (0, _stringify.default)({
73
+ id: createUuid(),
74
+ data: {
75
+ eventType: 'status.start_typing',
76
+ actor: {
77
+ id: 'actorId'
78
+ },
79
+ conversationId: createUuid()
80
+ },
81
+ timestamp: (0, _now.default)(),
82
+ trackingId: "suffix_".concat(createUuid(), "_").concat((0, _now.default)())
83
+ });
84
+ var emitAuthResponse = function emitAuthResponse() {
85
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
86
+ _ref$statusCode = _ref.statusCode,
87
+ statusCode = _ref$statusCode === void 0 ? 200 : _ref$statusCode,
88
+ _ref$statusMessage = _ref.statusMessage,
89
+ statusMessage = _ref$statusMessage === void 0 ? 'OK' : _ref$statusMessage;
90
+ var authRequest = JSON.parse(mockWebSocket.send.lastCall.args[0]);
91
+ mockWebSocket.emit('message', {
92
+ data: (0, _stringify.default)({
93
+ type: 'response_event',
94
+ subtype: _constants.MESSAGE_TYPES.AUTH,
95
+ trackingId: authRequest.trackingId,
96
+ statusCode: statusCode,
97
+ statusMessage: statusMessage
98
+ })
99
+ });
100
+ };
101
+ var createAsyncEvent = function createAsyncEvent(eventId) {
102
+ var eventType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'custom.event';
103
+ return {
104
+ data: {
105
+ type: 'async_event',
106
+ eventId: eventId,
107
+ data: {
108
+ eventType: eventType
109
+ }
110
+ }
111
+ };
112
+ };
113
+ var createSessionSocket = function createSessionSocket() {
114
+ return {
115
+ close: _sinon.default.stub().returns(_promise.default.resolve()),
116
+ removeAllListeners: _sinon.default.stub()
117
+ };
118
+ };
119
+ var countGenericEventEmits = function countGenericEventEmits(emitSpy, sessionId) {
120
+ return emitSpy.getCalls().filter(function (call) {
121
+ return call.args[0] === sessionId && call.args[1] === 'event';
122
+ }).length;
123
+ };
124
+ beforeEach(function () {
125
+ jest.useFakeTimers({
126
+ doNotFake: ['nextTick']
127
+ });
128
+ usingFakeTimers = true;
129
+ });
130
+ beforeEach(function () {
131
+ webex = new _testHelperMockWebex.default();
132
+ webex.credentials = {
133
+ refresh: _sinon.default.stub().returns(_promise.default.resolve()),
134
+ getUserToken: _sinon.default.stub().returns(_promise.default.resolve({
135
+ toString: function toString() {
136
+ return 'Bearer FAKE';
137
+ }
138
+ }))
139
+ };
140
+ webex.internal.device = {
141
+ registered: true,
142
+ register: _sinon.default.stub().returns(_promise.default.resolve()),
143
+ refresh: _sinon.default.stub().returns(_promise.default.resolve()),
144
+ webSocketUrl: 'ws://example.com',
145
+ getWebSocketUrl: _sinon.default.stub().returns(_promise.default.resolve('ws://example-2.com')),
146
+ useServiceCatalogUrl: _sinon.default.stub().returns(_promise.default.resolve('https://service-catalog-url.com'))
147
+ };
148
+ webex.internal.services = {
149
+ convertUrlToPriorityHostUrl: _sinon.default.stub().returns(_promise.default.resolve('ws://example-2.com')),
150
+ markFailedUrl: _sinon.default.stub().returns(_promise.default.resolve()),
151
+ switchActiveClusterIds: _sinon.default.stub(),
152
+ invalidateCache: _sinon.default.stub(),
153
+ isValidHost: _sinon.default.stub().returns(_promise.default.resolve(true))
154
+ };
155
+ webex.internal.metrics.submitClientMetrics = _sinon.default.stub();
156
+ webex.trackingId = 'fakeTrackingId';
157
+ webex.logger = console;
158
+ _sinon.default.stub(_index.Socket, 'getWebSocketConstructor').callsFake(function () {
159
+ return function () {
160
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
161
+ args[_key] = arguments[_key];
162
+ }
163
+ mockWebSocket = (0, _construct2.default)(_testHelperMockWebSocket.default, args);
164
+ return mockWebSocket;
165
+ };
166
+ });
167
+ var origOpen = _index.Socket.prototype.open;
168
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').callsFake(function () {
169
+ for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
170
+ args[_key2] = arguments[_key2];
171
+ }
172
+ var promise = (0, _apply.default)(origOpen, this, args);
173
+ process.nextTick(function () {
174
+ mockWebSocket.open();
175
+ // Simulate Mobius auth response after socket open
176
+ process.nextTick(function () {
177
+ emitAuthResponse();
178
+ });
179
+ });
180
+ return promise;
181
+ });
182
+ mobiusSocket = new _index.default(webex, _objectSpread({}, _index.config.mobiusSocket));
183
+ mobiusSocket.defaultSessionId = 'mobius-websocket-session';
184
+ });
185
+ afterEach(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee() {
186
+ var _t;
187
+ return _regenerator.default.wrap(function (_context) {
188
+ while (1) switch (_context.prev = _context.next) {
189
+ case 0:
190
+ if (usingFakeTimers) {
191
+ jest.useRealTimers();
192
+ usingFakeTimers = false;
193
+ }
194
+
195
+ // Clean up MobiusSocket connections and internal state
196
+ if (!mobiusSocket) {
197
+ _context.next = 5;
198
+ break;
199
+ }
200
+ _context.prev = 1;
201
+ _context.next = 2;
202
+ return mobiusSocket.disconnectAll();
203
+ case 2:
204
+ _context.next = 4;
205
+ break;
206
+ case 3:
207
+ _context.prev = 3;
208
+ _t = _context["catch"](1);
209
+ case 4:
210
+ // Clear any remaining connection promises
211
+ if (mobiusSocket._connectPromises) {
212
+ mobiusSocket._connectPromises.clear();
213
+ }
214
+ case 5:
215
+ // Ensure mock socket is properly closed
216
+ if (mockWebSocket && typeof mockWebSocket.close === 'function') {
217
+ try {
218
+ mockWebSocket.close();
219
+ } catch (e) {
220
+ // Ignore cleanup errors
221
+ }
222
+ }
223
+ if (socketOpenStub) {
224
+ socketOpenStub.restore();
225
+ }
226
+ if (_index.Socket.getWebSocketConstructor.restore) {
227
+ _index.Socket.getWebSocketConstructor.restore();
228
+ }
229
+
230
+ // Small delay to ensure all async operations complete
231
+ _context.next = 6;
232
+ return new _promise.default(function (resolve) {
233
+ return setTimeout(resolve, 10);
234
+ });
235
+ case 6:
236
+ case "end":
237
+ return _context.stop();
238
+ }
239
+ }, _callee, null, [[1, 3]]);
240
+ })));
241
+ describe('#connect()', function () {
242
+ it('lazily registers the device', function () {
243
+ webex.internal.device.registered = false;
244
+ _testHelperChai.assert.notCalled(webex.internal.device.register);
245
+ var promise = mobiusSocket.connect();
246
+ return promise.then(function () {
247
+ _testHelperChai.assert.calledOnce(webex.internal.device.register);
248
+ });
249
+ });
250
+ it('connects to MobiusSocket using default url', function () {
251
+ webex.internal.feature.updateFeature = _sinon.default.stub();
252
+ var promise = mobiusSocket.connect();
253
+ var envelope = {
254
+ data: {
255
+ featureToggle: {
256
+ 'feature-name': true
257
+ }
258
+ }
259
+ };
260
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
261
+ _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
262
+ mockWebSocket.open();
263
+ return promise.then(function () {
264
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
265
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
266
+ _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match.any);
267
+ mobiusSocket.emit('event:featureToggle_update', envelope);
268
+ _testHelperChai.assert.calledOnceWithExactly(webex.internal.feature.updateFeature, envelope.data.featureToggle);
269
+ _sinon.default.restore();
270
+ });
271
+ });
272
+ it('connects to MobiusSocket but does not call updateFeature', function () {
273
+ webex.internal.feature.updateFeature = _sinon.default.stub();
274
+ var promise = mobiusSocket.connect();
275
+ var envelope = {};
276
+ return promise.then(function () {
277
+ mobiusSocket.emit('event:featureToggle_update', envelope);
278
+ _testHelperChai.assert.notCalled(webex.internal.feature.updateFeature);
279
+ _sinon.default.restore();
280
+ });
281
+ });
282
+ it('MobiusSocket emit event:ActiveClusterStatusEvent, call services switchActiveClusterIds', function () {
283
+ var promise = mobiusSocket.connect();
284
+ var activeClusterEventEnvelope = {
285
+ data: {
286
+ activeClusters: {
287
+ wdm: 'wdm-cluster-id.com'
288
+ }
289
+ }
290
+ };
291
+ mockWebSocket.open();
292
+ return promise.then(function () {
293
+ mobiusSocket.emit('event:ActiveClusterStatusEvent', activeClusterEventEnvelope);
294
+ _testHelperChai.assert.calledOnceWithExactly(webex.internal.services.switchActiveClusterIds, activeClusterEventEnvelope.data.activeClusters);
295
+ _sinon.default.restore();
296
+ });
297
+ });
298
+ it('MobiusSocket emit event:ActiveClusterStatusEvent with no data, not call services switchActiveClusterIds', function () {
299
+ webex.internal.feature.updateFeature = _sinon.default.stub();
300
+ var promise = mobiusSocket.connect();
301
+ var envelope = {};
302
+ return promise.then(function () {
303
+ mobiusSocket.emit('event:ActiveClusterStatusEvent', envelope);
304
+ _testHelperChai.assert.notCalled(webex.internal.services.switchActiveClusterIds);
305
+ _sinon.default.restore();
306
+ });
307
+ });
308
+ it('MobiusSocket emit event:u2c.cache-invalidation, call services invalidateCache', function () {
309
+ var promise = mobiusSocket.connect();
310
+ var u2cInvalidateEventEnvelope = {
311
+ data: {
312
+ timestamp: '1759289614'
313
+ }
314
+ };
315
+ mockWebSocket.open();
316
+ return promise.then(function () {
317
+ mobiusSocket.emit('event:u2c.cache-invalidation', u2cInvalidateEventEnvelope);
318
+ _testHelperChai.assert.calledOnceWithExactly(webex.internal.services.invalidateCache, u2cInvalidateEventEnvelope.data.timestamp);
319
+ _sinon.default.restore();
320
+ });
321
+ });
322
+ it('MobiusSocket emit event:u2c.cache-invalidation with no data, not call services switchActiveClusterIds', function () {
323
+ webex.internal.feature.updateFeature = _sinon.default.stub();
324
+ var promise = mobiusSocket.connect();
325
+ var envelope = {};
326
+ return promise.then(function () {
327
+ mobiusSocket.emit('event:u2c.cache-invalidation', envelope);
328
+ _testHelperChai.assert.notCalled(webex.internal.services.invalidateCache);
329
+ _sinon.default.restore();
330
+ });
331
+ });
332
+ describe('when `maxRetries` is set', function () {
333
+ var check = function check() {
334
+ socketOpenStub.restore();
335
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
336
+ socketOpenStub.returns(_promise.default.reject(new _index.ConnectionError()));
337
+ _testHelperChai.assert.notCalled(_index.Socket.prototype.open);
338
+ var promise = mobiusSocket.connect();
339
+ return (0, _promiseTick.default)(5).then(function () {
340
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
341
+ return (0, _promiseTick.default)(5);
342
+ }).then(function () {
343
+ jest.advanceTimersByTime(mobiusSocket.config.backoffTimeReset);
344
+ return (0, _promiseTick.default)(5);
345
+ }).then(function () {
346
+ _testHelperChai.assert.calledTwice(_index.Socket.prototype.open);
347
+ jest.advanceTimersByTime(2 * mobiusSocket.config.backoffTimeReset);
348
+ return (0, _promiseTick.default)(5);
349
+ }).then(function () {
350
+ _testHelperChai.assert.calledThrice(_index.Socket.prototype.open);
351
+ jest.advanceTimersByTime(5 * mobiusSocket.config.backoffTimeReset);
352
+ return _testHelperChai.assert.isRejected(promise);
353
+ }).then(function () {
354
+ _testHelperChai.assert.calledThrice(_index.Socket.prototype.open);
355
+ });
356
+ };
357
+
358
+ // skipping due to apparent bug with lolex in all browsers but Chrome.
359
+ (0, _mochaHelpers.skipInBrowser)(it)('fails after configured `initialConnectionMaxRetries` attempts', function () {
360
+ mobiusSocket.config.maxRetries = 0;
361
+ mobiusSocket.config.initialConnectionMaxRetries = 2;
362
+ return check();
363
+ });
364
+
365
+ // skipping due to apparent bug with lolex in all browsers but Chrome.
366
+ // if initial retries is zero and mobiusSocket has never connected, do not retry
367
+ (0, _mochaHelpers.skipInBrowser)(it)('fails immediately when `initialConnectionMaxRetries` is 0', function () {
368
+ mobiusSocket.config.maxRetries = 2;
369
+ mobiusSocket.config.initialConnectionMaxRetries = 0;
370
+ socketOpenStub.restore();
371
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
372
+ socketOpenStub.returns(_promise.default.reject(new _index.ConnectionError()));
373
+ _testHelperChai.assert.notCalled(_index.Socket.prototype.open);
374
+ var promise = mobiusSocket.connect();
375
+ return (0, _promiseTick.default)(5).then(function () {
376
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
377
+ return _testHelperChai.assert.isRejected(promise);
378
+ }).then(function () {
379
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
380
+ });
381
+ });
382
+
383
+ // initial retries is non-zero so takes precedence over maxRetries when mobiusSocket has never connected
384
+ (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
385
+ mobiusSocket.config.maxRetries = 0;
386
+ mobiusSocket.config.initialConnectionMaxRetries = 2;
387
+ return check();
388
+ });
389
+
390
+ // initial retries is non-zero so takes precedence over maxRetries when mobiusSocket has never connected
391
+ (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
392
+ mobiusSocket.config.initialConnectionMaxRetries = 2;
393
+ mobiusSocket.config.maxRetries = 5;
394
+ return check();
395
+ });
396
+
397
+ // when mobiusSocket has connected maxRetries is used and the initialConnectionMaxRetries is ignored
398
+ (0, _mochaHelpers.skipInBrowser)(it)('fails after `initialConnectionMaxRetries` attempts', function () {
399
+ mobiusSocket.config.initialConnectionMaxRetries = 5;
400
+ mobiusSocket.config.maxRetries = 2;
401
+ mobiusSocket.hasEverConnected = true;
402
+ return check();
403
+ });
404
+ });
405
+ it('can safely be called multiple times', function () {
406
+ var promise = _promise.default.all([mobiusSocket.connect(), mobiusSocket.connect(), mobiusSocket.connect(), mobiusSocket.connect()]);
407
+ mockWebSocket.open();
408
+ return promise.then(function () {
409
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
410
+ });
411
+ });
412
+
413
+ // skipping due to apparent bug with lolex in all browsers but Chrome.
414
+ (0, _mochaHelpers.skipInBrowser)(describe)('when the connection fails', function () {
415
+ it('backs off exponentially', function () {
416
+ mobiusSocket.config.initialConnectionMaxRetries = 2;
417
+ socketOpenStub.restore();
418
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
419
+ socketOpenStub.returns(_promise.default.reject(new _index.ConnectionError({
420
+ code: 4001
421
+ })));
422
+ // Note: onCall is zero-based
423
+ socketOpenStub.onCall(2).returns(_promise.default.resolve(new _testHelperMockWebSocket.default()));
424
+ _testHelperChai.assert.notCalled(_index.Socket.prototype.open);
425
+ var promise = mobiusSocket.connect();
426
+ return (0, _promiseTick.default)(5).then(function () {
427
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
428
+
429
+ // I'm not sure why, but it's important the clock doesn't advance
430
+ // until a tick happens
431
+ return (0, _promiseTick.default)(5);
432
+ }).then(function () {
433
+ jest.advanceTimersByTime(mobiusSocket.config.backoffTimeReset);
434
+ return (0, _promiseTick.default)(5);
435
+ }).then(function () {
436
+ _testHelperChai.assert.calledTwice(_index.Socket.prototype.open);
437
+ jest.advanceTimersByTime(2 * mobiusSocket.config.backoffTimeReset);
438
+ return (0, _promiseTick.default)(5);
439
+ }).then(function () {
440
+ _testHelperChai.assert.calledThrice(_index.Socket.prototype.open);
441
+ jest.advanceTimersByTime(5 * mobiusSocket.config.backoffTimeReset);
442
+ return promise;
443
+ }).then(function () {
444
+ _testHelperChai.assert.calledThrice(_index.Socket.prototype.open);
445
+ jest.advanceTimersByTime(8 * mobiusSocket.config.backoffTimeReset);
446
+ return (0, _promiseTick.default)(5);
447
+ }).then(function () {
448
+ _testHelperChai.assert.calledThrice(_index.Socket.prototype.open);
449
+ });
450
+ });
451
+ describe('with `BadRequest`', function () {
452
+ it('fails permanently', function () {
453
+ jest.useRealTimers();
454
+ usingFakeTimers = false;
455
+ socketOpenStub.restore();
456
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.reject(new _index.BadRequest({
457
+ code: 4400
458
+ })));
459
+ return _testHelperChai.assert.isRejected(mobiusSocket.connect());
460
+ });
461
+ });
462
+ describe('with `UnknownResponse`', function () {
463
+ it('triggers a device refresh', function () {
464
+ mobiusSocket.config.initialConnectionMaxRetries = 1;
465
+ socketOpenStub.restore();
466
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.resolve());
467
+ socketOpenStub.onCall(0).returns(_promise.default.reject(new _index.UnknownResponse({
468
+ code: 4444
469
+ })));
470
+ _testHelperChai.assert.notCalled(webex.credentials.refresh);
471
+ _testHelperChai.assert.notCalled(webex.internal.device.refresh);
472
+ var promise = mobiusSocket.connect();
473
+ return (0, _promiseTick.default)(7).then(function () {
474
+ _testHelperChai.assert.notCalled(webex.credentials.refresh);
475
+ _testHelperChai.assert.called(webex.internal.device.refresh);
476
+ jest.advanceTimersByTime(1000);
477
+ return promise;
478
+ });
479
+ });
480
+ });
481
+ describe('with `NotAuthorized`', function () {
482
+ it('triggers a token refresh', function () {
483
+ mobiusSocket.config.initialConnectionMaxRetries = 1;
484
+ socketOpenStub.restore();
485
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.resolve());
486
+ socketOpenStub.onCall(0).returns(_promise.default.reject(new _index.NotAuthorized({
487
+ code: 4401
488
+ })));
489
+ _testHelperChai.assert.notCalled(webex.credentials.refresh);
490
+ _testHelperChai.assert.notCalled(webex.internal.device.refresh);
491
+ var promise = mobiusSocket.connect();
492
+ return (0, _promiseTick.default)(7).then(function () {
493
+ _testHelperChai.assert.called(webex.credentials.refresh);
494
+ _testHelperChai.assert.notCalled(webex.internal.device.refresh);
495
+ jest.advanceTimersByTime(1000);
496
+ return promise;
497
+ });
498
+ });
499
+ });
500
+ describe('with `Forbidden`', function () {
501
+ it('fails permanently', function () {
502
+ jest.useRealTimers();
503
+ usingFakeTimers = false;
504
+ socketOpenStub.restore();
505
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.reject(new _index.Forbidden({
506
+ code: 4403
507
+ })));
508
+ return _testHelperChai.assert.isRejected(mobiusSocket.connect());
509
+ });
510
+ });
511
+
512
+ // describe(`with \`NotFound\``, () => {
513
+ // it(`triggers a device refresh`, () => {
514
+ // socketOpenStub.restore();
515
+ // socketOpenStub = sinon.stub(Socket.prototype, `open`).returns(Promise.resolve());
516
+ // socketOpenStub.onCall(0).returns(Promise.reject(new NotFound({code: 4404})));
517
+ // assert.notCalled(webex.credentials.refresh);
518
+ // assert.notCalled(webex.internal.device.refresh);
519
+ // const promise = mobiusSocket.connect();
520
+ // return promiseTick(6)
521
+ // .then(() => {
522
+ // assert.notCalled(webex.credentials.refresh);
523
+ // assert.called(webex.internal.device.refresh);
524
+ // clock.tick(1000);
525
+ // return assert.isFulfilled(promise);
526
+ // });
527
+ // });
528
+ // });
529
+ });
530
+ describe('when connected', function () {
531
+ it('resolves immediately', function () {
532
+ return mobiusSocket.connect().then(function () {
533
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
534
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
535
+ var promise = mobiusSocket.connect();
536
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
537
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
538
+ return promise;
539
+ });
540
+ });
541
+
542
+ // skipping due to apparent bug with lolex in all browsers but Chrome.
543
+ (0, _mochaHelpers.skipInBrowser)(it)('does not continue attempting to connect', function () {
544
+ var promise = mobiusSocket.connect();
545
+
546
+ // Wait for the connection to be established before proceeding
547
+ mockWebSocket.open();
548
+ return promise.then(function () {
549
+ return (0, _promiseTick.default)(2).then(function () {
550
+ jest.advanceTimersByTime(6 * mobiusSocket.config.backoffTimeReset);
551
+ return (0, _promiseTick.default)(2);
552
+ }).then(function () {
553
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
554
+ });
555
+ });
556
+ });
557
+ });
558
+ describe('when webSocketUrl is provided', function () {
559
+ it('connects to MobiusSocket with provided url', function () {
560
+ var webSocketUrl = 'ws://providedurl.com';
561
+ var promise = mobiusSocket.connect(webSocketUrl);
562
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
563
+ _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
564
+ mockWebSocket.open();
565
+ return promise.then(function () {
566
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
567
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
568
+ _testHelperChai.assert.calledWith(_index.Socket.prototype.open, 'ws://providedurl.com', _sinon.default.match.any);
569
+ });
570
+ });
571
+ });
572
+ describe('when config.initialConnectionMaxRetries is set to 0', function () {
573
+ it('connects successfully through the shared backoff flow', function () {
574
+ var backoffSpy = _sinon.default.spy(mobiusSocket, '_connectWithBackoff');
575
+ mobiusSocket.config.initialConnectionMaxRetries = 0;
576
+ var promise = mobiusSocket.connect('ws://example.com');
577
+ _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
578
+ mockWebSocket.open();
579
+ return promise.then(function () {
580
+ var _backoffSpy$firstCall;
581
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
582
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
583
+ _testHelperChai.assert.isTrue(mobiusSocket.hasEverConnected, 'hasEverConnected is true');
584
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
585
+ _testHelperChai.assert.calledOnce(backoffSpy);
586
+ _testHelperChai.assert.isUndefined((_backoffSpy$firstCall = backoffSpy.firstCall.args[2]) === null || _backoffSpy$firstCall === void 0 ? void 0 : _backoffSpy$firstCall.initialConnectionMaxRetries);
587
+ backoffSpy.restore();
588
+ });
589
+ });
590
+ it('rejects immediately on failure without retrying', function () {
591
+ jest.useRealTimers();
592
+ usingFakeTimers = false;
593
+ socketOpenStub.restore();
594
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.reject(new _index.ConnectionError({
595
+ code: 4001
596
+ })));
597
+ mobiusSocket.config.initialConnectionMaxRetries = 0;
598
+ var promise = mobiusSocket.connect('ws://example.com');
599
+ return _testHelperChai.assert.isRejected(promise).then(function () {
600
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
601
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
602
+ });
603
+ });
604
+ it('uses config-driven initial retry behavior in the shared backoff strategy', function () {
605
+ var backoffSpy = _sinon.default.spy(mobiusSocket, '_connectWithBackoff');
606
+ mobiusSocket.config.initialConnectionMaxRetries = 0;
607
+ var promise = mobiusSocket.connect('ws://example.com');
608
+ mockWebSocket.open();
609
+ return promise.then(function () {
610
+ var _backoffSpy$firstCall2;
611
+ _testHelperChai.assert.calledOnce(backoffSpy);
612
+ _testHelperChai.assert.isUndefined((_backoffSpy$firstCall2 = backoffSpy.firstCall.args[2]) === null || _backoffSpy$firstCall2 === void 0 ? void 0 : _backoffSpy$firstCall2.initialConnectionMaxRetries);
613
+ backoffSpy.restore();
614
+ });
615
+ });
616
+ it('treats a different explicit URL as a fresh initial connect', function () {
617
+ jest.useRealTimers();
618
+ usingFakeTimers = false;
619
+ mobiusSocket.hasEverConnected = true;
620
+ mobiusSocket.socketUrl = 'ws://old-url.com';
621
+ mobiusSocket.config.initialConnectionMaxRetries = 0;
622
+ mobiusSocket.config.maxRetries = 5;
623
+ socketOpenStub.restore();
624
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open').returns(_promise.default.reject(new _index.ConnectionError({
625
+ code: 4001
626
+ })));
627
+ var promise = mobiusSocket.connect('ws://new-url.com');
628
+ return _testHelperChai.assert.isRejected(promise).then(function () {
629
+ _testHelperChai.assert.calledOnce(_index.Socket.prototype.open);
630
+ _testHelperChai.assert.equal(mobiusSocket.socketUrl, 'ws://new-url.com');
631
+ _testHelperChai.assert.isFalse(mobiusSocket.hasEverConnected, 'hasEverConnected is false');
632
+ });
633
+ });
634
+ });
635
+ });
636
+ describe('Websocket proxy agent', function () {
637
+ afterEach(function () {
638
+ delete webex.config.defaultMobiusSocketOptions;
639
+ });
640
+ it('connects to MobiusSocket using proxy agent', function () {
641
+ var testProxyUrl = 'http://proxyurl.com:80';
642
+ webex.config.defaultMobiusSocketOptions = {
643
+ agent: {
644
+ proxy: {
645
+ href: testProxyUrl
646
+ }
647
+ }
648
+ };
649
+ var promise = mobiusSocket.connect();
650
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
651
+ _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
652
+ mockWebSocket.open();
653
+ return promise.then(function () {
654
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
655
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
656
+ _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match.has('agent', _sinon.default.match.has('proxy', _sinon.default.match.has('href', testProxyUrl))));
657
+ });
658
+ });
659
+ it('connects to MobiusSocket without proxy agent', function () {
660
+ var promise = mobiusSocket.connect();
661
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
662
+ _testHelperChai.assert.isTrue(mobiusSocket.connecting, 'MobiusSocket is connecting');
663
+ mockWebSocket.open();
664
+ return promise.then(function () {
665
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
666
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
667
+ _testHelperChai.assert.calledWith(socketOpenStub, 'ws://example.com', _sinon.default.match({
668
+ agent: undefined
669
+ }));
670
+ });
671
+ });
672
+ });
673
+ describe('#logout()', function () {
674
+ it('calls disconnectAll and logs', function () {
675
+ _sinon.default.stub(mobiusSocket.logger, 'info');
676
+ _sinon.default.stub(mobiusSocket, 'disconnectAll');
677
+ mobiusSocket.logout();
678
+ _testHelperChai.assert.called(mobiusSocket.disconnectAll);
679
+ _testHelperChai.assert.calledOnce(mobiusSocket.logger.info);
680
+ _testHelperChai.assert.calledWith(mobiusSocket.logger.info.getCall(0), 'MobiusSocket: logout() called');
681
+ });
682
+ it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout', function () {
683
+ _sinon.default.stub(mobiusSocket, 'disconnectAll');
684
+ mobiusSocket.config.beforeLogoutOptionsCloseReason = 'done (permanent)';
685
+ mobiusSocket.logout();
686
+ _testHelperChai.assert.calledWith(mobiusSocket.disconnectAll, {
687
+ code: 3050,
688
+ reason: 'done (permanent)'
689
+ });
690
+ });
691
+ it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout if the reason is different than standard', function () {
692
+ _sinon.default.stub(mobiusSocket, 'disconnectAll');
693
+ mobiusSocket.config.beforeLogoutOptionsCloseReason = 'test';
694
+ mobiusSocket.logout();
695
+ _testHelperChai.assert.calledWith(mobiusSocket.disconnectAll, {
696
+ code: 3050,
697
+ reason: 'test'
698
+ });
699
+ });
700
+ it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send undefined for logout if the reason is same as standard', function () {
701
+ _sinon.default.stub(mobiusSocket, 'disconnectAll');
702
+ mobiusSocket.config.beforeLogoutOptionsCloseReason = 'done (forced)';
703
+ mobiusSocket.logout();
704
+ _testHelperChai.assert.calledWith(mobiusSocket.disconnectAll, undefined);
705
+ });
706
+ });
707
+ describe('#disconnect()', function () {
708
+ it('disconnects the WebSocket', function () {
709
+ return mobiusSocket.connect().then(function () {
710
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
711
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
712
+ var promise = mobiusSocket.disconnect();
713
+ mockWebSocket.emit('close', {
714
+ code: 1000,
715
+ reason: 'Done'
716
+ });
717
+ return promise;
718
+ }).then(function () {
719
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
720
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
721
+ _testHelperChai.assert.isUndefined(mobiusSocket.mockWebSocket, 'MobiusSocket does not have a mockWebSocket');
722
+ });
723
+ });
724
+ it('disconnects the WebSocket with code 3050', function () {
725
+ return mobiusSocket.connect().then(function () {
726
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
727
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
728
+ var promise = mobiusSocket.disconnect();
729
+ mockWebSocket.emit('close', {
730
+ code: 3050,
731
+ reason: 'done (permanent)'
732
+ });
733
+ return promise;
734
+ }).then(function () {
735
+ _testHelperChai.assert.isFalse(mobiusSocket.connected, 'MobiusSocket is not connected');
736
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
737
+ _testHelperChai.assert.isUndefined(mobiusSocket.mockWebSocket, 'MobiusSocket does not have a mockWebSocket');
738
+ });
739
+ });
740
+ it('stops emitting message events', function () {
741
+ var spy = _sinon.default.spy();
742
+ mobiusSocket.on('event:status.start_typing', spy);
743
+ return mobiusSocket.connect().then(function () {
744
+ _testHelperChai.assert.isTrue(mobiusSocket.connected, 'MobiusSocket is connected');
745
+ _testHelperChai.assert.isFalse(mobiusSocket.connecting, 'MobiusSocket is not connecting');
746
+ _testHelperChai.assert.notCalled(spy);
747
+ mockWebSocket.readyState = 1;
748
+ mockWebSocket.emit('open');
749
+ mockWebSocket.emit('message', {
750
+ data: statusStartTypingMessage
751
+ });
752
+ }).then(function () {
753
+ _testHelperChai.assert.calledOnce(spy);
754
+ var promise = mobiusSocket.disconnect();
755
+ mockWebSocket.readyState = 1;
756
+ mockWebSocket.emit('open');
757
+ mockWebSocket.emit('message', {
758
+ data: statusStartTypingMessage
759
+ });
760
+ mockWebSocket.emit('close', {
761
+ code: 1000,
762
+ reason: 'Done'
763
+ });
764
+ mockWebSocket.emit('message', {
765
+ data: statusStartTypingMessage
766
+ });
767
+ return promise;
768
+ }).then(function () {
769
+ mockWebSocket.readyState = 1;
770
+ mockWebSocket.emit('open');
771
+ mockWebSocket.emit('message', {
772
+ data: statusStartTypingMessage
773
+ });
774
+ _testHelperChai.assert.calledOnce(spy);
775
+ });
776
+ });
777
+ describe('when there is a connection attempt inflight', function () {
778
+ it('stops the attempt when disconnect called', function () {
779
+ socketOpenStub.restore();
780
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
781
+ socketOpenStub.onCall(0).returns(
782
+ // Delay the opening of the socket so that disconnect is called while open
783
+ // is in progress
784
+ (0, _promiseTick.default)(2 * mobiusSocket.config.backoffTimeReset)
785
+ // Pretend the socket opened successfully. Failing should be fine too but
786
+ // it generates more console output.
787
+ .then(function () {
788
+ return _promise.default.resolve();
789
+ }));
790
+ var promise = mobiusSocket.connect();
791
+
792
+ // Wait for the connect call to setup
793
+ return (0, _promiseTick.default)(mobiusSocket.config.backoffTimeReset).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee2() {
794
+ return _regenerator.default.wrap(function (_context2) {
795
+ while (1) switch (_context2.prev = _context2.next) {
796
+ case 0:
797
+ // By this time backoffCall and mobiusSocket socket should be defined by the
798
+ // 'connect' call
799
+ _testHelperChai.assert.isDefined(mobiusSocket.backoffCalls.get('mobius-websocket-session'), 'MobiusSocket backoffCall is not defined');
800
+ _testHelperChai.assert.isDefined(mobiusSocket.socket, 'MobiusSocket socket is not defined');
801
+ // Calling disconnect will abort the backoffCall, close the socket, and
802
+ // reject the connect
803
+ mobiusSocket.disconnect();
804
+ _testHelperChai.assert.isUndefined(mobiusSocket.backoffCalls.get('mobius-websocket-session'), 'MobiusSocket backoffCall is still defined');
805
+ // The socket will never be unset (which seems bad)
806
+ _testHelperChai.assert.isDefined(mobiusSocket.socket, 'MobiusSocket socket is not defined');
807
+ _context2.next = 1;
808
+ return _testHelperChai.assert.isRejected(promise);
809
+ case 1:
810
+ // connection did not fail, so no last error
811
+ _testHelperChai.assert.isUndefined(mobiusSocket.getLastError());
812
+ case 2:
813
+ case "end":
814
+ return _context2.stop();
815
+ }
816
+ }, _callee2);
817
+ })));
818
+ });
819
+ it('stops the attempt when backoffCall is undefined', function () {
820
+ socketOpenStub.restore();
821
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
822
+ socketOpenStub.returns(_promise.default.resolve());
823
+ var reason;
824
+ mobiusSocket.backoffCalls.clear();
825
+ var promise = mobiusSocket._attemptConnection('ws://example.com', 'mobius-websocket-session', function (_reason) {
826
+ reason = _reason;
827
+ });
828
+ return (0, _promiseTick.default)(mobiusSocket.config.backoffTimeReset).then(function () {
829
+ _testHelperChai.assert.equal(reason.message, "MobiusSocket: prevent socket open when backoffCall no longer defined for ".concat(mobiusSocket.defaultSessionId));
830
+
831
+ // Ensure the promise was actually rejected (short-circuited)
832
+ return _testHelperChai.assert.isRejected(promise);
833
+ });
834
+ });
835
+ it('sets lastError when retrying', function () {
836
+ var realError = new Error('FORCED');
837
+ mobiusSocket.config.initialConnectionMaxRetries = 1;
838
+ socketOpenStub.restore();
839
+ socketOpenStub = _sinon.default.stub(_index.Socket.prototype, 'open');
840
+ socketOpenStub.onCall(0).returns(_promise.default.reject(realError));
841
+ var promise = mobiusSocket.connect();
842
+
843
+ // Wait for the connect call to setup
844
+ return (0, _promiseTick.default)(mobiusSocket.config.backoffTimeReset).then(/*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee3() {
845
+ var error, lastError;
846
+ return _regenerator.default.wrap(function (_context3) {
847
+ while (1) switch (_context3.prev = _context3.next) {
848
+ case 0:
849
+ // Calling disconnect will abort the backoffCall, close the socket, and
850
+ // reject the connect
851
+ mobiusSocket.disconnect();
852
+ _context3.next = 1;
853
+ return _testHelperChai.assert.isRejected(promise);
854
+ case 1:
855
+ error = _context3.sent;
856
+ lastError = mobiusSocket.getLastError();
857
+ _testHelperChai.assert.match(error.message, /MobiusSocket Connection Aborted/);
858
+ _testHelperChai.assert.isDefined(lastError);
859
+ _testHelperChai.assert.equal(lastError, realError);
860
+ case 2:
861
+ case "end":
862
+ return _context3.stop();
863
+ }
864
+ }, _callee3);
865
+ })));
866
+ });
867
+ });
868
+ });
869
+ describe('#sendWssRequest()', function () {
870
+ beforeEach(function () {
871
+ mobiusSocket.config.wssResponseTimeout = 100;
872
+ });
873
+ it('resolves when a matching response_event arrives', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
874
+ var requestPromise, requestPayload, response;
875
+ return _regenerator.default.wrap(function (_context4) {
876
+ while (1) switch (_context4.prev = _context4.next) {
877
+ case 0:
878
+ _context4.next = 1;
879
+ return mobiusSocket.connect();
880
+ case 1:
881
+ requestPromise = mobiusSocket.sendWssRequest({
882
+ type: 'auth',
883
+ data: {
884
+ token: 'test'
885
+ }
886
+ });
887
+ _context4.next = 2;
888
+ return (0, _promiseTick.default)();
889
+ case 2:
890
+ requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
891
+ _testHelperChai.assert.equal(requestPayload.data.token, 'test');
892
+ mockWebSocket.emit('message', {
893
+ data: (0, _stringify.default)({
894
+ type: 'response_event',
895
+ subtype: 'auth',
896
+ trackingId: requestPayload.trackingId,
897
+ statusCode: 200,
898
+ statusMessage: 'OK'
899
+ })
900
+ });
901
+ _context4.next = 3;
902
+ return requestPromise;
903
+ case 3:
904
+ response = _context4.sent;
905
+ _testHelperChai.assert.equal(response.type, 'response_event');
906
+ _testHelperChai.assert.equal(response.subtype, 'auth');
907
+ _testHelperChai.assert.equal(response.trackingId, requestPayload.trackingId);
908
+ _testHelperChai.assert.equal(response.statusCode, 200);
909
+ case 4:
910
+ case "end":
911
+ return _context4.stop();
912
+ }
913
+ }, _callee4);
914
+ })));
915
+ it('strips the Bearer prefix from connect-time auth token', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5() {
916
+ var authPayload;
917
+ return _regenerator.default.wrap(function (_context5) {
918
+ while (1) switch (_context5.prev = _context5.next) {
919
+ case 0:
920
+ _context5.next = 1;
921
+ return mobiusSocket.connect();
922
+ case 1:
923
+ authPayload = JSON.parse(mockWebSocket.send.firstCall.args[0]);
924
+ _testHelperChai.assert.equal(authPayload.type, _constants.MESSAGE_TYPES.AUTH);
925
+ _testHelperChai.assert.equal(authPayload.data.token, 'FAKE');
926
+ case 2:
927
+ case "end":
928
+ return _context5.stop();
929
+ }
930
+ }, _callee5);
931
+ })));
932
+ it('rejects when a matching response_event is non-2xx', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6() {
933
+ var requestPromise, requestPayload, error;
934
+ return _regenerator.default.wrap(function (_context6) {
935
+ while (1) switch (_context6.prev = _context6.next) {
936
+ case 0:
937
+ _context6.next = 1;
938
+ return mobiusSocket.connect();
939
+ case 1:
940
+ requestPromise = mobiusSocket.sendWssRequest({
941
+ type: 'auth',
942
+ data: {
943
+ token: 'test'
944
+ }
945
+ });
946
+ _context6.next = 2;
947
+ return (0, _promiseTick.default)();
948
+ case 2:
949
+ requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
950
+ mockWebSocket.emit('message', {
951
+ data: (0, _stringify.default)({
952
+ type: 'response_event',
953
+ subtype: 'auth',
954
+ trackingId: requestPayload.trackingId,
955
+ statusCode: 403,
956
+ statusMessage: 'Forbidden'
957
+ })
958
+ });
959
+ _context6.next = 3;
960
+ return _testHelperChai.assert.isRejected(requestPromise);
961
+ case 3:
962
+ error = _context6.sent;
963
+ _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
964
+ _testHelperChai.assert.equal(error.statusCode, 403);
965
+ _testHelperChai.assert.equal(error.statusMessage, 'Forbidden');
966
+ _testHelperChai.assert.equal(error.trackingId, requestPayload.trackingId);
967
+ case 4:
968
+ case "end":
969
+ return _context6.stop();
970
+ }
971
+ }, _callee6);
972
+ })));
973
+ it('rejects when the matching response does not arrive before timeout', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee7() {
974
+ var requestPromise, error;
975
+ return _regenerator.default.wrap(function (_context7) {
976
+ while (1) switch (_context7.prev = _context7.next) {
977
+ case 0:
978
+ _context7.next = 1;
979
+ return mobiusSocket.connect();
980
+ case 1:
981
+ requestPromise = mobiusSocket.sendWssRequest({
982
+ type: 'auth',
983
+ data: {
984
+ token: 'test'
985
+ }
986
+ });
987
+ jest.advanceTimersByTime(101);
988
+ _context7.next = 2;
989
+ return (0, _promiseTick.default)();
990
+ case 2:
991
+ _context7.next = 3;
992
+ return _testHelperChai.assert.isRejected(requestPromise);
993
+ case 3:
994
+ error = _context7.sent;
995
+ _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
996
+ _testHelperChai.assert.equal(error.statusCode, 408);
997
+ _testHelperChai.assert.equal(error.statusMessage, 'Mobius websocket response timed out');
998
+ case 4:
999
+ case "end":
1000
+ return _context7.stop();
1001
+ }
1002
+ }, _callee7);
1003
+ })));
1004
+ it('rejects with a clear error when the matching response is missing status code', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee8() {
1005
+ var requestPromise, requestPayload, error;
1006
+ return _regenerator.default.wrap(function (_context8) {
1007
+ while (1) switch (_context8.prev = _context8.next) {
1008
+ case 0:
1009
+ _context8.next = 1;
1010
+ return mobiusSocket.connect();
1011
+ case 1:
1012
+ requestPromise = mobiusSocket.sendWssRequest({
1013
+ type: 'auth',
1014
+ data: {
1015
+ token: 'test'
1016
+ }
1017
+ });
1018
+ _context8.next = 2;
1019
+ return (0, _promiseTick.default)();
1020
+ case 2:
1021
+ requestPayload = JSON.parse(mockWebSocket.send.lastCall.args[0]);
1022
+ mockWebSocket.emit('message', {
1023
+ data: (0, _stringify.default)({
1024
+ type: 'response_event',
1025
+ subtype: 'auth',
1026
+ trackingId: requestPayload.trackingId
1027
+ })
1028
+ });
1029
+ _context8.next = 3;
1030
+ return _testHelperChai.assert.isRejected(requestPromise);
1031
+ case 3:
1032
+ error = _context8.sent;
1033
+ _testHelperChai.assert.equal(error.name, 'MobiusSocketResponseError');
1034
+ _testHelperChai.assert.isUndefined(error.statusCode);
1035
+ _testHelperChai.assert.equal(error.statusMessage, 'Socket response missing status code');
1036
+ case 4:
1037
+ case "end":
1038
+ return _context8.stop();
1039
+ }
1040
+ }, _callee8);
1041
+ })));
1042
+ it('rejects pending requests when the active socket closes', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee9() {
1043
+ var requestPromise, error;
1044
+ return _regenerator.default.wrap(function (_context9) {
1045
+ while (1) switch (_context9.prev = _context9.next) {
1046
+ case 0:
1047
+ _context9.next = 1;
1048
+ return mobiusSocket.connect();
1049
+ case 1:
1050
+ requestPromise = mobiusSocket.sendWssRequest({
1051
+ type: 'auth',
1052
+ data: {
1053
+ token: 'test'
1054
+ }
1055
+ });
1056
+ mockWebSocket.emit('close', {
1057
+ code: 1003,
1058
+ reason: 'service rejected request'
1059
+ });
1060
+ _context9.next = 2;
1061
+ return _testHelperChai.assert.isRejected(requestPromise);
1062
+ case 2:
1063
+ error = _context9.sent;
1064
+ _testHelperChai.assert.instanceOf(error, _index.ConnectionError);
1065
+ _testHelperChai.assert.equal(error.code, 1003);
1066
+ _testHelperChai.assert.equal(error.reason, 'service rejected request');
1067
+ case 3:
1068
+ case "end":
1069
+ return _context9.stop();
1070
+ }
1071
+ }, _callee9);
1072
+ })));
1073
+ it('rejects array payloads', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee0() {
1074
+ var error;
1075
+ return _regenerator.default.wrap(function (_context0) {
1076
+ while (1) switch (_context0.prev = _context0.next) {
1077
+ case 0:
1078
+ _context0.next = 1;
1079
+ return mobiusSocket.connect();
1080
+ case 1:
1081
+ _context0.next = 2;
1082
+ return _testHelperChai.assert.isRejected(mobiusSocket.sendWssRequest([]));
1083
+ case 2:
1084
+ error = _context0.sent;
1085
+ _testHelperChai.assert.equal(error.message, '`payload` is required');
1086
+ case 3:
1087
+ case "end":
1088
+ return _context0.stop();
1089
+ }
1090
+ }, _callee0);
1091
+ })));
1092
+ });
1093
+ describe('#getConnectedWebSocketUrl()', function () {
1094
+ it('returns the connected websocket url for the session', function () {
1095
+ mobiusSocket.sockets.set(mobiusSocket.defaultSessionId, {
1096
+ connected: true,
1097
+ url: 'ws://connected-url.com'
1098
+ });
1099
+ _testHelperChai.assert.equal(mobiusSocket.getConnectedWebSocketUrl(), 'ws://connected-url.com');
1100
+ });
1101
+ it('returns undefined when the session is not connected', function () {
1102
+ mobiusSocket.sockets.set(mobiusSocket.defaultSessionId, {
1103
+ connected: false,
1104
+ url: 'ws://disconnected-url.com'
1105
+ });
1106
+ _testHelperChai.assert.isUndefined(mobiusSocket.getConnectedWebSocketUrl());
1107
+ });
1108
+ });
1109
+ describe('#_emit()', function () {
1110
+ it('emits Error-safe events and log the error with the call parameters', function () {
1111
+ var error = 'error';
1112
+ var event = {
1113
+ data: 'some data'
1114
+ };
1115
+ mobiusSocket.on('break', function () {
1116
+ throw error;
1117
+ });
1118
+ _sinon.default.stub(mobiusSocket.logger, 'error');
1119
+ return _promise.default.resolve(mobiusSocket._emit(mobiusSocket.defaultSessionId, 'break', event)).then(function (res) {
1120
+ _testHelperChai.assert.calledWith(mobiusSocket.logger.error, 'MobiusSocket: error occurred in event handler:', error, ' with args: ', [mobiusSocket.defaultSessionId, 'break', event]);
1121
+ return res;
1122
+ });
1123
+ });
1124
+ });
1125
+ describe('#_applyOverrides()', function () {
1126
+ var lastSeenActivityDate = 'Some date';
1127
+ var lastReadableActivityDate = 'Some other date';
1128
+ it('merges a single header field with data', function () {
1129
+ var envelope = {
1130
+ headers: {
1131
+ 'data.activity.target.lastSeenActivityDate': lastSeenActivityDate
1132
+ },
1133
+ data: {
1134
+ activity: {}
1135
+ }
1136
+ };
1137
+ mobiusSocket._applyOverrides(envelope);
1138
+ _testHelperChai.assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
1139
+ });
1140
+ it('merges a multiple header fields with data', function () {
1141
+ var envelope = {
1142
+ headers: {
1143
+ 'data.activity.target.lastSeenActivityDate': lastSeenActivityDate,
1144
+ 'data.activity.target.lastReadableActivityDate': lastReadableActivityDate
1145
+ },
1146
+ data: {
1147
+ activity: {}
1148
+ }
1149
+ };
1150
+ mobiusSocket._applyOverrides(envelope);
1151
+ _testHelperChai.assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
1152
+ _testHelperChai.assert.equal(envelope.data.activity.target.lastReadableActivityDate, lastReadableActivityDate);
1153
+ });
1154
+ it('merges headers when MobiusSocket messages arrive', function () {
1155
+ var envelope = {
1156
+ headers: {
1157
+ 'data.activity.target.lastSeenActivityDate': lastSeenActivityDate
1158
+ },
1159
+ data: {
1160
+ activity: {}
1161
+ }
1162
+ };
1163
+ mobiusSocket._applyOverrides(envelope);
1164
+ _testHelperChai.assert.equal(envelope.data.activity.target.lastSeenActivityDate, lastSeenActivityDate);
1165
+ });
1166
+ });
1167
+ describe('#_setTimeOffset', function () {
1168
+ it('sets mercuryTimeOffset based on the difference between wsWriteTimestamp and now', function () {
1169
+ var event = {
1170
+ data: {
1171
+ wsWriteTimestamp: (0, _now.default)() - 60000
1172
+ }
1173
+ };
1174
+ _testHelperChai.assert.isUndefined(mobiusSocket.mercuryTimeOffset);
1175
+ mobiusSocket._setTimeOffset('mobius-websocket-session', event);
1176
+ _testHelperChai.assert.isDefined(mobiusSocket.mercuryTimeOffset);
1177
+ _testHelperChai.assert.isTrue(mobiusSocket.mercuryTimeOffset > 0);
1178
+ });
1179
+ it('handles negative offsets', function () {
1180
+ var event = {
1181
+ data: {
1182
+ wsWriteTimestamp: (0, _now.default)() + 60000
1183
+ }
1184
+ };
1185
+ mobiusSocket._setTimeOffset('mobius-websocket-session', event);
1186
+ _testHelperChai.assert.isTrue(mobiusSocket.mercuryTimeOffset < 0);
1187
+ });
1188
+ it('handles invalid wsWriteTimestamp', function () {
1189
+ var invalidTimestamps = [null, -1, 'invalid', undefined];
1190
+ invalidTimestamps.forEach(function (invalidTimestamp) {
1191
+ var event = {
1192
+ data: {
1193
+ wsWriteTimestamp: invalidTimestamp
1194
+ }
1195
+ };
1196
+ mobiusSocket._setTimeOffset('mobius-websocket-session', event);
1197
+ _testHelperChai.assert.isUndefined(mobiusSocket.mercuryTimeOffset);
1198
+ });
1199
+ });
1200
+ });
1201
+ describe('#_prepareUrl()', function () {
1202
+ it('returns the provided URL as-is (no Mercury URL transforms)', function () {
1203
+ return mobiusSocket._prepareUrl('ws://provided.com').then(function (wsUrl) {
1204
+ _testHelperChai.assert.equal(wsUrl, 'ws://provided.com');
1205
+ });
1206
+ });
1207
+ it('falls back to device webSocketUrl when no URL is provided', function () {
1208
+ return mobiusSocket._prepareUrl().then(function (wsUrl) {
1209
+ _testHelperChai.assert.equal(wsUrl, 'ws://example.com');
1210
+ });
1211
+ });
1212
+ });
1213
+ describe('shutdown protocol', function () {
1214
+ describe('#_handleImminentShutdown()', function () {
1215
+ var connectWithBackoffStub;
1216
+ var sessionId = 'mobius-websocket-session';
1217
+ beforeEach(function () {
1218
+ mobiusSocket.connected = true;
1219
+ mobiusSocket.sockets.set(sessionId, {
1220
+ url: 'ws://old-socket.com',
1221
+ removeAllListeners: _sinon.default.stub()
1222
+ });
1223
+ mobiusSocket.socket = mobiusSocket.sockets.get(sessionId);
1224
+ connectWithBackoffStub = _sinon.default.stub(mobiusSocket, '_connectWithBackoff');
1225
+ connectWithBackoffStub.returns(_promise.default.resolve());
1226
+ _sinon.default.stub(mobiusSocket, '_emit');
1227
+ });
1228
+ afterEach(function () {
1229
+ connectWithBackoffStub.restore();
1230
+ mobiusSocket._emit.restore();
1231
+ mobiusSocket.sockets.clear();
1232
+ });
1233
+ it('should be idempotent - no-op if already in progress', function () {
1234
+ // Simulate an existing switchover in progress by seeding the backoff map
1235
+ mobiusSocket._shutdownSwitchoverBackoffCalls.set(sessionId, {
1236
+ placeholder: true
1237
+ });
1238
+ mobiusSocket._handleImminentShutdown(sessionId);
1239
+ _testHelperChai.assert.notCalled(connectWithBackoffStub);
1240
+ });
1241
+ it('should set switchover flags when called', function () {
1242
+ mobiusSocket._handleImminentShutdown(sessionId);
1243
+ _testHelperChai.assert.calledOnce(connectWithBackoffStub);
1244
+ var callArgs = connectWithBackoffStub.firstCall.args;
1245
+ _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
1246
+ _testHelperChai.assert.equal(callArgs[1], sessionId); // sessionId
1247
+ _testHelperChai.assert.isObject(callArgs[2]); // context
1248
+ _testHelperChai.assert.isTrue(callArgs[2].isShutdownSwitchover);
1249
+ _testHelperChai.assert.isObject(callArgs[2].attemptOptions);
1250
+ _testHelperChai.assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1251
+ });
1252
+ it('should call _connectWithBackoff with correct parameters', function (done) {
1253
+ mobiusSocket._handleImminentShutdown(sessionId);
1254
+ process.nextTick(function () {
1255
+ _testHelperChai.assert.calledOnce(connectWithBackoffStub);
1256
+ var callArgs = connectWithBackoffStub.firstCall.args;
1257
+ _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
1258
+ _testHelperChai.assert.equal(callArgs[1], sessionId); // sessionId
1259
+ _testHelperChai.assert.isObject(callArgs[2]); // context
1260
+ _testHelperChai.assert.isTrue(callArgs[2].isShutdownSwitchover);
1261
+ _testHelperChai.assert.isObject(callArgs[2].attemptOptions);
1262
+ _testHelperChai.assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1263
+ done();
1264
+ });
1265
+ });
1266
+ it('should handle exceptions during switchover', function () {
1267
+ connectWithBackoffStub.restore();
1268
+ _sinon.default.stub(mobiusSocket, '_connectWithBackoff').throws(new Error('Connection failed'));
1269
+ mobiusSocket._handleImminentShutdown(sessionId);
1270
+
1271
+ // When an exception happens synchronously, the placeholder entry
1272
+ // should be removed from the map.
1273
+ var switchoverCall = mobiusSocket._shutdownSwitchoverBackoffCalls.get(sessionId);
1274
+ _testHelperChai.assert.isUndefined(switchoverCall);
1275
+ mobiusSocket._connectWithBackoff.restore();
1276
+ });
1277
+ });
1278
+ describe('#_onmessage() with shutdown message', function () {
1279
+ beforeEach(function () {
1280
+ _sinon.default.stub(mobiusSocket, '_handleImminentShutdown');
1281
+ _sinon.default.stub(mobiusSocket, '_emit');
1282
+ _sinon.default.stub(mobiusSocket, '_setTimeOffset');
1283
+ });
1284
+ afterEach(function () {
1285
+ mobiusSocket._handleImminentShutdown.restore();
1286
+ mobiusSocket._emit.restore();
1287
+ mobiusSocket._setTimeOffset.restore();
1288
+ });
1289
+ it('should trigger _handleImminentShutdown on shutdown message', function () {
1290
+ var shutdownEvent = {
1291
+ data: {
1292
+ type: 'shutdown'
1293
+ }
1294
+ };
1295
+ var result = mobiusSocket._onmessage(mobiusSocket.defaultSessionId, shutdownEvent);
1296
+ _testHelperChai.assert.calledOnce(mobiusSocket._handleImminentShutdown);
1297
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event:mercury_shutdown_imminent', shutdownEvent.data);
1298
+ _testHelperChai.assert.instanceOf(result, _promise.default);
1299
+ });
1300
+ it('should handle shutdown message without additional data gracefully', function () {
1301
+ var shutdownEvent = {
1302
+ data: {
1303
+ type: 'shutdown'
1304
+ }
1305
+ };
1306
+ mobiusSocket._onmessage(mobiusSocket.defaultSessionId, shutdownEvent);
1307
+ _testHelperChai.assert.calledOnce(mobiusSocket._handleImminentShutdown);
1308
+ });
1309
+ it('should not trigger shutdown handling for non-shutdown messages', function () {
1310
+ var regularEvent = {
1311
+ data: {
1312
+ type: 'regular',
1313
+ data: {
1314
+ eventType: 'conversation.activity'
1315
+ }
1316
+ }
1317
+ };
1318
+ mobiusSocket._onmessage(mobiusSocket.defaultSessionId, regularEvent);
1319
+ _testHelperChai.assert.notCalled(mobiusSocket._handleImminentShutdown);
1320
+ });
1321
+ });
1322
+ describe('#_onmessage() with missing data or eventType', function () {
1323
+ beforeEach(function () {
1324
+ _sinon.default.stub(mobiusSocket, '_emit');
1325
+ _sinon.default.stub(mobiusSocket, '_setTimeOffset');
1326
+ _sinon.default.stub(mobiusSocket, '_applyOverrides');
1327
+ });
1328
+ afterEach(function () {
1329
+ mobiusSocket._emit.restore();
1330
+ mobiusSocket._setTimeOffset.restore();
1331
+ mobiusSocket._applyOverrides.restore();
1332
+ });
1333
+ it('should not throw when envelope.data is undefined', function () {
1334
+ var event = {
1335
+ data: {
1336
+ type: 'someType'
1337
+ // no nested data property
1338
+ }
1339
+ };
1340
+ var result = mobiusSocket._onmessage(mobiusSocket.defaultSessionId, event);
1341
+ _testHelperChai.assert.instanceOf(result, _promise.default);
1342
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event', event.data);
1343
+ });
1344
+ it('should not throw when data.eventType is undefined', function () {
1345
+ var event = {
1346
+ data: {
1347
+ type: 'someType',
1348
+ data: {
1349
+ // no eventType property
1350
+ someField: 'value'
1351
+ }
1352
+ }
1353
+ };
1354
+ var result = mobiusSocket._onmessage(mobiusSocket.defaultSessionId, event);
1355
+ _testHelperChai.assert.instanceOf(result, _promise.default);
1356
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event', event.data);
1357
+ });
1358
+ it('should emit generic event for messages without eventType (e.g. subscription responses)', function () {
1359
+ var event = {
1360
+ data: {
1361
+ id: 'msg-123',
1362
+ sequenceNumber: 5,
1363
+ data: {
1364
+ statusCode: 200
1365
+ }
1366
+ }
1367
+ };
1368
+ var result = mobiusSocket._onmessage(mobiusSocket.defaultSessionId, event);
1369
+ _testHelperChai.assert.instanceOf(result, _promise.default);
1370
+ _testHelperChai.assert.calledOnce(mobiusSocket._emit);
1371
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event', event.data);
1372
+ });
1373
+ it('should still process messages with a valid eventType', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee1() {
1374
+ var event;
1375
+ return _regenerator.default.wrap(function (_context1) {
1376
+ while (1) switch (_context1.prev = _context1.next) {
1377
+ case 0:
1378
+ event = {
1379
+ data: {
1380
+ data: {
1381
+ eventType: 'conversation.activity'
1382
+ }
1383
+ }
1384
+ };
1385
+ _context1.next = 1;
1386
+ return mobiusSocket._onmessage(mobiusSocket.defaultSessionId, event);
1387
+ case 1:
1388
+ // Normal flow emits namespace-specific events after processing handlers.
1389
+ // The early-return guard only emits 'event', so asserting these proves the normal path was taken.
1390
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event:conversation', event.data);
1391
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'event:conversation.activity', event.data);
1392
+ case 2:
1393
+ case "end":
1394
+ return _context1.stop();
1395
+ }
1396
+ }, _callee1);
1397
+ })));
1398
+ });
1399
+ describe('#_onmessage() async_event deduplication', function () {
1400
+ var originalDedupCacheMaxSize;
1401
+ beforeEach(function () {
1402
+ originalDedupCacheMaxSize = mobiusSocket.config.dedupCacheMaxSize;
1403
+ });
1404
+ afterEach(function () {
1405
+ mobiusSocket.config.dedupCacheMaxSize = originalDedupCacheMaxSize;
1406
+ });
1407
+ it('suppresses duplicate async_event messages for the same session', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee10() {
1408
+ var emitSpy;
1409
+ return _regenerator.default.wrap(function (_context10) {
1410
+ while (1) switch (_context10.prev = _context10.next) {
1411
+ case 0:
1412
+ emitSpy = _sinon.default.spy(mobiusSocket, '_emit');
1413
+ _context10.next = 1;
1414
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('evt-1'));
1415
+ case 1:
1416
+ _context10.next = 2;
1417
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('evt-1'));
1418
+ case 2:
1419
+ _testHelperChai.assert.equal(countGenericEventEmits(emitSpy, 'session-a'), 1);
1420
+ emitSpy.restore();
1421
+ case 3:
1422
+ case "end":
1423
+ return _context10.stop();
1424
+ }
1425
+ }, _callee10);
1426
+ })));
1427
+ it('suppresses duplicate async_event messages across socket replacement without disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee11() {
1428
+ var sessionId, emitSpy;
1429
+ return _regenerator.default.wrap(function (_context11) {
1430
+ while (1) switch (_context11.prev = _context11.next) {
1431
+ case 0:
1432
+ sessionId = 'session-a';
1433
+ emitSpy = _sinon.default.spy(mobiusSocket, '_emit');
1434
+ mobiusSocket.sockets.set(sessionId, createSessionSocket());
1435
+ _context11.next = 1;
1436
+ return mobiusSocket._onmessage(sessionId, createAsyncEvent('evt-2'));
1437
+ case 1:
1438
+ mobiusSocket.sockets.set(sessionId, createSessionSocket());
1439
+ _context11.next = 2;
1440
+ return mobiusSocket._onmessage(sessionId, createAsyncEvent('evt-2'));
1441
+ case 2:
1442
+ _testHelperChai.assert.equal(countGenericEventEmits(emitSpy, sessionId), 1);
1443
+ emitSpy.restore();
1444
+ case 3:
1445
+ case "end":
1446
+ return _context11.stop();
1447
+ }
1448
+ }, _callee11);
1449
+ })));
1450
+ it('evicts only the oldest eventId when the dedup cache exceeds max size', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee12() {
1451
+ var emitSpy;
1452
+ return _regenerator.default.wrap(function (_context12) {
1453
+ while (1) switch (_context12.prev = _context12.next) {
1454
+ case 0:
1455
+ emitSpy = _sinon.default.spy(mobiusSocket, '_emit');
1456
+ mobiusSocket.config.dedupCacheMaxSize = 3;
1457
+ _context12.next = 1;
1458
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e1'));
1459
+ case 1:
1460
+ _context12.next = 2;
1461
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e2'));
1462
+ case 2:
1463
+ _context12.next = 3;
1464
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e3'));
1465
+ case 3:
1466
+ _context12.next = 4;
1467
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e4'));
1468
+ case 4:
1469
+ _context12.next = 5;
1470
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e2'));
1471
+ case 5:
1472
+ _context12.next = 6;
1473
+ return mobiusSocket._onmessage('session-a', createAsyncEvent('e1'));
1474
+ case 6:
1475
+ _testHelperChai.assert.equal(countGenericEventEmits(emitSpy, 'session-a'), 5);
1476
+ emitSpy.restore();
1477
+ case 7:
1478
+ case "end":
1479
+ return _context12.stop();
1480
+ }
1481
+ }, _callee12);
1482
+ })));
1483
+ it('clears the session dedup cache on disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee13() {
1484
+ var sessionId, emitSpy;
1485
+ return _regenerator.default.wrap(function (_context13) {
1486
+ while (1) switch (_context13.prev = _context13.next) {
1487
+ case 0:
1488
+ sessionId = 'session-a';
1489
+ emitSpy = _sinon.default.spy(mobiusSocket, '_emit');
1490
+ _context13.next = 1;
1491
+ return mobiusSocket._onmessage(sessionId, createAsyncEvent('evt-3'));
1492
+ case 1:
1493
+ mobiusSocket.sockets.set(sessionId, createSessionSocket());
1494
+ _context13.next = 2;
1495
+ return mobiusSocket.disconnect(undefined, sessionId);
1496
+ case 2:
1497
+ _context13.next = 3;
1498
+ return mobiusSocket._onmessage(sessionId, createAsyncEvent('evt-3'));
1499
+ case 3:
1500
+ _testHelperChai.assert.equal(countGenericEventEmits(emitSpy, sessionId), 2);
1501
+ emitSpy.restore();
1502
+ case 4:
1503
+ case "end":
1504
+ return _context13.stop();
1505
+ }
1506
+ }, _callee13);
1507
+ })));
1508
+ });
1509
+ describe('#_getEventHandlers()', function () {
1510
+ it('should return an empty array when eventType is undefined', function () {
1511
+ var result = mobiusSocket._getEventHandlers(undefined);
1512
+ _testHelperChai.assert.deepEqual(result, []);
1513
+ });
1514
+ it('should return an empty array when eventType is null', function () {
1515
+ var result = mobiusSocket._getEventHandlers(null);
1516
+ _testHelperChai.assert.deepEqual(result, []);
1517
+ });
1518
+ it('should return an empty array when eventType is an empty string', function () {
1519
+ var result = mobiusSocket._getEventHandlers('');
1520
+ _testHelperChai.assert.deepEqual(result, []);
1521
+ });
1522
+ it('should return an empty array when namespace is not registered', function () {
1523
+ var result = mobiusSocket._getEventHandlers('unknownNamespace.someEvent');
1524
+ _testHelperChai.assert.deepEqual(result, []);
1525
+ });
1526
+ });
1527
+ describe('#_onclose() with code 4001 (shutdown replacement)', function () {
1528
+ var mockSocket;
1529
+ var anotherSocket;
1530
+ beforeEach(function () {
1531
+ mockSocket = {
1532
+ url: 'ws://active-socket.com',
1533
+ removeAllListeners: _sinon.default.stub()
1534
+ };
1535
+ anotherSocket = {
1536
+ url: 'ws://old-socket.com',
1537
+ removeAllListeners: _sinon.default.stub()
1538
+ };
1539
+ mobiusSocket.socket = mockSocket;
1540
+ mobiusSocket.sockets.set(mobiusSocket.defaultSessionId, mockSocket);
1541
+ mobiusSocket.connected = true;
1542
+ _sinon.default.stub(mobiusSocket, '_emit');
1543
+ _sinon.default.stub(mobiusSocket, '_reconnect');
1544
+ });
1545
+ afterEach(function () {
1546
+ mobiusSocket._emit.restore();
1547
+ mobiusSocket._reconnect.restore();
1548
+ });
1549
+ it('should handle active socket close with 4001 - permanent failure', function () {
1550
+ var closeEvent = {
1551
+ code: 4001,
1552
+ reason: 'replaced during shutdown'
1553
+ };
1554
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, mockSocket);
1555
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'offline.permanent', closeEvent);
1556
+ _testHelperChai.assert.notCalled(mobiusSocket._reconnect); // No reconnect for 4001 on active socket
1557
+ _testHelperChai.assert.isFalse(mobiusSocket.connected);
1558
+ });
1559
+ it('should handle non-active socket close with 4001 - no reconnect needed', function () {
1560
+ var closeEvent = {
1561
+ code: 4001,
1562
+ reason: 'replaced during shutdown'
1563
+ };
1564
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, anotherSocket);
1565
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'offline.replaced', closeEvent);
1566
+ _testHelperChai.assert.notCalled(mobiusSocket._reconnect);
1567
+ _testHelperChai.assert.isTrue(mobiusSocket.connected); // Should remain connected
1568
+ _testHelperChai.assert.strictEqual(mobiusSocket.socket, mockSocket);
1569
+ });
1570
+ it('should distinguish between active and non-active socket closes', function () {
1571
+ var closeEvent = {
1572
+ code: 4001,
1573
+ reason: 'replaced during shutdown'
1574
+ };
1575
+
1576
+ // Test non-active socket
1577
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, anotherSocket);
1578
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'offline.replaced', closeEvent);
1579
+
1580
+ // Reset the spy call history
1581
+ mobiusSocket._emit.resetHistory();
1582
+
1583
+ // Test active socket
1584
+ mobiusSocket.sockets.set(mobiusSocket.defaultSessionId, mockSocket);
1585
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, mockSocket);
1586
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'offline.permanent', closeEvent);
1587
+ });
1588
+ it('should handle missing sourceSocket parameter (treats as non-active)', function () {
1589
+ var closeEvent = {
1590
+ code: 4001,
1591
+ reason: 'replaced during shutdown'
1592
+ };
1593
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent); // No sourceSocket parameter
1594
+
1595
+ // With simplified logic, undefined !== this.socket, so isActiveSocket = false
1596
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, mobiusSocket.defaultSessionId, 'offline.replaced', closeEvent);
1597
+ _testHelperChai.assert.notCalled(mobiusSocket._reconnect);
1598
+ });
1599
+ it('should clean up event listeners from non-active socket when it closes', function () {
1600
+ var closeEvent = {
1601
+ code: 4001,
1602
+ reason: 'replaced during shutdown'
1603
+ };
1604
+
1605
+ // Close non-active socket (not the active one)
1606
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, anotherSocket);
1607
+
1608
+ // Verify listeners were removed from the old socket
1609
+ // The _onclose method checks if sourceSocket !== this.socket (non-active)
1610
+ // and then calls removeAllListeners in the else branch
1611
+ _testHelperChai.assert.calledOnce(anotherSocket.removeAllListeners);
1612
+ });
1613
+ it('should not clean up listeners from active socket listeners until close handler runs', function () {
1614
+ var closeEvent = {
1615
+ code: 4001,
1616
+ reason: 'replaced during shutdown'
1617
+ };
1618
+
1619
+ // Close active socket
1620
+ mobiusSocket._onclose(mobiusSocket.defaultSessionId, closeEvent, mockSocket);
1621
+
1622
+ // Verify listeners were removed from active socket
1623
+ _testHelperChai.assert.calledOnce(mockSocket.removeAllListeners);
1624
+ });
1625
+ });
1626
+ describe('shutdown switchover with retry logic', function () {
1627
+ var connectWithBackoffStub;
1628
+ var sessionId = 'mobius-websocket-session';
1629
+ beforeEach(function () {
1630
+ mobiusSocket.connected = true;
1631
+ mobiusSocket.sockets.set(sessionId, {
1632
+ url: 'ws://old-socket.com',
1633
+ removeAllListeners: _sinon.default.stub()
1634
+ });
1635
+ mobiusSocket.socket = mobiusSocket.sockets.get(sessionId);
1636
+ connectWithBackoffStub = _sinon.default.stub(mobiusSocket, '_connectWithBackoff');
1637
+ _sinon.default.stub(mobiusSocket, '_emit');
1638
+ });
1639
+ afterEach(function () {
1640
+ connectWithBackoffStub.restore();
1641
+ mobiusSocket._emit.restore();
1642
+ mobiusSocket.sockets.clear();
1643
+ });
1644
+ it('should call _connectWithBackoff with shutdown switchover context', function (done) {
1645
+ connectWithBackoffStub.returns(_promise.default.resolve());
1646
+ mobiusSocket._handleImminentShutdown(sessionId);
1647
+ process.nextTick(function () {
1648
+ _testHelperChai.assert.calledOnce(connectWithBackoffStub);
1649
+ var callArgs = connectWithBackoffStub.firstCall.args;
1650
+ _testHelperChai.assert.isUndefined(callArgs[0]); // webSocketUrl
1651
+ _testHelperChai.assert.equal(callArgs[1], sessionId);
1652
+ _testHelperChai.assert.isObject(callArgs[2]);
1653
+ _testHelperChai.assert.isTrue(callArgs[2].isShutdownSwitchover);
1654
+ _testHelperChai.assert.isObject(callArgs[2].attemptOptions);
1655
+ _testHelperChai.assert.isTrue(callArgs[2].attemptOptions.isShutdownSwitchover);
1656
+ done();
1657
+ });
1658
+ });
1659
+ it('should set _shutdownSwitchoverInProgress flag during switchover', function () {
1660
+ // With the new behavior, "in progress" is represented by the presence
1661
+ // of an entry in _shutdownSwitchoverBackoffCalls.
1662
+ // Since _connectWithBackoff is stubbed in this suite, simulate its side-effect
1663
+ // of seeding the backoff-call map entry.
1664
+ connectWithBackoffStub.callsFake(function () {
1665
+ mobiusSocket._shutdownSwitchoverBackoffCalls.set(sessionId, {
1666
+ placeholder: true
1667
+ });
1668
+ return new _promise.default(function () {}); // Never resolves
1669
+ });
1670
+ mobiusSocket._handleImminentShutdown(sessionId);
1671
+ var switchoverBackoffCall = mobiusSocket._shutdownSwitchoverBackoffCalls.get(sessionId);
1672
+ _testHelperChai.assert.isOk(switchoverBackoffCall);
1673
+ });
1674
+ it('should emit success event when switchover completes', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee14() {
1675
+ var emitCalls, hasCompleteEvent;
1676
+ return _regenerator.default.wrap(function (_context14) {
1677
+ while (1) switch (_context14.prev = _context14.next) {
1678
+ case 0:
1679
+ connectWithBackoffStub.callsFake(function (url, sid, context) {
1680
+ if (context && context.attemptOptions && context.attemptOptions.onSuccess) {
1681
+ var mockSocket = {
1682
+ url: 'ws://new-socket.com'
1683
+ };
1684
+ context.attemptOptions.onSuccess(mockSocket, 'ws://new-socket.com');
1685
+ }
1686
+ return _promise.default.resolve();
1687
+ });
1688
+ mobiusSocket._handleImminentShutdown(sessionId);
1689
+ _context14.next = 1;
1690
+ return (0, _promiseTick.default)(50);
1691
+ case 1:
1692
+ emitCalls = mobiusSocket._emit.getCalls();
1693
+ hasCompleteEvent = emitCalls.some(function (call) {
1694
+ return call.args[0] === sessionId && call.args[1] === 'event:mercury_shutdown_switchover_complete';
1695
+ });
1696
+ _testHelperChai.assert.isTrue(hasCompleteEvent, 'Should emit switchover complete event');
1697
+ case 2:
1698
+ case "end":
1699
+ return _context14.stop();
1700
+ }
1701
+ }, _callee14);
1702
+ })));
1703
+ it('should emit failure event when switchover exhausts retries', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee15() {
1704
+ var testError, emitCalls, hasFailureEvent;
1705
+ return _regenerator.default.wrap(function (_context15) {
1706
+ while (1) switch (_context15.prev = _context15.next) {
1707
+ case 0:
1708
+ testError = new Error('Connection failed');
1709
+ connectWithBackoffStub.returns(_promise.default.reject(testError));
1710
+ mobiusSocket._handleImminentShutdown(sessionId);
1711
+ _context15.next = 1;
1712
+ return (0, _promiseTick.default)(50);
1713
+ case 1:
1714
+ emitCalls = mobiusSocket._emit.getCalls();
1715
+ hasFailureEvent = emitCalls.some(function (call) {
1716
+ return call.args[0] === sessionId && call.args[1] === 'event:mercury_shutdown_switchover_failed' && call.args[2] && call.args[2].reason === testError;
1717
+ });
1718
+ _testHelperChai.assert.isTrue(hasFailureEvent, 'Should emit switchover failed event');
1719
+ case 2:
1720
+ case "end":
1721
+ return _context15.stop();
1722
+ }
1723
+ }, _callee15);
1724
+ })));
1725
+ it('should allow old socket to be closed by server after switchover failure', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee16() {
1726
+ return _regenerator.default.wrap(function (_context16) {
1727
+ while (1) switch (_context16.prev = _context16.next) {
1728
+ case 0:
1729
+ connectWithBackoffStub.returns(_promise.default.reject(new Error('Failed')));
1730
+ mobiusSocket._handleImminentShutdown(sessionId);
1731
+ _context16.next = 1;
1732
+ return (0, _promiseTick.default)(50);
1733
+ case 1:
1734
+ _testHelperChai.assert.equal(mobiusSocket.socket.removeAllListeners.callCount, 0);
1735
+ case 2:
1736
+ case "end":
1737
+ return _context16.stop();
1738
+ }
1739
+ }, _callee16);
1740
+ })));
1741
+ });
1742
+ describe('#_prepareAndOpenSocket()', function () {
1743
+ var mockSocket;
1744
+ var prepareUrlStub;
1745
+ var getUserTokenStub;
1746
+ beforeEach(function () {
1747
+ mockSocket = {
1748
+ open: _sinon.default.stub().returns(_promise.default.resolve())
1749
+ };
1750
+ prepareUrlStub = _sinon.default.stub(mobiusSocket, '_prepareUrl').returns(_promise.default.resolve('ws://example.com'));
1751
+ getUserTokenStub = webex.credentials.getUserToken;
1752
+ getUserTokenStub.returns(_promise.default.resolve({
1753
+ toString: function toString() {
1754
+ return 'mock-token';
1755
+ }
1756
+ }));
1757
+ });
1758
+ afterEach(function () {
1759
+ prepareUrlStub.restore();
1760
+ });
1761
+ it('should prepare URL and get user token', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee17() {
1762
+ return _regenerator.default.wrap(function (_context17) {
1763
+ while (1) switch (_context17.prev = _context17.next) {
1764
+ case 0:
1765
+ _context17.next = 1;
1766
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, 'ws://test.com', false);
1767
+ case 1:
1768
+ _testHelperChai.assert.calledOnce(prepareUrlStub);
1769
+ _testHelperChai.assert.calledWith(prepareUrlStub, 'ws://test.com');
1770
+ _testHelperChai.assert.calledOnce(getUserTokenStub);
1771
+ case 2:
1772
+ case "end":
1773
+ return _context17.stop();
1774
+ }
1775
+ }, _callee17);
1776
+ })));
1777
+ it('should open socket with correct options for normal connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee18() {
1778
+ var callArgs;
1779
+ return _regenerator.default.wrap(function (_context18) {
1780
+ while (1) switch (_context18.prev = _context18.next) {
1781
+ case 0:
1782
+ _context18.next = 1;
1783
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, false);
1784
+ case 1:
1785
+ _testHelperChai.assert.calledOnce(mockSocket.open);
1786
+ callArgs = mockSocket.open.firstCall.args;
1787
+ _testHelperChai.assert.equal(callArgs[0], 'ws://example.com');
1788
+ _testHelperChai.assert.isObject(callArgs[1]);
1789
+ _testHelperChai.assert.equal(callArgs[1].token, 'mock-token');
1790
+ _testHelperChai.assert.isDefined(callArgs[1].forceCloseDelay);
1791
+ _testHelperChai.assert.isDefined(callArgs[1].wssResponseTimeout);
1792
+ case 2:
1793
+ case "end":
1794
+ return _context18.stop();
1795
+ }
1796
+ }, _callee18);
1797
+ })));
1798
+ it('should log with correct prefix for normal connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee19() {
1799
+ return _regenerator.default.wrap(function (_context19) {
1800
+ while (1) switch (_context19.prev = _context19.next) {
1801
+ case 0:
1802
+ _context19.next = 1;
1803
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, false);
1804
+ case 1:
1805
+ // The method should complete successfully - we're testing it runs without error
1806
+ // Actual log message verification is complex due to existing stubs in parent scope
1807
+ _testHelperChai.assert.calledOnce(mockSocket.open);
1808
+ case 2:
1809
+ case "end":
1810
+ return _context19.stop();
1811
+ }
1812
+ }, _callee19);
1813
+ })));
1814
+ it('should log with shutdown prefix for shutdown connection', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee20() {
1815
+ return _regenerator.default.wrap(function (_context20) {
1816
+ while (1) switch (_context20.prev = _context20.next) {
1817
+ case 0:
1818
+ _context20.next = 1;
1819
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, true);
1820
+ case 1:
1821
+ // The method should complete successfully with shutdown flag
1822
+ _testHelperChai.assert.calledOnce(mockSocket.open);
1823
+ case 2:
1824
+ case "end":
1825
+ return _context20.stop();
1826
+ }
1827
+ }, _callee20);
1828
+ })));
1829
+ it('should merge custom mobiusSocket options when provided', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee21() {
1830
+ var callArgs;
1831
+ return _regenerator.default.wrap(function (_context21) {
1832
+ while (1) switch (_context21.prev = _context21.next) {
1833
+ case 0:
1834
+ webex.config.defaultMobiusSocketOptions = {
1835
+ customOption: 'test-value',
1836
+ wssResponseTimeout: 99999
1837
+ };
1838
+ _context21.next = 1;
1839
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, false);
1840
+ case 1:
1841
+ callArgs = mockSocket.open.firstCall.args;
1842
+ _testHelperChai.assert.equal(callArgs[1].customOption, 'test-value');
1843
+ _testHelperChai.assert.equal(callArgs[1].wssResponseTimeout, 99999); // Custom value overrides default
1844
+ case 2:
1845
+ case "end":
1846
+ return _context21.stop();
1847
+ }
1848
+ }, _callee21);
1849
+ })));
1850
+ it('should return the webSocketUrl after opening', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee22() {
1851
+ var result;
1852
+ return _regenerator.default.wrap(function (_context22) {
1853
+ while (1) switch (_context22.prev = _context22.next) {
1854
+ case 0:
1855
+ _context22.next = 1;
1856
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, false);
1857
+ case 1:
1858
+ result = _context22.sent;
1859
+ _testHelperChai.assert.equal(result, 'ws://example.com');
1860
+ case 2:
1861
+ case "end":
1862
+ return _context22.stop();
1863
+ }
1864
+ }, _callee22);
1865
+ })));
1866
+ it('should handle errors during socket open', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee23() {
1867
+ var _t2;
1868
+ return _regenerator.default.wrap(function (_context23) {
1869
+ while (1) switch (_context23.prev = _context23.next) {
1870
+ case 0:
1871
+ mockSocket.open.returns(_promise.default.reject(new Error('Open failed')));
1872
+ _context23.prev = 1;
1873
+ _context23.next = 2;
1874
+ return mobiusSocket._prepareAndOpenSocket(mockSocket, undefined, false);
1875
+ case 2:
1876
+ _testHelperChai.assert.fail('Should have thrown an error');
1877
+ _context23.next = 4;
1878
+ break;
1879
+ case 3:
1880
+ _context23.prev = 3;
1881
+ _t2 = _context23["catch"](1);
1882
+ _testHelperChai.assert.equal(_t2.message, 'Open failed');
1883
+ case 4:
1884
+ case "end":
1885
+ return _context23.stop();
1886
+ }
1887
+ }, _callee23, null, [[1, 3]]);
1888
+ })));
1889
+ });
1890
+ describe('#_attemptConnection() with shutdown switchover', function () {
1891
+ var prepareAndOpenSocketStub;
1892
+ var callback;
1893
+ var sessionId = 'mobius-websocket-session';
1894
+ beforeEach(function () {
1895
+ prepareAndOpenSocketStub = _sinon.default.stub(mobiusSocket, '_prepareAndOpenSocket').returns(_promise.default.resolve('ws://new-socket.com'));
1896
+ callback = _sinon.default.stub();
1897
+ mobiusSocket._shutdownSwitchoverBackoffCalls.set(sessionId, {
1898
+ abort: _sinon.default.stub()
1899
+ });
1900
+ mobiusSocket.socket = {
1901
+ url: 'ws://test.com'
1902
+ };
1903
+ mobiusSocket.connected = true;
1904
+ _sinon.default.stub(mobiusSocket, '_emit');
1905
+ _sinon.default.stub(mobiusSocket, '_attachSocketEventListeners');
1906
+ });
1907
+ afterEach(function () {
1908
+ prepareAndOpenSocketStub.restore();
1909
+ mobiusSocket._emit.restore();
1910
+ mobiusSocket._attachSocketEventListeners.restore();
1911
+ mobiusSocket._shutdownSwitchoverBackoffCalls.clear();
1912
+ });
1913
+ it('should not set socket reference before opening for shutdown switchover', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee24() {
1914
+ var originalSocket;
1915
+ return _regenerator.default.wrap(function (_context24) {
1916
+ while (1) switch (_context24.prev = _context24.next) {
1917
+ case 0:
1918
+ originalSocket = mobiusSocket.socket;
1919
+ _context24.next = 1;
1920
+ return mobiusSocket._attemptConnection('ws://test.com', sessionId, callback, {
1921
+ isShutdownSwitchover: true,
1922
+ onSuccess: function onSuccess(newSocket, url) {
1923
+ _testHelperChai.assert.equal(mobiusSocket.socket, originalSocket);
1924
+ }
1925
+ });
1926
+ case 1:
1927
+ _testHelperChai.assert.equal(mobiusSocket.socket, originalSocket);
1928
+ case 2:
1929
+ case "end":
1930
+ return _context24.stop();
1931
+ }
1932
+ }, _callee24);
1933
+ })));
1934
+ it('should call onSuccess callback with new socket and URL for shutdown', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee25() {
1935
+ var onSuccessStub;
1936
+ return _regenerator.default.wrap(function (_context25) {
1937
+ while (1) switch (_context25.prev = _context25.next) {
1938
+ case 0:
1939
+ onSuccessStub = _sinon.default.stub();
1940
+ _context25.next = 1;
1941
+ return mobiusSocket._attemptConnection('ws://test.com', sessionId, callback, {
1942
+ isShutdownSwitchover: true,
1943
+ onSuccess: onSuccessStub
1944
+ });
1945
+ case 1:
1946
+ _testHelperChai.assert.calledOnce(onSuccessStub);
1947
+ _testHelperChai.assert.equal(onSuccessStub.firstCall.args[1], 'ws://new-socket.com');
1948
+ case 2:
1949
+ case "end":
1950
+ return _context25.stop();
1951
+ }
1952
+ }, _callee25);
1953
+ })));
1954
+ it('should emit shutdown switchover complete event', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee26() {
1955
+ return _regenerator.default.wrap(function (_context26) {
1956
+ while (1) switch (_context26.prev = _context26.next) {
1957
+ case 0:
1958
+ _context26.next = 1;
1959
+ return mobiusSocket._attemptConnection('ws://test.com', sessionId, callback, {
1960
+ isShutdownSwitchover: true,
1961
+ onSuccess: function onSuccess(newSocket, url) {
1962
+ mobiusSocket.socket = newSocket;
1963
+ mobiusSocket.connected = true;
1964
+ mobiusSocket._emit(sessionId, 'event:mercury_shutdown_switchover_complete', {
1965
+ url: url
1966
+ });
1967
+ }
1968
+ });
1969
+ case 1:
1970
+ _testHelperChai.assert.calledWith(mobiusSocket._emit, sessionId, 'event:mercury_shutdown_switchover_complete', _sinon.default.match.has('url', 'ws://new-socket.com'));
1971
+ case 2:
1972
+ case "end":
1973
+ return _context26.stop();
1974
+ }
1975
+ }, _callee26);
1976
+ })));
1977
+ it('should use simpler error handling for shutdown switchover failures', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee27() {
1978
+ return _regenerator.default.wrap(function (_context27) {
1979
+ while (1) switch (_context27.prev = _context27.next) {
1980
+ case 0:
1981
+ prepareAndOpenSocketStub.returns(_promise.default.reject(new Error('Connection failed')));
1982
+ _context27.next = 1;
1983
+ return mobiusSocket._attemptConnection('ws://test.com', sessionId, callback, {
1984
+ isShutdownSwitchover: true
1985
+ }).catch(function () {});
1986
+ case 1:
1987
+ _testHelperChai.assert.calledOnce(callback);
1988
+ _testHelperChai.assert.instanceOf(callback.firstCall.args[0], Error);
1989
+ case 2:
1990
+ case "end":
1991
+ return _context27.stop();
1992
+ }
1993
+ }, _callee27);
1994
+ })));
1995
+ it('should check _shutdownSwitchoverBackoffCall for shutdown connections', function () {
1996
+ mobiusSocket._shutdownSwitchoverBackoffCalls.clear();
1997
+ var result = mobiusSocket._attemptConnection('ws://test.com', sessionId, callback, {
1998
+ isShutdownSwitchover: true
1999
+ });
2000
+ return result.catch(function (err) {
2001
+ _testHelperChai.assert.instanceOf(err, Error);
2002
+ _testHelperChai.assert.match(err.message, /switchover backoff call/);
2003
+ });
2004
+ });
2005
+ });
2006
+ describe('#_connectWithBackoff() with shutdown switchover', function () {
2007
+ var sessionId = 'mobius-websocket-session';
2008
+ it('should use shutdown-specific parameters when called', function () {
2009
+ var connectWithBackoffStub = _sinon.default.stub(mobiusSocket, '_connectWithBackoff').returns(_promise.default.resolve());
2010
+ mobiusSocket._handleImminentShutdown(sessionId);
2011
+ _testHelperChai.assert.calledOnce(connectWithBackoffStub);
2012
+ var callArgs = connectWithBackoffStub.firstCall.args;
2013
+ _testHelperChai.assert.equal(callArgs[1], sessionId);
2014
+ _testHelperChai.assert.isObject(callArgs[2]);
2015
+ _testHelperChai.assert.isTrue(callArgs[2].isShutdownSwitchover);
2016
+ connectWithBackoffStub.restore();
2017
+ });
2018
+ it('should pass shutdown switchover options to _attemptConnection', function () {
2019
+ var attemptStub = _sinon.default.stub(mobiusSocket, '_attemptConnection');
2020
+ attemptStub.callsFake(function (url, sid, cb) {
2021
+ return cb();
2022
+ });
2023
+ var context = {
2024
+ isShutdownSwitchover: true,
2025
+ attemptOptions: {
2026
+ isShutdownSwitchover: true,
2027
+ onSuccess: function onSuccess() {}
2028
+ }
2029
+ };
2030
+ var promise = mobiusSocket._connectWithBackoff(undefined, sessionId, context);
2031
+ return promise.then(function () {
2032
+ _testHelperChai.assert.calledOnce(attemptStub);
2033
+ var callArgs = attemptStub.firstCall.args;
2034
+ _testHelperChai.assert.equal(callArgs[1], sessionId);
2035
+ _testHelperChai.assert.isObject(callArgs[3]);
2036
+ _testHelperChai.assert.isTrue(callArgs[3].isShutdownSwitchover);
2037
+ attemptStub.restore();
2038
+ });
2039
+ });
2040
+ it('should set and clear state flags appropriately', function () {
2041
+ _sinon.default.stub(mobiusSocket, '_attemptConnection').callsFake(function (url, sid, cb) {
2042
+ return cb();
2043
+ });
2044
+ mobiusSocket._shutdownSwitchoverBackoffCalls.set(sessionId, {
2045
+ placeholder: true
2046
+ });
2047
+ var promise = mobiusSocket._connectWithBackoff(undefined, sessionId, {
2048
+ isShutdownSwitchover: true,
2049
+ attemptOptions: {
2050
+ isShutdownSwitchover: true,
2051
+ onSuccess: function onSuccess() {}
2052
+ }
2053
+ });
2054
+ return promise.then(function () {
2055
+ _testHelperChai.assert.isUndefined(mobiusSocket._shutdownSwitchoverBackoffCalls.get(sessionId));
2056
+ mobiusSocket._attemptConnection.restore();
2057
+ });
2058
+ });
2059
+ });
2060
+ describe('#disconnect() with shutdown switchover in progress', function () {
2061
+ var abortStub;
2062
+ var sessionId = 'mobius-websocket-session';
2063
+ beforeEach(function () {
2064
+ mobiusSocket.sockets.clear();
2065
+ mobiusSocket.sockets.set(sessionId, {
2066
+ close: _sinon.default.stub().returns(_promise.default.resolve()),
2067
+ removeAllListeners: _sinon.default.stub()
2068
+ });
2069
+ abortStub = _sinon.default.stub();
2070
+ mobiusSocket._shutdownSwitchoverBackoffCalls.set(sessionId, {
2071
+ abort: abortStub
2072
+ });
2073
+ });
2074
+ it('should abort shutdown switchover backoff call on disconnect', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee28() {
2075
+ return _regenerator.default.wrap(function (_context28) {
2076
+ while (1) switch (_context28.prev = _context28.next) {
2077
+ case 0:
2078
+ _context28.next = 1;
2079
+ return mobiusSocket.disconnect(undefined, sessionId);
2080
+ case 1:
2081
+ _testHelperChai.assert.calledOnce(abortStub);
2082
+ case 2:
2083
+ case "end":
2084
+ return _context28.stop();
2085
+ }
2086
+ }, _callee28);
2087
+ })));
2088
+ it('should handle disconnect when no switchover is in progress', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee29() {
2089
+ return _regenerator.default.wrap(function (_context29) {
2090
+ while (1) switch (_context29.prev = _context29.next) {
2091
+ case 0:
2092
+ mobiusSocket._shutdownSwitchoverBackoffCalls.clear();
2093
+ _context29.next = 1;
2094
+ return mobiusSocket.disconnect(undefined, sessionId);
2095
+ case 1:
2096
+ _testHelperChai.assert.calledOnce(mobiusSocket.sockets.get(sessionId).close);
2097
+ case 2:
2098
+ case "end":
2099
+ return _context29.stop();
2100
+ }
2101
+ }, _callee29);
2102
+ })));
2103
+ });
2104
+ });
2105
+ });
2106
+ });
2107
+ //# sourceMappingURL=mobius-socket.test.js.map