@webex/plugin-meetings 3.5.0 → 3.6.0-next.10

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 (83) 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 +50 -0
  4. package/dist/common/errors/webinar-registration-error.js.map +1 -0
  5. package/dist/config.js +4 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +8 -0
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.js +7 -0
  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/parser.js +5 -1
  14. package/dist/locus-info/parser.js.map +1 -1
  15. package/dist/media/index.js +3 -1
  16. package/dist/media/index.js.map +1 -1
  17. package/dist/meeting/in-meeting-actions.js +3 -1
  18. package/dist/meeting/in-meeting-actions.js.map +1 -1
  19. package/dist/meeting/index.js +185 -103
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/muteState.js +5 -2
  22. package/dist/meeting/muteState.js.map +1 -1
  23. package/dist/meeting/util.js +8 -10
  24. package/dist/meeting/util.js.map +1 -1
  25. package/dist/meeting-info/meeting-info-v2.js +68 -17
  26. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  27. package/dist/meetings/index.js +102 -27
  28. package/dist/meetings/index.js.map +1 -1
  29. package/dist/metrics/constants.js +2 -1
  30. package/dist/metrics/constants.js.map +1 -1
  31. package/dist/multistream/remoteMedia.js +4 -0
  32. package/dist/multistream/remoteMedia.js.map +1 -1
  33. package/dist/roap/request.js +1 -1
  34. package/dist/roap/request.js.map +1 -1
  35. package/dist/types/common/errors/webinar-registration-error.d.ts +14 -0
  36. package/dist/types/config.d.ts +2 -0
  37. package/dist/types/constants.d.ts +8 -1
  38. package/dist/types/index.d.ts +2 -1
  39. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  40. package/dist/types/meeting/index.d.ts +11 -0
  41. package/dist/types/meeting/muteState.d.ts +2 -1
  42. package/dist/types/meeting-info/meeting-info-v2.d.ts +23 -0
  43. package/dist/types/meetings/index.d.ts +43 -2
  44. package/dist/types/metrics/constants.d.ts +1 -0
  45. package/dist/types/multistream/remoteMedia.d.ts +1 -0
  46. package/dist/webinar/index.js +1 -1
  47. package/package.json +22 -22
  48. package/src/common/errors/webinar-registration-error.ts +27 -0
  49. package/src/config.ts +3 -0
  50. package/src/constants.ts +7 -0
  51. package/src/index.ts +2 -0
  52. package/src/locus-info/parser.ts +8 -1
  53. package/src/media/index.ts +4 -1
  54. package/src/meeting/in-meeting-actions.ts +3 -0
  55. package/src/meeting/index.ts +82 -13
  56. package/src/meeting/muteState.ts +6 -2
  57. package/src/meeting/util.ts +27 -31
  58. package/src/meeting-info/meeting-info-v2.ts +51 -0
  59. package/src/meetings/index.ts +129 -38
  60. package/src/metrics/constants.ts +1 -0
  61. package/src/multistream/remoteMedia.ts +5 -0
  62. package/src/roap/request.ts +3 -1
  63. package/test/unit/spec/locus-info/index.js +29 -0
  64. package/test/unit/spec/media/index.ts +4 -0
  65. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  66. package/test/unit/spec/meeting/index.js +118 -18
  67. package/test/unit/spec/meeting/muteState.js +8 -4
  68. package/test/unit/spec/meeting/utils.js +50 -85
  69. package/test/unit/spec/meeting-info/meetinginfov2.js +37 -0
  70. package/test/unit/spec/meetings/index.js +128 -13
  71. package/test/unit/spec/multistream/remoteMedia.ts +16 -2
  72. package/dist/networkQualityMonitor/index.js +0 -227
  73. package/dist/networkQualityMonitor/index.js.map +0 -1
  74. package/dist/rtcMetrics/constants.js +0 -11
  75. package/dist/rtcMetrics/constants.js.map +0 -1
  76. package/dist/rtcMetrics/index.js +0 -197
  77. package/dist/rtcMetrics/index.js.map +0 -1
  78. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  79. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  80. package/dist/types/rtcMetrics/index.d.ts +0 -71
  81. package/src/rtcMetrics/constants.ts +0 -3
  82. package/src/rtcMetrics/index.ts +0 -186
  83. package/test/unit/spec/rtcMetrics/index.ts +0 -154
@@ -5,7 +5,6 @@ import 'jsdom-global/register';
5
5
  import {cloneDeep, forEach, isEqual, isUndefined} from 'lodash';
6
6
  import sinon from 'sinon';
7
7
  import * as InternalMediaCoreModule from '@webex/internal-media-core';
8
- import * as RtcMetricsModule from '@webex/plugin-meetings/src/rtcMetrics';
9
8
  import * as RemoteMediaManagerModule from '@webex/plugin-meetings/src/multistream/remoteMediaManager';
10
9
  import StateMachine from 'javascript-state-machine';
11
10
  import uuid from 'uuid';
@@ -91,13 +90,15 @@ import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-er
91
90
  import ParameterError from '../../../../src/common/errors/parameter';
92
91
  import PasswordError from '../../../../src/common/errors/password-error';
93
92
  import CaptchaError from '../../../../src/common/errors/captcha-error';
94
- import PermissionError from '../../../../src/common/errors/permission';
93
+ import PermissionError from '../../../../src/common/errors/permission';
94
+ import WebinarRegistrationError from '../../../../src/common/errors/webinar-registration-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
102
  } from '../../../../src/meeting-info/meeting-info-v2';
102
103
  import {
103
104
  DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
@@ -307,7 +308,7 @@ describe('plugin-meetings', () => {
307
308
  assert.equal(meeting.resource, uuid2);
308
309
  assert.equal(meeting.deviceUrl, uuid3);
309
310
  assert.equal(meeting.correlationId, correlationId);
310
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
311
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
311
312
  assert.deepEqual(meeting.meetingInfo, {});
312
313
  assert.instanceOf(meeting.members, Members);
313
314
  assert.calledOnceWithExactly(
@@ -376,7 +377,7 @@ describe('plugin-meetings', () => {
376
377
  }
377
378
  );
378
379
  assert.equal(newMeeting.correlationId, newMeeting.id);
379
- assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id});
380
+ assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id, sessionCorrelationId: ''});
380
381
  });
381
382
 
382
383
  it('correlationId can be provided in callStateForMetrics', () => {
@@ -403,6 +404,37 @@ describe('plugin-meetings', () => {
403
404
  correlationId: uuid4,
404
405
  joinTrigger: 'fake-join-trigger',
405
406
  loginType: 'fake-login-type',
407
+ sessionCorrelationId: '',
408
+ });
409
+ });
410
+
411
+ it('sessionCorrelationId can be provided in callStateForMetrics', () => {
412
+ const newMeeting = new Meeting(
413
+ {
414
+ userId: uuid1,
415
+ resource: uuid2,
416
+ deviceUrl: uuid3,
417
+ locus: {url: url1},
418
+ destination: testDestination,
419
+ destinationType: DESTINATION_TYPE.MEETING_ID,
420
+ callStateForMetrics: {
421
+ correlationId: uuid4,
422
+ sessionCorrelationId: uuid1,
423
+ joinTrigger: 'fake-join-trigger',
424
+ loginType: 'fake-login-type',
425
+ },
426
+ },
427
+ {
428
+ parent: webex,
429
+ }
430
+ );
431
+ assert.exists(newMeeting.sessionCorrelationId);
432
+ assert.equal(newMeeting.sessionCorrelationId, uuid1);
433
+ assert.deepEqual(newMeeting.callStateForMetrics, {
434
+ correlationId: uuid4,
435
+ sessionCorrelationId: uuid1,
436
+ joinTrigger: 'fake-join-trigger',
437
+ loginType: 'fake-login-type',
406
438
  });
407
439
  });
408
440
 
@@ -1611,7 +1643,6 @@ describe('plugin-meetings', () => {
1611
1643
 
1612
1644
  assert.calledOnce(MeetingUtil.joinMeeting);
1613
1645
  assert.calledOnce(webex.internal.device.meetingStarted);
1614
- assert.calledOnce(meeting.setLocus);
1615
1646
  assert.equal(result, joinMeetingResult);
1616
1647
  assert.calledWith(webex.internal.llm.on, 'online', meeting.handleLLMOnline);
1617
1648
  });
@@ -2456,8 +2487,8 @@ describe('plugin-meetings', () => {
2456
2487
  });
2457
2488
 
2458
2489
  it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
2459
- const fakeRtcMetrics = {id: 'fake rtc metrics object'};
2460
- const rtcMetricsCtor = sinon.stub(RtcMetricsModule, 'default').returns(fakeRtcMetrics);
2490
+ const setIntervalOriginal = window.setInterval;
2491
+ window.setInterval = sinon.stub().returns(1);
2461
2492
 
2462
2493
  // setup the minimum mocks required for multistream connection
2463
2494
  fakeMediaConnection.createSendSlot = sinon.stub().returns({
@@ -2478,8 +2509,6 @@ describe('plugin-meetings', () => {
2478
2509
  mediaSettings: {},
2479
2510
  });
2480
2511
 
2481
- assert.calledOnceWithExactly(rtcMetricsCtor, webex, meeting.id, meeting.correlationId);
2482
-
2483
2512
  // check that rtcMetrics was passed to Media.createMediaConnection
2484
2513
  assert.calledOnce(Media.createMediaConnection);
2485
2514
  assert.calledWith(
@@ -2487,10 +2516,10 @@ describe('plugin-meetings', () => {
2487
2516
  true,
2488
2517
  meeting.getMediaConnectionDebugId(),
2489
2518
  meeting.id,
2490
- sinon.match({
2491
- rtcMetrics: fakeRtcMetrics,
2492
- })
2519
+ sinon.match.hasNested('rtcMetrics.webex', webex)
2493
2520
  );
2521
+
2522
+ window.setInterval = setIntervalOriginal;
2494
2523
  });
2495
2524
 
2496
2525
  it('should pass the turn server info to the peer connection', async () => {
@@ -3677,6 +3706,7 @@ describe('plugin-meetings', () => {
3677
3706
  credential: 'turn password',
3678
3707
  },
3679
3708
  ],
3709
+ iceCandidatesTimeout: meeting.config.iceCandidatesGatheringTimeout,
3680
3710
  skipInactiveTransceivers: false,
3681
3711
  requireH264: true,
3682
3712
  sdpMunging: {
@@ -3761,11 +3791,16 @@ describe('plugin-meetings', () => {
3761
3791
  // that's being tested in these tests)
3762
3792
  meeting.webex.meetings.registered = true;
3763
3793
  meeting.webex.internal.device.config = {};
3764
- sinon.stub(MeetingUtil, 'joinMeeting').resolves({
3794
+ sinon.stub(MeetingUtil, 'parseLocusJoin').returns({
3765
3795
  id: 'fake locus from mocked join request',
3766
3796
  locusUrl: 'fake locus url',
3767
3797
  mediaId: 'fake media id',
3768
- });
3798
+ })
3799
+ sinon.stub(meeting.meetingRequest, 'joinMeeting').resolves({
3800
+ headers: {
3801
+ trackingid: 'fake tracking id',
3802
+ }
3803
+ })
3769
3804
  await meeting.join({enableMultistream: isMultistream});
3770
3805
  });
3771
3806
 
@@ -3958,6 +3993,7 @@ describe('plugin-meetings', () => {
3958
3993
  assert.notCalled(
3959
3994
  meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream
3960
3995
  );
3996
+ assert.throws(meeting.publishStreams(localStreams), `Attempted to publish microphone stream with ended readyState, correlationId=${meeting.correlationId}`);
3961
3997
  } else {
3962
3998
  assert.calledOnceWithExactly(
3963
3999
  meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream,
@@ -3970,6 +4006,7 @@ describe('plugin-meetings', () => {
3970
4006
  assert.notCalled(
3971
4007
  meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream
3972
4008
  );
4009
+ assert.throws(meeting.publishStreams(localStreams), `Attempted to publish camera stream with ended readyState, correlationId=${meeting.correlationId}`);
3973
4010
  } else {
3974
4011
  assert.calledOnceWithExactly(
3975
4012
  meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream,
@@ -3982,6 +4019,7 @@ describe('plugin-meetings', () => {
3982
4019
  assert.notCalled(
3983
4020
  meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream
3984
4021
  );
4022
+ assert.throws(meeting.publishStreams(localStreams), `Attempted to publish screenShare audio stream with ended readyState, correlationId=${meeting.correlationId}`);
3985
4023
  } else {
3986
4024
  assert.calledOnceWithExactly(
3987
4025
  meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
@@ -3994,6 +4032,7 @@ describe('plugin-meetings', () => {
3994
4032
  assert.notCalled(
3995
4033
  meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream
3996
4034
  );
4035
+ assert.throws(meeting.publishStreams(localStreams), `Attempted to publish screenShare video stream with ended readyState, correlationId=${meeting.correlationId}`);
3997
4036
  } else {
3998
4037
  assert.calledOnceWithExactly(
3999
4038
  meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream,
@@ -6217,6 +6256,22 @@ describe('plugin-meetings', () => {
6217
6256
 
6218
6257
  assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
6219
6258
  });
6259
+
6260
+ it('handles meetingInfoProvider webinar need registration error', async () => {
6261
+ meeting.destination = FAKE_DESTINATION;
6262
+ meeting.destinationType = FAKE_TYPE;
6263
+ meeting.attrs.meetingInfoProvider = {
6264
+ fetchMeetingInfo: sinon
6265
+ .stub()
6266
+ .throws(new MeetingInfoV2WebinarRegistrationError(403021, FAKE_MEETING_INFO, 'a message')),
6267
+ };
6268
+
6269
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), WebinarRegistrationError);
6270
+
6271
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6272
+ assert.equal(meeting.meetingInfoFailureCode, 403021);
6273
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION);
6274
+ });
6220
6275
  });
6221
6276
 
6222
6277
  describe('#refreshPermissionToken', () => {
@@ -6927,33 +6982,36 @@ describe('plugin-meetings', () => {
6927
6982
  describe('#setCorrelationId', () => {
6928
6983
  it('should set the correlationId and return undefined', () => {
6929
6984
  assert.equal(meeting.correlationId, correlationId);
6930
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6985
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6931
6986
  meeting.setCorrelationId(uuid1);
6932
6987
  assert.equal(meeting.correlationId, uuid1);
6933
- assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1});
6988
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1, sessionCorrelationId: ''});
6934
6989
  });
6935
6990
  });
6936
6991
 
6937
6992
  describe('#updateCallStateForMetrics', () => {
6938
6993
  it('should update the callState, overriding existing values', () => {
6939
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6994
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6940
6995
  meeting.updateCallStateForMetrics({
6941
6996
  correlationId: uuid1,
6997
+ sessionCorrelationId: uuid3,
6942
6998
  joinTrigger: 'jt',
6943
6999
  loginType: 'lt',
6944
7000
  });
6945
7001
  assert.deepEqual(meeting.callStateForMetrics, {
6946
7002
  correlationId: uuid1,
7003
+ sessionCorrelationId: uuid3,
6947
7004
  joinTrigger: 'jt',
6948
7005
  loginType: 'lt',
6949
7006
  });
6950
7007
  });
6951
7008
 
6952
7009
  it('should update the callState, keeping non-supplied values', () => {
6953
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
7010
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6954
7011
  meeting.updateCallStateForMetrics({joinTrigger: 'jt', loginType: 'lt'});
6955
7012
  assert.deepEqual(meeting.callStateForMetrics, {
6956
7013
  correlationId,
7014
+ sessionCorrelationId: '',
6957
7015
  joinTrigger: 'jt',
6958
7016
  loginType: 'lt',
6959
7017
  });
@@ -9837,6 +9895,11 @@ describe('plugin-meetings', () => {
9837
9895
  requiredDisplayHints: [],
9838
9896
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
9839
9897
  },
9898
+ {
9899
+ actionName: 'canPollingAndQA',
9900
+ requiredDisplayHints: [],
9901
+ requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
9902
+ },
9840
9903
  ],
9841
9904
  ({
9842
9905
  actionName,
@@ -12137,6 +12200,43 @@ describe('plugin-meetings', () => {
12137
12200
  await testEmit(false);
12138
12201
  });
12139
12202
  });
12203
+
12204
+ describe('LOCAL_UNMUTE_REQUIRED locus event', () => {
12205
+ const testEmit = async (unmuteAllowed) => {
12206
+ meeting.audio = {
12207
+ handleServerLocalUnmuteRequired: sinon.stub(),
12208
+ }
12209
+ await meeting.locusInfo.emitScoped(
12210
+ {},
12211
+ LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED,
12212
+ {
12213
+ unmuteAllowed,
12214
+ }
12215
+ );
12216
+
12217
+ assert.calledWith(
12218
+ TriggerProxy.trigger,
12219
+ sinon.match.instanceOf(Meeting),
12220
+ {
12221
+ file: 'meeting/index',
12222
+ function: 'setUpLocusInfoSelfListener',
12223
+ },
12224
+ EVENT_TRIGGERS.MEETING_SELF_UNMUTED_BY_OTHERS,
12225
+ {
12226
+ payload: {
12227
+ unmuteAllowed,
12228
+ },
12229
+ }
12230
+ );
12231
+ assert.calledOnceWithExactly(meeting.audio.handleServerLocalUnmuteRequired, meeting, unmuteAllowed)
12232
+ };
12233
+
12234
+ [true, false].forEach((unmuteAllowed) => {
12235
+ it(`emits the expected event and calls handleServerLocalUnmuteRequired() when unmuteAllowed=${unmuteAllowed}`, async () => {
12236
+ await testEmit(unmuteAllowed);
12237
+ });
12238
+ });
12239
+ });
12140
12240
  });
12141
12241
  });
12142
12242
 
@@ -151,7 +151,7 @@ describe('plugin-meetings', () => {
151
151
  meeting.mediaProperties.audioStream.setServerMuted = sinon.stub().callsFake((muted) => {
152
152
  meeting.mediaProperties.audioStream.userMuted = muted;
153
153
  });
154
- audio.handleServerLocalUnmuteRequired(meeting);
154
+ audio.handleServerLocalUnmuteRequired(meeting, true);
155
155
 
156
156
  await testUtils.flushPromises();
157
157
 
@@ -161,6 +161,8 @@ describe('plugin-meetings', () => {
161
161
  false,
162
162
  'localUnmuteRequired'
163
163
  );
164
+ // and unmuteAllowed was updated
165
+ assert.calledWith(meeting.mediaProperties.audioStream.setUnmuteAllowed, true);
164
166
 
165
167
  // and local unmute was sent to server
166
168
  assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
@@ -184,7 +186,7 @@ describe('plugin-meetings', () => {
184
186
  meeting.mediaProperties.audioStream.setServerMuted = sinon.stub().callsFake((muted) => {
185
187
  meeting.mediaProperties.audioStream.userMuted = muted;
186
188
  });
187
- audio.handleServerLocalUnmuteRequired(meeting);
189
+ audio.handleServerLocalUnmuteRequired(meeting, true);
188
190
 
189
191
  await testUtils.flushPromises();
190
192
 
@@ -215,7 +217,7 @@ describe('plugin-meetings', () => {
215
217
  meeting.mediaProperties.videoStream.setServerMuted = sinon.stub().callsFake((muted) => {
216
218
  meeting.mediaProperties.videoStream.userMuted = muted;
217
219
  });
218
- video.handleServerLocalUnmuteRequired(meeting);
220
+ video.handleServerLocalUnmuteRequired(meeting, true);
219
221
 
220
222
  await testUtils.flushPromises();
221
223
 
@@ -225,6 +227,8 @@ describe('plugin-meetings', () => {
225
227
  false,
226
228
  'localUnmuteRequired'
227
229
  );
230
+ // and unmuteAllowed was updated
231
+ assert.calledWith(meeting.mediaProperties.videoStream.setUnmuteAllowed, true);
228
232
 
229
233
  // and local unmute was sent to server
230
234
  assert.calledOnce(MeetingUtil.remoteUpdateAudioVideo);
@@ -248,7 +252,7 @@ describe('plugin-meetings', () => {
248
252
  meeting.mediaProperties.videoStream.setServerMuted = sinon.stub().callsFake((muted) => {
249
253
  meeting.mediaProperties.videoStream.userMuted = muted;
250
254
  });
251
- video.handleServerLocalUnmuteRequired(meeting);
255
+ video.handleServerLocalUnmuteRequired(meeting, true);
252
256
 
253
257
  await testUtils.flushPromises();
254
258
 
@@ -378,24 +378,36 @@ describe('plugin-meetings', () => {
378
378
  });
379
379
 
380
380
  describe('joinMeeting', () => {
381
- it('#Should call `meetingRequest.joinMeeting', async () => {
382
- const meeting = {
381
+ const joinMeetingResponse = {
382
+ body: {
383
+ mediaConnections: [],
384
+ locus: {
385
+ url: 'differentLocusUrl',
386
+ self: {
387
+ id: 'selfId',
388
+ },
389
+ },
390
+ },
391
+ headers: {
392
+ trackingid: 'trackingId',
393
+ },
394
+ };
395
+ let meeting;
396
+
397
+ beforeEach(() => {
398
+ meeting = {
383
399
  meetingJoinUrl: 'meetingJoinUrl',
384
400
  locusUrl: 'locusUrl',
385
401
  meetingRequest: {
386
402
  joinMeeting: sinon.stub().returns(
387
- Promise.resolve({
388
- body: {mediaConnections: 'mediaConnections'},
389
- headers: {
390
- trackingid: 'trackingId',
391
- },
392
- })
393
- ),
403
+ Promise.resolve(joinMeetingResponse)),
394
404
  },
395
405
  getWebexObject: sinon.stub().returns(webex),
406
+ setLocus: sinon.stub(),
396
407
  };
408
+ });
397
409
 
398
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
410
+ it('#Should call `meetingRequest.joinMeeting', async () => {
399
411
  await MeetingUtil.joinMeeting(meeting, {
400
412
  reachability: 'reachability',
401
413
  roapMessage: 'roapMessage',
@@ -409,6 +421,10 @@ describe('plugin-meetings', () => {
409
421
  assert.equal(parameter.reachability, 'reachability');
410
422
  assert.equal(parameter.roapMessage, 'roapMessage');
411
423
 
424
+ assert.calledOnce(meeting.setLocus)
425
+ const setLocusParameter = meeting.setLocus.getCall(0).args[0];
426
+ assert.deepEqual(setLocusParameter, MeetingUtil.parseLocusJoin(joinMeetingResponse))
427
+
412
428
  assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
413
429
  name: 'client.locus.join.request',
414
430
  options: {meetingId: meeting.id},
@@ -424,21 +440,12 @@ describe('plugin-meetings', () => {
424
440
  },
425
441
  options: {
426
442
  meetingId: meeting.id,
427
- mediaConnections: 'mediaConnections',
443
+ mediaConnections: [],
428
444
  },
429
445
  });
430
- parseLocusJoinSpy.restore();
431
446
  });
432
447
 
433
448
  it('#Should call meetingRequest.joinMeeting with breakoutsSupported=true when passed in as true', async () => {
434
- const meeting = {
435
- meetingRequest: {
436
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
437
- },
438
- getWebexObject: sinon.stub().returns(webex),
439
- };
440
-
441
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
442
449
  await MeetingUtil.joinMeeting(meeting, {
443
450
  breakoutsSupported: true,
444
451
  });
@@ -447,18 +454,9 @@ describe('plugin-meetings', () => {
447
454
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
448
455
 
449
456
  assert.equal(parameter.breakoutsSupported, true);
450
- parseLocusJoinSpy.restore();
451
457
  });
452
458
 
453
459
  it('#Should call meetingRequest.joinMeeting with liveAnnotationSupported=true when passed in as true', async () => {
454
- const meeting = {
455
- meetingRequest: {
456
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
457
- },
458
- getWebexObject: sinon.stub().returns(webex),
459
- };
460
-
461
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
462
460
  await MeetingUtil.joinMeeting(meeting, {
463
461
  liveAnnotationSupported: true,
464
462
  });
@@ -467,18 +465,9 @@ describe('plugin-meetings', () => {
467
465
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
468
466
 
469
467
  assert.equal(parameter.liveAnnotationSupported, true);
470
- parseLocusJoinSpy.restore();
471
468
  });
472
469
 
473
470
  it('#Should call meetingRequest.joinMeeting with locale=en_UK, deviceCapabilities=["TEST"] when they are passed in as those values', async () => {
474
- const meeting = {
475
- meetingRequest: {
476
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
477
- },
478
- getWebexObject: sinon.stub().returns(webex),
479
- };
480
-
481
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
482
471
  await MeetingUtil.joinMeeting(meeting, {
483
472
  locale: 'en_UK',
484
473
  deviceCapabilities: ['TEST'],
@@ -489,21 +478,10 @@ describe('plugin-meetings', () => {
489
478
 
490
479
  assert.equal(parameter.locale, 'en_UK');
491
480
  assert.deepEqual(parameter.deviceCapabilities, ['TEST']);
492
- parseLocusJoinSpy.restore();
493
481
  });
494
482
 
495
483
  it('#Should call meetingRequest.joinMeeting with preferTranscoding=false when multistream is enabled', async () => {
496
- const meeting = {
497
- isMultistream: true,
498
- meetingJoinUrl: 'meetingJoinUrl',
499
- locusUrl: 'locusUrl',
500
- meetingRequest: {
501
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
502
- },
503
- getWebexObject: sinon.stub().returns(webex),
504
- };
505
-
506
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
484
+ meeting.isMultistream = true;
507
485
  await MeetingUtil.joinMeeting(meeting, {});
508
486
 
509
487
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
@@ -511,40 +489,22 @@ describe('plugin-meetings', () => {
511
489
 
512
490
  assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
513
491
  assert.equal(parameter.preferTranscoding, false);
514
- parseLocusJoinSpy.restore();
515
492
  });
516
493
 
517
494
  it('#Should fallback sipUrl if meetingJoinUrl does not exists', async () => {
518
- const meeting = {
519
- sipUri: 'sipUri',
520
- locusUrl: 'locusUrl',
521
- meetingRequest: {
522
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
523
- },
524
- getWebexObject: sinon.stub().returns(webex),
525
- };
526
-
527
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
495
+ meeting.meetingJoinUrl = undefined;
496
+ meeting.sipUri = 'sipUri';
528
497
  await MeetingUtil.joinMeeting(meeting, {});
529
498
 
530
499
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
531
500
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
532
501
 
533
502
  assert.equal(parameter.inviteeAddress, 'sipUri');
534
- parseLocusJoinSpy.restore();
535
503
  });
536
504
 
537
505
  it('#Should fallback to meetingNumber if meetingJoinUrl/sipUrl does not exists', async () => {
538
- const meeting = {
539
- meetingNumber: 'meetingNumber',
540
- locusUrl: 'locusUrl',
541
- meetingRequest: {
542
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
543
- },
544
- getWebexObject: sinon.stub().returns(webex),
545
- };
546
-
547
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
506
+ meeting.meetingJoinUrl = undefined;
507
+ meeting.meetingNumber = 'meetingNumber';
548
508
  await MeetingUtil.joinMeeting(meeting, {});
549
509
 
550
510
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
@@ -552,28 +512,18 @@ describe('plugin-meetings', () => {
552
512
 
553
513
  assert.isUndefined(parameter.inviteeAddress);
554
514
  assert.equal(parameter.meetingNumber, 'meetingNumber');
555
- parseLocusJoinSpy.restore();
556
515
  });
557
516
 
558
517
  it('should pass in the locusClusterUrl from meetingInfo', async () => {
559
- const meeting = {
560
- meetingInfo: {
561
- locusClusterUrl: 'locusClusterUrl',
562
- },
563
- meetingRequest: {
564
- joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
565
- },
566
- getWebexObject: sinon.stub().returns(webex),
518
+ meeting.meetingInfo = {
519
+ locusClusterUrl: 'locusClusterUrl',
567
520
  };
568
-
569
- const parseLocusJoinSpy = sinon.stub(MeetingUtil, 'parseLocusJoin');
570
521
  await MeetingUtil.joinMeeting(meeting, {});
571
522
 
572
523
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
573
524
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
574
525
 
575
526
  assert.equal(parameter.locusClusterUrl, 'locusClusterUrl');
576
- parseLocusJoinSpy.restore();
577
527
  });
578
528
  });
579
529
 
@@ -1063,7 +1013,21 @@ describe('plugin-meetings', () => {
1063
1013
  assert.equal(MeetingUtil.getIpVersion(webex), expectedOutput);
1064
1014
  });
1065
1015
 
1066
- it(`returns undefined when supportsIpV4=${supportsIpV4} and supportsIpV6=${supportsIpV6} and browser is firefox`, () => {
1016
+ it(`returns ${expectedOutput} when supportsIpV4=${supportsIpV4} and supportsIpV6=${supportsIpV6} for Firefox if config is enabled`, () => {
1017
+ sinon
1018
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
1019
+ .get(() => supportsIpV4);
1020
+ sinon
1021
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
1022
+ .get(() => supportsIpV6);
1023
+
1024
+ webex.config.meetings.backendIpv6NativeSupport = true;
1025
+ isBrowserStub.callsFake((name) => name === 'firefox');
1026
+
1027
+ assert.equal(MeetingUtil.getIpVersion(webex), expectedOutput);
1028
+ });
1029
+
1030
+ it(`returns undefined when supportsIpV4=${supportsIpV4} and supportsIpV6=${supportsIpV6}, config disabled and browser is firefox`, () => {
1067
1031
  sinon
1068
1032
  .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
1069
1033
  .get(() => supportsIpV4);
@@ -1071,6 +1035,7 @@ describe('plugin-meetings', () => {
1071
1035
  .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
1072
1036
  .get(() => supportsIpV6);
1073
1037
 
1038
+ webex.config.meetings.backendIpv6NativeSupport = false;
1074
1039
  isBrowserStub.callsFake((name) => name === 'firefox');
1075
1040
 
1076
1041
  assert.equal(MeetingUtil.getIpVersion(webex), undefined);
@@ -18,6 +18,7 @@ import MeetingInfo, {
18
18
  MeetingInfoV2CaptchaError,
19
19
  MeetingInfoV2AdhocMeetingError,
20
20
  MeetingInfoV2PolicyError,
21
+ MeetingInfoV2WebinarRegistrationError,
21
22
  } from '@webex/plugin-meetings/src/meeting-info/meeting-info-v2';
22
23
  import MeetingInfoUtil from '@webex/plugin-meetings/src/meeting-info/utilv2';
23
24
  import Metrics from '@webex/plugin-meetings/src/metrics';
@@ -888,6 +889,42 @@ describe('plugin-meetings', () => {
888
889
  });
889
890
  }
890
891
  );
892
+
893
+ forEach(
894
+ [
895
+ {errorCode: 403021},
896
+ {errorCode: 403022},
897
+ {errorCode: 403024},
898
+ ],
899
+ ({errorCode}) => {
900
+ it(`should throw a MeetingInfoV2WebinarRegistrationError for error code ${errorCode}`, async () => {
901
+ const message = 'a message';
902
+ const meetingInfoData = {meetingInfo: {registrationUrl: 'registrationUrl'}};
903
+
904
+ webex.request = sinon.stub().rejects({
905
+ statusCode: 403,
906
+ body: {message, code: errorCode, data: {meetingInfo: meetingInfoData}},
907
+ });
908
+ try {
909
+ await meetingInfo.createAdhocSpaceMeeting(conversationUrl, installedOrgID);
910
+ assert.fail('createAdhocSpaceMeeting should have thrown, but has not done that');
911
+ } catch (err) {
912
+ assert.instanceOf(err, MeetingInfoV2WebinarRegistrationError);
913
+ assert.deepEqual(err.message, `${message}, code=${errorCode}`);
914
+ assert.equal(err.wbxAppApiCode, errorCode);
915
+ assert.deepEqual(err.meetingInfo, meetingInfoData);
916
+
917
+ assert(Metrics.sendBehavioralMetric.calledOnce);
918
+ assert.calledWith(
919
+ Metrics.sendBehavioralMetric,
920
+ BEHAVIORAL_METRICS.WEBINAR_REGISTRATION_ERROR,
921
+ {code: errorCode}
922
+ );
923
+
924
+ }
925
+ });
926
+ }
927
+ );
891
928
  });
892
929
  });
893
930
  });