@webex/plugin-meetings 3.3.1 → 3.4.0-next.10
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +7 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/constants.js +11 -4
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/selfUtils.js +0 -5
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +70 -15
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/index.js +18 -9
- package/dist/media/index.js.map +1 -1
- package/dist/meeting/connectionStateHandler.js +67 -0
- package/dist/meeting/connectionStateHandler.js.map +1 -0
- package/dist/meeting/index.js +576 -374
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +7 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +6 -1
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/index.js +4 -4
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +2 -2
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/util.js +17 -17
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +16 -16
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +1 -1
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +41 -35
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +8 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/util.js +3 -2
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.js +2 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +57 -0
- package/dist/metrics/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +1 -1
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/reachability/clusterReachability.js +108 -53
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +546 -115
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +1 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/rtcMetrics/index.js +26 -6
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/types/constants.d.ts +11 -3
- package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
- package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
- package/dist/types/meeting/index.d.ts +28 -8
- package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
- package/dist/types/meeting-info/index.d.ts +3 -2
- package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
- package/dist/types/meeting-info/util.d.ts +5 -4
- package/dist/types/meeting-info/utilv2.d.ts +3 -2
- package/dist/types/meetings/collection.d.ts +3 -2
- package/dist/types/meetings/index.d.ts +6 -4
- package/dist/types/meetings/meetings.types.d.ts +9 -0
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/metrics/index.d.ts +15 -0
- package/dist/types/reachability/clusterReachability.d.ts +31 -3
- package/dist/types/reachability/index.d.ts +107 -4
- package/dist/types/rtcMetrics/index.d.ts +11 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +23 -23
- package/src/breakouts/index.ts +7 -1
- package/src/constants.ts +13 -17
- package/src/locus-info/selfUtils.ts +0 -5
- package/src/media/MediaConnectionAwaiter.ts +89 -14
- package/src/media/index.ts +18 -9
- package/src/meeting/connectionStateHandler.ts +65 -0
- package/src/meeting/index.ts +541 -298
- package/src/meeting/locusMediaRequest.ts +5 -0
- package/src/meeting/muteState.ts +6 -1
- package/src/meeting/util.ts +1 -0
- package/src/meeting-info/index.ts +9 -6
- package/src/meeting-info/meeting-info-v2.ts +4 -4
- package/src/meeting-info/util.ts +23 -28
- package/src/meeting-info/utilv2.ts +18 -24
- package/src/meetings/collection.ts +3 -3
- package/src/meetings/index.ts +43 -43
- package/src/meetings/meetings.types.ts +11 -0
- package/src/meetings/util.ts +5 -4
- package/src/metrics/constants.ts +1 -0
- package/src/metrics/index.ts +44 -0
- package/src/personal-meeting-room/index.ts +2 -2
- package/src/reachability/clusterReachability.ts +86 -25
- package/src/reachability/index.ts +364 -30
- package/src/reconnection-manager/index.ts +1 -1
- package/src/rtcMetrics/index.ts +25 -5
- package/test/unit/spec/breakouts/index.ts +51 -32
- package/test/unit/spec/locus-info/selfUtils.js +25 -23
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
- package/test/unit/spec/media/index.ts +75 -34
- package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
- package/test/unit/spec/meeting/index.js +807 -185
- package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
- package/test/unit/spec/meeting/muteState.js +24 -0
- package/test/unit/spec/meeting-info/index.js +4 -4
- package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
- package/test/unit/spec/meeting-info/request.js +2 -2
- package/test/unit/spec/meeting-info/utilv2.js +41 -49
- package/test/unit/spec/meetings/index.js +44 -3
- package/test/unit/spec/metrics/index.js +126 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
- package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
- package/test/unit/spec/reachability/clusterReachability.ts +116 -22
- package/test/unit/spec/reachability/index.ts +1398 -131
- package/test/unit/spec/rtcMetrics/index.ts +32 -0
- package/dist/mediaQualityMetrics/config.js +0 -321
- package/dist/mediaQualityMetrics/config.js.map +0 -1
- package/dist/networkQualityMonitor/index.js +0 -227
- package/dist/networkQualityMonitor/index.js.map +0 -1
- package/dist/statsAnalyzer/global.js +0 -44
- package/dist/statsAnalyzer/global.js.map +0 -1
- package/dist/statsAnalyzer/index.js +0 -1072
- package/dist/statsAnalyzer/index.js.map +0 -1
- package/dist/statsAnalyzer/mqaUtil.js +0 -368
- package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
- package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
- package/dist/types/networkQualityMonitor/index.d.ts +0 -70
- package/dist/types/statsAnalyzer/global.d.ts +0 -36
- package/dist/types/statsAnalyzer/index.d.ts +0 -217
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
- package/src/mediaQualityMetrics/config.ts +0 -255
- package/src/networkQualityMonitor/index.ts +0 -211
- package/src/statsAnalyzer/global.ts +0 -37
- package/src/statsAnalyzer/index.ts +0 -1318
- package/src/statsAnalyzer/mqaUtil.ts +0 -463
- package/test/unit/spec/networkQualityMonitor/index.js +0 -99
- package/test/unit/spec/stats-analyzer/index.js +0 -1819
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/* eslint-disable class-methods-use-this */
|
|
6
|
-
import {mapValues} from 'lodash';
|
|
6
|
+
import {isEqual, mapValues, mean} from 'lodash';
|
|
7
7
|
|
|
8
|
+
import {Defer} from '@webex/common';
|
|
8
9
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
9
10
|
import MeetingUtil from '../meeting/util';
|
|
10
11
|
|
|
@@ -12,10 +13,16 @@ import {REACHABILITY} from '../constants';
|
|
|
12
13
|
|
|
13
14
|
import ReachabilityRequest, {ClusterList} from './request';
|
|
14
15
|
import {
|
|
16
|
+
ClientMediaIpsUpdatedEventData,
|
|
15
17
|
ClusterReachability,
|
|
16
18
|
ClusterReachabilityResult,
|
|
19
|
+
Events,
|
|
20
|
+
ResultEventData,
|
|
17
21
|
TransportResult,
|
|
18
22
|
} from './clusterReachability';
|
|
23
|
+
import EventsScope from '../common/events/events-scope';
|
|
24
|
+
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
25
|
+
import Metrics from '../metrics';
|
|
19
26
|
|
|
20
27
|
export type ReachabilityMetrics = {
|
|
21
28
|
reachability_public_udp_success: number;
|
|
@@ -60,11 +67,16 @@ export type ReachabilityResults = Record<
|
|
|
60
67
|
}
|
|
61
68
|
>;
|
|
62
69
|
|
|
70
|
+
// timeouts in seconds
|
|
71
|
+
const DEFAULT_TIMEOUT = 3;
|
|
72
|
+
const VIDEO_MESH_TIMEOUT = 1;
|
|
73
|
+
const OVERALL_TIMEOUT = 15;
|
|
74
|
+
|
|
63
75
|
/**
|
|
64
76
|
* @class Reachability
|
|
65
77
|
* @export
|
|
66
78
|
*/
|
|
67
|
-
export default class Reachability {
|
|
79
|
+
export default class Reachability extends EventsScope {
|
|
68
80
|
namespace = REACHABILITY.namespace;
|
|
69
81
|
webex: object;
|
|
70
82
|
reachabilityRequest: ReachabilityRequest;
|
|
@@ -72,12 +84,24 @@ export default class Reachability {
|
|
|
72
84
|
[key: string]: ClusterReachability;
|
|
73
85
|
};
|
|
74
86
|
|
|
87
|
+
reachabilityDefer?: Defer;
|
|
88
|
+
|
|
89
|
+
vmnTimer?: ReturnType<typeof setTimeout>;
|
|
90
|
+
publicCloudTimer?: ReturnType<typeof setTimeout>;
|
|
91
|
+
overallTimer?: ReturnType<typeof setTimeout>;
|
|
92
|
+
|
|
93
|
+
expectedResultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
|
|
94
|
+
resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
|
|
95
|
+
|
|
96
|
+
protected lastTrigger?: string;
|
|
97
|
+
|
|
75
98
|
/**
|
|
76
99
|
* Creates an instance of Reachability.
|
|
77
100
|
* @param {object} webex
|
|
78
101
|
* @memberof Reachability
|
|
79
102
|
*/
|
|
80
103
|
constructor(webex: object) {
|
|
104
|
+
super();
|
|
81
105
|
this.webex = webex;
|
|
82
106
|
|
|
83
107
|
/**
|
|
@@ -92,28 +116,51 @@ export default class Reachability {
|
|
|
92
116
|
this.clusterReachability = {};
|
|
93
117
|
}
|
|
94
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Fetches the list of media clusters from the backend
|
|
121
|
+
* @param {boolean} isRetry
|
|
122
|
+
* @private
|
|
123
|
+
* @returns {Promise<{clusters: ClusterList, joinCookie: any}>}
|
|
124
|
+
*/
|
|
125
|
+
async getClusters(isRetry = false): Promise<{clusters: ClusterList; joinCookie: any}> {
|
|
126
|
+
try {
|
|
127
|
+
const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
|
|
128
|
+
MeetingUtil.getIpVersion(this.webex)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return {clusters, joinCookie};
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (isRetry) {
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
LoggerProxy.logger.error(
|
|
138
|
+
`Reachability:index#getClusters --> Failed with error: ${error}, retrying...`
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
return this.getClusters(true);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
95
145
|
/**
|
|
96
146
|
* Gets a list of media clusters from the backend and performs reachability checks on all the clusters
|
|
147
|
+
* @param {string} trigger - explains the reason for starting reachability
|
|
97
148
|
* @returns {Promise<ReachabilityResults>} reachability results
|
|
98
149
|
* @public
|
|
99
150
|
* @memberof Reachability
|
|
100
151
|
*/
|
|
101
|
-
public async gatherReachability(): Promise<ReachabilityResults> {
|
|
152
|
+
public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
|
|
102
153
|
// Fetch clusters and measure latency
|
|
103
154
|
try {
|
|
104
|
-
|
|
105
|
-
MeetingUtil.getIpVersion(this.webex)
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// Perform Reachability Check
|
|
109
|
-
const results = await this.performReachabilityChecks(clusters);
|
|
155
|
+
this.lastTrigger = trigger;
|
|
110
156
|
|
|
157
|
+
// kick off ip version detection. For now we don't await it, as we're doing it
|
|
158
|
+
// to gather the timings and send them with our reachability metrics
|
|
111
159
|
// @ts-ignore
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
);
|
|
160
|
+
this.webex.internal.device.ipNetworkDetector.detect();
|
|
161
|
+
|
|
162
|
+
const {clusters, joinCookie} = await this.getClusters();
|
|
163
|
+
|
|
117
164
|
// @ts-ignore
|
|
118
165
|
await this.webex.boundedStorage.put(
|
|
119
166
|
this.namespace,
|
|
@@ -121,11 +168,12 @@ export default class Reachability {
|
|
|
121
168
|
JSON.stringify(joinCookie)
|
|
122
169
|
);
|
|
123
170
|
|
|
124
|
-
|
|
125
|
-
'Reachability:index#gatherReachability --> Reachability checks completed'
|
|
126
|
-
);
|
|
171
|
+
this.reachabilityDefer = new Defer();
|
|
127
172
|
|
|
128
|
-
|
|
173
|
+
// Perform Reachability Check
|
|
174
|
+
await this.performReachabilityChecks(clusters);
|
|
175
|
+
|
|
176
|
+
return this.reachabilityDefer.promise;
|
|
129
177
|
} catch (error) {
|
|
130
178
|
LoggerProxy.logger.error(`Reachability:index#gatherReachability --> Error:`, error);
|
|
131
179
|
|
|
@@ -395,16 +443,221 @@ export default class Reachability {
|
|
|
395
443
|
});
|
|
396
444
|
}
|
|
397
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Returns true if we've obtained all the reachability results for all the public clusters
|
|
448
|
+
* In other words, it means that all public clusters are reachable over each protocol,
|
|
449
|
+
* because we only get a "result" if we managed to reach a cluster
|
|
450
|
+
*
|
|
451
|
+
* @returns {boolean}
|
|
452
|
+
*/
|
|
453
|
+
private areAllPublicClusterResultsReady() {
|
|
454
|
+
return isEqual(this.expectedResultsCount.public, this.resultsCount.public);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Returns true if we've obtained all the reachability results for all the clusters
|
|
459
|
+
*
|
|
460
|
+
* @returns {boolean}
|
|
461
|
+
*/
|
|
462
|
+
private areAllResultsReady() {
|
|
463
|
+
return isEqual(this.expectedResultsCount, this.resultsCount);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Resolves the promise returned by gatherReachability() method
|
|
468
|
+
* @returns {void}
|
|
469
|
+
*/
|
|
470
|
+
private resolveReachabilityPromise() {
|
|
471
|
+
if (this.vmnTimer) {
|
|
472
|
+
clearTimeout(this.vmnTimer);
|
|
473
|
+
}
|
|
474
|
+
if (this.publicCloudTimer) {
|
|
475
|
+
clearTimeout(this.publicCloudTimer);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.logUnreachableClusters();
|
|
479
|
+
this.reachabilityDefer?.resolve();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Aborts all cluster reachability checks that are in progress
|
|
484
|
+
*
|
|
485
|
+
* @returns {void}
|
|
486
|
+
*/
|
|
487
|
+
private abortClusterReachability() {
|
|
488
|
+
Object.values(this.clusterReachability).forEach((clusterReachability) => {
|
|
489
|
+
clusterReachability.abort();
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Helper function for calculating min/max/average values of latency
|
|
495
|
+
*
|
|
496
|
+
* @param {Array<any>} results
|
|
497
|
+
* @param {string} protocol
|
|
498
|
+
* @param {boolean} isVideoMesh
|
|
499
|
+
* @returns {{min:number, max: number, average: number}}
|
|
500
|
+
*/
|
|
501
|
+
protected getStatistics(
|
|
502
|
+
results: Array<ClusterReachabilityResult & {isVideoMesh: boolean}>,
|
|
503
|
+
protocol: 'udp' | 'tcp' | 'xtls',
|
|
504
|
+
isVideoMesh: boolean
|
|
505
|
+
) {
|
|
506
|
+
const values = results
|
|
507
|
+
.filter((result) => result.isVideoMesh === isVideoMesh)
|
|
508
|
+
.filter((result) => result[protocol].result === 'reachable')
|
|
509
|
+
.map((result) => result[protocol].latencyInMilliseconds);
|
|
510
|
+
|
|
511
|
+
if (values.length === 0) {
|
|
512
|
+
return {
|
|
513
|
+
min: -1,
|
|
514
|
+
max: -1,
|
|
515
|
+
average: -1,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
min: Math.min(...values),
|
|
521
|
+
max: Math.max(...values),
|
|
522
|
+
average: mean(values),
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Sends a metric with all the statistics about how long reachability took
|
|
528
|
+
*
|
|
529
|
+
* @returns {void}
|
|
530
|
+
*/
|
|
531
|
+
protected async sendMetric() {
|
|
532
|
+
const results = [];
|
|
533
|
+
|
|
534
|
+
Object.values(this.clusterReachability).forEach((clusterReachability) => {
|
|
535
|
+
results.push({
|
|
536
|
+
...clusterReachability.getResult(),
|
|
537
|
+
isVideoMesh: clusterReachability.isVideoMesh,
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const stats = {
|
|
542
|
+
vmn: {
|
|
543
|
+
udp: this.getStatistics(results, 'udp', true),
|
|
544
|
+
},
|
|
545
|
+
public: {
|
|
546
|
+
udp: this.getStatistics(results, 'udp', false),
|
|
547
|
+
tcp: this.getStatistics(results, 'tcp', false),
|
|
548
|
+
xtls: this.getStatistics(results, 'xtls', false),
|
|
549
|
+
},
|
|
550
|
+
ipver: {
|
|
551
|
+
// @ts-ignore
|
|
552
|
+
firstIpV4: this.webex.internal.device.ipNetworkDetector.firstIpV4,
|
|
553
|
+
// @ts-ignore
|
|
554
|
+
firstIpV6: this.webex.internal.device.ipNetworkDetector.firstIpV6,
|
|
555
|
+
// @ts-ignore
|
|
556
|
+
firstMdns: this.webex.internal.device.ipNetworkDetector.firstMdns,
|
|
557
|
+
// @ts-ignore
|
|
558
|
+
totalTime: this.webex.internal.device.ipNetworkDetector.totalTime,
|
|
559
|
+
},
|
|
560
|
+
trigger: this.lastTrigger,
|
|
561
|
+
};
|
|
562
|
+
Metrics.sendBehavioralMetric(
|
|
563
|
+
BEHAVIORAL_METRICS.REACHABILITY_COMPLETED,
|
|
564
|
+
Metrics.prepareMetricFields(stats)
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Starts all the timers used for various timeouts
|
|
570
|
+
*
|
|
571
|
+
* @returns {void}
|
|
572
|
+
*/
|
|
573
|
+
private startTimers() {
|
|
574
|
+
this.vmnTimer = setTimeout(() => {
|
|
575
|
+
this.vmnTimer = undefined;
|
|
576
|
+
// if we are only missing VMN results, then we don't want to wait for them any longer
|
|
577
|
+
// as they are likely to fail if users are not on corporate network
|
|
578
|
+
if (this.areAllPublicClusterResultsReady()) {
|
|
579
|
+
LoggerProxy.logger.log(
|
|
580
|
+
'Reachability:index#startTimers --> Reachability checks timed out (VMN timeout)'
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
this.resolveReachabilityPromise();
|
|
584
|
+
}
|
|
585
|
+
}, VIDEO_MESH_TIMEOUT * 1000);
|
|
586
|
+
|
|
587
|
+
this.publicCloudTimer = setTimeout(() => {
|
|
588
|
+
this.publicCloudTimer = undefined;
|
|
589
|
+
|
|
590
|
+
LoggerProxy.logger.log(
|
|
591
|
+
`Reachability:index#startTimers --> Reachability checks timed out (${DEFAULT_TIMEOUT}s)`
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
// resolve the promise, so that the client won't be blocked waiting on meetings.register() for too long
|
|
595
|
+
this.resolveReachabilityPromise();
|
|
596
|
+
}, DEFAULT_TIMEOUT * 1000);
|
|
597
|
+
|
|
598
|
+
this.overallTimer = setTimeout(() => {
|
|
599
|
+
this.overallTimer = undefined;
|
|
600
|
+
this.abortClusterReachability();
|
|
601
|
+
this.emit(
|
|
602
|
+
{
|
|
603
|
+
file: 'reachability',
|
|
604
|
+
function: 'overallTimer timeout',
|
|
605
|
+
},
|
|
606
|
+
'reachability:done',
|
|
607
|
+
{}
|
|
608
|
+
);
|
|
609
|
+
this.sendMetric();
|
|
610
|
+
|
|
611
|
+
LoggerProxy.logger.log(
|
|
612
|
+
`Reachability:index#startTimers --> Reachability checks fully timed out (${OVERALL_TIMEOUT}s)`
|
|
613
|
+
);
|
|
614
|
+
}, OVERALL_TIMEOUT * 1000);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Stores given reachability results in local storage
|
|
619
|
+
*
|
|
620
|
+
* @param {ReachabilityResults} results
|
|
621
|
+
* @returns {Promise<void>}
|
|
622
|
+
*/
|
|
623
|
+
private async storeResults(results: ReachabilityResults) {
|
|
624
|
+
// @ts-ignore
|
|
625
|
+
await this.webex.boundedStorage.put(
|
|
626
|
+
this.namespace,
|
|
627
|
+
REACHABILITY.localStorageResult,
|
|
628
|
+
JSON.stringify(results)
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Resets all the internal counters that keep track of the results
|
|
634
|
+
*
|
|
635
|
+
* @returns {void}
|
|
636
|
+
*/
|
|
637
|
+
private resetResultCounters() {
|
|
638
|
+
this.expectedResultsCount.videoMesh.udp = 0;
|
|
639
|
+
this.expectedResultsCount.public.udp = 0;
|
|
640
|
+
this.expectedResultsCount.public.tcp = 0;
|
|
641
|
+
this.expectedResultsCount.public.xtls = 0;
|
|
642
|
+
|
|
643
|
+
this.resultsCount.videoMesh.udp = 0;
|
|
644
|
+
this.resultsCount.public.udp = 0;
|
|
645
|
+
this.resultsCount.public.tcp = 0;
|
|
646
|
+
this.resultsCount.public.xtls = 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
398
649
|
/**
|
|
399
650
|
* Performs reachability checks for all clusters
|
|
400
651
|
* @param {ClusterList} clusterList
|
|
401
|
-
* @returns {Promise<
|
|
652
|
+
* @returns {Promise<void>} promise that's resolved as soon as the checks are started
|
|
402
653
|
*/
|
|
403
|
-
private async performReachabilityChecks(clusterList: ClusterList)
|
|
654
|
+
private async performReachabilityChecks(clusterList: ClusterList) {
|
|
404
655
|
const results: ReachabilityResults = {};
|
|
405
656
|
|
|
657
|
+
this.clusterReachability = {};
|
|
658
|
+
|
|
406
659
|
if (!clusterList || !Object.keys(clusterList).length) {
|
|
407
|
-
return
|
|
660
|
+
return;
|
|
408
661
|
}
|
|
409
662
|
|
|
410
663
|
LoggerProxy.logger.log(
|
|
@@ -417,7 +670,11 @@ export default class Reachability {
|
|
|
417
670
|
} reachability checks`
|
|
418
671
|
);
|
|
419
672
|
|
|
420
|
-
|
|
673
|
+
this.resetResultCounters();
|
|
674
|
+
this.startTimers();
|
|
675
|
+
|
|
676
|
+
// sanitize the urls in the clusterList
|
|
677
|
+
Object.keys(clusterList).forEach((key) => {
|
|
421
678
|
const cluster = clusterList[key];
|
|
422
679
|
|
|
423
680
|
// Linus doesn't support TCP reachability checks on video mesh nodes
|
|
@@ -429,6 +686,7 @@ export default class Reachability {
|
|
|
429
686
|
cluster.tcp = [];
|
|
430
687
|
}
|
|
431
688
|
|
|
689
|
+
// Linus doesn't support xTLS reachability checks on video mesh nodes
|
|
432
690
|
const includeTlsReachability =
|
|
433
691
|
// @ts-ignore
|
|
434
692
|
this.webex.config.meetings.experimental.enableTlsReachability && !cluster.isVideoMesh;
|
|
@@ -437,18 +695,94 @@ export default class Reachability {
|
|
|
437
695
|
cluster.xtls = [];
|
|
438
696
|
}
|
|
439
697
|
|
|
440
|
-
|
|
698
|
+
// initialize the result for this cluster
|
|
699
|
+
results[key] = {
|
|
700
|
+
udp: {result: cluster.udp.length > 0 ? 'unreachable' : 'untested'},
|
|
701
|
+
tcp: {result: cluster.tcp.length > 0 ? 'unreachable' : 'untested'},
|
|
702
|
+
xtls: {result: cluster.xtls.length > 0 ? 'unreachable' : 'untested'},
|
|
703
|
+
isVideoMesh: cluster.isVideoMesh,
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// update expected results counters to include this cluster
|
|
707
|
+
this.expectedResultsCount[cluster.isVideoMesh ? 'videoMesh' : 'public'].udp +=
|
|
708
|
+
cluster.udp.length;
|
|
709
|
+
if (!cluster.isVideoMesh) {
|
|
710
|
+
this.expectedResultsCount.public.tcp += cluster.tcp.length;
|
|
711
|
+
this.expectedResultsCount.public.xtls += cluster.xtls.length;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
const isFirstResult = {
|
|
716
|
+
udp: true,
|
|
717
|
+
tcp: true,
|
|
718
|
+
xtls: true,
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// save the initialized results (in case we don't get any "resultReady" events at all)
|
|
722
|
+
await this.storeResults(results);
|
|
723
|
+
|
|
724
|
+
// now start the reachability on all the clusters
|
|
725
|
+
Object.keys(clusterList).forEach((key) => {
|
|
726
|
+
const cluster = clusterList[key];
|
|
441
727
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
728
|
+
this.clusterReachability[key] = new ClusterReachability(key, cluster);
|
|
729
|
+
this.clusterReachability[key].on(Events.resultReady, async (data: ResultEventData) => {
|
|
730
|
+
const {protocol, result, clientMediaIPs, latencyInMilliseconds} = data;
|
|
731
|
+
|
|
732
|
+
if (isFirstResult[protocol]) {
|
|
733
|
+
this.emit(
|
|
734
|
+
{
|
|
735
|
+
file: 'reachability',
|
|
736
|
+
function: 'resultReady event handler',
|
|
737
|
+
},
|
|
738
|
+
'reachability:firstResultAvailable',
|
|
739
|
+
{
|
|
740
|
+
protocol,
|
|
741
|
+
}
|
|
742
|
+
);
|
|
743
|
+
isFirstResult[protocol] = false;
|
|
744
|
+
}
|
|
745
|
+
this.resultsCount[cluster.isVideoMesh ? 'videoMesh' : 'public'][protocol] += 1;
|
|
746
|
+
|
|
747
|
+
const areAllResultsReady = this.areAllResultsReady();
|
|
748
|
+
|
|
749
|
+
results[key][protocol].result = result;
|
|
750
|
+
results[key][protocol].clientMediaIPs = clientMediaIPs;
|
|
751
|
+
results[key][protocol].latencyInMilliseconds = latencyInMilliseconds;
|
|
752
|
+
|
|
753
|
+
await this.storeResults(results);
|
|
754
|
+
|
|
755
|
+
if (areAllResultsReady) {
|
|
756
|
+
clearTimeout(this.overallTimer);
|
|
757
|
+
this.overallTimer = undefined;
|
|
758
|
+
this.emit(
|
|
759
|
+
{
|
|
760
|
+
file: 'reachability',
|
|
761
|
+
function: 'performReachabilityChecks',
|
|
762
|
+
},
|
|
763
|
+
'reachability:done',
|
|
764
|
+
{}
|
|
765
|
+
);
|
|
766
|
+
this.sendMetric();
|
|
767
|
+
|
|
768
|
+
LoggerProxy.logger.log(
|
|
769
|
+
`Reachability:index#gatherReachability --> Reachability checks fully completed`
|
|
770
|
+
);
|
|
771
|
+
this.resolveReachabilityPromise();
|
|
772
|
+
}
|
|
445
773
|
});
|
|
446
|
-
});
|
|
447
774
|
|
|
448
|
-
|
|
775
|
+
// clientMediaIps can be updated independently from the results, so we need to listen for them too
|
|
776
|
+
this.clusterReachability[key].on(
|
|
777
|
+
Events.clientMediaIpsUpdated,
|
|
778
|
+
async (data: ClientMediaIpsUpdatedEventData) => {
|
|
779
|
+
results[key][data.protocol].clientMediaIPs = data.clientMediaIPs;
|
|
449
780
|
|
|
450
|
-
|
|
781
|
+
await this.storeResults(results);
|
|
782
|
+
}
|
|
783
|
+
);
|
|
451
784
|
|
|
452
|
-
|
|
785
|
+
this.clusterReachability[key].start(); // not awaiting on purpose
|
|
786
|
+
});
|
|
453
787
|
}
|
|
454
788
|
}
|
|
@@ -342,7 +342,7 @@ export default class ReconnectionManager {
|
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
try {
|
|
345
|
-
await this.webex.meetings.startReachability();
|
|
345
|
+
await this.webex.meetings.startReachability('reconnection');
|
|
346
346
|
} catch (err) {
|
|
347
347
|
LoggerProxy.logger.info(
|
|
348
348
|
'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
|
package/src/rtcMetrics/index.ts
CHANGED
|
@@ -34,6 +34,8 @@ export default class RtcMetrics {
|
|
|
34
34
|
|
|
35
35
|
connectionId: string;
|
|
36
36
|
|
|
37
|
+
shouldSendMetricsOnNextStatsReport: boolean;
|
|
38
|
+
|
|
37
39
|
/**
|
|
38
40
|
* Initialize the interval.
|
|
39
41
|
*
|
|
@@ -47,9 +49,7 @@ export default class RtcMetrics {
|
|
|
47
49
|
this.meetingId = meetingId;
|
|
48
50
|
this.webex = webex;
|
|
49
51
|
this.correlationId = correlationId;
|
|
50
|
-
this.
|
|
51
|
-
// Send the first set of metrics at 5 seconds in the case of a user leaving the call shortly after joining.
|
|
52
|
-
setTimeout(this.sendMetricsInQueue.bind(this), 5 * 1000);
|
|
52
|
+
this.resetConnection();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
@@ -64,6 +64,18 @@ export default class RtcMetrics {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Forces sending metrics when we get the next stats-report
|
|
69
|
+
*
|
|
70
|
+
* This is useful for cases when something important happens that affects the media connection,
|
|
71
|
+
* for example when we move from lobby into the meeting.
|
|
72
|
+
*
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
public sendNextMetrics() {
|
|
76
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
67
79
|
/**
|
|
68
80
|
* Add metrics items to the metrics queue.
|
|
69
81
|
*
|
|
@@ -79,6 +91,13 @@ export default class RtcMetrics {
|
|
|
79
91
|
|
|
80
92
|
this.metricsQueue.push(data);
|
|
81
93
|
|
|
94
|
+
if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {
|
|
95
|
+
// this is the first useful set of data (WCME gives it to us after 5s), send it out immediately
|
|
96
|
+
// in case the user is unhappy and closes the browser early
|
|
97
|
+
this.sendMetricsInQueue();
|
|
98
|
+
this.shouldSendMetricsOnNextStatsReport = false;
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
try {
|
|
83
102
|
// If a connection fails, send the rest of the metrics in queue and get a new connection id.
|
|
84
103
|
const parsedPayload = parseJsonPayload(data.payload);
|
|
@@ -88,7 +107,7 @@ export default class RtcMetrics {
|
|
|
88
107
|
parsedPayload.value === 'failed'
|
|
89
108
|
) {
|
|
90
109
|
this.sendMetricsInQueue();
|
|
91
|
-
this.
|
|
110
|
+
this.resetConnection();
|
|
92
111
|
}
|
|
93
112
|
} catch (e) {
|
|
94
113
|
console.error(e);
|
|
@@ -130,8 +149,9 @@ export default class RtcMetrics {
|
|
|
130
149
|
*
|
|
131
150
|
* @returns {void}
|
|
132
151
|
*/
|
|
133
|
-
private
|
|
152
|
+
private resetConnection() {
|
|
134
153
|
this.connectionId = uuid.v4();
|
|
154
|
+
this.shouldSendMetricsOnNextStatsReport = true;
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
/**
|
|
@@ -87,6 +87,7 @@ describe('plugin-meetings', () => {
|
|
|
87
87
|
// @ts-ignore
|
|
88
88
|
webex = new MockWebex({});
|
|
89
89
|
webex.internal.llm.on = sinon.stub();
|
|
90
|
+
webex.internal.llm.isConnected = sinon.stub();
|
|
90
91
|
webex.internal.mercury.on = sinon.stub();
|
|
91
92
|
breakouts = new Breakouts({}, {parent: webex});
|
|
92
93
|
breakouts.groupId = 'groupId';
|
|
@@ -225,38 +226,6 @@ describe('plugin-meetings', () => {
|
|
|
225
226
|
});
|
|
226
227
|
});
|
|
227
228
|
|
|
228
|
-
describe('#listenToBroadcastMessages', () => {
|
|
229
|
-
it('triggers message event when a message received', () => {
|
|
230
|
-
const call = webex.internal.llm.on.getCall(0);
|
|
231
|
-
const callback = call.args[1];
|
|
232
|
-
|
|
233
|
-
assert.equal(call.args[0], 'event:breakout.message');
|
|
234
|
-
|
|
235
|
-
let message;
|
|
236
|
-
|
|
237
|
-
breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MESSAGE, (event) => {
|
|
238
|
-
message = event;
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
breakouts.currentBreakoutSession.sessionId = 'sessionId';
|
|
242
|
-
|
|
243
|
-
callback({
|
|
244
|
-
data: {
|
|
245
|
-
senderUserId: 'senderUserId',
|
|
246
|
-
sentTime: 'sentTime',
|
|
247
|
-
message: 'message',
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
assert.deepEqual(message, {
|
|
252
|
-
senderUserId: 'senderUserId',
|
|
253
|
-
sentTime: 'sentTime',
|
|
254
|
-
message: 'message',
|
|
255
|
-
sessionId: 'sessionId',
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
229
|
describe('#listenToBreakoutRosters', () => {
|
|
261
230
|
it('triggers member update event when a roster received', () => {
|
|
262
231
|
const call = webex.internal.mercury.on.getCall(0);
|
|
@@ -496,8 +465,58 @@ describe('plugin-meetings', () => {
|
|
|
496
465
|
describe('#locusUrlUpdate', () => {
|
|
497
466
|
it('sets the locus url', () => {
|
|
498
467
|
breakouts.locusUrlUpdate('newUrl');
|
|
468
|
+
assert.equal(breakouts.locusUrl, 'newUrl');
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('#listenToBroadcastMessages', () => {
|
|
473
|
+
it('do not subscribe message if llm not connected', () => {
|
|
474
|
+
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
475
|
+
breakouts.listenTo = sinon.stub();
|
|
476
|
+
breakouts.locusUrlUpdate('newUrl');
|
|
477
|
+
assert.equal(breakouts.locusUrl, 'newUrl');
|
|
478
|
+
assert.notCalled(breakouts.listenTo);
|
|
479
|
+
});
|
|
499
480
|
|
|
481
|
+
it('do not subscribe message if already done', () => {
|
|
482
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
483
|
+
breakouts.hasSubscribedToMessage = true;
|
|
484
|
+
breakouts.listenTo = sinon.stub();
|
|
485
|
+
breakouts.locusUrlUpdate('newUrl');
|
|
500
486
|
assert.equal(breakouts.locusUrl, 'newUrl');
|
|
487
|
+
assert.notCalled(breakouts.listenTo);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('triggers message event when a message received', () => {
|
|
491
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
492
|
+
breakouts.locusUrlUpdate('newUrl');
|
|
493
|
+
const call = webex.internal.llm.on.getCall(0);
|
|
494
|
+
const callback = call.args[1];
|
|
495
|
+
|
|
496
|
+
assert.equal(call.args[0], 'event:breakout.message');
|
|
497
|
+
|
|
498
|
+
let message;
|
|
499
|
+
|
|
500
|
+
breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MESSAGE, (event) => {
|
|
501
|
+
message = event;
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
breakouts.currentBreakoutSession.sessionId = 'sessionId';
|
|
505
|
+
|
|
506
|
+
callback({
|
|
507
|
+
data: {
|
|
508
|
+
senderUserId: 'senderUserId',
|
|
509
|
+
sentTime: 'sentTime',
|
|
510
|
+
message: 'message',
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
assert.deepEqual(message, {
|
|
515
|
+
senderUserId: 'senderUserId',
|
|
516
|
+
sentTime: 'sentTime',
|
|
517
|
+
message: 'message',
|
|
518
|
+
sessionId: 'sessionId',
|
|
519
|
+
});
|
|
501
520
|
});
|
|
502
521
|
});
|
|
503
522
|
|