@webex/plugin-meetings 3.8.0-next.7 → 3.8.0-next.71

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 (167) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/webex-errors.js +12 -2
  4. package/dist/common/errors/webex-errors.js.map +1 -1
  5. package/dist/config.js +4 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +17 -121
  8. package/dist/constants.js.map +1 -1
  9. package/dist/controls-options-manager/enums.js +2 -0
  10. package/dist/controls-options-manager/enums.js.map +1 -1
  11. package/dist/controls-options-manager/types.js.map +1 -1
  12. package/dist/controls-options-manager/util.js +52 -0
  13. package/dist/controls-options-manager/util.js.map +1 -1
  14. package/dist/interpretation/index.js +1 -1
  15. package/dist/interpretation/siLanguage.js +1 -1
  16. package/dist/locus-info/controlsUtils.js +28 -10
  17. package/dist/locus-info/controlsUtils.js.map +1 -1
  18. package/dist/locus-info/index.js +32 -12
  19. package/dist/locus-info/index.js.map +1 -1
  20. package/dist/locus-info/selfUtils.js +432 -418
  21. package/dist/locus-info/selfUtils.js.map +1 -1
  22. package/dist/media/index.js +14 -16
  23. package/dist/media/index.js.map +1 -1
  24. package/dist/media/properties.js +94 -6
  25. package/dist/media/properties.js.map +1 -1
  26. package/dist/meeting/brbState.js +6 -0
  27. package/dist/meeting/brbState.js.map +1 -1
  28. package/dist/meeting/in-meeting-actions.js +17 -1
  29. package/dist/meeting/in-meeting-actions.js.map +1 -1
  30. package/dist/meeting/index.js +541 -302
  31. package/dist/meeting/index.js.map +1 -1
  32. package/dist/meeting/locusMediaRequest.js +0 -17
  33. package/dist/meeting/locusMediaRequest.js.map +1 -1
  34. package/dist/meeting/muteState.js +0 -2
  35. package/dist/meeting/muteState.js.map +1 -1
  36. package/dist/meeting/request.js +30 -0
  37. package/dist/meeting/request.js.map +1 -1
  38. package/dist/meeting/request.type.js.map +1 -1
  39. package/dist/meeting/util.js +13 -2
  40. package/dist/meeting/util.js.map +1 -1
  41. package/dist/meeting-info/meeting-info-v2.js +359 -60
  42. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  43. package/dist/meetings/index.js +114 -1
  44. package/dist/meetings/index.js.map +1 -1
  45. package/dist/meetings/util.js +14 -0
  46. package/dist/meetings/util.js.map +1 -1
  47. package/dist/member/index.js +10 -0
  48. package/dist/member/index.js.map +1 -1
  49. package/dist/member/util.js +330 -353
  50. package/dist/member/util.js.map +1 -1
  51. package/dist/members/index.js +23 -0
  52. package/dist/members/index.js.map +1 -1
  53. package/dist/members/request.js +21 -0
  54. package/dist/members/request.js.map +1 -1
  55. package/dist/members/util.js +15 -0
  56. package/dist/members/util.js.map +1 -1
  57. package/dist/metrics/constants.js +9 -0
  58. package/dist/metrics/constants.js.map +1 -1
  59. package/dist/reachability/clusterReachability.js +63 -27
  60. package/dist/reachability/clusterReachability.js.map +1 -1
  61. package/dist/reachability/index.js +112 -47
  62. package/dist/reachability/index.js.map +1 -1
  63. package/dist/reachability/reachability.types.js +14 -0
  64. package/dist/reachability/reachability.types.js.map +1 -1
  65. package/dist/reachability/request.js +19 -3
  66. package/dist/reachability/request.js.map +1 -1
  67. package/dist/reconnection-manager/index.js +2 -2
  68. package/dist/reconnection-manager/index.js.map +1 -1
  69. package/dist/recording-controller/util.js +5 -5
  70. package/dist/recording-controller/util.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 +12 -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 +32 -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 +80 -0
  91. package/dist/types/meetings/index.d.ts +48 -0
  92. package/dist/types/member/index.d.ts +1 -0
  93. package/dist/types/member/util.d.ts +159 -1
  94. package/dist/types/members/index.d.ts +8 -0
  95. package/dist/types/members/request.d.ts +19 -0
  96. package/dist/types/members/util.d.ts +13 -0
  97. package/dist/types/metrics/constants.d.ts +9 -0
  98. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  99. package/dist/types/reachability/index.d.ts +10 -1
  100. package/dist/types/reachability/reachability.types.d.ts +5 -0
  101. package/dist/types/roap/index.d.ts +3 -2
  102. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  103. package/dist/types/roap/types.d.ts +16 -0
  104. package/dist/webinar/index.js +1 -1
  105. package/package.json +24 -23
  106. package/src/common/errors/webex-errors.ts +8 -1
  107. package/src/config.ts +2 -0
  108. package/src/constants.ts +19 -90
  109. package/src/controls-options-manager/enums.ts +2 -0
  110. package/src/controls-options-manager/types.ts +11 -1
  111. package/src/controls-options-manager/util.ts +62 -0
  112. package/src/locus-info/controlsUtils.ts +44 -14
  113. package/src/locus-info/index.ts +38 -12
  114. package/src/locus-info/selfUtils.ts +496 -442
  115. package/src/media/index.ts +20 -21
  116. package/src/media/properties.ts +96 -0
  117. package/src/meeting/brbState.ts +7 -0
  118. package/src/meeting/in-meeting-actions.ts +32 -0
  119. package/src/meeting/index.ts +346 -93
  120. package/src/meeting/locusMediaRequest.ts +0 -18
  121. package/src/meeting/muteState.ts +0 -2
  122. package/src/meeting/request.ts +36 -1
  123. package/src/meeting/request.type.ts +7 -0
  124. package/src/meeting/util.ts +11 -2
  125. package/src/meeting-info/meeting-info-v2.ts +247 -6
  126. package/src/meetings/index.ts +128 -1
  127. package/src/meetings/util.ts +18 -0
  128. package/src/member/index.ts +13 -2
  129. package/src/member/util.ts +351 -348
  130. package/src/members/index.ts +25 -0
  131. package/src/members/request.ts +26 -0
  132. package/src/members/util.ts +16 -0
  133. package/src/metrics/constants.ts +9 -0
  134. package/src/reachability/clusterReachability.ts +73 -26
  135. package/src/reachability/index.ts +70 -1
  136. package/src/reachability/reachability.types.ts +6 -0
  137. package/src/reachability/request.ts +7 -0
  138. package/src/reconnection-manager/index.ts +2 -2
  139. package/src/recording-controller/util.ts +17 -13
  140. package/src/roap/index.ts +3 -7
  141. package/src/roap/turnDiscovery.ts +34 -39
  142. package/src/roap/types.ts +23 -0
  143. package/test/unit/spec/controls-options-manager/util.js +120 -0
  144. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  145. package/test/unit/spec/locus-info/index.js +141 -73
  146. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  147. package/test/unit/spec/media/index.ts +98 -16
  148. package/test/unit/spec/media/properties.ts +130 -0
  149. package/test/unit/spec/meeting/brbState.ts +19 -0
  150. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  151. package/test/unit/spec/meeting/index.js +524 -35
  152. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  153. package/test/unit/spec/meeting/muteState.js +0 -2
  154. package/test/unit/spec/meeting/request.js +32 -1
  155. package/test/unit/spec/meeting/utils.js +119 -18
  156. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  157. package/test/unit/spec/meetings/index.js +133 -2
  158. package/test/unit/spec/member/index.js +7 -0
  159. package/test/unit/spec/member/util.js +24 -0
  160. package/test/unit/spec/members/index.js +103 -26
  161. package/test/unit/spec/members/request.js +45 -22
  162. package/test/unit/spec/members/utils.js +33 -0
  163. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  164. package/test/unit/spec/reachability/index.ts +101 -0
  165. package/test/unit/spec/reachability/request.js +47 -2
  166. package/test/unit/spec/reconnection-manager/index.js +4 -4
  167. 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,7 @@ 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: ['turns:turn-server-url-1:443?transport=tcp', 'turns:turn-server-url-2:443?transport=tcp'],
83
84
  username: 'turn username',
84
85
  password: 'turn password',
85
86
  },
@@ -91,12 +92,7 @@ describe('createMediaConnection', () => {
91
92
  {
92
93
  iceServers: [
93
94
  {
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',
95
+ urls: ['turns:turn-server-url-1:443?transport=tcp', 'turns:turn-server-url-2:443?transport=tcp'],
100
96
  username: 'turn username',
101
97
  credential: 'turn password',
102
98
  },
@@ -159,11 +155,12 @@ describe('createMediaConnection', () => {
159
155
  },
160
156
  rtcMetrics,
161
157
  turnServerInfo: {
162
- url: 'turns:turn-server-url:443?transport=tcp',
158
+ urls: ['turns:turn-server-url-1:443?transport=tcp', 'turns:turn-server-url-2:443?transport=tcp'],
163
159
  username: 'turn username',
164
160
  password: 'turn password',
165
161
  },
166
162
  bundlePolicy: 'max-bundle',
163
+ disableAudioMainDtx: false,
167
164
  });
168
165
  assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
169
166
  assert.calledWith(
@@ -171,17 +168,13 @@ describe('createMediaConnection', () => {
171
168
  {
172
169
  iceServers: [
173
170
  {
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',
171
+ urls: ['turns:turn-server-url-1:443?transport=tcp', 'turns:turn-server-url-2:443?transport=tcp'],
180
172
  username: 'turn username',
181
173
  credential: 'turn password',
182
174
  },
183
175
  ],
184
176
  bundlePolicy: 'max-bundle',
177
+ disableAudioMainDtx: false,
185
178
  },
186
179
  'meeting id'
187
180
  );
@@ -205,14 +198,75 @@ describe('createMediaConnection', () => {
205
198
 
206
199
  sendMetricsInQueueCallback();
207
200
  assert.calledOnce(rtcMetrics.sendMetricsInQueue);
201
+ });
202
+
203
+ it('multistream non-firefox does not care about stopIceGatheringAfterFirstRelayCandidate', () => {
204
+ const multistreamRoapMediaConnectionConstructorStub = sinon
205
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
206
+ .returns(fakeRoapMediaConnection);
207
+
208
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
209
+ stopIceGatheringAfterFirstRelayCandidate: true,
210
+ });
211
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
212
+ assert.calledWith(
213
+ multistreamRoapMediaConnectionConstructorStub,
214
+ {
215
+ iceServers: []
216
+ },
217
+ 'meeting id'
218
+ );
219
+ });
220
+
221
+ it('multistream firefox stops gathering after first relay if stopIceGatheringAfterFirstRelayCandidate is true', () => {
222
+ const multistreamRoapMediaConnectionConstructorStub = sinon
223
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
224
+ .returns(fakeRoapMediaConnection);
225
+
226
+ sinon.stub(BrowserInfo, 'isFirefox').returns(true);
227
+
228
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
229
+ stopIceGatheringAfterFirstRelayCandidate: true,
230
+ });
231
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
232
+ assert.calledWith(
233
+ multistreamRoapMediaConnectionConstructorStub,
234
+ {
235
+ iceServers: [],
236
+ doFullIce: true,
237
+ stopIceGatheringAfterFirstRelayCandidate: true,
238
+ },
239
+ 'meeting id'
240
+ );
241
+ });
208
242
 
243
+ it('multistream firefox continues gathering if stopIceGatheringAfterFirstRelayCandidate is false', () => {
244
+ const multistreamRoapMediaConnectionConstructorStub = sinon
245
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
246
+ .returns(fakeRoapMediaConnection);
247
+
248
+ sinon.stub(BrowserInfo, 'isFirefox').returns(true);
249
+
250
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
251
+ stopIceGatheringAfterFirstRelayCandidate: false,
252
+ });
253
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
254
+ assert.calledWith(
255
+ multistreamRoapMediaConnectionConstructorStub,
256
+ {
257
+ iceServers: [],
258
+ doFullIce: true,
259
+ stopIceGatheringAfterFirstRelayCandidate: false,
260
+ },
261
+ 'meeting id'
262
+ );
209
263
  });
210
264
 
211
265
  [
212
266
  {testCase: 'turnServerInfo is undefined', turnServerInfo: undefined},
213
267
  {
214
268
  testCase: 'turnServerInfo.url is empty string',
215
- turnServerInfo: {url: '', username: 'turn username', password: 'turn password'},
269
+ turnServerInfo: {urls: [], username: 'turn username', password: 'turn password'},
216
270
  },
217
271
  ].forEach(({testCase, turnServerInfo}) => {
218
272
  it(`passes empty ICE servers array to MultistreamRoapMediaConnection if ${testCase} (multistream enabled)`, () => {
@@ -272,11 +326,39 @@ describe('createMediaConnection', () => {
272
326
  );
273
327
  });
274
328
 
329
+ it('does not pass disableAudioMainDtx to MultistreamRoapMediaConnection if disableAudioMainDtx is undefined', () => {
330
+ const multistreamRoapMediaConnectionConstructorStub = sinon
331
+ .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
332
+ .returns(fakeRoapMediaConnection);
333
+
334
+ Media.createMediaConnection(true, 'debug string', 'meeting id', {
335
+ mediaProperties: {
336
+ mediaDirection: {
337
+ sendAudio: true,
338
+ sendVideo: true,
339
+ sendShare: false,
340
+ receiveAudio: true,
341
+ receiveVideo: true,
342
+ receiveShare: true,
343
+ },
344
+ },
345
+ disableAudioMainDtx: undefined,
346
+ });
347
+ assert.calledOnce(multistreamRoapMediaConnectionConstructorStub);
348
+ assert.calledWith(
349
+ multistreamRoapMediaConnectionConstructorStub,
350
+ {
351
+ iceServers: [],
352
+ },
353
+ 'meeting id'
354
+ );
355
+ });
356
+
275
357
  [
276
358
  {testCase: 'turnServerInfo is undefined', turnServerInfo: undefined},
277
359
  {
278
360
  testCase: 'turnServerInfo.url is empty string',
279
- turnServerInfo: {url: '', username: 'turn username', password: 'turn password'},
361
+ turnServerInfo: {urls: [], username: 'turn username', password: 'turn password'},
280
362
  },
281
363
  ].forEach(({testCase, turnServerInfo}) => {
282
364
  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'}]);
@@ -110,5 +110,24 @@ describe('plugin-meetings', () => {
110
110
 
111
111
  assert.isTrue(brbState.state.server.enabled);
112
112
  });
113
+
114
+ it('invokes handleServerBrbUpdate with correct client state after syncing with server', async () => {
115
+ const sendLocalBrbStateToServerStub = sinon
116
+ .stub(brbState, 'sendLocalBrbStateToServer')
117
+ .resolves();
118
+
119
+ const handleServerBrbUpdateSpy = sinon.spy(brbState, 'handleServerBrbUpdate');
120
+
121
+ await brbState.enable(true, meeting.sendSlotManager);
122
+
123
+ assert.isTrue(sendLocalBrbStateToServerStub.calledOnce);
124
+
125
+ assert.isTrue(handleServerBrbUpdateSpy.calledOnceWith(brbState.state.client.enabled));
126
+
127
+ assert.isFalse(brbState.state.syncToServerInProgress);
128
+
129
+ sendLocalBrbStateToServerStub.restore();
130
+ handleServerBrbUpdateSpy.restore();
131
+ });
113
132
  });
114
133
  });
@@ -76,6 +76,8 @@ describe('plugin-meetings', () => {
76
76
  canShareDesktop: null,
77
77
  canShareContent: null,
78
78
  canTransferFile: null,
79
+ canRealtimeCloseCaption: null,
80
+ canRealtimeCloseCaptionManual: null,
79
81
  canChat: null,
80
82
  canDoVideo: null,
81
83
  canAnnotate: null,
@@ -90,10 +92,16 @@ describe('plugin-meetings', () => {
90
92
  canShowStageView: null,
91
93
  canEnableStageView: null,
92
94
  canDisableStageView: null,
93
- isPracticeSessionOn : null,
94
- isPracticeSessionOff : null,
95
+ isPracticeSessionOn: null,
96
+ isPracticeSessionOff: null,
95
97
  canStartPracticeSession: null,
96
98
  canStopPracticeSession: null,
99
+ requiresPostMeetingDataConsentPrompt: null,
100
+ canEnableAnnotation: null,
101
+ canDisableAnnotation: null,
102
+ canEnableRemoteDesktopControl: null,
103
+ canDisableRemoteDesktopControl: null,
104
+ canMoveToLobby: null,
97
105
 
98
106
  ...expected,
99
107
  };
@@ -176,6 +184,8 @@ describe('plugin-meetings', () => {
176
184
  'canShareDesktop',
177
185
  'canShareContent',
178
186
  'canTransferFile',
187
+ 'canRealtimeCloseCaption',
188
+ 'canRealtimeCloseCaptionManual',
179
189
  'canChat',
180
190
  'canDoVideo',
181
191
  'canAnnotate',
@@ -194,8 +204,13 @@ describe('plugin-meetings', () => {
194
204
  'isPracticeSessionOff',
195
205
  'canStartPracticeSession',
196
206
  'canStopPracticeSession',
197
-
198
- ].forEach((key) => {
207
+ 'requiresPostMeetingDataConsentPrompt',
208
+ 'canEnableAnnotation',
209
+ 'canDisableAnnotation',
210
+ 'canEnableRemoteDesktopControl',
211
+ 'canDisableRemoteDesktopControl',
212
+ 'canMoveToLobby',
213
+ ].forEach((key) => {
199
214
  it(`get and set for ${key} work as expected`, () => {
200
215
  const inMeetingActions = new InMeetingActions();
201
216