@webex/internal-plugin-mercury 3.12.0-next.5 → 3.12.0-next.51
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 +78 -10
- package/dist/mercury.js.map +1 -1
- package/package.json +17 -17
- package/src/mercury.js +90 -10
- package/test/unit/spec/mercury-events.js +15 -1
- package/test/unit/spec/mercury.js +315 -39
package/src/mercury.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from './errors';
|
|
22
22
|
|
|
23
23
|
const normalReconnectReasons = ['idle', 'done (forced)', 'pong not received', 'pong mismatch'];
|
|
24
|
+
const MERCURY_CLOSE_4000_METRIC = 'JS_SDK_MERCURY_CLOSE_4000';
|
|
24
25
|
|
|
25
26
|
const Mercury = WebexPlugin.extend({
|
|
26
27
|
namespace: 'Mercury',
|
|
@@ -44,6 +45,10 @@ const Mercury = WebexPlugin.extend({
|
|
|
44
45
|
default: () => new Map(),
|
|
45
46
|
type: 'object',
|
|
46
47
|
},
|
|
48
|
+
sessionWebSocketUrls: {
|
|
49
|
+
default: () => new Map(),
|
|
50
|
+
type: 'object',
|
|
51
|
+
},
|
|
47
52
|
backoffCalls: {
|
|
48
53
|
default: () => new Map(),
|
|
49
54
|
type: 'object',
|
|
@@ -377,6 +382,7 @@ const Mercury = WebexPlugin.extend({
|
|
|
377
382
|
return Promise.all(disconnectPromises).then(() => {
|
|
378
383
|
this.connected = false;
|
|
379
384
|
this.sockets.clear();
|
|
385
|
+
this.sessionWebSocketUrls.clear();
|
|
380
386
|
this.backoffCalls.clear();
|
|
381
387
|
// Clear connection promises to prevent stale promises
|
|
382
388
|
if (this._connectPromises) {
|
|
@@ -610,12 +616,24 @@ const Mercury = WebexPlugin.extend({
|
|
|
610
616
|
return this.webex.internal.feature
|
|
611
617
|
.getFeature('developer', 'web-high-availability')
|
|
612
618
|
.then((haMessagingEnabled) => {
|
|
619
|
+
const wsUrl = newWSUrl || reason.webSocketUrl;
|
|
620
|
+
|
|
613
621
|
if (haMessagingEnabled) {
|
|
614
622
|
this.logger.info(
|
|
615
|
-
`${this.namespace}: received a generic connection error for ${sessionId}, will try to connect to another datacenter. failed, action: 'failed', url: ${
|
|
623
|
+
`${this.namespace}: received a generic connection error for ${sessionId}, will try to connect to another datacenter. failed, action: 'failed', url: ${wsUrl} error: ${reason.message}`
|
|
616
624
|
);
|
|
617
625
|
|
|
618
|
-
|
|
626
|
+
if (wsUrl) {
|
|
627
|
+
this.logger.info(
|
|
628
|
+
`${this.namespace}: marking ${wsUrl} as failed for ${sessionId} due to connection error`
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
return this.webex.internal.services.markFailedUrl(wsUrl);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
this.logger.info(
|
|
635
|
+
`${this.namespace}: no socket url available to mark as failed for ${sessionId} due to connection error`
|
|
636
|
+
);
|
|
619
637
|
}
|
|
620
638
|
|
|
621
639
|
return null;
|
|
@@ -664,7 +682,22 @@ const Mercury = WebexPlugin.extend({
|
|
|
664
682
|
|
|
665
683
|
this.logger.info(`${this.namespace} ${logPrefix} url for ${sessionId}: ${webSocketUrl}`);
|
|
666
684
|
|
|
667
|
-
|
|
685
|
+
// Remember the resolved URL used to open this socket so that
|
|
686
|
+
// reconnection in _onclose re-derives from a catalog-valid host. This is
|
|
687
|
+
// captured before socket.open(), which a lower layer (e.g. the same-site
|
|
688
|
+
// websocket proxy done with interceptors) may rewrite into a non-catalog host.
|
|
689
|
+
// Reusing the post-proxy native socket URL would fail
|
|
690
|
+
// host-catalog validation in _prepareUrl and block reconnection.
|
|
691
|
+
this.sessionWebSocketUrls.set(sessionId, webSocketUrl);
|
|
692
|
+
|
|
693
|
+
return socket
|
|
694
|
+
.open(webSocketUrl, options)
|
|
695
|
+
.then(() => webSocketUrl)
|
|
696
|
+
.catch((err) => {
|
|
697
|
+
err.webSocketUrl = webSocketUrl;
|
|
698
|
+
|
|
699
|
+
return Promise.reject(err);
|
|
700
|
+
});
|
|
668
701
|
}
|
|
669
702
|
);
|
|
670
703
|
},
|
|
@@ -850,6 +883,29 @@ const Mercury = WebexPlugin.extend({
|
|
|
850
883
|
return handlers;
|
|
851
884
|
},
|
|
852
885
|
|
|
886
|
+
_submitMercuryClose4000Metric(sessionId, event, options) {
|
|
887
|
+
const {action, isActiveSocket, messageType} = options;
|
|
888
|
+
|
|
889
|
+
try {
|
|
890
|
+
this.webex.internal.metrics.submitClientMetrics(MERCURY_CLOSE_4000_METRIC, {
|
|
891
|
+
fields: {
|
|
892
|
+
action,
|
|
893
|
+
close_code: event.code,
|
|
894
|
+
close_reason: event.reason || '',
|
|
895
|
+
is_active_socket: isActiveSocket,
|
|
896
|
+
message_type: messageType,
|
|
897
|
+
session_id: sessionId,
|
|
898
|
+
},
|
|
899
|
+
tags: {
|
|
900
|
+
action,
|
|
901
|
+
message_type: messageType,
|
|
902
|
+
},
|
|
903
|
+
});
|
|
904
|
+
} catch (error) {
|
|
905
|
+
this.logger.warn(`${this.namespace}: failed to submit Mercury 4000 close metric`, error);
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
|
|
853
909
|
_onclose(sessionId, event, sourceSocket) {
|
|
854
910
|
// I don't see any way to avoid the complexity or statement count in here.
|
|
855
911
|
/* eslint complexity: [0] */
|
|
@@ -857,13 +913,18 @@ const Mercury = WebexPlugin.extend({
|
|
|
857
913
|
try {
|
|
858
914
|
const reason = event.reason && event.reason.toLowerCase();
|
|
859
915
|
const sessionSocket = this.sockets.get(sessionId);
|
|
860
|
-
let socketUrl;
|
|
861
916
|
event.sessionId = sessionId;
|
|
862
917
|
|
|
863
918
|
const isActiveSocket = sourceSocket === sessionSocket;
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
919
|
+
// Reconnect using the resolved URL this session's socket was
|
|
920
|
+
// opened with, captured in _prepareAndOpenSocket. Do NOT reuse
|
|
921
|
+
// sourceSocket.url: that reflects the URL the native WebSocket was
|
|
922
|
+
// actually opened with, which a lower layer may have rewritten
|
|
923
|
+
// (e.g. via interceptors) into a host that is not in the SDK host
|
|
924
|
+
// catalog. Feeding such a URL back into _prepareUrl fails host-catalog
|
|
925
|
+
// validation and permanently blocks reconnection.
|
|
926
|
+
const socketUrl = this.sessionWebSocketUrls.get(sessionId);
|
|
927
|
+
|
|
867
928
|
this.sockets.delete(sessionId);
|
|
868
929
|
|
|
869
930
|
if (isActiveSocket) {
|
|
@@ -901,9 +962,28 @@ const Mercury = WebexPlugin.extend({
|
|
|
901
962
|
break;
|
|
902
963
|
case 4000:
|
|
903
964
|
// metric: disconnect
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
965
|
+
if (reason === 'replaced') {
|
|
966
|
+
this._submitMercuryClose4000Metric(sessionId, event, {
|
|
967
|
+
action: 'no_action',
|
|
968
|
+
isActiveSocket,
|
|
969
|
+
messageType: 'replaced',
|
|
970
|
+
});
|
|
971
|
+
this.logger.info(`${this.namespace}: socket ${sessionId} replaced; will not reconnect`);
|
|
972
|
+
if (isActiveSocket) this._emit(sessionId, 'offline.replaced', event);
|
|
973
|
+
} else {
|
|
974
|
+
this._submitMercuryClose4000Metric(sessionId, event, {
|
|
975
|
+
action: isActiveSocket ? 'reconnect' : 'ignore_non_active',
|
|
976
|
+
isActiveSocket,
|
|
977
|
+
messageType: 'other',
|
|
978
|
+
});
|
|
979
|
+
this.logger.info(
|
|
980
|
+
`${this.namespace}: socket ${sessionId} disconnected with 4000: ${event.reason}; reconnecting`
|
|
981
|
+
);
|
|
982
|
+
if (isActiveSocket) {
|
|
983
|
+
this._emit(sessionId, 'offline.transient', event);
|
|
984
|
+
this._reconnect(socketUrl, sessionId);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
907
987
|
break;
|
|
908
988
|
case 4001:
|
|
909
989
|
// replaced during shutdown
|
|
@@ -267,8 +267,14 @@ describe('plugin-mercury', () => {
|
|
|
267
267
|
},
|
|
268
268
|
{
|
|
269
269
|
code: 4000,
|
|
270
|
+
reason: 'Replaced',
|
|
270
271
|
action: 'replace',
|
|
271
272
|
},
|
|
273
|
+
{
|
|
274
|
+
code: 4000,
|
|
275
|
+
reason: 'Unexpected close',
|
|
276
|
+
action: 'reconnect',
|
|
277
|
+
},
|
|
272
278
|
{
|
|
273
279
|
action: 'close',
|
|
274
280
|
},
|
|
@@ -292,6 +298,8 @@ describe('plugin-mercury', () => {
|
|
|
292
298
|
mercury._reconnect.restore();
|
|
293
299
|
}
|
|
294
300
|
|
|
301
|
+
let expectedReconnectUrl;
|
|
302
|
+
|
|
295
303
|
sinon.spy(mercury, 'connect');
|
|
296
304
|
|
|
297
305
|
const offlineSpy = sinon.spy();
|
|
@@ -313,6 +321,12 @@ describe('plugin-mercury', () => {
|
|
|
313
321
|
// Make sure mercury.connect has a call count of zero
|
|
314
322
|
mercury.connect.resetHistory();
|
|
315
323
|
|
|
324
|
+
// Reconnection re-derives from the resolved URL
|
|
325
|
+
// captured in _prepareAndOpenSocket, not the socket's actual
|
|
326
|
+
// (possibly changed by interceptors) url.
|
|
327
|
+
expectedReconnectUrl =
|
|
328
|
+
mercury.sessionWebSocketUrls.get('mercury-default-session');
|
|
329
|
+
|
|
316
330
|
mockWebSocket.emit('close', {code, reason});
|
|
317
331
|
|
|
318
332
|
return promiseTick(1);
|
|
@@ -342,7 +356,7 @@ describe('plugin-mercury', () => {
|
|
|
342
356
|
assert.isFalse(mercury.connected, 'Mercury is not connected');
|
|
343
357
|
if (action === 'reconnect') {
|
|
344
358
|
assert.called(mercury.connect);
|
|
345
|
-
assert.calledWith(mercury.connect,
|
|
359
|
+
assert.calledWith(mercury.connect, expectedReconnectUrl);
|
|
346
360
|
assert.isTrue(mercury.connecting, 'Mercury is connecting');
|
|
347
361
|
|
|
348
362
|
// Block until reconnect completes so logs don't overlap
|