@webex/plugin-meetings 3.10.0 → 3.11.0

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 (283) hide show
  1. package/dist/annotation/annotation.types.js.map +1 -1
  2. package/dist/annotation/constants.js.map +1 -1
  3. package/dist/annotation/index.js +19 -22
  4. package/dist/annotation/index.js.map +1 -1
  5. package/dist/breakouts/breakout.js +6 -6
  6. package/dist/breakouts/breakout.js.map +1 -1
  7. package/dist/breakouts/collection.js.map +1 -1
  8. package/dist/breakouts/edit-lock-error.js +9 -11
  9. package/dist/breakouts/edit-lock-error.js.map +1 -1
  10. package/dist/breakouts/events.js.map +1 -1
  11. package/dist/breakouts/index.js +126 -127
  12. package/dist/breakouts/index.js.map +1 -1
  13. package/dist/breakouts/request.js +6 -8
  14. package/dist/breakouts/request.js.map +1 -1
  15. package/dist/breakouts/utils.js.map +1 -1
  16. package/dist/common/browser-detection.js.map +1 -1
  17. package/dist/common/collection.js +1 -2
  18. package/dist/common/collection.js.map +1 -1
  19. package/dist/common/config.js.map +1 -1
  20. package/dist/common/errors/captcha-error.js +9 -11
  21. package/dist/common/errors/captcha-error.js.map +1 -1
  22. package/dist/common/errors/intent-to-join.js +10 -12
  23. package/dist/common/errors/intent-to-join.js.map +1 -1
  24. package/dist/common/errors/join-forbidden-error.js +10 -12
  25. package/dist/common/errors/join-forbidden-error.js.map +1 -1
  26. package/dist/common/errors/join-meeting.js +10 -12
  27. package/dist/common/errors/join-meeting.js.map +1 -1
  28. package/dist/common/errors/join-webinar-error.js +9 -11
  29. package/dist/common/errors/join-webinar-error.js.map +1 -1
  30. package/dist/common/errors/media.js +9 -11
  31. package/dist/common/errors/media.js.map +1 -1
  32. package/dist/common/errors/multistream-not-supported-error.js +9 -11
  33. package/dist/common/errors/multistream-not-supported-error.js.map +1 -1
  34. package/dist/common/errors/no-meeting-info.js +9 -11
  35. package/dist/common/errors/no-meeting-info.js.map +1 -1
  36. package/dist/common/errors/parameter.js +11 -14
  37. package/dist/common/errors/parameter.js.map +1 -1
  38. package/dist/common/errors/password-error.js +9 -11
  39. package/dist/common/errors/password-error.js.map +1 -1
  40. package/dist/common/errors/permission.js +9 -11
  41. package/dist/common/errors/permission.js.map +1 -1
  42. package/dist/common/errors/reclaim-host-role-errors.js +32 -38
  43. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -1
  44. package/dist/common/errors/reconnection-not-started.js +5 -6
  45. package/dist/common/errors/reconnection-not-started.js.map +1 -1
  46. package/dist/common/errors/reconnection.js +9 -11
  47. package/dist/common/errors/reconnection.js.map +1 -1
  48. package/dist/common/errors/stats.js +9 -11
  49. package/dist/common/errors/stats.js.map +1 -1
  50. package/dist/common/errors/webex-errors.js +38 -27
  51. package/dist/common/errors/webex-errors.js.map +1 -1
  52. package/dist/common/errors/webex-meetings-error.js +9 -12
  53. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  54. package/dist/common/events/events-scope.js +9 -10
  55. package/dist/common/events/events-scope.js.map +1 -1
  56. package/dist/common/events/events.js +9 -10
  57. package/dist/common/events/events.js.map +1 -1
  58. package/dist/common/events/trigger-proxy.js.map +1 -1
  59. package/dist/common/events/util.js.map +1 -1
  60. package/dist/common/logs/logger-config.js.map +1 -1
  61. package/dist/common/logs/logger-proxy.js.map +1 -1
  62. package/dist/common/logs/request.js +17 -17
  63. package/dist/common/logs/request.js.map +1 -1
  64. package/dist/common/queue.js +1 -2
  65. package/dist/common/queue.js.map +1 -1
  66. package/dist/config.js +2 -2
  67. package/dist/config.js.map +1 -1
  68. package/dist/constants.js +13 -8
  69. package/dist/constants.js.map +1 -1
  70. package/dist/controls-options-manager/constants.js.map +1 -1
  71. package/dist/controls-options-manager/enums.js.map +1 -1
  72. package/dist/controls-options-manager/index.js +1 -2
  73. package/dist/controls-options-manager/index.js.map +1 -1
  74. package/dist/controls-options-manager/types.js.map +1 -1
  75. package/dist/controls-options-manager/util.js +1 -2
  76. package/dist/controls-options-manager/util.js.map +1 -1
  77. package/dist/hashTree/constants.js +20 -0
  78. package/dist/hashTree/constants.js.map +1 -0
  79. package/dist/hashTree/hashTree.js +515 -0
  80. package/dist/hashTree/hashTree.js.map +1 -0
  81. package/dist/hashTree/hashTreeParser.js +1250 -0
  82. package/dist/hashTree/hashTreeParser.js.map +1 -0
  83. package/dist/hashTree/types.js +23 -0
  84. package/dist/hashTree/types.js.map +1 -0
  85. package/dist/hashTree/utils.js +59 -0
  86. package/dist/hashTree/utils.js.map +1 -0
  87. package/dist/index.js +8 -2
  88. package/dist/index.js.map +1 -1
  89. package/dist/interceptors/index.js.map +1 -1
  90. package/dist/interceptors/locusRetry.js +6 -8
  91. package/dist/interceptors/locusRetry.js.map +1 -1
  92. package/dist/interceptors/locusRouteToken.js +33 -13
  93. package/dist/interceptors/locusRouteToken.js.map +1 -1
  94. package/dist/interpretation/collection.js.map +1 -1
  95. package/dist/interpretation/index.js +1 -2
  96. package/dist/interpretation/index.js.map +1 -1
  97. package/dist/interpretation/siLanguage.js +1 -1
  98. package/dist/interpretation/siLanguage.js.map +1 -1
  99. package/dist/locus-info/controlsUtils.js +5 -3
  100. package/dist/locus-info/controlsUtils.js.map +1 -1
  101. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  102. package/dist/locus-info/fullState.js.map +1 -1
  103. package/dist/locus-info/hostUtils.js.map +1 -1
  104. package/dist/locus-info/index.js +619 -177
  105. package/dist/locus-info/index.js.map +1 -1
  106. package/dist/locus-info/infoUtils.js.map +1 -1
  107. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  108. package/dist/locus-info/parser.js +3 -4
  109. package/dist/locus-info/parser.js.map +1 -1
  110. package/dist/locus-info/selfUtils.js.map +1 -1
  111. package/dist/locus-info/types.js +7 -0
  112. package/dist/locus-info/types.js.map +1 -0
  113. package/dist/media/MediaConnectionAwaiter.js +1 -2
  114. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  115. package/dist/media/index.js +5 -2
  116. package/dist/media/index.js.map +1 -1
  117. package/dist/media/properties.js +15 -17
  118. package/dist/media/properties.js.map +1 -1
  119. package/dist/media/util.js.map +1 -1
  120. package/dist/meeting/brbState.js +8 -9
  121. package/dist/meeting/brbState.js.map +1 -1
  122. package/dist/meeting/connectionStateHandler.js +10 -13
  123. package/dist/meeting/connectionStateHandler.js.map +1 -1
  124. package/dist/meeting/in-meeting-actions.js.map +1 -1
  125. package/dist/meeting/index.js +1632 -1535
  126. package/dist/meeting/index.js.map +1 -1
  127. package/dist/meeting/locusMediaRequest.js +13 -17
  128. package/dist/meeting/locusMediaRequest.js.map +1 -1
  129. package/dist/meeting/muteState.js +11 -12
  130. package/dist/meeting/muteState.js.map +1 -1
  131. package/dist/meeting/request.js +101 -104
  132. package/dist/meeting/request.js.map +1 -1
  133. package/dist/meeting/request.type.js.map +1 -1
  134. package/dist/meeting/state.js.map +1 -1
  135. package/dist/meeting/type.js.map +1 -1
  136. package/dist/meeting/util.js +24 -23
  137. package/dist/meeting/util.js.map +1 -1
  138. package/dist/meeting/voicea-meeting.js +3 -3
  139. package/dist/meeting/voicea-meeting.js.map +1 -1
  140. package/dist/meeting-info/collection.js +7 -10
  141. package/dist/meeting-info/collection.js.map +1 -1
  142. package/dist/meeting-info/index.js +1 -2
  143. package/dist/meeting-info/index.js.map +1 -1
  144. package/dist/meeting-info/meeting-info-v2.js +135 -146
  145. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  146. package/dist/meeting-info/request.js +1 -2
  147. package/dist/meeting-info/request.js.map +1 -1
  148. package/dist/meeting-info/util.js +36 -37
  149. package/dist/meeting-info/util.js.map +1 -1
  150. package/dist/meeting-info/utilv2.js +30 -31
  151. package/dist/meeting-info/utilv2.js.map +1 -1
  152. package/dist/meetings/collection.js +6 -8
  153. package/dist/meetings/collection.js.map +1 -1
  154. package/dist/meetings/index.js +200 -148
  155. package/dist/meetings/index.js.map +1 -1
  156. package/dist/meetings/meetings.types.js.map +1 -1
  157. package/dist/meetings/request.js +6 -8
  158. package/dist/meetings/request.js.map +1 -1
  159. package/dist/meetings/util.js +36 -30
  160. package/dist/meetings/util.js.map +1 -1
  161. package/dist/member/index.js +1 -2
  162. package/dist/member/index.js.map +1 -1
  163. package/dist/member/types.js +6 -3
  164. package/dist/member/types.js.map +1 -1
  165. package/dist/member/util.js.map +1 -1
  166. package/dist/members/collection.js +1 -2
  167. package/dist/members/collection.js.map +1 -1
  168. package/dist/members/index.js +18 -21
  169. package/dist/members/index.js.map +1 -1
  170. package/dist/members/request.js +8 -11
  171. package/dist/members/request.js.map +1 -1
  172. package/dist/members/types.js.map +1 -1
  173. package/dist/members/util.js.map +1 -1
  174. package/dist/metrics/constants.js +4 -1
  175. package/dist/metrics/constants.js.map +1 -1
  176. package/dist/metrics/index.js +3 -4
  177. package/dist/metrics/index.js.map +1 -1
  178. package/dist/multistream/mediaRequestManager.js +1 -2
  179. package/dist/multistream/mediaRequestManager.js.map +1 -1
  180. package/dist/multistream/receiveSlot.js +34 -45
  181. package/dist/multistream/receiveSlot.js.map +1 -1
  182. package/dist/multistream/receiveSlotManager.js +8 -9
  183. package/dist/multistream/receiveSlotManager.js.map +1 -1
  184. package/dist/multistream/remoteMedia.js +12 -15
  185. package/dist/multistream/remoteMedia.js.map +1 -1
  186. package/dist/multistream/remoteMediaGroup.js +1 -2
  187. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  188. package/dist/multistream/remoteMediaManager.js +122 -123
  189. package/dist/multistream/remoteMediaManager.js.map +1 -1
  190. package/dist/multistream/sendSlotManager.js +29 -30
  191. package/dist/multistream/sendSlotManager.js.map +1 -1
  192. package/dist/personal-meeting-room/index.js +16 -19
  193. package/dist/personal-meeting-room/index.js.map +1 -1
  194. package/dist/personal-meeting-room/request.js +7 -10
  195. package/dist/personal-meeting-room/request.js.map +1 -1
  196. package/dist/personal-meeting-room/util.js.map +1 -1
  197. package/dist/reachability/clusterReachability.js +188 -352
  198. package/dist/reachability/clusterReachability.js.map +1 -1
  199. package/dist/reachability/index.js +206 -206
  200. package/dist/reachability/index.js.map +1 -1
  201. package/dist/reachability/reachability.types.js +14 -1
  202. package/dist/reachability/reachability.types.js.map +1 -1
  203. package/dist/reachability/reachabilityPeerConnection.js +445 -0
  204. package/dist/reachability/reachabilityPeerConnection.js.map +1 -0
  205. package/dist/reachability/request.js.map +1 -1
  206. package/dist/reachability/util.js.map +1 -1
  207. package/dist/reactions/constants.js.map +1 -1
  208. package/dist/reactions/reactions.js.map +1 -1
  209. package/dist/reactions/reactions.type.js.map +1 -1
  210. package/dist/reconnection-manager/index.js +178 -176
  211. package/dist/reconnection-manager/index.js.map +1 -1
  212. package/dist/recording-controller/enums.js.map +1 -1
  213. package/dist/recording-controller/index.js +1 -2
  214. package/dist/recording-controller/index.js.map +1 -1
  215. package/dist/recording-controller/util.js.map +1 -1
  216. package/dist/roap/index.js +12 -15
  217. package/dist/roap/index.js.map +1 -1
  218. package/dist/roap/request.js +24 -26
  219. package/dist/roap/request.js.map +1 -1
  220. package/dist/roap/turnDiscovery.js +75 -76
  221. package/dist/roap/turnDiscovery.js.map +1 -1
  222. package/dist/roap/types.js.map +1 -1
  223. package/dist/transcription/index.js +4 -5
  224. package/dist/transcription/index.js.map +1 -1
  225. package/dist/types/common/errors/webex-errors.d.ts +12 -0
  226. package/dist/types/config.d.ts +1 -0
  227. package/dist/types/constants.d.ts +27 -21
  228. package/dist/types/hashTree/constants.d.ts +8 -0
  229. package/dist/types/hashTree/hashTree.d.ts +129 -0
  230. package/dist/types/hashTree/hashTreeParser.d.ts +250 -0
  231. package/dist/types/hashTree/types.d.ts +33 -0
  232. package/dist/types/hashTree/utils.d.ts +16 -0
  233. package/dist/types/index.d.ts +2 -1
  234. package/dist/types/interceptors/locusRouteToken.d.ts +2 -0
  235. package/dist/types/locus-info/index.d.ts +98 -80
  236. package/dist/types/locus-info/types.d.ts +54 -0
  237. package/dist/types/meeting/index.d.ts +35 -9
  238. package/dist/types/meetings/index.d.ts +9 -2
  239. package/dist/types/metrics/constants.d.ts +3 -0
  240. package/dist/types/reachability/clusterReachability.d.ts +33 -84
  241. package/dist/types/reachability/reachability.types.d.ts +12 -1
  242. package/dist/types/reachability/reachabilityPeerConnection.d.ts +111 -0
  243. package/dist/webinar/collection.js +1 -2
  244. package/dist/webinar/collection.js.map +1 -1
  245. package/dist/webinar/index.js +148 -158
  246. package/dist/webinar/index.js.map +1 -1
  247. package/package.json +24 -23
  248. package/src/common/errors/webex-errors.ts +19 -0
  249. package/src/config.ts +1 -0
  250. package/src/constants.ts +15 -2
  251. package/src/hashTree/constants.ts +9 -0
  252. package/src/hashTree/hashTree.ts +463 -0
  253. package/src/hashTree/hashTreeParser.ts +1143 -0
  254. package/src/hashTree/types.ts +39 -0
  255. package/src/hashTree/utils.ts +53 -0
  256. package/src/index.ts +2 -0
  257. package/src/interceptors/locusRouteToken.ts +22 -5
  258. package/src/locus-info/controlsUtils.ts +6 -0
  259. package/src/locus-info/index.ts +641 -164
  260. package/src/locus-info/types.ts +53 -0
  261. package/src/media/index.ts +6 -0
  262. package/src/meeting/index.ts +137 -28
  263. package/src/meeting/util.ts +1 -0
  264. package/src/meetings/index.ts +119 -59
  265. package/src/meetings/util.ts +10 -9
  266. package/src/metrics/constants.ts +3 -0
  267. package/src/reachability/clusterReachability.ts +159 -330
  268. package/src/reachability/index.ts +6 -1
  269. package/src/reachability/reachability.types.ts +15 -1
  270. package/src/reachability/reachabilityPeerConnection.ts +418 -0
  271. package/test/unit/spec/hashTree/hashTree.ts +655 -0
  272. package/test/unit/spec/hashTree/hashTreeParser.ts +1524 -0
  273. package/test/unit/spec/hashTree/utils.ts +140 -0
  274. package/test/unit/spec/interceptors/locusRouteToken.ts +44 -0
  275. package/test/unit/spec/locus-info/controlsUtils.js +27 -1
  276. package/test/unit/spec/locus-info/index.js +879 -16
  277. package/test/unit/spec/media/index.ts +140 -9
  278. package/test/unit/spec/meeting/index.js +299 -94
  279. package/test/unit/spec/meeting/utils.js +78 -1
  280. package/test/unit/spec/meetings/index.js +263 -29
  281. package/test/unit/spec/meetings/utils.js +51 -1
  282. package/test/unit/spec/reachability/clusterReachability.ts +404 -137
  283. package/test/unit/spec/reachability/index.ts +3 -3
@@ -1,12 +1,14 @@
1
- import {Defer} from '@webex/common';
2
-
3
- import LoggerProxy from '../common/logs/logger-proxy';
4
1
  import {ClusterNode} from './request';
5
- import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
6
2
  import EventsScope from '../common/events/events-scope';
3
+ import LoggerProxy from '../common/logs/logger-proxy';
7
4
 
8
- import {CONNECTION_STATE, Enum, ICE_GATHERING_STATE} from '../constants';
9
- import {ClusterReachabilityResult, NatType} from './reachability.types';
5
+ import {Enum} from '../constants';
6
+ import {
7
+ ClusterReachabilityResult,
8
+ NatType,
9
+ ReachabilityPeerConnectionEvents,
10
+ } from './reachability.types';
11
+ import {ReachabilityPeerConnection} from './reachabilityPeerConnection';
10
12
 
11
13
  // data for the Events.resultReady event
12
14
  export type ResultEventData = {
@@ -36,398 +38,225 @@ export type Events = Enum<typeof Events>;
36
38
 
37
39
  /**
38
40
  * A class that handles reachability checks for a single cluster.
39
- * It emits events from Events enum
41
+ * Creates and orchestrates ReachabilityPeerConnection instance(s).
42
+ * Listens to events and emits them to consumers.
43
+ *
44
+ * When enablePerUdpUrlReachability is true:
45
+ * - Creates one ReachabilityPeerConnection for each UDP URL
46
+ * - Creates one ReachabilityPeerConnection for all TCP and TLS URLs together
47
+ * Otherwise:
48
+ * - Creates a single ReachabilityPeerConnection for all URLs
40
49
  */
41
50
  export class ClusterReachability extends EventsScope {
42
- private numUdpUrls: number;
43
- private numTcpUrls: number;
44
- private numXTlsUrls: number;
45
- private result: ClusterReachabilityResult;
46
- private pc?: RTCPeerConnection;
47
- private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
48
- private startTimestamp: number;
49
- private srflxIceCandidates: RTCIceCandidate[] = [];
51
+ private reachabilityPeerConnection: ReachabilityPeerConnection | null = null;
52
+ private reachabilityPeerConnectionsForUdp: ReachabilityPeerConnection[] = [];
53
+
50
54
  public readonly isVideoMesh: boolean;
51
55
  public readonly name;
52
56
  public readonly reachedSubnets: Set<string> = new Set();
53
57
 
58
+ private enablePerUdpUrlReachability: boolean;
59
+ private udpResultEmitted = false;
60
+
54
61
  /**
55
62
  * Constructor for ClusterReachability
56
63
  * @param {string} name cluster name
57
64
  * @param {ClusterNode} clusterInfo information about the media cluster
65
+ * @param {boolean} enablePerUdpUrlReachability whether to create separate peer connections per UDP URL
58
66
  */
59
- constructor(name: string, clusterInfo: ClusterNode) {
67
+ constructor(name: string, clusterInfo: ClusterNode, enablePerUdpUrlReachability = false) {
60
68
  super();
61
69
  this.name = name;
62
70
  this.isVideoMesh = clusterInfo.isVideoMesh;
63
- this.numUdpUrls = clusterInfo.udp.length;
64
- this.numTcpUrls = clusterInfo.tcp.length;
65
- this.numXTlsUrls = clusterInfo.xtls.length;
71
+ this.enablePerUdpUrlReachability = enablePerUdpUrlReachability;
66
72
 
67
- this.pc = this.createPeerConnection(clusterInfo);
68
-
69
- this.defer = new Defer();
70
- this.result = {
71
- udp: {
72
- result: 'untested',
73
- },
74
- tcp: {
75
- result: 'untested',
76
- },
77
- xtls: {
78
- result: 'untested',
79
- },
80
- };
73
+ if (this.enablePerUdpUrlReachability) {
74
+ this.initializePerUdpUrlReachabilityCheck(clusterInfo);
75
+ } else {
76
+ this.initializeSingleReachabilityPeerConnection(clusterInfo);
77
+ }
81
78
  }
82
79
 
83
80
  /**
84
- * Gets total elapsed time, can be called only after start() is called
85
- * @returns {Number} Milliseconds
81
+ * Initializes a single ReachabilityPeerConnection for all protocols
82
+ * @param {ClusterNode} clusterInfo information about the media cluster
83
+ * @returns {void}
86
84
  */
87
- private getElapsedTime() {
88
- return Math.round(performance.now() - this.startTimestamp);
85
+ private initializeSingleReachabilityPeerConnection(clusterInfo: ClusterNode) {
86
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(this.name, clusterInfo);
87
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
89
88
  }
90
89
 
91
90
  /**
92
- * Generate peerConnection config settings
93
- * @param {ClusterNode} cluster
94
- * @returns {RTCConfiguration} peerConnectionConfig
91
+ * Initializes per-URL UDP reachability checks:
92
+ * - One ReachabilityPeerConnection per UDP URL
93
+ * - One ReachabilityPeerConnection for all TCP and TLS URLs together
94
+ * @param {ClusterNode} clusterInfo information about the media cluster
95
+ * @returns {void}
95
96
  */
96
- private buildPeerConnectionConfig(cluster: ClusterNode): RTCConfiguration {
97
- const udpIceServers = cluster.udp.map((url) => ({
98
- username: '',
99
- credential: '',
100
- urls: [url],
101
- }));
102
-
103
- // STUN servers are contacted only using UDP, so in order to test TCP reachability
104
- // we pretend that Linus is a TURN server, because we can explicitly say "transport=tcp" in TURN urls.
105
- // We then check for relay candidates to know if TURN-TCP worked (see registerIceCandidateListener()).
106
- const tcpIceServers = cluster.tcp.map((urlString: string) => {
107
- return {
108
- username: 'webexturnreachuser',
109
- credential: 'webexturnreachpwd',
110
- urls: [convertStunUrlToTurn(urlString, 'tcp')],
97
+ private initializePerUdpUrlReachabilityCheck(clusterInfo: ClusterNode) {
98
+ LoggerProxy.logger.log(
99
+ `ClusterReachability#initializePerUdpUrlReachabilityCheck --> cluster: ${this.name}, performing per-URL UDP reachability for ${clusterInfo.udp.length} URLs`
100
+ );
101
+
102
+ // Create one ReachabilityPeerConnection for each UDP URL
103
+ clusterInfo.udp.forEach((udpUrl) => {
104
+ const singleUdpClusterInfo: ClusterNode = {
105
+ isVideoMesh: clusterInfo.isVideoMesh,
106
+ udp: [udpUrl],
107
+ tcp: [],
108
+ xtls: [],
111
109
  };
110
+ const rpc = new ReachabilityPeerConnection(this.name, singleUdpClusterInfo);
111
+ this.setupReachabilityPeerConnectionEventListeners(rpc, true);
112
+ this.reachabilityPeerConnectionsForUdp.push(rpc);
112
113
  });
113
114
 
114
- const turnTlsIceServers = cluster.xtls.map((urlString: string) => {
115
- return {
116
- username: 'webexturnreachuser',
117
- credential: 'webexturnreachpwd',
118
- urls: [convertStunUrlToTurnTls(urlString)],
115
+ // Create one ReachabilityPeerConnection for all TCP and TLS URLs together
116
+ if (clusterInfo.tcp.length > 0 || clusterInfo.xtls.length > 0) {
117
+ const tcpTlsClusterInfo: ClusterNode = {
118
+ isVideoMesh: clusterInfo.isVideoMesh,
119
+ udp: [],
120
+ tcp: clusterInfo.tcp,
121
+ xtls: clusterInfo.xtls,
119
122
  };
120
- });
121
-
122
- return {
123
- iceServers: [...udpIceServers, ...tcpIceServers, ...turnTlsIceServers],
124
- iceCandidatePoolSize: 0,
125
- iceTransportPolicy: 'all',
126
- };
127
- }
128
-
129
- /**
130
- * Creates an RTCPeerConnection
131
- * @param {ClusterNode} clusterInfo information about the media cluster
132
- * @returns {RTCPeerConnection} peerConnection
133
- */
134
- private createPeerConnection(clusterInfo: ClusterNode) {
135
- try {
136
- const config = this.buildPeerConnectionConfig(clusterInfo);
137
-
138
- const peerConnection = new RTCPeerConnection(config);
139
-
140
- return peerConnection;
141
- } catch (peerConnectionError) {
142
- LoggerProxy.logger.warn(
143
- `Reachability:index#createPeerConnection --> Error creating peerConnection:`,
144
- peerConnectionError
123
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(
124
+ this.name,
125
+ tcpTlsClusterInfo
145
126
  );
146
-
147
- return undefined;
127
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
148
128
  }
149
129
  }
150
130
 
151
131
  /**
152
- * @returns {ClusterReachabilityResult} reachability result for this cluster
153
- */
154
- getResult() {
155
- return this.result;
156
- }
157
-
158
- /**
159
- * Closes the peerConnection
160
- *
132
+ * Sets up event listeners for a ReachabilityPeerConnection instance
133
+ * @param {ReachabilityPeerConnection} rpc the ReachabilityPeerConnection instance
134
+ * @param {boolean} isUdpPerUrl whether this is a per-URL UDP instance
161
135
  * @returns {void}
162
136
  */
163
- private closePeerConnection() {
164
- if (this.pc) {
165
- this.pc.onicecandidate = null;
166
- this.pc.onicegatheringstatechange = null;
167
- this.pc.close();
168
- }
169
- }
170
-
171
- /**
172
- * Resolves the defer, indicating that reachability checks for this cluster are completed
173
- *
174
- * @returns {void}
175
- */
176
- private finishReachabilityCheck() {
177
- this.defer.resolve();
178
- }
179
-
180
- /**
181
- * Aborts the cluster reachability checks by closing the peer connection
182
- *
183
- * @returns {void}
184
- */
185
- public abort() {
186
- const {CLOSED} = CONNECTION_STATE;
187
-
188
- if (this.pc.connectionState !== CLOSED) {
189
- this.closePeerConnection();
190
- this.finishReachabilityCheck();
191
- }
192
- }
193
-
194
- /**
195
- * Adds public IP (client media IPs)
196
- * @param {string} protocol
197
- * @param {string} publicIP
198
- * @returns {void}
199
- */
200
- private addPublicIP(protocol: 'udp' | 'tcp' | 'xtls', publicIP?: string | null) {
201
- const result = this.result[protocol];
202
-
203
- if (publicIP) {
204
- let ipAdded = false;
205
-
206
- if (result.clientMediaIPs) {
207
- if (!result.clientMediaIPs.includes(publicIP)) {
208
- result.clientMediaIPs.push(publicIP);
209
- ipAdded = true;
137
+ private setupReachabilityPeerConnectionEventListeners(
138
+ rpc: ReachabilityPeerConnection,
139
+ isUdpPerUrl = false
140
+ ) {
141
+ rpc.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
142
+ // For per-URL UDP checks, only emit the first successful UDP result
143
+ if (isUdpPerUrl && data.protocol === 'udp') {
144
+ if (this.udpResultEmitted) {
145
+ return;
146
+ }
147
+ if (data.result === 'reachable') {
148
+ this.udpResultEmitted = true;
210
149
  }
211
- } else {
212
- result.clientMediaIPs = [publicIP];
213
- ipAdded = true;
214
- }
215
-
216
- if (ipAdded)
217
- this.emit(
218
- {
219
- file: 'clusterReachability',
220
- function: 'addPublicIP',
221
- },
222
- Events.clientMediaIpsUpdated,
223
- {
224
- protocol,
225
- clientMediaIPs: result.clientMediaIPs,
226
- }
227
- );
228
- }
229
- }
230
-
231
- /**
232
- * Registers a listener for the iceGatheringStateChange event
233
- *
234
- * @returns {void}
235
- */
236
- private registerIceGatheringStateChangeListener() {
237
- this.pc.onicegatheringstatechange = () => {
238
- if (this.pc.iceGatheringState === ICE_GATHERING_STATE.COMPLETE) {
239
- this.closePeerConnection();
240
- this.finishReachabilityCheck();
241
150
  }
242
- };
243
- }
244
-
245
- /**
246
- * Saves the latency in the result for the given protocol and marks it as reachable,
247
- * emits the "resultReady" event if this is the first result for that protocol,
248
- * emits the "clientMediaIpsUpdated" event if we already had a result and only found
249
- * a new client IP
250
- *
251
- * @param {string} protocol
252
- * @param {number} latency
253
- * @param {string|null} [publicIp]
254
- * @param {string|null} [serverIp]
255
- * @returns {void}
256
- */
257
- private saveResult(
258
- protocol: 'udp' | 'tcp' | 'xtls',
259
- latency: number,
260
- publicIp?: string | null,
261
- serverIp?: string | null
262
- ) {
263
- const result = this.result[protocol];
264
151
 
265
- if (result.latencyInMilliseconds === undefined) {
266
- LoggerProxy.logger.log(
267
- // @ts-ignore
268
- `Reachability:index#saveResult --> Successfully reached ${this.name} over ${protocol}: ${latency}ms`
152
+ this.emit(
153
+ {
154
+ file: 'clusterReachability',
155
+ function: 'setupReachabilityPeerConnectionEventListeners',
156
+ },
157
+ Events.resultReady,
158
+ data
269
159
  );
270
- result.latencyInMilliseconds = latency;
271
- result.result = 'reachable';
272
- if (publicIp) {
273
- result.clientMediaIPs = [publicIp];
274
- }
160
+ });
275
161
 
162
+ rpc.on(ReachabilityPeerConnectionEvents.clientMediaIpsUpdated, (data) => {
276
163
  this.emit(
277
164
  {
278
165
  file: 'clusterReachability',
279
- function: 'saveResult',
166
+ function: 'setupReachabilityPeerConnectionEventListeners',
280
167
  },
281
- Events.resultReady,
168
+ Events.clientMediaIpsUpdated,
169
+ data
170
+ );
171
+ });
172
+
173
+ rpc.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
174
+ this.emit(
282
175
  {
283
- protocol,
284
- ...result,
285
- }
176
+ file: 'clusterReachability',
177
+ function: 'setupReachabilityPeerConnectionEventListeners',
178
+ },
179
+ Events.natTypeUpdated,
180
+ data
286
181
  );
287
- } else {
288
- this.addPublicIP(protocol, publicIp);
289
- }
182
+ });
290
183
 
291
- if (serverIp) {
292
- this.reachedSubnets.add(serverIp);
293
- }
184
+ rpc.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
185
+ data.subnets.forEach((subnet: string) => {
186
+ this.reachedSubnets.add(subnet);
187
+ });
188
+ });
294
189
  }
295
190
 
296
191
  /**
297
- * Determines NAT Type.
298
- *
299
- * @param {RTCIceCandidate} candidate
300
- * @returns {void}
192
+ * Gets the aggregated reachability result for this cluster.
193
+ * @returns {ClusterReachabilityResult} reachability result for this cluster
301
194
  */
302
- private determineNatType(candidate: RTCIceCandidate) {
303
- this.srflxIceCandidates.push(candidate);
304
-
305
- if (this.srflxIceCandidates.length > 1) {
306
- const portsFound: Record<string, Set<number>> = {};
307
-
308
- this.srflxIceCandidates.forEach((c) => {
309
- const key = `${c.address}:${c.relatedPort}`;
310
- if (!portsFound[key]) {
311
- portsFound[key] = new Set();
312
- }
313
- portsFound[key].add(c.port);
314
- });
315
-
316
- Object.entries(portsFound).forEach(([, ports]) => {
317
- if (ports.size > 1) {
318
- // Found candidates with the same address and relatedPort, but different ports
319
- this.emit(
320
- {
321
- file: 'clusterReachability',
322
- function: 'determineNatType',
323
- },
324
- Events.natTypeUpdated,
325
- {
326
- natType: NatType.SymmetricNat,
327
- }
328
- );
195
+ getResult(): ClusterReachabilityResult {
196
+ if (!this.enablePerUdpUrlReachability) {
197
+ return (
198
+ this.reachabilityPeerConnection?.getResult() ?? {
199
+ udp: {result: 'untested'},
200
+ tcp: {result: 'untested'},
201
+ xtls: {result: 'untested'},
329
202
  }
330
- });
203
+ );
331
204
  }
332
- }
333
-
334
- /**
335
- * Registers a listener for the icecandidate event
336
- *
337
- * @returns {void}
338
- */
339
- private registerIceCandidateListener() {
340
- this.pc.onicecandidate = (e) => {
341
- const TURN_TLS_PORT = 443;
342
- const CANDIDATE_TYPES = {
343
- SERVER_REFLEXIVE: 'srflx',
344
- RELAY: 'relay',
345
- };
346
-
347
- const latencyInMilliseconds = this.getElapsedTime();
348
-
349
- if (e.candidate) {
350
- if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
351
- let serverIp = null;
352
- if ('url' in e.candidate) {
353
- const stunServerUrlRegex = /stun:([\d.]+):\d+/;
354
205
 
355
- const match = (e.candidate as any).url.match(stunServerUrlRegex);
356
- if (match) {
357
- // eslint-disable-next-line prefer-destructuring
358
- serverIp = match[1];
359
- }
360
- }
206
+ const result: ClusterReachabilityResult = {
207
+ udp: {result: 'untested'},
208
+ tcp: {result: 'untested'},
209
+ xtls: {result: 'untested'},
210
+ };
361
211
 
362
- this.saveResult('udp', latencyInMilliseconds, e.candidate.address, serverIp);
212
+ // Get the first reachable UDP result from per-URL instances
213
+ for (const rpc of this.reachabilityPeerConnectionsForUdp) {
214
+ const rpcResult = rpc.getResult();
215
+ if (rpcResult.udp.result === 'reachable') {
216
+ result.udp = rpcResult.udp;
217
+ break;
218
+ }
219
+ if (rpcResult.udp.result === 'unreachable' && result.udp.result === 'untested') {
220
+ result.udp = rpcResult.udp;
221
+ }
222
+ }
363
223
 
364
- this.determineNatType(e.candidate);
365
- }
224
+ // Get TCP and TLS results from the main peer connection
225
+ if (this.reachabilityPeerConnection) {
226
+ const mainResult = this.reachabilityPeerConnection.getResult();
227
+ result.tcp = mainResult.tcp;
228
+ result.xtls = mainResult.xtls;
229
+ }
366
230
 
367
- if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
368
- const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
369
- this.saveResult(protocol, latencyInMilliseconds, null, e.candidate.address);
370
- }
371
- }
372
- };
231
+ return result;
373
232
  }
374
233
 
375
234
  /**
376
- * Starts the process of doing UDP and TCP reachability checks on the media cluster.
377
- * XTLS reachability checking is not supported.
378
- *
379
- * @returns {Promise}
235
+ * Starts the process of doing UDP, TCP, and XTLS reachability checks on the media cluster.
236
+ * @returns {Promise<ClusterReachabilityResult>}
380
237
  */
381
238
  async start(): Promise<ClusterReachabilityResult> {
382
- if (!this.pc) {
383
- LoggerProxy.logger.warn(
384
- `Reachability:ClusterReachability#start --> Error: peerConnection is undefined`
385
- );
386
-
387
- return this.result;
388
- }
389
-
390
- // Initialize this.result as saying that nothing is reachable.
391
- // It will get updated as we go along and successfully gather ICE candidates.
392
- this.result.udp = {
393
- result: this.numUdpUrls > 0 ? 'unreachable' : 'untested',
394
- };
395
- this.result.tcp = {
396
- result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
397
- };
398
- this.result.xtls = {
399
- result: this.numXTlsUrls > 0 ? 'unreachable' : 'untested',
400
- };
239
+ const startPromises: Promise<ClusterReachabilityResult>[] = [];
401
240
 
402
- try {
403
- const offer = await this.pc.createOffer({offerToReceiveAudio: true});
404
-
405
- this.startTimestamp = performance.now();
406
-
407
- // Set up the state change listeners before triggering the ICE gathering
408
- const gatherIceCandidatePromise = this.gatherIceCandidates();
409
-
410
- // not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
411
- // we just need to make this call to trigger the ICE gathering process
412
- this.pc.setLocalDescription(offer);
241
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => {
242
+ startPromises.push(rpc.start());
243
+ });
413
244
 
414
- await gatherIceCandidatePromise;
415
- } catch (error) {
416
- LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
245
+ if (this.reachabilityPeerConnection) {
246
+ startPromises.push(this.reachabilityPeerConnection.start());
417
247
  }
418
248
 
419
- return this.result;
249
+ await Promise.all(startPromises);
250
+
251
+ return this.getResult();
420
252
  }
421
253
 
422
254
  /**
423
- * Starts the process of gathering ICE candidates
424
- *
425
- * @returns {Promise} promise that's resolved once reachability checks for this cluster are completed or timeout is reached
255
+ * Aborts the cluster reachability checks
256
+ * @returns {void}
426
257
  */
427
- private gatherIceCandidates() {
428
- this.registerIceGatheringStateChangeListener();
429
- this.registerIceCandidateListener();
430
-
431
- return this.defer.promise;
258
+ public abort() {
259
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => rpc.abort());
260
+ this.reachabilityPeerConnection?.abort();
432
261
  }
433
262
  }
@@ -961,7 +961,12 @@ export default class Reachability extends EventsScope {
961
961
  Object.keys(clusterList).forEach((key) => {
962
962
  const cluster = clusterList[key];
963
963
 
964
- this.clusterReachability[key] = new ClusterReachability(key, cluster);
964
+ this.clusterReachability[key] = new ClusterReachability(
965
+ key,
966
+ cluster,
967
+ // @ts-ignore
968
+ this.webex.config.meetings.enablePerUdpUrlReachability
969
+ );
965
970
  this.clusterReachability[key].on(Events.resultReady, async (data: ResultEventData) => {
966
971
  const {protocol, result, clientMediaIPs, latencyInMilliseconds} = data;
967
972
 
@@ -1,4 +1,18 @@
1
- import {IP_VERSION} from '../constants';
1
+ import {IP_VERSION, Enum} from '../constants';
2
+
3
+ export type Protocol = 'udp' | 'tcp' | 'xtls';
4
+
5
+ /**
6
+ * Events emitted by ReachabilityPeerConnection
7
+ */
8
+ export const ReachabilityPeerConnectionEvents = {
9
+ resultReady: 'resultReady', // emitted when successfully reached over a protocol
10
+ clientMediaIpsUpdated: 'clientMediaIpsUpdated', // emitted when new public IPs are found
11
+ natTypeUpdated: 'natTypeUpdated', // emitted when NAT type is determined
12
+ reachedSubnets: 'reachedSubnets', // emitted when server IP (subnet) is discovered
13
+ } as const;
14
+
15
+ export type ReachabilityPeerConnectionEvents = Enum<typeof ReachabilityPeerConnectionEvents>;
2
16
 
3
17
  // result for a specific transport protocol (like udp or tcp)
4
18
  export type TransportResult = {