@webex/plugin-meetings 2.24.1 → 2.26.0

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 (47) hide show
  1. package/dist/common/logs/logger-proxy.js +7 -6
  2. package/dist/common/logs/logger-proxy.js.map +1 -1
  3. package/dist/config.js +2 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +4 -11
  6. package/dist/constants.js.map +1 -1
  7. package/dist/media/properties.js +2 -2
  8. package/dist/media/properties.js.map +1 -1
  9. package/dist/media/util.js +18 -4
  10. package/dist/media/util.js.map +1 -1
  11. package/dist/meeting/index.js +9 -5
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/metrics/constants.js +2 -1
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/peer-connection-manager/index.js +6 -4
  16. package/dist/peer-connection-manager/index.js.map +1 -1
  17. package/dist/reconnection-manager/index.js +40 -14
  18. package/dist/reconnection-manager/index.js.map +1 -1
  19. package/dist/roap/handler.js +0 -2
  20. package/dist/roap/handler.js.map +1 -1
  21. package/dist/roap/index.js +36 -7
  22. package/dist/roap/index.js.map +1 -1
  23. package/dist/roap/request.js +5 -3
  24. package/dist/roap/request.js.map +1 -1
  25. package/dist/roap/turnDiscovery.js +273 -0
  26. package/dist/roap/turnDiscovery.js.map +1 -0
  27. package/dist/roap/util.js +0 -2
  28. package/dist/roap/util.js.map +1 -1
  29. package/package.json +5 -5
  30. package/src/common/logs/logger-proxy.js +7 -6
  31. package/src/config.js +2 -1
  32. package/src/constants.ts +3 -4
  33. package/src/media/properties.js +2 -2
  34. package/src/media/util.js +17 -7
  35. package/src/meeting/index.js +9 -7
  36. package/src/metrics/constants.js +2 -1
  37. package/src/peer-connection-manager/index.js +6 -3
  38. package/src/reconnection-manager/index.js +13 -10
  39. package/src/roap/handler.js +0 -2
  40. package/src/roap/index.js +36 -6
  41. package/src/roap/request.js +5 -3
  42. package/src/roap/turnDiscovery.ts +238 -0
  43. package/src/roap/util.js +0 -2
  44. package/test/unit/spec/meeting/index.js +47 -0
  45. package/test/unit/spec/peerconnection-manager/index.js +47 -4
  46. package/test/unit/spec/roap/index.ts +113 -0
  47. package/test/unit/spec/roap/turnDiscovery.ts +334 -0
@@ -0,0 +1,334 @@
1
+ import sinon from 'sinon';
2
+ import {assert} from '@webex/test-helper-chai';
3
+ import TurnDiscovery from '@webex/plugin-meetings/src/roap/turnDiscovery';
4
+
5
+ import Metrics from '@webex/plugin-meetings/src/metrics';
6
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
7
+ import RoapRequest from '@webex/plugin-meetings/src/roap/request';
8
+
9
+ import testUtils from '../../../utils/testUtils';
10
+
11
+ describe('TurnDiscovery', () => {
12
+ let clock;
13
+ let mockRoapRequest: RoapRequest;
14
+ let testMeeting: any;
15
+
16
+ const FAKE_TURN_URL = 'turns:fakeTurnServer.com:443?transport=tcp';
17
+ const FAKE_TURN_USERNAME = 'someUsernameFromServer';
18
+ const FAKE_TURN_PASSWORD = 'fakePasswordFromServer';
19
+ const FAKE_LOCUS_ID = '09493311-f5d5-3e58-b491-009cc628162e';
20
+ const FAKE_MEDIA_CONNECTIONS_FROM_LOCUS = [{mediaId: '464ff97f-4bda-466a-ad06-3a22184a2274'}];
21
+
22
+ beforeEach(() => {
23
+ clock = sinon.useFakeTimers();
24
+
25
+ sinon.stub(Metrics, 'sendBehavioralMetric');
26
+
27
+ mockRoapRequest = {
28
+ sendRoap: sinon.fake.resolves({mediaConnections: FAKE_MEDIA_CONNECTIONS_FROM_LOCUS})
29
+ } as unknown as RoapRequest;
30
+
31
+ testMeeting = {
32
+ id: 'fake meeting id',
33
+ config: {
34
+ experimental: {
35
+ enableTurnDiscovery: true
36
+ }
37
+ },
38
+ correlationId: 'fake correlation id',
39
+ selfUrl: 'fake self url',
40
+ mediaId: 'fake media id',
41
+ locusUrl: `https://locus-a.wbx2.com/locus/api/v1/loci/${FAKE_LOCUS_ID}`,
42
+ roapSeq: -1,
43
+ isAudioMuted: () => true,
44
+ isVideoMuted: () => false,
45
+ setRoapSeq: sinon.fake((newSeq) => {
46
+ testMeeting.roapSeq = newSeq;
47
+ }),
48
+ updateMediaConnections: sinon.stub(),
49
+ };
50
+ });
51
+
52
+ afterEach(() => {
53
+ clock.restore();
54
+ sinon.restore();
55
+ });
56
+
57
+ const checkRoapMessageSent = async (messageType, expectedSeq, expectedMediaId = testMeeting.mediaId) => {
58
+ await testUtils.flushPromises();
59
+
60
+ assert.calledOnce(mockRoapRequest.sendRoap);
61
+ assert.calledWith(mockRoapRequest.sendRoap, {
62
+ roapMessage: {
63
+ messageType,
64
+ version: '2',
65
+ seq: expectedSeq,
66
+ },
67
+ correlationId: testMeeting.correlationId,
68
+ locusSelfUrl: testMeeting.selfUrl,
69
+ mediaId: expectedMediaId,
70
+ audioMuted: testMeeting.isAudioMuted(),
71
+ videoMuted: testMeeting.isVideoMuted(),
72
+ meetingId: testMeeting.id
73
+ });
74
+
75
+ if (messageType === 'TURN_DISCOVERY_REQUEST') {
76
+ // check also that we've applied the media connections from the response
77
+ assert.calledOnce(testMeeting.updateMediaConnections);
78
+ assert.calledWith(testMeeting.updateMediaConnections, FAKE_MEDIA_CONNECTIONS_FROM_LOCUS);
79
+ }
80
+ };
81
+
82
+ const checkFailureMetricsSent = () => {
83
+ assert.calledOnce(Metrics.sendBehavioralMetric);
84
+ assert.calledWith(
85
+ Metrics.sendBehavioralMetric,
86
+ BEHAVIORAL_METRICS.TURN_DISCOVERY_FAILURE,
87
+ sinon.match({
88
+ correlation_id: testMeeting.correlationId,
89
+ locus_id: FAKE_LOCUS_ID,
90
+ })
91
+ );
92
+ };
93
+
94
+ describe('doTurnDiscovery', () => {
95
+ it('sends TURN_DISCOVERY_REQUEST, waits for response and sends OK', async () => {
96
+ const td = new TurnDiscovery(mockRoapRequest);
97
+
98
+ const result = td.doTurnDiscovery(testMeeting, false);
99
+
100
+ // check that TURN_DISCOVERY_REQUEST was sent
101
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
102
+
103
+ mockRoapRequest.sendRoap.resetHistory();
104
+
105
+ // simulate the response
106
+ td.handleTurnDiscoveryResponse({
107
+ headers: [
108
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
109
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
110
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
111
+ ]
112
+ });
113
+
114
+ await testUtils.flushPromises();
115
+
116
+ // check that we've sent OK
117
+ await checkRoapMessageSent('OK', 0);
118
+
119
+ const turnInfo = await result;
120
+
121
+ assert.deepEqual(turnInfo, {
122
+ url: FAKE_TURN_URL,
123
+ username: FAKE_TURN_USERNAME,
124
+ password: FAKE_TURN_PASSWORD
125
+ });
126
+ });
127
+
128
+ it('sends TURN_DISCOVERY_REQUEST with empty mediaId when isReconnecting is true', async () => {
129
+ const td = new TurnDiscovery(mockRoapRequest);
130
+
131
+ const result = td.doTurnDiscovery(testMeeting, true);
132
+
133
+ // check that TURN_DISCOVERY_REQUEST was sent with empty mediaId
134
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0, '');
135
+
136
+ // the main part of the test is complete now, checking the remaining part of the flow just for completeness
137
+ mockRoapRequest.sendRoap.resetHistory();
138
+
139
+ // simulate the response
140
+ td.handleTurnDiscoveryResponse({
141
+ headers: [
142
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
143
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
144
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
145
+ ]
146
+ });
147
+
148
+ await testUtils.flushPromises();
149
+
150
+ // check that we've sent OK
151
+ await checkRoapMessageSent('OK', 0);
152
+
153
+ const turnInfo = await result;
154
+
155
+ assert.deepEqual(turnInfo, {
156
+ url: FAKE_TURN_URL,
157
+ username: FAKE_TURN_USERNAME,
158
+ password: FAKE_TURN_PASSWORD
159
+ });
160
+ });
161
+
162
+ it('ignores any extra, unexpected headers in the response', async () => {
163
+ const td = new TurnDiscovery(mockRoapRequest);
164
+ const result = td.doTurnDiscovery(testMeeting, false);
165
+
166
+ // check that TURN_DISCOVERY_REQUEST was sent
167
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
168
+
169
+ mockRoapRequest.sendRoap.resetHistory();
170
+
171
+ // simulate the response with some extra headers
172
+ td.handleTurnDiscoveryResponse({
173
+ headers: [
174
+ 'x-cisco-turn-unexpected-header=xxx',
175
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
176
+ 'x-cisco-some-other-header',
177
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
178
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
179
+ 'another-header-at-the-end=12345',
180
+ ]
181
+ });
182
+
183
+ await testUtils.flushPromises();
184
+
185
+ // check that we've sent OK and still parsed the headers we care about
186
+ await checkRoapMessageSent('OK', 0);
187
+
188
+ const turnInfo = await result;
189
+
190
+ assert.deepEqual(turnInfo, {
191
+ url: FAKE_TURN_URL,
192
+ username: FAKE_TURN_USERNAME,
193
+ password: FAKE_TURN_PASSWORD
194
+ });
195
+ });
196
+
197
+ it('resolves with undefined if turn discovery feature is disabled in config', async () => {
198
+ const prevConfigValue = testMeeting.config.experimental.enableTurnDiscovery;
199
+
200
+ testMeeting.config.experimental.enableTurnDiscovery = false;
201
+
202
+ const result = await new TurnDiscovery(mockRoapRequest).doTurnDiscovery(testMeeting);
203
+
204
+ assert.isUndefined(result);
205
+ assert.notCalled(mockRoapRequest.sendRoap);
206
+ assert.notCalled(Metrics.sendBehavioralMetric);
207
+
208
+ // restore previous config
209
+ testMeeting.config.experimental.enableTurnDiscovery = prevConfigValue;
210
+ });
211
+
212
+ it('resolves with undefined if sending the request fails', async () => {
213
+ const td = new TurnDiscovery(mockRoapRequest);
214
+
215
+ mockRoapRequest.sendRoap = sinon.fake.rejects(new Error('fake error'));
216
+
217
+ const result = await td.doTurnDiscovery(testMeeting, false);
218
+
219
+ assert.isUndefined(result);
220
+ checkFailureMetricsSent();
221
+ });
222
+
223
+ it('resolves with undefined if we don\'t get a response within 10s', async () => {
224
+ const td = new TurnDiscovery(mockRoapRequest);
225
+
226
+ const promise = td.doTurnDiscovery(testMeeting, false);
227
+
228
+ await clock.tickAsync(10 * 1000);
229
+ await testUtils.flushPromises();
230
+
231
+ const turnInfo = await promise;
232
+
233
+ assert.isUndefined(turnInfo);
234
+ checkFailureMetricsSent();
235
+ });
236
+
237
+ it('resolves with undefined if the response does not have all the headers we expect', async () => {
238
+ const td = new TurnDiscovery(mockRoapRequest);
239
+ const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
240
+
241
+ // simulate the response without the password
242
+ td.handleTurnDiscoveryResponse({
243
+ headers: [
244
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
245
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
246
+ ]
247
+ });
248
+ await testUtils.flushPromises();
249
+ const turnInfo = await turnDiscoveryPromise;
250
+
251
+ assert.isUndefined(turnInfo);
252
+ checkFailureMetricsSent();
253
+ });
254
+
255
+ it('resolves with undefined if the response does not have any headers', async () => {
256
+ const td = new TurnDiscovery(mockRoapRequest);
257
+ const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
258
+
259
+ // simulate the response without the headers
260
+ td.handleTurnDiscoveryResponse({});
261
+
262
+ await testUtils.flushPromises();
263
+ const turnInfo = await turnDiscoveryPromise;
264
+
265
+ assert.isUndefined(turnInfo);
266
+ checkFailureMetricsSent();
267
+ });
268
+
269
+ it('resolves with undefined if the response has empty headers array', async () => {
270
+ const td = new TurnDiscovery(mockRoapRequest);
271
+ const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
272
+
273
+ // simulate the response without the headers
274
+ td.handleTurnDiscoveryResponse({headers: []});
275
+
276
+ await testUtils.flushPromises();
277
+ const turnInfo = await turnDiscoveryPromise;
278
+
279
+ assert.isUndefined(turnInfo);
280
+ checkFailureMetricsSent();
281
+ });
282
+
283
+ it('resolves with undefined if failed to send OK', async () => {
284
+ const td = new TurnDiscovery(mockRoapRequest);
285
+
286
+ const turnDiscoveryPromise = td.doTurnDiscovery(testMeeting, false);
287
+
288
+ // check that TURN_DISCOVERY_REQUEST was sent
289
+ await checkRoapMessageSent('TURN_DISCOVERY_REQUEST', 0);
290
+
291
+ mockRoapRequest.sendRoap.resetHistory();
292
+
293
+ // setup the mock so that sending of OK fails
294
+ mockRoapRequest.sendRoap = sinon.fake.rejects(new Error('fake error'));
295
+
296
+ // simulate the response
297
+ td.handleTurnDiscoveryResponse({
298
+ headers: [
299
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
300
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
301
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
302
+ ]
303
+ });
304
+
305
+ await testUtils.flushPromises();
306
+
307
+ // check that we've sent OK
308
+ await checkRoapMessageSent('OK', 0);
309
+
310
+ const turnInfo = await turnDiscoveryPromise;
311
+
312
+ assert.isUndefined(turnInfo);
313
+ checkFailureMetricsSent();
314
+ });
315
+ });
316
+
317
+ describe('handleTurnDiscoveryResponse', () => {
318
+ it('doesn\'t do anything if turn discovery was not started', () => {
319
+ const td = new TurnDiscovery(mockRoapRequest);
320
+
321
+ // there is not much we can check, but we mainly want to make
322
+ // sure that it doesn't crash
323
+ td.handleTurnDiscoveryResponse({
324
+ headers: [
325
+ `x-cisco-turn-url=${FAKE_TURN_URL}`,
326
+ `x-cisco-turn-username=${FAKE_TURN_USERNAME}`,
327
+ `x-cisco-turn-password=${FAKE_TURN_PASSWORD}`,
328
+ ]
329
+ });
330
+
331
+ assert.notCalled(mockRoapRequest.sendRoap);
332
+ });
333
+ });
334
+ });