@webex/plugin-meetings 3.8.1-next.4 → 3.8.1-next.41

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 (94) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +24 -2
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/index.js +38 -84
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/media/index.js +2 -2
  11. package/dist/media/index.js.map +1 -1
  12. package/dist/meeting/brbState.js +14 -12
  13. package/dist/meeting/brbState.js.map +1 -1
  14. package/dist/meeting/in-meeting-actions.js +6 -0
  15. package/dist/meeting/in-meeting-actions.js.map +1 -1
  16. package/dist/meeting/index.js +213 -77
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meeting/request.js +19 -0
  19. package/dist/meeting/request.js.map +1 -1
  20. package/dist/meeting/request.type.js.map +1 -1
  21. package/dist/meeting/type.js +7 -0
  22. package/dist/meeting/type.js.map +1 -0
  23. package/dist/meeting/util.js +11 -0
  24. package/dist/meeting/util.js.map +1 -1
  25. package/dist/meetings/index.js +35 -33
  26. package/dist/meetings/index.js.map +1 -1
  27. package/dist/members/index.js +11 -9
  28. package/dist/members/index.js.map +1 -1
  29. package/dist/members/request.js +3 -3
  30. package/dist/members/request.js.map +1 -1
  31. package/dist/members/util.js +18 -6
  32. package/dist/members/util.js.map +1 -1
  33. package/dist/multistream/mediaRequestManager.js +1 -1
  34. package/dist/multistream/mediaRequestManager.js.map +1 -1
  35. package/dist/multistream/remoteMedia.js +34 -5
  36. package/dist/multistream/remoteMedia.js.map +1 -1
  37. package/dist/multistream/remoteMediaGroup.js +42 -2
  38. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  39. package/dist/multistream/sendSlotManager.js +32 -2
  40. package/dist/multistream/sendSlotManager.js.map +1 -1
  41. package/dist/reachability/index.js +5 -10
  42. package/dist/reachability/index.js.map +1 -1
  43. package/dist/types/constants.d.ts +22 -0
  44. package/dist/types/locus-info/index.d.ts +0 -9
  45. package/dist/types/meeting/brbState.d.ts +0 -1
  46. package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
  47. package/dist/types/meeting/index.d.ts +35 -18
  48. package/dist/types/meeting/request.d.ts +9 -1
  49. package/dist/types/meeting/request.type.d.ts +74 -0
  50. package/dist/types/meeting/type.d.ts +9 -0
  51. package/dist/types/meeting/util.d.ts +3 -0
  52. package/dist/types/members/index.d.ts +10 -7
  53. package/dist/types/members/request.d.ts +1 -1
  54. package/dist/types/members/util.d.ts +7 -3
  55. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  56. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  57. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  58. package/dist/types/reachability/index.d.ts +2 -2
  59. package/dist/webinar/index.js +1 -1
  60. package/package.json +24 -25
  61. package/src/constants.ts +23 -2
  62. package/src/locus-info/index.ts +47 -82
  63. package/src/media/index.ts +2 -2
  64. package/src/meeting/brbState.ts +9 -7
  65. package/src/meeting/in-meeting-actions.ts +13 -0
  66. package/src/meeting/index.ts +168 -42
  67. package/src/meeting/request.ts +16 -0
  68. package/src/meeting/request.type.ts +64 -0
  69. package/src/meeting/type.ts +9 -0
  70. package/src/meeting/util.ts +13 -0
  71. package/src/meetings/index.ts +3 -2
  72. package/src/members/index.ts +13 -10
  73. package/src/members/request.ts +2 -2
  74. package/src/members/util.ts +16 -4
  75. package/src/multistream/mediaRequestManager.ts +7 -7
  76. package/src/multistream/remoteMedia.ts +34 -4
  77. package/src/multistream/remoteMediaGroup.ts +37 -2
  78. package/src/multistream/sendSlotManager.ts +34 -2
  79. package/src/reachability/index.ts +5 -13
  80. package/test/unit/spec/locus-info/index.js +177 -83
  81. package/test/unit/spec/media/index.ts +107 -0
  82. package/test/unit/spec/meeting/brbState.ts +9 -9
  83. package/test/unit/spec/meeting/in-meeting-actions.ts +6 -0
  84. package/test/unit/spec/meeting/index.js +694 -60
  85. package/test/unit/spec/meeting/request.js +71 -0
  86. package/test/unit/spec/meeting/utils.js +21 -0
  87. package/test/unit/spec/meetings/index.js +2 -0
  88. package/test/unit/spec/members/index.js +68 -9
  89. package/test/unit/spec/members/request.js +2 -2
  90. package/test/unit/spec/members/utils.js +27 -7
  91. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  92. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  93. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  94. package/test/unit/spec/reachability/index.ts +2 -6
@@ -56,6 +56,7 @@ import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/reques
56
56
  import LocusInfo from '@webex/plugin-meetings/src/locus-info';
57
57
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
58
58
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
59
+ import MembersUtil from '@webex/plugin-meetings/src/members/util';
59
60
  import MeetingsUtil from '@webex/plugin-meetings/src/meetings/util';
60
61
  import Media from '@webex/plugin-meetings/src/media/index';
61
62
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
@@ -244,6 +245,7 @@ describe('plugin-meetings', () => {
244
245
  });
245
246
 
246
247
  webex.internal.newMetrics.callDiagnosticMetrics.clearErrorCache = sinon.stub();
248
+ webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
247
249
  webex.internal.support.submitLogs = sinon.stub().returns(Promise.resolve());
248
250
  webex.internal.services = {get: sinon.stub().returns('locus-url')};
249
251
  webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
@@ -368,6 +370,35 @@ describe('plugin-meetings', () => {
368
370
  assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
369
371
  assert.instanceOf(meeting.webinar, Webinar);
370
372
  });
373
+
374
+ it('should call the callback with the meeting that has id already set', () => {
375
+ let meetingIdFromCallback;
376
+ // check that the meeting id is already set correctly at the time when the callback is called
377
+ const meetingCreationCallback = sinon.stub().callsFake((meeting) => {
378
+ meetingIdFromCallback = meeting.id;
379
+ });
380
+
381
+ meeting = new Meeting(
382
+ {
383
+ userId: uuid1,
384
+ resource: uuid2,
385
+ deviceUrl: uuid3,
386
+ locus: {url: url1},
387
+ destination: testDestination,
388
+ destinationType: DESTINATION_TYPE.MEETING_ID,
389
+ correlationId,
390
+ selfId: uuid1,
391
+ },
392
+ {
393
+ parent: webex,
394
+ },
395
+ meetingCreationCallback
396
+ );
397
+ assert.exists(meeting.id);
398
+ assert.calledOnceWithExactly(meetingCreationCallback, meeting);
399
+ assert.equal(meeting.id, meetingIdFromCallback);
400
+ });
401
+
371
402
  it('creates MediaRequestManager instances', () => {
372
403
  assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
373
404
  assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
@@ -581,7 +612,6 @@ describe('plugin-meetings', () => {
581
612
  assert.isFalse(meeting.isLocusCall());
582
613
  });
583
614
  });
584
-
585
615
  describe('#invite', () => {
586
616
  it('should have #invite', () => {
587
617
  assert.exists(meeting.invite);
@@ -592,8 +622,6 @@ describe('plugin-meetings', () => {
592
622
  it('should proxy members #addMember and return a promise', async () => {
593
623
  const invite = meeting.invite(uuid1, false);
594
624
 
595
- assert.exists(invite.then);
596
- await invite;
597
625
  assert.calledOnce(meeting.members.addMember);
598
626
  assert.calledWith(meeting.members.addMember, uuid1, false);
599
627
  });
@@ -614,20 +642,20 @@ describe('plugin-meetings', () => {
614
642
  assert.calledWith(meeting.members.cancelPhoneInvite, uuid1);
615
643
  });
616
644
  });
617
- describe('#cancelSIPInvite', () => {
618
- it('should have #cancelSIPInvite', () => {
619
- assert.exists(meeting.cancelSIPInvite);
645
+ describe('#cancelInviteByMemberId', () => {
646
+ it('should have #cancelInviteByMemberId', () => {
647
+ assert.exists(meeting.cancelInviteByMemberId);
620
648
  });
621
649
  beforeEach(() => {
622
- meeting.members.cancelSIPInvite = sinon.stub().returns(Promise.resolve(test1));
650
+ meeting.members.cancelInviteByMemberId = sinon.stub().returns(Promise.resolve(test1));
623
651
  });
624
- it('should proxy members #cancelSIPInvite and return a promise', async () => {
625
- const cancel = meeting.cancelSIPInvite({memberId: uuid1});
652
+ it('should proxy members #cancelInviteByMemberId and return a promise', async () => {
653
+ const cancel = meeting.cancelInviteByMemberId({memberId: uuid1});
626
654
 
627
655
  assert.exists(cancel.then);
628
656
  await cancel;
629
- assert.calledOnce(meeting.members.cancelSIPInvite);
630
- assert.calledWith(meeting.members.cancelSIPInvite, {memberId: uuid1});
657
+ assert.calledOnce(meeting.members.cancelInviteByMemberId);
658
+ assert.calledWith(meeting.members.cancelInviteByMemberId, {memberId: uuid1});
631
659
  });
632
660
  });
633
661
  describe('#admit', () => {
@@ -1208,8 +1236,73 @@ describe('plugin-meetings', () => {
1208
1236
  reason: 'joinWithMedia failure',
1209
1237
  });
1210
1238
  });
1211
- });
1212
1239
 
1240
+ it('should ignore sendVideo/receiveVideo when videoEnabled is false', async () => {
1241
+ await meeting.joinWithMedia({
1242
+ joinOptions,
1243
+ mediaOptions: {
1244
+ videoEnabled: false,
1245
+ sendVideo: true,
1246
+ receiveVideo: true,
1247
+ allowMediaInLobby: true,
1248
+ },
1249
+ });
1250
+
1251
+ assert.calledWithMatch(
1252
+ meeting.addMediaInternal,
1253
+ sinon.match.any,
1254
+ sinon.match.any,
1255
+ sinon.match.any,
1256
+ sinon.match.has('videoEnabled', false).and(sinon.match.has('allowMediaInLobby', true))
1257
+ );
1258
+ });
1259
+
1260
+ it('should ignore sendAudio/receiveAudio when audioEnabled is false', async () => {
1261
+ await meeting.joinWithMedia({
1262
+ joinOptions,
1263
+ mediaOptions: {
1264
+ audioEnabled: false,
1265
+ sendAudio: true,
1266
+ receiveAudio: false,
1267
+ allowMediaInLobby: true,
1268
+ },
1269
+ });
1270
+
1271
+ assert.calledWithMatch(
1272
+ meeting.addMediaInternal,
1273
+ sinon.match.any,
1274
+ sinon.match.any,
1275
+ sinon.match.any,
1276
+ sinon.match.has('audioEnabled', false).and(sinon.match.has('allowMediaInLobby', true))
1277
+ );
1278
+ });
1279
+
1280
+ it('should use provided send/receive values when videoEnabled/audioEnabled are true or not set', async () => {
1281
+ await meeting.joinWithMedia({
1282
+ joinOptions,
1283
+ mediaOptions: {
1284
+ sendVideo: true,
1285
+ receiveVideo: false,
1286
+ sendAudio: false,
1287
+ receiveAudio: true,
1288
+ allowMediaInLobby: true,
1289
+ },
1290
+ });
1291
+
1292
+ assert.calledWith(
1293
+ meeting.addMediaInternal,
1294
+ sinon.match.any,
1295
+ sinon.match.any,
1296
+ sinon.match.any,
1297
+ sinon.match({
1298
+ sendVideo: true,
1299
+ receiveVideo: false,
1300
+ sendAudio: false,
1301
+ receiveAudio: true,
1302
+ })
1303
+ );
1304
+ });
1305
+ });
1213
1306
  describe('#isTranscriptionSupported', () => {
1214
1307
  it('should return false if the feature is not supported for the meeting', () => {
1215
1308
  meeting.locusInfo.controls = {transcribe: {caption: false}};
@@ -1233,14 +1326,13 @@ describe('plugin-meetings', () => {
1233
1326
  sinon.restore();
1234
1327
  });
1235
1328
  it('should call voicea.onSpokenLanguageUpdate when joined', async () => {
1236
-
1237
1329
  meeting.joinedWith = {state: 'JOINED'};
1238
1330
  await meeting.locusInfo.emitScoped(
1239
1331
  {function: 'test', file: 'test'},
1240
1332
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1241
- {spokenLanguage: 'fr'},
1333
+ {spokenLanguage: 'fr'}
1242
1334
  );
1243
- assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'fr');
1335
+ assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'fr', meeting.id);
1244
1336
  assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'fr');
1245
1337
  assert.calledWith(
1246
1338
  TriggerProxy.trigger,
@@ -1251,12 +1343,11 @@ describe('plugin-meetings', () => {
1251
1343
  });
1252
1344
 
1253
1345
  it('should also call voicea.onSpokenLanguageUpdate when not joined', async () => {
1254
-
1255
1346
  meeting.joinedWith = {state: 'NOT_JOINED'};
1256
1347
  await meeting.locusInfo.emitScoped(
1257
1348
  {function: 'test', file: 'test'},
1258
1349
  LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIPTION_SPOKEN_LANGUAGE_UPDATED,
1259
- {spokenLanguage: 'de'},
1350
+ {spokenLanguage: 'de'}
1260
1351
  );
1261
1352
  assert.calledWith(webex.internal.voicea.onSpokenLanguageUpdate, 'de');
1262
1353
  assert.equal(meeting.transcription.languageOptions.currentSpokenLanguage, 'de');
@@ -2101,14 +2192,12 @@ describe('plugin-meetings', () => {
2101
2192
  };
2102
2193
  meeting.mediaProperties.setMediaDirection = sinon.stub().returns(true);
2103
2194
  meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2104
- meeting.mediaProperties.getCurrentConnectionInfo = sinon
2105
- .stub()
2106
- .resolves({
2107
- connectionType: 'udp',
2108
- selectedCandidatePairChanges: 2,
2109
- numTransports: 1,
2110
- ipVersion: 'IPv6',
2111
- });
2195
+ meeting.mediaProperties.getCurrentConnectionInfo = sinon.stub().resolves({
2196
+ connectionType: 'udp',
2197
+ selectedCandidatePairChanges: 2,
2198
+ numTransports: 1,
2199
+ ipVersion: 'IPv6',
2200
+ });
2112
2201
  meeting.audio = muteStateStub;
2113
2202
  meeting.video = muteStateStub;
2114
2203
  sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
@@ -2227,8 +2316,9 @@ describe('plugin-meetings', () => {
2227
2316
  someReachabilityMetric1: 'some value1',
2228
2317
  someReachabilityMetric2: 'some value2',
2229
2318
  selectedCandidatePairChanges: 2,
2230
- isSubnetReachable: null,
2231
- selectedCluster: null,
2319
+ subnet_reachable: null,
2320
+ selected_cluster: null,
2321
+ selected_subnet: null,
2232
2322
  numTransports: 1,
2233
2323
  iceCandidatesCount: 0,
2234
2324
  }
@@ -2275,8 +2365,9 @@ describe('plugin-meetings', () => {
2275
2365
  signalingState: 'unknown',
2276
2366
  connectionState: 'unknown',
2277
2367
  iceConnectionState: 'unknown',
2278
- isSubnetReachable: null,
2279
- selectedCluster: null,
2368
+ subnet_reachable: null,
2369
+ selected_cluster: null,
2370
+ selected_subnet: null,
2280
2371
  })
2281
2372
  );
2282
2373
 
@@ -2342,8 +2433,9 @@ describe('plugin-meetings', () => {
2342
2433
  selectedCandidatePairChanges: 2,
2343
2434
  numTransports: 1,
2344
2435
  iceCandidatesCount: 0,
2345
- isSubnetReachable: null,
2346
- selectedCluster: null,
2436
+ subnet_reachable: null,
2437
+ selected_cluster: null,
2438
+ selected_subnet: null,
2347
2439
  }
2348
2440
  );
2349
2441
  });
@@ -2401,8 +2493,9 @@ describe('plugin-meetings', () => {
2401
2493
  signalingState: 'have-local-offer',
2402
2494
  connectionState: 'connecting',
2403
2495
  iceConnectionState: 'checking',
2404
- isSubnetReachable: null,
2405
- selectedCluster: null,
2496
+ subnet_reachable: null,
2497
+ selected_cluster: null,
2498
+ selected_subnet: null,
2406
2499
  })
2407
2500
  );
2408
2501
 
@@ -2460,8 +2553,9 @@ describe('plugin-meetings', () => {
2460
2553
  signalingState: 'have-local-offer',
2461
2554
  connectionState: 'connecting',
2462
2555
  iceConnectionState: 'checking',
2463
- isSubnetReachable: null,
2464
- selectedCluster: null,
2556
+ subnet_reachable: null,
2557
+ selected_cluster: null,
2558
+ selected_subnet: null,
2465
2559
  })
2466
2560
  );
2467
2561
 
@@ -2983,8 +3077,9 @@ describe('plugin-meetings', () => {
2983
3077
  selectedCandidatePairChanges: 2,
2984
3078
  numTransports: 1,
2985
3079
  iceCandidatesCount: 0,
2986
- isSubnetReachable: null,
2987
- selectedCluster: null,
3080
+ subnet_reachable: null,
3081
+ selected_cluster: null,
3082
+ selected_subnet: null,
2988
3083
  },
2989
3084
  ]);
2990
3085
 
@@ -3191,8 +3286,9 @@ describe('plugin-meetings', () => {
3191
3286
  retriedWithTurnServer: true,
3192
3287
  isJoinWithMediaRetry: false,
3193
3288
  iceCandidatesCount: 0,
3194
- isSubnetReachable: null,
3195
- selectedCluster: null,
3289
+ subnet_reachable: null,
3290
+ selected_cluster: null,
3291
+ selected_subnet: null,
3196
3292
  },
3197
3293
  ]);
3198
3294
  meeting.roap.doTurnDiscovery;
@@ -3326,8 +3422,8 @@ describe('plugin-meetings', () => {
3326
3422
  meeting.mediaConnections = [
3327
3423
  {
3328
3424
  mediaAgentCluster: 'some.cluster',
3329
- }
3330
- ]
3425
+ },
3426
+ ];
3331
3427
  meeting.iceCandidatesCount = 3;
3332
3428
  meeting.iceCandidateErrors.set('701_error', 3);
3333
3429
  meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
@@ -3355,8 +3451,9 @@ describe('plugin-meetings', () => {
3355
3451
  iceCandidatesCount: 3,
3356
3452
  '701_error': 3,
3357
3453
  '701_turn_host_lookup_received_error': 1,
3358
- isSubnetReachable: null,
3359
- selectedCluster: 'some.cluster',
3454
+ subnet_reachable: null,
3455
+ selected_cluster: 'some.cluster',
3456
+ selected_subnet: null,
3360
3457
  }
3361
3458
  );
3362
3459
 
@@ -3419,8 +3516,9 @@ describe('plugin-meetings', () => {
3419
3516
  iceConnectionState: 'unknown',
3420
3517
  selectedCandidatePairChanges: 2,
3421
3518
  numTransports: 1,
3422
- isSubnetReachable: null,
3423
- selectedCluster: null,
3519
+ subnet_reachable: null,
3520
+ selected_cluster: null,
3521
+ selected_subnet: null,
3424
3522
  iceCandidatesCount: 0,
3425
3523
  }
3426
3524
  );
@@ -3482,8 +3580,9 @@ describe('plugin-meetings', () => {
3482
3580
  numTransports: 1,
3483
3581
  '701_error': 2,
3484
3582
  '701_turn_host_lookup_received_error': 1,
3485
- isSubnetReachable: null,
3486
- selectedCluster: null,
3583
+ subnet_reachable: null,
3584
+ selected_cluster: null,
3585
+ selected_subnet: null,
3487
3586
  iceCandidatesCount: 0,
3488
3587
  }
3489
3588
  );
@@ -3491,7 +3590,7 @@ describe('plugin-meetings', () => {
3491
3590
  assert.isOk(errorThrown);
3492
3591
  });
3493
3592
 
3494
- it('should send valid isSubnetReachability if media connection success', async () => {
3593
+ it('should send subnet reachablity metrics if media connection success', async () => {
3495
3594
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
3496
3595
  turnServerInfo: undefined,
3497
3596
  turnDiscoverySkippedReason: undefined,
@@ -3505,6 +3604,12 @@ describe('plugin-meetings', () => {
3505
3604
  stopReachability: sinon.stub(),
3506
3605
  isSubnetReachable: sinon.stub().returns(false),
3507
3606
  };
3607
+ meeting.mediaServerIp = '1.2.3.4';
3608
+ meeting.mediaConnections = [
3609
+ {
3610
+ mediaAgentCluster: 'some.cluster',
3611
+ },
3612
+ ];
3508
3613
 
3509
3614
  const forceRtcMetricsSend = sinon.stub().resolves();
3510
3615
  const closeMediaConnectionStub = sinon.stub();
@@ -3532,12 +3637,13 @@ describe('plugin-meetings', () => {
3532
3637
  isJoinWithMediaRetry: false,
3533
3638
  iceCandidatesCount: 0,
3534
3639
  reachability_public_udp_success: 5,
3535
- isSubnetReachable: false,
3536
- selectedCluster: null,
3640
+ subnet_reachable: false,
3641
+ selected_cluster: 'some.cluster',
3642
+ selected_subnet: '1.X.X.X',
3537
3643
  });
3538
3644
  });
3539
3645
 
3540
- it('should send valid isSubnetReachability if media connection fails', async () => {
3646
+ it('should send subnet reachablity metrics if media connection fails', async () => {
3541
3647
  let errorThrown = undefined;
3542
3648
 
3543
3649
  meeting.roap.doTurnDiscovery = sinon.stub().returns({
@@ -3553,6 +3659,12 @@ describe('plugin-meetings', () => {
3553
3659
  stopReachability: sinon.stub(),
3554
3660
  isSubnetReachable: sinon.stub().returns(true),
3555
3661
  };
3662
+ meeting.mediaServerIp = '1.2.3.4';
3663
+ meeting.mediaConnections = [
3664
+ {
3665
+ mediaAgentCluster: 'some.cluster',
3666
+ },
3667
+ ];
3556
3668
 
3557
3669
  const forceRtcMetricsSend = sinon.stub().resolves();
3558
3670
  const closeMediaConnectionStub = sinon.stub();
@@ -3594,8 +3706,9 @@ describe('plugin-meetings', () => {
3594
3706
  selectedCandidatePairChanges: 2,
3595
3707
  numTransports: 1,
3596
3708
  reachability_public_udp_success: 5,
3597
- isSubnetReachable: true,
3598
- selectedCluster: null,
3709
+ subnet_reachable: true,
3710
+ selected_cluster: 'some.cluster',
3711
+ selected_subnet: '1.X.X.X',
3599
3712
  iceCandidatesCount: 0,
3600
3713
  }
3601
3714
  );
@@ -4262,11 +4375,9 @@ describe('plugin-meetings', () => {
4262
4375
 
4263
4376
  const error = new Error();
4264
4377
  meeting.meetingRequest.setBrb = sinon.stub().rejects(error);
4265
-
4266
- await expect(
4267
- meeting.beRightBack(true)
4268
- ).to.be.rejectedWith(error);
4269
-
4378
+
4379
+ await expect(meeting.beRightBack(true)).to.be.rejectedWith(error);
4380
+
4270
4381
  assert.isFalse(meeting.brbState.state.syncToServerInProgress);
4271
4382
  });
4272
4383
  });
@@ -8010,6 +8121,7 @@ describe('plugin-meetings', () => {
8010
8121
 
8011
8122
  meeting.requestScreenShareFloor = sinon.stub().resolves({});
8012
8123
  meeting.releaseScreenShareFloor = sinon.stub().resolves({});
8124
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
8013
8125
  meeting.mediaProperties.mediaDirection = {
8014
8126
  sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
8015
8127
  sendVideo: 'fake value',
@@ -8091,6 +8203,12 @@ describe('plugin-meetings', () => {
8091
8203
  payload: {mediaType: 'share', shareInstanceId: meeting.localShareInstanceId},
8092
8204
  options: {meetingId: meeting.id},
8093
8205
  });
8206
+
8207
+ // ensure the share start timestamp is saved
8208
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8209
+ key: 'internal.client.share.initiated',
8210
+ });
8211
+
8094
8212
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
8095
8213
 
8096
8214
  assert.equal(meeting.shareCAEventSentStatus.transmitStart, false);
@@ -8109,6 +8227,11 @@ describe('plugin-meetings', () => {
8109
8227
  options: {meetingId: meeting.id},
8110
8228
  });
8111
8229
 
8230
+ // ensure the share start timestamp is saved
8231
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
8232
+ key: 'internal.client.share.initiated',
8233
+ });
8234
+
8112
8235
  assert.calledWith(
8113
8236
  meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
8114
8237
  stream
@@ -10382,6 +10505,8 @@ describe('plugin-meetings', () => {
10382
10505
  meeting.mediaProperties = {mediaDirection: {sendShare: true}};
10383
10506
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
10384
10507
  (meeting.deviceUrl = 'deviceUrl.com'), (meeting.localShareInstanceId = '1234-5678');
10508
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
10509
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
10385
10510
  });
10386
10511
  it('should call changeMeetingFloor()', async () => {
10387
10512
  meeting.screenShareFloorState = 'GRANTED';
@@ -10399,6 +10524,22 @@ describe('plugin-meetings', () => {
10399
10524
  assert.exists(share.then);
10400
10525
  await share;
10401
10526
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
10527
+
10528
+ // ensure the share stop timestamp is saved
10529
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
10530
+ key: 'internal.client.share.stopped',
10531
+ });
10532
+
10533
+ // ensure the CA share stopped metric is submitted with duration
10534
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
10535
+ name: 'client.share.stopped',
10536
+ payload: {
10537
+ mediaType: 'share',
10538
+ shareInstanceId: meeting.localShareInstanceId,
10539
+ shareDuration: 1000,
10540
+ },
10541
+ options: {meetingId: meeting.id},
10542
+ });
10402
10543
  });
10403
10544
  it('should not call changeMeetingFloor() if someone else already has the floor', async () => {
10404
10545
  // change selfId so that it doesn't match the beneficiary id from meeting.locusInfo.mediaShares
@@ -11971,6 +12112,7 @@ describe('plugin-meetings', () => {
11971
12112
  meeting.locusInfo.self = {url: url1};
11972
12113
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
11973
12114
  meeting.deviceUrl = 'deviceUrl.com';
12115
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
11974
12116
  });
11975
12117
  it('should have #startWhiteboardShare', () => {
11976
12118
  assert.exists(meeting.startWhiteboardShare);
@@ -11998,6 +12140,11 @@ describe('plugin-meetings', () => {
11998
12140
  payload: {mediaType: 'whiteboard'},
11999
12141
  options: {meetingId: meeting.id},
12000
12142
  });
12143
+
12144
+ // ensure the share start timestamp is saved
12145
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12146
+ key: 'internal.client.share.initiated',
12147
+ });
12001
12148
  });
12002
12149
  });
12003
12150
  describe('#stopWhiteboardShare', () => {
@@ -12009,6 +12156,8 @@ describe('plugin-meetings', () => {
12009
12156
  meeting.locusInfo.self = {url: url1};
12010
12157
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
12011
12158
  meeting.deviceUrl = 'deviceUrl.com';
12159
+ webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp = sinon.stub();
12160
+ webex.internal.newMetrics.callDiagnosticLatencies.getShareDuration = sinon.stub().returns(1000);
12012
12161
  });
12013
12162
  it('should stop the whiteboard share', async () => {
12014
12163
  const whiteboardShare = meeting.stopWhiteboardShare();
@@ -12023,6 +12172,21 @@ describe('plugin-meetings', () => {
12023
12172
  uri: url1,
12024
12173
  });
12025
12174
  assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
12175
+
12176
+ // ensure the share stop timestamp is saved
12177
+ assert.calledWith(webex.internal.newMetrics.callDiagnosticLatencies.saveTimestamp, {
12178
+ key: 'internal.client.share.stopped',
12179
+ });
12180
+
12181
+ // ensure the CA share stopped metric is submitted with duration
12182
+ assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
12183
+ name: 'client.share.stopped',
12184
+ payload: {
12185
+ mediaType: 'whiteboard',
12186
+ shareDuration: 1000,
12187
+ },
12188
+ options: {meetingId: meeting.id},
12189
+ });
12026
12190
  });
12027
12191
  });
12028
12192
  });
@@ -12321,7 +12485,7 @@ describe('plugin-meetings', () => {
12321
12485
  activeSharingId.whiteboard = beneficiaryId;
12322
12486
 
12323
12487
  eventTrigger.share.push(
12324
- meeting.webinar.selfIsAttendee
12488
+ meeting.webinar.selfIsAttendee || meeting.guest
12325
12489
  ? {
12326
12490
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
12327
12491
  functionName: 'remoteShare',
@@ -12340,7 +12504,8 @@ describe('plugin-meetings', () => {
12340
12504
  }
12341
12505
  );
12342
12506
 
12343
- shareStatus = meeting.webinar.selfIsAttendee
12507
+ shareStatus =
12508
+ meeting.webinar.selfIsAttendee || meeting.guest
12344
12509
  ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
12345
12510
  : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
12346
12511
  }
@@ -12558,6 +12723,36 @@ describe('plugin-meetings', () => {
12558
12723
  });
12559
12724
  });
12560
12725
 
12726
+ describe('Whiteboard Share - User is guest', () => {
12727
+ it('User receives a remote share instead of whiteboard share', () => {
12728
+ // Set the guest flag
12729
+ meeting.guest = true;
12730
+
12731
+ // Step 1: Start sharing whiteboard A
12732
+ const data1 = generateData(
12733
+ blankPayload, // Initial payload
12734
+ true, // isGranting: Granting share
12735
+ false, // isContent: Whiteboard (not content)
12736
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
12737
+ RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
12738
+ );
12739
+
12740
+ // Step 2: Stop sharing whiteboard A
12741
+ const data2 = generateData(
12742
+ data1.payload, // Updated payload from Step 1
12743
+ false, // isGranting: Stopping share
12744
+ false, // isContent: Whiteboard
12745
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
12746
+ );
12747
+
12748
+ // Validate the payload changes and status updates
12749
+ payloadTestHelper([data1]);
12750
+
12751
+ // Specific assertions for guest
12752
+ assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
12753
+ });
12754
+ });
12755
+
12561
12756
  describe('Whiteboard A --> Whiteboard B', () => {
12562
12757
  it('Scenario #1: you share both whiteboards', () => {
12563
12758
  const data1 = generateData(
@@ -14002,4 +14197,443 @@ describe('plugin-meetings', () => {
14002
14197
  assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
14003
14198
  });
14004
14199
  });
14200
+
14201
+ describe('#setStage', () => {
14202
+ const check = async (options, expectedVideoLayout) => {
14203
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14204
+ meeting.locusUrl = locusUrl;
14205
+
14206
+ const setStagePromise = meeting.setStage(options);
14207
+
14208
+ assert.exists(setStagePromise.then);
14209
+ await setStagePromise;
14210
+
14211
+ assert.calledOnceWithExactly(
14212
+ meeting.meetingRequest.synchronizeStage,
14213
+ locusUrl,
14214
+ expectedVideoLayout
14215
+ );
14216
+ };
14217
+
14218
+ beforeEach(() => {
14219
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14220
+ });
14221
+
14222
+ it('sends the expected request when no options are provided', async () => {
14223
+ await check(undefined, {
14224
+ overrideDefault: true,
14225
+ lockAttendeeViewOnStageOnly: false,
14226
+ stageParameters: {
14227
+ activeSpeakerProportion: 0.5,
14228
+ showActiveSpeaker: {show: false, order: 0},
14229
+ stageManagerType: 0,
14230
+ },
14231
+ });
14232
+ });
14233
+
14234
+ it('sends the expected request when empty options are provided', async () => {
14235
+ await check(
14236
+ {},
14237
+ {
14238
+ overrideDefault: true,
14239
+ lockAttendeeViewOnStageOnly: false,
14240
+ stageParameters: {
14241
+ activeSpeakerProportion: 0.5,
14242
+ showActiveSpeaker: {show: false, order: 0},
14243
+ stageManagerType: 0,
14244
+ },
14245
+ }
14246
+ );
14247
+ });
14248
+
14249
+ [0.25, 0.5, 0.75].forEach((activeSpeakerProportion) => {
14250
+ it(`sends the expected request when only the active speaker proportion option is provided as ${activeSpeakerProportion}`, async () => {
14251
+ await check(
14252
+ {activeSpeakerProportion},
14253
+ {
14254
+ overrideDefault: true,
14255
+ lockAttendeeViewOnStageOnly: false,
14256
+ stageParameters: {
14257
+ activeSpeakerProportion,
14258
+ showActiveSpeaker: {show: false, order: 0},
14259
+ stageManagerType: 0,
14260
+ },
14261
+ }
14262
+ );
14263
+ });
14264
+ });
14265
+
14266
+ it('sends the expected request when only the custom background option is provided', async () => {
14267
+ const customBackground = {
14268
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14269
+ };
14270
+
14271
+ await check(
14272
+ {customBackground},
14273
+ {
14274
+ overrideDefault: true,
14275
+ lockAttendeeViewOnStageOnly: false,
14276
+ stageParameters: {
14277
+ activeSpeakerProportion: 0.5,
14278
+ showActiveSpeaker: {show: false, order: 0},
14279
+ stageManagerType: 2,
14280
+ },
14281
+ customLayouts: {background: customBackground},
14282
+ }
14283
+ );
14284
+ });
14285
+
14286
+ it('sends the expected request when only the custom logo option is provided', async () => {
14287
+ const customLogo = {
14288
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14289
+ position: 'LowerRight',
14290
+ };
14291
+
14292
+ await check(
14293
+ {customLogo},
14294
+ {
14295
+ overrideDefault: true,
14296
+ lockAttendeeViewOnStageOnly: false,
14297
+ stageParameters: {
14298
+ activeSpeakerProportion: 0.5,
14299
+ showActiveSpeaker: {show: false, order: 0},
14300
+ stageManagerType: 1,
14301
+ },
14302
+ customLayouts: {logo: customLogo},
14303
+ }
14304
+ );
14305
+ });
14306
+
14307
+ it('sends the expected request when only the custom name label option is provided', async () => {
14308
+ const customNameLabel = {
14309
+ accentColor: '#0A7806',
14310
+ background: {color: 'rgba(255, 255, 255, 1)'},
14311
+ border: {color: 'rgba(255, 255, 255, 1)'},
14312
+ content: {
14313
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14314
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14315
+ },
14316
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14317
+ fadeOut: {delay: 15},
14318
+ type: 'Primary',
14319
+ };
14320
+
14321
+ await check(
14322
+ {customNameLabel},
14323
+ {
14324
+ overrideDefault: true,
14325
+ lockAttendeeViewOnStageOnly: false,
14326
+ stageParameters: {
14327
+ activeSpeakerProportion: 0.5,
14328
+ showActiveSpeaker: {show: false, order: 0},
14329
+ stageManagerType: 4,
14330
+ },
14331
+ nameLabelStyle: customNameLabel,
14332
+ }
14333
+ );
14334
+ });
14335
+
14336
+ it('sends the expected request when only the custom background and logo options are provided', async () => {
14337
+ const customBackground = {
14338
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14339
+ };
14340
+ const customLogo = {
14341
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14342
+ position: 'UpperRight',
14343
+ };
14344
+
14345
+ await check(
14346
+ {customBackground, customLogo},
14347
+ {
14348
+ overrideDefault: true,
14349
+ lockAttendeeViewOnStageOnly: false,
14350
+ stageParameters: {
14351
+ activeSpeakerProportion: 0.5,
14352
+ showActiveSpeaker: {show: false, order: 0},
14353
+ stageManagerType: 3,
14354
+ },
14355
+ customLayouts: {background: customBackground, logo: customLogo},
14356
+ }
14357
+ );
14358
+ });
14359
+
14360
+ it('sends the expected request when only the custom background and name label options are provided', async () => {
14361
+ const customBackground = {
14362
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14363
+ };
14364
+ const customNameLabel = {
14365
+ accentColor: '#00A3FF',
14366
+ background: {color: 'rgba(0, 163, 255, 1)'},
14367
+ border: {color: 'rgba(0, 163, 255, 1)'},
14368
+ content: {
14369
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14370
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14371
+ },
14372
+ decoration: {color: 'rgba(255, 255, 255, 0.95)'},
14373
+ fadeOut: {delay: 15},
14374
+ type: 'PrimaryInverted',
14375
+ };
14376
+
14377
+ await check(
14378
+ {customBackground, customNameLabel},
14379
+ {
14380
+ overrideDefault: true,
14381
+ lockAttendeeViewOnStageOnly: false,
14382
+ stageParameters: {
14383
+ activeSpeakerProportion: 0.5,
14384
+ showActiveSpeaker: {show: false, order: 0},
14385
+ stageManagerType: 6,
14386
+ },
14387
+ customLayouts: {background: customBackground},
14388
+ nameLabelStyle: customNameLabel,
14389
+ }
14390
+ );
14391
+ });
14392
+
14393
+ it('sends the expected request when only the custom logo and name label options are provided', async () => {
14394
+ const customLogo = {
14395
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14396
+ position: 'UpperLeft',
14397
+ };
14398
+ const customNameLabel = {
14399
+ accentColor: '#942B2B',
14400
+ background: {color: 'rgba(255, 255, 255, 1)'},
14401
+ border: {color: 'rgba(148, 43, 43, 1)'},
14402
+ content: {
14403
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14404
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14405
+ },
14406
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14407
+ fadeOut: {delay: 15},
14408
+ type: 'Secondary',
14409
+ };
14410
+
14411
+ await check(
14412
+ {customLogo, customNameLabel},
14413
+ {
14414
+ overrideDefault: true,
14415
+ lockAttendeeViewOnStageOnly: false,
14416
+ stageParameters: {
14417
+ activeSpeakerProportion: 0.5,
14418
+ showActiveSpeaker: {show: false, order: 0},
14419
+ stageManagerType: 5,
14420
+ },
14421
+ customLayouts: {logo: customLogo},
14422
+ nameLabelStyle: customNameLabel,
14423
+ }
14424
+ );
14425
+ });
14426
+
14427
+ it('sends the expected request when only the custom background, logo, name label options are provided', async () => {
14428
+ const customBackground = {
14429
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14430
+ };
14431
+ const customLogo = {
14432
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14433
+ position: 'LowerLeft',
14434
+ };
14435
+ const customNameLabel = {
14436
+ accentColor: '#EBD960',
14437
+ background: {color: 'rgba(235, 217, 96, 0.55)'},
14438
+ border: {color: 'rgba(235, 217, 96, 0.55)'},
14439
+ content: {
14440
+ displayName: {color: 'rgba(255, 255, 255, 0.95)'},
14441
+ subtitle: {color: 'rgba(255, 255, 255, 0.7)'},
14442
+ },
14443
+ decoration: {color: 'rgba(0, 0, 0, 0)'},
14444
+ fadeOut: {delay: 15},
14445
+ type: 'SecondaryInverted',
14446
+ };
14447
+
14448
+ await check(
14449
+ {customBackground, customLogo, customNameLabel},
14450
+ {
14451
+ overrideDefault: true,
14452
+ lockAttendeeViewOnStageOnly: false,
14453
+ stageParameters: {
14454
+ activeSpeakerProportion: 0.5,
14455
+ showActiveSpeaker: {show: false, order: 0},
14456
+ stageManagerType: 7,
14457
+ },
14458
+ customLayouts: {background: customBackground, logo: customLogo},
14459
+ nameLabelStyle: customNameLabel,
14460
+ }
14461
+ );
14462
+ });
14463
+
14464
+ it('sends the expected request when only the important participants option is provided as empty', async () => {
14465
+ await check(
14466
+ {importantParticipants: []},
14467
+ {
14468
+ overrideDefault: true,
14469
+ lockAttendeeViewOnStageOnly: false,
14470
+ stageParameters: {
14471
+ activeSpeakerProportion: 0.5,
14472
+ showActiveSpeaker: {show: false, order: 0},
14473
+ stageManagerType: 0,
14474
+ },
14475
+ }
14476
+ );
14477
+ });
14478
+
14479
+ it('sends the expected request when only the important participants option is provided as populated', async () => {
14480
+ const importantParticipants = [
14481
+ {mainCsi: 11111111, participantId: uuidv4()},
14482
+ {mainCsi: 22222222, participantId: uuidv4()},
14483
+ {mainCsi: 33333333, participantId: uuidv4()},
14484
+ {mainCsi: 44444444, participantId: uuidv4()},
14485
+ {mainCsi: 55555555, participantId: uuidv4()},
14486
+ {mainCsi: 66666666, participantId: uuidv4()},
14487
+ {mainCsi: 77777777, participantId: uuidv4()},
14488
+ {mainCsi: 88888888, participantId: uuidv4()},
14489
+ ];
14490
+
14491
+ await check(
14492
+ {importantParticipants},
14493
+ {
14494
+ overrideDefault: true,
14495
+ lockAttendeeViewOnStageOnly: false,
14496
+ stageParameters: {
14497
+ activeSpeakerProportion: 0.5,
14498
+ importantParticipants: [
14499
+ {...importantParticipants[0], order: 1},
14500
+ {...importantParticipants[1], order: 2},
14501
+ {...importantParticipants[2], order: 3},
14502
+ {...importantParticipants[3], order: 4},
14503
+ {...importantParticipants[4], order: 5},
14504
+ {...importantParticipants[5], order: 6},
14505
+ {...importantParticipants[6], order: 7},
14506
+ {...importantParticipants[7], order: 8},
14507
+ ],
14508
+ showActiveSpeaker: {show: false, order: 0},
14509
+ stageManagerType: 0,
14510
+ },
14511
+ }
14512
+ );
14513
+ });
14514
+
14515
+ [false, true].forEach((lockAttendeeViewOnStage) => {
14516
+ it(`sends the expected request when only the lock attendee view on stage option is provided as ${lockAttendeeViewOnStage}`, async () => {
14517
+ await check(
14518
+ {lockAttendeeViewOnStage},
14519
+ {
14520
+ overrideDefault: true,
14521
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14522
+ stageParameters: {
14523
+ activeSpeakerProportion: 0.5,
14524
+ showActiveSpeaker: {show: false, order: 0},
14525
+ stageManagerType: 0,
14526
+ },
14527
+ }
14528
+ );
14529
+ });
14530
+ });
14531
+
14532
+ [false, true].forEach((showActiveSpeaker) => {
14533
+ it(`sends the expected request when only the show active speaker option is provided as ${showActiveSpeaker}`, async () => {
14534
+ await check(
14535
+ {showActiveSpeaker},
14536
+ {
14537
+ overrideDefault: true,
14538
+ lockAttendeeViewOnStageOnly: false,
14539
+ stageParameters: {
14540
+ activeSpeakerProportion: 0.5,
14541
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14542
+ stageManagerType: 0,
14543
+ },
14544
+ }
14545
+ );
14546
+ });
14547
+ });
14548
+
14549
+ it('sends the expected request when all options are provided', async () => {
14550
+ const activeSpeakerProportion = 0.6;
14551
+ const customBackground = {
14552
+ url: `https://test.wbx2.com/background/${uuidv4()}.jpg`,
14553
+ };
14554
+ const customLogo = {
14555
+ url: `https://test.wbx2.com/logo/${uuidv4()}.png`,
14556
+ position: 'UpperMiddle',
14557
+ };
14558
+ const customNameLabel = {
14559
+ accentColor: '#0A7806',
14560
+ background: {color: 'rgba(255, 255, 255, 1)'},
14561
+ border: {color: 'rgba(255, 255, 255, 1)'},
14562
+ content: {
14563
+ displayName: {color: 'rgba(0, 0, 0, 0.95)'},
14564
+ subtitle: {color: 'rgba(0, 0, 0, 0.6)'},
14565
+ },
14566
+ decoration: {color: 'rgba(10, 120, 6, 1)'},
14567
+ fadeOut: {delay: 15},
14568
+ type: 'Primary',
14569
+ };
14570
+ const importantParticipants = [
14571
+ {mainCsi: 11111111, participantId: uuidv4()},
14572
+ {mainCsi: 22222222, participantId: uuidv4()},
14573
+ {mainCsi: 33333333, participantId: uuidv4()},
14574
+ {mainCsi: 44444444, participantId: uuidv4()},
14575
+ {mainCsi: 55555555, participantId: uuidv4()},
14576
+ {mainCsi: 66666666, participantId: uuidv4()},
14577
+ {mainCsi: 77777777, participantId: uuidv4()},
14578
+ {mainCsi: 88888888, participantId: uuidv4()},
14579
+ ];
14580
+ const lockAttendeeViewOnStage = true;
14581
+ const showActiveSpeaker = true;
14582
+
14583
+ await check(
14584
+ {
14585
+ activeSpeakerProportion,
14586
+ customBackground,
14587
+ customLogo,
14588
+ customNameLabel,
14589
+ importantParticipants,
14590
+ lockAttendeeViewOnStage,
14591
+ showActiveSpeaker,
14592
+ },
14593
+ {
14594
+ overrideDefault: true,
14595
+ lockAttendeeViewOnStageOnly: lockAttendeeViewOnStage,
14596
+ stageParameters: {
14597
+ activeSpeakerProportion,
14598
+ importantParticipants: [
14599
+ {...importantParticipants[0], order: 1},
14600
+ {...importantParticipants[1], order: 2},
14601
+ {...importantParticipants[2], order: 3},
14602
+ {...importantParticipants[3], order: 4},
14603
+ {...importantParticipants[4], order: 5},
14604
+ {...importantParticipants[5], order: 6},
14605
+ {...importantParticipants[6], order: 7},
14606
+ {...importantParticipants[7], order: 8},
14607
+ ],
14608
+ showActiveSpeaker: {show: showActiveSpeaker, order: 0},
14609
+ stageManagerType: 7,
14610
+ },
14611
+ customLayouts: {background: customBackground, logo: customLogo},
14612
+ nameLabelStyle: customNameLabel,
14613
+ }
14614
+ );
14615
+ });
14616
+ });
14617
+
14618
+ describe('#unsetStage', () => {
14619
+ beforeEach(() => {
14620
+ meeting.meetingRequest.synchronizeStage = sinon.stub().returns(Promise.resolve());
14621
+ });
14622
+
14623
+ it('sends the expected request', async () => {
14624
+ const locusUrl = `https://locus-test.wbx2.com/locus/api/v1/loci/${uuidv4()}`;
14625
+ meeting.locusUrl = locusUrl;
14626
+
14627
+ const unsetStagePromise = meeting.unsetStage();
14628
+
14629
+ assert.exists(unsetStagePromise.then);
14630
+ await unsetStagePromise;
14631
+
14632
+ assert.calledOnceWithExactly(
14633
+ meeting.meetingRequest.synchronizeStage,
14634
+ locusUrl,
14635
+ {overrideDefault: false}
14636
+ );
14637
+ });
14638
+ });
14005
14639
  });