@webex/plugin-meetings 3.8.0-next.3 → 3.8.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 (98) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +4 -4
  8. package/dist/interpretation/index.js.map +1 -1
  9. package/dist/interpretation/siLanguage.js +1 -1
  10. package/dist/locus-info/controlsUtils.js +1 -1
  11. package/dist/locus-info/controlsUtils.js.map +1 -1
  12. package/dist/media/index.js +3 -15
  13. package/dist/media/index.js.map +1 -1
  14. package/dist/meeting/index.js +89 -5
  15. package/dist/meeting/index.js.map +1 -1
  16. package/dist/meeting/locusMediaRequest.js +21 -5
  17. package/dist/meeting/locusMediaRequest.js.map +1 -1
  18. package/dist/meeting/util.js +4 -1
  19. package/dist/meeting/util.js.map +1 -1
  20. package/dist/meeting-info/meeting-info-v2.js +359 -60
  21. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  22. package/dist/meetings/index.js +60 -1
  23. package/dist/meetings/index.js.map +1 -1
  24. package/dist/member/index.js +10 -0
  25. package/dist/member/index.js.map +1 -1
  26. package/dist/member/util.js +3 -0
  27. package/dist/member/util.js.map +1 -1
  28. package/dist/metrics/constants.js +9 -0
  29. package/dist/metrics/constants.js.map +1 -1
  30. package/dist/reachability/clusterReachability.js +52 -8
  31. package/dist/reachability/clusterReachability.js.map +1 -1
  32. package/dist/reachability/index.js +70 -45
  33. package/dist/reachability/index.js.map +1 -1
  34. package/dist/reachability/reachability.types.js +14 -0
  35. package/dist/reachability/reachability.types.js.map +1 -1
  36. package/dist/reachability/request.js +19 -3
  37. package/dist/reachability/request.js.map +1 -1
  38. package/dist/reconnection-manager/index.js +2 -2
  39. package/dist/reconnection-manager/index.js.map +1 -1
  40. package/dist/recording-controller/util.js +5 -5
  41. package/dist/recording-controller/util.js.map +1 -1
  42. package/dist/roap/index.js.map +1 -1
  43. package/dist/roap/turnDiscovery.js +31 -23
  44. package/dist/roap/turnDiscovery.js.map +1 -1
  45. package/dist/roap/types.js +17 -0
  46. package/dist/roap/types.js.map +1 -0
  47. package/dist/types/config.d.ts +1 -0
  48. package/dist/types/constants.d.ts +1 -0
  49. package/dist/types/meeting/index.d.ts +32 -1
  50. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  51. package/dist/types/meetings/index.d.ts +29 -0
  52. package/dist/types/member/index.d.ts +1 -0
  53. package/dist/types/metrics/constants.d.ts +9 -0
  54. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  55. package/dist/types/reachability/index.d.ts +2 -1
  56. package/dist/types/reachability/reachability.types.d.ts +5 -0
  57. package/dist/types/roap/index.d.ts +3 -2
  58. package/dist/types/roap/turnDiscovery.d.ts +1 -17
  59. package/dist/types/roap/types.d.ts +16 -0
  60. package/dist/webinar/index.js +1 -1
  61. package/package.json +22 -22
  62. package/src/config.ts +1 -0
  63. package/src/constants.ts +1 -0
  64. package/src/interpretation/index.ts +3 -3
  65. package/src/locus-info/controlsUtils.ts +2 -2
  66. package/src/media/index.ts +5 -21
  67. package/src/meeting/index.ts +91 -13
  68. package/src/meeting/locusMediaRequest.ts +27 -4
  69. package/src/meeting/util.ts +2 -1
  70. package/src/meeting-info/meeting-info-v2.ts +247 -6
  71. package/src/meetings/index.ts +72 -1
  72. package/src/member/index.ts +11 -0
  73. package/src/member/util.ts +3 -0
  74. package/src/metrics/constants.ts +9 -0
  75. package/src/reachability/clusterReachability.ts +47 -1
  76. package/src/reachability/index.ts +15 -0
  77. package/src/reachability/reachability.types.ts +6 -0
  78. package/src/reachability/request.ts +7 -0
  79. package/src/reconnection-manager/index.ts +2 -2
  80. package/src/recording-controller/util.ts +17 -13
  81. package/src/roap/index.ts +3 -7
  82. package/src/roap/turnDiscovery.ts +21 -35
  83. package/src/roap/types.ts +23 -0
  84. package/test/unit/spec/interpretation/index.ts +39 -1
  85. package/test/unit/spec/locus-info/controlsUtils.js +8 -0
  86. package/test/unit/spec/media/index.ts +6 -16
  87. package/test/unit/spec/meeting/index.js +212 -125
  88. package/test/unit/spec/meeting/locusMediaRequest.ts +96 -58
  89. package/test/unit/spec/meeting/utils.js +55 -0
  90. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  91. package/test/unit/spec/meetings/index.js +78 -1
  92. package/test/unit/spec/member/index.js +7 -0
  93. package/test/unit/spec/member/util.js +24 -0
  94. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  95. package/test/unit/spec/reachability/index.ts +12 -0
  96. package/test/unit/spec/reachability/request.js +47 -2
  97. package/test/unit/spec/reconnection-manager/index.js +4 -4
  98. package/test/unit/spec/roap/turnDiscovery.ts +72 -28
@@ -93,13 +93,14 @@ import CaptchaError from '../../../../src/common/errors/captcha-error';
93
93
  import PermissionError from '../../../../src/common/errors/permission';
94
94
  import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
95
95
  import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
96
- import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';;
96
+ import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';
97
97
  import testUtils from '../../../utils/testUtils';
98
98
  import {
99
99
  MeetingInfoV2CaptchaError,
100
100
  MeetingInfoV2PasswordError,
101
101
  MeetingInfoV2PolicyError,
102
- MeetingInfoV2JoinWebinarError, MeetingInfoV2JoinForbiddenError,
102
+ MeetingInfoV2JoinWebinarError,
103
+ MeetingInfoV2JoinForbiddenError,
103
104
  } from '../../../../src/meeting-info/meeting-info-v2';
104
105
  import {
105
106
  DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
@@ -115,7 +116,8 @@ import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
115
116
 
116
117
  import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
117
118
  import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
118
- import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
119
+ import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
120
+ import { EventEmitter } from 'stream';
119
121
 
120
122
  describe('plugin-meetings', () => {
121
123
  const logger = {
@@ -208,6 +210,8 @@ describe('plugin-meetings', () => {
208
210
  let membersSpy;
209
211
  let meetingRequestSpy;
210
212
  let correlationId;
213
+ let isoLocalClientMeetingJoinTime;
214
+ let uploadEvent;
211
215
 
212
216
  beforeEach(() => {
213
217
  webex = new MockWebex({
@@ -277,6 +281,8 @@ describe('plugin-meetings', () => {
277
281
  test4 = `test4-${uuid.v4()}`;
278
282
  testDestination = `testDestination-${uuid.v4()}`;
279
283
  correlationId = uuid.v4();
284
+ uploadEvent = new EventEmitter();
285
+ uploadEvent.addListener('progress', () => {})
280
286
 
281
287
  meeting = new Meeting(
282
288
  {
@@ -667,7 +673,7 @@ describe('plugin-meetings', () => {
667
673
  beforeEach(() => {
668
674
  meeting.join = sinon.stub().callsFake((joinOptions) => {
669
675
  meeting.isMultistream = joinOptions.enableMultistream;
670
- return Promise.resolve(fakeJoinResult)
676
+ return Promise.resolve(fakeJoinResult);
671
677
  });
672
678
  addMediaInternalStub = sinon
673
679
  .stub(meeting, 'addMediaInternal')
@@ -1070,7 +1076,11 @@ describe('plugin-meetings', () => {
1070
1076
  mediaOptions,
1071
1077
  });
1072
1078
 
1073
- assert.deepEqual(result, {join: fakeJoinResult, media: undefined, multistreamEnabled: false});
1079
+ assert.deepEqual(result, {
1080
+ join: fakeJoinResult,
1081
+ media: undefined,
1082
+ multistreamEnabled: false,
1083
+ });
1074
1084
 
1075
1085
  assert.calledOnce(meeting.join);
1076
1086
 
@@ -1174,7 +1184,10 @@ describe('plugin-meetings', () => {
1174
1184
  type: addMediaError.name,
1175
1185
  }
1176
1186
  );
1177
- assert.calledOnceWithExactly(meeting.leave, {resourceId: undefined, reason: 'joinWithMedia failure'})
1187
+ assert.calledOnceWithExactly(meeting.leave, {
1188
+ resourceId: undefined,
1189
+ reason: 'joinWithMedia failure',
1190
+ });
1178
1191
  });
1179
1192
  });
1180
1193
 
@@ -1680,10 +1693,6 @@ describe('plugin-meetings', () => {
1680
1693
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
1681
1694
  });
1682
1695
 
1683
- afterEach(() => {
1684
- assert.exists(meeting.isoLocalClientMeetingJoinTime);
1685
- });
1686
-
1687
1696
  it('should join the meeting and return promise', async () => {
1688
1697
  const join = meeting.join({pstnAudioType: 'dial-in'});
1689
1698
  meeting.config.enableAutomaticLLM = true;
@@ -2655,7 +2664,7 @@ describe('plugin-meetings', () => {
2655
2664
 
2656
2665
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
2657
2666
  turnServerInfo: {
2658
- url: FAKE_TURN_URL,
2667
+ urls: [FAKE_TURN_URL],
2659
2668
  username: FAKE_TURN_USER,
2660
2669
  password: FAKE_TURN_PASSWORD,
2661
2670
  },
@@ -2677,7 +2686,7 @@ describe('plugin-meetings', () => {
2677
2686
  meeting.id,
2678
2687
  sinon.match({
2679
2688
  turnServerInfo: {
2680
- url: FAKE_TURN_URL,
2689
+ urls: [FAKE_TURN_URL],
2681
2690
  username: FAKE_TURN_USER,
2682
2691
  password: FAKE_TURN_PASSWORD,
2683
2692
  },
@@ -2735,7 +2744,7 @@ describe('plugin-meetings', () => {
2735
2744
  .onSecondCall()
2736
2745
  .returns({
2737
2746
  turnServerInfo: {
2738
- url: FAKE_TURN_URL,
2747
+ urls: [FAKE_TURN_URL],
2739
2748
  username: FAKE_TURN_USER,
2740
2749
  password: FAKE_TURN_PASSWORD,
2741
2750
  },
@@ -2947,7 +2956,7 @@ describe('plugin-meetings', () => {
2947
2956
  .onSecondCall()
2948
2957
  .returns({
2949
2958
  turnServerInfo: {
2950
- url: FAKE_TURN_URL,
2959
+ urls: [FAKE_TURN_URL],
2951
2960
  username: FAKE_TURN_USER,
2952
2961
  password: FAKE_TURN_PASSWORD,
2953
2962
  },
@@ -3124,7 +3133,7 @@ describe('plugin-meetings', () => {
3124
3133
  .onSecondCall()
3125
3134
  .returns({
3126
3135
  turnServerInfo: {
3127
- url: FAKE_TURN_URL,
3136
+ urls: [FAKE_TURN_URL],
3128
3137
  username: FAKE_TURN_USER,
3129
3138
  password: FAKE_TURN_PASSWORD,
3130
3139
  },
@@ -3176,7 +3185,7 @@ describe('plugin-meetings', () => {
3176
3185
  .onSecondCall()
3177
3186
  .returns({
3178
3187
  turnServerInfo: {
3179
- url: FAKE_TURN_URL,
3188
+ urls: [FAKE_TURN_URL],
3180
3189
  username: FAKE_TURN_USER,
3181
3190
  password: FAKE_TURN_PASSWORD,
3182
3191
  },
@@ -3550,18 +3559,18 @@ describe('plugin-meetings', () => {
3550
3559
  it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
3551
3560
  let fakeMembersCollection = {
3552
3561
  members: {
3553
- member1: { isInMeeting: true },
3554
- member2: { isInMeeting: true },
3555
- member3: { isInMeeting: false },
3562
+ member1: {isInMeeting: true},
3563
+ member2: {isInMeeting: true},
3564
+ member3: {isInMeeting: false},
3556
3565
  },
3557
3566
  };
3558
- sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
3559
- const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
3567
+ sinon.stub(meeting, 'getMembers').returns({membersCollection: fakeMembersCollection});
3568
+ const fakeData = {intervalMetadata: {}, networkType: 'wifi'};
3560
3569
 
3561
3570
  statsAnalyzerStub.emit(
3562
- { file: 'test', function: 'test' },
3571
+ {file: 'test', function: 'test'},
3563
3572
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3564
- { data: fakeData }
3573
+ {data: fakeData}
3565
3574
  );
3566
3575
 
3567
3576
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3570,15 +3579,17 @@ describe('plugin-meetings', () => {
3570
3579
  meetingId: meeting.id,
3571
3580
  },
3572
3581
  payload: {
3573
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
3582
+ intervals: [
3583
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2)),
3584
+ ],
3574
3585
  },
3575
3586
  });
3576
3587
  fakeMembersCollection.members.member2.isInMeeting = false;
3577
3588
 
3578
3589
  statsAnalyzerStub.emit(
3579
- { file: 'test', function: 'test' },
3590
+ {file: 'test', function: 'test'},
3580
3591
  StatsAnalyzerEventNames.MEDIA_QUALITY,
3581
- { data: fakeData }
3592
+ {data: fakeData}
3582
3593
  );
3583
3594
 
3584
3595
  assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
@@ -3587,7 +3598,9 @@ describe('plugin-meetings', () => {
3587
3598
  meetingId: meeting.id,
3588
3599
  },
3589
3600
  payload: {
3590
- intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
3601
+ intervals: [
3602
+ sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1)),
3603
+ ],
3591
3604
  },
3592
3605
  });
3593
3606
  });
@@ -3624,7 +3637,7 @@ describe('plugin-meetings', () => {
3624
3637
 
3625
3638
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3626
3639
  turnServerInfo: {
3627
- url: FAKE_TURN_URL,
3640
+ urls: [FAKE_TURN_URL],
3628
3641
  username: FAKE_TURN_USER,
3629
3642
  password: FAKE_TURN_PASSWORD,
3630
3643
  },
@@ -3650,7 +3663,7 @@ describe('plugin-meetings', () => {
3650
3663
  meeting.id,
3651
3664
  sinon.match({
3652
3665
  turnServerInfo: {
3653
- url: FAKE_TURN_URL,
3666
+ urls: [FAKE_TURN_URL],
3654
3667
  username: FAKE_TURN_USER,
3655
3668
  password: FAKE_TURN_PASSWORD,
3656
3669
  },
@@ -3842,7 +3855,6 @@ describe('plugin-meetings', () => {
3842
3855
  });
3843
3856
 
3844
3857
  describe('when in a multistream meeting', () => {
3845
-
3846
3858
  beforeEach(() => {
3847
3859
  meeting.isMultistream = true;
3848
3860
  });
@@ -3853,7 +3865,7 @@ describe('plugin-meetings', () => {
3853
3865
  await brbResult;
3854
3866
  assert.exists(brbResult.then);
3855
3867
  assert.calledOnce(meeting.brbState.enable);
3856
- })
3868
+ });
3857
3869
 
3858
3870
  it('should disable #beRightBack and return a promise', async () => {
3859
3871
  const brbResult = meeting.beRightBack(false);
@@ -3861,7 +3873,7 @@ describe('plugin-meetings', () => {
3861
3873
  await brbResult;
3862
3874
  assert.exists(brbResult.then);
3863
3875
  assert.calledOnce(meeting.brbState.enable);
3864
- })
3876
+ });
3865
3877
 
3866
3878
  it('should throw an error and reject the promise if setBrb fails', async () => {
3867
3879
  const error = new Error('setBrb failed');
@@ -3874,7 +3886,7 @@ describe('plugin-meetings', () => {
3874
3886
  assert.equal(err.message, 'setBrb failed');
3875
3887
  assert.isRejected((Promise.reject()));
3876
3888
  }
3877
- })
3889
+ });
3878
3890
  });
3879
3891
  });
3880
3892
 
@@ -3928,7 +3940,7 @@ describe('plugin-meetings', () => {
3928
3940
  .resolves({id: 'fake clientMediaPreferences'});
3929
3941
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3930
3942
  turnServerInfo: {
3931
- url: 'turns:turn-server-url:443?transport=tcp',
3943
+ urls: ['turns:turn-server-url1:443?transport=tcp', 'turns:turn-server-url2:443?transport=tcp'],
3932
3944
  username: 'turn user',
3933
3945
  password: 'turn password',
3934
3946
  },
@@ -3946,12 +3958,7 @@ describe('plugin-meetings', () => {
3946
3958
  expectedMediaConnectionConfig = {
3947
3959
  iceServers: [
3948
3960
  {
3949
- urls: 'turn:turn-server-url:5004?transport=tcp',
3950
- username: 'turn user',
3951
- credential: 'turn password',
3952
- },
3953
- {
3954
- urls: 'turns:turn-server-url:443?transport=tcp',
3961
+ urls: ['turns:turn-server-url1:443?transport=tcp', 'turns:turn-server-url2:443?transport=tcp'],
3955
3962
  username: 'turn user',
3956
3963
  credential: 'turn password',
3957
3964
  },
@@ -4006,7 +4013,7 @@ describe('plugin-meetings', () => {
4006
4013
  initiateOffer: sinon.stub().resolves({}),
4007
4014
  update: sinon.stub().resolves({}),
4008
4015
  on: sinon.stub(),
4009
- roapMessageReceived: sinon.stub()
4016
+ roapMessageReceived: sinon.stub(),
4010
4017
  };
4011
4018
 
4012
4019
  fakeMultistreamRoapMediaConnection = {
@@ -4035,7 +4042,7 @@ describe('plugin-meetings', () => {
4035
4042
 
4036
4043
  locusMediaRequestStub = sinon
4037
4044
  .stub(WebexPlugin.prototype, 'request')
4038
- .resolves({body: {locus: {fullState: {}}}});
4045
+ .resolves({body: {locus: {fullState: {}}}, upload: sinon.match.instanceOf(EventEmitter), download: sinon.match.instanceOf(EventEmitter)});
4039
4046
 
4040
4047
  // setup some things and mocks so that the call to join() works
4041
4048
  // (we need to call join() because it creates the LocusMediaRequest instance
@@ -4144,6 +4151,8 @@ describe('plugin-meetings', () => {
4144
4151
  id: 'fake clientMediaPreferences',
4145
4152
  },
4146
4153
  },
4154
+ upload: sinon.match.instanceOf(EventEmitter),
4155
+ download: sinon.match.instanceOf(EventEmitter),
4147
4156
  });
4148
4157
  };
4149
4158
 
@@ -4171,6 +4180,8 @@ describe('plugin-meetings', () => {
4171
4180
  },
4172
4181
  ],
4173
4182
  },
4183
+ upload: sinon.match.instanceOf(EventEmitter),
4184
+ download: sinon.match.instanceOf(EventEmitter),
4174
4185
  });
4175
4186
  };
4176
4187
 
@@ -4195,6 +4206,8 @@ describe('plugin-meetings', () => {
4195
4206
  respOnlySdp: true,
4196
4207
  usingResource: null,
4197
4208
  },
4209
+ upload: sinon.match.instanceOf(EventEmitter),
4210
+ download: sinon.match.instanceOf(EventEmitter),
4198
4211
  });
4199
4212
  };
4200
4213
 
@@ -5213,7 +5226,7 @@ describe('plugin-meetings', () => {
5213
5226
  // and check that when we fallback to transcoded we still do another TURN discovery
5214
5227
  await runCheck(
5215
5228
  {
5216
- url: 'turns:turn-server-url:443?transport=tcp',
5229
+ urls: ['turns:turn-server-url1:443?transport=tcp', 'turns:turn-server-url2:443?transport=tcp'],
5217
5230
  username: 'turn user',
5218
5231
  password: 'turn password',
5219
5232
  },
@@ -5227,7 +5240,7 @@ describe('plugin-meetings', () => {
5227
5240
  // but doing it just for completeness
5228
5241
  await runCheck(
5229
5242
  {
5230
- url: 'turns:turn-server-url:443?transport=tcp',
5243
+ urls: ['turns:turn-server-url1:443?transport=tcp', 'turns:turn-server-url2:443?transport=tcp'],
5231
5244
  username: 'turn user',
5232
5245
  password: 'turn password',
5233
5246
  },
@@ -6337,7 +6350,10 @@ describe('plugin-meetings', () => {
6337
6350
  .throws(new MeetingInfoV2JoinForbiddenError(403003, FAKE_MEETING_INFO)),
6338
6351
  };
6339
6352
 
6340
- await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinForbiddenError);
6353
+ await assert.isRejected(
6354
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6355
+ JoinForbiddenError
6356
+ );
6341
6357
 
6342
6358
  assert.calledWith(
6343
6359
  meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
@@ -6353,10 +6369,7 @@ describe('plugin-meetings', () => {
6353
6369
 
6354
6370
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6355
6371
  assert.equal(meeting.meetingInfoFailureCode, 403003);
6356
- assert.equal(
6357
- meeting.meetingInfoFailureReason,
6358
- MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH
6359
- );
6372
+ assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH);
6360
6373
  assert.equal(meeting.requiredCaptcha, null);
6361
6374
  });
6362
6375
 
@@ -6733,15 +6746,10 @@ describe('plugin-meetings', () => {
6733
6746
  meeting.attrs.meetingInfoProvider = {
6734
6747
  fetchMeetingInfo: sinon
6735
6748
  .stub()
6736
- .throws(
6737
- new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6738
- ),
6749
+ .throws(new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')),
6739
6750
  };
6740
6751
 
6741
- await assert.isRejected(
6742
- meeting.fetchMeetingInfo({sendCAevents: true}),
6743
- JoinWebinarError
6744
- );
6752
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6745
6753
 
6746
6754
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6747
6755
  assert.equal(
@@ -6756,15 +6764,10 @@ describe('plugin-meetings', () => {
6756
6764
  meeting.attrs.meetingInfoProvider = {
6757
6765
  fetchMeetingInfo: sinon
6758
6766
  .stub()
6759
- .throws(
6760
- new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6761
- ),
6767
+ .throws(new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')),
6762
6768
  };
6763
6769
 
6764
- await assert.isRejected(
6765
- meeting.fetchMeetingInfo({sendCAevents: true}),
6766
- JoinWebinarError
6767
- );
6770
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6768
6771
 
6769
6772
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6770
6773
  assert.equal(
@@ -6779,15 +6782,10 @@ describe('plugin-meetings', () => {
6779
6782
  meeting.attrs.meetingInfoProvider = {
6780
6783
  fetchMeetingInfo: sinon
6781
6784
  .stub()
6782
- .throws(
6783
- new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6784
- ),
6785
+ .throws(new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')),
6785
6786
  };
6786
6787
 
6787
- await assert.isRejected(
6788
- meeting.fetchMeetingInfo({sendCAevents: true}),
6789
- JoinWebinarError
6790
- );
6788
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinWebinarError);
6791
6789
 
6792
6790
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6793
6791
  assert.equal(
@@ -7524,6 +7522,27 @@ describe('plugin-meetings', () => {
7524
7522
  });
7525
7523
  });
7526
7524
 
7525
+ describe('#setIsoLocalClientMeetingJoinTime', () => {
7526
+ it('should fallback to system clock ISO string when given an undefined value', () => {
7527
+ const currentSystemTime = new Date().toISOString();
7528
+ meeting.isoLocalClientMeetingJoinTime = undefined;
7529
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7530
+ });
7531
+
7532
+ it('should fallback to system clock ISO string when given an invalid value', () => {
7533
+ const currentSystemTime = new Date().toISOString();
7534
+ meeting.isoLocalClientMeetingJoinTime = 'invalid-date';
7535
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, currentSystemTime);
7536
+ });
7537
+
7538
+ it('should set the isoLocalClientMeetingJoinTime correctly for a valid date string', () => {
7539
+ const validDateString = 'Tue, 01 Apr 2025 13:00:36 GMT';
7540
+ const expectedISOString = new Date(validDateString).toISOString();
7541
+ meeting.isoLocalClientMeetingJoinTime = validDateString;
7542
+ assert.equal(meeting.isoLocalClientMeetingJoinTime, expectedISOString);
7543
+ });
7544
+ });
7545
+
7527
7546
  describe('#updateCallStateForMetrics', () => {
7528
7547
  it('should update the callState, overriding existing values', () => {
7529
7548
  assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
@@ -8590,13 +8609,19 @@ describe('plugin-meetings', () => {
8590
8609
  const fakeErrorMessage = 'test error';
8591
8610
  const fakeRootCauseName = 'root cause name';
8592
8611
  const fakeErrorName = 'test error name';
8612
+ let clock;
8593
8613
 
8594
8614
  beforeEach(() => {
8615
+ clock = sinon.useFakeTimers();
8595
8616
  meeting.setupMediaConnectionListeners();
8596
8617
  webex.internal.newMetrics.submitClientEvent.resetHistory();
8597
8618
  Metrics.sendBehavioralMetric.resetHistory();
8598
8619
  });
8599
8620
 
8621
+ afterEach(() => {
8622
+ clock.restore();
8623
+ });
8624
+
8600
8625
  const checkMetricSent = (event, error) => {
8601
8626
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
8602
8627
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
@@ -8665,6 +8690,13 @@ describe('plugin-meetings', () => {
8665
8690
  });
8666
8691
 
8667
8692
  it('should send metrics for SdpAnswerHandlingError error', () => {
8693
+ meeting.sdpResponseTimer = '1234';
8694
+ meeting.deferSDPAnswer = {
8695
+ reject: sinon.stub(),
8696
+ };
8697
+
8698
+ const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
8699
+
8668
8700
  const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
8669
8701
  name: fakeErrorName,
8670
8702
  cause: {name: fakeRootCauseName},
@@ -8679,6 +8711,8 @@ describe('plugin-meetings', () => {
8679
8711
  fakeErrorMessage,
8680
8712
  fakeRootCauseName
8681
8713
  );
8714
+ assert.calledOnce(meeting.deferSDPAnswer.reject);
8715
+ assert.calledOnce(clearTimeoutSpy);
8682
8716
  });
8683
8717
 
8684
8718
  it('should send metrics for SdpError error', () => {
@@ -9223,22 +9257,22 @@ describe('plugin-meetings', () => {
9223
9257
  const assertBrb = (enabled) => {
9224
9258
  meeting.brbState = createBrbState(meeting, false);
9225
9259
  meeting.locusInfo.emit(
9226
- { function: 'test', file: 'test' },
9260
+ {function: 'test', file: 'test'},
9227
9261
  LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
9228
- { brb: { enabled } },
9229
- )
9262
+ {brb: {enabled}}
9263
+ );
9230
9264
  assert.calledWithExactly(
9231
9265
  TriggerProxy.trigger,
9232
9266
  meeting,
9233
9267
  {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
9234
9268
  EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
9235
- { payload: { brb: { enabled } } },
9269
+ {payload: {brb: {enabled}}}
9236
9270
  );
9237
- }
9271
+ };
9238
9272
 
9239
9273
  assertBrb(true);
9240
9274
  assertBrb(false);
9241
- })
9275
+ });
9242
9276
 
9243
9277
  it('listens to the interpretation changed event', () => {
9244
9278
  meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
@@ -9922,6 +9956,22 @@ describe('plugin-meetings', () => {
9922
9956
  });
9923
9957
  });
9924
9958
 
9959
+ describe('#emailInput', () => {
9960
+ it('should set the email input', () => {
9961
+ assert.notOk(meeting.emailInput);
9962
+ meeting.emailInput = 'current';
9963
+ assert.equal(meeting.emailInput, 'current');
9964
+ });
9965
+ });
9966
+
9967
+ describe('#userNameInput', () => {
9968
+ it('should set the user name input', () => {
9969
+ assert.notOk(meeting.userNameInput);
9970
+ meeting.userNameInput = 'current';
9971
+ assert.equal(meeting.userNameInput, 'current');
9972
+ });
9973
+ });
9974
+
9925
9975
  describe('#setPermissionTokenPayload', () => {
9926
9976
  let now;
9927
9977
  let clock;
@@ -11326,18 +11376,21 @@ describe('plugin-meetings', () => {
11326
11376
  );
11327
11377
  });
11328
11378
 
11329
-
11330
11379
  it('connect ps data channel if ps started in webinar', async () => {
11331
11380
  meeting.joinedWith = {state: 'JOINED'};
11332
- meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
11381
+ meeting.locusInfo = {
11382
+ url: 'a url',
11383
+ info: {
11384
+ datachannelUrl: 'a datachannel url',
11385
+ practiceSessionDatachannelUrl: 'a ps datachannel url',
11386
+ },
11387
+ };
11333
11388
  meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
11334
11389
  await meeting.updateLLMConnection();
11335
11390
 
11336
11391
  assert.notCalled(webex.internal.llm.disconnectLLM);
11337
11392
  assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
11338
-
11339
11393
  });
11340
-
11341
11394
  });
11342
11395
 
11343
11396
  describe('#setLocus', () => {
@@ -11755,24 +11808,29 @@ describe('plugin-meetings', () => {
11755
11808
 
11756
11809
  activeSharingId.whiteboard = beneficiaryId;
11757
11810
 
11758
- eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11759
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11760
- functionName: 'remoteShare',
11761
- eventPayload: {
11762
- memberId: null,
11763
- url,
11764
- shareInstanceId,
11765
- annotationInfo: undefined,
11766
- resourceType: undefined,
11767
- },
11768
- } : {
11769
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11770
- functionName: 'startWhiteboardShare',
11771
- eventPayload: {resourceUrl, memberId: beneficiaryId},
11772
- });
11773
-
11774
- shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11811
+ eventTrigger.share.push(
11812
+ meeting.webinar.selfIsAttendee
11813
+ ? {
11814
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11815
+ functionName: 'remoteShare',
11816
+ eventPayload: {
11817
+ memberId: null,
11818
+ url,
11819
+ shareInstanceId,
11820
+ annotationInfo: undefined,
11821
+ resourceType: undefined,
11822
+ },
11823
+ }
11824
+ : {
11825
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11826
+ functionName: 'startWhiteboardShare',
11827
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
11828
+ }
11829
+ );
11775
11830
 
11831
+ shareStatus = meeting.webinar.selfIsAttendee
11832
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
11833
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11776
11834
  }
11777
11835
 
11778
11836
  if (eventTrigger.member) {
@@ -11804,24 +11862,29 @@ describe('plugin-meetings', () => {
11804
11862
  newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
11805
11863
  newPayload.current.content.beneficiaryId = otherBeneficiaryId;
11806
11864
 
11807
- eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
11808
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11809
- functionName: 'remoteShare',
11810
- eventPayload: {
11811
- memberId: null,
11812
- url,
11813
- shareInstanceId,
11814
- annotationInfo: undefined,
11815
- resourceType: undefined,
11816
- },
11817
- } : {
11818
- eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11819
- functionName: 'startWhiteboardShare',
11820
- eventPayload: {resourceUrl, memberId: beneficiaryId},
11821
- });
11822
-
11823
- shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11865
+ eventTrigger.share.push(
11866
+ meeting.webinar.selfIsAttendee
11867
+ ? {
11868
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
11869
+ functionName: 'remoteShare',
11870
+ eventPayload: {
11871
+ memberId: null,
11872
+ url,
11873
+ shareInstanceId,
11874
+ annotationInfo: undefined,
11875
+ resourceType: undefined,
11876
+ },
11877
+ }
11878
+ : {
11879
+ eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
11880
+ functionName: 'startWhiteboardShare',
11881
+ eventPayload: {resourceUrl, memberId: beneficiaryId},
11882
+ }
11883
+ );
11824
11884
 
11885
+ shareStatus = meeting.webinar.selfIsAttendee
11886
+ ? SHARE_STATUS.REMOTE_SHARE_ACTIVE
11887
+ : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
11825
11888
  } else {
11826
11889
  eventTrigger.share.push({
11827
11890
  eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
@@ -11951,24 +12014,24 @@ describe('plugin-meetings', () => {
11951
12014
  describe('Whiteboard Share - Webinar Attendee', () => {
11952
12015
  it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
11953
12016
  // Set the webinar attendee flag
11954
- meeting.webinar = { selfIsAttendee: true };
12017
+ meeting.webinar = {selfIsAttendee: true};
11955
12018
  meeting.locusInfo.info.isWebinar = true;
11956
12019
 
11957
12020
  // Step 1: Start sharing whiteboard A
11958
12021
  const data1 = generateData(
11959
- blankPayload, // Initial payload
11960
- true, // isGranting: Granting share
11961
- false, // isContent: Whiteboard (not content)
11962
- USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
12022
+ blankPayload, // Initial payload
12023
+ true, // isGranting: Granting share
12024
+ false, // isContent: Whiteboard (not content)
12025
+ USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
11963
12026
  RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
11964
12027
  );
11965
12028
 
11966
12029
  // Step 2: Stop sharing whiteboard A
11967
12030
  const data2 = generateData(
11968
- data1.payload, // Updated payload from Step 1
11969
- false, // isGranting: Stopping share
11970
- false, // isContent: Whiteboard
11971
- USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
12031
+ data1.payload, // Updated payload from Step 1
12032
+ false, // isGranting: Stopping share
12033
+ false, // isContent: Whiteboard
12034
+ USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
11972
12035
  );
11973
12036
 
11974
12037
  // Validate the payload changes and status updates
@@ -11979,7 +12042,6 @@ describe('plugin-meetings', () => {
11979
12042
  });
11980
12043
  });
11981
12044
 
11982
-
11983
12045
  describe('Whiteboard A --> Whiteboard B', () => {
11984
12046
  it('Scenario #1: you share both whiteboards', () => {
11985
12047
  const data1 = generateData(
@@ -12632,6 +12694,31 @@ describe('plugin-meetings', () => {
12632
12694
  });
12633
12695
  });
12634
12696
  });
12697
+
12698
+ describe('handleShareVideoStreamMuteStateChange', () => {
12699
+ it('should emit MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE event with correct fields', () => {
12700
+ meeting.isMultistream = true;
12701
+ meeting.statsAnalyzer = {shareVideoEncoderImplementation: 'OpenH264'};
12702
+ meeting.mediaProperties.shareVideoStream = {
12703
+ getSettings: sinon.stub().returns({displaySurface: 'monitor', frameRate: 30}),
12704
+ };
12705
+
12706
+ meeting.handleShareVideoStreamMuteStateChange(true);
12707
+
12708
+ assert.calledOnceWithExactly(
12709
+ Metrics.sendBehavioralMetric,
12710
+ BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE,
12711
+ {
12712
+ correlationId: meeting.correlationId,
12713
+ muted: true,
12714
+ encoderImplementation: 'OpenH264',
12715
+ displaySurface: 'monitor',
12716
+ isMultistream: true,
12717
+ frameRate: 30,
12718
+ }
12719
+ );
12720
+ });
12721
+ });
12635
12722
  });
12636
12723
 
12637
12724
  describe('#startKeepAlive', () => {
@@ -13290,7 +13377,7 @@ describe('plugin-meetings', () => {
13290
13377
  await meeting.roapMessageReceived(fakeMessage);
13291
13378
 
13292
13379
  assert.fail('Expected MultistreamNotSupportedError to be thrown');
13293
- } catch(e) {
13380
+ } catch (e) {
13294
13381
  assert.isTrue(e instanceof MultistreamNotSupportedError);
13295
13382
  }
13296
13383