@webex/internal-plugin-mercury 3.8.1 → 3.9.0-multipleLLM.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mercury.js +321 -130
- package/dist/mercury.js.map +1 -1
- package/dist/socket/socket-base.js +15 -0
- package/dist/socket/socket-base.js.map +1 -1
- package/package.json +17 -17
- package/src/mercury.js +280 -105
- package/src/socket/socket-base.js +13 -0
- package/test/unit/spec/mercury-events.js +21 -2
- package/test/unit/spec/mercury.js +74 -28
|
@@ -33,6 +33,8 @@ export default class Socket extends EventEmitter {
|
|
|
33
33
|
this._domain = 'unknown-domain';
|
|
34
34
|
this.onmessage = this.onmessage.bind(this);
|
|
35
35
|
this.onclose = this.onclose.bind(this);
|
|
36
|
+
// Increase max listeners to avoid memory leak warning in tests
|
|
37
|
+
this.setMaxListeners(5);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
@@ -358,9 +360,20 @@ export default class Socket extends EventEmitter {
|
|
|
358
360
|
return Promise.reject(new Error('`event.data.id` is required'));
|
|
359
361
|
}
|
|
360
362
|
|
|
363
|
+
// Don't try to acknowledge if socket is not in open state
|
|
364
|
+
if (this.readyState !== 1) {
|
|
365
|
+
return Promise.resolve(); // Silently ignore acknowledgment for closed sockets
|
|
366
|
+
}
|
|
367
|
+
|
|
361
368
|
return this.send({
|
|
362
369
|
messageId: event.data.id,
|
|
363
370
|
type: 'ack',
|
|
371
|
+
}).catch((error) => {
|
|
372
|
+
// Gracefully handle send errors (like INVALID_STATE_ERROR) to prevent test issues
|
|
373
|
+
if (error.message === 'INVALID_STATE_ERROR') {
|
|
374
|
+
return Promise.resolve(); // Socket was closed, ignore the acknowledgment
|
|
375
|
+
}
|
|
376
|
+
throw error; // Re-throw other errors
|
|
364
377
|
});
|
|
365
378
|
}
|
|
366
379
|
|
|
@@ -38,14 +38,31 @@ describe('plugin-mercury', () => {
|
|
|
38
38
|
},
|
|
39
39
|
timestamp: Date.now(),
|
|
40
40
|
trackingId: `suffix_${uuid.v4()}_${Date.now()}`,
|
|
41
|
+
sessionId: 'mercury-default-session',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
beforeEach(() => {
|
|
44
45
|
clock = FakeTimers.install({now: Date.now()});
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
afterEach(() => {
|
|
48
|
+
afterEach(async () => {
|
|
48
49
|
clock.uninstall();
|
|
50
|
+
// Clean up mercury socket and mockWebSocket
|
|
51
|
+
if (mercury && mercury.socket) {
|
|
52
|
+
try {
|
|
53
|
+
await mercury.socket.close();
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
}
|
|
56
|
+
if (mockWebSocket && typeof mockWebSocket.close === 'function') {
|
|
57
|
+
mockWebSocket.close();
|
|
58
|
+
}
|
|
59
|
+
// Restore stubs
|
|
60
|
+
if (Socket.getWebSocketConstructor.restore) {
|
|
61
|
+
Socket.getWebSocketConstructor.restore();
|
|
62
|
+
}
|
|
63
|
+
if (socketOpenStub && socketOpenStub.restore) {
|
|
64
|
+
socketOpenStub.restore();
|
|
65
|
+
}
|
|
49
66
|
});
|
|
50
67
|
|
|
51
68
|
beforeEach(() => {
|
|
@@ -56,6 +73,7 @@ describe('plugin-mercury', () => {
|
|
|
56
73
|
});
|
|
57
74
|
|
|
58
75
|
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
76
|
+
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus = sinon.stub();
|
|
59
77
|
webex.trackingId = 'fakeTrackingId';
|
|
60
78
|
webex.config.mercury = mercuryConfig.mercury;
|
|
61
79
|
|
|
@@ -75,6 +93,7 @@ describe('plugin-mercury', () => {
|
|
|
75
93
|
});
|
|
76
94
|
|
|
77
95
|
mercury = webex.internal.mercury;
|
|
96
|
+
mercury.defaultSessionId = 'mercury-default-session';
|
|
78
97
|
});
|
|
79
98
|
|
|
80
99
|
afterEach(() => {
|
|
@@ -300,7 +319,7 @@ describe('plugin-mercury', () => {
|
|
|
300
319
|
})
|
|
301
320
|
.then(() => {
|
|
302
321
|
assert.called(offlineSpy);
|
|
303
|
-
assert.calledWith(offlineSpy, {code, reason});
|
|
322
|
+
assert.calledWith(offlineSpy, {code, reason, sessionId: 'mercury-default-session'});
|
|
304
323
|
switch (action) {
|
|
305
324
|
case 'close':
|
|
306
325
|
assert.called(permanentSpy);
|
|
@@ -77,6 +77,7 @@ describe('plugin-mercury', () => {
|
|
|
77
77
|
markFailedUrl: sinon.stub().returns(Promise.resolve()),
|
|
78
78
|
};
|
|
79
79
|
webex.internal.metrics.submitClientMetrics = sinon.stub();
|
|
80
|
+
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus = sinon.stub();
|
|
80
81
|
webex.trackingId = 'fakeTrackingId';
|
|
81
82
|
webex.config.mercury = mercuryConfig.mercury;
|
|
82
83
|
|
|
@@ -96,9 +97,32 @@ describe('plugin-mercury', () => {
|
|
|
96
97
|
});
|
|
97
98
|
|
|
98
99
|
mercury = webex.internal.mercury;
|
|
100
|
+
mercury.defaultSessionId = 'mercury-default-session';
|
|
99
101
|
});
|
|
100
102
|
|
|
101
|
-
afterEach(() => {
|
|
103
|
+
afterEach(async () => {
|
|
104
|
+
// Clean up Mercury connections and internal state
|
|
105
|
+
if (mercury) {
|
|
106
|
+
try {
|
|
107
|
+
await mercury.disconnectAll();
|
|
108
|
+
} catch (e) {
|
|
109
|
+
// Ignore cleanup errors
|
|
110
|
+
}
|
|
111
|
+
// Clear any remaining connection promises
|
|
112
|
+
if (mercury._connectPromises) {
|
|
113
|
+
mercury._connectPromises.clear();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Ensure mock socket is properly closed
|
|
118
|
+
if (mockWebSocket && typeof mockWebSocket.close === 'function') {
|
|
119
|
+
try {
|
|
120
|
+
mockWebSocket.close();
|
|
121
|
+
} catch (e) {
|
|
122
|
+
// Ignore cleanup errors
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
if (socketOpenStub) {
|
|
103
127
|
socketOpenStub.restore();
|
|
104
128
|
}
|
|
@@ -106,21 +130,38 @@ describe('plugin-mercury', () => {
|
|
|
106
130
|
if (Socket.getWebSocketConstructor.restore) {
|
|
107
131
|
Socket.getWebSocketConstructor.restore();
|
|
108
132
|
}
|
|
133
|
+
|
|
134
|
+
// Small delay to ensure all async operations complete
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
109
136
|
});
|
|
110
137
|
|
|
111
138
|
describe('#listen()', () => {
|
|
112
139
|
it('proxies to #connect()', () => {
|
|
113
|
-
sinon.stub(mercury, 'connect');
|
|
114
|
-
mercury.listen()
|
|
115
|
-
|
|
140
|
+
const connectStub = sinon.stub(mercury, 'connect').callThrough();
|
|
141
|
+
return mercury.listen().then(() => {
|
|
142
|
+
assert.called(connectStub);
|
|
143
|
+
assert.calledWith(
|
|
144
|
+
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus,
|
|
145
|
+
true
|
|
146
|
+
);
|
|
147
|
+
});
|
|
116
148
|
});
|
|
117
149
|
});
|
|
118
150
|
|
|
119
151
|
describe('#stopListening()', () => {
|
|
120
152
|
it('proxies to #disconnect()', () => {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
return mercury.connect().then(() => {
|
|
154
|
+
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus.resetHistory();
|
|
155
|
+
const disconnectStub = sinon.stub(mercury, 'disconnect').callThrough();
|
|
156
|
+
|
|
157
|
+
mercury.stopListening();
|
|
158
|
+
assert.called(disconnectStub);
|
|
159
|
+
mockWebSocket.emit('close', {code: 1000, reason: 'test'});
|
|
160
|
+
assert.calledWith(
|
|
161
|
+
webex.internal.newMetrics.callDiagnosticMetrics.setMercuryConnectedStatus,
|
|
162
|
+
false
|
|
163
|
+
);
|
|
164
|
+
});
|
|
124
165
|
});
|
|
125
166
|
});
|
|
126
167
|
|
|
@@ -421,9 +462,13 @@ describe('plugin-mercury', () => {
|
|
|
421
462
|
|
|
422
463
|
// skipping due to apparent bug with lolex in all browsers but Chrome.
|
|
423
464
|
skipInBrowser(it)('does not continue attempting to connect', () => {
|
|
424
|
-
mercury.connect();
|
|
465
|
+
const promise = mercury.connect();
|
|
425
466
|
|
|
426
|
-
|
|
467
|
+
// Wait for the connection to be established before proceeding
|
|
468
|
+
mockWebSocket.open();
|
|
469
|
+
|
|
470
|
+
return promise.then(() =>
|
|
471
|
+
promiseTick(2)
|
|
427
472
|
.then(() => {
|
|
428
473
|
clock.tick(6 * webex.internal.mercury.config.backoffTimeReset);
|
|
429
474
|
|
|
@@ -431,7 +476,8 @@ describe('plugin-mercury', () => {
|
|
|
431
476
|
})
|
|
432
477
|
.then(() => {
|
|
433
478
|
assert.calledOnce(Socket.prototype.open);
|
|
434
|
-
|
|
479
|
+
})
|
|
480
|
+
);
|
|
435
481
|
});
|
|
436
482
|
});
|
|
437
483
|
|
|
@@ -506,11 +552,11 @@ describe('plugin-mercury', () => {
|
|
|
506
552
|
});
|
|
507
553
|
|
|
508
554
|
describe('#logout()', () => {
|
|
509
|
-
it('calls
|
|
555
|
+
it('calls disconnectAll and logs', () => {
|
|
510
556
|
sinon.stub(mercury.logger, 'info');
|
|
511
|
-
sinon.stub(mercury, '
|
|
557
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
512
558
|
mercury.logout();
|
|
513
|
-
assert.called(mercury.
|
|
559
|
+
assert.called(mercury.disconnectAll);
|
|
514
560
|
assert.calledTwice(mercury.logger.info);
|
|
515
561
|
|
|
516
562
|
assert.calledWith(mercury.logger.info.getCall(0), 'Mercury: logout() called');
|
|
@@ -522,24 +568,24 @@ describe('plugin-mercury', () => {
|
|
|
522
568
|
});
|
|
523
569
|
|
|
524
570
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout', () => {
|
|
525
|
-
sinon.stub(mercury, '
|
|
571
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
526
572
|
mercury.config.beforeLogoutOptionsCloseReason = 'done (permanent)';
|
|
527
573
|
mercury.logout();
|
|
528
|
-
assert.calledWith(mercury.
|
|
574
|
+
assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'done (permanent)'});
|
|
529
575
|
});
|
|
530
576
|
|
|
531
577
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send code 3050 for logout if the reason is different than standard', () => {
|
|
532
|
-
sinon.stub(mercury, '
|
|
578
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
533
579
|
mercury.config.beforeLogoutOptionsCloseReason = 'test';
|
|
534
580
|
mercury.logout();
|
|
535
|
-
assert.calledWith(mercury.
|
|
581
|
+
assert.calledWith(mercury.disconnectAll, {code: 3050, reason: 'test'});
|
|
536
582
|
});
|
|
537
583
|
|
|
538
584
|
it('uses the config.beforeLogoutOptionsCloseReason to disconnect and will send undefined for logout if the reason is same as standard', () => {
|
|
539
|
-
sinon.stub(mercury, '
|
|
585
|
+
sinon.stub(mercury, 'disconnectAll');
|
|
540
586
|
mercury.config.beforeLogoutOptionsCloseReason = 'done (forced)';
|
|
541
587
|
mercury.logout();
|
|
542
|
-
assert.calledWith(mercury.
|
|
588
|
+
assert.calledWith(mercury.disconnectAll, undefined);
|
|
543
589
|
});
|
|
544
590
|
});
|
|
545
591
|
|
|
@@ -645,12 +691,12 @@ describe('plugin-mercury', () => {
|
|
|
645
691
|
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
646
692
|
// By this time backoffCall and mercury socket should be defined by the
|
|
647
693
|
// 'connect' call
|
|
648
|
-
assert.isDefined(mercury.
|
|
694
|
+
assert.isDefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is not defined');
|
|
649
695
|
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
650
696
|
// Calling disconnect will abort the backoffCall, close the socket, and
|
|
651
697
|
// reject the connect
|
|
652
698
|
mercury.disconnect();
|
|
653
|
-
assert.isUndefined(mercury.
|
|
699
|
+
assert.isUndefined(mercury.backoffCalls.get('mercury-default-session'), 'Mercury backoffCall is still defined');
|
|
654
700
|
// The socket will never be unset (which seems bad)
|
|
655
701
|
assert.isDefined(mercury.socket, 'Mercury socket is not defined');
|
|
656
702
|
|
|
@@ -668,15 +714,15 @@ describe('plugin-mercury', () => {
|
|
|
668
714
|
|
|
669
715
|
let reason;
|
|
670
716
|
|
|
671
|
-
mercury.
|
|
672
|
-
mercury._attemptConnection('ws://example.com', (_reason) => {
|
|
717
|
+
mercury.backoffCalls.clear();
|
|
718
|
+
mercury._attemptConnection('ws://example.com', 'mercury-default-session',(_reason) => {
|
|
673
719
|
reason = _reason;
|
|
674
720
|
});
|
|
675
721
|
|
|
676
722
|
return promiseTick(webex.internal.mercury.config.backoffTimeReset).then(() => {
|
|
677
723
|
assert.equal(
|
|
678
724
|
reason.message,
|
|
679
|
-
|
|
725
|
+
`Mercury: prevent socket open when backoffCall no longer defined for ${mercury.defaultSessionId}`
|
|
680
726
|
);
|
|
681
727
|
});
|
|
682
728
|
});
|
|
@@ -698,7 +744,7 @@ describe('plugin-mercury', () => {
|
|
|
698
744
|
return assert.isRejected(promise).then((error) => {
|
|
699
745
|
const lastError = mercury.getLastError();
|
|
700
746
|
|
|
701
|
-
assert.equal(error.message,
|
|
747
|
+
assert.equal(error.message, `Mercury Connection Aborted for ${mercury.defaultSessionId}`);
|
|
702
748
|
assert.isDefined(lastError);
|
|
703
749
|
assert.equal(lastError, realError);
|
|
704
750
|
});
|
|
@@ -792,7 +838,7 @@ describe('plugin-mercury', () => {
|
|
|
792
838
|
},
|
|
793
839
|
};
|
|
794
840
|
assert.isUndefined(mercury.mercuryTimeOffset);
|
|
795
|
-
mercury._setTimeOffset(event);
|
|
841
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
796
842
|
assert.isDefined(mercury.mercuryTimeOffset);
|
|
797
843
|
assert.isTrue(mercury.mercuryTimeOffset > 0);
|
|
798
844
|
});
|
|
@@ -802,7 +848,7 @@ describe('plugin-mercury', () => {
|
|
|
802
848
|
wsWriteTimestamp: Date.now() + 60000,
|
|
803
849
|
},
|
|
804
850
|
};
|
|
805
|
-
mercury._setTimeOffset(event);
|
|
851
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
806
852
|
assert.isTrue(mercury.mercuryTimeOffset < 0);
|
|
807
853
|
});
|
|
808
854
|
it('handles invalid wsWriteTimestamp', () => {
|
|
@@ -813,7 +859,7 @@ describe('plugin-mercury', () => {
|
|
|
813
859
|
wsWriteTimestamp: invalidTimestamp,
|
|
814
860
|
},
|
|
815
861
|
};
|
|
816
|
-
mercury._setTimeOffset(event);
|
|
862
|
+
mercury._setTimeOffset('mercury-default-session', event);
|
|
817
863
|
assert.isUndefined(mercury.mercuryTimeOffset);
|
|
818
864
|
});
|
|
819
865
|
});
|