@webex/plugin-meetings 2.60.1-next.13 → 2.60.1-next.15
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/mediaQualityMetrics/config.d.ts +103 -99
- package/dist/mediaQualityMetrics/config.js +133 -129
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/index.d.ts +0 -1
- package/dist/meeting/index.js +7 -15
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.d.ts +2 -0
- package/dist/meeting/request.js +4 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/voicea-meeting.d.ts +0 -4
- package/dist/meeting/voicea-meeting.js +26 -58
- package/dist/meeting/voicea-meeting.js.map +1 -1
- package/dist/meetings/index.js +19 -0
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/metrics/constants.d.ts +2 -0
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/index.js +14 -20
- package/dist/reachability/index.js.map +1 -1
- package/dist/reconnection-manager/index.js +63 -43
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/turnDiscovery.d.ts +18 -2
- package/dist/roap/turnDiscovery.js +163 -69
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/rtcMetrics/index.d.ts +7 -0
- package/dist/rtcMetrics/index.js +38 -1
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/index.js +135 -23
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.d.ts +28 -4
- package/dist/statsAnalyzer/mqaUtil.js +278 -148
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/webinar/index.js +1 -1
- package/package.json +21 -21
- package/src/mediaQualityMetrics/config.ts +107 -107
- package/src/meeting/index.ts +5 -18
- package/src/meeting/request.ts +6 -0
- package/src/meeting/voicea-meeting.ts +26 -65
- package/src/meetings/index.ts +22 -0
- package/src/meetings/util.ts +1 -1
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/index.ts +0 -6
- package/src/reconnection-manager/index.ts +18 -7
- package/src/roap/turnDiscovery.ts +100 -24
- package/src/rtcMetrics/index.ts +43 -1
- package/src/statsAnalyzer/index.ts +158 -24
- package/src/statsAnalyzer/mqaUtil.ts +302 -154
- package/test/unit/spec/meeting/index.js +195 -4
- package/test/unit/spec/meeting/request.js +2 -0
- package/test/unit/spec/meeting/voicea-meeting.ts +266 -0
- package/test/unit/spec/meetings/utils.js +35 -8
- package/test/unit/spec/reachability/index.ts +74 -0
- package/test/unit/spec/reconnection-manager/index.js +36 -1
- package/test/unit/spec/roap/turnDiscovery.ts +326 -76
- package/test/unit/spec/rtcMetrics/index.ts +32 -3
- package/test/unit/spec/stats-analyzer/index.js +439 -1
- package/test/utils/webex-test-users.js +12 -4
|
@@ -128,6 +128,11 @@ describe('gatherReachability', () => {
|
|
|
128
128
|
'reachability.result',
|
|
129
129
|
JSON.stringify({old: 'results'})
|
|
130
130
|
);
|
|
131
|
+
await webex.boundedStorage.put(
|
|
132
|
+
'Reachability',
|
|
133
|
+
'reachability.joinCookie',
|
|
134
|
+
JSON.stringify({old: 'joinCookie'})
|
|
135
|
+
);
|
|
131
136
|
});
|
|
132
137
|
|
|
133
138
|
afterEach(() => {
|
|
@@ -169,6 +174,75 @@ describe('gatherReachability', () => {
|
|
|
169
174
|
assert.equal(JSON.stringify(getClustersResult.joinCookie), storedResultForJoinCookie);
|
|
170
175
|
});
|
|
171
176
|
|
|
177
|
+
it('keeps the stored reachability from previous call to gatherReachability if getClusters fails', async () => {
|
|
178
|
+
const reachability = new Reachability(webex);
|
|
179
|
+
|
|
180
|
+
const reachabilityResults = {
|
|
181
|
+
clusters: {
|
|
182
|
+
clusterId: {
|
|
183
|
+
udp: 'testUDP',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
const getClustersResult = {
|
|
188
|
+
clusters: {clusterId: 'cluster'},
|
|
189
|
+
joinCookie: {id: 'id'},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
reachability.reachabilityRequest.getClusters = sinon.stub().throws();
|
|
193
|
+
|
|
194
|
+
const result = await reachability.gatherReachability();
|
|
195
|
+
|
|
196
|
+
assert.empty(result);
|
|
197
|
+
|
|
198
|
+
const storedResultForReachabilityResult = await webex.boundedStorage.get(
|
|
199
|
+
'Reachability',
|
|
200
|
+
'reachability.result'
|
|
201
|
+
);
|
|
202
|
+
const storedResultForJoinCookie = await webex.boundedStorage.get(
|
|
203
|
+
'Reachability',
|
|
204
|
+
'reachability.joinCookie'
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
|
|
208
|
+
assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('keeps the stored reachability from previous call to gatherReachability if performReachabilityChecks fails', async () => {
|
|
212
|
+
const reachability = new Reachability(webex);
|
|
213
|
+
|
|
214
|
+
const reachabilityResults = {
|
|
215
|
+
clusters: {
|
|
216
|
+
clusterId: {
|
|
217
|
+
udp: 'testUDP',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
const getClustersResult = {
|
|
222
|
+
clusters: {clusterId: 'cluster'},
|
|
223
|
+
joinCookie: {id: 'id'},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
reachability.reachabilityRequest.getClusters = sinon.stub().returns(getClustersResult);
|
|
227
|
+
(reachability as any).performReachabilityChecks = sinon.stub().throws();
|
|
228
|
+
|
|
229
|
+
const result = await reachability.gatherReachability();
|
|
230
|
+
|
|
231
|
+
assert.empty(result);
|
|
232
|
+
|
|
233
|
+
const storedResultForReachabilityResult = await webex.boundedStorage.get(
|
|
234
|
+
'Reachability',
|
|
235
|
+
'reachability.result'
|
|
236
|
+
);
|
|
237
|
+
const storedResultForJoinCookie = await webex.boundedStorage.get(
|
|
238
|
+
'Reachability',
|
|
239
|
+
'reachability.joinCookie'
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
assert.equal(JSON.stringify({old: 'results'}), storedResultForReachabilityResult);
|
|
243
|
+
assert.equal(JSON.stringify({old: 'joinCookie'}), storedResultForJoinCookie);
|
|
244
|
+
});
|
|
245
|
+
|
|
172
246
|
it('starts ClusterReachability on each media cluster', async () => {
|
|
173
247
|
webex.config.meetings.experimental = {enableTcpReachability: true};
|
|
174
248
|
|
|
@@ -4,6 +4,8 @@ import chaiAsPromised from 'chai-as-promised';
|
|
|
4
4
|
import sinon from 'sinon';
|
|
5
5
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
|
6
6
|
import { RECONNECTION } from '../../../../src/constants';
|
|
7
|
+
import LoggerProxy from '../../../../src/common/logs/logger-proxy';
|
|
8
|
+
import LoggerConfig from '../../../../src/common/logs/logger-config';
|
|
7
9
|
|
|
8
10
|
const {assert} = chai;
|
|
9
11
|
|
|
@@ -12,8 +14,16 @@ sinon.assert.expose(chai.assert, {prefix: ''});
|
|
|
12
14
|
|
|
13
15
|
describe('plugin-meetings', () => {
|
|
14
16
|
describe('ReconnectionManager.reconnect', () => {
|
|
17
|
+
const sandbox = sinon.createSandbox();
|
|
15
18
|
let fakeMediaConnection;
|
|
16
19
|
let fakeMeeting;
|
|
20
|
+
let loggerSpy;
|
|
21
|
+
|
|
22
|
+
before(() => {
|
|
23
|
+
LoggerConfig.set({ enable: false });
|
|
24
|
+
LoggerProxy.set();
|
|
25
|
+
loggerSpy = sandbox.spy(LoggerProxy.logger, 'info');
|
|
26
|
+
});
|
|
17
27
|
|
|
18
28
|
beforeEach(() => {
|
|
19
29
|
fakeMediaConnection = {
|
|
@@ -65,6 +75,7 @@ describe('plugin-meetings', () => {
|
|
|
65
75
|
meetings: {
|
|
66
76
|
getMeetingByType: sinon.stub().returns(true),
|
|
67
77
|
syncMeetings: sinon.stub().resolves({}),
|
|
78
|
+
startReachability: sinon.stub().resolves({}),
|
|
68
79
|
},
|
|
69
80
|
internal: {
|
|
70
81
|
newMetrics: {
|
|
@@ -75,6 +86,10 @@ describe('plugin-meetings', () => {
|
|
|
75
86
|
};
|
|
76
87
|
});
|
|
77
88
|
|
|
89
|
+
afterEach(() => {
|
|
90
|
+
sandbox.reset();
|
|
91
|
+
});
|
|
92
|
+
|
|
78
93
|
it('syncs meetings if it is not an unverified guest', async () => {
|
|
79
94
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
80
95
|
|
|
@@ -94,6 +109,27 @@ describe('plugin-meetings', () => {
|
|
|
94
109
|
assert.notCalled(rm.webex.meetings.syncMeetings);
|
|
95
110
|
});
|
|
96
111
|
|
|
112
|
+
it('calls startReachability on reconnect', async () => {
|
|
113
|
+
const rm = new ReconnectionManager(fakeMeeting);
|
|
114
|
+
|
|
115
|
+
await rm.reconnect();
|
|
116
|
+
|
|
117
|
+
assert.calledOnce(rm.webex.meetings.startReachability);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('continues with reconnection attempt if startReachability throws an error', async () => {
|
|
121
|
+
const reachabilityError = new Error();
|
|
122
|
+
fakeMeeting.webex.meetings.startReachability = sinon.stub().throws(reachabilityError);
|
|
123
|
+
|
|
124
|
+
const rm = new ReconnectionManager(fakeMeeting);
|
|
125
|
+
|
|
126
|
+
await rm.reconnect();
|
|
127
|
+
|
|
128
|
+
assert.calledOnce(rm.webex.meetings.startReachability);
|
|
129
|
+
assert.calledWith(loggerSpy, 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ', reachabilityError);
|
|
130
|
+
assert.calledWith(loggerSpy, 'ReconnectionManager:index#executeReconnection --> Attempting to reconnect to meeting.');
|
|
131
|
+
});
|
|
132
|
+
|
|
97
133
|
it('uses correct TURN TLS information on the reconnection', async () => {
|
|
98
134
|
const rm = new ReconnectionManager(fakeMeeting);
|
|
99
135
|
|
|
@@ -142,7 +178,6 @@ describe('plugin-meetings', () => {
|
|
|
142
178
|
assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
|
|
143
179
|
});
|
|
144
180
|
|
|
145
|
-
|
|
146
181
|
it('sends the correct client event when reconnection fails', async () => {
|
|
147
182
|
sinon.stub(ReconnectionManager.prototype, 'executeReconnection').rejects();
|
|
148
183
|
fakeMeeting.isMultistream = true;
|
|
@@ -84,19 +84,13 @@ describe('TurnDiscovery', () => {
|
|
|
84
84
|
|
|
85
85
|
if (messageType === 'TURN_DISCOVERY_REQUEST') {
|
|
86
86
|
expectedSendRoapArgs.ipVersion = 0;
|
|
87
|
+
expectedSendRoapArgs.roapMessage.headers = ['includeAnswerInHttpResponse', 'noOkInTransaction'];
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
assert.calledWith(mockRoapRequest.sendRoap, expectedSendRoapArgs);
|
|
90
|
-
|
|
91
|
-
if (messageType === 'TURN_DISCOVERY_REQUEST') {
|
|
92
|
-
// check also that we've applied the media connections from the response
|
|
93
|
-
assert.calledOnce(testMeeting.updateMediaConnections);
|
|
94
|
-
assert.calledWith(testMeeting.updateMediaConnections, FAKE_MEDIA_CONNECTIONS_FROM_LOCUS);
|
|
95
|
-
}
|
|
96
91
|
};
|
|
97
92
|
|
|
98
93
|
const checkFailureMetricsSent = () => {
|
|
99
|
-
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
100
94
|
assert.calledWith(
|
|
101
95
|
Metrics.sendBehavioralMetric,
|
|
102
96
|
BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,
|
|
@@ -107,50 +101,281 @@ describe('TurnDiscovery', () => {
|
|
|
107
101
|
);
|
|
108
102
|
};
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
const checkHttpResponseMissingMetricsSent = () => {
|
|
105
|
+
assert.calledWith(
|
|
106
|
+
Metrics.sendBehavioralMetric,
|
|
107
|
+
BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING,
|
|
108
|
+
sinon.match({
|
|
109
|
+
correlationId: testMeeting.correlationId,
|
|
110
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
111
|
+
isMultistream: testMeeting.isMultistream,
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
};
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
describe('doTurnDiscovery', () => {
|
|
117
|
+
[false, true].forEach(function (enabledMultistream) {
|
|
118
|
+
describe('when Multistream is ' + (enabledMultistream ? 'enabled' : 'disabled'), () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
testMeeting.isMultistream = enabledMultistream;
|
|
121
|
+
});
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
// checks that OK roap message was sent or not sent and that the result is as expected
|
|
124
|
+
const checkResult = async (resultPromise, expectedRoapMessageSent, expectedResult) => {
|
|
125
|
+
let turnServerInfo, turnDiscoverySkippedReason;
|
|
126
|
+
|
|
127
|
+
if (expectedRoapMessageSent === 'OK') {
|
|
128
|
+
await testUtils.flushPromises();
|
|
129
|
+
|
|
130
|
+
// check that we've sent OK
|
|
131
|
+
await checkRoapMessageSent('OK', 0);
|
|
132
|
+
|
|
133
|
+
assert.calledWith(
|
|
134
|
+
Metrics.sendBehavioralMetric,
|
|
135
|
+
BEHAVIORAL_METRICS.TURN_DISCOVERY_REQUIRES_OK,
|
|
136
|
+
sinon.match({
|
|
137
|
+
correlation_id: testMeeting.correlationId,
|
|
138
|
+
locus_id: FAKE_LOCUS_ID,
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
({turnServerInfo, turnDiscoverySkippedReason} = await resultPromise);
|
|
143
|
+
} else {
|
|
144
|
+
({turnServerInfo, turnDiscoverySkippedReason} = await resultPromise);
|
|
145
|
+
|
|
146
|
+
await testUtils.flushPromises();
|
|
147
|
+
|
|
148
|
+
// check that we didn't send OK or any other message
|
|
149
|
+
assert.notCalled(mockRoapRequest.sendRoap);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
assert.deepEqual(turnServerInfo, expectedResult);
|
|
153
|
+
assert.isUndefined(turnDiscoverySkippedReason);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
it('sends TURN_DISCOVERY_REQUEST, waits for response and sends OK', async () => {
|
|
157
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
158
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
159
|
+
|
|
160
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
161
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
162
|
+
|
|
163
|
+
// check also that we've applied the media connections from the response
|
|
164
|
+
assert.calledOnce(testMeeting.updateMediaConnections);
|
|
165
|
+
assert.calledWith(testMeeting.updateMediaConnections, FAKE_MEDIA_CONNECTIONS_FROM_LOCUS);
|
|
166
|
+
|
|
167
|
+
// response is not in http response, so we expect a metric for that
|
|
168
|
+
checkHttpResponseMissingMetricsSent();
|
|
169
|
+
|
|
170
|
+
// @ts-ignore
|
|
171
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
172
|
+
|
|
173
|
+
// simulate the response
|
|
174
|
+
td.handleTurnDiscoveryResponse(
|
|
175
|
+
{
|
|
176
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
177
|
+
headers: [
|
|
178
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
179
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
180
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
'from test'
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await checkResult(result, 'OK', {
|
|
187
|
+
url: FAKE_TURN_URL,
|
|
188
|
+
username: FAKE_TURN_USERNAME,
|
|
189
|
+
password: FAKE_TURN_PASSWORD,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
118
192
|
|
|
119
|
-
|
|
120
|
-
|
|
193
|
+
it('sends TURN_DISCOVERY_REQUEST, waits for response and does not send OK if response received from Mercury has "noOkInTransaction" header', async () => {
|
|
194
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
195
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
196
|
+
|
|
197
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
198
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
199
|
+
|
|
200
|
+
// check also that we've applied the media connections from the response
|
|
201
|
+
assert.calledOnce(testMeeting.updateMediaConnections);
|
|
202
|
+
assert.calledWith(testMeeting.updateMediaConnections, FAKE_MEDIA_CONNECTIONS_FROM_LOCUS);
|
|
203
|
+
|
|
204
|
+
// @ts-ignore
|
|
205
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
206
|
+
|
|
207
|
+
// simulate the response
|
|
208
|
+
td.handleTurnDiscoveryResponse(
|
|
209
|
+
{
|
|
210
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
211
|
+
headers: [
|
|
212
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
213
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
214
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
215
|
+
'noOkInTransaction',
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
'from test'
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
await checkResult(result, undefined, {
|
|
222
|
+
url: FAKE_TURN_URL,
|
|
223
|
+
username: FAKE_TURN_USERNAME,
|
|
224
|
+
password: FAKE_TURN_PASSWORD,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
121
227
|
|
|
122
|
-
|
|
123
|
-
|
|
228
|
+
it('sends TURN_DISCOVERY_REQUEST, handles http response and does not send OK if received response has "noOkInTransaction" header', async () => {
|
|
229
|
+
mockRoapRequest.sendRoap = sinon.fake.resolves({
|
|
230
|
+
mediaConnections: [
|
|
231
|
+
{
|
|
232
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
233
|
+
remoteSdp: `{"roapMessage": {"messageType":"TURN_DISCOVERY_RESPONSE","seq":"0","headers": ["x-cisco-turn-url=${FAKE_TURN_URL}","x-cisco-turn-username=${FAKE_TURN_USERNAME}","x-cisco-turn-password=${FAKE_TURN_PASSWORD}", "noOkInTransaction"]}}`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
239
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
240
|
+
|
|
241
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
242
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
243
|
+
|
|
244
|
+
// @ts-ignore
|
|
245
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
246
|
+
|
|
247
|
+
await checkResult(result, undefined, {
|
|
248
|
+
url: FAKE_TURN_URL,
|
|
249
|
+
username: FAKE_TURN_USERNAME,
|
|
250
|
+
password: FAKE_TURN_PASSWORD,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
124
253
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
254
|
+
it('sends TURN_DISCOVERY_REQUEST, handles http response and sends OK if received response does not have "noOkInTransaction" header', async () => {
|
|
255
|
+
let sendRoapPromiseResolve;
|
|
256
|
+
const sendRoapResult = {
|
|
257
|
+
mediaConnections: [
|
|
258
|
+
{
|
|
259
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
260
|
+
remoteSdp: `{"roapMessage": {"messageType":"TURN_DISCOVERY_RESPONSE","seq":"0","headers": ["x-cisco-turn-url=${FAKE_TURN_URL}","x-cisco-turn-username=${FAKE_TURN_USERNAME}","x-cisco-turn-password=${FAKE_TURN_PASSWORD}"]}}`,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
mockRoapRequest.sendRoap = sinon.fake.returns(new Promise((resolve) => {
|
|
265
|
+
sendRoapPromiseResolve = resolve;
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
269
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
270
|
+
|
|
271
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
272
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
273
|
+
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
276
|
+
// simulate the http response without 'noOkInTransaction' header
|
|
277
|
+
sendRoapPromiseResolve(sendRoapResult);
|
|
278
|
+
|
|
279
|
+
await checkResult(result, 'OK', {
|
|
280
|
+
url: FAKE_TURN_URL,
|
|
281
|
+
username: FAKE_TURN_USERNAME,
|
|
282
|
+
password: FAKE_TURN_PASSWORD,
|
|
283
|
+
});
|
|
132
284
|
});
|
|
133
285
|
|
|
134
|
-
|
|
286
|
+
it('handles http response that has invalid JSON in the remoteSdp field', async () => {
|
|
287
|
+
mockRoapRequest.sendRoap = sinon.fake.resolves({
|
|
288
|
+
mediaConnections: [
|
|
289
|
+
{
|
|
290
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
291
|
+
remoteSdp: `not a json`,
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
297
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
135
298
|
|
|
136
|
-
|
|
137
|
-
|
|
299
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
300
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
138
301
|
|
|
139
|
-
|
|
302
|
+
// @ts-ignore
|
|
303
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
140
304
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
username: FAKE_TURN_USERNAME,
|
|
144
|
-
password: FAKE_TURN_PASSWORD
|
|
305
|
+
await checkResult(result, undefined, undefined);
|
|
306
|
+
checkFailureMetricsSent();
|
|
145
307
|
});
|
|
146
308
|
|
|
147
|
-
|
|
309
|
+
it('waits for response from Mercury if http response does not contain a roapMessage', async () => {
|
|
310
|
+
mockRoapRequest.sendRoap = sinon.fake.resolves({
|
|
311
|
+
mediaConnections: [
|
|
312
|
+
{
|
|
313
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
314
|
+
remoteSdp: `{"something": "whatever"}`,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
320
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
321
|
+
|
|
322
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
323
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
324
|
+
|
|
325
|
+
checkHttpResponseMissingMetricsSent();
|
|
326
|
+
|
|
327
|
+
// @ts-ignore
|
|
328
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
329
|
+
|
|
330
|
+
// simulate the response coming from Mercury
|
|
331
|
+
td.handleTurnDiscoveryResponse(
|
|
332
|
+
{
|
|
333
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
334
|
+
headers: [
|
|
335
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
336
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
337
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
'from test'
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
await checkResult(result, 'OK', {
|
|
344
|
+
url: FAKE_TURN_URL,
|
|
345
|
+
username: FAKE_TURN_USERNAME,
|
|
346
|
+
password: FAKE_TURN_PASSWORD,
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('handles unexpected roap message type in http response', async () => {
|
|
351
|
+
mockRoapRequest.sendRoap = sinon.fake.resolves({
|
|
352
|
+
mediaConnections: [
|
|
353
|
+
{
|
|
354
|
+
mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274',
|
|
355
|
+
remoteSdp: `{"roapMessage": {"messageType":"ERROR","seq":"0"}}`,
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const td = new TurnDiscovery(mockRoapRequest);
|
|
361
|
+
const result = td.doTurnDiscovery(testMeeting, false);
|
|
362
|
+
|
|
363
|
+
// check that TURN_DISCOVERY_REQUEST was sent
|
|
364
|
+
await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
|
|
365
|
+
|
|
366
|
+
// @ts-ignore
|
|
367
|
+
mockRoapRequest.sendRoap.resetHistory();
|
|
368
|
+
|
|
369
|
+
await checkResult(result, undefined, undefined);
|
|
370
|
+
});
|
|
148
371
|
});
|
|
149
372
|
});
|
|
150
373
|
|
|
151
374
|
it('sends TURN_DISCOVERY_REQUEST, waits for response and sends OK when isForced = true when cluster is reachable', async () => {
|
|
152
375
|
const prev = testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable;
|
|
153
|
-
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = sinon
|
|
376
|
+
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = sinon
|
|
377
|
+
.stub()
|
|
378
|
+
.resolves(true);
|
|
154
379
|
|
|
155
380
|
const td = new TurnDiscovery(mockRoapRequest);
|
|
156
381
|
const result = td.doTurnDiscovery(testMeeting, false, true);
|
|
@@ -163,13 +388,17 @@ describe('TurnDiscovery', () => {
|
|
|
163
388
|
// @ts-ignore
|
|
164
389
|
mockRoapRequest.sendRoap.resetHistory();
|
|
165
390
|
// simulate the response
|
|
166
|
-
td.handleTurnDiscoveryResponse(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
391
|
+
td.handleTurnDiscoveryResponse(
|
|
392
|
+
{
|
|
393
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
394
|
+
headers: [
|
|
395
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
396
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
397
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
'from test'
|
|
401
|
+
);
|
|
173
402
|
await testUtils.flushPromises();
|
|
174
403
|
// check that we've sent OK
|
|
175
404
|
await checkRoapMessageSent('OK', 0);
|
|
@@ -178,7 +407,7 @@ describe('TurnDiscovery', () => {
|
|
|
178
407
|
assert.deepEqual(turnServerInfo, {
|
|
179
408
|
url: FAKE_TURN_URL,
|
|
180
409
|
username: FAKE_TURN_USERNAME,
|
|
181
|
-
password: FAKE_TURN_PASSWORD
|
|
410
|
+
password: FAKE_TURN_PASSWORD,
|
|
182
411
|
});
|
|
183
412
|
assert.isUndefined(turnDiscoverySkippedReason);
|
|
184
413
|
|
|
@@ -199,13 +428,17 @@ describe('TurnDiscovery', () => {
|
|
|
199
428
|
mockRoapRequest.sendRoap.resetHistory();
|
|
200
429
|
|
|
201
430
|
// simulate the response
|
|
202
|
-
td.handleTurnDiscoveryResponse(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
431
|
+
td.handleTurnDiscoveryResponse(
|
|
432
|
+
{
|
|
433
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
434
|
+
headers: [
|
|
435
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
436
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
437
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
'from test'
|
|
441
|
+
);
|
|
209
442
|
|
|
210
443
|
await testUtils.flushPromises();
|
|
211
444
|
|
|
@@ -232,16 +465,20 @@ describe('TurnDiscovery', () => {
|
|
|
232
465
|
mockRoapRequest.sendRoap.resetHistory();
|
|
233
466
|
|
|
234
467
|
// simulate the response with some extra headers
|
|
235
|
-
td.handleTurnDiscoveryResponse(
|
|
236
|
-
|
|
237
|
-
'
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
468
|
+
td.handleTurnDiscoveryResponse(
|
|
469
|
+
{
|
|
470
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
471
|
+
headers: [
|
|
472
|
+
'x-cisco-turn-unexpected-header=xxx',
|
|
473
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
474
|
+
'x-cisco-some-other-header',
|
|
475
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
476
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
477
|
+
'another-header-at-the-end=12345',
|
|
478
|
+
],
|
|
479
|
+
},
|
|
480
|
+
'from test'
|
|
481
|
+
);
|
|
245
482
|
|
|
246
483
|
await testUtils.flushPromises();
|
|
247
484
|
|
|
@@ -273,7 +510,8 @@ describe('TurnDiscovery', () => {
|
|
|
273
510
|
|
|
274
511
|
it('resolves with undefined when cluster is reachable', async () => {
|
|
275
512
|
const prev = testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable;
|
|
276
|
-
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = () =>
|
|
513
|
+
testMeeting.webex.meetings.reachability.isAnyPublicClusterReachable = () =>
|
|
514
|
+
Promise.resolve(true);
|
|
277
515
|
const result = await new TurnDiscovery(mockRoapRequest).doTurnDiscovery(testMeeting);
|
|
278
516
|
|
|
279
517
|
const {turnServerInfo, turnDiscoverySkippedReason} = result;
|
|
@@ -307,12 +545,16 @@ describe('TurnDiscovery', () => {
|
|
|
307
545
|
await testUtils.flushPromises();
|
|
308
546
|
|
|
309
547
|
// simulate the response without the password
|
|
310
|
-
td.handleTurnDiscoveryResponse(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
548
|
+
td.handleTurnDiscoveryResponse(
|
|
549
|
+
{
|
|
550
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
551
|
+
headers: [
|
|
552
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
553
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
554
|
+
],
|
|
555
|
+
},
|
|
556
|
+
'from test'
|
|
557
|
+
);
|
|
316
558
|
await testUtils.flushPromises();
|
|
317
559
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
318
560
|
|
|
@@ -328,7 +570,7 @@ describe('TurnDiscovery', () => {
|
|
|
328
570
|
await testUtils.flushPromises();
|
|
329
571
|
|
|
330
572
|
// simulate the response without the headers
|
|
331
|
-
td.handleTurnDiscoveryResponse({});
|
|
573
|
+
td.handleTurnDiscoveryResponse({messageType: 'TURN_DISCOVERY_RESPONSE'}, 'from test');
|
|
332
574
|
|
|
333
575
|
await testUtils.flushPromises();
|
|
334
576
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
@@ -345,7 +587,10 @@ describe('TurnDiscovery', () => {
|
|
|
345
587
|
await testUtils.flushPromises();
|
|
346
588
|
|
|
347
589
|
// simulate the response without the headers
|
|
348
|
-
td.handleTurnDiscoveryResponse(
|
|
590
|
+
td.handleTurnDiscoveryResponse(
|
|
591
|
+
{messageType: 'TURN_DISCOVERY_RESPONSE', headers: []},
|
|
592
|
+
'from test'
|
|
593
|
+
);
|
|
349
594
|
|
|
350
595
|
await testUtils.flushPromises();
|
|
351
596
|
const {turnServerInfo, turnDiscoverySkippedReason} = await turnDiscoveryPromise;
|
|
@@ -371,13 +616,17 @@ describe('TurnDiscovery', () => {
|
|
|
371
616
|
mockRoapRequest.sendRoap = sinon.fake.rejects(new Error('fake error'));
|
|
372
617
|
|
|
373
618
|
// simulate the response
|
|
374
|
-
td.handleTurnDiscoveryResponse(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
619
|
+
td.handleTurnDiscoveryResponse(
|
|
620
|
+
{
|
|
621
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
622
|
+
headers: [
|
|
623
|
+
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
624
|
+
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
625
|
+
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
626
|
+
],
|
|
627
|
+
},
|
|
628
|
+
'from test'
|
|
629
|
+
);
|
|
381
630
|
|
|
382
631
|
await testUtils.flushPromises();
|
|
383
632
|
|
|
@@ -416,12 +665,13 @@ describe('TurnDiscovery', () => {
|
|
|
416
665
|
// there is not much we can check, but we mainly want to make
|
|
417
666
|
// sure that it doesn't crash
|
|
418
667
|
td.handleTurnDiscoveryResponse({
|
|
668
|
+
messageType: 'TURN_DISCOVERY_RESPONSE',
|
|
419
669
|
headers: [
|
|
420
670
|
`x-cisco-turn-url=${FAKE_TURN_URL}`,
|
|
421
671
|
`x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
|
|
422
672
|
`x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
|
|
423
673
|
],
|
|
424
|
-
});
|
|
674
|
+
}, 'from test');
|
|
425
675
|
|
|
426
676
|
assert.notCalled(mockRoapRequest.sendRoap);
|
|
427
677
|
});
|