@webex/plugin-meetings 3.0.0 → 3.1.0-next.2

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 (252) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +2 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +8 -4
  6. package/dist/constants.js.map +1 -1
  7. package/dist/index.js +86 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/interpretation/index.js +16 -2
  10. package/dist/interpretation/index.js.map +1 -1
  11. package/dist/interpretation/siLanguage.js +1 -1
  12. package/dist/locus-info/mediaSharesUtils.js +15 -1
  13. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  14. package/dist/locus-info/selfUtils.js +5 -0
  15. package/dist/locus-info/selfUtils.js.map +1 -1
  16. package/dist/media/MediaConnectionAwaiter.js +163 -0
  17. package/dist/media/MediaConnectionAwaiter.js.map +1 -0
  18. package/dist/media/index.js +4 -1
  19. package/dist/media/index.js.map +1 -1
  20. package/dist/media/properties.js +4 -24
  21. package/dist/media/properties.js.map +1 -1
  22. package/dist/meeting/index.js +893 -677
  23. package/dist/meeting/index.js.map +1 -1
  24. package/dist/meeting/muteState.js +37 -25
  25. package/dist/meeting/muteState.js.map +1 -1
  26. package/dist/meeting/request.js +32 -23
  27. package/dist/meeting/request.js.map +1 -1
  28. package/dist/meeting/util.js +1 -0
  29. package/dist/meeting/util.js.map +1 -1
  30. package/dist/meeting-info/util.js +304 -267
  31. package/dist/meeting-info/util.js.map +1 -1
  32. package/dist/meeting-info/utilv2.js +334 -295
  33. package/dist/meeting-info/utilv2.js.map +1 -1
  34. package/dist/meetings/index.js +20 -0
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/multistream/mediaRequestManager.js +1 -1
  37. package/dist/multistream/mediaRequestManager.js.map +1 -1
  38. package/dist/multistream/remoteMediaGroup.js +16 -2
  39. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  40. package/dist/multistream/remoteMediaManager.js +179 -65
  41. package/dist/multistream/remoteMediaManager.js.map +1 -1
  42. package/dist/multistream/sendSlotManager.js +22 -0
  43. package/dist/multistream/sendSlotManager.js.map +1 -1
  44. package/dist/reachability/clusterReachability.js +29 -15
  45. package/dist/reachability/clusterReachability.js.map +1 -1
  46. package/dist/reachability/index.js +18 -2
  47. package/dist/reachability/index.js.map +1 -1
  48. package/dist/reachability/request.js +12 -10
  49. package/dist/reachability/request.js.map +1 -1
  50. package/dist/reachability/util.js +19 -0
  51. package/dist/reachability/util.js.map +1 -1
  52. package/dist/reconnection-manager/index.js +2 -1
  53. package/dist/reconnection-manager/index.js.map +1 -1
  54. package/dist/roap/index.js +15 -0
  55. package/dist/roap/index.js.map +1 -1
  56. package/dist/roap/request.js +3 -3
  57. package/dist/roap/request.js.map +1 -1
  58. package/dist/roap/turnDiscovery.js +307 -126
  59. package/dist/roap/turnDiscovery.js.map +1 -1
  60. package/dist/statsAnalyzer/index.js +53 -30
  61. package/dist/statsAnalyzer/index.js.map +1 -1
  62. package/dist/{config.d.ts → types/config.d.ts} +1 -0
  63. package/dist/{constants.d.ts → types/constants.d.ts} +5 -4
  64. package/dist/types/index.d.ts +19 -0
  65. package/dist/types/media/MediaConnectionAwaiter.d.ts +61 -0
  66. package/dist/{meeting → types/meeting}/index.d.ts +26 -7
  67. package/dist/{meeting → types/meeting}/muteState.d.ts +2 -8
  68. package/dist/{meeting → types/meeting}/request.d.ts +3 -0
  69. package/dist/{meeting-info → types/meeting-info}/index.d.ts +1 -1
  70. package/dist/{meeting-info → types/meeting-info}/meeting-info-v2.d.ts +1 -1
  71. package/dist/types/meeting-info/util.d.ts +49 -0
  72. package/dist/types/meeting-info/utilv2.d.ts +65 -0
  73. package/dist/{meetings → types/meetings}/index.d.ts +8 -0
  74. package/dist/{multistream → types/multistream}/mediaRequestManager.d.ts +2 -1
  75. package/dist/{multistream → types/multistream}/remoteMediaGroup.d.ts +2 -0
  76. package/dist/{multistream → types/multistream}/remoteMediaManager.d.ts +15 -0
  77. package/dist/{multistream → types/multistream}/sendSlotManager.d.ts +9 -1
  78. package/dist/{reachability → types/reachability}/clusterReachability.d.ts +1 -0
  79. package/dist/{reachability → types/reachability}/index.d.ts +4 -0
  80. package/dist/{reachability → types/reachability}/util.d.ts +7 -0
  81. package/dist/{roap → types/roap}/index.d.ts +10 -2
  82. package/dist/{roap → types/roap}/turnDiscovery.d.ts +64 -17
  83. package/dist/webinar/index.js +1 -1
  84. package/package.json +23 -23
  85. package/src/config.ts +1 -0
  86. package/src/constants.ts +7 -3
  87. package/src/index.ts +31 -0
  88. package/src/interpretation/index.ts +18 -1
  89. package/src/locus-info/mediaSharesUtils.ts +16 -0
  90. package/src/locus-info/selfUtils.ts +5 -0
  91. package/src/media/MediaConnectionAwaiter.ts +174 -0
  92. package/src/media/index.ts +3 -1
  93. package/src/media/properties.ts +6 -31
  94. package/src/meeting/index.ts +321 -106
  95. package/src/meeting/muteState.ts +34 -20
  96. package/src/meeting/request.ts +18 -2
  97. package/src/meeting/util.ts +1 -0
  98. package/src/meeting-info/util.ts +241 -233
  99. package/src/meeting-info/utilv2.ts +250 -243
  100. package/src/meetings/index.ts +18 -0
  101. package/src/multistream/mediaRequestManager.ts +4 -1
  102. package/src/multistream/remoteMediaGroup.ts +19 -0
  103. package/src/multistream/remoteMediaManager.ts +101 -16
  104. package/src/multistream/sendSlotManager.ts +28 -0
  105. package/src/reachability/clusterReachability.ts +20 -5
  106. package/src/reachability/index.ts +24 -1
  107. package/src/reachability/request.ts +15 -11
  108. package/src/reachability/util.ts +21 -0
  109. package/src/reconnection-manager/index.ts +1 -1
  110. package/src/roap/index.ts +25 -3
  111. package/src/roap/request.ts +3 -3
  112. package/src/roap/turnDiscovery.ts +244 -78
  113. package/src/statsAnalyzer/index.ts +63 -27
  114. package/test/integration/spec/journey.js +14 -14
  115. package/test/integration/spec/space-meeting.js +1 -1
  116. package/test/unit/spec/interpretation/index.ts +39 -3
  117. package/test/unit/spec/locus-info/index.js +28 -19
  118. package/test/unit/spec/locus-info/mediaSharesUtils.ts +9 -0
  119. package/test/unit/spec/locus-info/selfUtils.js +42 -12
  120. package/test/unit/spec/media/MediaConnectionAwaiter.ts +344 -0
  121. package/test/unit/spec/media/index.ts +89 -78
  122. package/test/unit/spec/media/properties.ts +16 -70
  123. package/test/unit/spec/meeting/index.js +638 -139
  124. package/test/unit/spec/meeting/muteState.js +219 -67
  125. package/test/unit/spec/meeting/request.js +21 -0
  126. package/test/unit/spec/meeting/utils.js +6 -1
  127. package/test/unit/spec/meeting-info/utilv2.js +6 -0
  128. package/test/unit/spec/meetings/index.js +40 -20
  129. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  130. package/test/unit/spec/multistream/remoteMediaGroup.ts +79 -1
  131. package/test/unit/spec/multistream/remoteMediaManager.ts +199 -1
  132. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  133. package/test/unit/spec/reachability/clusterReachability.ts +86 -22
  134. package/test/unit/spec/reachability/index.ts +197 -60
  135. package/test/unit/spec/reachability/request.js +15 -7
  136. package/test/unit/spec/reachability/util.ts +32 -2
  137. package/test/unit/spec/reconnection-manager/index.js +28 -0
  138. package/test/unit/spec/roap/index.ts +61 -6
  139. package/test/unit/spec/roap/turnDiscovery.ts +298 -16
  140. package/test/unit/spec/stats-analyzer/index.js +179 -0
  141. package/dist/index.d.ts +0 -7
  142. package/dist/meeting-info/util.d.ts +0 -2
  143. package/dist/meeting-info/utilv2.d.ts +0 -2
  144. package/dist/member/member.types.d.ts +0 -11
  145. package/dist/member/member.types.js +0 -17
  146. package/dist/member/member.types.js.map +0 -1
  147. package/src/member/member.types.ts +0 -13
  148. /package/dist/{annotation → types/annotation}/annotation.types.d.ts +0 -0
  149. /package/dist/{annotation → types/annotation}/constants.d.ts +0 -0
  150. /package/dist/{annotation → types/annotation}/index.d.ts +0 -0
  151. /package/dist/{breakouts → types/breakouts}/breakout.d.ts +0 -0
  152. /package/dist/{breakouts → types/breakouts}/collection.d.ts +0 -0
  153. /package/dist/{breakouts → types/breakouts}/edit-lock-error.d.ts +0 -0
  154. /package/dist/{breakouts → types/breakouts}/events.d.ts +0 -0
  155. /package/dist/{breakouts → types/breakouts}/index.d.ts +0 -0
  156. /package/dist/{breakouts → types/breakouts}/request.d.ts +0 -0
  157. /package/dist/{breakouts → types/breakouts}/utils.d.ts +0 -0
  158. /package/dist/{common → types/common}/browser-detection.d.ts +0 -0
  159. /package/dist/{common → types/common}/collection.d.ts +0 -0
  160. /package/dist/{common → types/common}/config.d.ts +0 -0
  161. /package/dist/{common → types/common}/errors/captcha-error.d.ts +0 -0
  162. /package/dist/{common → types/common}/errors/intent-to-join.d.ts +0 -0
  163. /package/dist/{common → types/common}/errors/join-meeting.d.ts +0 -0
  164. /package/dist/{common → types/common}/errors/media.d.ts +0 -0
  165. /package/dist/{common → types/common}/errors/no-meeting-info.d.ts +0 -0
  166. /package/dist/{common → types/common}/errors/parameter.d.ts +0 -0
  167. /package/dist/{common → types/common}/errors/password-error.d.ts +0 -0
  168. /package/dist/{common → types/common}/errors/permission.d.ts +0 -0
  169. /package/dist/{common → types/common}/errors/reclaim-host-role-errors.d.ts +0 -0
  170. /package/dist/{common → types/common}/errors/reconnection-in-progress.d.ts +0 -0
  171. /package/dist/{common → types/common}/errors/reconnection.d.ts +0 -0
  172. /package/dist/{common → types/common}/errors/stats.d.ts +0 -0
  173. /package/dist/{common → types/common}/errors/webex-errors.d.ts +0 -0
  174. /package/dist/{common → types/common}/errors/webex-meetings-error.d.ts +0 -0
  175. /package/dist/{common → types/common}/events/events-scope.d.ts +0 -0
  176. /package/dist/{common → types/common}/events/events.d.ts +0 -0
  177. /package/dist/{common → types/common}/events/trigger-proxy.d.ts +0 -0
  178. /package/dist/{common → types/common}/events/util.d.ts +0 -0
  179. /package/dist/{common → types/common}/logs/logger-config.d.ts +0 -0
  180. /package/dist/{common → types/common}/logs/logger-proxy.d.ts +0 -0
  181. /package/dist/{common → types/common}/logs/request.d.ts +0 -0
  182. /package/dist/{common → types/common}/queue.d.ts +0 -0
  183. /package/dist/{controls-options-manager → types/controls-options-manager}/constants.d.ts +0 -0
  184. /package/dist/{controls-options-manager → types/controls-options-manager}/enums.d.ts +0 -0
  185. /package/dist/{controls-options-manager → types/controls-options-manager}/index.d.ts +0 -0
  186. /package/dist/{controls-options-manager → types/controls-options-manager}/types.d.ts +0 -0
  187. /package/dist/{controls-options-manager → types/controls-options-manager}/util.d.ts +0 -0
  188. /package/dist/{interceptors → types/interceptors}/index.d.ts +0 -0
  189. /package/dist/{interceptors → types/interceptors}/locusRetry.d.ts +0 -0
  190. /package/dist/{interpretation → types/interpretation}/collection.d.ts +0 -0
  191. /package/dist/{interpretation → types/interpretation}/index.d.ts +0 -0
  192. /package/dist/{interpretation → types/interpretation}/siLanguage.d.ts +0 -0
  193. /package/dist/{locus-info → types/locus-info}/controlsUtils.d.ts +0 -0
  194. /package/dist/{locus-info → types/locus-info}/embeddedAppsUtils.d.ts +0 -0
  195. /package/dist/{locus-info → types/locus-info}/fullState.d.ts +0 -0
  196. /package/dist/{locus-info → types/locus-info}/hostUtils.d.ts +0 -0
  197. /package/dist/{locus-info → types/locus-info}/index.d.ts +0 -0
  198. /package/dist/{locus-info → types/locus-info}/infoUtils.d.ts +0 -0
  199. /package/dist/{locus-info → types/locus-info}/mediaSharesUtils.d.ts +0 -0
  200. /package/dist/{locus-info → types/locus-info}/parser.d.ts +0 -0
  201. /package/dist/{locus-info → types/locus-info}/selfUtils.d.ts +0 -0
  202. /package/dist/{media → types/media}/index.d.ts +0 -0
  203. /package/dist/{media → types/media}/properties.d.ts +0 -0
  204. /package/dist/{media → types/media}/util.d.ts +0 -0
  205. /package/dist/{mediaQualityMetrics → types/mediaQualityMetrics}/config.d.ts +0 -0
  206. /package/dist/{meeting → types/meeting}/in-meeting-actions.d.ts +0 -0
  207. /package/dist/{meeting → types/meeting}/locusMediaRequest.d.ts +0 -0
  208. /package/dist/{meeting → types/meeting}/request.type.d.ts +0 -0
  209. /package/dist/{meeting → types/meeting}/state.d.ts +0 -0
  210. /package/dist/{meeting → types/meeting}/util.d.ts +0 -0
  211. /package/dist/{meeting → types/meeting}/voicea-meeting.d.ts +0 -0
  212. /package/dist/{meeting-info → types/meeting-info}/collection.d.ts +0 -0
  213. /package/dist/{meeting-info → types/meeting-info}/request.d.ts +0 -0
  214. /package/dist/{meetings → types/meetings}/collection.d.ts +0 -0
  215. /package/dist/{meetings → types/meetings}/meetings.types.d.ts +0 -0
  216. /package/dist/{meetings → types/meetings}/request.d.ts +0 -0
  217. /package/dist/{meetings → types/meetings}/util.d.ts +0 -0
  218. /package/dist/{member → types/member}/index.d.ts +0 -0
  219. /package/dist/{member → types/member}/types.d.ts +0 -0
  220. /package/dist/{member → types/member}/util.d.ts +0 -0
  221. /package/dist/{members → types/members}/collection.d.ts +0 -0
  222. /package/dist/{members → types/members}/index.d.ts +0 -0
  223. /package/dist/{members → types/members}/request.d.ts +0 -0
  224. /package/dist/{members → types/members}/types.d.ts +0 -0
  225. /package/dist/{members → types/members}/util.d.ts +0 -0
  226. /package/dist/{metrics → types/metrics}/constants.d.ts +0 -0
  227. /package/dist/{metrics → types/metrics}/index.d.ts +0 -0
  228. /package/dist/{multistream → types/multistream}/receiveSlot.d.ts +0 -0
  229. /package/dist/{multistream → types/multistream}/receiveSlotManager.d.ts +0 -0
  230. /package/dist/{multistream → types/multistream}/remoteMedia.d.ts +0 -0
  231. /package/dist/{networkQualityMonitor → types/networkQualityMonitor}/index.d.ts +0 -0
  232. /package/dist/{personal-meeting-room → types/personal-meeting-room}/index.d.ts +0 -0
  233. /package/dist/{personal-meeting-room → types/personal-meeting-room}/request.d.ts +0 -0
  234. /package/dist/{personal-meeting-room → types/personal-meeting-room}/util.d.ts +0 -0
  235. /package/dist/{reachability → types/reachability}/request.d.ts +0 -0
  236. /package/dist/{reactions → types/reactions}/constants.d.ts +0 -0
  237. /package/dist/{reactions → types/reactions}/reactions.d.ts +0 -0
  238. /package/dist/{reactions → types/reactions}/reactions.type.d.ts +0 -0
  239. /package/dist/{reconnection-manager → types/reconnection-manager}/index.d.ts +0 -0
  240. /package/dist/{recording-controller → types/recording-controller}/enums.d.ts +0 -0
  241. /package/dist/{recording-controller → types/recording-controller}/index.d.ts +0 -0
  242. /package/dist/{recording-controller → types/recording-controller}/util.d.ts +0 -0
  243. /package/dist/{roap → types/roap}/request.d.ts +0 -0
  244. /package/dist/{rtcMetrics → types/rtcMetrics}/constants.d.ts +0 -0
  245. /package/dist/{rtcMetrics → types/rtcMetrics}/index.d.ts +0 -0
  246. /package/dist/{statsAnalyzer → types/statsAnalyzer}/global.d.ts +0 -0
  247. /package/dist/{statsAnalyzer → types/statsAnalyzer}/index.d.ts +0 -0
  248. /package/dist/{statsAnalyzer → types/statsAnalyzer}/mqaUtil.d.ts +0 -0
  249. /package/dist/{transcription → types/transcription}/index.d.ts +0 -0
  250. /package/dist/{webinar → types/webinar}/collection.d.ts +0 -0
  251. /package/dist/{webinar → types/webinar}/index.d.ts +0 -0
  252. /package/test/unit/spec/locus-info/{lib/selfConstant.js → selfConstant.js} +0 -0
@@ -4,7 +4,7 @@ import {Defer} from '@webex/common';
4
4
  import Metrics from '../metrics';
5
5
  import BEHAVIORAL_METRICS from '../metrics/constants';
6
6
  import LoggerProxy from '../common/logs/logger-proxy';
7
- import {ROAP} from '../constants';
7
+ import {ROAP, Enum} from '../constants';
8
8
 
9
9
  import RoapRequest from './request';
10
10
  import Meeting from '../meeting';
@@ -18,6 +18,28 @@ const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
18
18
  // and do the SDP offer with seq=1
19
19
  const TURN_DISCOVERY_SEQ = 0;
20
20
 
21
+ const TurnDiscoverySkipReason = {
22
+ missingHttpResponse: 'missing http response', // when we asked for the TURN discovery response to be in the http response, but it wasn't there
23
+ reachability: 'reachability', // when udp reachability to public clusters is ok, so we don't need TURN (this doens't apply when joinWithMedia() is used)
24
+ alreadyInProgress: 'already in progress', // when we try to start TURN discovery while it's already in progress
25
+ } as const;
26
+
27
+ export type TurnDiscoverySkipReason =
28
+ | Enum<typeof TurnDiscoverySkipReason> // this is a kind of FYI, because in practice typescript will infer the type of TurnDiscoverySkipReason as a string
29
+ | string // used in case of errors, contains the error message
30
+ | undefined; // used when TURN discovery is not skipped
31
+
32
+ export type TurnServerInfo = {
33
+ url: string;
34
+ username: string;
35
+ password: string;
36
+ };
37
+
38
+ export type TurnDiscoveryResult = {
39
+ turnServerInfo?: TurnServerInfo;
40
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason;
41
+ };
42
+
21
43
  /**
22
44
  * Handles the process of finding out TURN server information from Linus.
23
45
  * This is achieved by sending a TURN_DISCOVERY_REQUEST.
@@ -27,11 +49,7 @@ export default class TurnDiscovery {
27
49
 
28
50
  private defer?: Defer; // used for waiting for the response
29
51
 
30
- private turnInfo: {
31
- url: string;
32
- username: string;
33
- password: string;
34
- };
52
+ private turnInfo: TurnServerInfo;
35
53
 
36
54
  private responseTimer?: ReturnType<typeof setTimeout>;
37
55
 
@@ -85,7 +103,8 @@ export default class TurnDiscovery {
85
103
  }
86
104
 
87
105
  /**
88
- * handles TURN_DISCOVERY_RESPONSE roap message
106
+ * Handles TURN_DISCOVERY_RESPONSE roap message. Use it if the roap message comes over the websocket,
107
+ * otherwise use handleTurnDiscoveryHttpResponse() if it comes in the http response.
89
108
  *
90
109
  * @param {Object} roapMessage
91
110
  * @param {string} from string to indicate how we got the response (used just for logging)
@@ -158,18 +177,191 @@ export default class TurnDiscovery {
158
177
  }
159
178
 
160
179
  /**
161
- * handles TURN_DISCOVERY_RESPONSE roap message that came in http response
180
+ * Generates TURN_DISCOVERY_REQUEST roap message. When this method returns a roapMessage, it means that a TURN discovery process has started.
181
+ * It needs be ended by calling handleTurnDiscoveryHttpResponse() once you get a response from the backend. If you don't get any response
182
+ * or want to abort, you need to call abort().
162
183
  *
163
- * @param {Object} roapMessage
164
- * @returns {Promise}
184
+ * @param {Meeting} meeting
185
+ * @param {boolean} isForced
186
+ * @returns {Object}
187
+ */
188
+ public async generateTurnDiscoveryRequestMessage(
189
+ meeting: Meeting,
190
+ isForced: boolean
191
+ ): Promise<{roapMessage?: object; turnDiscoverySkippedReason: TurnDiscoverySkipReason}> {
192
+ if (this.defer) {
193
+ LoggerProxy.logger.warn(
194
+ 'Roap:turnDiscovery#generateTurnDiscoveryRequestMessage --> TURN discovery already in progress'
195
+ );
196
+
197
+ return {
198
+ roapMessage: undefined,
199
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason.alreadyInProgress,
200
+ };
201
+ }
202
+
203
+ let turnDiscoverySkippedReason: TurnDiscoverySkipReason;
204
+
205
+ if (!isForced) {
206
+ turnDiscoverySkippedReason = await this.getSkipReason(meeting);
207
+ }
208
+
209
+ if (turnDiscoverySkippedReason) {
210
+ return {roapMessage: undefined, turnDiscoverySkippedReason};
211
+ }
212
+
213
+ this.defer = new Defer();
214
+
215
+ const roapMessage = {
216
+ messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,
217
+ version: ROAP.ROAP_VERSION,
218
+ seq: TURN_DISCOVERY_SEQ,
219
+ headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
220
+ };
221
+
222
+ LoggerProxy.logger.info(
223
+ 'Roap:turnDiscovery#generateTurnDiscoveryRequestMessage --> generated TURN_DISCOVERY_REQUEST message'
224
+ );
225
+
226
+ return {roapMessage, turnDiscoverySkippedReason: undefined};
227
+ }
228
+
229
+ /**
230
+ * Handles any errors that occur during TURN discovery without re-throwing them.
231
+ *
232
+ * @param {Meeting} meeting
233
+ * @param {Error} error
234
+ * @returns {TurnDiscoveryResult}
235
+ */
236
+ private handleTurnDiscoveryFailure(meeting: Meeting, error: Error): TurnDiscoveryResult {
237
+ // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
238
+ LoggerProxy.logger.info(
239
+ `Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${error}`
240
+ );
241
+
242
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
243
+ correlation_id: meeting.correlationId,
244
+ locus_id: meeting.locusUrl.split('/').pop(),
245
+ reason: error.message,
246
+ stack: error.stack,
247
+ });
248
+
249
+ return {turnServerInfo: undefined, turnDiscoverySkippedReason: `failure: ${error.message}`};
250
+ }
251
+
252
+ /**
253
+ * Handles TURN_DISCOVERY_RESPONSE roap message that came in http response. If the response is not valid,
254
+ * it returns an object with turnServerInfo set to undefined. In that case you need to call abort()
255
+ * to end the TURN discovery process.
256
+ *
257
+ * @param {Meeting} meeting
258
+ * @param {Object|undefined} httpResponse can be undefined to indicate that we didn't get the response
259
+ * @returns {Promise<TurnDiscoveryResult>}
165
260
  * @memberof Roap
166
261
  */
167
- private async handleTurnDiscoveryResponseInHttpResponse(
168
- roapMessage: object
169
- ): Promise<{isOkRequired: boolean}> {
170
- this.handleTurnDiscoveryResponse(roapMessage, 'in http response');
262
+ public async handleTurnDiscoveryHttpResponse(
263
+ meeting: Meeting,
264
+ httpResponse?: object
265
+ ): Promise<TurnDiscoveryResult> {
266
+ if (!this.defer) {
267
+ LoggerProxy.logger.warn(
268
+ 'Roap:turnDiscovery#handleTurnDiscoveryHttpResponse --> unexpected http response, TURN discovery is not in progress'
269
+ );
171
270
 
172
- return this.defer.promise;
271
+ throw new Error(
272
+ 'handleTurnDiscoveryHttpResponse() called before generateTurnDiscoveryRequestMessage()'
273
+ );
274
+ }
275
+
276
+ if (httpResponse === undefined) {
277
+ return {
278
+ turnServerInfo: undefined,
279
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason.missingHttpResponse,
280
+ };
281
+ }
282
+
283
+ try {
284
+ const roapMessage = this.parseHttpTurnDiscoveryResponse(meeting, httpResponse);
285
+
286
+ if (!roapMessage) {
287
+ return {
288
+ turnServerInfo: undefined,
289
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason.missingHttpResponse,
290
+ };
291
+ }
292
+
293
+ this.handleTurnDiscoveryResponse(roapMessage, 'in http response');
294
+
295
+ const {isOkRequired} = await this.defer.promise;
296
+
297
+ if (isOkRequired) {
298
+ await this.sendRoapOK(meeting);
299
+ }
300
+
301
+ this.defer = undefined;
302
+
303
+ LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
304
+
305
+ return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
306
+ } catch (error) {
307
+ this.abort();
308
+
309
+ return this.handleTurnDiscoveryFailure(meeting, error);
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Aborts current TURN discovery. This method needs to be called if you called generateTurnDiscoveryRequestMessage(),
315
+ * but then never got any response from the server.
316
+ * @returns {void}
317
+ */
318
+ public abort() {
319
+ if (this.defer) {
320
+ this.defer.reject(new Error('TURN discovery aborted'));
321
+ this.defer = undefined;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Parses the TURN_DISCOVERY_RESPONSE roap message out of the http response
327
+ * and returns it.
328
+ *
329
+ * @param {Meeting} meeting
330
+ * @param {any} httpResponse
331
+ * @returns {any}
332
+ */
333
+ private parseHttpTurnDiscoveryResponse(
334
+ meeting: Meeting,
335
+ httpResponse: {mediaConnections?: Array<{remoteSdp?: string}>}
336
+ ) {
337
+ let turnDiscoveryResponse;
338
+
339
+ if (httpResponse.mediaConnections?.[0]?.remoteSdp) {
340
+ const remoteSdp = JSON.parse(httpResponse.mediaConnections[0].remoteSdp);
341
+
342
+ if (remoteSdp.roapMessage) {
343
+ // yes, it's misleading that remoteSdp actually contains a TURN discovery response, but that's how the backend works...
344
+ const {seq, messageType, errorType, errorCause, headers} = remoteSdp.roapMessage;
345
+
346
+ turnDiscoveryResponse = {
347
+ seq,
348
+ messageType,
349
+ errorType,
350
+ errorCause,
351
+ headers,
352
+ };
353
+ }
354
+ }
355
+
356
+ if (!turnDiscoveryResponse) {
357
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
358
+ correlationId: meeting.correlationId,
359
+ messageType: 'TURN_DISCOVERY_RESPONSE',
360
+ isMultistream: meeting.isMultistream,
361
+ });
362
+ }
363
+
364
+ return turnDiscoveryResponse;
173
365
  }
174
366
 
175
367
  /**
@@ -181,13 +373,19 @@ export default class TurnDiscovery {
181
373
  * @private
182
374
  * @memberof Roap
183
375
  */
184
- sendRoapTurnDiscoveryRequest(meeting: Meeting, isReconnecting: boolean) {
376
+ private sendRoapTurnDiscoveryRequest(
377
+ meeting: Meeting,
378
+ isReconnecting: boolean
379
+ ): Promise<TurnDiscoveryResult> {
185
380
  if (this.defer) {
186
381
  LoggerProxy.logger.warn(
187
382
  'Roap:turnDiscovery#sendRoapTurnDiscoveryRequest --> already in progress'
188
383
  );
189
384
 
190
- return Promise.resolve();
385
+ return Promise.resolve({
386
+ turnServerInfo: undefined,
387
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason.alreadyInProgress,
388
+ });
191
389
  }
192
390
 
193
391
  this.defer = new Defer();
@@ -215,41 +413,14 @@ export default class TurnDiscovery {
215
413
  // @ts-ignore - because of meeting.webex
216
414
  ipVersion: MeetingUtil.getIpVersion(meeting.webex),
217
415
  })
218
- .then((response) => {
416
+ .then(async (response) => {
219
417
  const {mediaConnections} = response;
220
418
 
221
- let turnDiscoveryResponse;
222
-
223
419
  if (mediaConnections) {
224
420
  meeting.updateMediaConnections(mediaConnections);
225
-
226
- if (mediaConnections[0]?.remoteSdp) {
227
- const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
228
-
229
- if (remoteSdp.roapMessage) {
230
- // yes, it's misleading that remoteSdp actually contains a TURN discovery response, but that's how the backend works...
231
- const {seq, messageType, errorType, errorCause, headers} = remoteSdp.roapMessage;
232
-
233
- turnDiscoveryResponse = {
234
- seq,
235
- messageType,
236
- errorType,
237
- errorCause,
238
- headers,
239
- };
240
- }
241
- }
242
421
  }
243
422
 
244
- if (!turnDiscoveryResponse) {
245
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
246
- correlationId: meeting.correlationId,
247
- messageType: 'TURN_DISCOVERY_RESPONSE',
248
- isMultistream: meeting.isMultistream,
249
- });
250
- }
251
-
252
- return turnDiscoveryResponse;
423
+ return this.handleTurnDiscoveryHttpResponse(meeting, response);
253
424
  });
254
425
  }
255
426
 
@@ -261,7 +432,14 @@ export default class TurnDiscovery {
261
432
  * @returns {Promise}
262
433
  */
263
434
  sendRoapOK(meeting: Meeting) {
264
- LoggerProxy.logger.info('Roap:turnDiscovery#sendRoapOK --> sending OK');
435
+ LoggerProxy.logger.info(
436
+ 'Roap:turnDiscovery#sendRoapOK --> TURN discovery response requires OK, sending it...'
437
+ );
438
+
439
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK, {
440
+ correlation_id: meeting.correlationId,
441
+ locus_id: meeting.locusUrl.split('/').pop(),
442
+ });
265
443
 
266
444
  return this.roapRequest.sendRoap({
267
445
  roapMessage: {
@@ -284,7 +462,7 @@ export default class TurnDiscovery {
284
462
  * @param {Meeting} meeting
285
463
  * @returns {Promise<string>} Promise with empty string if reachability is not skipped or a reason if it is skipped
286
464
  */
287
- private async getSkipReason(meeting: Meeting): Promise<string> {
465
+ private async getSkipReason(meeting: Meeting): Promise<TurnDiscoverySkipReason> {
288
466
  const isAnyPublicClusterReachable =
289
467
  // @ts-ignore - fix type
290
468
  await meeting.webex.meetings.reachability.isAnyPublicClusterReachable();
@@ -294,10 +472,10 @@ export default class TurnDiscovery {
294
472
  'Roap:turnDiscovery#getSkipReason --> reachability has not failed, skipping TURN discovery'
295
473
  );
296
474
 
297
- return 'reachability';
475
+ return TurnDiscoverySkipReason.reachability;
298
476
  }
299
477
 
300
- return '';
478
+ return undefined;
301
479
  }
302
480
 
303
481
  /**
@@ -330,8 +508,12 @@ export default class TurnDiscovery {
330
508
  * @param {Boolean} [isForced]
331
509
  * @returns {Promise}
332
510
  */
333
- async doTurnDiscovery(meeting: Meeting, isReconnecting?: boolean, isForced?: boolean) {
334
- let turnDiscoverySkippedReason: string;
511
+ async doTurnDiscovery(
512
+ meeting: Meeting,
513
+ isReconnecting?: boolean,
514
+ isForced?: boolean
515
+ ): Promise<TurnDiscoveryResult> {
516
+ let turnDiscoverySkippedReason: TurnDiscoverySkipReason;
335
517
 
336
518
  if (!isForced) {
337
519
  turnDiscoverySkippedReason = await this.getSkipReason(meeting);
@@ -345,24 +527,20 @@ export default class TurnDiscovery {
345
527
  }
346
528
 
347
529
  try {
348
- const httpResponse = await this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting);
530
+ const turnDiscoveryResult = await this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting);
531
+
532
+ if (
533
+ turnDiscoveryResult.turnDiscoverySkippedReason !==
534
+ TurnDiscoverySkipReason.missingHttpResponse
535
+ ) {
536
+ return turnDiscoveryResult;
537
+ }
349
538
 
350
539
  // if we haven't got the response over http, we need to wait for it to come over the websocket via Mercury
351
- const {isOkRequired} = httpResponse
352
- ? await this.handleTurnDiscoveryResponseInHttpResponse(httpResponse)
353
- : await this.waitForTurnDiscoveryResponse();
540
+ const {isOkRequired} = await this.waitForTurnDiscoveryResponse();
354
541
 
355
542
  if (isOkRequired) {
356
543
  await this.sendRoapOK(meeting);
357
-
358
- LoggerProxy.logger.info(
359
- 'Roap:turnDiscovery#doTurnDiscovery --> TURN discovery response requires OK'
360
- );
361
-
362
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK, {
363
- correlation_id: meeting.correlationId,
364
- locus_id: meeting.locusUrl.split('/').pop(),
365
- });
366
544
  }
367
545
 
368
546
  this.defer = undefined;
@@ -371,19 +549,7 @@ export default class TurnDiscovery {
371
549
 
372
550
  return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
373
551
  } catch (e) {
374
- // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
375
- LoggerProxy.logger.info(
376
- `Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`
377
- );
378
-
379
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
380
- correlation_id: meeting.correlationId,
381
- locus_id: meeting.locusUrl.split('/').pop(),
382
- reason: e.message,
383
- stack: e.stack,
384
- });
385
-
386
- return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
552
+ return this.handleTurnDiscoveryFailure(meeting, e);
387
553
  }
388
554
  }
389
555
  }
@@ -264,7 +264,7 @@ export class StatsAnalyzer extends EventsScope {
264
264
 
265
265
  // Add stats for individual streams
266
266
  Object.keys(this.statsResults).forEach((mediaType) => {
267
- if (mediaType.includes('audio-send')) {
267
+ if (mediaType.startsWith('audio-send')) {
268
268
  const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
269
269
 
270
270
  getAudioSenderStreamMqa({
@@ -276,7 +276,7 @@ export class StatsAnalyzer extends EventsScope {
276
276
  newMqa.audioTransmit[0].streams.push(audioSenderStream);
277
277
 
278
278
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
279
- } else if (mediaType.includes('audio-share-send')) {
279
+ } else if (mediaType.startsWith('audio-share-send')) {
280
280
  const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
281
281
 
282
282
  getAudioSenderStreamMqa({
@@ -288,7 +288,7 @@ export class StatsAnalyzer extends EventsScope {
288
288
  newMqa.audioTransmit[1].streams.push(audioSenderStream);
289
289
 
290
290
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
291
- } else if (mediaType.includes('audio-recv')) {
291
+ } else if (mediaType.startsWith('audio-recv')) {
292
292
  const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
293
293
 
294
294
  getAudioReceiverStreamMqa({
@@ -300,7 +300,7 @@ export class StatsAnalyzer extends EventsScope {
300
300
  newMqa.audioReceive[0].streams.push(audioReceiverStream);
301
301
 
302
302
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
303
- } else if (mediaType.includes('audio-share-recv')) {
303
+ } else if (mediaType.startsWith('audio-share-recv')) {
304
304
  const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
305
305
 
306
306
  getAudioReceiverStreamMqa({
@@ -312,7 +312,8 @@ export class StatsAnalyzer extends EventsScope {
312
312
  newMqa.audioReceive[1].streams.push(audioReceiverStream);
313
313
 
314
314
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
315
- } else if (mediaType.includes('video-send')) {
315
+ } else if (mediaType.startsWith('video-send-layer')) {
316
+ // We only want the stream-specific stats we get with video-send-layer-0, video-send-layer-1, etc.
316
317
  const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
317
318
 
318
319
  getVideoSenderStreamMqa({
@@ -324,7 +325,7 @@ export class StatsAnalyzer extends EventsScope {
324
325
  newMqa.videoTransmit[0].streams.push(videoSenderStream);
325
326
 
326
327
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
327
- } else if (mediaType.includes('video-share-send')) {
328
+ } else if (mediaType.startsWith('video-share-send')) {
328
329
  const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
329
330
 
330
331
  getVideoSenderStreamMqa({
@@ -336,7 +337,7 @@ export class StatsAnalyzer extends EventsScope {
336
337
  newMqa.videoTransmit[1].streams.push(videoSenderStream);
337
338
 
338
339
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
339
- } else if (mediaType.includes('video-recv')) {
340
+ } else if (mediaType.startsWith('video-recv')) {
340
341
  const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
341
342
 
342
343
  getVideoReceiverStreamMqa({
@@ -348,7 +349,7 @@ export class StatsAnalyzer extends EventsScope {
348
349
  newMqa.videoReceive[0].streams.push(videoReceiverStream);
349
350
 
350
351
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
351
- } else if (mediaType.includes('video-share-recv')) {
352
+ } else if (mediaType.startsWith('video-share-recv')) {
352
353
  const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
353
354
 
354
355
  getVideoReceiverStreamMqa({
@@ -373,9 +374,14 @@ export class StatsAnalyzer extends EventsScope {
373
374
  name: MEDIA_DEVICES.MICROPHONE,
374
375
  });
375
376
  }
376
- if (this.statsResults['video-send']) {
377
+
378
+ const existingVideoSender = Object.keys(this.statsResults).find((item) =>
379
+ item.includes('video-send')
380
+ );
381
+
382
+ if (existingVideoSender) {
377
383
  newMqa.intervalMetadata.peripherals.push({
378
- information: this.statsResults['video-send'].trackLabel || _UNKNOWN_,
384
+ information: this.statsResults[existingVideoSender].trackLabel || _UNKNOWN_,
379
385
  name: MEDIA_DEVICES.CAMERA,
380
386
  });
381
387
  }
@@ -552,9 +558,21 @@ export class StatsAnalyzer extends EventsScope {
552
558
  }
553
559
  });
554
560
 
561
+ let videoSenderIndex = 0;
555
562
  statsItem.report.forEach((result) => {
556
563
  if (types.includes(result.type)) {
557
- this.parseGetStatsResult(result, type, isSender);
564
+ // if the video sender has multiple streams in the report, it is a new stream object.
565
+ if (type === 'video-send' && result.type === 'outbound-rtp') {
566
+ const newType = `video-send-layer-${videoSenderIndex}`;
567
+ this.parseGetStatsResult(result, newType, isSender);
568
+ videoSenderIndex += 1;
569
+
570
+ this.statsResults[newType].direction = statsItem.currentDirection;
571
+ this.statsResults[newType].trackLabel = statsItem.localTrackLabel;
572
+ this.statsResults[newType].csi = statsItem.csi;
573
+ } else {
574
+ this.parseGetStatsResult(result, type, isSender);
575
+ }
558
576
  }
559
577
  });
560
578
 
@@ -664,12 +682,23 @@ export class StatsAnalyzer extends EventsScope {
664
682
  const getCurrentStatsTotals = (keyPrefix: string, value: string): number =>
665
683
  Object.keys(this.statsResults)
666
684
  .filter((key) => key.startsWith(keyPrefix))
667
- .reduce((prev, cur) => prev + (this.statsResults[cur]?.recv[value] || 0), 0);
685
+ .reduce(
686
+ (prev, cur) =>
687
+ prev +
688
+ (this.statsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] || 0),
689
+ 0
690
+ );
668
691
 
669
692
  const getPreviousStatsTotals = (keyPrefix: string, value: string): number =>
670
693
  Object.keys(this.statsResults)
671
694
  .filter((key) => key.startsWith(keyPrefix))
672
- .reduce((prev, cur) => prev + (this.lastStatsResults[cur]?.recv[value] || 0), 0);
695
+ .reduce(
696
+ (prev, cur) =>
697
+ prev +
698
+ (this.lastStatsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] ||
699
+ 0),
700
+ 0
701
+ );
673
702
 
674
703
  // Audio Transmit
675
704
  if (this.lastStatsResults['audio-send']) {
@@ -731,47 +760,54 @@ export class StatsAnalyzer extends EventsScope {
731
760
  false
732
761
  );
733
762
 
763
+ const currentTotalPacketsSent = getCurrentStatsTotals('video-send', 'totalPacketsSent');
764
+ const previousTotalPacketsSent = getPreviousStatsTotals('video-send', 'totalPacketsSent');
765
+
766
+ const currentFramesEncoded = getCurrentStatsTotals('video-send', 'framesEncoded');
767
+ const previousFramesEncoded = getPreviousStatsTotals('video-send', 'framesEncoded');
768
+
769
+ const currentFramesSent = getCurrentStatsTotals('video-send', 'framesSent');
770
+ const previousFramesSent = getPreviousStatsTotals('video-send', 'framesSent');
771
+
772
+ const doesVideoSendExist = Object.keys(this.lastStatsResults).some((item) =>
773
+ item.includes('video-send')
774
+ );
775
+
734
776
  // Video Transmit
735
- if (this.lastStatsResults['video-send']) {
777
+ if (doesVideoSendExist) {
736
778
  // compare video stats sent
737
- const currentStats = this.statsResults['video-send'].send;
738
- const previousStats = this.lastStatsResults['video-send'].send;
739
779
 
740
780
  if (
741
781
  this.meetingMediaStatus.expected.sendVideo &&
742
- (currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
743
- currentStats.totalPacketsSent === 0)
782
+ (currentTotalPacketsSent === previousTotalPacketsSent || currentTotalPacketsSent === 0)
744
783
  ) {
745
784
  LoggerProxy.logger.info(
746
785
  `StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
747
- currentStats.totalPacketsSent
786
+ currentTotalPacketsSent
748
787
  );
749
788
  } else {
750
789
  if (
751
790
  this.meetingMediaStatus.expected.sendVideo &&
752
- (currentStats.framesEncoded === previousStats.framesEncoded ||
753
- currentStats.framesEncoded === 0)
791
+ (currentFramesEncoded === previousFramesEncoded || currentFramesEncoded === 0)
754
792
  ) {
755
793
  LoggerProxy.logger.info(
756
794
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
757
- currentStats.framesEncoded
795
+ currentFramesEncoded
758
796
  );
759
797
  }
760
798
 
761
799
  if (
762
800
  this.meetingMediaStatus.expected.sendVideo &&
763
- (this.statsResults['video-send'].send.framesSent ===
764
- this.lastStatsResults['video-send'].send.framesSent ||
765
- this.statsResults['video-send'].send.framesSent === 0)
801
+ (currentFramesSent === previousFramesSent || currentFramesSent === 0)
766
802
  ) {
767
803
  LoggerProxy.logger.info(
768
804
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
769
- this.statsResults['video-send'].send.framesSent
805
+ currentFramesSent
770
806
  );
771
807
  }
772
808
  }
773
809
 
774
- this.emitStartStopEvents('video', previousStats.framesSent, currentStats.framesSent, true);
810
+ this.emitStartStopEvents('video', previousFramesSent, currentFramesSent, true);
775
811
  }
776
812
 
777
813
  // Video Receive