@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.
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/webex-errors.js +21 -1
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/config.js +1 -2
- package/dist/config.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +357 -173
- package/dist/meeting/index.js.map +1 -1
- package/dist/meetings/index.js +0 -17
- package/dist/meetings/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +2 -2
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +3 -2
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/turnDiscovery.js +15 -16
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/types/common/errors/webex-errors.d.ts +12 -0
- package/dist/types/config.d.ts +0 -1
- package/dist/types/meeting/index.d.ts +42 -1
- package/dist/types/meetings/index.d.ts +0 -8
- package/dist/types/roap/index.d.ts +2 -1
- package/dist/types/roap/turnDiscovery.d.ts +3 -2
- package/dist/webinar/index.js +1 -1
- package/package.json +19 -19
- package/src/common/errors/webex-errors.ts +17 -0
- package/src/config.ts +0 -1
- package/src/meeting/index.ts +150 -9
- package/src/meetings/index.ts +0 -15
- package/src/reconnection-manager/index.ts +2 -2
- package/src/roap/index.ts +3 -2
- package/src/roap/turnDiscovery.ts +8 -12
- package/test/unit/spec/meeting/index.js +310 -45
- package/test/unit/spec/meetings/index.js +0 -28
- package/test/unit/spec/reconnection-manager/index.js +1 -0
- package/test/unit/spec/roap/index.ts +16 -22
- 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
|
-
|
|
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
|
-
|
|
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 =
|
|
1701
|
+
.catch((err) => {
|
|
1702
|
+
errorThrown = err;
|
|
1703
|
+
assert.instanceOf(err, AddMediaFailed);
|
|
1704
1704
|
});
|
|
1705
1705
|
|
|
1706
|
-
assert.
|
|
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
|
-
|
|
1712
|
-
assert.calledTwice(
|
|
1713
|
-
assert.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
+
const RESULT = {something: 'some value'};
|
|
21
|
+
const meeting = {id: 'some meeting id'} as Meeting;
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const doTurnDiscoveryStub = sinon
|
|
24
|
+
.stub(TurnDiscovery.prototype, 'doTurnDiscovery')
|
|
25
|
+
.resolves(RESULT);
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
const roap = new Roap({}, {parent: webex});
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
const result = await roap.doTurnDiscovery(meeting, true);
|
|
29
|
+
const result = await roap.doTurnDiscovery(meeting, isReconnecting, isForced);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, isReconnecting, isForced);
|
|
32
|
+
assert.deepEqual(result, RESULT);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
{
|
|
383
|
-
{
|
|
384
|
-
|
|
385
|
-
{
|
|
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);
|