@webex/plugin-meetings 3.7.0 → 3.8.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.
Files changed (206) hide show
  1. package/dist/annotation/index.js +17 -0
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/common/errors/join-forbidden-error.js +52 -0
  6. package/dist/common/errors/join-forbidden-error.js.map +1 -0
  7. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  8. package/dist/common/errors/join-webinar-error.js.map +1 -0
  9. package/dist/common/errors/multistream-not-supported-error.js +53 -0
  10. package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
  11. package/dist/config.js +3 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/constants.js +69 -6
  14. package/dist/constants.js.map +1 -1
  15. package/dist/index.js +16 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/interpretation/index.js +4 -4
  18. package/dist/interpretation/index.js.map +1 -1
  19. package/dist/interpretation/siLanguage.js +1 -1
  20. package/dist/locus-info/index.js +14 -3
  21. package/dist/locus-info/index.js.map +1 -1
  22. package/dist/locus-info/selfUtils.js +35 -17
  23. package/dist/locus-info/selfUtils.js.map +1 -1
  24. package/dist/media/MediaConnectionAwaiter.js +1 -0
  25. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  26. package/dist/media/properties.js +30 -16
  27. package/dist/media/properties.js.map +1 -1
  28. package/dist/meeting/brbState.js +167 -0
  29. package/dist/meeting/brbState.js.map +1 -0
  30. package/dist/meeting/in-meeting-actions.js +13 -1
  31. package/dist/meeting/in-meeting-actions.js.map +1 -1
  32. package/dist/meeting/index.js +1373 -1052
  33. package/dist/meeting/index.js.map +1 -1
  34. package/dist/meeting/locusMediaRequest.js +32 -11
  35. package/dist/meeting/locusMediaRequest.js.map +1 -1
  36. package/dist/meeting/muteState.js +1 -6
  37. package/dist/meeting/muteState.js.map +1 -1
  38. package/dist/meeting/request.js +51 -29
  39. package/dist/meeting/request.js.map +1 -1
  40. package/dist/meeting/request.type.js.map +1 -1
  41. package/dist/meeting/util.js +103 -67
  42. package/dist/meeting/util.js.map +1 -1
  43. package/dist/meeting-info/meeting-info-v2.js +115 -45
  44. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  45. package/dist/meeting-info/utilv2.js +6 -2
  46. package/dist/meeting-info/utilv2.js.map +1 -1
  47. package/dist/meetings/index.js +107 -55
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/meetings.types.js +2 -0
  50. package/dist/meetings/meetings.types.js.map +1 -1
  51. package/dist/meetings/util.js +1 -1
  52. package/dist/meetings/util.js.map +1 -1
  53. package/dist/member/index.js +9 -0
  54. package/dist/member/index.js.map +1 -1
  55. package/dist/member/types.js.map +1 -1
  56. package/dist/member/util.js +39 -28
  57. package/dist/member/util.js.map +1 -1
  58. package/dist/members/util.js +4 -2
  59. package/dist/members/util.js.map +1 -1
  60. package/dist/metrics/constants.js +6 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/remoteMedia.js +30 -15
  63. package/dist/multistream/remoteMedia.js.map +1 -1
  64. package/dist/multistream/remoteMediaManager.js +40 -8
  65. package/dist/multistream/remoteMediaManager.js.map +1 -1
  66. package/dist/multistream/sendSlotManager.js +24 -0
  67. package/dist/multistream/sendSlotManager.js.map +1 -1
  68. package/dist/reachability/clusterReachability.js +12 -15
  69. package/dist/reachability/clusterReachability.js.map +1 -1
  70. package/dist/reachability/index.js +471 -140
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/{rtcMetrics/constants.js → reachability/reachability.types.js} +1 -5
  73. package/dist/reachability/reachability.types.js.map +1 -0
  74. package/dist/reachability/request.js +21 -8
  75. package/dist/reachability/request.js.map +1 -1
  76. package/dist/recording-controller/enums.js +8 -4
  77. package/dist/recording-controller/enums.js.map +1 -1
  78. package/dist/recording-controller/index.js +18 -9
  79. package/dist/recording-controller/index.js.map +1 -1
  80. package/dist/recording-controller/util.js +13 -9
  81. package/dist/recording-controller/util.js.map +1 -1
  82. package/dist/roap/index.js +15 -15
  83. package/dist/roap/index.js.map +1 -1
  84. package/dist/roap/request.js +45 -79
  85. package/dist/roap/request.js.map +1 -1
  86. package/dist/roap/turnDiscovery.js +3 -6
  87. package/dist/roap/turnDiscovery.js.map +1 -1
  88. package/dist/types/annotation/index.d.ts +5 -0
  89. package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
  90. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  91. package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
  92. package/dist/types/config.d.ts +2 -0
  93. package/dist/types/constants.d.ts +54 -1
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/locus-info/index.d.ts +2 -1
  96. package/dist/types/meeting/brbState.d.ts +54 -0
  97. package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
  98. package/dist/types/meeting/index.d.ts +86 -14
  99. package/dist/types/meeting/locusMediaRequest.d.ts +6 -3
  100. package/dist/types/meeting/request.d.ts +14 -3
  101. package/dist/types/meeting/request.type.d.ts +6 -0
  102. package/dist/types/meeting/util.d.ts +3 -3
  103. package/dist/types/meeting-info/meeting-info-v2.d.ts +30 -5
  104. package/dist/types/meetings/index.d.ts +20 -2
  105. package/dist/types/meetings/meetings.types.d.ts +8 -0
  106. package/dist/types/member/index.d.ts +1 -0
  107. package/dist/types/member/types.d.ts +7 -0
  108. package/dist/types/members/util.d.ts +2 -0
  109. package/dist/types/metrics/constants.d.ts +6 -1
  110. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  111. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  112. package/dist/types/reachability/clusterReachability.d.ts +1 -10
  113. package/dist/types/reachability/index.d.ts +83 -36
  114. package/dist/types/reachability/reachability.types.d.ts +64 -0
  115. package/dist/types/reachability/request.d.ts +5 -1
  116. package/dist/types/recording-controller/enums.d.ts +5 -2
  117. package/dist/types/recording-controller/index.d.ts +1 -0
  118. package/dist/types/recording-controller/util.d.ts +2 -1
  119. package/dist/types/roap/request.d.ts +1 -13
  120. package/dist/webinar/index.js +390 -7
  121. package/dist/webinar/index.js.map +1 -1
  122. package/package.json +23 -22
  123. package/src/annotation/index.ts +16 -0
  124. package/src/common/errors/join-forbidden-error.ts +26 -0
  125. package/src/common/errors/join-webinar-error.ts +24 -0
  126. package/src/common/errors/multistream-not-supported-error.ts +30 -0
  127. package/src/config.ts +2 -0
  128. package/src/constants.ts +62 -3
  129. package/src/index.ts +5 -3
  130. package/src/interpretation/index.ts +3 -3
  131. package/src/locus-info/index.ts +20 -3
  132. package/src/locus-info/selfUtils.ts +24 -6
  133. package/src/media/MediaConnectionAwaiter.ts +2 -0
  134. package/src/media/properties.ts +34 -13
  135. package/src/meeting/brbState.ts +169 -0
  136. package/src/meeting/in-meeting-actions.ts +25 -0
  137. package/src/meeting/index.ts +485 -88
  138. package/src/meeting/locusMediaRequest.ts +38 -12
  139. package/src/meeting/muteState.ts +1 -6
  140. package/src/meeting/request.ts +30 -12
  141. package/src/meeting/request.type.ts +7 -0
  142. package/src/meeting/util.ts +32 -13
  143. package/src/meeting-info/meeting-info-v2.ts +83 -12
  144. package/src/meeting-info/utilv2.ts +17 -3
  145. package/src/meetings/index.ts +79 -20
  146. package/src/meetings/meetings.types.ts +10 -0
  147. package/src/meetings/util.ts +2 -1
  148. package/src/member/index.ts +9 -0
  149. package/src/member/types.ts +8 -0
  150. package/src/member/util.ts +34 -24
  151. package/src/members/util.ts +1 -0
  152. package/src/metrics/constants.ts +6 -1
  153. package/src/multistream/remoteMedia.ts +28 -15
  154. package/src/multistream/remoteMediaManager.ts +32 -10
  155. package/src/multistream/sendSlotManager.ts +31 -0
  156. package/src/reachability/clusterReachability.ts +5 -15
  157. package/src/reachability/index.ts +315 -75
  158. package/src/reachability/reachability.types.ts +85 -0
  159. package/src/reachability/request.ts +55 -31
  160. package/src/recording-controller/enums.ts +5 -2
  161. package/src/recording-controller/index.ts +17 -4
  162. package/src/recording-controller/util.ts +28 -9
  163. package/src/roap/index.ts +14 -13
  164. package/src/roap/request.ts +30 -44
  165. package/src/roap/turnDiscovery.ts +2 -4
  166. package/src/webinar/index.ts +235 -9
  167. package/test/unit/spec/annotation/index.ts +46 -1
  168. package/test/unit/spec/interpretation/index.ts +39 -1
  169. package/test/unit/spec/locus-info/index.js +292 -60
  170. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  171. package/test/unit/spec/locus-info/selfUtils.js +101 -1
  172. package/test/unit/spec/media/properties.ts +15 -0
  173. package/test/unit/spec/meeting/brbState.ts +114 -0
  174. package/test/unit/spec/meeting/in-meeting-actions.ts +15 -1
  175. package/test/unit/spec/meeting/index.js +908 -124
  176. package/test/unit/spec/meeting/locusMediaRequest.ts +111 -66
  177. package/test/unit/spec/meeting/muteState.js +0 -24
  178. package/test/unit/spec/meeting/request.js +3 -26
  179. package/test/unit/spec/meeting/utils.js +73 -28
  180. package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
  181. package/test/unit/spec/meeting-info/utilv2.js +26 -0
  182. package/test/unit/spec/meetings/index.js +172 -18
  183. package/test/unit/spec/meetings/utils.js +10 -0
  184. package/test/unit/spec/member/util.js +52 -11
  185. package/test/unit/spec/members/utils.js +95 -0
  186. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  187. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  188. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  189. package/test/unit/spec/reachability/index.ts +391 -9
  190. package/test/unit/spec/reachability/request.js +48 -12
  191. package/test/unit/spec/recording-controller/index.js +61 -5
  192. package/test/unit/spec/recording-controller/util.js +39 -3
  193. package/test/unit/spec/roap/index.ts +48 -1
  194. package/test/unit/spec/roap/request.ts +51 -109
  195. package/test/unit/spec/roap/turnDiscovery.ts +202 -147
  196. package/test/unit/spec/webinar/index.ts +509 -0
  197. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  198. package/dist/networkQualityMonitor/index.js +0 -227
  199. package/dist/networkQualityMonitor/index.js.map +0 -1
  200. package/dist/rtcMetrics/constants.js.map +0 -1
  201. package/dist/rtcMetrics/index.js +0 -197
  202. package/dist/rtcMetrics/index.js.map +0 -1
  203. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  204. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  205. package/dist/types/rtcMetrics/index.d.ts +0 -71
  206. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -6,20 +6,7 @@ import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
6
6
  import EventsScope from '../common/events/events-scope';
7
7
 
8
8
  import {CONNECTION_STATE, Enum, ICE_GATHERING_STATE} from '../constants';
9
-
10
- // result for a specific transport protocol (like udp or tcp)
11
- export type TransportResult = {
12
- result: 'reachable' | 'unreachable' | 'untested';
13
- latencyInMilliseconds?: number; // amount of time it took to get the first ICE candidate
14
- clientMediaIPs?: string[];
15
- };
16
-
17
- // reachability result for a specific media cluster
18
- export type ClusterReachabilityResult = {
19
- udp: TransportResult;
20
- tcp: TransportResult;
21
- xtls: TransportResult;
22
- };
9
+ import {ClusterReachabilityResult} from './reachability.types';
23
10
 
24
11
  // data for the Events.resultReady event
25
12
  export type ResultEventData = {
@@ -370,11 +357,14 @@ export class ClusterReachability extends EventsScope {
370
357
 
371
358
  this.startTimestamp = performance.now();
372
359
 
360
+ // Set up the state change listeners before triggering the ICE gathering
361
+ const gatherIceCandidatePromise = this.gatherIceCandidates();
362
+
373
363
  // not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
374
364
  // we just need to make this call to trigger the ICE gathering process
375
365
  this.pc.setLocalDescription(offer);
376
366
 
377
- await this.gatherIceCandidates();
367
+ await gatherIceCandidatePromise;
378
368
  } catch (error) {
379
369
  LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
380
370
  }
@@ -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(isRetry = false): Promise<{clusters: ClusterList; joinCookie: any}> {
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
- MeetingUtil.getIpVersion(this.webex)
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
 
@@ -150,6 +143,10 @@ export default class Reachability extends EventsScope {
150
143
  * @memberof Reachability
151
144
  */
152
145
  public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
146
+ // @ts-ignore
147
+ if (!this.webex.config.meetings.enableReachabilityChecks) {
148
+ throw new Error('enableReachabilityChecks is disabled in config');
149
+ }
153
150
  // Fetch clusters and measure latency
154
151
  try {
155
152
  this.lastTrigger = trigger;
@@ -159,14 +156,7 @@ export default class Reachability extends EventsScope {
159
156
  // @ts-ignore
160
157
  this.webex.internal.device.ipNetworkDetector.detect(true);
161
158
 
162
- const {clusters, joinCookie} = await this.getClusters();
163
-
164
- // @ts-ignore
165
- await this.webex.boundedStorage.put(
166
- this.namespace,
167
- REACHABILITY.localStorageJoinCookie,
168
- JSON.stringify(joinCookie)
169
- );
159
+ const {clusters} = await this.getClusters('startup');
170
160
 
171
161
  this.reachabilityDefer = new Defer();
172
162
 
@@ -181,6 +171,124 @@ export default class Reachability extends EventsScope {
181
171
  }
182
172
  }
183
173
 
174
+ /**
175
+ * Gets the last join cookie we got from Orpheus
176
+ *
177
+ * @returns {Promise<Object>} join cookie
178
+ */
179
+ async getJoinCookie() {
180
+ // @ts-ignore
181
+ const joinCookieRaw = await this.webex.boundedStorage
182
+ .get(REACHABILITY.namespace, REACHABILITY.localStorageJoinCookie)
183
+ .catch(() => {});
184
+
185
+ let joinCookie;
186
+
187
+ if (joinCookieRaw) {
188
+ try {
189
+ joinCookie = JSON.parse(joinCookieRaw);
190
+ } catch (e) {
191
+ LoggerProxy.logger.error(
192
+ `MeetingRequest#constructor --> Error in parsing join cookie data: ${e}`
193
+ );
194
+ }
195
+ }
196
+
197
+ return joinCookie;
198
+ }
199
+
200
+ /**
201
+ * Returns the reachability report that needs to be attached to the ROAP messages
202
+ * that we send to the backend.
203
+ *
204
+ * @returns {Promise<Object>}
205
+ */
206
+ async getReachabilityReport(): Promise<
207
+ | {
208
+ joinCookie: any;
209
+ reachability?: ReachabilityReportV1;
210
+ }
211
+ | {
212
+ reachability: ReachabilityReportV0;
213
+ }
214
+ > {
215
+ const reachabilityResult = await this.getReachabilityResults();
216
+ const joinCookie = await this.getJoinCookie();
217
+
218
+ // Orpheus API version 0
219
+ if (!this.orpheusApiVersion) {
220
+ return {
221
+ reachability: reachabilityResult,
222
+ };
223
+ }
224
+
225
+ // Orpheus API version 1
226
+ return {
227
+ reachability: {
228
+ version: 1,
229
+ result: {
230
+ usedDiscoveryOptions: {
231
+ 'early-call-min-clusters': this.minRequiredClusters,
232
+ },
233
+ metrics: {
234
+ 'total-duration-ms': this.totalDuration,
235
+ },
236
+ tests: reachabilityResult,
237
+ },
238
+ },
239
+ joinCookie,
240
+ };
241
+ }
242
+
243
+ /**
244
+ * This method is called when we don't succeed in reaching the minimum number of clusters
245
+ * required by Orpheus. It sends the results to Orpheus and gets a new list that it tries to reach again.
246
+ * @returns {Promise<ReachabilityResults>} reachability results
247
+ * @public
248
+ * @memberof Reachability
249
+ */
250
+ public async gatherReachabilityFallback(): Promise<void> {
251
+ try {
252
+ const reachabilityReport = await this.getReachabilityReport();
253
+
254
+ const {clusters} = await this.getClusters('early-call/no-min-reached', reachabilityReport);
255
+
256
+ // stop all previous reachability checks that might still be going on in the background
257
+ this.abortCurrentChecks();
258
+
259
+ // Perform Reachability Check
260
+ await this.performReachabilityChecks(clusters);
261
+ } catch (error) {
262
+ LoggerProxy.logger.error(`Reachability:index#gatherReachabilityFallback --> Error:`, error);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Stops all reachability checks that are in progress
268
+ * @public
269
+ * @memberof Reachability
270
+ * @returns {void}
271
+ */
272
+ public stopReachability() {
273
+ // overallTimer is always there only if there is reachability in progress
274
+ if (this.overallTimer) {
275
+ LoggerProxy.logger.log(
276
+ 'Reachability:index#stopReachability --> stopping reachability checks'
277
+ );
278
+ this.abortCurrentChecks();
279
+ this.emit(
280
+ {
281
+ file: 'reachability',
282
+ function: 'stopReachability',
283
+ },
284
+ 'reachability:stopped',
285
+ {}
286
+ );
287
+ this.sendMetric(true);
288
+ this.resolveReachabilityPromise();
289
+ }
290
+ }
291
+
184
292
  /**
185
293
  * Returns statistics about last reachability results. The returned value is an object
186
294
  * with a flat list of properties so that it can be easily sent with metrics
@@ -304,7 +412,7 @@ export default class Reachability extends EventsScope {
304
412
  } catch (e) {
305
413
  // empty storage, that's ok
306
414
  LoggerProxy.logger.warn(
307
- 'Roap:request#attachReachabilityData --> Error parsing reachability data: ',
415
+ 'Reachability:index#getReachabilityResults --> Error parsing reachability data: ',
308
416
  e
309
417
  );
310
418
  }
@@ -336,7 +444,7 @@ export default class Reachability extends EventsScope {
336
444
  );
337
445
  } catch (e) {
338
446
  LoggerProxy.logger.error(
339
- `Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`
447
+ `Reachability:index#isAnyPublicClusterReachable --> Error in parsing reachability data: ${e}`
340
448
  );
341
449
  }
342
450
  }
@@ -393,7 +501,7 @@ export default class Reachability extends EventsScope {
393
501
  );
394
502
  } catch (e) {
395
503
  LoggerProxy.logger.error(
396
- `Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`
504
+ `Reachability:index#isWebexMediaBackendUnreachable --> Error in parsing reachability data: ${e}`
397
505
  );
398
506
  }
399
507
  }
@@ -427,6 +535,30 @@ export default class Reachability extends EventsScope {
427
535
  return unreachableList;
428
536
  }
429
537
 
538
+ /**
539
+ * Gets the number of reachable clusters from last run reachability check
540
+ * @returns {number} reachable clusters count
541
+ * @private
542
+ * @memberof Reachability
543
+ */
544
+ private getNumberOfReachableClusters(): number {
545
+ let count = 0;
546
+
547
+ Object.entries(this.clusterReachability).forEach(([key, clusterReachability]) => {
548
+ const result = clusterReachability.getResult();
549
+
550
+ if (
551
+ result.udp.result === 'reachable' ||
552
+ result.tcp.result === 'reachable' ||
553
+ result.xtls.result === 'reachable'
554
+ ) {
555
+ count += 1;
556
+ }
557
+ });
558
+
559
+ return count;
560
+ }
561
+
430
562
  /**
431
563
  * Make a log of unreachable clusters.
432
564
  * @returns {undefined}
@@ -465,18 +597,27 @@ export default class Reachability extends EventsScope {
465
597
 
466
598
  /**
467
599
  * Resolves the promise returned by gatherReachability() method
600
+ * @param {boolean} checkMinRequiredClusters - if true, it will check if we have reached the minimum required clusters and do a fallback if needed
468
601
  * @returns {void}
469
602
  */
470
- private resolveReachabilityPromise() {
471
- if (this.vmnTimer) {
472
- clearTimeout(this.vmnTimer);
473
- }
474
- if (this.publicCloudTimer) {
475
- clearTimeout(this.publicCloudTimer);
476
- }
603
+ private resolveReachabilityPromise(checkMinRequiredClusters = true) {
604
+ this.totalDuration = performance.now() - this.startTime;
605
+
606
+ this.clearTimer('vmnTimer');
607
+ this.clearTimer('publicCloudTimer');
477
608
 
478
609
  this.logUnreachableClusters();
479
610
  this.reachabilityDefer?.resolve();
611
+
612
+ if (checkMinRequiredClusters) {
613
+ const numReachableClusters = this.getNumberOfReachableClusters();
614
+ if (this.minRequiredClusters && numReachableClusters < this.minRequiredClusters) {
615
+ LoggerProxy.logger.log(
616
+ `Reachability:index#resolveReachabilityPromise --> minRequiredClusters not reached (${numReachableClusters} < ${this.minRequiredClusters}), doing reachability fallback`
617
+ );
618
+ this.gatherReachabilityFallback();
619
+ }
620
+ }
480
621
  }
481
622
 
482
623
  /**
@@ -526,9 +667,10 @@ export default class Reachability extends EventsScope {
526
667
  /**
527
668
  * Sends a metric with all the statistics about how long reachability took
528
669
  *
670
+ * @param {boolean} aborted true if the reachability checks were aborted
529
671
  * @returns {void}
530
672
  */
531
- protected async sendMetric() {
673
+ protected async sendMetric(aborted = false) {
532
674
  const results = [];
533
675
 
534
676
  Object.values(this.clusterReachability).forEach((clusterReachability) => {
@@ -539,6 +681,7 @@ export default class Reachability extends EventsScope {
539
681
  });
540
682
 
541
683
  const stats = {
684
+ aborted,
542
685
  vmn: {
543
686
  udp: this.getStatistics(results, 'udp', true),
544
687
  },
@@ -591,6 +734,8 @@ export default class Reachability extends EventsScope {
591
734
  `Reachability:index#startTimers --> Reachability checks timed out (${DEFAULT_TIMEOUT}s)`
592
735
  );
593
736
 
737
+ // check against minimum required clusters, do a new call if we don't have enough
738
+
594
739
  // resolve the promise, so that the client won't be blocked waiting on meetings.register() for too long
595
740
  this.resolveReachabilityPromise();
596
741
  }, DEFAULT_TIMEOUT * 1000);
@@ -646,6 +791,32 @@ export default class Reachability extends EventsScope {
646
791
  this.resultsCount.public.xtls = 0;
647
792
  }
648
793
 
794
+ /**
795
+ * Clears the timer
796
+ *
797
+ * @param {string} timer name of the timer to clear
798
+ * @returns {void}
799
+ */
800
+ private clearTimer(timer: string) {
801
+ if (this[timer]) {
802
+ clearTimeout(this[timer]);
803
+ this[timer] = undefined;
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Aborts current checks that are in progress
809
+ *
810
+ * @returns {void}
811
+ */
812
+ private abortCurrentChecks() {
813
+ this.clearTimer('vmnTimer');
814
+ this.clearTimer('publicCloudTimer');
815
+ this.clearTimer('overallTimer');
816
+
817
+ this.abortClusterReachability();
818
+ }
819
+
649
820
  /**
650
821
  * Performs reachability checks for all clusters
651
822
  * @param {ClusterList} clusterList
@@ -656,9 +827,7 @@ export default class Reachability extends EventsScope {
656
827
 
657
828
  this.clusterReachability = {};
658
829
 
659
- if (!clusterList || !Object.keys(clusterList).length) {
660
- return;
661
- }
830
+ this.startTime = performance.now();
662
831
 
663
832
  LoggerProxy.logger.log(
664
833
  `Reachability:index#performReachabilityChecks --> doing UDP${
@@ -671,7 +840,6 @@ export default class Reachability extends EventsScope {
671
840
  );
672
841
 
673
842
  this.resetResultCounters();
674
- this.startTimers();
675
843
 
676
844
  // sanitize the urls in the clusterList
677
845
  Object.keys(clusterList).forEach((key) => {
@@ -721,6 +889,24 @@ export default class Reachability extends EventsScope {
721
889
  // save the initialized results (in case we don't get any "resultReady" events at all)
722
890
  await this.storeResults(results);
723
891
 
892
+ if (!clusterList || !Object.keys(clusterList).length) {
893
+ // nothing to do, finish immediately
894
+ this.resolveReachabilityPromise(false);
895
+
896
+ this.emit(
897
+ {
898
+ file: 'reachability',
899
+ function: 'performReachabilityChecks',
900
+ },
901
+ 'reachability:done',
902
+ {}
903
+ );
904
+
905
+ return;
906
+ }
907
+
908
+ this.startTimers();
909
+
724
910
  // now start the reachability on all the clusters
725
911
  Object.keys(clusterList).forEach((key) => {
726
912
  const cluster = clusterList[key];
@@ -753,8 +939,7 @@ export default class Reachability extends EventsScope {
753
939
  await this.storeResults(results);
754
940
 
755
941
  if (areAllResultsReady) {
756
- clearTimeout(this.overallTimer);
757
- this.overallTimer = undefined;
942
+ this.clearTimer('overallTimer');
758
943
  this.emit(
759
944
  {
760
945
  file: 'reachability',
@@ -785,4 +970,59 @@ export default class Reachability extends EventsScope {
785
970
  this.clusterReachability[key].start(); // not awaiting on purpose
786
971
  });
787
972
  }
973
+
974
+ /**
975
+ * Returns the clientMediaPreferences object that needs to be sent to the backend
976
+ * when joining a meeting
977
+ *
978
+ * @param {boolean} isMultistream
979
+ * @param {IP_VERSION} ipver
980
+ * @returns {Object}
981
+ */
982
+ async getClientMediaPreferences(
983
+ isMultistream: boolean,
984
+ ipver?: IP_VERSION
985
+ ): Promise<ClientMediaPreferences> {
986
+ // if 0 or undefined, we assume version 0 and don't send any reachability in clientMediaPreferences
987
+ if (!this.orpheusApiVersion) {
988
+ return {
989
+ ipver,
990
+ joinCookie: await this.getJoinCookie(),
991
+ preferTranscoding: !isMultistream,
992
+ };
993
+ }
994
+
995
+ // must be version 1
996
+
997
+ // for version 1, the reachability report goes into clientMediaPreferences (and it contains joinCookie)
998
+ const reachabilityReport = (await this.getReachabilityReport()) as {
999
+ joinCookie: any;
1000
+ reachability?: ReachabilityReportV1;
1001
+ };
1002
+
1003
+ return {
1004
+ ipver,
1005
+ preferTranscoding: !isMultistream,
1006
+ ...reachabilityReport,
1007
+ };
1008
+ }
1009
+
1010
+ /**
1011
+ * Returns the reachability report that needs to be attached to the ROAP messages
1012
+ * that we send to the backend.
1013
+ * It may return undefined, if reachability is not needed to be attached to ROAP messages (that's the case for v1 or Orpheus API)
1014
+ *
1015
+ * @returns {Promise<ReachabilityReportV0>} object that needs to be attached to Roap messages
1016
+ */
1017
+ async getReachabilityReportToAttachToRoap(): Promise<ReachabilityReportV0 | undefined> {
1018
+ // version 0
1019
+ if (!this.orpheusApiVersion) {
1020
+ return this.getReachabilityResults();
1021
+ }
1022
+
1023
+ // version 1
1024
+
1025
+ // for version 1 we don't attach anything to Roap messages, reachability report is sent inside clientMediaPreferences
1026
+ return undefined;
1027
+ }
788
1028
  }
@@ -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';