@webex/plugin-meetings 3.0.0-stream-classes.4 → 3.0.0-test.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/no-meeting-info.js +51 -0
  5. package/dist/common/errors/no-meeting-info.js.map +1 -0
  6. package/dist/common/errors/reclaim-host-role-errors.js +158 -0
  7. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  8. package/dist/common/errors/webex-errors.js +23 -3
  9. package/dist/common/errors/webex-errors.js.map +1 -1
  10. package/dist/common/logs/request.js +5 -1
  11. package/dist/common/logs/request.js.map +1 -1
  12. package/dist/config.js +1 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants.js +69 -9
  15. package/dist/constants.js.map +1 -1
  16. package/dist/index.js +11 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/interceptors/index.js +15 -0
  19. package/dist/interceptors/index.js.map +1 -0
  20. package/dist/interceptors/locusRetry.js +93 -0
  21. package/dist/interceptors/locusRetry.js.map +1 -0
  22. package/dist/interpretation/index.js +16 -2
  23. package/dist/interpretation/index.js.map +1 -1
  24. package/dist/interpretation/siLanguage.js +1 -1
  25. package/dist/locus-info/index.js +40 -11
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/locus-info/mediaSharesUtils.js +15 -1
  28. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  29. package/dist/locus-info/parser.js +42 -21
  30. package/dist/locus-info/parser.js.map +1 -1
  31. package/dist/media/index.js +10 -6
  32. package/dist/media/index.js.map +1 -1
  33. package/dist/media/properties.js +13 -3
  34. package/dist/media/properties.js.map +1 -1
  35. package/dist/mediaQualityMetrics/config.js +135 -330
  36. package/dist/mediaQualityMetrics/config.js.map +1 -1
  37. package/dist/meeting/in-meeting-actions.js +4 -0
  38. package/dist/meeting/in-meeting-actions.js.map +1 -1
  39. package/dist/meeting/index.js +2187 -1074
  40. package/dist/meeting/index.js.map +1 -1
  41. package/dist/meeting/muteState.js +37 -25
  42. package/dist/meeting/muteState.js.map +1 -1
  43. package/dist/meeting/request.js +34 -19
  44. package/dist/meeting/request.js.map +1 -1
  45. package/dist/meeting/util.js +71 -0
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meeting-info/index.js +48 -23
  48. package/dist/meeting-info/index.js.map +1 -1
  49. package/dist/meeting-info/meeting-info-v2.js +25 -4
  50. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  51. package/dist/meeting-info/utilv2.js +1 -1
  52. package/dist/meeting-info/utilv2.js.map +1 -1
  53. package/dist/meetings/collection.js +17 -0
  54. package/dist/meetings/collection.js.map +1 -1
  55. package/dist/meetings/index.js +142 -57
  56. package/dist/meetings/index.js.map +1 -1
  57. package/dist/meetings/util.js +2 -6
  58. package/dist/meetings/util.js.map +1 -1
  59. package/dist/member/index.js +9 -0
  60. package/dist/member/index.js.map +1 -1
  61. package/dist/member/util.js +11 -0
  62. package/dist/member/util.js.map +1 -1
  63. package/dist/members/index.js +17 -1
  64. package/dist/members/index.js.map +1 -1
  65. package/dist/members/types.js.map +1 -1
  66. package/dist/members/util.js +15 -4
  67. package/dist/members/util.js.map +1 -1
  68. package/dist/metrics/constants.js +15 -1
  69. package/dist/metrics/constants.js.map +1 -1
  70. package/dist/multistream/mediaRequestManager.js +1 -1
  71. package/dist/multistream/mediaRequestManager.js.map +1 -1
  72. package/dist/multistream/remoteMediaGroup.js +16 -2
  73. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  74. package/dist/multistream/remoteMediaManager.js +222 -73
  75. package/dist/multistream/remoteMediaManager.js.map +1 -1
  76. package/dist/multistream/sendSlotManager.js +22 -0
  77. package/dist/multistream/sendSlotManager.js.map +1 -1
  78. package/dist/reachability/clusterReachability.js +356 -0
  79. package/dist/reachability/clusterReachability.js.map +1 -0
  80. package/dist/reachability/index.js +262 -432
  81. package/dist/reachability/index.js.map +1 -1
  82. package/dist/reachability/request.js +1 -1
  83. package/dist/reachability/request.js.map +1 -1
  84. package/dist/reachability/util.js +29 -0
  85. package/dist/reachability/util.js.map +1 -0
  86. package/dist/reconnection-manager/index.js +113 -96
  87. package/dist/reconnection-manager/index.js.map +1 -1
  88. package/dist/roap/index.js +57 -25
  89. package/dist/roap/index.js.map +1 -1
  90. package/dist/roap/request.js +5 -13
  91. package/dist/roap/request.js.map +1 -1
  92. package/dist/roap/turnDiscovery.js +173 -81
  93. package/dist/roap/turnDiscovery.js.map +1 -1
  94. package/dist/rtcMetrics/index.js +68 -6
  95. package/dist/rtcMetrics/index.js.map +1 -1
  96. package/dist/statsAnalyzer/index.js +338 -289
  97. package/dist/statsAnalyzer/index.js.map +1 -1
  98. package/dist/statsAnalyzer/mqaUtil.js +296 -156
  99. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  100. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  101. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  102. package/dist/types/common/errors/webex-errors.d.ts +13 -1
  103. package/dist/types/common/logs/request.d.ts +2 -0
  104. package/dist/types/config.d.ts +1 -1
  105. package/dist/types/constants.d.ts +66 -13
  106. package/dist/types/index.d.ts +1 -1
  107. package/dist/types/interceptors/index.d.ts +2 -0
  108. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  109. package/dist/types/locus-info/index.d.ts +1 -1
  110. package/dist/types/locus-info/parser.d.ts +3 -2
  111. package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
  112. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  113. package/dist/types/meeting/index.d.ts +285 -34
  114. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  115. package/dist/types/meeting/muteState.d.ts +2 -8
  116. package/dist/types/meeting/request.d.ts +4 -1
  117. package/dist/types/meeting/util.d.ts +25 -1
  118. package/dist/types/meeting-info/index.d.ts +7 -0
  119. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  120. package/dist/types/meetings/collection.d.ts +9 -0
  121. package/dist/types/meetings/index.d.ts +42 -14
  122. package/dist/types/member/index.d.ts +1 -0
  123. package/dist/types/members/types.d.ts +1 -0
  124. package/dist/types/members/util.d.ts +5 -0
  125. package/dist/types/metrics/constants.d.ts +15 -0
  126. package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
  127. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
  128. package/dist/types/multistream/remoteMediaManager.d.ts +25 -1
  129. package/dist/types/multistream/sendSlotManager.d.ts +9 -0
  130. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  131. package/dist/types/reachability/index.d.ts +59 -112
  132. package/dist/types/reachability/request.d.ts +1 -1
  133. package/dist/types/reachability/util.d.ts +8 -0
  134. package/dist/types/reconnection-manager/index.d.ts +10 -0
  135. package/dist/types/roap/index.d.ts +2 -1
  136. package/dist/types/roap/request.d.ts +2 -1
  137. package/dist/types/roap/turnDiscovery.d.ts +21 -4
  138. package/dist/types/rtcMetrics/index.d.ts +15 -1
  139. package/dist/types/statsAnalyzer/index.d.ts +28 -11
  140. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  141. package/dist/types/webinar/collection.d.ts +16 -0
  142. package/dist/types/webinar/index.d.ts +5 -0
  143. package/dist/webinar/collection.js +44 -0
  144. package/dist/webinar/collection.js.map +1 -0
  145. package/dist/webinar/index.js +69 -0
  146. package/dist/webinar/index.js.map +1 -0
  147. package/package.json +3 -2
  148. package/src/common/errors/no-meeting-info.ts +24 -0
  149. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  150. package/src/common/errors/webex-errors.ts +19 -2
  151. package/src/common/logs/request.ts +5 -1
  152. package/src/config.ts +1 -1
  153. package/src/constants.ts +71 -6
  154. package/src/index.ts +5 -0
  155. package/src/interceptors/index.ts +3 -0
  156. package/src/interceptors/locusRetry.ts +67 -0
  157. package/src/interpretation/index.ts +18 -1
  158. package/src/locus-info/index.ts +52 -16
  159. package/src/locus-info/mediaSharesUtils.ts +16 -0
  160. package/src/locus-info/parser.ts +47 -21
  161. package/src/media/index.ts +8 -6
  162. package/src/media/properties.ts +17 -2
  163. package/src/mediaQualityMetrics/config.ts +103 -238
  164. package/src/meeting/in-meeting-actions.ts +8 -0
  165. package/src/meeting/index.ts +1510 -529
  166. package/src/meeting/muteState.ts +34 -20
  167. package/src/meeting/request.ts +19 -1
  168. package/src/meeting/util.ts +97 -0
  169. package/src/meeting-info/index.ts +47 -20
  170. package/src/meeting-info/meeting-info-v2.ts +27 -5
  171. package/src/meeting-info/utilv2.ts +1 -1
  172. package/src/meetings/collection.ts +13 -0
  173. package/src/meetings/index.ts +112 -31
  174. package/src/meetings/util.ts +2 -8
  175. package/src/member/index.ts +9 -0
  176. package/src/member/util.ts +14 -0
  177. package/src/members/index.ts +29 -2
  178. package/src/members/types.ts +1 -0
  179. package/src/members/util.ts +15 -1
  180. package/src/metrics/constants.ts +14 -0
  181. package/src/multistream/mediaRequestManager.ts +4 -1
  182. package/src/multistream/remoteMediaGroup.ts +19 -0
  183. package/src/multistream/remoteMediaManager.ts +141 -18
  184. package/src/multistream/sendSlotManager.ts +29 -0
  185. package/src/reachability/clusterReachability.ts +320 -0
  186. package/src/reachability/index.ts +221 -382
  187. package/src/reachability/request.ts +1 -1
  188. package/src/reachability/util.ts +24 -0
  189. package/src/reconnection-manager/index.ts +87 -83
  190. package/src/roap/index.ts +60 -24
  191. package/src/roap/request.ts +3 -16
  192. package/src/roap/turnDiscovery.ts +112 -39
  193. package/src/rtcMetrics/index.ts +71 -5
  194. package/src/statsAnalyzer/index.ts +430 -427
  195. package/src/statsAnalyzer/mqaUtil.ts +317 -168
  196. package/src/webinar/collection.ts +31 -0
  197. package/src/webinar/index.ts +62 -0
  198. package/test/integration/spec/converged-space-meetings.js +7 -7
  199. package/test/integration/spec/journey.js +86 -104
  200. package/test/integration/spec/space-meeting.js +9 -9
  201. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  202. package/test/unit/spec/interpretation/index.ts +36 -3
  203. package/test/unit/spec/locus-info/index.js +205 -12
  204. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  205. package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
  206. package/test/unit/spec/locus-info/parser.js +54 -13
  207. package/test/unit/spec/media/index.ts +20 -4
  208. package/test/unit/spec/media/properties.ts +2 -2
  209. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  210. package/test/unit/spec/meeting/index.js +4027 -1075
  211. package/test/unit/spec/meeting/muteState.js +219 -67
  212. package/test/unit/spec/meeting/request.js +63 -12
  213. package/test/unit/spec/meeting/utils.js +93 -0
  214. package/test/unit/spec/meeting-info/index.js +180 -61
  215. package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
  216. package/test/unit/spec/meetings/collection.js +12 -0
  217. package/test/unit/spec/meetings/index.js +619 -206
  218. package/test/unit/spec/meetings/utils.js +35 -12
  219. package/test/unit/spec/member/index.js +8 -7
  220. package/test/unit/spec/member/util.js +32 -0
  221. package/test/unit/spec/members/index.js +130 -17
  222. package/test/unit/spec/members/utils.js +26 -0
  223. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  224. package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
  225. package/test/unit/spec/multistream/remoteMediaManager.ts +210 -3
  226. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  227. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  228. package/test/unit/spec/reachability/index.ts +505 -135
  229. package/test/unit/spec/reachability/util.ts +40 -0
  230. package/test/unit/spec/reconnection-manager/index.js +74 -17
  231. package/test/unit/spec/roap/index.ts +181 -61
  232. package/test/unit/spec/roap/request.ts +27 -3
  233. package/test/unit/spec/roap/turnDiscovery.ts +362 -101
  234. package/test/unit/spec/rtcMetrics/index.ts +57 -3
  235. package/test/unit/spec/stats-analyzer/index.js +1225 -12
  236. package/test/unit/spec/webinar/collection.ts +13 -0
  237. package/test/unit/spec/webinar/index.ts +60 -0
  238. package/test/utils/integrationTestUtils.js +4 -4
  239. package/test/utils/webex-test-users.js +12 -4
@@ -49,7 +49,7 @@ class ReachabilityRequest {
49
49
  const {clusters, joinCookie} = res.body;
50
50
 
51
51
  Object.keys(clusters).forEach((key) => {
52
- clusters[key].isVideoMesh = res.body.clusterClasses?.hybridMedia?.includes(key);
52
+ clusters[key].isVideoMesh = !!res.body.clusterClasses?.hybridMedia?.includes(key);
53
53
  });
54
54
 
55
55
  LoggerProxy.logger.log(
@@ -0,0 +1,24 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+ /**
3
+ * Converts a stun url to a turn url
4
+ *
5
+ * @param {string} stunUrl url of a stun server
6
+ * @param {'tcp'|'udp'} protocol what protocol to use for the turn server
7
+ * @returns {string} url of a turn server
8
+ */
9
+ export function convertStunUrlToTurn(stunUrl: string, protocol: 'udp' | 'tcp') {
10
+ // stunUrl looks like this: "stun:external-media91.public.wjfkm-a-10.prod.infra.webex.com:5004"
11
+ // and we need it to be like this: "turn:external-media91.public.wjfkm-a-10.prod.infra.webex.com:5004?transport=tcp"
12
+ const url = new URL(stunUrl);
13
+
14
+ if (url.protocol !== 'stun:') {
15
+ throw new Error(`Not a STUN URL: ${stunUrl}`);
16
+ }
17
+
18
+ url.protocol = 'turn:';
19
+ if (protocol === 'tcp') {
20
+ url.searchParams.append('transport', 'tcp');
21
+ }
22
+
23
+ return url.toString();
24
+ }
@@ -14,13 +14,14 @@ import {
14
14
  _CALL_,
15
15
  _LEFT_,
16
16
  _ID_,
17
+ RECONNECTION_STATE,
17
18
  } from '../constants';
18
19
  import BEHAVIORAL_METRICS from '../metrics/constants';
19
- import ReconnectionError from '../common/errors/reconnection';
20
20
  import ReconnectInProgress from '../common/errors/reconnection-in-progress';
21
21
  import Metrics from '../metrics';
22
22
  import Meeting from '../meeting';
23
23
  import {MediaRequestManager} from '../multistream/mediaRequestManager';
24
+ import ReconnectionError from '../common/errors/reconnection';
24
25
 
25
26
  /**
26
27
  * Used to indicate that the reconnect logic needs to be retried.
@@ -96,7 +97,7 @@ export default class ReconnectionManager {
96
97
 
97
98
  /**
98
99
  * @instance
99
- * @type {String}
100
+ * @type {RECONNECTION_STATE}
100
101
  * @private
101
102
  * @memberof ReconnectionManager
102
103
  */
@@ -227,7 +228,6 @@ export default class ReconnectionManager {
227
228
  */
228
229
  public cleanUp() {
229
230
  this.reset();
230
- this.meeting = null;
231
231
  }
232
232
 
233
233
  /**
@@ -265,6 +265,18 @@ export default class ReconnectionManager {
265
265
  return this.status === RECONNECTION.STATE.IN_PROGRESS;
266
266
  }
267
267
 
268
+ /**
269
+ * Sets the reconnection status
270
+ *
271
+ * @public
272
+ * @param {RECONNECTION_STATE} status
273
+ * @memberof ReconnectionManager
274
+ * @returns {undefined}
275
+ */
276
+ public setStatus(status: RECONNECTION_STATE) {
277
+ this.status = status;
278
+ }
279
+
268
280
  /**
269
281
  * @returns {Boolean}
270
282
  * @throws {ReconnectionError}
@@ -337,73 +349,66 @@ export default class ReconnectionManager {
337
349
  });
338
350
  }
339
351
 
340
- return this.executeReconnection({networkDisconnect})
341
- .then(() => {
342
- LoggerProxy.logger.info('ReconnectionManager:index#reconnect --> Reconnection successful.');
343
- LoggerProxy.logger.info(
344
- 'ReconnectionManager:index#reconnect --> Sending reconnect success metric.'
345
- );
352
+ try {
353
+ await this.webex.meetings.startReachability();
354
+ } catch (err) {
355
+ LoggerProxy.logger.info(
356
+ 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
357
+ err
358
+ );
359
+ }
346
360
 
347
- // @ts-ignore
348
- this.webex.internal.newMetrics.submitClientEvent({
349
- name: 'client.media.recovered',
350
- payload: {
351
- recoveredBy: 'new',
352
- },
353
- options: {
354
- meetingId: this.meeting.id,
355
- },
356
- });
357
- })
358
- .catch((reconnectError) => {
359
- if (reconnectError instanceof NeedsRetryError) {
360
- LoggerProxy.logger.info(
361
- 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
362
- );
363
- // Reset our reconnect status since we are looping back to the beginning
364
- this.status = RECONNECTION.STATE.DEFAULT_STATUS;
365
-
366
- // This is a network retry, so we should not log START metrics again
367
- return this.reconnect({networkDisconnect: true, networkRetry: true});
368
- }
361
+ try {
362
+ const media = await this.executeReconnection({networkDisconnect});
369
363
 
370
- // Reconnect has failed
371
- LoggerProxy.logger.error(
372
- 'ReconnectionManager:index#reconnect --> Reconnection failed.',
373
- reconnectError.message
374
- );
364
+ return media;
365
+ } catch (reconnectError) {
366
+ if (reconnectError instanceof NeedsRetryError) {
375
367
  LoggerProxy.logger.info(
376
- 'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
368
+ 'ReconnectionManager:index#reconnect --> Reconnection not successful, retrying.'
377
369
  );
370
+ // Reset our reconnect status since we are looping back to the beginning
371
+ this.status = RECONNECTION.STATE.DEFAULT_STATUS;
378
372
 
379
- // @ts-ignore
380
- this.webex.internal.newMetrics.submitClientEvent({
381
- name: 'client.call.aborted',
382
- payload: {
383
- errors: [
384
- {
385
- category: 'expected',
386
- errorCode: 2008,
387
- fatal: true,
388
- name: 'media-engine',
389
- shownToUser: false,
390
- },
391
- ],
392
- },
393
- options: {
394
- meetingId: this.meeting.id,
395
- },
396
- });
397
- if (reconnectError instanceof NeedsRejoinError) {
398
- // send call aborded event with catogery as expected as we are trying to rejoin
373
+ // This is a network retry, so we should not log START metrics again
374
+ return this.reconnect({networkDisconnect: true, networkRetry: true});
375
+ }
399
376
 
400
- if (this.autoRejoinEnabled) {
401
- return this.rejoinMeeting(reconnectError.wasSharing);
402
- }
403
- }
377
+ // Reconnect has failed
378
+ LoggerProxy.logger.error(
379
+ 'ReconnectionManager:index#reconnect --> Reconnection failed.',
380
+ reconnectError.message
381
+ );
382
+ LoggerProxy.logger.info(
383
+ 'ReconnectionManager:index#reconnect --> Sending reconnect abort metric.'
384
+ );
404
385
 
405
- throw reconnectError;
386
+ // send call aborted event with catogery as expected as we are trying to rejoin
387
+ // @ts-ignore
388
+ this.webex.internal.newMetrics.submitClientEvent({
389
+ name: 'client.call.aborted',
390
+ payload: {
391
+ errors: [
392
+ {
393
+ category: 'expected',
394
+ errorCode: 2008,
395
+ fatal: true,
396
+ name: 'media-engine',
397
+ shownToUser: false,
398
+ },
399
+ ],
400
+ },
401
+ options: {
402
+ meetingId: this.meeting.id,
403
+ },
406
404
  });
405
+
406
+ if (reconnectError instanceof NeedsRejoinError && this.autoRejoinEnabled) {
407
+ return this.rejoinMeeting(reconnectError.wasSharing);
408
+ }
409
+
410
+ throw reconnectError;
411
+ }
407
412
  }
408
413
 
409
414
  /**
@@ -443,19 +448,17 @@ export default class ReconnectionManager {
443
448
  }
444
449
  }
445
450
 
446
- if (!this.webex.credentials.isUnverifiedGuest) {
447
- try {
448
- LoggerProxy.logger.info(
449
- 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
450
- );
451
- await this.webex.meetings.syncMeetings();
452
- } catch (syncError) {
453
- LoggerProxy.logger.info(
454
- 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
455
- syncError
456
- );
457
- throw new NeedsRetryError(syncError);
458
- }
451
+ try {
452
+ LoggerProxy.logger.info(
453
+ 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
454
+ );
455
+ await this.webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
456
+ } catch (syncError) {
457
+ LoggerProxy.logger.info(
458
+ 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
459
+ syncError
460
+ );
461
+ throw new NeedsRetryError(syncError);
459
462
  }
460
463
 
461
464
  // TODO: try to improve this logic as the reconnection manager saves the instance of deleted meeting object
@@ -485,14 +488,13 @@ export default class ReconnectionManager {
485
488
  const media = await this.reconnectMedia();
486
489
 
487
490
  LoggerProxy.logger.log(
488
- 'ReconnectionManager:index#executeReconnection --> Media reestablished'
491
+ 'ReconnectionManager:index#executeReconnection --> webRTC media connection renewed and local sdp offer sent'
489
492
  );
490
- this.status = RECONNECTION.STATE.COMPLETE;
491
493
 
492
494
  return media;
493
495
  } catch (error) {
494
496
  LoggerProxy.logger.error(
495
- 'ReconnectionManager:index#executeReconnection --> Media reestablishment failed'
497
+ 'ReconnectionManager:index#executeReconnection --> failed to renew webRTC media connection or initiate offer'
496
498
  );
497
499
  this.status = RECONNECTION.STATE.FAILURE;
498
500
 
@@ -559,12 +561,10 @@ export default class ReconnectionManager {
559
561
  * @memberof ReconnectionManager
560
562
  */
561
563
  async reconnectMedia() {
562
- LoggerProxy.logger.log(
563
- 'ReconnectionManager:index#reconnectMedia --> Begin reestablishment of media'
564
- );
564
+ LoggerProxy.logger.log('ReconnectionManager:index#reconnectMedia --> do turn discovery');
565
565
 
566
- // do the TURN server discovery again since the TURN server might change
567
- const turnServerResult = await this.meeting.roap.doTurnDiscovery(this.meeting, true);
566
+ // do the TURN server discovery again and ignore reachability results since the TURN server might change
567
+ const turnServerResult = await this.meeting.roap.doTurnDiscovery(this.meeting, true, true);
568
568
 
569
569
  const iceServers = [];
570
570
 
@@ -576,6 +576,10 @@ export default class ReconnectionManager {
576
576
  });
577
577
  }
578
578
 
579
+ LoggerProxy.logger.log(
580
+ 'ReconnectionManager:index#reconnectMedia --> renew webRTC media connection and send local sdp offer'
581
+ );
582
+
579
583
  await this.meeting.mediaProperties.webrtcMediaConnection.reconnect(iceServers);
580
584
 
581
585
  // resend media requests
package/src/roap/index.ts CHANGED
@@ -8,6 +8,8 @@ import RoapRequest from './request';
8
8
  import TurnDiscovery from './turnDiscovery';
9
9
  import Meeting from '../meeting';
10
10
  import MeetingUtil from '../meeting/util';
11
+ import Metrics from '../metrics';
12
+ import BEHAVIORAL_METRICS from '../metrics/constants';
11
13
 
12
14
  /**
13
15
  * Roap options
@@ -179,39 +181,72 @@ export default class Roap extends StatelessWebexPlugin {
179
181
  * @memberof Roap
180
182
  */
181
183
  sendRoapMediaRequest(options: any) {
182
- const {meeting, seq, sdp, reconnect, tieBreaker} = options;
184
+ const {meeting, seq, sdp, tieBreaker} = options;
183
185
  const roapMessage = {
184
186
  messageType: ROAP.ROAP_TYPES.OFFER,
185
187
  sdps: [sdp],
186
188
  version: ROAP.ROAP_VERSION,
187
189
  seq,
188
190
  tieBreaker,
191
+ headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
189
192
  };
190
193
 
191
- // When reconnecting, it's important that the first roap message being sent out has empty media id.
192
- // Normally this is the roap offer, but when TURN discovery is enabled,
193
- // then this is the TURN discovery request message
194
- return this.turnDiscovery.isSkipped(meeting).then((isTurnDiscoverySkipped) => {
195
- const sendEmptyMediaId = reconnect && isTurnDiscoverySkipped;
194
+ // The only time we want to send an empty media id is when we are reconnecting, because this way we tell Locus
195
+ // that it needs to create a new confluence, but when reconnecting we always send TURN_DISCOVERY_REQUEST first,
196
+ // so we don't need to ever send an empty media id here
197
+ const sendEmptyMediaId = false;
196
198
 
197
- return this.roapRequest
198
- .sendRoap({
199
- roapMessage,
200
- locusSelfUrl: meeting.selfUrl,
201
- mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
202
- meetingId: meeting.id,
203
- preferTranscoding: !meeting.isMultistream,
204
- locusMediaRequest: meeting.locusMediaRequest,
205
- ipVersion: MeetingUtil.getIpVersion(meeting.webex),
206
- })
207
- .then(({locus, mediaConnections}) => {
208
- if (mediaConnections) {
209
- meeting.updateMediaConnections(mediaConnections);
199
+ return this.roapRequest
200
+ .sendRoap({
201
+ roapMessage,
202
+ locusSelfUrl: meeting.selfUrl,
203
+ mediaId: sendEmptyMediaId ? '' : meeting.mediaId,
204
+ meetingId: meeting.id,
205
+ preferTranscoding: !meeting.isMultistream,
206
+ locusMediaRequest: meeting.locusMediaRequest,
207
+ ipVersion: MeetingUtil.getIpVersion(meeting.webex),
208
+ })
209
+ .then(({locus, mediaConnections}) => {
210
+ if (mediaConnections) {
211
+ meeting.updateMediaConnections(mediaConnections);
212
+ }
213
+
214
+ let roapAnswer;
215
+
216
+ if (mediaConnections?.[0]?.remoteSdp) {
217
+ const remoteSdp = JSON.parse(mediaConnections[0].remoteSdp);
218
+
219
+ if (remoteSdp.roapMessage) {
220
+ const {
221
+ seq: answerSeq,
222
+ messageType,
223
+ sdps,
224
+ errorType,
225
+ errorCause,
226
+ headers,
227
+ } = remoteSdp.roapMessage;
228
+
229
+ roapAnswer = {
230
+ seq: answerSeq,
231
+ messageType,
232
+ sdp: sdps[0],
233
+ errorType,
234
+ errorCause,
235
+ headers,
236
+ };
210
237
  }
238
+ }
211
239
 
212
- return locus;
213
- });
214
- });
240
+ if (!roapAnswer) {
241
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING, {
242
+ correlationId: meeting.correlationId,
243
+ messageType: 'ANSWER',
244
+ isMultistream: meeting.isMultistream,
245
+ });
246
+ }
247
+
248
+ return {locus, roapAnswer};
249
+ });
215
250
  }
216
251
 
217
252
  /**
@@ -222,9 +257,10 @@ export default class Roap extends StatelessWebexPlugin {
222
257
  * @param {Meeting} meeting
223
258
  * @param {Boolean} isReconnecting should be set to true if this is a new
224
259
  * media connection just after a reconnection
260
+ * @param {Boolean} [isForced]
225
261
  * @returns {Promise}
226
262
  */
227
- doTurnDiscovery(meeting: Meeting, isReconnecting: boolean) {
228
- return this.turnDiscovery.doTurnDiscovery(meeting, isReconnecting);
263
+ doTurnDiscovery(meeting: Meeting, isReconnecting: boolean, isForced?: boolean) {
264
+ return this.turnDiscovery.doTurnDiscovery(meeting, isReconnecting, isForced);
229
265
  }
230
266
  }
@@ -18,23 +18,10 @@ export default class RoapRequest extends StatelessWebexPlugin {
18
18
  let joinCookie;
19
19
 
20
20
  // @ts-ignore
21
- const reachabilityData = await this.webex.boundedStorage
22
- .get(REACHABILITY.namespace, REACHABILITY.localStorageResult)
23
- .catch(() => {});
24
-
25
- if (reachabilityData) {
26
- try {
27
- const reachabilityResult = JSON.parse(reachabilityData);
21
+ const reachabilityResult = await this.webex.meetings.reachability.getReachabilityResults();
28
22
 
29
- /* istanbul ignore else */
30
- if (reachabilityResult && Object.keys(reachabilityResult).length) {
31
- localSdp.reachability = reachabilityResult;
32
- }
33
- } catch (e) {
34
- LoggerProxy.logger.error(
35
- `Roap:request#attachReachabilityData --> Error in parsing reachability data: ${e}`
36
- );
37
- }
23
+ if (reachabilityResult && Object.keys(reachabilityResult).length) {
24
+ localSdp.reachability = reachabilityResult;
38
25
  }
39
26
 
40
27
  // @ts-ignore
@@ -56,7 +56,7 @@ export default class TurnDiscovery {
56
56
  * @private
57
57
  * @memberof Roap
58
58
  */
59
- private waitForTurnDiscoveryResponse() {
59
+ private waitForTurnDiscoveryResponse(): Promise<{isOkRequired: boolean}> {
60
60
  if (!this.defer) {
61
61
  LoggerProxy.logger.warn(
62
62
  'Roap:turnDiscovery#waitForTurnDiscoveryResponse --> TURN discovery is not in progress'
@@ -88,22 +88,32 @@ export default class TurnDiscovery {
88
88
  * handles TURN_DISCOVERY_RESPONSE roap message
89
89
  *
90
90
  * @param {Object} roapMessage
91
+ * @param {string} from string to indicate how we got the response (used just for logging)
91
92
  * @returns {void}
92
93
  * @public
93
94
  * @memberof Roap
94
95
  */
95
- public handleTurnDiscoveryResponse(roapMessage: object) {
96
- // @ts-ignore - Fix missing type
96
+ public handleTurnDiscoveryResponse(roapMessage: any, from: string) {
97
97
  const {headers} = roapMessage;
98
98
 
99
99
  if (!this.defer) {
100
100
  LoggerProxy.logger.warn(
101
- 'Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response'
101
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> unexpected TURN discovery response ${from}`
102
102
  );
103
103
 
104
104
  return;
105
105
  }
106
106
 
107
+ if (roapMessage.messageType !== ROAP.ROAP_TYPES.TURN_DISCOVERY_RESPONSE) {
108
+ this.defer.reject(
109
+ new Error(
110
+ `TURN_DISCOVERY_RESPONSE ${from} has unexpected messageType: ${JSON.stringify(
111
+ roapMessage
112
+ )}`
113
+ )
114
+ );
115
+ }
116
+
107
117
  const expectedHeaders = [
108
118
  {headerName: 'x-cisco-turn-url', field: 'url'},
109
119
  {headerName: 'x-cisco-turn-username', field: 'username'},
@@ -129,21 +139,39 @@ export default class TurnDiscovery {
129
139
 
130
140
  if (foundHeaders !== expectedHeaders.length) {
131
141
  LoggerProxy.logger.warn(
132
- `Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received: ${JSON.stringify(
142
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received ${from}: ${JSON.stringify(
133
143
  headers
134
144
  )}`
135
145
  );
136
146
  this.defer.reject(
137
- new Error(`TURN_DISCOVERY_RESPONSE missing some headers: ${JSON.stringify(headers)}`)
147
+ new Error(
148
+ `TURN_DISCOVERY_RESPONSE ${from} missing some headers: ${JSON.stringify(headers)}`
149
+ )
138
150
  );
139
151
  } else {
140
152
  LoggerProxy.logger.info(
141
- `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response, url=${this.turnInfo.url}`
153
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, url=${this.turnInfo.url}`
142
154
  );
143
- this.defer.resolve();
155
+
156
+ this.defer.resolve({isOkRequired: !headers?.includes('noOkInTransaction')});
144
157
  }
145
158
  }
146
159
 
160
+ /**
161
+ * handles TURN_DISCOVERY_RESPONSE roap message that came in http response
162
+ *
163
+ * @param {Object} roapMessage
164
+ * @returns {Promise}
165
+ * @memberof Roap
166
+ */
167
+ private async handleTurnDiscoveryResponseInHttpResponse(
168
+ roapMessage: object
169
+ ): Promise<{isOkRequired: boolean}> {
170
+ this.handleTurnDiscoveryResponse(roapMessage, 'in http response');
171
+
172
+ return this.defer.promise;
173
+ }
174
+
147
175
  /**
148
176
  * sends the TURN_DISCOVERY_REQUEST roap request
149
177
  *
@@ -168,6 +196,7 @@ export default class TurnDiscovery {
168
196
  messageType: ROAP.ROAP_TYPES.TURN_DISCOVERY_REQUEST,
169
197
  version: ROAP.ROAP_VERSION,
170
198
  seq: TURN_DISCOVERY_SEQ,
199
+ headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
171
200
  };
172
201
 
173
202
  LoggerProxy.logger.info(
@@ -186,10 +215,41 @@ export default class TurnDiscovery {
186
215
  // @ts-ignore - because of meeting.webex
187
216
  ipVersion: MeetingUtil.getIpVersion(meeting.webex),
188
217
  })
189
- .then(({mediaConnections}) => {
218
+ .then((response) => {
219
+ const {mediaConnections} = response;
220
+
221
+ let turnDiscoveryResponse;
222
+
190
223
  if (mediaConnections) {
191
224
  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
+ }
243
+
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
+ });
192
250
  }
251
+
252
+ return turnDiscoveryResponse;
193
253
  });
194
254
  }
195
255
 
@@ -225,10 +285,11 @@ export default class TurnDiscovery {
225
285
  * @returns {Promise<string>} Promise with empty string if reachability is not skipped or a reason if it is skipped
226
286
  */
227
287
  private async getSkipReason(meeting: Meeting): Promise<string> {
228
- // @ts-ignore - fix type
229
- const isAnyClusterReachable = await meeting.webex.meetings.reachability.isAnyClusterReachable();
288
+ const isAnyPublicClusterReachable =
289
+ // @ts-ignore - fix type
290
+ await meeting.webex.meetings.reachability.isAnyPublicClusterReachable();
230
291
 
231
- if (isAnyClusterReachable) {
292
+ if (isAnyPublicClusterReachable) {
232
293
  LoggerProxy.logger.info(
233
294
  'Roap:turnDiscovery#getSkipReason --> reachability has not failed, skipping TURN discovery'
234
295
  );
@@ -236,15 +297,6 @@ export default class TurnDiscovery {
236
297
  return 'reachability';
237
298
  }
238
299
 
239
- // @ts-ignore - fix type
240
- if (!meeting.config.experimental.enableTurnDiscovery) {
241
- LoggerProxy.logger.info(
242
- 'Roap:turnDiscovery#getSkipReason --> TURN discovery disabled in config, skipping it'
243
- );
244
-
245
- return 'config';
246
- }
247
-
248
300
  return '';
249
301
  }
250
302
 
@@ -273,12 +325,17 @@ export default class TurnDiscovery {
273
325
  * so it works fine no matter if TURN discovery is done or not.
274
326
  *
275
327
  * @param {Meeting} meeting
276
- * @param {Boolean} isReconnecting should be set to true if this is a new
328
+ * @param {Boolean} [isReconnecting] should be set to true if this is a new
277
329
  * media connection just after a reconnection
330
+ * @param {Boolean} [isForced]
278
331
  * @returns {Promise}
279
332
  */
280
- async doTurnDiscovery(meeting: Meeting, isReconnecting?: boolean) {
281
- const turnDiscoverySkippedReason = await this.getSkipReason(meeting);
333
+ async doTurnDiscovery(meeting: Meeting, isReconnecting?: boolean, isForced?: boolean) {
334
+ let turnDiscoverySkippedReason: string;
335
+
336
+ if (!isForced) {
337
+ turnDiscoverySkippedReason = await this.getSkipReason(meeting);
338
+ }
282
339
 
283
340
  if (turnDiscoverySkippedReason) {
284
341
  return {
@@ -287,30 +344,46 @@ export default class TurnDiscovery {
287
344
  };
288
345
  }
289
346
 
290
- return this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting)
291
- .then(() => this.waitForTurnDiscoveryResponse())
292
- .then(() => this.sendRoapOK(meeting))
293
- .then(() => {
294
- this.defer = undefined;
347
+ try {
348
+ const httpResponse = await this.sendRoapTurnDiscoveryRequest(meeting, isReconnecting);
295
349
 
296
- LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
350
+ // 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();
354
+
355
+ if (isOkRequired) {
356
+ await this.sendRoapOK(meeting);
297
357
 
298
- return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
299
- })
300
- .catch((e) => {
301
- // we catch any errors and resolve with no turn information so that the normal call join flow can continue without TURN
302
358
  LoggerProxy.logger.info(
303
- `Roap:turnDiscovery#doTurnDiscovery --> TURN discovery failed, continuing without TURN: ${e}`
359
+ 'Roap:turnDiscovery#doTurnDiscovery --> TURN discovery response requires OK'
304
360
  );
305
361
 
306
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE, {
362
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK, {
307
363
  correlation_id: meeting.correlationId,
308
364
  locus_id: meeting.locusUrl.split('/').pop(),
309
- reason: e.message,
310
- stack: e.stack,
311
365
  });
366
+ }
367
+
368
+ this.defer = undefined;
312
369
 
313
- return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
370
+ LoggerProxy.logger.info('Roap:turnDiscovery#doTurnDiscovery --> TURN discovery completed');
371
+
372
+ return {turnServerInfo: this.turnInfo, turnDiscoverySkippedReason: undefined};
373
+ } 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,
314
384
  });
385
+
386
+ return {turnServerInfo: undefined, turnDiscoverySkippedReason: undefined};
387
+ }
315
388
  }
316
389
  }