@webex/plugin-meetings 3.7.0-next.2 → 3.7.0-next.21

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 (87) 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 +29 -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 +5 -2
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/meeting/in-meeting-actions.js +11 -1
  16. package/dist/meeting/in-meeting-actions.js.map +1 -1
  17. package/dist/meeting/index.js +115 -150
  18. package/dist/meeting/index.js.map +1 -1
  19. package/dist/meeting/util.js +3 -8
  20. package/dist/meeting/util.js.map +1 -1
  21. package/dist/meeting-info/meeting-info-v2.js +29 -17
  22. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  23. package/dist/meetings/index.js +6 -3
  24. package/dist/meetings/index.js.map +1 -1
  25. package/dist/members/util.js +4 -2
  26. package/dist/members/util.js.map +1 -1
  27. package/dist/metrics/constants.js +3 -1
  28. package/dist/metrics/constants.js.map +1 -1
  29. package/dist/multistream/remoteMedia.js +30 -15
  30. package/dist/multistream/remoteMedia.js.map +1 -1
  31. package/dist/reachability/clusterReachability.js +12 -11
  32. package/dist/reachability/clusterReachability.js.map +1 -1
  33. package/dist/recording-controller/enums.js +8 -4
  34. package/dist/recording-controller/enums.js.map +1 -1
  35. package/dist/recording-controller/index.js +18 -9
  36. package/dist/recording-controller/index.js.map +1 -1
  37. package/dist/recording-controller/util.js +13 -9
  38. package/dist/recording-controller/util.js.map +1 -1
  39. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  40. package/dist/types/constants.d.ts +21 -1
  41. package/dist/types/index.d.ts +3 -3
  42. package/dist/types/locus-info/index.d.ts +2 -1
  43. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  44. package/dist/types/meeting/index.d.ts +1 -10
  45. package/dist/types/meeting/util.d.ts +1 -1
  46. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  47. package/dist/types/meetings/index.d.ts +3 -0
  48. package/dist/types/members/util.d.ts +2 -0
  49. package/dist/types/metrics/constants.d.ts +3 -1
  50. package/dist/types/recording-controller/enums.d.ts +5 -2
  51. package/dist/types/recording-controller/index.d.ts +1 -0
  52. package/dist/types/recording-controller/util.d.ts +2 -1
  53. package/dist/webinar/index.js +390 -7
  54. package/dist/webinar/index.js.map +1 -1
  55. package/package.json +22 -22
  56. package/src/common/errors/join-webinar-error.ts +24 -0
  57. package/src/config.ts +1 -1
  58. package/src/constants.ts +26 -3
  59. package/src/index.ts +2 -3
  60. package/src/locus-info/index.ts +4 -2
  61. package/src/meeting/in-meeting-actions.ts +21 -0
  62. package/src/meeting/index.ts +86 -54
  63. package/src/meeting/util.ts +3 -9
  64. package/src/meeting-info/meeting-info-v2.ts +23 -11
  65. package/src/meetings/index.ts +8 -2
  66. package/src/members/util.ts +1 -0
  67. package/src/metrics/constants.ts +3 -1
  68. package/src/multistream/remoteMedia.ts +28 -15
  69. package/src/reachability/clusterReachability.ts +4 -1
  70. package/src/recording-controller/enums.ts +5 -2
  71. package/src/recording-controller/index.ts +17 -4
  72. package/src/recording-controller/util.ts +20 -5
  73. package/src/webinar/index.ts +235 -9
  74. package/test/unit/spec/locus-info/index.js +129 -0
  75. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
  76. package/test/unit/spec/meeting/index.js +179 -81
  77. package/test/unit/spec/meeting/utils.js +11 -19
  78. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  79. package/test/unit/spec/meetings/index.js +9 -5
  80. package/test/unit/spec/members/utils.js +95 -0
  81. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  82. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  83. package/test/unit/spec/recording-controller/index.js +61 -5
  84. package/test/unit/spec/recording-controller/util.js +39 -3
  85. package/test/unit/spec/webinar/index.ts +504 -0
  86. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  87. 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
  });
@@ -3552,14 +3556,6 @@ describe('plugin-meetings', () => {
3552
3556
  });
3553
3557
  });
3554
3558
 
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
3559
  describe('CA ice failures checks', () => {
3564
3560
  [
3565
3561
  {
@@ -3743,8 +3739,12 @@ describe('plugin-meetings', () => {
3743
3739
  meeting.setMercuryListener = sinon.stub();
3744
3740
  meeting.locusInfo.onFullLocus = sinon.stub();
3745
3741
  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'});
3742
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
3743
+ .stub()
3744
+ .resolves({id: 'fake reachability'});
3745
+ meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
3746
+ .stub()
3747
+ .resolves({id: 'fake clientMediaPreferences'});
3748
3748
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3749
3749
  turnServerInfo: {
3750
3750
  url: 'turns:turn-server-url:443?transport=tcp',
@@ -3930,8 +3930,14 @@ describe('plugin-meetings', () => {
3930
3930
  const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
3931
3931
  const {sdp, seq, tieBreaker} = roapOfferMessage;
3932
3932
 
3933
- assert.calledWith(meeting.webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, 0);
3934
- assert.calledWith(meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap);
3933
+ assert.calledWith(
3934
+ meeting.webex.meetings.reachability.getClientMediaPreferences,
3935
+ meeting.isMultistream,
3936
+ 0
3937
+ );
3938
+ assert.calledWith(
3939
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
3940
+ );
3935
3941
 
3936
3942
  assert.calledWith(locusMediaRequestStub, {
3937
3943
  method: 'PUT',
@@ -4176,7 +4182,6 @@ describe('plugin-meetings', () => {
4176
4182
  });
4177
4183
 
4178
4184
  it('addMedia() works correctly when media is enabled with streams to publish', async () => {
4179
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4180
4185
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
4181
4186
  await simulateRoapOffer();
4182
4187
  await simulateRoapOk();
@@ -4207,12 +4212,9 @@ describe('plugin-meetings', () => {
4207
4212
 
4208
4213
  // and that these were the only /media requests that were sent
4209
4214
  assert.calledTwice(locusMediaRequestStub);
4210
-
4211
- assert.calledOnce(handleDeviceLoggingSpy);
4212
4215
  });
4213
4216
 
4214
4217
  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
4218
  fakeMicrophoneStream.userMuted = true;
4217
4219
 
4218
4220
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
@@ -4244,7 +4246,6 @@ describe('plugin-meetings', () => {
4244
4246
 
4245
4247
  // and that these were the only /media requests that were sent
4246
4248
  assert.calledTwice(locusMediaRequestStub);
4247
- assert.calledOnce(handleDeviceLoggingSpy);
4248
4249
  });
4249
4250
 
4250
4251
  it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
@@ -4316,7 +4317,6 @@ describe('plugin-meetings', () => {
4316
4317
  });
4317
4318
 
4318
4319
  it('addMedia() works correctly when media is disabled with streams to publish', async () => {
4319
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4320
4320
  await meeting.addMedia({
4321
4321
  localStreams: {microphone: fakeMicrophoneStream},
4322
4322
  audioEnabled: false,
@@ -4350,20 +4350,6 @@ describe('plugin-meetings', () => {
4350
4350
 
4351
4351
  // and that these were the only /media requests that were sent
4352
4352
  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
4353
  });
4368
4354
 
4369
4355
  it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
@@ -4399,20 +4385,6 @@ describe('plugin-meetings', () => {
4399
4385
  assert.calledTwice(locusMediaRequestStub);
4400
4386
  });
4401
4387
 
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
4388
  it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4417
4389
  await meeting.addMedia({videoEnabled: false});
4418
4390
  await simulateRoapOffer();
@@ -4479,13 +4451,6 @@ describe('plugin-meetings', () => {
4479
4451
  assert.calledTwice(locusMediaRequestStub);
4480
4452
  });
4481
4453
 
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
4454
  describe('publishStreams()/unpublishStreams() calls', () => {
4490
4455
  [
4491
4456
  {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
@@ -6332,29 +6297,74 @@ describe('plugin-meetings', () => {
6332
6297
  assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
6333
6298
  });
6334
6299
 
6335
- it('handles meetingInfoProvider webinar need registration error', async () => {
6300
+ it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
6336
6301
  meeting.destination = FAKE_DESTINATION;
6337
6302
  meeting.destinationType = FAKE_TYPE;
6338
6303
  meeting.attrs.meetingInfoProvider = {
6339
6304
  fetchMeetingInfo: sinon
6340
6305
  .stub()
6341
6306
  .throws(
6342
- new MeetingInfoV2WebinarRegistrationError(403021, FAKE_MEETING_INFO, 'a message')
6307
+ new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6343
6308
  ),
6344
6309
  };
6345
6310
 
6346
6311
  await assert.isRejected(
6347
6312
  meeting.fetchMeetingInfo({sendCAevents: true}),
6348
- WebinarRegistrationError
6313
+ JoinWebinarError
6349
6314
  );
6350
6315
 
6351
6316
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6352
- assert.equal(meeting.meetingInfoFailureCode, 403021);
6353
6317
  assert.equal(
6354
6318
  meeting.meetingInfoFailureReason,
6355
6319
  MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
6356
6320
  );
6357
6321
  });
6322
+
6323
+ it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
6324
+ meeting.destination = FAKE_DESTINATION;
6325
+ meeting.destinationType = FAKE_TYPE;
6326
+ meeting.attrs.meetingInfoProvider = {
6327
+ fetchMeetingInfo: sinon
6328
+ .stub()
6329
+ .throws(
6330
+ new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6331
+ ),
6332
+ };
6333
+
6334
+ await assert.isRejected(
6335
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6336
+ JoinWebinarError
6337
+ );
6338
+
6339
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6340
+ assert.equal(
6341
+ meeting.meetingInfoFailureReason,
6342
+ MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
6343
+ );
6344
+ });
6345
+
6346
+ it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
6347
+ meeting.destination = FAKE_DESTINATION;
6348
+ meeting.destinationType = FAKE_TYPE;
6349
+ meeting.attrs.meetingInfoProvider = {
6350
+ fetchMeetingInfo: sinon
6351
+ .stub()
6352
+ .throws(
6353
+ new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6354
+ ),
6355
+ };
6356
+
6357
+ await assert.isRejected(
6358
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6359
+ JoinWebinarError
6360
+ );
6361
+
6362
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6363
+ assert.equal(
6364
+ meeting.meetingInfoFailureReason,
6365
+ MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
6366
+ );
6367
+ });
6358
6368
  });
6359
6369
 
6360
6370
  describe('#refreshPermissionToken', () => {
@@ -7817,7 +7827,9 @@ describe('plugin-meetings', () => {
7817
7827
  });
7818
7828
 
7819
7829
  it('should collect ice candidates', () => {
7820
- eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: {candidate: 'candidate'}});
7830
+ eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
7831
+ candidate: {candidate: 'candidate'},
7832
+ });
7821
7833
 
7822
7834
  assert.equal(meeting.iceCandidatesCount, 1);
7823
7835
  });
@@ -8123,10 +8135,10 @@ describe('plugin-meetings', () => {
8123
8135
  meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
8124
8136
  meeting.reconnectionManager = {
8125
8137
  reconnect: sinon.stub().resolves(),
8126
- resetReconnectionTimer: () => {}
8138
+ resetReconnectionTimer: () => {},
8127
8139
  };
8128
8140
  meeting.currentMediaStatus = {
8129
- video: true
8141
+ video: true,
8130
8142
  };
8131
8143
 
8132
8144
  await mockFailedEvent();
@@ -8677,6 +8689,13 @@ describe('plugin-meetings', () => {
8677
8689
  {payload: test1}
8678
8690
  );
8679
8691
  assert.calledOnce(meeting.updateLLMConnection);
8692
+ assert.calledOnceWithExactly(
8693
+ Metrics.sendBehavioralMetric,
8694
+ BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
8695
+ {
8696
+ correlation_id: meeting.correlationId,
8697
+ }
8698
+ );
8680
8699
  done();
8681
8700
  });
8682
8701
  it('listens to the self admitted guest event', (done) => {
@@ -8698,6 +8717,13 @@ describe('plugin-meetings', () => {
8698
8717
  assert.calledOnce(meeting.updateLLMConnection);
8699
8718
  assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
8700
8719
 
8720
+ assert.calledOnceWithExactly(
8721
+ Metrics.sendBehavioralMetric,
8722
+ BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
8723
+ {
8724
+ correlation_id: meeting.correlationId,
8725
+ }
8726
+ );
8701
8727
  done();
8702
8728
  });
8703
8729
 
@@ -9030,6 +9056,8 @@ describe('plugin-meetings', () => {
9030
9056
  });
9031
9057
 
9032
9058
  it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
9059
+ meeting.webinar.updatePracticeSessionStatus = sinon.stub();
9060
+
9033
9061
  const state = {example: 'value'};
9034
9062
 
9035
9063
  await meeting.locusInfo.emitScoped(
@@ -9038,6 +9066,7 @@ describe('plugin-meetings', () => {
9038
9066
  {state}
9039
9067
  );
9040
9068
 
9069
+ assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
9041
9070
  assert.calledWith(
9042
9071
  TriggerProxy.trigger,
9043
9072
  meeting,
@@ -10657,6 +10686,7 @@ describe('plugin-meetings', () => {
10657
10686
  meeting.webex.internal.llm.on = sinon.stub();
10658
10687
  meeting.webex.internal.llm.off = sinon.stub();
10659
10688
  meeting.processRelayEvent = sinon.stub();
10689
+ meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
10660
10690
  });
10661
10691
 
10662
10692
  it('does not connect if the call is not joined yet', async () => {
@@ -10788,6 +10818,19 @@ describe('plugin-meetings', () => {
10788
10818
  meeting.processRelayEvent
10789
10819
  );
10790
10820
  });
10821
+
10822
+
10823
+ it('connect ps data channel if ps started in webinar', async () => {
10824
+ meeting.joinedWith = {state: 'JOINED'};
10825
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
10826
+ meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
10827
+ await meeting.updateLLMConnection();
10828
+
10829
+ assert.notCalled(webex.internal.llm.disconnectLLM);
10830
+ assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
10831
+
10832
+ });
10833
+
10791
10834
  });
10792
10835
 
10793
10836
  describe('#setLocus', () => {
@@ -10979,6 +11022,7 @@ describe('plugin-meetings', () => {
10979
11022
  beforeEach(() => {
10980
11023
  meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
10981
11024
  meeting.deviceUrl = 'my-web-url';
11025
+ meeting.locusInfo.info = {isWebinar: false};
10982
11026
  });
10983
11027
 
10984
11028
  const USER_IDS = {
@@ -11204,13 +11248,24 @@ describe('plugin-meetings', () => {
11204
11248
 
11205
11249
  activeSharingId.whiteboard = beneficiaryId;
11206
11250
 
11207
- eventTrigger.share.push({
11251
+ eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11252
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11253
+ functionName: 'remoteShare',
11254
+ eventPayload: {
11255
+ memberId: null,
11256
+ url,
11257
+ shareInstanceId,
11258
+ annotationInfo: undefined,
11259
+ resourceType: undefined,
11260
+ },
11261
+ } : {
11208
11262
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11209
11263
  functionName: 'startWhiteboardShare',
11210
11264
  eventPayload: {resourceUrl, memberId: beneficiaryId},
11211
11265
  });
11212
11266
 
11213
- shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11267
+ shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11268
+
11214
11269
  }
11215
11270
 
11216
11271
  if (eventTrigger.member) {
@@ -11242,13 +11297,24 @@ describe('plugin-meetings', () => {
11242
11297
  newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
11243
11298
  newPayload.current.content.beneficiaryId = otherBeneficiaryId;
11244
11299
 
11245
- eventTrigger.share.push({
11300
+ eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11301
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11302
+ functionName: 'remoteShare',
11303
+ eventPayload: {
11304
+ memberId: null,
11305
+ url,
11306
+ shareInstanceId,
11307
+ annotationInfo: undefined,
11308
+ resourceType: undefined,
11309
+ },
11310
+ } : {
11246
11311
  eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11247
11312
  functionName: 'startWhiteboardShare',
11248
11313
  eventPayload: {resourceUrl, memberId: beneficiaryId},
11249
11314
  });
11250
11315
 
11251
- shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11316
+ shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11317
+
11252
11318
  } else {
11253
11319
  eventTrigger.share.push({
11254
11320
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
@@ -11375,6 +11441,38 @@ describe('plugin-meetings', () => {
11375
11441
  assert.exists(meeting.setUpLocusMediaSharesListener);
11376
11442
  });
11377
11443
 
11444
+ describe('Whiteboard Share - Webinar Attendee', () => {
11445
+ it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
11446
+ // Set the webinar attendee flag
11447
+ meeting.webinar = { selfIsAttendee: true };
11448
+ meeting.locusInfo.info.isWebinar = true;
11449
+
11450
+ // Step 1: Start sharing whiteboard A
11451
+ const data1 = generateData(
11452
+ blankPayload, // Initial payload
11453
+ true, // isGranting: Granting share
11454
+ false, // isContent: Whiteboard (not content)
11455
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
11456
+ RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
11457
+ );
11458
+
11459
+ // Step 2: Stop sharing whiteboard A
11460
+ const data2 = generateData(
11461
+ data1.payload, // Updated payload from Step 1
11462
+ false, // isGranting: Stopping share
11463
+ false, // isContent: Whiteboard
11464
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
11465
+ );
11466
+
11467
+ // Validate the payload changes and status updates
11468
+ payloadTestHelper([data1]);
11469
+
11470
+ // Specific assertions for webinar attendee status
11471
+ assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
11472
+ });
11473
+ });
11474
+
11475
+
11378
11476
  describe('Whiteboard A --> Whiteboard B', () => {
11379
11477
  it('Scenario #1: you share both whiteboards', () => {
11380
11478
  const data1 = generateData(
@@ -26,7 +26,7 @@ describe('plugin-meetings', () => {
26
26
  webex.meetings.reachability = {
27
27
  getReachabilityReportToAttachToRoap: sinon.stub().resolves({}),
28
28
  getClientMediaPreferences: sinon.stub().resolves({}),
29
- };
29
+ };
30
30
 
31
31
  const logger = {
32
32
  info: sandbox.stub(),
@@ -165,21 +165,6 @@ describe('plugin-meetings', () => {
165
165
  assert(LoggerProxy.logger.log.called, 'log called');
166
166
  });
167
167
  });
168
-
169
- describe('#handleDeviceLogging', () => {
170
- it('should not log if called without devices', () => {
171
- MeetingUtil.handleDeviceLogging();
172
- assert(!LoggerProxy.logger.log.called, 'log not called');
173
- });
174
-
175
- it('should log device settings', () => {
176
- const mockDevices = [{deviceId: 'device-1'}, {deviceId: 'device-2'}];
177
-
178
- assert(MeetingUtil.handleDeviceLogging, 'is defined');
179
- MeetingUtil.handleDeviceLogging(mockDevices);
180
- assert(LoggerProxy.logger.log.called, 'log called');
181
- });
182
- });
183
168
  });
184
169
 
185
170
  describe('addSequence', () => {
@@ -424,17 +409,17 @@ describe('plugin-meetings', () => {
424
409
  const FAKE_CLIENT_MEDIA_PREFERENCES = {
425
410
  id: 'fake client media preferences',
426
411
  };
427
-
412
+
428
413
  webex.meetings.reachability.getReachabilityReportToAttachToRoap.resolves(FAKE_REACHABILITY_REPORT);
429
414
  webex.meetings.reachability.getClientMediaPreferences.resolves(FAKE_CLIENT_MEDIA_PREFERENCES);
430
-
415
+
431
416
  sinon
432
417
  .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
433
418
  .get(() => true);
434
419
  sinon
435
420
  .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
436
421
  .get(() => true);
437
-
422
+
438
423
  await MeetingUtil.joinMeeting(meeting, {
439
424
  reachability: 'reachability',
440
425
  roapMessage: 'roapMessage',
@@ -775,6 +760,13 @@ describe('plugin-meetings', () => {
775
760
  });
776
761
  });
777
762
 
763
+ describe('canStartBreakout', () => {
764
+ it('works as expected', () => {
765
+ assert.deepEqual(MeetingUtil.canStartBreakout(['DISABLE_BREAKOUT_START']), false);
766
+ assert.deepEqual(MeetingUtil.canStartBreakout([]), true);
767
+ });
768
+ });
769
+
778
770
  describe('canBroadcastMessageToBreakout', () => {
779
771
  it('works as expected', () => {
780
772
  assert.deepEqual(
@@ -18,7 +18,7 @@ import MeetingInfo, {
18
18
  MeetingInfoV2CaptchaError,
19
19
  MeetingInfoV2AdhocMeetingError,
20
20
  MeetingInfoV2PolicyError,
21
- MeetingInfoV2WebinarRegistrationError,
21
+ MeetingInfoV2JoinWebinarError,
22
22
  } from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
23
23
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
24
24
  import Metrics from '@webex/plugin-meetings/src/metrics';
@@ -895,9 +895,14 @@ describe('plugin-meetings', () => {
895
895
  {errorCode: 403021},
896
896
  {errorCode: 403022},
897
897
  {errorCode: 403024},
898
+ {errorCode: 403137},
899
+ {errorCode: 423007},
900
+ {errorCode: 403026},
901
+ {errorCode: 403037},
902
+ {errorCode: 403137},
898
903
  ],
899
904
  ({errorCode}) => {
900
- it(`should throw a MeetingInfoV2WebinarRegistrationError for error code ${errorCode}`, async () => {
905
+ it(`should throw a MeetingInfoV2JoinWebinarError for error code ${errorCode}`, async () => {
901
906
  const message = 'a message';
902
907
  const meetingInfoData = {meetingInfo: {registrationUrl: 'registrationUrl'}};
903
908
 
@@ -909,7 +914,7 @@ describe('plugin-meetings', () => {
909
914
  await meetingInfo.createAdhocSpaceMeeting(conversationUrl, installedOrgID);
910
915
  assert.fail('createAdhocSpaceMeeting should have thrown, but has not done that');
911
916
  } catch (err) {
912
- assert.instanceOf(err, MeetingInfoV2WebinarRegistrationError);
917
+ assert.instanceOf(err, MeetingInfoV2JoinWebinarError);
913
918
  assert.deepEqual(err.message, `${message}, code=${errorCode}`);
914
919
  assert.equal(err.wbxAppApiCode, errorCode);
915
920
  assert.deepEqual(err.meetingInfo, meetingInfoData);
@@ -917,7 +922,7 @@ describe('plugin-meetings', () => {
917
922
  assert(Metrics.sendBehavioralMetric.calledOnce);
918
923
  assert.calledWith(
919
924
  Metrics.sendBehavioralMetric,
920
- BEHAVIORAL_METRICS.WEBINAR_REGISTRATION_ERROR,
925
+ BEHAVIORAL_METRICS.JOIN_WEBINAR_ERROR,
921
926
  {code: errorCode}
922
927
  );
923
928
 
@@ -131,9 +131,9 @@ describe('plugin-meetings', () => {
131
131
  logger,
132
132
  people: {
133
133
  _getMe: sinon.stub().resolves({
134
- type: 'validuser',
134
+ type: 'validuser',
135
135
  }),
136
- }
136
+ },
137
137
  });
138
138
 
139
139
  startReachabilityStub = sinon.stub(webex.meetings, 'startReachability').resolves();
@@ -1985,6 +1985,8 @@ describe('plugin-meetings', () => {
1985
1985
  const meetingIds = {
1986
1986
  meetingId: meeting.id,
1987
1987
  correlationId: meeting.correlationId,
1988
+ roles: meeting.roles,
1989
+ callStateForMetrics: meeting.callStateForMetrics,
1988
1990
  };
1989
1991
 
1990
1992
  webex.meetings.destroy(meeting, test1);
@@ -2021,6 +2023,8 @@ describe('plugin-meetings', () => {
2021
2023
 
2022
2024
  assert.equal(deletedMeetingInfo.id, meetingIds.meetingId);
2023
2025
  assert.equal(deletedMeetingInfo.correlationId, meetingIds.correlationId);
2026
+ assert.equal(deletedMeetingInfo.roles, meetingIds.roles);
2027
+ assert.equal(deletedMeetingInfo.callStateForMetrics, meetingIds.callStateForMetrics);
2024
2028
  });
2025
2029
  });
2026
2030
 
@@ -2092,7 +2096,7 @@ describe('plugin-meetings', () => {
2092
2096
  );
2093
2097
  });
2094
2098
 
2095
- const setup = ({me = { type: 'validuser'}, user} = {}) => {
2099
+ const setup = ({me = {type: 'validuser'}, user} = {}) => {
2096
2100
  loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
2097
2101
  assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
2098
2102
 
@@ -2113,9 +2117,9 @@ describe('plugin-meetings', () => {
2113
2117
 
2114
2118
  it('should not call request.getMeetingPreferences if user is a guest', async () => {
2115
2119
  setup({me: {type: 'appuser'}});
2116
-
2120
+
2117
2121
  await webex.meetings.fetchUserPreferredWebexSite();
2118
-
2122
+
2119
2123
  assert.equal(webex.meetings.preferredWebexSite, '');
2120
2124
  assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
2121
2125
  assert.notCalled(webex.internal.services.getMeetingPreferences);