@webex/plugin-meetings 3.6.0-next.9 → 3.7.0-next.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/README.md +2 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +24 -2
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +1 -0
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/controls-options-manager/index.js +10 -3
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/types.js.map +1 -1
- package/dist/controls-options-manager/util.js +12 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +28 -4
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js +2 -1
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/index.js +61 -3
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +19 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +564 -441
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +2 -6
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/request.js +21 -29
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +94 -59
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +2 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/members/index.js +3 -2
- package/dist/members/index.js.map +1 -1
- package/dist/members/util.js +9 -5
- package/dist/members/util.js.map +1 -1
- package/dist/reachability/clusterReachability.js +0 -4
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.js +433 -136
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +7 -0
- package/dist/reachability/reachability.types.js.map +1 -0
- package/dist/reachability/request.js +23 -9
- package/dist/reachability/request.js.map +1 -1
- package/dist/roap/index.js +5 -7
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +45 -79
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +3 -6
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +19 -0
- package/dist/types/controls-options-manager/enums.d.ts +2 -1
- package/dist/types/controls-options-manager/index.d.ts +2 -1
- package/dist/types/controls-options-manager/types.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +9 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +18 -0
- package/dist/types/meeting/index.d.ts +14 -3
- package/dist/types/meeting/locusMediaRequest.d.ts +2 -3
- package/dist/types/meeting/request.d.ts +2 -2
- package/dist/types/meeting/util.d.ts +2 -2
- package/dist/types/meetings/index.d.ts +1 -1
- package/dist/types/members/index.d.ts +2 -1
- package/dist/types/members/util.d.ts +3 -1
- package/dist/types/reachability/clusterReachability.d.ts +1 -10
- package/dist/types/reachability/index.d.ts +74 -35
- package/dist/types/reachability/reachability.types.d.ts +64 -0
- package/dist/types/reachability/request.d.ts +5 -1
- package/dist/types/roap/request.d.ts +1 -13
- package/dist/webinar/index.js +32 -19
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/config.ts +1 -0
- package/src/constants.ts +25 -0
- package/src/controls-options-manager/enums.ts +1 -0
- package/src/controls-options-manager/index.ts +19 -2
- package/src/controls-options-manager/types.ts +2 -0
- package/src/controls-options-manager/util.ts +12 -0
- package/src/locus-info/controlsUtils.ts +46 -2
- package/src/locus-info/fullState.ts +1 -0
- package/src/locus-info/index.ts +60 -0
- package/src/meeting/in-meeting-actions.ts +37 -0
- package/src/meeting/index.ts +114 -11
- package/src/meeting/locusMediaRequest.ts +4 -8
- package/src/meeting/request.ts +4 -11
- package/src/meeting/util.ts +24 -4
- package/src/meetings/index.ts +46 -39
- package/src/members/index.ts +4 -2
- package/src/members/util.ts +3 -1
- package/src/reachability/clusterReachability.ts +1 -14
- package/src/reachability/index.ts +285 -77
- package/src/reachability/reachability.types.ts +85 -0
- package/src/reachability/request.ts +55 -30
- package/src/roap/index.ts +4 -5
- package/src/roap/request.ts +30 -44
- package/src/roap/turnDiscovery.ts +2 -4
- package/src/webinar/index.ts +31 -17
- package/test/unit/spec/controls-options-manager/index.js +56 -32
- package/test/unit/spec/controls-options-manager/util.js +44 -0
- package/test/unit/spec/locus-info/controlsUtils.js +80 -4
- package/test/unit/spec/locus-info/index.js +59 -2
- package/test/unit/spec/meeting/in-meeting-actions.ts +18 -0
- package/test/unit/spec/meeting/index.js +231 -100
- package/test/unit/spec/meeting/locusMediaRequest.ts +18 -11
- package/test/unit/spec/meeting/request.js +3 -26
- package/test/unit/spec/meeting/utils.js +53 -13
- package/test/unit/spec/meetings/index.js +16 -1
- package/test/unit/spec/members/index.js +25 -2
- package/test/unit/spec/members/request.js +37 -3
- package/test/unit/spec/members/utils.js +15 -1
- package/test/unit/spec/reachability/index.ts +265 -1
- package/test/unit/spec/reachability/request.js +56 -15
- package/test/unit/spec/roap/index.ts +1 -1
- package/test/unit/spec/roap/request.ts +51 -109
- package/test/unit/spec/roap/turnDiscovery.ts +202 -147
- package/test/unit/spec/webinar/index.ts +82 -16
|
@@ -9,64 +9,31 @@ import {Defer} from '@webex/common';
|
|
|
9
9
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
10
10
|
import MeetingUtil from '../meeting/util';
|
|
11
11
|
|
|
12
|
-
import {REACHABILITY} from '../constants';
|
|
12
|
+
import {IP_VERSION, REACHABILITY} from '../constants';
|
|
13
13
|
|
|
14
14
|
import ReachabilityRequest, {ClusterList} from './request';
|
|
15
|
+
import {
|
|
16
|
+
ClusterReachabilityResult,
|
|
17
|
+
TransportResult,
|
|
18
|
+
ClientMediaPreferences,
|
|
19
|
+
ReachabilityMetrics,
|
|
20
|
+
ReachabilityReportV0,
|
|
21
|
+
ReachabilityReportV1,
|
|
22
|
+
ReachabilityResults,
|
|
23
|
+
ReachabilityResultsForBackend,
|
|
24
|
+
TransportResultForBackend,
|
|
25
|
+
GetClustersTrigger,
|
|
26
|
+
} from './reachability.types';
|
|
15
27
|
import {
|
|
16
28
|
ClientMediaIpsUpdatedEventData,
|
|
17
29
|
ClusterReachability,
|
|
18
|
-
ClusterReachabilityResult,
|
|
19
30
|
Events,
|
|
20
31
|
ResultEventData,
|
|
21
|
-
TransportResult,
|
|
22
32
|
} from './clusterReachability';
|
|
23
33
|
import EventsScope from '../common/events/events-scope';
|
|
24
34
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
25
35
|
import Metrics from '../metrics';
|
|
26
36
|
|
|
27
|
-
export type ReachabilityMetrics = {
|
|
28
|
-
reachability_public_udp_success: number;
|
|
29
|
-
reachability_public_udp_failed: number;
|
|
30
|
-
reachability_public_tcp_success: number;
|
|
31
|
-
reachability_public_tcp_failed: number;
|
|
32
|
-
reachability_public_xtls_success: number;
|
|
33
|
-
reachability_public_xtls_failed: number;
|
|
34
|
-
reachability_vmn_udp_success: number;
|
|
35
|
-
reachability_vmn_udp_failed: number;
|
|
36
|
-
reachability_vmn_tcp_success: number;
|
|
37
|
-
reachability_vmn_tcp_failed: number;
|
|
38
|
-
reachability_vmn_xtls_success: number;
|
|
39
|
-
reachability_vmn_xtls_failed: number;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* This is the type that matches what backend expects us to send to them. It is a bit weird, because
|
|
44
|
-
* it uses strings instead of booleans and numbers, but that's what they require.
|
|
45
|
-
*/
|
|
46
|
-
export type TransportResultForBackend = {
|
|
47
|
-
reachable?: 'true' | 'false';
|
|
48
|
-
latencyInMilliseconds?: string;
|
|
49
|
-
clientMediaIPs?: string[];
|
|
50
|
-
untested?: 'true';
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type ReachabilityResultForBackend = {
|
|
54
|
-
udp: TransportResultForBackend;
|
|
55
|
-
tcp: TransportResultForBackend;
|
|
56
|
-
xtls: TransportResultForBackend;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// this is the type that is required by the backend when we send them reachability results
|
|
60
|
-
export type ReachabilityResultsForBackend = Record<string, ReachabilityResultForBackend>;
|
|
61
|
-
|
|
62
|
-
// this is the type used by Reachability class internally and stored in local storage
|
|
63
|
-
export type ReachabilityResults = Record<
|
|
64
|
-
string,
|
|
65
|
-
ClusterReachabilityResult & {
|
|
66
|
-
isVideoMesh?: boolean;
|
|
67
|
-
}
|
|
68
|
-
>;
|
|
69
|
-
|
|
70
37
|
// timeouts in seconds
|
|
71
38
|
const DEFAULT_TIMEOUT = 3;
|
|
72
39
|
const VIDEO_MESH_TIMEOUT = 1;
|
|
@@ -84,6 +51,9 @@ export default class Reachability extends EventsScope {
|
|
|
84
51
|
[key: string]: ClusterReachability;
|
|
85
52
|
};
|
|
86
53
|
|
|
54
|
+
minRequiredClusters?: number;
|
|
55
|
+
orpheusApiVersion?: number;
|
|
56
|
+
|
|
87
57
|
reachabilityDefer?: Defer;
|
|
88
58
|
|
|
89
59
|
vmnTimer?: ReturnType<typeof setTimeout>;
|
|
@@ -92,6 +62,8 @@ export default class Reachability extends EventsScope {
|
|
|
92
62
|
|
|
93
63
|
expectedResultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
|
|
94
64
|
resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
|
|
65
|
+
startTime = undefined;
|
|
66
|
+
totalDuration = undefined;
|
|
95
67
|
|
|
96
68
|
protected lastTrigger?: string;
|
|
97
69
|
|
|
@@ -118,14 +90,35 @@ export default class Reachability extends EventsScope {
|
|
|
118
90
|
|
|
119
91
|
/**
|
|
120
92
|
* Fetches the list of media clusters from the backend
|
|
93
|
+
* @param {string} trigger - explains the reason for starting reachability, used by Orpheus
|
|
94
|
+
* @param {Object} previousReport - last reachability report
|
|
121
95
|
* @param {boolean} isRetry
|
|
122
96
|
* @private
|
|
123
97
|
* @returns {Promise<{clusters: ClusterList, joinCookie: any}>}
|
|
124
98
|
*/
|
|
125
|
-
async getClusters(
|
|
99
|
+
async getClusters(
|
|
100
|
+
trigger: GetClustersTrigger,
|
|
101
|
+
previousReport?: any,
|
|
102
|
+
isRetry = false
|
|
103
|
+
): Promise<{
|
|
104
|
+
clusters: ClusterList;
|
|
105
|
+
joinCookie: any;
|
|
106
|
+
}> {
|
|
126
107
|
try {
|
|
127
|
-
const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
|
|
128
|
-
|
|
108
|
+
const {clusters, joinCookie, discoveryOptions} = await this.reachabilityRequest.getClusters(
|
|
109
|
+
trigger,
|
|
110
|
+
MeetingUtil.getIpVersion(this.webex),
|
|
111
|
+
previousReport
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
this.minRequiredClusters = discoveryOptions?.['early-call-min-clusters'];
|
|
115
|
+
this.orpheusApiVersion = discoveryOptions?.['report-version'];
|
|
116
|
+
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
await this.webex.boundedStorage.put(
|
|
119
|
+
this.namespace,
|
|
120
|
+
REACHABILITY.localStorageJoinCookie,
|
|
121
|
+
JSON.stringify(joinCookie)
|
|
129
122
|
);
|
|
130
123
|
|
|
131
124
|
return {clusters, joinCookie};
|
|
@@ -138,7 +131,7 @@ export default class Reachability extends EventsScope {
|
|
|
138
131
|
`Reachability:index#getClusters --> Failed with error: ${error}, retrying...`
|
|
139
132
|
);
|
|
140
133
|
|
|
141
|
-
return this.getClusters(true);
|
|
134
|
+
return this.getClusters(trigger, previousReport, true);
|
|
142
135
|
}
|
|
143
136
|
}
|
|
144
137
|
|
|
@@ -154,19 +147,12 @@ export default class Reachability extends EventsScope {
|
|
|
154
147
|
try {
|
|
155
148
|
this.lastTrigger = trigger;
|
|
156
149
|
|
|
157
|
-
// kick off ip version detection.
|
|
158
|
-
//
|
|
150
|
+
// kick off ip version detection. We don't await it, as we don't want to waste time
|
|
151
|
+
// and if it fails, that's ok we can still carry on
|
|
159
152
|
// @ts-ignore
|
|
160
|
-
this.webex.internal.device.ipNetworkDetector.detect();
|
|
153
|
+
this.webex.internal.device.ipNetworkDetector.detect(true);
|
|
161
154
|
|
|
162
|
-
const {clusters
|
|
163
|
-
|
|
164
|
-
// @ts-ignore
|
|
165
|
-
await this.webex.boundedStorage.put(
|
|
166
|
-
this.namespace,
|
|
167
|
-
REACHABILITY.localStorageJoinCookie,
|
|
168
|
-
JSON.stringify(joinCookie)
|
|
169
|
-
);
|
|
155
|
+
const {clusters} = await this.getClusters('startup');
|
|
170
156
|
|
|
171
157
|
this.reachabilityDefer = new Defer();
|
|
172
158
|
|
|
@@ -181,6 +167,98 @@ export default class Reachability extends EventsScope {
|
|
|
181
167
|
}
|
|
182
168
|
}
|
|
183
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Gets the last join cookie we got from Orpheus
|
|
172
|
+
*
|
|
173
|
+
* @returns {Promise<Object>} join cookie
|
|
174
|
+
*/
|
|
175
|
+
async getJoinCookie() {
|
|
176
|
+
// @ts-ignore
|
|
177
|
+
const joinCookieRaw = await this.webex.boundedStorage
|
|
178
|
+
.get(REACHABILITY.namespace, REACHABILITY.localStorageJoinCookie)
|
|
179
|
+
.catch(() => {});
|
|
180
|
+
|
|
181
|
+
let joinCookie;
|
|
182
|
+
|
|
183
|
+
if (joinCookieRaw) {
|
|
184
|
+
try {
|
|
185
|
+
joinCookie = JSON.parse(joinCookieRaw);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
LoggerProxy.logger.error(
|
|
188
|
+
`MeetingRequest#constructor --> Error in parsing join cookie data: ${e}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return joinCookie;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Returns the reachability report that needs to be attached to the ROAP messages
|
|
198
|
+
* that we send to the backend.
|
|
199
|
+
*
|
|
200
|
+
* @returns {Promise<Object>}
|
|
201
|
+
*/
|
|
202
|
+
async getReachabilityReport(): Promise<
|
|
203
|
+
| {
|
|
204
|
+
joinCookie: any;
|
|
205
|
+
reachability?: ReachabilityReportV1;
|
|
206
|
+
}
|
|
207
|
+
| {
|
|
208
|
+
reachability: ReachabilityReportV0;
|
|
209
|
+
}
|
|
210
|
+
> {
|
|
211
|
+
const reachabilityResult = await this.getReachabilityResults();
|
|
212
|
+
const joinCookie = await this.getJoinCookie();
|
|
213
|
+
|
|
214
|
+
// Orpheus API version 0
|
|
215
|
+
if (!this.orpheusApiVersion) {
|
|
216
|
+
return {
|
|
217
|
+
reachability: reachabilityResult,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Orpheus API version 1
|
|
222
|
+
return {
|
|
223
|
+
reachability: {
|
|
224
|
+
version: 1,
|
|
225
|
+
result: {
|
|
226
|
+
usedDiscoveryOptions: {
|
|
227
|
+
'early-call-min-clusters': this.minRequiredClusters,
|
|
228
|
+
},
|
|
229
|
+
metrics: {
|
|
230
|
+
'total-duration-ms': this.totalDuration,
|
|
231
|
+
},
|
|
232
|
+
tests: reachabilityResult,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
joinCookie,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* This method is called when we don't succeed in reaching the minimum number of clusters
|
|
241
|
+
* required by Orpheus. It sends the results to Orpheus and gets a new list that it tries to reach again.
|
|
242
|
+
* @returns {Promise<ReachabilityResults>} reachability results
|
|
243
|
+
* @public
|
|
244
|
+
* @memberof Reachability
|
|
245
|
+
*/
|
|
246
|
+
public async gatherReachabilityFallback(): Promise<void> {
|
|
247
|
+
try {
|
|
248
|
+
const reachabilityReport = await this.getReachabilityReport();
|
|
249
|
+
|
|
250
|
+
const {clusters} = await this.getClusters('early-call/no-min-reached', reachabilityReport);
|
|
251
|
+
|
|
252
|
+
// stop all previous reachability checks that might still be going on in the background
|
|
253
|
+
this.abortCurrentChecks();
|
|
254
|
+
|
|
255
|
+
// Perform Reachability Check
|
|
256
|
+
await this.performReachabilityChecks(clusters);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
LoggerProxy.logger.error(`Reachability:index#gatherReachabilityFallback --> Error:`, error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
184
262
|
/**
|
|
185
263
|
* Returns statistics about last reachability results. The returned value is an object
|
|
186
264
|
* with a flat list of properties so that it can be easily sent with metrics
|
|
@@ -304,7 +382,7 @@ export default class Reachability extends EventsScope {
|
|
|
304
382
|
} catch (e) {
|
|
305
383
|
// empty storage, that's ok
|
|
306
384
|
LoggerProxy.logger.warn(
|
|
307
|
-
'
|
|
385
|
+
'Reachability:index#getReachabilityResults --> Error parsing reachability data: ',
|
|
308
386
|
e
|
|
309
387
|
);
|
|
310
388
|
}
|
|
@@ -336,7 +414,7 @@ export default class Reachability extends EventsScope {
|
|
|
336
414
|
);
|
|
337
415
|
} catch (e) {
|
|
338
416
|
LoggerProxy.logger.error(
|
|
339
|
-
`
|
|
417
|
+
`Reachability:index#isAnyPublicClusterReachable --> Error in parsing reachability data: ${e}`
|
|
340
418
|
);
|
|
341
419
|
}
|
|
342
420
|
}
|
|
@@ -393,7 +471,7 @@ export default class Reachability extends EventsScope {
|
|
|
393
471
|
);
|
|
394
472
|
} catch (e) {
|
|
395
473
|
LoggerProxy.logger.error(
|
|
396
|
-
`
|
|
474
|
+
`Reachability:index#isWebexMediaBackendUnreachable --> Error in parsing reachability data: ${e}`
|
|
397
475
|
);
|
|
398
476
|
}
|
|
399
477
|
}
|
|
@@ -427,6 +505,30 @@ export default class Reachability extends EventsScope {
|
|
|
427
505
|
return unreachableList;
|
|
428
506
|
}
|
|
429
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Gets the number of reachable clusters from last run reachability check
|
|
510
|
+
* @returns {number} reachable clusters count
|
|
511
|
+
* @private
|
|
512
|
+
* @memberof Reachability
|
|
513
|
+
*/
|
|
514
|
+
private getNumberOfReachableClusters(): number {
|
|
515
|
+
let count = 0;
|
|
516
|
+
|
|
517
|
+
Object.entries(this.clusterReachability).forEach(([key, clusterReachability]) => {
|
|
518
|
+
const result = clusterReachability.getResult();
|
|
519
|
+
|
|
520
|
+
if (
|
|
521
|
+
result.udp.result === 'reachable' ||
|
|
522
|
+
result.tcp.result === 'reachable' ||
|
|
523
|
+
result.xtls.result === 'reachable'
|
|
524
|
+
) {
|
|
525
|
+
count += 1;
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
return count;
|
|
530
|
+
}
|
|
531
|
+
|
|
430
532
|
/**
|
|
431
533
|
* Make a log of unreachable clusters.
|
|
432
534
|
* @returns {undefined}
|
|
@@ -465,18 +567,27 @@ export default class Reachability extends EventsScope {
|
|
|
465
567
|
|
|
466
568
|
/**
|
|
467
569
|
* Resolves the promise returned by gatherReachability() method
|
|
570
|
+
* @param {boolean} checkMinRequiredClusters - if true, it will check if we have reached the minimum required clusters and do a fallback if needed
|
|
468
571
|
* @returns {void}
|
|
469
572
|
*/
|
|
470
|
-
private resolveReachabilityPromise() {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
clearTimeout(this.publicCloudTimer);
|
|
476
|
-
}
|
|
573
|
+
private resolveReachabilityPromise(checkMinRequiredClusters = true) {
|
|
574
|
+
this.totalDuration = performance.now() - this.startTime;
|
|
575
|
+
|
|
576
|
+
this.clearTimer('vmnTimer');
|
|
577
|
+
this.clearTimer('publicCloudTimer');
|
|
477
578
|
|
|
478
579
|
this.logUnreachableClusters();
|
|
479
580
|
this.reachabilityDefer?.resolve();
|
|
581
|
+
|
|
582
|
+
if (checkMinRequiredClusters) {
|
|
583
|
+
const numReachableClusters = this.getNumberOfReachableClusters();
|
|
584
|
+
if (this.minRequiredClusters && numReachableClusters < this.minRequiredClusters) {
|
|
585
|
+
LoggerProxy.logger.log(
|
|
586
|
+
`Reachability:index#resolveReachabilityPromise --> minRequiredClusters not reached (${numReachableClusters} < ${this.minRequiredClusters}), doing reachability fallback`
|
|
587
|
+
);
|
|
588
|
+
this.gatherReachabilityFallback();
|
|
589
|
+
}
|
|
590
|
+
}
|
|
480
591
|
}
|
|
481
592
|
|
|
482
593
|
/**
|
|
@@ -591,6 +702,8 @@ export default class Reachability extends EventsScope {
|
|
|
591
702
|
`Reachability:index#startTimers --> Reachability checks timed out (${DEFAULT_TIMEOUT}s)`
|
|
592
703
|
);
|
|
593
704
|
|
|
705
|
+
// check against minimum required clusters, do a new call if we don't have enough
|
|
706
|
+
|
|
594
707
|
// resolve the promise, so that the client won't be blocked waiting on meetings.register() for too long
|
|
595
708
|
this.resolveReachabilityPromise();
|
|
596
709
|
}, DEFAULT_TIMEOUT * 1000);
|
|
@@ -646,6 +759,32 @@ export default class Reachability extends EventsScope {
|
|
|
646
759
|
this.resultsCount.public.xtls = 0;
|
|
647
760
|
}
|
|
648
761
|
|
|
762
|
+
/**
|
|
763
|
+
* Clears the timer
|
|
764
|
+
*
|
|
765
|
+
* @param {string} timer name of the timer to clear
|
|
766
|
+
* @returns {void}
|
|
767
|
+
*/
|
|
768
|
+
private clearTimer(timer: string) {
|
|
769
|
+
if (this[timer]) {
|
|
770
|
+
clearTimeout(this[timer]);
|
|
771
|
+
this[timer] = undefined;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Aborts current checks that are in progress
|
|
777
|
+
*
|
|
778
|
+
* @returns {void}
|
|
779
|
+
*/
|
|
780
|
+
private abortCurrentChecks() {
|
|
781
|
+
this.clearTimer('vmnTimer');
|
|
782
|
+
this.clearTimer('publicCloudTimer');
|
|
783
|
+
this.clearTimer('overallTimer');
|
|
784
|
+
|
|
785
|
+
this.abortClusterReachability();
|
|
786
|
+
}
|
|
787
|
+
|
|
649
788
|
/**
|
|
650
789
|
* Performs reachability checks for all clusters
|
|
651
790
|
* @param {ClusterList} clusterList
|
|
@@ -656,9 +795,7 @@ export default class Reachability extends EventsScope {
|
|
|
656
795
|
|
|
657
796
|
this.clusterReachability = {};
|
|
658
797
|
|
|
659
|
-
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
798
|
+
this.startTime = performance.now();
|
|
662
799
|
|
|
663
800
|
LoggerProxy.logger.log(
|
|
664
801
|
`Reachability:index#performReachabilityChecks --> doing UDP${
|
|
@@ -671,7 +808,6 @@ export default class Reachability extends EventsScope {
|
|
|
671
808
|
);
|
|
672
809
|
|
|
673
810
|
this.resetResultCounters();
|
|
674
|
-
this.startTimers();
|
|
675
811
|
|
|
676
812
|
// sanitize the urls in the clusterList
|
|
677
813
|
Object.keys(clusterList).forEach((key) => {
|
|
@@ -721,6 +857,24 @@ export default class Reachability extends EventsScope {
|
|
|
721
857
|
// save the initialized results (in case we don't get any "resultReady" events at all)
|
|
722
858
|
await this.storeResults(results);
|
|
723
859
|
|
|
860
|
+
if (!clusterList || !Object.keys(clusterList).length) {
|
|
861
|
+
// nothing to do, finish immediately
|
|
862
|
+
this.resolveReachabilityPromise(false);
|
|
863
|
+
|
|
864
|
+
this.emit(
|
|
865
|
+
{
|
|
866
|
+
file: 'reachability',
|
|
867
|
+
function: 'performReachabilityChecks',
|
|
868
|
+
},
|
|
869
|
+
'reachability:done',
|
|
870
|
+
{}
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
this.startTimers();
|
|
877
|
+
|
|
724
878
|
// now start the reachability on all the clusters
|
|
725
879
|
Object.keys(clusterList).forEach((key) => {
|
|
726
880
|
const cluster = clusterList[key];
|
|
@@ -753,8 +907,7 @@ export default class Reachability extends EventsScope {
|
|
|
753
907
|
await this.storeResults(results);
|
|
754
908
|
|
|
755
909
|
if (areAllResultsReady) {
|
|
756
|
-
|
|
757
|
-
this.overallTimer = undefined;
|
|
910
|
+
this.clearTimer('overallTimer');
|
|
758
911
|
this.emit(
|
|
759
912
|
{
|
|
760
913
|
file: 'reachability',
|
|
@@ -785,4 +938,59 @@ export default class Reachability extends EventsScope {
|
|
|
785
938
|
this.clusterReachability[key].start(); // not awaiting on purpose
|
|
786
939
|
});
|
|
787
940
|
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Returns the clientMediaPreferences object that needs to be sent to the backend
|
|
944
|
+
* when joining a meeting
|
|
945
|
+
*
|
|
946
|
+
* @param {boolean} isMultistream
|
|
947
|
+
* @param {IP_VERSION} ipver
|
|
948
|
+
* @returns {Object}
|
|
949
|
+
*/
|
|
950
|
+
async getClientMediaPreferences(
|
|
951
|
+
isMultistream: boolean,
|
|
952
|
+
ipver?: IP_VERSION
|
|
953
|
+
): Promise<ClientMediaPreferences> {
|
|
954
|
+
// if 0 or undefined, we assume version 0 and don't send any reachability in clientMediaPreferences
|
|
955
|
+
if (!this.orpheusApiVersion) {
|
|
956
|
+
return {
|
|
957
|
+
ipver,
|
|
958
|
+
joinCookie: await this.getJoinCookie(),
|
|
959
|
+
preferTranscoding: !isMultistream,
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// must be version 1
|
|
964
|
+
|
|
965
|
+
// for version 1, the reachability report goes into clientMediaPreferences (and it contains joinCookie)
|
|
966
|
+
const reachabilityReport = (await this.getReachabilityReport()) as {
|
|
967
|
+
joinCookie: any;
|
|
968
|
+
reachability?: ReachabilityReportV1;
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
ipver,
|
|
973
|
+
preferTranscoding: !isMultistream,
|
|
974
|
+
...reachabilityReport,
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Returns the reachability report that needs to be attached to the ROAP messages
|
|
980
|
+
* that we send to the backend.
|
|
981
|
+
* It may return undefined, if reachability is not needed to be attached to ROAP messages (that's the case for v1 or Orpheus API)
|
|
982
|
+
*
|
|
983
|
+
* @returns {Promise<ReachabilityReportV0>} object that needs to be attached to Roap messages
|
|
984
|
+
*/
|
|
985
|
+
async getReachabilityReportToAttachToRoap(): Promise<ReachabilityReportV0 | undefined> {
|
|
986
|
+
// version 0
|
|
987
|
+
if (!this.orpheusApiVersion) {
|
|
988
|
+
return this.getReachabilityResults();
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// version 1
|
|
992
|
+
|
|
993
|
+
// for version 1 we don't attach anything to Roap messages, reachability report is sent inside clientMediaPreferences
|
|
994
|
+
return undefined;
|
|
995
|
+
}
|
|
788
996
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {IP_VERSION} from '../constants';
|
|
2
|
+
|
|
3
|
+
// result for a specific transport protocol (like udp or tcp)
|
|
4
|
+
export type TransportResult = {
|
|
5
|
+
result: 'reachable' | 'unreachable' | 'untested';
|
|
6
|
+
latencyInMilliseconds?: number; // amount of time it took to get the first ICE candidate
|
|
7
|
+
clientMediaIPs?: string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// reachability result for a specific media cluster
|
|
11
|
+
export type ClusterReachabilityResult = {
|
|
12
|
+
udp: TransportResult;
|
|
13
|
+
tcp: TransportResult;
|
|
14
|
+
xtls: TransportResult;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ReachabilityMetrics = {
|
|
18
|
+
reachability_public_udp_success: number;
|
|
19
|
+
reachability_public_udp_failed: number;
|
|
20
|
+
reachability_public_tcp_success: number;
|
|
21
|
+
reachability_public_tcp_failed: number;
|
|
22
|
+
reachability_public_xtls_success: number;
|
|
23
|
+
reachability_public_xtls_failed: number;
|
|
24
|
+
reachability_vmn_udp_success: number;
|
|
25
|
+
reachability_vmn_udp_failed: number;
|
|
26
|
+
reachability_vmn_tcp_success: number;
|
|
27
|
+
reachability_vmn_tcp_failed: number;
|
|
28
|
+
reachability_vmn_xtls_success: number;
|
|
29
|
+
reachability_vmn_xtls_failed: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* This is the type that matches what backend expects us to send to them. It is a bit weird, because
|
|
34
|
+
* it uses strings instead of booleans and numbers, but that's what they require.
|
|
35
|
+
*/
|
|
36
|
+
export type TransportResultForBackend = {
|
|
37
|
+
reachable?: 'true' | 'false';
|
|
38
|
+
latencyInMilliseconds?: string;
|
|
39
|
+
clientMediaIPs?: string[];
|
|
40
|
+
untested?: 'true';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ReachabilityResultForBackend = {
|
|
44
|
+
udp: TransportResultForBackend;
|
|
45
|
+
tcp: TransportResultForBackend;
|
|
46
|
+
xtls: TransportResultForBackend;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// this is the type that is required by the backend when we send them reachability results
|
|
50
|
+
export type ReachabilityResultsForBackend = Record<string, ReachabilityResultForBackend>;
|
|
51
|
+
|
|
52
|
+
// this is the type used by Reachability class internally and stored in local storage
|
|
53
|
+
export type ReachabilityResults = Record<
|
|
54
|
+
string,
|
|
55
|
+
ClusterReachabilityResult & {
|
|
56
|
+
isVideoMesh?: boolean;
|
|
57
|
+
}
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
export type ReachabilityReportV0 = ReachabilityResultsForBackend;
|
|
61
|
+
|
|
62
|
+
export type ReachabilityReportV1 = {
|
|
63
|
+
version: 1;
|
|
64
|
+
result: {
|
|
65
|
+
usedDiscoveryOptions: {
|
|
66
|
+
'early-call-min-clusters': number;
|
|
67
|
+
// there are more options, but we don't support them yet
|
|
68
|
+
};
|
|
69
|
+
metrics: {
|
|
70
|
+
'total-duration-ms': number;
|
|
71
|
+
// there are more metrics, but we don't support them yet
|
|
72
|
+
};
|
|
73
|
+
tests: Record<string, ReachabilityResultForBackend>;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export interface ClientMediaPreferences {
|
|
78
|
+
ipver: IP_VERSION;
|
|
79
|
+
joinCookie: any;
|
|
80
|
+
preferTranscoding: boolean;
|
|
81
|
+
reachability?: ReachabilityReportV1; // only present when using Orpheus API version 1
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Orpheus API supports more triggers, but we don't use them yet */
|
|
85
|
+
export type GetClustersTrigger = 'startup' | 'early-call/no-min-reached';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
2
2
|
import {HTTP_VERBS, RESOURCE, API, IP_VERSION} from '../constants';
|
|
3
|
+
import {GetClustersTrigger} from './reachability.types';
|
|
3
4
|
|
|
4
5
|
export interface ClusterNode {
|
|
5
6
|
isVideoMesh: boolean;
|
|
@@ -30,43 +31,67 @@ class ReachabilityRequest {
|
|
|
30
31
|
/**
|
|
31
32
|
* Gets the cluster information
|
|
32
33
|
*
|
|
34
|
+
* @param {string} trigger that's passed to Orpheus
|
|
33
35
|
* @param {IP_VERSION} ipVersion information about current ip network we're on
|
|
36
|
+
* @param {Object} previousReport last reachability result
|
|
34
37
|
* @returns {Promise}
|
|
35
38
|
*/
|
|
36
|
-
getClusters = (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
getClusters = (
|
|
40
|
+
trigger: GetClustersTrigger,
|
|
41
|
+
ipVersion?: IP_VERSION,
|
|
42
|
+
previousReport?: any
|
|
43
|
+
): Promise<{
|
|
44
|
+
clusters: ClusterList;
|
|
45
|
+
joinCookie: any;
|
|
46
|
+
discoveryOptions?: Record<string, any>;
|
|
47
|
+
}> => {
|
|
48
|
+
// we only measure latency for the initial startup call, not for other triggers
|
|
49
|
+
const callWrapper =
|
|
50
|
+
trigger === 'startup'
|
|
51
|
+
? this.webex.internal.newMetrics.callDiagnosticLatencies.measureLatency.bind(
|
|
52
|
+
this.webex.internal.newMetrics.callDiagnosticLatencies
|
|
53
|
+
)
|
|
54
|
+
: (func) => func();
|
|
55
|
+
|
|
56
|
+
return callWrapper(
|
|
57
|
+
() =>
|
|
58
|
+
this.webex.request({
|
|
59
|
+
method: HTTP_VERBS.POST,
|
|
60
|
+
shouldRefreshAccessToken: false,
|
|
61
|
+
api: API.CALLIOPEDISCOVERY,
|
|
62
|
+
resource: RESOURCE.CLUSTERS,
|
|
63
|
+
body: {
|
|
64
|
+
ipver: ipVersion,
|
|
65
|
+
'supported-options': {
|
|
66
|
+
'report-version': 1,
|
|
67
|
+
'early-call-min-clusters': true,
|
|
48
68
|
},
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
'previous-report': previousReport,
|
|
70
|
+
trigger,
|
|
71
|
+
},
|
|
72
|
+
timeout: this.webex.config.meetings.reachabilityGetClusterTimeout,
|
|
73
|
+
}),
|
|
74
|
+
'internal.get.cluster.time'
|
|
75
|
+
).then((res) => {
|
|
76
|
+
const {clusters, joinCookie, discoveryOptions} = res.body;
|
|
54
77
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
78
|
+
Object.keys(clusters).forEach((key) => {
|
|
79
|
+
clusters[key].isVideoMesh = !!res.body.clusterClasses?.hybridMedia?.includes(key);
|
|
80
|
+
});
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
LoggerProxy.logger.log(
|
|
83
|
+
`Reachability:request#getClusters --> get clusters (ipver=${ipVersion}) successful:${JSON.stringify(
|
|
84
|
+
clusters
|
|
85
|
+
)}`
|
|
86
|
+
);
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
88
|
+
return {
|
|
89
|
+
clusters,
|
|
90
|
+
joinCookie,
|
|
91
|
+
discoveryOptions,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
};
|
|
70
95
|
|
|
71
96
|
/**
|
|
72
97
|
* gets remote SDP For Clusters
|