@webex/plugin-meetings 3.7.0 → 3.8.0-next.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/annotation/index.js +17 -0
  2. package/dist/annotation/index.js.map +1 -1
  3. package/dist/breakouts/breakout.js +1 -1
  4. package/dist/breakouts/index.js +1 -1
  5. package/dist/common/errors/join-forbidden-error.js +52 -0
  6. package/dist/common/errors/join-forbidden-error.js.map +1 -0
  7. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  8. package/dist/common/errors/join-webinar-error.js.map +1 -0
  9. package/dist/common/errors/multistream-not-supported-error.js +53 -0
  10. package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
  11. package/dist/config.js +3 -1
  12. package/dist/config.js.map +1 -1
  13. package/dist/constants.js +69 -6
  14. package/dist/constants.js.map +1 -1
  15. package/dist/index.js +16 -11
  16. package/dist/index.js.map +1 -1
  17. package/dist/interpretation/index.js +4 -4
  18. package/dist/interpretation/index.js.map +1 -1
  19. package/dist/interpretation/siLanguage.js +1 -1
  20. package/dist/locus-info/index.js +14 -3
  21. package/dist/locus-info/index.js.map +1 -1
  22. package/dist/locus-info/selfUtils.js +35 -17
  23. package/dist/locus-info/selfUtils.js.map +1 -1
  24. package/dist/media/MediaConnectionAwaiter.js +1 -0
  25. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  26. package/dist/media/properties.js +30 -16
  27. package/dist/media/properties.js.map +1 -1
  28. package/dist/meeting/brbState.js +167 -0
  29. package/dist/meeting/brbState.js.map +1 -0
  30. package/dist/meeting/in-meeting-actions.js +13 -1
  31. package/dist/meeting/in-meeting-actions.js.map +1 -1
  32. package/dist/meeting/index.js +1373 -1052
  33. package/dist/meeting/index.js.map +1 -1
  34. package/dist/meeting/locusMediaRequest.js +32 -11
  35. package/dist/meeting/locusMediaRequest.js.map +1 -1
  36. package/dist/meeting/muteState.js +1 -6
  37. package/dist/meeting/muteState.js.map +1 -1
  38. package/dist/meeting/request.js +51 -29
  39. package/dist/meeting/request.js.map +1 -1
  40. package/dist/meeting/request.type.js.map +1 -1
  41. package/dist/meeting/util.js +103 -67
  42. package/dist/meeting/util.js.map +1 -1
  43. package/dist/meeting-info/meeting-info-v2.js +115 -45
  44. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  45. package/dist/meeting-info/utilv2.js +6 -2
  46. package/dist/meeting-info/utilv2.js.map +1 -1
  47. package/dist/meetings/index.js +107 -55
  48. package/dist/meetings/index.js.map +1 -1
  49. package/dist/meetings/meetings.types.js +2 -0
  50. package/dist/meetings/meetings.types.js.map +1 -1
  51. package/dist/meetings/util.js +1 -1
  52. package/dist/meetings/util.js.map +1 -1
  53. package/dist/member/index.js +9 -0
  54. package/dist/member/index.js.map +1 -1
  55. package/dist/member/types.js.map +1 -1
  56. package/dist/member/util.js +39 -28
  57. package/dist/member/util.js.map +1 -1
  58. package/dist/members/util.js +4 -2
  59. package/dist/members/util.js.map +1 -1
  60. package/dist/metrics/constants.js +6 -1
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/multistream/remoteMedia.js +30 -15
  63. package/dist/multistream/remoteMedia.js.map +1 -1
  64. package/dist/multistream/remoteMediaManager.js +40 -8
  65. package/dist/multistream/remoteMediaManager.js.map +1 -1
  66. package/dist/multistream/sendSlotManager.js +24 -0
  67. package/dist/multistream/sendSlotManager.js.map +1 -1
  68. package/dist/reachability/clusterReachability.js +12 -15
  69. package/dist/reachability/clusterReachability.js.map +1 -1
  70. package/dist/reachability/index.js +471 -140
  71. package/dist/reachability/index.js.map +1 -1
  72. package/dist/{rtcMetrics/constants.js → reachability/reachability.types.js} +1 -5
  73. package/dist/reachability/reachability.types.js.map +1 -0
  74. package/dist/reachability/request.js +21 -8
  75. package/dist/reachability/request.js.map +1 -1
  76. package/dist/recording-controller/enums.js +8 -4
  77. package/dist/recording-controller/enums.js.map +1 -1
  78. package/dist/recording-controller/index.js +18 -9
  79. package/dist/recording-controller/index.js.map +1 -1
  80. package/dist/recording-controller/util.js +13 -9
  81. package/dist/recording-controller/util.js.map +1 -1
  82. package/dist/roap/index.js +15 -15
  83. package/dist/roap/index.js.map +1 -1
  84. package/dist/roap/request.js +45 -79
  85. package/dist/roap/request.js.map +1 -1
  86. package/dist/roap/turnDiscovery.js +3 -6
  87. package/dist/roap/turnDiscovery.js.map +1 -1
  88. package/dist/types/annotation/index.d.ts +5 -0
  89. package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
  90. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  91. package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
  92. package/dist/types/config.d.ts +2 -0
  93. package/dist/types/constants.d.ts +54 -1
  94. package/dist/types/index.d.ts +3 -3
  95. package/dist/types/locus-info/index.d.ts +2 -1
  96. package/dist/types/meeting/brbState.d.ts +54 -0
  97. package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
  98. package/dist/types/meeting/index.d.ts +86 -14
  99. package/dist/types/meeting/locusMediaRequest.d.ts +6 -3
  100. package/dist/types/meeting/request.d.ts +14 -3
  101. package/dist/types/meeting/request.type.d.ts +6 -0
  102. package/dist/types/meeting/util.d.ts +3 -3
  103. package/dist/types/meeting-info/meeting-info-v2.d.ts +30 -5
  104. package/dist/types/meetings/index.d.ts +20 -2
  105. package/dist/types/meetings/meetings.types.d.ts +8 -0
  106. package/dist/types/member/index.d.ts +1 -0
  107. package/dist/types/member/types.d.ts +7 -0
  108. package/dist/types/members/util.d.ts +2 -0
  109. package/dist/types/metrics/constants.d.ts +6 -1
  110. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  111. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  112. package/dist/types/reachability/clusterReachability.d.ts +1 -10
  113. package/dist/types/reachability/index.d.ts +83 -36
  114. package/dist/types/reachability/reachability.types.d.ts +64 -0
  115. package/dist/types/reachability/request.d.ts +5 -1
  116. package/dist/types/recording-controller/enums.d.ts +5 -2
  117. package/dist/types/recording-controller/index.d.ts +1 -0
  118. package/dist/types/recording-controller/util.d.ts +2 -1
  119. package/dist/types/roap/request.d.ts +1 -13
  120. package/dist/webinar/index.js +390 -7
  121. package/dist/webinar/index.js.map +1 -1
  122. package/package.json +23 -22
  123. package/src/annotation/index.ts +16 -0
  124. package/src/common/errors/join-forbidden-error.ts +26 -0
  125. package/src/common/errors/join-webinar-error.ts +24 -0
  126. package/src/common/errors/multistream-not-supported-error.ts +30 -0
  127. package/src/config.ts +2 -0
  128. package/src/constants.ts +62 -3
  129. package/src/index.ts +5 -3
  130. package/src/interpretation/index.ts +3 -3
  131. package/src/locus-info/index.ts +20 -3
  132. package/src/locus-info/selfUtils.ts +24 -6
  133. package/src/media/MediaConnectionAwaiter.ts +2 -0
  134. package/src/media/properties.ts +34 -13
  135. package/src/meeting/brbState.ts +169 -0
  136. package/src/meeting/in-meeting-actions.ts +25 -0
  137. package/src/meeting/index.ts +485 -88
  138. package/src/meeting/locusMediaRequest.ts +38 -12
  139. package/src/meeting/muteState.ts +1 -6
  140. package/src/meeting/request.ts +30 -12
  141. package/src/meeting/request.type.ts +7 -0
  142. package/src/meeting/util.ts +32 -13
  143. package/src/meeting-info/meeting-info-v2.ts +83 -12
  144. package/src/meeting-info/utilv2.ts +17 -3
  145. package/src/meetings/index.ts +79 -20
  146. package/src/meetings/meetings.types.ts +10 -0
  147. package/src/meetings/util.ts +2 -1
  148. package/src/member/index.ts +9 -0
  149. package/src/member/types.ts +8 -0
  150. package/src/member/util.ts +34 -24
  151. package/src/members/util.ts +1 -0
  152. package/src/metrics/constants.ts +6 -1
  153. package/src/multistream/remoteMedia.ts +28 -15
  154. package/src/multistream/remoteMediaManager.ts +32 -10
  155. package/src/multistream/sendSlotManager.ts +31 -0
  156. package/src/reachability/clusterReachability.ts +5 -15
  157. package/src/reachability/index.ts +315 -75
  158. package/src/reachability/reachability.types.ts +85 -0
  159. package/src/reachability/request.ts +55 -31
  160. package/src/recording-controller/enums.ts +5 -2
  161. package/src/recording-controller/index.ts +17 -4
  162. package/src/recording-controller/util.ts +28 -9
  163. package/src/roap/index.ts +14 -13
  164. package/src/roap/request.ts +30 -44
  165. package/src/roap/turnDiscovery.ts +2 -4
  166. package/src/webinar/index.ts +235 -9
  167. package/test/unit/spec/annotation/index.ts +46 -1
  168. package/test/unit/spec/interpretation/index.ts +39 -1
  169. package/test/unit/spec/locus-info/index.js +292 -60
  170. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  171. package/test/unit/spec/locus-info/selfUtils.js +101 -1
  172. package/test/unit/spec/media/properties.ts +15 -0
  173. package/test/unit/spec/meeting/brbState.ts +114 -0
  174. package/test/unit/spec/meeting/in-meeting-actions.ts +15 -1
  175. package/test/unit/spec/meeting/index.js +908 -124
  176. package/test/unit/spec/meeting/locusMediaRequest.ts +111 -66
  177. package/test/unit/spec/meeting/muteState.js +0 -24
  178. package/test/unit/spec/meeting/request.js +3 -26
  179. package/test/unit/spec/meeting/utils.js +73 -28
  180. package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
  181. package/test/unit/spec/meeting-info/utilv2.js +26 -0
  182. package/test/unit/spec/meetings/index.js +172 -18
  183. package/test/unit/spec/meetings/utils.js +10 -0
  184. package/test/unit/spec/member/util.js +52 -11
  185. package/test/unit/spec/members/utils.js +95 -0
  186. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  187. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  188. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  189. package/test/unit/spec/reachability/index.ts +391 -9
  190. package/test/unit/spec/reachability/request.js +48 -12
  191. package/test/unit/spec/recording-controller/index.js +61 -5
  192. package/test/unit/spec/recording-controller/util.js +39 -3
  193. package/test/unit/spec/roap/index.ts +48 -1
  194. package/test/unit/spec/roap/request.ts +51 -109
  195. package/test/unit/spec/roap/turnDiscovery.ts +202 -147
  196. package/test/unit/spec/webinar/index.ts +509 -0
  197. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  198. package/dist/networkQualityMonitor/index.js +0 -227
  199. package/dist/networkQualityMonitor/index.js.map +0 -1
  200. package/dist/rtcMetrics/constants.js.map +0 -1
  201. package/dist/rtcMetrics/index.js +0 -197
  202. package/dist/rtcMetrics/index.js.map +0 -1
  203. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  204. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  205. package/dist/types/rtcMetrics/index.d.ts +0 -71
  206. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -15,6 +15,7 @@ describe('ClusterReachability', () => {
15
15
  let previousRTCPeerConnection;
16
16
  let clusterReachability;
17
17
  let fakePeerConnection;
18
+ let gatherIceCandidatesSpy;
18
19
 
19
20
  const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData)[]> = {
20
21
  [Events.resultReady]: [],
@@ -44,6 +45,8 @@ describe('ClusterReachability', () => {
44
45
  xtls: ['stun:xtls1.webex.com', 'stun:xtls2.webex.com:443'],
45
46
  });
46
47
 
48
+ gatherIceCandidatesSpy = sinon.spy(clusterReachability, 'gatherIceCandidates');
49
+
47
50
  resetEmittedEvents();
48
51
 
49
52
  clusterReachability.on(Events.resultReady, (data: ResultEventData) => {
@@ -151,6 +154,10 @@ describe('ClusterReachability', () => {
151
154
  assert.calledOnceWithExactly(fakePeerConnection.createOffer, {offerToReceiveAudio: true});
152
155
  assert.calledOnce(fakePeerConnection.setLocalDescription);
153
156
 
157
+ // Make sure that gatherIceCandidates is called before setLocalDescription
158
+ // as setLocalDescription triggers the ICE gathering process
159
+ assert.isTrue(gatherIceCandidatesSpy.calledBefore(fakePeerConnection.setLocalDescription));
160
+
154
161
  clusterReachability.abort();
155
162
  await promise;
156
163
 
@@ -538,6 +538,14 @@ describe('gatherReachability', () => {
538
538
  assert.equal(storedResultForJoinCookie, JSON.stringify(expectedJoinCookie));
539
539
  };
540
540
 
541
+ it('rejects if reachability is disabled in config', async () => {
542
+ webex.config.meetings.enableReachabilityChecks = false;
543
+
544
+ const reachability = new Reachability(webex);
545
+
546
+ await assert.isRejected(reachability.gatherReachability('test'), 'enableReachabilityChecks is disabled in config');
547
+ });
548
+
541
549
  [
542
550
  // ========================================================================
543
551
  {
@@ -1050,6 +1058,7 @@ describe('gatherReachability', () => {
1050
1058
 
1051
1059
  // the metrics related to ipver and trigger are not tested in these tests and are all the same, so setting them up here
1052
1060
  const expectedMetricsFull = {
1061
+ aborted: false,
1053
1062
  ...expectedMetrics,
1054
1063
  ipver_firstIpV4: -1,
1055
1064
  ipver_firstIpV6: -1,
@@ -1238,6 +1247,7 @@ describe('gatherReachability', () => {
1238
1247
 
1239
1248
  // finally, check the metrics - they should contain values from ipNetworkDetector
1240
1249
  assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
1250
+ aborted: false,
1241
1251
  vmn_udp_min: -1,
1242
1252
  vmn_udp_max: -1,
1243
1253
  vmn_udp_average: -1,
@@ -1622,14 +1632,14 @@ describe('gatherReachability', () => {
1622
1632
  const reachability = new Reachability(webex);
1623
1633
 
1624
1634
  let getClustersCallCount = 0;
1625
-
1635
+
1626
1636
  reachability.reachabilityRequest.getClusters = sinon.stub().callsFake(() => {
1627
1637
  getClustersCallCount++;
1628
1638
 
1629
1639
  if (getClustersCallCount == 1) {
1630
1640
  throw new Error('fake error');
1631
1641
  }
1632
-
1642
+
1633
1643
  return getClustersResult;
1634
1644
  });
1635
1645
 
@@ -1637,7 +1647,7 @@ describe('gatherReachability', () => {
1637
1647
 
1638
1648
  await simulateTimeout();
1639
1649
  await promise;
1640
-
1650
+
1641
1651
  assert.equal(getClustersCallCount, 2);
1642
1652
 
1643
1653
  assert.calledOnce(clusterReachabilityCtorStub);
@@ -1647,7 +1657,7 @@ describe('gatherReachability', () => {
1647
1657
  const reachability = new Reachability(webex);
1648
1658
 
1649
1659
  let getClustersCallCount = 0;
1650
-
1660
+
1651
1661
  reachability.reachabilityRequest.getClusters = sinon.stub().callsFake(() => {
1652
1662
  getClustersCallCount++;
1653
1663
 
@@ -1657,13 +1667,356 @@ describe('gatherReachability', () => {
1657
1667
  const promise = reachability.gatherReachability('test');
1658
1668
 
1659
1669
  await simulateTimeout();
1660
-
1670
+
1661
1671
  await promise;
1662
-
1672
+
1663
1673
  assert.equal(getClustersCallCount, 2);
1664
1674
 
1665
1675
  assert.neverCalledWith(clusterReachabilityCtorStub);
1666
1676
  });
1677
+
1678
+ describe('fallback mechanism and multiple calls to getClusters', () => {
1679
+ let receivedEvents;
1680
+
1681
+ const mockGetClustersEmptyResult = {
1682
+ discoveryOptions: {
1683
+ ['early-call-min-clusters']: 0,
1684
+ ['report-version']: 1,
1685
+ },
1686
+ clusters: {}, // empty cluster list
1687
+ joinCookie: {id: 'cookie'},
1688
+ };
1689
+
1690
+ beforeEach(() => {
1691
+ webex.config.meetings.experimental = {
1692
+ enableTcpReachability: true,
1693
+ enableTlsReachability: true,
1694
+ };
1695
+
1696
+ receivedEvents = {
1697
+ done: 0,
1698
+ };
1699
+ });
1700
+
1701
+ it('keeps retrying if minimum required clusters are not reached', async () => {
1702
+ const reachability = new Reachability(webex);
1703
+
1704
+ reachability.on('reachability:done', () => {
1705
+ receivedEvents.done += 1;
1706
+ });
1707
+
1708
+ const mockGetClustersResult1 = {
1709
+ discoveryOptions: {
1710
+ ['early-call-min-clusters']: 2,
1711
+ ['report-version']: 1,
1712
+ },
1713
+ clusters: {
1714
+ clusterA0: {
1715
+ udp: ['udp-urlA'],
1716
+ tcp: ['tcp-urlA'],
1717
+ xtls: ['xtls-urlA'],
1718
+ isVideoMesh: false,
1719
+ },
1720
+ clusterB0: {
1721
+ udp: ['udp-urlB'],
1722
+ tcp: ['tcp-urlB'],
1723
+ xtls: ['xtls-urlB'],
1724
+ isVideoMesh: false,
1725
+ },
1726
+ },
1727
+ joinCookie: {id: 'cookie1'},
1728
+ };
1729
+ const mockGetClustersResult2 = {
1730
+ discoveryOptions: {
1731
+ ['early-call-min-clusters']: 2,
1732
+ ['report-version']: 1,
1733
+ },
1734
+ clusters: {
1735
+ clusterA1: {
1736
+ udp: ['udp-urlA'],
1737
+ tcp: ['tcp-urlA'],
1738
+ xtls: ['xtls-urlA'],
1739
+ isVideoMesh: false,
1740
+ },
1741
+ clusterB1: {
1742
+ udp: ['udp-urlB'],
1743
+ tcp: ['tcp-urlB'],
1744
+ xtls: ['xtls-urlB'],
1745
+ isVideoMesh: false,
1746
+ },
1747
+ },
1748
+ joinCookie: {id: 'cookie2'},
1749
+ };
1750
+ const mockGetClustersResult3 = {
1751
+ discoveryOptions: {
1752
+ ['early-call-min-clusters']: 1,
1753
+ ['report-version']: 1,
1754
+ },
1755
+ clusters: {
1756
+ clusterA2: {
1757
+ udp: ['udp-urlA'],
1758
+ tcp: ['tcp-urlA'],
1759
+ xtls: ['xtls-urlA'],
1760
+ isVideoMesh: false,
1761
+ },
1762
+ clusterB2: {
1763
+ udp: ['udp-urlB'],
1764
+ tcp: ['tcp-urlB'],
1765
+ xtls: ['xtls-urlB'],
1766
+ isVideoMesh: false,
1767
+ },
1768
+ },
1769
+ joinCookie: {id: 'cookie3'},
1770
+ };
1771
+
1772
+ reachability.reachabilityRequest.getClusters = sinon.stub();
1773
+ reachability.reachabilityRequest.getClusters.onCall(0).returns(mockGetClustersResult1);
1774
+ reachability.reachabilityRequest.getClusters.onCall(1).returns(mockGetClustersResult2);
1775
+
1776
+ reachability.reachabilityRequest.getClusters.onCall(2).returns(mockGetClustersResult3);
1777
+
1778
+ const resultPromise = reachability.gatherReachability('test');
1779
+
1780
+ await testUtils.flushPromises();
1781
+
1782
+ // trigger some mock result events from ClusterReachability instances,
1783
+ // but only from 1 cluster, so not enough to reach the minimum required
1784
+ mockClusterReachabilityInstances['clusterA0'].emitFakeResult('udp', {
1785
+ result: 'reachable',
1786
+ clientMediaIPs: ['1.2.3.4'],
1787
+ latencyInMilliseconds: 11,
1788
+ });
1789
+
1790
+ clock.tick(3000);
1791
+ await resultPromise;
1792
+ await testUtils.flushPromises();
1793
+
1794
+ // because the minimum was not reached, another call to getClusters should be made
1795
+ assert.calledTwice(reachability.reachabilityRequest.getClusters);
1796
+
1797
+ // simulate no results this time
1798
+
1799
+ // check that while the 2nd attempt is in progress, the join cookie is already available from the 2nd call to getClusters
1800
+ const clientMediaPreferences = await reachability.getClientMediaPreferences(
1801
+ true,
1802
+ IP_VERSION.unknown
1803
+ );
1804
+
1805
+ assert.deepEqual(clientMediaPreferences.joinCookie, mockGetClustersResult2.joinCookie);
1806
+
1807
+ clock.tick(3000);
1808
+ await testUtils.flushPromises();
1809
+
1810
+ assert.calledThrice(reachability.reachabilityRequest.getClusters);
1811
+
1812
+ await testUtils.flushPromises();
1813
+
1814
+ // this time 1 result will be enough to reach the minimum
1815
+ mockClusterReachabilityInstances['clusterA2'].emitFakeResult('udp', {
1816
+ result: 'reachable',
1817
+ clientMediaIPs: ['1.2.3.4'],
1818
+ latencyInMilliseconds: 11,
1819
+ });
1820
+ clock.tick(3000);
1821
+
1822
+ // the reachability results should include only results from the last attempt
1823
+ await checkResults(
1824
+ {
1825
+ clusterA2: {
1826
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 11},
1827
+ tcp: {result: 'unreachable'},
1828
+ xtls: {result: 'unreachable'},
1829
+ isVideoMesh: false,
1830
+ },
1831
+ clusterB2: {
1832
+ udp: {result: 'unreachable'},
1833
+ tcp: {result: 'unreachable'},
1834
+ xtls: {result: 'unreachable'},
1835
+ isVideoMesh: false,
1836
+ },
1837
+ },
1838
+ mockGetClustersResult3.joinCookie
1839
+ );
1840
+
1841
+ // wait some more time to make sure that there are no timers that fire from one of the previous checks
1842
+ clock.tick(20000);
1843
+
1844
+ // as the first 2 attempts failed and didn't reach the overall timeout, there should be only 1 done event emitted
1845
+ assert.equal(receivedEvents.done, 1);
1846
+ });
1847
+
1848
+ it('handles getClusters() returning empty list on 1st call', async () => {
1849
+ const reachability = new Reachability(webex);
1850
+
1851
+ reachability.on('reachability:done', () => {
1852
+ receivedEvents.done += 1;
1853
+ });
1854
+
1855
+ reachability.reachabilityRequest.getClusters = sinon
1856
+ .stub()
1857
+ .resolves(mockGetClustersEmptyResult);
1858
+
1859
+ const resultPromise = reachability.gatherReachability('test');
1860
+
1861
+ await testUtils.flushPromises();
1862
+
1863
+ clock.tick(3000);
1864
+ await resultPromise;
1865
+ await testUtils.flushPromises();
1866
+
1867
+ assert.calledOnce(reachability.reachabilityRequest.getClusters);
1868
+ reachability.reachabilityRequest.getClusters.resetHistory();
1869
+
1870
+ assert.equal(receivedEvents.done, 1);
1871
+ await checkResults({}, mockGetClustersEmptyResult.joinCookie);
1872
+
1873
+ // because we didn't actually test anything (we got empty cluster list from getClusters()), we should
1874
+ // not say that webex backend is unreachable
1875
+ assert.equal(await reachability.isWebexMediaBackendUnreachable(), false);
1876
+
1877
+ // wait to check that there are no other things happening
1878
+ clock.tick(20000);
1879
+ await testUtils.flushPromises();
1880
+
1881
+ assert.notCalled(reachability.reachabilityRequest.getClusters);
1882
+ assert.equal(receivedEvents.done, 1);
1883
+ });
1884
+
1885
+ it('handles getClusters() returning empty list on 2nd call', async () => {
1886
+ const reachability = new Reachability(webex);
1887
+
1888
+ reachability.on('reachability:done', () => {
1889
+ receivedEvents.done += 1;
1890
+ });
1891
+
1892
+ const mockGetClustersResult1 = {
1893
+ discoveryOptions: {
1894
+ ['early-call-min-clusters']: 2,
1895
+ ['report-version']: 1,
1896
+ },
1897
+ clusters: {
1898
+ clusterA0: {
1899
+ udp: ['udp-urlA'],
1900
+ tcp: ['tcp-urlA'],
1901
+ xtls: ['xtls-urlA'],
1902
+ isVideoMesh: false,
1903
+ },
1904
+ clusterB0: {
1905
+ udp: ['udp-urlB'],
1906
+ tcp: ['tcp-urlB'],
1907
+ xtls: ['xtls-urlB'],
1908
+ isVideoMesh: false,
1909
+ },
1910
+ },
1911
+ joinCookie: {id: 'cookie1'},
1912
+ };
1913
+
1914
+ reachability.reachabilityRequest.getClusters = sinon.stub();
1915
+ reachability.reachabilityRequest.getClusters.onCall(0).returns(mockGetClustersResult1);
1916
+ reachability.reachabilityRequest.getClusters.onCall(1).returns(mockGetClustersEmptyResult);
1917
+
1918
+ const resultPromise = reachability.gatherReachability('test');
1919
+
1920
+ await testUtils.flushPromises();
1921
+
1922
+ clock.tick(3000);
1923
+ await resultPromise;
1924
+ await testUtils.flushPromises();
1925
+
1926
+ // because the minimum was not reached, another call to getClusters should be made
1927
+ assert.calledTwice(reachability.reachabilityRequest.getClusters);
1928
+
1929
+ // the reachability results should include only results from the last attempt
1930
+ await checkResults({}, mockGetClustersEmptyResult.joinCookie);
1931
+
1932
+ // as the first 2 attempts failed and didn't reach the overall timeout, there should be only 1 done event emitted
1933
+ assert.equal(receivedEvents.done, 1);
1934
+ // because we didn't actually test anything (we got empty cluster list from getClusters()), we should
1935
+ // not say that webex backend is unreachable
1936
+ assert.equal(await reachability.isWebexMediaBackendUnreachable(), false);
1937
+ });
1938
+ });
1939
+
1940
+ describe('stopReachability', () => {
1941
+ let reachability;
1942
+ let receivedEvents;
1943
+ let sendMetricSpy;
1944
+
1945
+ beforeEach(() => {
1946
+ reachability = new Reachability(webex);
1947
+
1948
+ receivedEvents = {};
1949
+
1950
+ sendMetricSpy = sinon.stub(reachability, 'sendMetric').resolves();
1951
+ });
1952
+
1953
+ const setListener = (event) => {
1954
+ reachability.on(event, () => {
1955
+ receivedEvents[event] = receivedEvents[event] + 1 || 1;
1956
+ });
1957
+ };
1958
+ it('works as expected', async () => {
1959
+ setListener('reachability:stopped');
1960
+ setListener('reachability:done');
1961
+ setListener('reachability:firstResultAvailable');
1962
+
1963
+ const mockGetClustersResult = {
1964
+ clusters: {
1965
+ clusterA: {
1966
+ udp: ['udp-urlA'],
1967
+ tcp: ['tcp-urlA'],
1968
+ xtls: ['xtls-urlA'],
1969
+ isVideoMesh: false,
1970
+ },
1971
+ clusterB: {
1972
+ udp: ['udp-urlB'],
1973
+ tcp: ['tcp-urlB'],
1974
+ xtls: ['xtls-urlB'],
1975
+ isVideoMesh: false,
1976
+ },
1977
+ },
1978
+ joinCookie: {id: 'id'},
1979
+ };
1980
+
1981
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
1982
+
1983
+ const gatherReachabilityFallbackSpy = sinon.spy(reachability, 'gatherReachabilityFallback');
1984
+
1985
+ const resultPromise = reachability.gatherReachability('test');
1986
+
1987
+ await testUtils.flushPromises();
1988
+
1989
+ reachability.stopReachability();
1990
+
1991
+ await resultPromise;
1992
+
1993
+ // simulate a lot of time passing to check that all timers were stopped and nothing else happens
1994
+ clock.tick(99000);
1995
+
1996
+ assert.calledOnceWithExactly(mockClusterReachabilityInstances['clusterA'].abort);
1997
+ assert.calledOnceWithExactly(mockClusterReachabilityInstances['clusterB'].abort);
1998
+
1999
+ assert.calledOnceWithExactly(sendMetricSpy, true);
2000
+
2001
+ assert.equal(receivedEvents['reachability:stopped'], 1);
2002
+ assert.equal(receivedEvents['reachability:done'], undefined);
2003
+ assert.equal(receivedEvents['reachability:firstResultAvailable'], undefined);
2004
+
2005
+ assert.notCalled(gatherReachabilityFallbackSpy);
2006
+ });
2007
+
2008
+ it('does nothing if called without reachability being started', async () => {
2009
+ const reachability = new Reachability(webex);
2010
+
2011
+ reachability.stopReachability();
2012
+
2013
+ assert.notCalled(sendMetricSpy);
2014
+
2015
+ assert.equal(receivedEvents['reachability:stopped'], undefined);
2016
+ assert.equal(receivedEvents['reachability:done'], undefined);
2017
+ assert.equal(receivedEvents['reachability:firstResultAvailable'], undefined);
2018
+ });
2019
+ });
1667
2020
  });
1668
2021
 
1669
2022
  describe('getReachabilityResults', () => {
@@ -2225,19 +2578,18 @@ describe('getStatistics', () => {
2225
2578
  describe('sendMetric', () => {
2226
2579
  let webex;
2227
2580
  let reachability;
2581
+ let getStatisticsStub;
2228
2582
 
2229
2583
  beforeEach(() => {
2230
2584
  webex = new MockWebex();
2231
2585
  reachability = new TestReachability(webex);
2232
2586
 
2233
2587
  sinon.stub(Metrics, 'sendBehavioralMetric');
2234
- });
2235
2588
 
2236
- it('works as expected', async () => {
2237
2589
  // setup stub for getStatistics to return values that show what parameters it was called with,
2238
2590
  // this way we can verify that the correct results of calls to getStatistics are placed
2239
2591
  // in correct data fields when sendBehavioralMetric() is called
2240
- const getStatisticsStub = sinon
2592
+ getStatisticsStub = sinon
2241
2593
  .stub(reachability, 'getStatistics')
2242
2594
  .callsFake((results, protocol, isVideoMesh) => {
2243
2595
  return {result: 'fake', protocol, isVideoMesh};
@@ -2258,7 +2610,13 @@ describe('sendMetric', () => {
2258
2610
  isVideoMesh: false,
2259
2611
  },
2260
2612
  });
2613
+ });
2261
2614
 
2615
+ afterEach(() => {
2616
+ sinon.restore();
2617
+ });
2618
+
2619
+ it('works as expected', async () => {
2262
2620
  await reachability.sendMetric();
2263
2621
 
2264
2622
  // each call to getStatistics should be made with all the results from all fake clusterReachability:
@@ -2282,6 +2640,30 @@ describe('sendMetric', () => {
2282
2640
  assert.alwaysCalledWith(getStatisticsStub, expectedResults, sinon.match.any, sinon.match.any);
2283
2641
 
2284
2642
  assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
2643
+ aborted: false,
2644
+ vmn_udp_result: 'fake',
2645
+ vmn_udp_protocol: 'udp',
2646
+ vmn_udp_isVideoMesh: true,
2647
+
2648
+ public_udp_result: 'fake',
2649
+ public_udp_protocol: 'udp',
2650
+ public_udp_isVideoMesh: false,
2651
+
2652
+ public_tcp_result: 'fake',
2653
+ public_tcp_protocol: 'tcp',
2654
+ public_tcp_isVideoMesh: false,
2655
+
2656
+ public_xtls_result: 'fake',
2657
+ public_xtls_protocol: 'xtls',
2658
+ public_xtls_isVideoMesh: false,
2659
+ });
2660
+ });
2661
+
2662
+ it('sends metric with "aborted:true" if called with aborted=true arg', async () => {
2663
+ await reachability.sendMetric(true);
2664
+
2665
+ assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
2666
+ aborted: true,
2285
2667
  vmn_udp_result: 'fake',
2286
2668
  vmn_udp_protocol: 'udp',
2287
2669
  vmn_udp_isVideoMesh: true,
@@ -35,16 +35,11 @@ describe('plugin-meetings/reachability', () => {
35
35
  });
36
36
 
37
37
  describe('#getClusters', () => {
38
+ let previousReport;
38
39
 
39
40
  beforeEach(() => {
40
41
  sinon.spy(webex.internal.newMetrics.callDiagnosticLatencies, 'measureLatency');
41
- });
42
-
43
- afterEach(() => {
44
- sinon.restore();
45
- });
46
42
 
47
- it('sends a GET request with the correct params', async () => {
48
43
  webex.request = sinon.mock().returns(Promise.resolve({
49
44
  body: {
50
45
  clusterClasses: {
@@ -59,24 +54,65 @@ describe('plugin-meetings/reachability', () => {
59
54
 
60
55
  webex.config.meetings.reachabilityGetClusterTimeout = 3000;
61
56
 
62
- const res = await reachabilityRequest.getClusters(IP_VERSION.only_ipv4);
57
+ previousReport = {
58
+ id: 'fake previous report',
59
+ }
60
+ });
61
+
62
+ afterEach(() => {
63
+ sinon.restore();
64
+ });
65
+
66
+ it('sends a POST request with the correct params when trigger is "startup"', async () => {
67
+ const res = await reachabilityRequest.getClusters('startup', IP_VERSION.only_ipv4, previousReport);
63
68
  const requestParams = webex.request.getCall(0).args[0];
64
69
 
65
70
  assert.deepEqual(requestParams, {
66
- method: 'GET',
71
+ method: 'POST',
67
72
  resource: `clusters`,
68
73
  api: 'calliopeDiscovery',
69
74
  shouldRefreshAccessToken: false,
70
- qs: {
71
- JCSupport: 1,
72
- ipver: 4,
73
- },
74
75
  timeout: 3000,
76
+ body: {
77
+ ipver: IP_VERSION.only_ipv4,
78
+ 'supported-options': {
79
+ 'report-version': 1,
80
+ 'early-call-min-clusters': true,
81
+ },
82
+ 'previous-report': previousReport,
83
+ trigger: 'startup',
84
+ },
75
85
  });
76
86
 
77
87
  assert.deepEqual(res.clusters.clusterId, {udp: "testUDP", isVideoMesh: true})
78
88
  assert.deepEqual(res.joinCookie, {anycastEntryPoint: "aws-eu-west-1"})
79
89
  assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.cluster.time');
80
90
  });
91
+
92
+ it('sends a POST request with the correct params when trigger is other than "startup"', async () => {
93
+ const res = await reachabilityRequest.getClusters('early-call/no-min-reached', IP_VERSION.only_ipv4, previousReport);
94
+ const requestParams = webex.request.getCall(0).args[0];
95
+
96
+ assert.deepEqual(requestParams, {
97
+ method: 'POST',
98
+ resource: `clusters`,
99
+ api: 'calliopeDiscovery',
100
+ shouldRefreshAccessToken: false,
101
+ timeout: 3000,
102
+ body: {
103
+ ipver: IP_VERSION.only_ipv4,
104
+ 'supported-options': {
105
+ 'report-version': 1,
106
+ 'early-call-min-clusters': true,
107
+ },
108
+ 'previous-report': previousReport,
109
+ trigger: 'early-call/no-min-reached',
110
+ },
111
+ });
112
+
113
+ assert.deepEqual(res.clusters.clusterId, {udp: "testUDP", isVideoMesh: true})
114
+ assert.deepEqual(res.joinCookie, {anycastEntryPoint: "aws-eu-west-1"})
115
+ assert.notCalled(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency);
116
+ });
81
117
  });
82
118
  });