@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.
Files changed (63) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/interpretation/index.js +1 -1
  4. package/dist/interpretation/siLanguage.js +1 -1
  5. package/dist/mediaQualityMetrics/config.d.ts +103 -99
  6. package/dist/mediaQualityMetrics/config.js +133 -129
  7. package/dist/mediaQualityMetrics/config.js.map +1 -1
  8. package/dist/meeting/index.d.ts +0 -1
  9. package/dist/meeting/index.js +7 -15
  10. package/dist/meeting/index.js.map +1 -1
  11. package/dist/meeting/request.d.ts +2 -0
  12. package/dist/meeting/request.js +4 -0
  13. package/dist/meeting/request.js.map +1 -1
  14. package/dist/meeting/voicea-meeting.d.ts +0 -4
  15. package/dist/meeting/voicea-meeting.js +26 -58
  16. package/dist/meeting/voicea-meeting.js.map +1 -1
  17. package/dist/meetings/index.js +19 -0
  18. package/dist/meetings/index.js.map +1 -1
  19. package/dist/meetings/util.js +1 -1
  20. package/dist/meetings/util.js.map +1 -1
  21. package/dist/metrics/constants.d.ts +2 -0
  22. package/dist/metrics/constants.js +3 -1
  23. package/dist/metrics/constants.js.map +1 -1
  24. package/dist/reachability/index.js +14 -20
  25. package/dist/reachability/index.js.map +1 -1
  26. package/dist/reconnection-manager/index.js +63 -43
  27. package/dist/reconnection-manager/index.js.map +1 -1
  28. package/dist/roap/turnDiscovery.d.ts +18 -2
  29. package/dist/roap/turnDiscovery.js +163 -69
  30. package/dist/roap/turnDiscovery.js.map +1 -1
  31. package/dist/rtcMetrics/index.d.ts +7 -0
  32. package/dist/rtcMetrics/index.js +38 -1
  33. package/dist/rtcMetrics/index.js.map +1 -1
  34. package/dist/statsAnalyzer/index.js +135 -23
  35. package/dist/statsAnalyzer/index.js.map +1 -1
  36. package/dist/statsAnalyzer/mqaUtil.d.ts +28 -4
  37. package/dist/statsAnalyzer/mqaUtil.js +278 -148
  38. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  39. package/dist/webinar/index.js +1 -1
  40. package/package.json +21 -21
  41. package/src/mediaQualityMetrics/config.ts +107 -107
  42. package/src/meeting/index.ts +5 -18
  43. package/src/meeting/request.ts +6 -0
  44. package/src/meeting/voicea-meeting.ts +26 -65
  45. package/src/meetings/index.ts +22 -0
  46. package/src/meetings/util.ts +1 -1
  47. package/src/metrics/constants.ts +2 -0
  48. package/src/reachability/index.ts +0 -6
  49. package/src/reconnection-manager/index.ts +18 -7
  50. package/src/roap/turnDiscovery.ts +100 -24
  51. package/src/rtcMetrics/index.ts +43 -1
  52. package/src/statsAnalyzer/index.ts +158 -24
  53. package/src/statsAnalyzer/mqaUtil.ts +302 -154
  54. package/test/unit/spec/meeting/index.js +195 -4
  55. package/test/unit/spec/meeting/request.js +2 -0
  56. package/test/unit/spec/meeting/voicea-meeting.ts +266 -0
  57. package/test/unit/spec/meetings/utils.js +35 -8
  58. package/test/unit/spec/reachability/index.ts +74 -0
  59. package/test/unit/spec/reconnection-manager/index.js +36 -1
  60. package/test/unit/spec/roap/turnDiscovery.ts +326 -76
  61. package/test/unit/spec/rtcMetrics/index.ts +32 -3
  62. package/test/unit/spec/stats-analyzer/index.js +439 -1
  63. 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
- describe('doTurnDiscovery', () => {
111
- [false, true].forEach(function (enabledMultistream ) {
112
- it('sends TURN_DISCOVERY_REQUEST'+ (enabledMultistream ? ' when enable Multistream':'') + ', waits for response and sends OK', async () => {
113
- testMeeting.isMultistream = enabledMultistream;
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
- const td = new TurnDiscovery(mockRoapRequest);
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
- const result = td.doTurnDiscovery(testMeeting, false);
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
- // check that TURN_DISCOVERY_REQUEST was sent
120
- await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
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
- // @ts-ignore
123
- mockRoapRequest.sendRoap.resetHistory();
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
- // simulate the response
126
- td.handleTurnDiscoveryResponse({
127
- headers: [
128
- `x-cisco-turn-url=${FAKE_TURN_URL}`,
129
- `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
130
- `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
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
- await testUtils.flushPromises();
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
- // check that we've sent OK
137
- await checkRoapMessageSent('OK', 0);
299
+ // check that TURN_DISCOVERY_REQUEST was sent
300
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
138
301
 
139
- const {turnServerInfo, turnDiscoverySkippedReason} = await result;
302
+ // @ts-ignore
303
+ mockRoapRequest.sendRoap.resetHistory();
140
304
 
141
- assert.deepEqual(turnServerInfo, {
142
- url: FAKE_TURN_URL,
143
- username: FAKE_TURN_USERNAME,
144
- password: FAKE_TURN_PASSWORD
305
+ await checkResult(result, undefined, undefined);
306
+ checkFailureMetricsSent();
145
307
  });
146
308
 
147
- assert.isUndefined(turnDiscoverySkippedReason);
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.stub().resolves(true);
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
- 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
- });
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
- headers: [
204
- `x-cisco-turn-url=${FAKE_TURN_URL}`,
205
- `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
206
- `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
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
- headers: [
237
- 'x-cisco-turn-unexpected-header=xxx',
238
- `x-cisco-turn-url=${FAKE_TURN_URL}`,
239
- 'x-cisco-some-other-header',
240
- `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
241
- `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
242
- 'another-header-at-the-end=12345',
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 = () => Promise.resolve(true);
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
- headers: [
312
- `x-cisco-turn-url=${FAKE_TURN_URL}`,
313
- `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
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({headers: []});
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
- headers: [
376
- `x-cisco-turn-url=${FAKE_TURN_URL}`,
377
- `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
378
- `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
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
  });