@webex/plugin-meetings 3.0.0-beta.291 → 3.0.0-beta.293

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 (38) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/webex-errors.js +21 -1
  4. package/dist/common/errors/webex-errors.js.map +1 -1
  5. package/dist/config.js +1 -2
  6. package/dist/config.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/meeting/index.js +357 -173
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meetings/index.js +0 -17
  12. package/dist/meetings/index.js.map +1 -1
  13. package/dist/reconnection-manager/index.js +2 -2
  14. package/dist/reconnection-manager/index.js.map +1 -1
  15. package/dist/roap/index.js +3 -2
  16. package/dist/roap/index.js.map +1 -1
  17. package/dist/roap/turnDiscovery.js +15 -16
  18. package/dist/roap/turnDiscovery.js.map +1 -1
  19. package/dist/types/common/errors/webex-errors.d.ts +12 -0
  20. package/dist/types/config.d.ts +0 -1
  21. package/dist/types/meeting/index.d.ts +42 -1
  22. package/dist/types/meetings/index.d.ts +0 -8
  23. package/dist/types/roap/index.d.ts +2 -1
  24. package/dist/types/roap/turnDiscovery.d.ts +3 -2
  25. package/dist/webinar/index.js +1 -1
  26. package/package.json +19 -19
  27. package/src/common/errors/webex-errors.ts +17 -0
  28. package/src/config.ts +0 -1
  29. package/src/meeting/index.ts +150 -9
  30. package/src/meetings/index.ts +0 -15
  31. package/src/reconnection-manager/index.ts +2 -2
  32. package/src/roap/index.ts +3 -2
  33. package/src/roap/turnDiscovery.ts +8 -12
  34. package/test/unit/spec/meeting/index.js +310 -45
  35. package/test/unit/spec/meetings/index.js +0 -28
  36. package/test/unit/spec/reconnection-manager/index.js +1 -0
  37. package/test/unit/spec/roap/index.ts +16 -22
  38. package/test/unit/spec/roap/turnDiscovery.ts +42 -31
@@ -85,6 +85,7 @@ import {
85
85
  UserNotJoinedError,
86
86
  MeetingNotActiveError,
87
87
  UserInLobbyError,
88
+ AddMediaFailed,
88
89
  } from '../../../../src/common/errors/webex-errors';
89
90
  import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-error';
90
91
  import ParameterError from '../../../../src/common/errors/parameter';
@@ -1190,7 +1191,7 @@ describe('plugin-meetings', () => {
1190
1191
  }
1191
1192
  });
1192
1193
 
1193
- it('should send metrics and reset the statsAnalyzer to null if addMedia throws an error', async () => {
1194
+ it('should send metrics and reset the statsAnalyzer to null if addMedia throws an error without a turn server retry', async () => {
1194
1195
  meeting.meetingState = 'ACTIVE';
1195
1196
 
1196
1197
  meeting.webex.meetings.reachability = {
@@ -1229,6 +1230,7 @@ describe('plugin-meetings', () => {
1229
1230
  {
1230
1231
  correlation_id: meeting.correlationId,
1231
1232
  turnServerUsed: true,
1233
+ retriedWithTurnServer: false,
1232
1234
  latency: undefined,
1233
1235
  }
1234
1236
  );
@@ -1243,6 +1245,7 @@ describe('plugin-meetings', () => {
1243
1245
  code: error.code,
1244
1246
  turnDiscoverySkippedReason: undefined,
1245
1247
  turnServerUsed: true,
1248
+ retriedWithTurnServer: false,
1246
1249
  isMultistream: false,
1247
1250
  signalingState: 'unknown',
1248
1251
  connectionState: 'unknown',
@@ -1253,37 +1256,6 @@ describe('plugin-meetings', () => {
1253
1256
  );
1254
1257
  });
1255
1258
 
1256
- it('checks metrics called with skipped reason config', async () => {
1257
- meeting.roap.doTurnDiscovery = sinon
1258
- .stub()
1259
- .resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: 'config'});
1260
- meeting.meetingState = 'ACTIVE';
1261
- await meeting.addMedia().catch((err) => {
1262
- assert.exists(err);
1263
- assert(webex.internal.newMetrics.submitInternalEvent.calledTwice);
1264
- assert.calledWith(webex.internal.newMetrics.submitInternalEvent.firstCall, {
1265
- name: 'internal.client.add-media.turn-discovery.start',
1266
- });
1267
- assert.calledWith(webex.internal.newMetrics.submitInternalEvent.secondCall, {
1268
- name: 'internal.client.add-media.turn-discovery.end',
1269
- });
1270
- assert(Metrics.sendBehavioralMetric.calledOnce);
1271
- assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
1272
- correlation_id: meeting.correlationId,
1273
- locus_id: meeting.locusUrl.split('/').pop(),
1274
- reason: err.message,
1275
- stack: err.stack,
1276
- code: err.code,
1277
- turnDiscoverySkippedReason: 'config',
1278
- turnServerUsed: false,
1279
- isMultistream: false,
1280
- signalingState: 'unknown',
1281
- connectionState: 'unknown',
1282
- iceConnectionState: 'unknown',
1283
- });
1284
- });
1285
- });
1286
-
1287
1259
  it('should reset the webrtcMediaConnection to null if addMedia throws an error', async () => {
1288
1260
  meeting.meetingState = 'ACTIVE';
1289
1261
  // setup the mock so that a media connection is created, but its initiateOffer() method fails
@@ -1307,6 +1279,7 @@ describe('plugin-meetings', () => {
1307
1279
  {
1308
1280
  correlation_id: meeting.correlationId,
1309
1281
  turnServerUsed: true,
1282
+ retriedWithTurnServer: false,
1310
1283
  latency: undefined,
1311
1284
  }
1312
1285
  );
@@ -1361,6 +1334,7 @@ describe('plugin-meetings', () => {
1361
1334
  {
1362
1335
  correlation_id: meeting.correlationId,
1363
1336
  turnServerUsed: true,
1337
+ retriedWithTurnServer: false,
1364
1338
  latency: undefined,
1365
1339
  }
1366
1340
  );
@@ -1375,6 +1349,7 @@ describe('plugin-meetings', () => {
1375
1349
  code: error.code,
1376
1350
  turnDiscoverySkippedReason: undefined,
1377
1351
  turnServerUsed: true,
1352
+ retriedWithTurnServer: false,
1378
1353
  isMultistream: false,
1379
1354
  signalingState: 'unknown',
1380
1355
  connectionState: 'unknown',
@@ -1419,6 +1394,7 @@ describe('plugin-meetings', () => {
1419
1394
  {
1420
1395
  correlation_id: meeting.correlationId,
1421
1396
  turnServerUsed: true,
1397
+ retriedWithTurnServer: false,
1422
1398
  latency: undefined,
1423
1399
  }
1424
1400
  );
@@ -1475,6 +1451,7 @@ describe('plugin-meetings', () => {
1475
1451
  {
1476
1452
  correlation_id: meeting.correlationId,
1477
1453
  turnServerUsed: true,
1454
+ retriedWithTurnServer: false,
1478
1455
  latency: undefined,
1479
1456
  }
1480
1457
  );
@@ -1630,7 +1607,7 @@ describe('plugin-meetings', () => {
1630
1607
  username: FAKE_TURN_USER,
1631
1608
  password: FAKE_TURN_PASSWORD,
1632
1609
  },
1633
- turnServerSkippedReason: undefined,
1610
+ turnDiscoverySkippedReason: undefined,
1634
1611
  });
1635
1612
  const media = meeting.addMedia({
1636
1613
  mediaSettings: {},
@@ -1678,39 +1655,106 @@ describe('plugin-meetings', () => {
1678
1655
  clock.restore();
1679
1656
  });
1680
1657
 
1681
- it('should reject if waitForMediaConnectionConnected() rejects', async () => {
1658
+ it('should reject if waitForMediaConnectionConnected() rejects after turn server retry', async () => {
1682
1659
  const FAKE_ERROR = {fatal: true};
1683
1660
  const getErrorPayloadForClientErrorCodeStub = webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
1684
1661
  .stub()
1685
1662
  .returns(FAKE_ERROR);
1686
-
1687
1663
  const MOCK_CLIENT_ERROR_CODE = 2004;
1688
1664
  const generateClientErrorCodeForIceFailureStub = sinon
1689
1665
  .stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
1690
1666
  .returns(MOCK_CLIENT_ERROR_CODE);
1667
+ const FAKE_TURN_URL = 'turns:webex.com:3478';
1668
+ const FAKE_TURN_USER = 'some-turn-username';
1669
+ const FAKE_TURN_PASSWORD = 'some-password';
1670
+ let errorThrown = undefined;
1691
1671
 
1692
-
1672
+ // Stub doTurnDiscovery so that on the first call we skip turn discovery
1673
+ meeting.roap.doTurnDiscovery = sinon.stub().onFirstCall().returns({
1674
+ turnServerInfo: undefined,
1675
+ turnDiscoverySkippedReason: 'reachability',
1676
+ }).onSecondCall().returns({
1677
+ turnServerInfo: {
1678
+ url: FAKE_TURN_URL,
1679
+ username: FAKE_TURN_USER,
1680
+ password: FAKE_TURN_PASSWORD,
1681
+ },
1682
+ turnDiscoverySkippedReason: undefined,
1683
+ });
1693
1684
  meeting.meetingState = 'ACTIVE';
1694
1685
  meeting.mediaProperties.waitForMediaConnectionConnected.rejects(new Error('fake error'));
1695
1686
 
1696
- let errorThrown = false;
1687
+ const forceRtcMetricsSend = sinon.stub().resolves();
1688
+ const closeMediaConnectionStub = sinon.stub();
1689
+ Media.createMediaConnection = sinon.stub().returns({
1690
+ close: closeMediaConnectionStub,
1691
+ forceRtcMetricsSend,
1692
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1693
+ initiateOffer: sinon.stub().resolves({}),
1694
+ on: sinon.stub(),
1695
+ });
1697
1696
 
1698
1697
  await meeting
1699
1698
  .addMedia({
1700
1699
  mediaSettings: {},
1701
1700
  })
1702
- .catch(() => {
1703
- errorThrown = true;
1701
+ .catch((err) => {
1702
+ errorThrown = err;
1703
+ assert.instanceOf(err, AddMediaFailed);
1704
1704
  });
1705
1705
 
1706
- assert.calledOnceWithExactly(generateClientErrorCodeForIceFailureStub, {
1706
+ assert.calledTwice(generateClientErrorCodeForIceFailureStub);
1707
+ assert.calledWith(generateClientErrorCodeForIceFailureStub, {
1708
+ signalingState: 'unknown',
1709
+ iceConnectionState: 'unknown',
1710
+ turnServerUsed: false,
1711
+ })
1712
+ assert.calledWith(generateClientErrorCodeForIceFailureStub, {
1707
1713
  signalingState: 'unknown',
1708
1714
  iceConnectionState: 'unknown',
1709
1715
  turnServerUsed: true,
1710
1716
  })
1711
- assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {clientErrorCode: MOCK_CLIENT_ERROR_CODE});
1712
- assert.calledTwice(webex.internal.newMetrics.submitClientEvent);
1713
- assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
1717
+
1718
+ assert.calledTwice(getErrorPayloadForClientErrorCodeStub);
1719
+ assert.alwaysCalledWithExactly(getErrorPayloadForClientErrorCodeStub, {clientErrorCode: MOCK_CLIENT_ERROR_CODE});
1720
+
1721
+ assert.calledThrice(webex.internal.newMetrics.submitClientEvent);
1722
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.firstCall, {
1723
+ name: 'client.media.capabilities',
1724
+ payload: {
1725
+ mediaCapabilities: {
1726
+ rx: {
1727
+ audio: false,
1728
+ share: false,
1729
+ share_audio: false,
1730
+ video: false,
1731
+ whiteboard: false,
1732
+ },
1733
+ tx: {
1734
+ audio: false,
1735
+ share: false,
1736
+ share_audio: false,
1737
+ video: false,
1738
+ whiteboard: false,
1739
+ },
1740
+ },
1741
+ },
1742
+ options: {
1743
+ meetingId: meeting.id,
1744
+ },
1745
+ });
1746
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.secondCall, {
1747
+ name: 'client.ice.end',
1748
+ payload: {
1749
+ canProceed: true,
1750
+ icePhase: 'JOIN_MEETING_RETRY',
1751
+ errors: [FAKE_ERROR],
1752
+ },
1753
+ options: {
1754
+ meetingId: meeting.id,
1755
+ },
1756
+ });
1757
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
1714
1758
  name: 'client.ice.end',
1715
1759
  payload: {
1716
1760
  canProceed: false,
@@ -1722,7 +1766,228 @@ describe('plugin-meetings', () => {
1722
1766
  },
1723
1767
  });
1724
1768
 
1725
- assert.isTrue(errorThrown);
1769
+ // Turn discovery internal events are sent twice this time as we go through establishMediaConnection a second time on the retry
1770
+ const submitInternalEventCalls = webex.internal.newMetrics.submitInternalEvent.getCalls();
1771
+ assert.equal(submitInternalEventCalls.length, 4);
1772
+ assert.deepEqual(submitInternalEventCalls[0].args, [{
1773
+ name: 'internal.client.add-media.turn-discovery.start',
1774
+ }]);
1775
+ assert.deepEqual(submitInternalEventCalls[1].args, [{
1776
+ name: 'internal.client.add-media.turn-discovery.end',
1777
+ }]);
1778
+ assert.deepEqual(submitInternalEventCalls[2].args, [{
1779
+ name: 'internal.client.add-media.turn-discovery.start',
1780
+ }]);
1781
+ assert.deepEqual(submitInternalEventCalls[3].args, [{
1782
+ name: 'internal.client.add-media.turn-discovery.end',
1783
+ }]);
1784
+
1785
+ const sendBehavioralMetricCalls = Metrics.sendBehavioralMetric.getCalls();
1786
+ assert.equal(sendBehavioralMetricCalls.length, 2);
1787
+ assert.deepEqual(sendBehavioralMetricCalls[0].args, [
1788
+ BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY,
1789
+ {
1790
+ correlation_id: meeting.correlationId,
1791
+ turnServerUsed: true,
1792
+ retriedWithTurnServer: true,
1793
+ latency: undefined,
1794
+ }
1795
+ ]);
1796
+ assert.deepEqual(sendBehavioralMetricCalls[1].args, [
1797
+ BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
1798
+ {
1799
+ correlation_id: meeting.correlationId,
1800
+ locus_id: meeting.locusUrl.split('/').pop(),
1801
+ reason: errorThrown.message,
1802
+ stack: errorThrown.stack,
1803
+ code: errorThrown.code,
1804
+ turnDiscoverySkippedReason: undefined,
1805
+ turnServerUsed: true,
1806
+ retriedWithTurnServer: true,
1807
+ isMultistream: false,
1808
+ signalingState: 'unknown',
1809
+ connectionState: 'unknown',
1810
+ iceConnectionState: 'unknown',
1811
+ }
1812
+ ]);
1813
+
1814
+ // Check that doTurnDiscovery is called with th4 correct value of isForced
1815
+ const doTurnDiscoveryCalls = meeting.roap.doTurnDiscovery.getCalls();
1816
+ assert.equal(doTurnDiscoveryCalls.length, 2);
1817
+ assert.deepEqual(doTurnDiscoveryCalls[0].args, [
1818
+ meeting,
1819
+ false,
1820
+ false
1821
+ ]);
1822
+ assert.deepEqual(doTurnDiscoveryCalls[1].args, [
1823
+ meeting,
1824
+ true,
1825
+ true
1826
+ ]);
1827
+
1828
+ // Some clean up steps happens twice
1829
+ assert.calledTwice(forceRtcMetricsSend);
1830
+ assert.calledTwice(closeMediaConnectionStub);
1831
+ assert.isNull(meeting.mediaProperties.webrtcMediaConnection);
1832
+
1833
+ assert.isOk(errorThrown);
1834
+ });
1835
+
1836
+ it('should resolve if waitForMediaConnectionConnected() rejects the first time but resolves the second time', async () => {
1837
+ const FAKE_ERROR = {fatal: true};
1838
+ const getErrorPayloadForClientErrorCodeStub = webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
1839
+ .stub()
1840
+ .returns(FAKE_ERROR);
1841
+ const MOCK_CLIENT_ERROR_CODE = 2004;
1842
+ const generateClientErrorCodeForIceFailureStub = sinon
1843
+ .stub(CallDiagnosticUtils, 'generateClientErrorCodeForIceFailure')
1844
+ .returns(MOCK_CLIENT_ERROR_CODE);
1845
+ const FAKE_TURN_URL = 'turns:webex.com:3478';
1846
+ const FAKE_TURN_USER = 'some-turn-username';
1847
+ const FAKE_TURN_PASSWORD = 'some-password';
1848
+ let errorThrown = undefined;
1849
+
1850
+ meeting.meetingState = 'ACTIVE';
1851
+ meeting.roap.doTurnDiscovery = sinon.stub().onFirstCall().returns({
1852
+ turnServerInfo: undefined,
1853
+ turnDiscoverySkippedReason: 'reachability',
1854
+ }).onSecondCall().returns({
1855
+ turnServerInfo: {
1856
+ url: FAKE_TURN_URL,
1857
+ username: FAKE_TURN_USER,
1858
+ password: FAKE_TURN_PASSWORD,
1859
+ },
1860
+ turnDiscoverySkippedReason: undefined,
1861
+ });
1862
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().onFirstCall().rejects().onSecondCall().resolves();
1863
+
1864
+ const forceRtcMetricsSend = sinon.stub().resolves();
1865
+ const closeMediaConnectionStub = sinon.stub();
1866
+ Media.createMediaConnection = sinon.stub().returns({
1867
+ close: closeMediaConnectionStub,
1868
+ forceRtcMetricsSend,
1869
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1870
+ initiateOffer: sinon.stub().resolves({}),
1871
+ on: sinon.stub(),
1872
+ });
1873
+
1874
+ await meeting
1875
+ .addMedia({
1876
+ mediaSettings: {},
1877
+ })
1878
+ .catch((err) => {
1879
+ errorThrown = err;
1880
+ });
1881
+
1882
+ assert.calledOnce(generateClientErrorCodeForIceFailureStub);
1883
+ assert.calledWith(generateClientErrorCodeForIceFailureStub, {
1884
+ signalingState: 'unknown',
1885
+ iceConnectionState: 'unknown',
1886
+ turnServerUsed: false,
1887
+ })
1888
+
1889
+ assert.calledOnce(getErrorPayloadForClientErrorCodeStub);
1890
+ assert.calledWith(getErrorPayloadForClientErrorCodeStub, {clientErrorCode: MOCK_CLIENT_ERROR_CODE});
1891
+
1892
+ assert.calledThrice(webex.internal.newMetrics.submitClientEvent);
1893
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.firstCall, {
1894
+ name: 'client.media.capabilities',
1895
+ payload: {
1896
+ mediaCapabilities: {
1897
+ rx: {
1898
+ audio: false,
1899
+ share: false,
1900
+ share_audio: false,
1901
+ video: false,
1902
+ whiteboard: false,
1903
+ },
1904
+ tx: {
1905
+ audio: false,
1906
+ share: false,
1907
+ share_audio: false,
1908
+ video: false,
1909
+ whiteboard: false,
1910
+ },
1911
+ },
1912
+ },
1913
+ options: {
1914
+ meetingId: meeting.id,
1915
+ },
1916
+ });
1917
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.secondCall, {
1918
+ name: 'client.ice.end',
1919
+ payload: {
1920
+ canProceed: true,
1921
+ icePhase: 'JOIN_MEETING_RETRY',
1922
+ errors: [FAKE_ERROR],
1923
+ },
1924
+ options: {
1925
+ meetingId: meeting.id,
1926
+ },
1927
+ });
1928
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent.thirdCall, {
1929
+ name: 'client.media-engine.ready',
1930
+ options: {
1931
+ meetingId: meeting.id,
1932
+ },
1933
+ });
1934
+
1935
+ // Turn discovery internal events are sent twice this time as we go through establishMediaConnection a second time on the retry
1936
+ const submitInternalEventCalls = webex.internal.newMetrics.submitInternalEvent.getCalls();
1937
+ assert.equal(submitInternalEventCalls.length, 4);
1938
+ assert.deepEqual(submitInternalEventCalls[0].args, [{
1939
+ name: 'internal.client.add-media.turn-discovery.start',
1940
+ }]);
1941
+ assert.deepEqual(submitInternalEventCalls[1].args, [{
1942
+ name: 'internal.client.add-media.turn-discovery.end',
1943
+ }]);
1944
+ assert.deepEqual(submitInternalEventCalls[2].args, [{
1945
+ name: 'internal.client.add-media.turn-discovery.start',
1946
+ }]);
1947
+ assert.deepEqual(submitInternalEventCalls[3].args, [{
1948
+ name: 'internal.client.add-media.turn-discovery.end',
1949
+ }]);
1950
+
1951
+ const sendBehavioralMetricCalls = Metrics.sendBehavioralMetric.getCalls();
1952
+ assert.equal(sendBehavioralMetricCalls.length, 2);
1953
+ assert.deepEqual(sendBehavioralMetricCalls[0].args, [
1954
+ BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY,
1955
+ {
1956
+ correlation_id: meeting.correlationId,
1957
+ turnServerUsed: true,
1958
+ retriedWithTurnServer: true,
1959
+ latency: undefined,
1960
+ }
1961
+ ]);
1962
+ assert.deepEqual(sendBehavioralMetricCalls[1].args, [
1963
+ BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
1964
+ {
1965
+ correlation_id: meeting.correlationId,
1966
+ locus_id: meeting.locusUrl.split('/').pop(),
1967
+ connectionType: 'udp',
1968
+ isMultistream: false,
1969
+ }
1970
+ ]);
1971
+ meeting.roap.doTurnDiscovery
1972
+
1973
+ // Check that doTurnDiscovery is called with th4 correct value of isForced
1974
+ const doTurnDiscoveryCalls = meeting.roap.doTurnDiscovery.getCalls();
1975
+ assert.equal(doTurnDiscoveryCalls.length, 2);
1976
+ assert.deepEqual(doTurnDiscoveryCalls[0].args, [
1977
+ meeting,
1978
+ false,
1979
+ false
1980
+ ]);
1981
+ assert.deepEqual(doTurnDiscoveryCalls[1].args, [
1982
+ meeting,
1983
+ true,
1984
+ true
1985
+ ]);
1986
+
1987
+ assert.calledOnce(forceRtcMetricsSend);
1988
+ assert.calledOnce(closeMediaConnectionStub);
1989
+
1990
+ assert.isNotOk(errorThrown);
1726
1991
  });
1727
1992
 
1728
1993
  it('should send ADD_MEDIA_SUCCESS metrics', async () => {
@@ -1907,7 +2172,7 @@ describe('plugin-meetings', () => {
1907
2172
  username: FAKE_TURN_USER,
1908
2173
  password: FAKE_TURN_PASSWORD,
1909
2174
  },
1910
- turnServerSkippedReason: undefined,
2175
+ turnDiscoverySkippedReason: undefined,
1911
2176
  });
1912
2177
  const media = meeting.addMedia({
1913
2178
  mediaSettings: {},
@@ -236,34 +236,6 @@ describe('plugin-meetings', () => {
236
236
  });
237
237
  });
238
238
 
239
- describe('#_toggleTurnDiscovery', () => {
240
- it('should have toggleAdhocMeetings', () => {
241
- assert.equal(typeof webex.meetings._toggleTurnDiscovery, 'function');
242
- });
243
-
244
- describe('success', () => {
245
- it('should update meetings to do TURN discovery', () => {
246
- webex.meetings._toggleTurnDiscovery(true);
247
- assert.equal(webex.meetings.config.experimental.enableTurnDiscovery, true);
248
-
249
- webex.meetings._toggleTurnDiscovery(false);
250
- assert.equal(webex.meetings.config.experimental.enableTurnDiscovery, false);
251
- });
252
- });
253
-
254
- describe('failure', () => {
255
- it('should not accept non boolean input', () => {
256
- const currentEnableTurnDiscovery = webex.meetings.config.experimental.enableTurnDiscovery;
257
-
258
- webex.meetings._toggleTurnDiscovery('test');
259
- assert.equal(
260
- webex.meetings.config.experimental.enableAdhocMeetings,
261
- currentEnableTurnDiscovery
262
- );
263
- });
264
- });
265
- });
266
-
267
239
  describe('Public API Contracts', () => {
268
240
  describe('#register', () => {
269
241
  it('emits an event and resolves when register succeeds', async () => {
@@ -98,6 +98,7 @@ describe('plugin-meetings', () => {
98
98
  await rm.reconnect();
99
99
 
100
100
  assert.calledOnce(fakeMeeting.roap.doTurnDiscovery);
101
+ assert.calledWith(fakeMeeting.roap.doTurnDiscovery, fakeMeeting, true, true);
101
102
  assert.calledOnce(fakeMediaConnection.reconnect);
102
103
  assert.calledWith(fakeMediaConnection.reconnect, [
103
104
  {
@@ -12,33 +12,28 @@ import { IP_VERSION } from '../../../../src/constants';
12
12
 
13
13
  describe('Roap', () => {
14
14
  describe('doTurnDiscovery', () => {
15
- it('calls this.turnDiscovery.doTurnDiscovery() and forwards all the arguments', async () => {
16
- const webex = new MockWebex({});
15
+ [false, true].forEach(function (isReconnecting) {
16
+ [false, true, undefined].forEach(function (isForced) {
17
+ it(`calls this.turnDiscovery.doTurnDiscovery() and forwards all the arguments when isReconnecting = ${isReconnecting} and isForced = ${isForced}`, async () => {
18
+ const webex = new MockWebex({});
17
19
 
18
- const RESULT = {something: 'some value'};
19
- const meeting = {id: 'some meeting id'} as Meeting;
20
+ const RESULT = {something: 'some value'};
21
+ const meeting = {id: 'some meeting id'} as Meeting;
20
22
 
21
- const doTurnDiscoveryStub = sinon
22
- .stub(TurnDiscovery.prototype, 'doTurnDiscovery')
23
- .resolves(RESULT);
23
+ const doTurnDiscoveryStub = sinon
24
+ .stub(TurnDiscovery.prototype, 'doTurnDiscovery')
25
+ .resolves(RESULT);
24
26
 
25
- const roap = new Roap({}, {parent: webex});
27
+ const roap = new Roap({}, {parent: webex});
26
28
 
27
- // call with isReconnecting: true
28
- const result = await roap.doTurnDiscovery(meeting, true);
29
+ const result = await roap.doTurnDiscovery(meeting, isReconnecting, isForced);
29
30
 
30
- assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, true);
31
- assert.deepEqual(result, RESULT);
31
+ assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, isReconnecting, isForced);
32
+ assert.deepEqual(result, RESULT);
32
33
 
33
- doTurnDiscoveryStub.resetHistory();
34
-
35
- // and with isReconnecting: false
36
- const result2 = await roap.doTurnDiscovery(meeting, false);
37
-
38
- assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, false);
39
- assert.deepEqual(result2, RESULT);
40
-
41
- sinon.restore();
34
+ sinon.restore();
35
+ });
36
+ });
42
37
  });
43
38
  });
44
39
 
@@ -62,7 +57,6 @@ describe('Roap', () => {
62
57
  isLocallyMuted: () => false,
63
58
  },
64
59
  setRoapSeq: sinon.stub(),
65
- config: {experimental: {enableTurnDiscovery: false}},
66
60
  locusMediaRequest: {fake: true},
67
61
  webex: { meetings: { reachability: { isAnyPublicClusterReachable: () => true}}},
68
62
  };
@@ -33,11 +33,6 @@ describe('TurnDiscovery', () => {
33
33
 
34
34
  testMeeting = {
35
35
  id: 'fake meeting id',
36
- config: {
37
- experimental: {
38
- enableTurnDiscovery: true,
39
- },
40
- },
41
36
  correlationId: 'fake correlation id',
42
37
  selfUrl: 'fake self url',
43
38
  mediaId: 'fake media id',
@@ -153,6 +148,44 @@ describe('TurnDiscovery', () => {
153
148
  });
154
149
  });
155
150
 
151
+ it('sends TURN_DISCOVERY_REQUEST, waits for response and sends OK when isForced = true when cluster is reachable', async () => {
152
+ const prev = testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable;
153
+ testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = sinon.stub().resolves(true);
154
+
155
+ const td = new TurnDiscovery(mockRoapRequest);
156
+ const result = td.doTurnDiscovery(testMeeting, false, true);
157
+
158
+ // We ignore reachability results so we don't get skip reason
159
+ assert.notCalled(testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable);
160
+
161
+ // check that TURN_DISCOVERY_REQUEST was sent
162
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
163
+ // @ts-ignore
164
+ mockRoapRequest.sendRoap.resetHistory();
165
+ // simulate the response
166
+ td.handleTurnDiscoveryResponse({
167
+ headers: [
168
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
169
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
170
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
171
+ ]
172
+ });
173
+ await testUtils.flushPromises();
174
+ // check that we've sent OK
175
+ await checkRoapMessageSent('OK', 0);
176
+
177
+ const {turnServerInfo, turnDiscoverySkippedReason} = await result;
178
+ assert.deepEqual(turnServerInfo, {
179
+ url: FAKE_TURN_URL,
180
+ username: FAKE_TURN_USERNAME,
181
+ password: FAKE_TURN_PASSWORD
182
+ });
183
+ assert.isUndefined(turnDiscoverySkippedReason);
184
+
185
+ // restore previous callback
186
+ testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = prev;
187
+ });
188
+
156
189
  it('sends TURN_DISCOVERY_REQUEST with empty mediaId when isReconnecting is true', async () => {
157
190
  const td = new TurnDiscovery(mockRoapRequest);
158
191
 
@@ -224,24 +257,6 @@ describe('TurnDiscovery', () => {
224
257
  assert.isUndefined(turnDiscoverySkippedReason);
225
258
  });
226
259
 
227
- it('resolves with undefined if turn discovery feature is disabled in config', async () => {
228
- const prevConfigValue = testMeeting.config.experimental.enableTurnDiscovery;
229
-
230
- testMeeting.config.experimental.enableTurnDiscovery = false;
231
- // @ts-ignore
232
- const result = await new TurnDiscovery(mockRoapRequest).doTurnDiscovery(testMeeting);
233
-
234
- const {turnServerInfo, turnDiscoverySkippedReason} = result;
235
-
236
- assert.isUndefined(turnServerInfo);
237
- assert.equal(turnDiscoverySkippedReason, 'config');
238
- assert.notCalled(mockRoapRequest.sendRoap);
239
- assert.notCalled(Metrics.sendBehavioralMetric);
240
-
241
- // restore previous config
242
- testMeeting.config.experimental.enableTurnDiscovery = prevConfigValue;
243
- });
244
-
245
260
  it('resolves with undefined if sending the request fails', async () => {
246
261
  const td = new TurnDiscovery(mockRoapRequest);
247
262
 
@@ -379,14 +394,10 @@ describe('TurnDiscovery', () => {
379
394
 
380
395
  describe('isSkipped', () => {
381
396
  [
382
- {enabledInConfig: true, isAnyPublicClusterReachable: true, expectedIsSkipped: true},
383
- {enabledInConfig: true, isAnyPublicClusterReachable: false, expectedIsSkipped: false},
384
- {enabledInConfig: false, isAnyPublicClusterReachable: true, expectedIsSkipped: true},
385
- {enabledInConfig: false, isAnyPublicClusterReachable: false, expectedIsSkipped: true},
386
- ].forEach(({enabledInConfig, isAnyPublicClusterReachable, expectedIsSkipped}) => {
387
- it(`returns ${expectedIsSkipped} when TURN discovery is ${enabledInConfig ? '' : 'not '} enabled in config and isAnyPublicClusterReachable() returns ${isAnyPublicClusterReachable ? 'true' : 'false'}`, async () => {
388
- testMeeting.config.experimental.enableTurnDiscovery = enabledInConfig;
389
-
397
+ {isAnyPublicClusterReachable: true, expectedIsSkipped: true},
398
+ {isAnyPublicClusterReachable: false, expectedIsSkipped: false},
399
+ ].forEach(({isAnyPublicClusterReachable, expectedIsSkipped}) => {
400
+ it(`returns ${expectedIsSkipped} when isAnyPublicClusterReachable() returns ${isAnyPublicClusterReachable ? 'true' : 'false'}`, async () => {
390
401
  sinon.stub(testMeeting.webex.meetings.reachability, 'isAnyPublicClusterReachable').resolves(isAnyPublicClusterReachable);
391
402
 
392
403
  const td = new TurnDiscovery(mockRoapRequest);