@webex/plugin-meetings 3.7.0-next.3 → 3.7.0-next.31

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 (114) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  4. package/dist/common/errors/join-webinar-error.js.map +1 -0
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +31 -6
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.js +8 -15
  10. package/dist/index.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/index.js +13 -2
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/locus-info/selfUtils.js +30 -17
  16. package/dist/locus-info/selfUtils.js.map +1 -1
  17. package/dist/meeting/in-meeting-actions.js +11 -1
  18. package/dist/meeting/in-meeting-actions.js.map +1 -1
  19. package/dist/meeting/index.js +810 -779
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/request.js +30 -0
  22. package/dist/meeting/request.js.map +1 -1
  23. package/dist/meeting/request.type.js.map +1 -1
  24. package/dist/meeting/util.js +3 -8
  25. package/dist/meeting/util.js.map +1 -1
  26. package/dist/meeting-info/meeting-info-v2.js +29 -17
  27. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  28. package/dist/meetings/index.js +6 -3
  29. package/dist/meetings/index.js.map +1 -1
  30. package/dist/member/index.js +9 -0
  31. package/dist/member/index.js.map +1 -1
  32. package/dist/member/types.js.map +1 -1
  33. package/dist/member/util.js +39 -28
  34. package/dist/member/util.js.map +1 -1
  35. package/dist/members/util.js +4 -2
  36. package/dist/members/util.js.map +1 -1
  37. package/dist/metrics/constants.js +1 -1
  38. package/dist/metrics/constants.js.map +1 -1
  39. package/dist/multistream/remoteMedia.js +30 -15
  40. package/dist/multistream/remoteMedia.js.map +1 -1
  41. package/dist/multistream/sendSlotManager.js +24 -0
  42. package/dist/multistream/sendSlotManager.js.map +1 -1
  43. package/dist/reachability/clusterReachability.js +12 -11
  44. package/dist/reachability/clusterReachability.js.map +1 -1
  45. package/dist/recording-controller/enums.js +8 -4
  46. package/dist/recording-controller/enums.js.map +1 -1
  47. package/dist/recording-controller/index.js +18 -9
  48. package/dist/recording-controller/index.js.map +1 -1
  49. package/dist/recording-controller/util.js +13 -9
  50. package/dist/recording-controller/util.js.map +1 -1
  51. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  52. package/dist/types/constants.d.ts +23 -1
  53. package/dist/types/index.d.ts +3 -3
  54. package/dist/types/locus-info/index.d.ts +2 -1
  55. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  56. package/dist/types/meeting/index.d.ts +9 -10
  57. package/dist/types/meeting/request.d.ts +12 -1
  58. package/dist/types/meeting/request.type.d.ts +6 -0
  59. package/dist/types/meeting/util.d.ts +1 -1
  60. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  61. package/dist/types/meetings/index.d.ts +3 -0
  62. package/dist/types/member/index.d.ts +1 -0
  63. package/dist/types/member/types.d.ts +7 -0
  64. package/dist/types/members/util.d.ts +2 -0
  65. package/dist/types/metrics/constants.d.ts +1 -1
  66. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  67. package/dist/types/recording-controller/enums.d.ts +5 -2
  68. package/dist/types/recording-controller/index.d.ts +1 -0
  69. package/dist/types/recording-controller/util.d.ts +2 -1
  70. package/dist/webinar/index.js +390 -7
  71. package/dist/webinar/index.js.map +1 -1
  72. package/package.json +23 -22
  73. package/src/common/errors/join-webinar-error.ts +24 -0
  74. package/src/config.ts +1 -1
  75. package/src/constants.ts +28 -3
  76. package/src/index.ts +2 -3
  77. package/src/locus-info/index.ts +17 -2
  78. package/src/locus-info/selfUtils.ts +19 -6
  79. package/src/meeting/in-meeting-actions.ts +21 -0
  80. package/src/meeting/index.ts +147 -54
  81. package/src/meeting/request.ts +26 -1
  82. package/src/meeting/request.type.ts +7 -0
  83. package/src/meeting/util.ts +3 -9
  84. package/src/meeting-info/meeting-info-v2.ts +23 -11
  85. package/src/meetings/index.ts +8 -2
  86. package/src/member/index.ts +9 -0
  87. package/src/member/types.ts +8 -0
  88. package/src/member/util.ts +34 -24
  89. package/src/members/util.ts +1 -0
  90. package/src/metrics/constants.ts +1 -1
  91. package/src/multistream/remoteMedia.ts +28 -15
  92. package/src/multistream/sendSlotManager.ts +31 -0
  93. package/src/reachability/clusterReachability.ts +4 -1
  94. package/src/recording-controller/enums.ts +5 -2
  95. package/src/recording-controller/index.ts +17 -4
  96. package/src/recording-controller/util.ts +20 -5
  97. package/src/webinar/index.ts +235 -9
  98. package/test/unit/spec/locus-info/index.js +222 -0
  99. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  100. package/test/unit/spec/locus-info/selfUtils.js +91 -1
  101. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
  102. package/test/unit/spec/meeting/index.js +318 -81
  103. package/test/unit/spec/meeting/utils.js +11 -19
  104. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  105. package/test/unit/spec/meetings/index.js +9 -5
  106. package/test/unit/spec/member/util.js +52 -11
  107. package/test/unit/spec/members/utils.js +95 -0
  108. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  109. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  110. package/test/unit/spec/recording-controller/index.js +61 -5
  111. package/test/unit/spec/recording-controller/util.js +39 -3
  112. package/test/unit/spec/webinar/index.ts +504 -0
  113. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  114. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -91,14 +91,14 @@ import ParameterError from '../../../../src/common/errors/parameter';
91
91
  import PasswordError from '../../../../src/common/errors/password-error';
92
92
  import CaptchaError from '../../../../src/common/errors/captcha-error';
93
93
  import PermissionError from '../../../../src/common/errors/permission';
94
- import WebinarRegistrationError from '../../../../src/common/errors/webinar-registration-error';
94
+ import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
95
95
  import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
96
96
  import testUtils from '../../../utils/testUtils';
97
97
  import {
98
98
  MeetingInfoV2CaptchaError,
99
99
  MeetingInfoV2PasswordError,
100
100
  MeetingInfoV2PolicyError,
101
- MeetingInfoV2WebinarRegistrationError,
101
+ MeetingInfoV2JoinWebinarError,
102
102
  } from '../../../../src/meeting-info/meeting-info-v2';
103
103
  import {
104
104
  DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
@@ -1705,6 +1705,12 @@ describe('plugin-meetings', () => {
1705
1705
  sinon.assert.called(setCorrelationIdSpy);
1706
1706
  assert.equal(meeting.correlationId, '123');
1707
1707
  });
1708
+
1709
+ it('should not send client.call.initiated if told not to', async () => {
1710
+ await meeting.join({sendCallInitiated: false});
1711
+
1712
+ sinon.assert.notCalled(webex.internal.newMetrics.submitClientEvent);
1713
+ });
1708
1714
  });
1709
1715
 
1710
1716
  describe('failure', () => {
@@ -2492,9 +2498,11 @@ describe('plugin-meetings', () => {
2492
2498
  mediaSettings: {},
2493
2499
  });
2494
2500
 
2495
- const checkLogCounter = (delay, expectedCounter) => {
2501
+ const checkLogCounter = (delayInMinutes, expectedCounter) => {
2502
+ const delayInMilliseconds = delayInMinutes * 60 * 1000;
2503
+
2496
2504
  // first check that the counter is not increased just before the delay
2497
- clock.tick(delay - 50);
2505
+ clock.tick(delayInMilliseconds - 50);
2498
2506
  assert.equal(logUploadCounter, expectedCounter - 1);
2499
2507
 
2500
2508
  // and now check that it has reached expected value after the delay
@@ -2502,22 +2510,18 @@ describe('plugin-meetings', () => {
2502
2510
  assert.equal(logUploadCounter, expectedCounter);
2503
2511
  };
2504
2512
 
2505
- checkLogCounter(100, 1);
2506
- checkLogCounter(1000, 2);
2507
- checkLogCounter(15000, 3);
2508
- checkLogCounter(15000, 4);
2509
- checkLogCounter(30000, 5);
2510
- checkLogCounter(30000, 6);
2511
- checkLogCounter(30000, 7);
2512
- checkLogCounter(60000, 8);
2513
- checkLogCounter(60000, 9);
2514
- checkLogCounter(60000, 10);
2513
+ checkLogCounter(0.1, 1);
2514
+ checkLogCounter(15, 2);
2515
+ checkLogCounter(30, 3);
2516
+ checkLogCounter(60, 4);
2517
+ checkLogCounter(60, 5);
2515
2518
 
2516
- // simulate media connection being removed -> no more log uploads should happen
2519
+ // simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
2517
2520
  meeting.mediaProperties.webrtcMediaConnection = undefined;
2521
+ checkLogCounter(60, 6);
2518
2522
 
2519
- clock.tick(60000);
2520
- assert.equal(logUploadCounter, 11);
2523
+ clock.tick(120 * 1000 * 60);
2524
+ assert.equal(logUploadCounter, 6);
2521
2525
 
2522
2526
  clock.restore();
2523
2527
  });
@@ -3475,6 +3479,51 @@ describe('plugin-meetings', () => {
3475
3479
  });
3476
3480
  });
3477
3481
 
3482
+ it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
3483
+ let fakeMembersCollection = {
3484
+ members: {
3485
+ member1: { isInMeeting: true },
3486
+ member2: { isInMeeting: true },
3487
+ member3: { isInMeeting: false },
3488
+ },
3489
+ };
3490
+ sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
3491
+ const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
3492
+
3493
+ statsAnalyzerStub.emit(
3494
+ { file: 'test', function: 'test' },
3495
+ StatsAnalyzerEventNames.MEDIA_QUALITY,
3496
+ { data: fakeData }
3497
+ );
3498
+
3499
+ assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
3500
+ name: 'client.mediaquality.event',
3501
+ options: {
3502
+ meetingId: meeting.id,
3503
+ },
3504
+ payload: {
3505
+ intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
3506
+ },
3507
+ });
3508
+ fakeMembersCollection.members.member2.isInMeeting = false;
3509
+
3510
+ statsAnalyzerStub.emit(
3511
+ { file: 'test', function: 'test' },
3512
+ StatsAnalyzerEventNames.MEDIA_QUALITY,
3513
+ { data: fakeData }
3514
+ );
3515
+
3516
+ assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
3517
+ name: 'client.mediaquality.event',
3518
+ options: {
3519
+ meetingId: meeting.id,
3520
+ },
3521
+ payload: {
3522
+ intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
3523
+ },
3524
+ });
3525
+ });
3526
+
3478
3527
  it('calls submitMQE correctly', async () => {
3479
3528
  const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
3480
3529
 
@@ -3552,14 +3601,6 @@ describe('plugin-meetings', () => {
3552
3601
  });
3553
3602
  });
3554
3603
 
3555
- it('succeeds even if getDevices() throws', async () => {
3556
- meeting.meetingState = 'ACTIVE';
3557
-
3558
- sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
3559
-
3560
- await meeting.addMedia();
3561
- });
3562
-
3563
3604
  describe('CA ice failures checks', () => {
3564
3605
  [
3565
3606
  {
@@ -3701,6 +3742,93 @@ describe('plugin-meetings', () => {
3701
3742
  });
3702
3743
  });
3703
3744
 
3745
+ describe(`#beRightBack`, () => {
3746
+ const fakeMultistreamRoapMediaConnection = {
3747
+ createSendSlot: sinon.stub().returns({
3748
+ setSourceStateOverride: sinon.stub().resolves(),
3749
+ clearSourceStateOverride: sinon.stub().resolves(),
3750
+ }),
3751
+ };
3752
+
3753
+ beforeEach(() => {
3754
+ meeting.meetingRequest.setBrb = sinon.stub().resolves({body: 'test'});
3755
+ meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
3756
+ meeting.sendSlotManager.createSlot(
3757
+ fakeMultistreamRoapMediaConnection,
3758
+ MediaType.VideoMain
3759
+ );
3760
+
3761
+ meeting.locusUrl = 'locus url';
3762
+ meeting.deviceUrl = 'device url';
3763
+ meeting.selfId = 'self id';
3764
+ });
3765
+
3766
+ afterEach(() => {
3767
+ sinon.restore();
3768
+ });
3769
+
3770
+ it('should have #beRightBack', () => {
3771
+ assert.exists(meeting.beRightBack);
3772
+ });
3773
+
3774
+ describe('when in a multistream meeting', () => {
3775
+
3776
+ beforeEach(() => {
3777
+ meeting.isMultistream = true;
3778
+ });
3779
+
3780
+ it('should enable #beRightBack and return a promise', async () => {
3781
+ const brbResult = meeting.beRightBack(true);
3782
+
3783
+ await brbResult;
3784
+ assert.exists(brbResult.then);
3785
+ assert.calledOnce(meeting.meetingRequest.setBrb);
3786
+ })
3787
+
3788
+ it('should disable #beRightBack and return a promise', async () => {
3789
+ const brbResult = meeting.beRightBack(false);
3790
+
3791
+ await brbResult;
3792
+ assert.exists(brbResult.then);
3793
+ assert.calledOnce(meeting.meetingRequest.setBrb);
3794
+ })
3795
+
3796
+ it('should throw an error and reject the promise if setBrb fails', async () => {
3797
+ const error = new Error('setBrb failed');
3798
+ meeting.meetingRequest.setBrb.rejects(error);
3799
+
3800
+ try {
3801
+ await meeting.beRightBack(true);
3802
+ } catch (err) {
3803
+ assert.instanceOf(err, Error);
3804
+ assert.equal(err.message, 'setBrb failed');
3805
+ assert.isRejected((Promise.reject()));
3806
+ }
3807
+ })
3808
+ });
3809
+
3810
+ describe('when in a transcoded meeting', () => {
3811
+
3812
+ beforeEach(() => {
3813
+ meeting.isMultistream = false;
3814
+ });
3815
+
3816
+ it('should ignore enabling #beRightBack', async () => {
3817
+ meeting.beRightBack(true);
3818
+
3819
+ assert.isRejected((Promise.reject()));
3820
+ assert.notCalled(meeting.meetingRequest.setBrb);
3821
+ })
3822
+
3823
+ it('should ignore disabling #beRightBack', async () => {
3824
+ meeting.beRightBack(false);
3825
+
3826
+ assert.isRejected((Promise.reject()));
3827
+ assert.notCalled(meeting.meetingRequest.setBrb);
3828
+ })
3829
+ });
3830
+ });
3831
+
3704
3832
  /* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
3705
3833
  They mock the @webex/internal-media-core and sending of /media http requests to Locus.
3706
3834
  Their main purpose is to test that we send the right http requests to Locus and make right calls
@@ -3743,8 +3871,12 @@ describe('plugin-meetings', () => {
3743
3871
  meeting.setMercuryListener = sinon.stub();
3744
3872
  meeting.locusInfo.onFullLocus = sinon.stub();
3745
3873
  meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
3746
- meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon.stub().resolves({id: 'fake reachability'});
3747
- meeting.webex.meetings.reachability.getClientMediaPreferences = sinon.stub().resolves({id: 'fake clientMediaPreferences'});
3874
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
3875
+ .stub()
3876
+ .resolves({id: 'fake reachability'});
3877
+ meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
3878
+ .stub()
3879
+ .resolves({id: 'fake clientMediaPreferences'});
3748
3880
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3749
3881
  turnServerInfo: {
3750
3882
  url: 'turns:turn-server-url:443?transport=tcp',
@@ -3930,8 +4062,14 @@ describe('plugin-meetings', () => {
3930
4062
  const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
3931
4063
  const {sdp, seq, tieBreaker} = roapOfferMessage;
3932
4064
 
3933
- assert.calledWith(meeting.webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, 0);
3934
- assert.calledWith(meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap);
4065
+ assert.calledWith(
4066
+ meeting.webex.meetings.reachability.getClientMediaPreferences,
4067
+ meeting.isMultistream,
4068
+ 0
4069
+ );
4070
+ assert.calledWith(
4071
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
4072
+ );
3935
4073
 
3936
4074
  assert.calledWith(locusMediaRequestStub, {
3937
4075
  method: 'PUT',
@@ -4176,7 +4314,6 @@ describe('plugin-meetings', () => {
4176
4314
  });
4177
4315
 
4178
4316
  it('addMedia() works correctly when media is enabled with streams to publish', async () => {
4179
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4180
4317
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
4181
4318
  await simulateRoapOffer();
4182
4319
  await simulateRoapOk();
@@ -4207,12 +4344,9 @@ describe('plugin-meetings', () => {
4207
4344
 
4208
4345
  // and that these were the only /media requests that were sent
4209
4346
  assert.calledTwice(locusMediaRequestStub);
4210
-
4211
- assert.calledOnce(handleDeviceLoggingSpy);
4212
4347
  });
4213
4348
 
4214
4349
  it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
4215
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4216
4350
  fakeMicrophoneStream.userMuted = true;
4217
4351
 
4218
4352
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
@@ -4244,7 +4378,6 @@ describe('plugin-meetings', () => {
4244
4378
 
4245
4379
  // and that these were the only /media requests that were sent
4246
4380
  assert.calledTwice(locusMediaRequestStub);
4247
- assert.calledOnce(handleDeviceLoggingSpy);
4248
4381
  });
4249
4382
 
4250
4383
  it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
@@ -4316,7 +4449,6 @@ describe('plugin-meetings', () => {
4316
4449
  });
4317
4450
 
4318
4451
  it('addMedia() works correctly when media is disabled with streams to publish', async () => {
4319
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4320
4452
  await meeting.addMedia({
4321
4453
  localStreams: {microphone: fakeMicrophoneStream},
4322
4454
  audioEnabled: false,
@@ -4350,20 +4482,6 @@ describe('plugin-meetings', () => {
4350
4482
 
4351
4483
  // and that these were the only /media requests that were sent
4352
4484
  assert.calledTwice(locusMediaRequestStub);
4353
- assert.calledOnce(handleDeviceLoggingSpy);
4354
- });
4355
-
4356
- it('handleDeviceLogging not called when media is disabled', async () => {
4357
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4358
- await meeting.addMedia({
4359
- localStreams: {microphone: fakeMicrophoneStream},
4360
- audioEnabled: false,
4361
- videoEnabled: false,
4362
- });
4363
- await simulateRoapOffer();
4364
- await simulateRoapOk();
4365
-
4366
- assert.notCalled(handleDeviceLoggingSpy);
4367
4485
  });
4368
4486
 
4369
4487
  it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
@@ -4399,20 +4517,6 @@ describe('plugin-meetings', () => {
4399
4517
  assert.calledTwice(locusMediaRequestStub);
4400
4518
  });
4401
4519
 
4402
- it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
4403
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4404
- await meeting.addMedia({audioEnabled: false});
4405
- //calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
4406
- assert.calledWith(handleDeviceLoggingSpy, false, true);
4407
- });
4408
-
4409
- it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4410
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4411
- await meeting.addMedia({videoEnabled: false});
4412
- //calling handleDeviceLogging audioEnabled as true videoEnabled as false
4413
- assert.calledWith(handleDeviceLoggingSpy, true, false);
4414
- });
4415
-
4416
4520
  it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4417
4521
  await meeting.addMedia({videoEnabled: false});
4418
4522
  await simulateRoapOffer();
@@ -4479,13 +4583,6 @@ describe('plugin-meetings', () => {
4479
4583
  assert.calledTwice(locusMediaRequestStub);
4480
4584
  });
4481
4585
 
4482
- it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
4483
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4484
- await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
4485
- //calling handleDeviceLogging with audioEnabled true and videoEnabled as true
4486
- assert.calledWith(handleDeviceLoggingSpy, true, true);
4487
- });
4488
-
4489
4586
  describe('publishStreams()/unpublishStreams() calls', () => {
4490
4587
  [
4491
4588
  {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
@@ -6332,29 +6429,74 @@ describe('plugin-meetings', () => {
6332
6429
  assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
6333
6430
  });
6334
6431
 
6335
- it('handles meetingInfoProvider webinar need registration error', async () => {
6432
+ it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
6336
6433
  meeting.destination = FAKE_DESTINATION;
6337
6434
  meeting.destinationType = FAKE_TYPE;
6338
6435
  meeting.attrs.meetingInfoProvider = {
6339
6436
  fetchMeetingInfo: sinon
6340
6437
  .stub()
6341
6438
  .throws(
6342
- new MeetingInfoV2WebinarRegistrationError(403021, FAKE_MEETING_INFO, 'a message')
6439
+ new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6343
6440
  ),
6344
6441
  };
6345
6442
 
6346
6443
  await assert.isRejected(
6347
6444
  meeting.fetchMeetingInfo({sendCAevents: true}),
6348
- WebinarRegistrationError
6445
+ JoinWebinarError
6349
6446
  );
6350
6447
 
6351
6448
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6352
- assert.equal(meeting.meetingInfoFailureCode, 403021);
6353
6449
  assert.equal(
6354
6450
  meeting.meetingInfoFailureReason,
6355
6451
  MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
6356
6452
  );
6357
6453
  });
6454
+
6455
+ it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
6456
+ meeting.destination = FAKE_DESTINATION;
6457
+ meeting.destinationType = FAKE_TYPE;
6458
+ meeting.attrs.meetingInfoProvider = {
6459
+ fetchMeetingInfo: sinon
6460
+ .stub()
6461
+ .throws(
6462
+ new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6463
+ ),
6464
+ };
6465
+
6466
+ await assert.isRejected(
6467
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6468
+ JoinWebinarError
6469
+ );
6470
+
6471
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6472
+ assert.equal(
6473
+ meeting.meetingInfoFailureReason,
6474
+ MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
6475
+ );
6476
+ });
6477
+
6478
+ it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
6479
+ meeting.destination = FAKE_DESTINATION;
6480
+ meeting.destinationType = FAKE_TYPE;
6481
+ meeting.attrs.meetingInfoProvider = {
6482
+ fetchMeetingInfo: sinon
6483
+ .stub()
6484
+ .throws(
6485
+ new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6486
+ ),
6487
+ };
6488
+
6489
+ await assert.isRejected(
6490
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6491
+ JoinWebinarError
6492
+ );
6493
+
6494
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6495
+ assert.equal(
6496
+ meeting.meetingInfoFailureReason,
6497
+ MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
6498
+ );
6499
+ });
6358
6500
  });
6359
6501
 
6360
6502
  describe('#refreshPermissionToken', () => {
@@ -7817,7 +7959,9 @@ describe('plugin-meetings', () => {
7817
7959
  });
7818
7960
 
7819
7961
  it('should collect ice candidates', () => {
7820
- eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: {candidate: 'candidate'}});
7962
+ eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
7963
+ candidate: {candidate: 'candidate'},
7964
+ });
7821
7965
 
7822
7966
  assert.equal(meeting.iceCandidatesCount, 1);
7823
7967
  });
@@ -8123,10 +8267,10 @@ describe('plugin-meetings', () => {
8123
8267
  meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
8124
8268
  meeting.reconnectionManager = {
8125
8269
  reconnect: sinon.stub().resolves(),
8126
- resetReconnectionTimer: () => {}
8270
+ resetReconnectionTimer: () => {},
8127
8271
  };
8128
8272
  meeting.currentMediaStatus = {
8129
- video: true
8273
+ video: true,
8130
8274
  };
8131
8275
 
8132
8276
  await mockFailedEvent();
@@ -8662,6 +8806,7 @@ describe('plugin-meetings', () => {
8662
8806
  });
8663
8807
  });
8664
8808
  });
8809
+
8665
8810
  describe('#setUpLocusInfoSelfListener', () => {
8666
8811
  it('listens to the self unadmitted guest event', (done) => {
8667
8812
  meeting.startKeepAlive = sinon.stub();
@@ -8756,6 +8901,26 @@ describe('plugin-meetings', () => {
8756
8901
  );
8757
8902
  });
8758
8903
 
8904
+ it('listens to the brb state changed event', () => {
8905
+ const assertBrb = (enabled) => {
8906
+ meeting.locusInfo.emit(
8907
+ { function: 'test', file: 'test' },
8908
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
8909
+ { brb: { enabled } },
8910
+ )
8911
+ assert.calledWithExactly(
8912
+ TriggerProxy.trigger,
8913
+ meeting,
8914
+ {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
8915
+ EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
8916
+ { payload: { brb: { enabled } } },
8917
+ );
8918
+ }
8919
+
8920
+ assertBrb(true);
8921
+ assertBrb(false);
8922
+ })
8923
+
8759
8924
  it('listens to the interpretation changed event', () => {
8760
8925
  meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
8761
8926
 
@@ -9044,6 +9209,8 @@ describe('plugin-meetings', () => {
9044
9209
  });
9045
9210
 
9046
9211
  it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
9212
+ meeting.webinar.updatePracticeSessionStatus = sinon.stub();
9213
+
9047
9214
  const state = {example: 'value'};
9048
9215
 
9049
9216
  await meeting.locusInfo.emitScoped(
@@ -9052,6 +9219,7 @@ describe('plugin-meetings', () => {
9052
9219
  {state}
9053
9220
  );
9054
9221
 
9222
+ assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
9055
9223
  assert.calledWith(
9056
9224
  TriggerProxy.trigger,
9057
9225
  meeting,
@@ -10671,6 +10839,7 @@ describe('plugin-meetings', () => {
10671
10839
  meeting.webex.internal.llm.on = sinon.stub();
10672
10840
  meeting.webex.internal.llm.off = sinon.stub();
10673
10841
  meeting.processRelayEvent = sinon.stub();
10842
+ meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
10674
10843
  });
10675
10844
 
10676
10845
  it('does not connect if the call is not joined yet', async () => {
@@ -10802,6 +10971,19 @@ describe('plugin-meetings', () => {
10802
10971
  meeting.processRelayEvent
10803
10972
  );
10804
10973
  });
10974
+
10975
+
10976
+ it('connect ps data channel if ps started in webinar', async () => {
10977
+ meeting.joinedWith = {state: 'JOINED'};
10978
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
10979
+ meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
10980
+ await meeting.updateLLMConnection();
10981
+
10982
+ assert.notCalled(webex.internal.llm.disconnectLLM);
10983
+ assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
10984
+
10985
+ });
10986
+
10805
10987
  });
10806
10988
 
10807
10989
  describe('#setLocus', () => {
@@ -10993,6 +11175,7 @@ describe('plugin-meetings', () => {
10993
11175
  beforeEach(() => {
10994
11176
  meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
10995
11177
  meeting.deviceUrl = 'my-web-url';
11178
+ meeting.locusInfo.info = {isWebinar: false};
10996
11179
  });
10997
11180
 
10998
11181
  const USER_IDS = {
@@ -11218,13 +11401,24 @@ describe('plugin-meetings', () => {
11218
11401
 
11219
11402
  activeSharingId.whiteboard = beneficiaryId;
11220
11403
 
11221
- eventTrigger.share.push({
11404
+ eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11405
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11406
+ functionName: 'remoteShare',
11407
+ eventPayload: {
11408
+ memberId: null,
11409
+ url,
11410
+ shareInstanceId,
11411
+ annotationInfo: undefined,
11412
+ resourceType: undefined,
11413
+ },
11414
+ } : {
11222
11415
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11223
11416
  functionName: 'startWhiteboardShare',
11224
11417
  eventPayload: {resourceUrl, memberId: beneficiaryId},
11225
11418
  });
11226
11419
 
11227
- shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11420
+ shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11421
+
11228
11422
  }
11229
11423
 
11230
11424
  if (eventTrigger.member) {
@@ -11256,13 +11450,24 @@ describe('plugin-meetings', () => {
11256
11450
  newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
11257
11451
  newPayload.current.content.beneficiaryId = otherBeneficiaryId;
11258
11452
 
11259
- eventTrigger.share.push({
11453
+ eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11454
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11455
+ functionName: 'remoteShare',
11456
+ eventPayload: {
11457
+ memberId: null,
11458
+ url,
11459
+ shareInstanceId,
11460
+ annotationInfo: undefined,
11461
+ resourceType: undefined,
11462
+ },
11463
+ } : {
11260
11464
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11261
11465
  functionName: 'startWhiteboardShare',
11262
11466
  eventPayload: {resourceUrl, memberId: beneficiaryId},
11263
11467
  });
11264
11468
 
11265
- shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11469
+ shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11470
+
11266
11471
  } else {
11267
11472
  eventTrigger.share.push({
11268
11473
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
@@ -11389,6 +11594,38 @@ describe('plugin-meetings', () => {
11389
11594
  assert.exists(meeting.setUpLocusMediaSharesListener);
11390
11595
  });
11391
11596
 
11597
+ describe('Whiteboard Share - Webinar Attendee', () => {
11598
+ it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
11599
+ // Set the webinar attendee flag
11600
+ meeting.webinar = { selfIsAttendee: true };
11601
+ meeting.locusInfo.info.isWebinar = true;
11602
+
11603
+ // Step 1: Start sharing whiteboard A
11604
+ const data1 = generateData(
11605
+ blankPayload, // Initial payload
11606
+ true, // isGranting: Granting share
11607
+ false, // isContent: Whiteboard (not content)
11608
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
11609
+ RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
11610
+ );
11611
+
11612
+ // Step 2: Stop sharing whiteboard A
11613
+ const data2 = generateData(
11614
+ data1.payload, // Updated payload from Step 1
11615
+ false, // isGranting: Stopping share
11616
+ false, // isContent: Whiteboard
11617
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
11618
+ );
11619
+
11620
+ // Validate the payload changes and status updates
11621
+ payloadTestHelper([data1]);
11622
+
11623
+ // Specific assertions for webinar attendee status
11624
+ assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
11625
+ });
11626
+ });
11627
+
11628
+
11392
11629
  describe('Whiteboard A --> Whiteboard B', () => {
11393
11630
  it('Scenario #1: you share both whiteboards', () => {
11394
11631
  const data1 = generateData(