@webex/plugin-meetings 3.8.0-web-workers-keepalive.1 → 3.8.1-next.1

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 (168) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +70 -6
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/common/errors/webex-errors.js +12 -2
  5. package/dist/common/errors/webex-errors.js.map +1 -1
  6. package/dist/config.js +4 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +22 -123
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +2 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +52 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +30 -10
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +83 -12
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/selfUtils.js +432 -418
  22. package/dist/locus-info/selfUtils.js.map +1 -1
  23. package/dist/media/index.js +17 -17
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/media/properties.js +94 -6
  26. package/dist/media/properties.js.map +1 -1
  27. package/dist/meeting/brbState.js +9 -2
  28. package/dist/meeting/brbState.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +17 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +568 -328
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/locusMediaRequest.js +0 -17
  34. package/dist/meeting/locusMediaRequest.js.map +1 -1
  35. package/dist/meeting/muteState.js +4 -4
  36. package/dist/meeting/muteState.js.map +1 -1
  37. package/dist/meeting/request.js +30 -0
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +9 -1
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meeting-info/meeting-info-v2.js +19 -13
  43. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  44. package/dist/meeting-info/utilv2.js +5 -1
  45. package/dist/meeting-info/utilv2.js.map +1 -1
  46. package/dist/meetings/index.js +76 -0
  47. package/dist/meetings/index.js.map +1 -1
  48. package/dist/meetings/util.js +14 -0
  49. package/dist/meetings/util.js.map +1 -1
  50. package/dist/member/index.js +45 -9
  51. package/dist/member/index.js.map +1 -1
  52. package/dist/member/types.js +3 -0
  53. package/dist/member/types.js.map +1 -1
  54. package/dist/member/util.js +335 -356
  55. package/dist/member/util.js.map +1 -1
  56. package/dist/members/collection.js.map +1 -1
  57. package/dist/members/index.js +137 -29
  58. package/dist/members/index.js.map +1 -1
  59. package/dist/members/request.js +38 -0
  60. package/dist/members/request.js.map +1 -1
  61. package/dist/members/util.js +36 -1
  62. package/dist/members/util.js.map +1 -1
  63. package/dist/metrics/constants.js +1 -0
  64. package/dist/metrics/constants.js.map +1 -1
  65. package/dist/reachability/clusterReachability.js +23 -31
  66. package/dist/reachability/clusterReachability.js.map +1 -1
  67. package/dist/reachability/index.js +42 -2
  68. package/dist/reachability/index.js.map +1 -1
  69. package/dist/reconnection-manager/index.js +2 -2
  70. package/dist/reconnection-manager/index.js.map +1 -1
  71. package/dist/roap/index.js.map +1 -1
  72. package/dist/roap/turnDiscovery.js +45 -27
  73. package/dist/roap/turnDiscovery.js.map +1 -1
  74. package/dist/roap/types.js +17 -0
  75. package/dist/roap/types.js.map +1 -0
  76. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  77. package/dist/types/config.d.ts +2 -0
  78. package/dist/types/constants.d.ts +15 -85
  79. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  80. package/dist/types/controls-options-manager/types.d.ts +7 -1
  81. package/dist/types/locus-info/index.d.ts +3 -3
  82. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  83. package/dist/types/media/properties.d.ts +15 -0
  84. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  85. package/dist/types/meeting/index.d.ts +35 -1
  86. package/dist/types/meeting/muteState.d.ts +0 -1
  87. package/dist/types/meeting/request.d.ts +12 -1
  88. package/dist/types/meeting/request.type.d.ts +6 -0
  89. package/dist/types/meeting/util.d.ts +3 -1
  90. package/dist/types/meeting-info/meeting-info-v2.d.ts +2 -1
  91. package/dist/types/meetings/index.d.ts +28 -0
  92. package/dist/types/member/index.d.ts +20 -6
  93. package/dist/types/member/types.d.ts +73 -14
  94. package/dist/types/member/util.d.ts +156 -1
  95. package/dist/types/members/collection.d.ts +6 -5
  96. package/dist/types/members/index.d.ts +32 -43
  97. package/dist/types/members/request.d.ts +26 -0
  98. package/dist/types/members/util.d.ts +27 -0
  99. package/dist/types/metrics/constants.d.ts +1 -0
  100. package/dist/types/reachability/clusterReachability.d.ts +2 -6
  101. package/dist/types/reachability/index.d.ts +8 -0
  102. package/dist/types/roap/index.d.ts +3 -2
  103. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  104. package/dist/types/roap/types.d.ts +16 -0
  105. package/dist/webinar/index.js +1 -1
  106. package/package.json +24 -23
  107. package/src/breakouts/index.ts +69 -0
  108. package/src/common/errors/webex-errors.ts +8 -1
  109. package/src/config.ts +2 -0
  110. package/src/constants.ts +23 -90
  111. package/src/controls-options-manager/enums.ts +2 -0
  112. package/src/controls-options-manager/types.ts +11 -1
  113. package/src/controls-options-manager/util.ts +62 -0
  114. package/src/locus-info/controlsUtils.ts +48 -12
  115. package/src/locus-info/index.ts +88 -13
  116. package/src/locus-info/selfUtils.ts +496 -442
  117. package/src/media/index.ts +23 -21
  118. package/src/media/properties.ts +96 -0
  119. package/src/meeting/brbState.ts +11 -2
  120. package/src/meeting/in-meeting-actions.ts +32 -0
  121. package/src/meeting/index.ts +356 -87
  122. package/src/meeting/locusMediaRequest.ts +0 -18
  123. package/src/meeting/muteState.ts +4 -4
  124. package/src/meeting/request.ts +36 -1
  125. package/src/meeting/request.type.ts +7 -0
  126. package/src/meeting/util.ts +9 -1
  127. package/src/meeting-info/meeting-info-v2.ts +7 -2
  128. package/src/meeting-info/utilv2.ts +5 -0
  129. package/src/meetings/index.ts +76 -0
  130. package/src/meetings/util.ts +18 -0
  131. package/src/member/index.ts +57 -22
  132. package/src/member/types.ts +82 -16
  133. package/src/member/util.ts +357 -353
  134. package/src/members/collection.ts +4 -3
  135. package/src/members/index.ts +137 -18
  136. package/src/members/request.ts +44 -0
  137. package/src/members/util.ts +43 -1
  138. package/src/metrics/constants.ts +1 -0
  139. package/src/reachability/clusterReachability.ts +26 -25
  140. package/src/reachability/index.ts +55 -1
  141. package/src/reconnection-manager/index.ts +2 -2
  142. package/src/roap/index.ts +3 -7
  143. package/src/roap/turnDiscovery.ts +34 -39
  144. package/src/roap/types.ts +23 -0
  145. package/test/unit/spec/breakouts/index.ts +167 -95
  146. package/test/unit/spec/controls-options-manager/util.js +120 -0
  147. package/test/unit/spec/locus-info/controlsUtils.js +131 -9
  148. package/test/unit/spec/locus-info/index.js +195 -73
  149. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  150. package/test/unit/spec/media/index.ts +150 -18
  151. package/test/unit/spec/media/properties.ts +130 -0
  152. package/test/unit/spec/meeting/brbState.ts +40 -2
  153. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  154. package/test/unit/spec/meeting/index.js +553 -36
  155. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  156. package/test/unit/spec/meeting/muteState.js +73 -2
  157. package/test/unit/spec/meeting/request.js +32 -1
  158. package/test/unit/spec/meeting/utils.js +79 -33
  159. package/test/unit/spec/meeting-info/meetinginfov2.js +41 -0
  160. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  161. package/test/unit/spec/meetings/index.js +68 -1
  162. package/test/unit/spec/members/index.js +304 -78
  163. package/test/unit/spec/members/request.js +68 -22
  164. package/test/unit/spec/members/utils.js +75 -0
  165. package/test/unit/spec/reachability/clusterReachability.ts +41 -55
  166. package/test/unit/spec/reachability/index.ts +89 -0
  167. package/test/unit/spec/reconnection-manager/index.js +4 -4
  168. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -4,7 +4,7 @@ import {cloneDeep, defaultsDeep} from 'lodash';
4
4
  import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
5
5
 
6
6
  import {self} from './selfConstant';
7
- import {_IDLE_, _WAIT_} from '@webex/plugin-meetings/src/constants';
7
+ import {_IDLE_, _WAIT_, _OBSERVE_, _NONE_} from '@webex/plugin-meetings/src/constants';
8
8
 
9
9
  describe('plugin-meetings', () => {
10
10
  describe('selfUtils', () => {
@@ -269,13 +269,18 @@ describe('plugin-meetings', () => {
269
269
  });
270
270
 
271
271
  describe('getSelves', () => {
272
+ let parsedSelf;
273
+
274
+ beforeEach(() => {
275
+ parsedSelf = SelfUtils.parse(self);
276
+ });
272
277
  describe('canNotViewTheParticipantListChanged', () => {
273
278
  it('should return canNotViewTheParticipantListChanged = true when changed', () => {
274
279
  const clonedSelf = cloneDeep(self);
275
280
 
276
281
  clonedSelf.canNotViewTheParticipantList = true; // different
277
282
 
278
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
283
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
279
284
 
280
285
  assert.equal(updates.canNotViewTheParticipantListChanged, true);
281
286
  });
@@ -285,7 +290,7 @@ describe('plugin-meetings', () => {
285
290
 
286
291
  clonedSelf.canNotViewTheParticipantList = false; // same
287
292
 
288
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
293
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
289
294
 
290
295
  assert.equal(updates.canNotViewTheParticipantListChanged, false);
291
296
  });
@@ -295,7 +300,7 @@ describe('plugin-meetings', () => {
295
300
  it('should return localAudioUnmuteRequestedByServer = false when requestedToUnmute = false', () => {
296
301
  const clonedSelf = cloneDeep(self);
297
302
 
298
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
303
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
299
304
 
300
305
  assert.equal(updates.localAudioUnmuteRequestedByServer, false);
301
306
  });
@@ -307,7 +312,7 @@ describe('plugin-meetings', () => {
307
312
  clonedSelf.controls.audio.requestedToUnmute = true;
308
313
  clonedSelf.controls.audio.lastModifiedRequestedToUnmute = '2023-06-16T18:25:04.369Z';
309
314
 
310
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
315
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
311
316
 
312
317
  assert.equal(updates.localAudioUnmuteRequestedByServer, true);
313
318
  });
@@ -321,7 +326,7 @@ describe('plugin-meetings', () => {
321
326
  clonedSelf.controls.audio.requestedToUnmute = true;
322
327
  clonedSelf.controls.audio.lastModifiedRequestedToUnmute = '2023-06-16T19:25:04.369Z';
323
328
 
324
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
329
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
325
330
 
326
331
  assert.equal(updates.localAudioUnmuteRequestedByServer, true);
327
332
  });
@@ -334,70 +339,139 @@ describe('plugin-meetings', () => {
334
339
  clonedSelf.controls.audio.requestedToUnmute = true;
335
340
  clonedSelf.controls.audio.lastModifiedRequestedToUnmute = '2023-06-16T18:25:04.369Z';
336
341
 
337
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
342
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
338
343
 
339
344
  assert.equal(updates.localAudioUnmuteRequestedByServer, false);
340
345
  });
341
346
  });
342
347
 
343
- describe('updates.isUserUnadmitted', () => {
344
- const testIsUserUnadmitted = (previousObjectDelta, currentObjectDelta, expected) => function () {
345
- const previous =
346
- previousObjectDelta === undefined ? undefined : defaultsDeep(previousObjectDelta, self);
347
- const current = defaultsDeep(currentObjectDelta, self);
348
-
349
- const {updates} = SelfUtils.getSelves(previous, current, self.devices[0].url);
350
-
351
- assert.equal(updates.isUserUnadmitted, expected);
352
- };
348
+ describe('updates.hasUserEnteredLobby', () => {
349
+ const testIsUserUnadmitted = (
350
+ previousParsedSelves,
351
+ currentSelfDelta,
352
+ participants,
353
+ expected
354
+ ) =>
355
+ function () {
356
+ const currentSelf = defaultsDeep(currentSelfDelta, self);
357
+
358
+ if (previousParsedSelves === undefined) {
359
+ parsedSelf.state = undefined;
360
+ } else {
361
+ parsedSelf = defaultsDeep(previousParsedSelves, parsedSelf);
362
+ }
363
+ const {updates} = SelfUtils.getSelves(
364
+ parsedSelf,
365
+ currentSelf,
366
+ currentSelf.devices[0].url,
367
+ participants
368
+ );
369
+
370
+ assert.equal(updates.hasUserEnteredLobby, expected);
371
+ };
353
372
 
354
373
  it(
355
374
  'should return true when previous is undefined and current is in lobby',
356
375
  testIsUserUnadmitted(
357
376
  undefined,
358
377
  {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
378
+ [],
359
379
  true
360
380
  )
361
381
  );
362
382
 
363
383
  it(
364
384
  'should return false when previous is undefined and user is not in meeting',
365
- testIsUserUnadmitted(undefined, {devices: [], state: _IDLE_}, false)
385
+ testIsUserUnadmitted(undefined, {devices: [], state: _IDLE_}, [], false)
366
386
  );
367
387
 
368
388
  it(
369
389
  'should return false when previous is undefined and current is in meeting',
370
- testIsUserUnadmitted(undefined, {}, false)
390
+ testIsUserUnadmitted(undefined, {}, [], false)
391
+ );
392
+
393
+ it(
394
+ 'should return true when previous is undefined and current is in lobby with paired device',
395
+ testIsUserUnadmitted(
396
+ undefined,
397
+ {
398
+ devices: [{intent: {type: _OBSERVE_, associatedWith: 'pairedDeviceUrl'}}],
399
+ state: _IDLE_,
400
+ },
401
+ [{url: 'pairedDeviceUrl', devices: [{intent: {type: _WAIT_}}]}],
402
+ true
403
+ )
404
+ );
405
+
406
+ it(
407
+ 'should return false when previous is in lobby with paired device and current is the same',
408
+ testIsUserUnadmitted(
409
+ {
410
+ pairedWith: {intent: {type: _WAIT_}},
411
+ joinedWith: {intent: {type: _OBSERVE_}},
412
+ state: _IDLE_,
413
+ },
414
+ {
415
+ devices: [{intent: {type: _OBSERVE_, associatedWith: 'pairedDeviceUrl'}}],
416
+ state: _IDLE_,
417
+ },
418
+ [{url: 'pairedDeviceUrl', devices: [{intent: {type: _WAIT_}}]}],
419
+ false
420
+ )
371
421
  );
372
422
 
373
423
  it(
374
424
  'should return false when previous is in lobby and current is in lobby',
375
425
  testIsUserUnadmitted(
426
+ {joinedWith: {intent: {type: _WAIT_}}, state: _IDLE_},
376
427
  {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
377
- {devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
428
+ [],
429
+ false
430
+ )
431
+ );
432
+
433
+ it(
434
+ 'should return false when previous is in lobby with paired device and current is in the meeting',
435
+ testIsUserUnadmitted(
436
+ {
437
+ pairedWith: {intent: {type: _WAIT_}},
438
+ joinedWith: {intent: {type: _OBSERVE_}},
439
+ state: _IDLE_,
440
+ },
441
+ {
442
+ devices: [{intent: {type: _OBSERVE_, associatedWith: 'pairedDeviceUrl'}}],
443
+ state: _IDLE_,
444
+ },
445
+ [{url: 'pairedDeviceUrl', devices: [{intent: {type: _NONE_}}]}],
378
446
  false
379
447
  )
380
448
  );
381
449
 
382
450
  it(
383
451
  'should return false when previous is in lobby and current is in meeting',
384
- testIsUserUnadmitted({devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, {}, false)
452
+ testIsUserUnadmitted({joinedWith: {intent: {type: _WAIT_}}, state: _IDLE_}, {}, [], false)
385
453
  );
386
454
 
387
455
  it(
388
456
  'should return true when previous is in meeting and current is in lobby',
389
- testIsUserUnadmitted({}, {devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, true)
457
+ testIsUserUnadmitted({}, {devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, [], true)
390
458
  );
391
459
  });
392
460
  });
393
461
 
394
462
  describe('isSharingBlocked', () => {
463
+ let parsedSelf;
464
+
465
+ beforeEach(() => {
466
+ parsedSelf = SelfUtils.parse(self);
467
+ });
468
+
395
469
  it('should return isSharingBlockedChanged = true when changed', () => {
396
470
  const clonedSelf = cloneDeep(self);
397
471
 
398
472
  clonedSelf.isSharingBlocked = true; // different
399
473
 
400
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
474
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
401
475
 
402
476
  assert.equal(updates.isSharingBlockedChanged, true);
403
477
  });
@@ -407,7 +481,7 @@ describe('plugin-meetings', () => {
407
481
 
408
482
  clonedSelf.isSharingBlocked = false; // same
409
483
 
410
- const {updates} = SelfUtils.getSelves(self, clonedSelf);
484
+ const {updates} = SelfUtils.getSelves(parsedSelf, clonedSelf);
411
485
 
412
486
  assert.equal(updates.isSharingBlockedChanged, false);
413
487
  });
@@ -4,6 +4,7 @@ import Media from '@webex/plugin-meetings/src/media/index';
4
4
  import {assert} from '@webex/test-helper-chai';
5
5
  import sinon from 'sinon';
6
6
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
7
+ import {BrowserInfo} from '@webex/web-capabilities';
7
8
 
8
9
  describe('createMediaConnection', () => {
9
10
  let clock;
@@ -79,7 +80,10 @@ describe('createMediaConnection', () => {
79
80
  enableRtx: ENABLE_RTX,
80
81
  enableExtmap: ENABLE_EXTMAP,
81
82
  turnServerInfo: {
82
- url: 'turns:turn-server-url:443?transport=tcp',
83
+ urls: [
84
+ 'turns:turn-server-url-1:443?transport=tcp',
85
+ 'turns:turn-server-url-2:443?transport=tcp',
86
+ ],
83
87
  username: 'turn username',
84
88
  password: 'turn password',
85
89
  },
@@ -91,12 +95,10 @@ describe('createMediaConnection', () => {
91
95
  {
92
96
  iceServers: [
93
97
  {
94
- urls: 'turn:turn-server-url:5004?transport=tcp',
95
- username: 'turn username',
96
- credential: 'turn password',
97
- },
98
- {
99
- urls: 'turns:turn-server-url:443?transport=tcp',
98
+ urls: [
99
+ 'turns:turn-server-url-1:443?transport=tcp',
100
+ 'turns:turn-server-url-2:443?transport=tcp',
101
+ ],
100
102
  username: 'turn username',
101
103
  credential: 'turn password',
102
104
  },
@@ -159,11 +161,16 @@ describe('createMediaConnection', () => {
159
161
  },
160
162
  rtcMetrics,
161
163
  turnServerInfo: {
162
- url: 'turns:turn-server-url:443?transport=tcp',
164
+ urls: [
165
+ 'turns:turn-server-url-1:443?transport=tcp',
166
+ 'turns:turn-server-url-2:443?transport=tcp',
167
+ ],
163
168
  username: 'turn username',
164
169
  password: 'turn password',
165
170
  },
166
171
  bundlePolicy: 'max-bundle',
172
+ disableAudioMainDtx: false,
173
+ enableAudioTwcc: true,
167
174
  });
168
175
  assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
169
176
  assert.calledWith(
@@ -171,25 +178,27 @@ describe('createMediaConnection', () => {
171
178
  {
172
179
  iceServers: [
173
180
  {
174
- urls: 'turn:turn-server-url:5004?transport=tcp',
175
- username: 'turn username',
176
- credential: 'turn password',
177
- },
178
- {
179
- urls: 'turns:turn-server-url:443?transport=tcp',
181
+ urls: [
182
+ 'turns:turn-server-url-1:443?transport=tcp',
183
+ 'turns:turn-server-url-2:443?transport=tcp',
184
+ ],
180
185
  username: 'turn username',
181
186
  credential: 'turn password',
182
187
  },
183
188
  ],
184
189
  bundlePolicy: 'max-bundle',
190
+ disableAudioMainDtx: false,
191
+ disableAudioTwcc: false,
185
192
  },
186
193
  'meeting id'
187
194
  );
188
195
 
189
196
  // check if rtcMetrics callbacks are configured correctly
190
197
  const addMetricsCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[2];
191
- const closeMetricsCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[3];
192
- const sendMetricsInQueueCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[4];
198
+ const closeMetricsCallback =
199
+ multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[3];
200
+ const sendMetricsInQueueCallback =
201
+ multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[4];
193
202
 
194
203
  assert.isFunction(addMetricsCallback);
195
204
  assert.isFunction(closeMetricsCallback);
@@ -205,14 +214,78 @@ describe('createMediaConnection', () => {
205
214
 
206
215
  sendMetricsInQueueCallback();
207
216
  assert.calledOnce(rtcMetrics.sendMetricsInQueue);
217
+ });
218
+
219
+ it('multistream non-firefox does not care about stopIceGatheringAfterFirstRelayCandidate', () => {
220
+ const multistreamRoapMediaConnectionConstructorStub = sinon
221
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
222
+ .returns(fakeRoapMediaConnection);
223
+
224
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
225
+ stopIceGatheringAfterFirstRelayCandidate: true,
226
+ });
227
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
228
+ assert.calledWith(
229
+ multistreamRoapMediaConnectionConstructorStub,
230
+ {
231
+ iceServers: [],
232
+ disableAudioTwcc: true,
233
+ },
234
+ 'meeting id'
235
+ );
236
+ });
237
+
238
+ it('multistream firefox stops gathering after first relay if stopIceGatheringAfterFirstRelayCandidate is true', () => {
239
+ const multistreamRoapMediaConnectionConstructorStub = sinon
240
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
241
+ .returns(fakeRoapMediaConnection);
242
+
243
+ sinon.stub(BrowserInfo, 'isFirefox').returns(true);
208
244
 
245
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
246
+ stopIceGatheringAfterFirstRelayCandidate: true,
247
+ });
248
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
249
+ assert.calledWith(
250
+ multistreamRoapMediaConnectionConstructorStub,
251
+ {
252
+ iceServers: [],
253
+ doFullIce: true,
254
+ stopIceGatheringAfterFirstRelayCandidate: true,
255
+ disableAudioTwcc: true,
256
+ },
257
+ 'meeting id'
258
+ );
259
+ });
260
+
261
+ it('multistream firefox continues gathering if stopIceGatheringAfterFirstRelayCandidate is false', () => {
262
+ const multistreamRoapMediaConnectionConstructorStub = sinon
263
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
264
+ .returns(fakeRoapMediaConnection);
265
+
266
+ sinon.stub(BrowserInfo, 'isFirefox').returns(true);
267
+
268
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
269
+ stopIceGatheringAfterFirstRelayCandidate: false,
270
+ });
271
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
272
+ assert.calledWith(
273
+ multistreamRoapMediaConnectionConstructorStub,
274
+ {
275
+ iceServers: [],
276
+ doFullIce: true,
277
+ stopIceGatheringAfterFirstRelayCandidate: false,
278
+ disableAudioTwcc: true,
279
+ },
280
+ 'meeting id'
281
+ );
209
282
  });
210
283
 
211
284
  [
212
285
  {testCase: 'turnServerInfo is undefined', turnServerInfo: undefined},
213
286
  {
214
287
  testCase: 'turnServerInfo.url is empty string',
215
- turnServerInfo: {url: '', username: 'turn username', password: 'turn password'},
288
+ turnServerInfo: {urls: [], username: 'turn username', password: 'turn password'},
216
289
  },
217
290
  ].forEach(({testCase, turnServerInfo}) => {
218
291
  it(`passes empty ICE servers array to MultistreamRoapMediaConnection if ${testCase} (multistream enabled)`, () => {
@@ -238,6 +311,7 @@ describe('createMediaConnection', () => {
238
311
  multistreamRoapMediaConnectionConstructorStub,
239
312
  {
240
313
  iceServers: [],
314
+ disableAudioTwcc: true,
241
315
  },
242
316
  'meeting id'
243
317
  );
@@ -267,6 +341,64 @@ describe('createMediaConnection', () => {
267
341
  multistreamRoapMediaConnectionConstructorStub,
268
342
  {
269
343
  iceServers: [],
344
+ disableAudioTwcc: true,
345
+ },
346
+ 'meeting id'
347
+ );
348
+ });
349
+
350
+ it('does not pass disableAudioMainDtx to MultistreamRoapMediaConnection if disableAudioMainDtx is undefined', () => {
351
+ const multistreamRoapMediaConnectionConstructorStub = sinon
352
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
353
+ .returns(fakeRoapMediaConnection);
354
+
355
+ Media.createMediaConnection(true, 'debug string', 'meeting id', {
356
+ mediaProperties: {
357
+ mediaDirection: {
358
+ sendAudio: true,
359
+ sendVideo: true,
360
+ sendShare: false,
361
+ receiveAudio: true,
362
+ receiveVideo: true,
363
+ receiveShare: true,
364
+ },
365
+ },
366
+ disableAudioMainDtx: undefined,
367
+ });
368
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
369
+ assert.calledWith(
370
+ multistreamRoapMediaConnectionConstructorStub,
371
+ {
372
+ iceServers: [],
373
+ disableAudioTwcc: true,
374
+ },
375
+ 'meeting id'
376
+ );
377
+ });
378
+
379
+ it('MultistreamRoapMediaConnection disable audio twcc by default', () => {
380
+ const multistreamRoapMediaConnectionConstructorStub = sinon
381
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
382
+ .returns(fakeRoapMediaConnection);
383
+
384
+ Media.createMediaConnection(true, 'debug string', 'meeting id', {
385
+ mediaProperties: {
386
+ mediaDirection: {
387
+ sendAudio: true,
388
+ sendVideo: true,
389
+ sendShare: false,
390
+ receiveAudio: true,
391
+ receiveVideo: true,
392
+ receiveShare: true,
393
+ },
394
+ },
395
+ });
396
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
397
+ assert.calledWith(
398
+ multistreamRoapMediaConnectionConstructorStub,
399
+ {
400
+ iceServers: [],
401
+ disableAudioTwcc: true,
270
402
  },
271
403
  'meeting id'
272
404
  );
@@ -276,7 +408,7 @@ describe('createMediaConnection', () => {
276
408
  {testCase: 'turnServerInfo is undefined', turnServerInfo: undefined},
277
409
  {
278
410
  testCase: 'turnServerInfo.url is empty string',
279
- turnServerInfo: {url: '', username: 'turn username', password: 'turn password'},
411
+ turnServerInfo: {urls: [], username: 'turn username', password: 'turn password'},
280
412
  },
281
413
  ].forEach(({testCase, turnServerInfo}) => {
282
414
  it(`passes empty ICE servers array to RoapMediaConnection if ${testCase} (multistream disabled)`, () => {
@@ -2,6 +2,7 @@ import 'jsdom-global/register';
2
2
  import {assert} from '@webex/test-helper-chai';
3
3
  import sinon from 'sinon';
4
4
  import {ConnectionState} from '@webex/internal-media-core';
5
+ import * as tsSdpModule from '@webex/ts-sdp';
5
6
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
6
7
  import {Defer} from '@webex/common';
7
8
  import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
@@ -10,15 +11,21 @@ describe('MediaProperties', () => {
10
11
  let mediaProperties;
11
12
  let mockMC;
12
13
  let clock;
14
+ let rtcPeerConnection;
13
15
 
14
16
  beforeEach(() => {
15
17
  clock = sinon.useFakeTimers();
16
18
 
19
+ rtcPeerConnection = {
20
+ localDescription: {sdp: ''},
21
+ };
22
+
17
23
  mockMC = {
18
24
  getStats: sinon.stub().resolves([]),
19
25
  on: sinon.stub(),
20
26
  off: sinon.stub(),
21
27
  getConnectionState: sinon.stub().returns(ConnectionState.Connected),
28
+ multistreamConnection: {pc: {pc: rtcPeerConnection}},
22
29
  };
23
30
 
24
31
  mediaProperties = new MediaProperties();
@@ -81,6 +88,129 @@ describe('MediaProperties', () => {
81
88
  assert.equal(numTransports, 0);
82
89
  });
83
90
 
91
+ describe('ipVersion', () => {
92
+ it('returns ipVersion=undefined if getStats() returns no candidate pairs', async () => {
93
+ mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
94
+ const info = await mediaProperties.getCurrentConnectionInfo();
95
+ assert.equal(info.ipVersion, undefined);
96
+ });
97
+
98
+ it('returns ipVersion=undefined if getStats() returns no selected candidate pair', async () => {
99
+ mockMC.getStats.resolves([{type: 'candidate-pair', id: '1234', selected: false}]);
100
+ const info = await mediaProperties.getCurrentConnectionInfo();
101
+ assert.equal(info.ipVersion, undefined);
102
+ });
103
+
104
+ it('returns ipVersion="IPv4" if transport has selectedCandidatePairId and local candidate has IPv4 address', async () => {
105
+ mockMC.getStats.resolves([
106
+ {type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
107
+ {type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
108
+ {type: 'local-candidate', id: 'lc1', address: '192.168.1.1'},
109
+ ]);
110
+ const info = await mediaProperties.getCurrentConnectionInfo();
111
+ assert.equal(info.ipVersion, 'IPv4');
112
+ });
113
+
114
+ it('returns ipVersion="IPv6" if transport has selectedCandidatePairId and local candidate has IPv6 address', async () => {
115
+ mockMC.getStats.resolves([
116
+ {type: 'transport', id: 't1', selectedCandidatePairId: 'cp1'},
117
+ {type: 'candidate-pair', id: 'cp1', localCandidateId: 'lc1'},
118
+ {type: 'local-candidate', id: 'lc1', address: 'fd8f:12e6:5e53:784f:a0ba:f8d5:b906:1acc'},
119
+ ]);
120
+ const info = await mediaProperties.getCurrentConnectionInfo();
121
+ assert.equal(info.ipVersion, 'IPv6');
122
+ });
123
+
124
+ it('returns ipVersion="IPv4" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv4 address', async () => {
125
+ mockMC.getStats.resolves([
126
+ {type: 'transport', id: 't1'},
127
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
128
+ {type: 'local-candidate', id: 'lc2', address: '10.0.0.1'},
129
+ ]);
130
+ const info = await mediaProperties.getCurrentConnectionInfo();
131
+ assert.equal(info.ipVersion, 'IPv4');
132
+ });
133
+
134
+ it('returns ipVersion="IPv6" if transport has no selectedCandidatePairId but finds selected candidate pair and local candidate has IPv6 address', async () => {
135
+ mockMC.getStats.resolves([
136
+ {type: 'transport', id: 't1'},
137
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
138
+ {type: 'local-candidate', id: 'lc2', address: 'fe80::1ff:fe23:4567:890a'},
139
+ ]);
140
+ const info = await mediaProperties.getCurrentConnectionInfo();
141
+ assert.equal(info.ipVersion, 'IPv6');
142
+ });
143
+
144
+ describe('local candidate without address', () => {
145
+ it('return="IPv4" if candidate from SDP with matching port number has IPv4 address', async () => {
146
+ sinon.stub(tsSdpModule, 'parse').returns({
147
+ avMedia: [
148
+ {
149
+ iceInfo: {
150
+ candidates: [
151
+ {
152
+ port: 1234,
153
+ connectionAddress: '192.168.0.1',
154
+ },
155
+ ],
156
+ },
157
+ },
158
+ ],
159
+ });
160
+
161
+ mockMC.getStats.resolves([
162
+ {type: 'transport', id: 't1'},
163
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
164
+ {type: 'local-candidate', id: 'lc2', port: 1234},
165
+ ]);
166
+ const info = await mediaProperties.getCurrentConnectionInfo();
167
+ assert.equal(info.ipVersion, 'IPv4');
168
+
169
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
170
+ });
171
+
172
+ it('returns ipVersion="IPv6" if candidate from SDP with matching port number has IPv6 address', async () => {
173
+ sinon.stub(tsSdpModule, 'parse').returns({
174
+ avMedia: [
175
+ {
176
+ iceInfo: {
177
+ candidates: [
178
+ {
179
+ port: 5000,
180
+ connectionAddress: 'fe80::1ff:fe23:4567:890a',
181
+ },
182
+ ],
183
+ },
184
+ },
185
+ ],
186
+ });
187
+
188
+ mockMC.getStats.resolves([
189
+ {type: 'transport', id: 't1'},
190
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
191
+ {type: 'local-candidate', id: 'lc2', port: 5000},
192
+ ]);
193
+ const info = await mediaProperties.getCurrentConnectionInfo();
194
+ assert.equal(info.ipVersion, 'IPv6');
195
+
196
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
197
+ });
198
+
199
+ it('returns ipVersion=undefined if parsing of the SDP fails', async () => {
200
+ sinon.stub(tsSdpModule, 'parse').throws(new Error('fake error'));
201
+
202
+ mockMC.getStats.resolves([
203
+ {type: 'candidate-pair', id: 'cp2', localCandidateId: 'lc2', selected: true},
204
+ {type: 'local-candidate', id: 'lc2', port: 5000},
205
+ ]);
206
+ const info = await mediaProperties.getCurrentConnectionInfo();
207
+ assert.equal(info.ipVersion, undefined);
208
+
209
+ assert.calledWith(tsSdpModule.parse, rtcPeerConnection.localDescription.sdp);
210
+ });
211
+ });
212
+ });
213
+
84
214
  describe('selectedCandidatePairChanges and numTransports', () => {
85
215
  it('returns correct values when getStats() returns no transport stats at all', async () => {
86
216
  mockMC.getStats.resolves([{type: 'something', id: '1234'}]);