@webex/plugin-meetings 3.6.0-next.9 → 3.7.0-ipv6-multi-turn-urls.1

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 (159) hide show
  1. package/README.md +2 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  5. package/dist/common/errors/join-webinar-error.js.map +1 -0
  6. package/dist/config.js +3 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +50 -7
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +1 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/index.js +10 -3
  13. package/dist/controls-options-manager/index.js.map +1 -1
  14. package/dist/controls-options-manager/types.js.map +1 -1
  15. package/dist/controls-options-manager/util.js +12 -0
  16. package/dist/controls-options-manager/util.js.map +1 -1
  17. package/dist/index.js +7 -7
  18. package/dist/index.js.map +1 -1
  19. package/dist/interpretation/index.js +1 -1
  20. package/dist/interpretation/siLanguage.js +1 -1
  21. package/dist/locus-info/controlsUtils.js +28 -4
  22. package/dist/locus-info/controlsUtils.js.map +1 -1
  23. package/dist/locus-info/fullState.js +2 -1
  24. package/dist/locus-info/fullState.js.map +1 -1
  25. package/dist/locus-info/index.js +61 -3
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/media/index.js +29 -1
  28. package/dist/media/index.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +29 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +692 -472
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/locusMediaRequest.js +2 -6
  34. package/dist/meeting/locusMediaRequest.js.map +1 -1
  35. package/dist/meeting/request.js +21 -29
  36. package/dist/meeting/request.js.map +1 -1
  37. package/dist/meeting/util.js +95 -59
  38. package/dist/meeting/util.js.map +1 -1
  39. package/dist/meeting-info/meeting-info-v2.js +29 -17
  40. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  41. package/dist/meetings/index.js +8 -3
  42. package/dist/meetings/index.js.map +1 -1
  43. package/dist/members/index.js +3 -2
  44. package/dist/members/index.js.map +1 -1
  45. package/dist/members/util.js +13 -7
  46. package/dist/members/util.js.map +1 -1
  47. package/dist/metrics/constants.js +3 -1
  48. package/dist/metrics/constants.js.map +1 -1
  49. package/dist/multistream/remoteMedia.js +30 -15
  50. package/dist/multistream/remoteMedia.js.map +1 -1
  51. package/dist/reachability/clusterReachability.js +12 -15
  52. package/dist/reachability/clusterReachability.js.map +1 -1
  53. package/dist/reachability/index.js +433 -136
  54. package/dist/reachability/index.js.map +1 -1
  55. package/dist/reachability/reachability.types.js +7 -0
  56. package/dist/reachability/reachability.types.js.map +1 -0
  57. package/dist/reachability/request.js +23 -9
  58. package/dist/reachability/request.js.map +1 -1
  59. package/dist/recording-controller/enums.js +8 -4
  60. package/dist/recording-controller/enums.js.map +1 -1
  61. package/dist/recording-controller/index.js +18 -9
  62. package/dist/recording-controller/index.js.map +1 -1
  63. package/dist/recording-controller/util.js +13 -9
  64. package/dist/recording-controller/util.js.map +1 -1
  65. package/dist/roap/index.js +5 -7
  66. package/dist/roap/index.js.map +1 -1
  67. package/dist/roap/request.js +45 -79
  68. package/dist/roap/request.js.map +1 -1
  69. package/dist/roap/turnDiscovery.js +3 -6
  70. package/dist/roap/turnDiscovery.js.map +1 -1
  71. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  72. package/dist/types/config.d.ts +2 -0
  73. package/dist/types/constants.d.ts +38 -1
  74. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  75. package/dist/types/controls-options-manager/index.d.ts +2 -1
  76. package/dist/types/controls-options-manager/types.d.ts +2 -0
  77. package/dist/types/index.d.ts +2 -2
  78. package/dist/types/locus-info/index.d.ts +9 -0
  79. package/dist/types/meeting/in-meeting-actions.d.ts +28 -0
  80. package/dist/types/meeting/index.d.ts +34 -3
  81. package/dist/types/meeting/locusMediaRequest.d.ts +2 -3
  82. package/dist/types/meeting/request.d.ts +2 -2
  83. package/dist/types/meeting/util.d.ts +2 -2
  84. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  85. package/dist/types/meetings/index.d.ts +4 -1
  86. package/dist/types/members/index.d.ts +2 -1
  87. package/dist/types/members/util.d.ts +5 -1
  88. package/dist/types/metrics/constants.d.ts +3 -1
  89. package/dist/types/reachability/clusterReachability.d.ts +1 -10
  90. package/dist/types/reachability/index.d.ts +74 -35
  91. package/dist/types/reachability/reachability.types.d.ts +64 -0
  92. package/dist/types/reachability/request.d.ts +5 -1
  93. package/dist/types/recording-controller/enums.d.ts +5 -2
  94. package/dist/types/recording-controller/index.d.ts +1 -0
  95. package/dist/types/recording-controller/util.d.ts +2 -1
  96. package/dist/types/roap/request.d.ts +1 -13
  97. package/dist/webinar/index.js +382 -19
  98. package/dist/webinar/index.js.map +1 -1
  99. package/package.json +22 -22
  100. package/src/common/errors/join-webinar-error.ts +24 -0
  101. package/src/config.ts +2 -0
  102. package/src/constants.ts +49 -3
  103. package/src/controls-options-manager/enums.ts +1 -0
  104. package/src/controls-options-manager/index.ts +19 -2
  105. package/src/controls-options-manager/types.ts +2 -0
  106. package/src/controls-options-manager/util.ts +12 -0
  107. package/src/index.ts +2 -2
  108. package/src/locus-info/controlsUtils.ts +46 -2
  109. package/src/locus-info/fullState.ts +1 -0
  110. package/src/locus-info/index.ts +60 -0
  111. package/src/media/index.ts +15 -0
  112. package/src/meeting/in-meeting-actions.ts +58 -0
  113. package/src/meeting/index.ts +232 -25
  114. package/src/meeting/locusMediaRequest.ts +4 -8
  115. package/src/meeting/request.ts +4 -11
  116. package/src/meeting/util.ts +25 -4
  117. package/src/meeting-info/meeting-info-v2.ts +23 -11
  118. package/src/meetings/index.ts +54 -41
  119. package/src/members/index.ts +4 -2
  120. package/src/members/util.ts +4 -1
  121. package/src/metrics/constants.ts +3 -1
  122. package/src/multistream/remoteMedia.ts +28 -15
  123. package/src/reachability/clusterReachability.ts +5 -15
  124. package/src/reachability/index.ts +285 -77
  125. package/src/reachability/reachability.types.ts +85 -0
  126. package/src/reachability/request.ts +55 -30
  127. package/src/recording-controller/enums.ts +5 -2
  128. package/src/recording-controller/index.ts +17 -4
  129. package/src/recording-controller/util.ts +20 -5
  130. package/src/roap/index.ts +4 -5
  131. package/src/roap/request.ts +30 -44
  132. package/src/roap/turnDiscovery.ts +2 -4
  133. package/src/webinar/index.ts +223 -17
  134. package/test/unit/spec/controls-options-manager/index.js +56 -32
  135. package/test/unit/spec/controls-options-manager/util.js +44 -0
  136. package/test/unit/spec/locus-info/controlsUtils.js +80 -4
  137. package/test/unit/spec/locus-info/index.js +59 -2
  138. package/test/unit/spec/meeting/in-meeting-actions.ts +31 -1
  139. package/test/unit/spec/meeting/index.js +369 -103
  140. package/test/unit/spec/meeting/locusMediaRequest.ts +18 -11
  141. package/test/unit/spec/meeting/request.js +3 -26
  142. package/test/unit/spec/meeting/utils.js +55 -13
  143. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  144. package/test/unit/spec/meetings/index.js +25 -6
  145. package/test/unit/spec/members/index.js +25 -2
  146. package/test/unit/spec/members/request.js +37 -3
  147. package/test/unit/spec/members/utils.js +110 -1
  148. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  149. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  150. package/test/unit/spec/reachability/index.ts +265 -1
  151. package/test/unit/spec/reachability/request.js +56 -15
  152. package/test/unit/spec/recording-controller/index.js +61 -5
  153. package/test/unit/spec/recording-controller/util.js +39 -3
  154. package/test/unit/spec/roap/index.ts +1 -1
  155. package/test/unit/spec/roap/request.ts +51 -109
  156. package/test/unit/spec/roap/turnDiscovery.ts +202 -147
  157. package/test/unit/spec/webinar/index.ts +443 -14
  158. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  159. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -34,12 +34,19 @@ describe('LocusMediaRequest.send()', () => {
34
34
  'wjfkm.wjfkm.*': {udp:{reachable: true}, tcp:{reachable:false}},
35
35
  '1eb65fdf-9643-417f-9974-ad72cae0e10f.59268c12-7a04-4b23-a1a1-4c74be03019a.*': {udp:{reachable: false}, tcp:{reachable:true}},
36
36
  },
37
- joinCookie: {
38
- anycastEntryPoint: 'aws-eu-west-1',
39
- clientIpAddress: 'some ip',
40
- timeShot: '2023-05-23T08:03:49Z',
41
- },
42
- ipVersion: IP_VERSION.only_ipv4,
37
+ clientMediaPreferences: {
38
+ preferTranscoding: false,
39
+ joinCookie: {
40
+ anycastEntryPoint: 'aws-eu-west-1',
41
+ clientIpAddress: 'some ip',
42
+ timeShot: '2023-05-23T08:03:49Z',
43
+ },
44
+ ipver: IP_VERSION.only_ipv4,
45
+ reachability: {
46
+ version: '1',
47
+ result: 'some fake reachability result',
48
+ }
49
+ }
43
50
  };
44
51
 
45
52
  const createExpectedRoapBody = (expectedMessageType, expectedMute:{audioMuted: boolean, videoMuted: boolean}) => {
@@ -53,12 +60,16 @@ describe('LocusMediaRequest.send()', () => {
53
60
  }
54
61
  ],
55
62
  clientMediaPreferences: {
56
- preferTranscoding: true,
63
+ preferTranscoding: false,
57
64
  ipver: 4,
58
65
  joinCookie: {
59
66
  anycastEntryPoint: 'aws-eu-west-1',
60
67
  clientIpAddress: 'some ip',
61
68
  timeShot: '2023-05-23T08:03:49Z'
69
+ },
70
+ reachability: {
71
+ version: '1',
72
+ result: 'some fake reachability result',
62
73
  }
63
74
  }
64
75
  };
@@ -87,10 +98,6 @@ describe('LocusMediaRequest.send()', () => {
87
98
  localSdp: `{"audioMuted":${expectedMute.audioMuted},"videoMuted":${expectedMute.videoMuted}}`,
88
99
  },
89
100
  ],
90
- clientMediaPreferences: {
91
- preferTranscoding: true,
92
- ipver: undefined,
93
- },
94
101
  };
95
102
 
96
103
  if (sequence) {
@@ -196,6 +196,7 @@ describe('plugin-meetings', () => {
196
196
  const permissionToken = 'permission-token';
197
197
  const installationId = 'installationId';
198
198
  const reachability = 'reachability';
199
+ const clientMediaPreferences = 'clientMediaPreferences';
199
200
 
200
201
  await meetingsRequest.joinMeeting({
201
202
  locusUrl,
@@ -204,6 +205,7 @@ describe('plugin-meetings', () => {
204
205
  roapMessage,
205
206
  reachability,
206
207
  permissionToken,
208
+ clientMediaPreferences
207
209
  });
208
210
  const requestParams = meetingsRequest.request.getCall(0).args[0];
209
211
 
@@ -214,6 +216,7 @@ describe('plugin-meetings', () => {
214
216
  assert.equal(requestParams.body.device.countryCode, 'US');
215
217
  assert.equal(requestParams.body.permissionToken, 'permission-token');
216
218
  assert.equal(requestParams.body.device.regionCode, 'WEST-COAST');
219
+ assert.equal(requestParams.body.clientMediaPreferences, 'clientMediaPreferences');
217
220
  assert.include(requestParams.body.device.localIp, '127.0.0');
218
221
  assert.deepEqual(requestParams.body.localMedias, [
219
222
  {localSdp: '{"roapMessage":"roap-message","reachability":"reachability"}'},
@@ -386,32 +389,6 @@ describe('plugin-meetings', () => {
386
389
 
387
390
  assert.deepEqual(requestParams.body.alias, undefined);
388
391
  });
389
-
390
- it('includes joinCookie and ipver correctly', async () => {
391
- const locusUrl = 'locusURL';
392
- const deviceUrl = 'deviceUrl';
393
- const correlationId = 'random-uuid';
394
- const roapMessage = 'roap-message';
395
- const permissionToken = 'permission-token';
396
-
397
- await meetingsRequest.joinMeeting({
398
- locusUrl,
399
- deviceUrl,
400
- correlationId,
401
- roapMessage,
402
- permissionToken,
403
- ipVersion: IP_VERSION.ipv4_and_ipv6,
404
- });
405
- const requestParams = meetingsRequest.request.getCall(0).args[0];
406
-
407
- assert.equal(requestParams.method, 'POST');
408
- assert.equal(requestParams.uri, `${locusUrl}/participant?alternateRedirect=true`);
409
- assert.deepEqual(requestParams.body.clientMediaPreferences, {
410
- joinCookie: {anycastEntryPoint: 'aws-eu-west-1'},
411
- preferTranscoding: true,
412
- ipver: 1,
413
- });
414
- });
415
392
  });
416
393
 
417
394
  describe('#pstn', () => {
@@ -22,6 +22,12 @@ describe('plugin-meetings', () => {
22
22
  meetings: Meetings,
23
23
  },
24
24
  });
25
+
26
+ webex.meetings.reachability = {
27
+ getReachabilityReportToAttachToRoap: sinon.stub().resolves({}),
28
+ getClientMediaPreferences: sinon.stub().resolves({}),
29
+ };
30
+
25
31
  const logger = {
26
32
  info: sandbox.stub(),
27
33
  log: sandbox.stub(),
@@ -39,6 +45,7 @@ describe('plugin-meetings', () => {
39
45
  meeting.cleanupLocalStreams = sinon.stub().returns(Promise.resolve());
40
46
  meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
41
47
  meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
48
+ meeting.stopPeriodicLogUpload = sinon.stub();
42
49
 
43
50
  meeting.unsetRemoteStreams = sinon.stub();
44
51
  meeting.unsetPeerConnections = sinon.stub();
@@ -64,6 +71,7 @@ describe('plugin-meetings', () => {
64
71
  assert.calledOnce(meeting.cleanupLocalStreams);
65
72
  assert.calledOnce(meeting.closeRemoteStreams);
66
73
  assert.calledOnce(meeting.closePeerConnections);
74
+ assert.calledOnce(meeting.stopPeriodicLogUpload);
67
75
 
68
76
  assert.calledOnce(meeting.unsetRemoteStreams);
69
77
  assert.calledOnce(meeting.unsetPeerConnections);
@@ -408,17 +416,39 @@ describe('plugin-meetings', () => {
408
416
  });
409
417
 
410
418
  it('#Should call `meetingRequest.joinMeeting', async () => {
419
+ meeting.isMultistream = true;
420
+
421
+ const FAKE_REACHABILITY_REPORT = {
422
+ id: 'fake reachability report',
423
+ };
424
+ const FAKE_CLIENT_MEDIA_PREFERENCES = {
425
+ id: 'fake client media preferences',
426
+ };
427
+
428
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.resolves(FAKE_REACHABILITY_REPORT);
429
+ webex.meetings.reachability.getClientMediaPreferences.resolves(FAKE_CLIENT_MEDIA_PREFERENCES);
430
+
431
+ sinon
432
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
433
+ .get(() => true);
434
+ sinon
435
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
436
+ .get(() => true);
437
+
411
438
  await MeetingUtil.joinMeeting(meeting, {
412
439
  reachability: 'reachability',
413
440
  roapMessage: 'roapMessage',
414
441
  });
415
442
 
443
+ assert.calledOnceWithExactly(webex.meetings.reachability.getReachabilityReportToAttachToRoap);
444
+ assert.calledOnceWithExactly(webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, IP_VERSION.ipv4_and_ipv6);
445
+
416
446
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
417
447
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
418
448
 
419
449
  assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
420
- assert.equal(parameter.preferTranscoding, true);
421
- assert.equal(parameter.reachability, 'reachability');
450
+ assert.equal(parameter.reachability, FAKE_REACHABILITY_REPORT);
451
+ assert.equal(parameter.clientMediaPreferences, FAKE_CLIENT_MEDIA_PREFERENCES);
422
452
  assert.equal(parameter.roapMessage, 'roapMessage');
423
453
 
424
454
  assert.calledOnce(meeting.setLocus)
@@ -445,6 +475,29 @@ describe('plugin-meetings', () => {
445
475
  });
446
476
  });
447
477
 
478
+ it('should handle failed reachability report retrieval', async () => {
479
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.rejects(
480
+ new Error('fake error')
481
+ );
482
+ await MeetingUtil.joinMeeting(meeting, {});
483
+ // Verify meeting join still proceeds
484
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
485
+ });
486
+
487
+ it('should handle failed clientMediaPreferences retrieval', async () => {
488
+ webex.meetings.reachability.getClientMediaPreferences.rejects(new Error('fake error'));
489
+ meeting.isMultistream = true;
490
+ await MeetingUtil.joinMeeting(meeting, {});
491
+ // Verify meeting join still proceeds
492
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
493
+ const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
494
+ assert.deepEqual(parameter.clientMediaPreferences, {
495
+ preferTranscoding: false,
496
+ ipver: 0,
497
+ joinCookie: undefined,
498
+ });
499
+ });
500
+
448
501
  it('#Should call meetingRequest.joinMeeting with breakoutsSupported=true when passed in as true', async () => {
449
502
  await MeetingUtil.joinMeeting(meeting, {
450
503
  breakoutsSupported: true,
@@ -480,17 +533,6 @@ describe('plugin-meetings', () => {
480
533
  assert.deepEqual(parameter.deviceCapabilities, ['TEST']);
481
534
  });
482
535
 
483
- it('#Should call meetingRequest.joinMeeting with preferTranscoding=false when multistream is enabled', async () => {
484
- meeting.isMultistream = true;
485
- await MeetingUtil.joinMeeting(meeting, {});
486
-
487
- assert.calledOnce(meeting.meetingRequest.joinMeeting);
488
- const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
489
-
490
- assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
491
- assert.equal(parameter.preferTranscoding, false);
492
- });
493
-
494
536
  it('#Should fallback sipUrl if meetingJoinUrl does not exists', async () => {
495
537
  meeting.meetingJoinUrl = undefined;
496
538
  meeting.sipUri = 'sipUri';
@@ -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
 
@@ -2077,7 +2081,22 @@ describe('plugin-meetings', () => {
2077
2081
  ]);
2078
2082
  });
2079
2083
 
2080
- const setup = ({me = { type: 'validuser'}, user} = {}) => {
2084
+ it('should handle failure to get user information if scopes are insufficient', async () => {
2085
+ loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
2086
+ Object.assign(webex.people, {
2087
+ _getMe: sinon.stub().returns(Promise.reject()),
2088
+ });
2089
+
2090
+ await webex.meetings.fetchUserPreferredWebexSite();
2091
+
2092
+ assert.equal(webex.meetings.preferredWebexSite, '');
2093
+ assert.calledOnceWithExactly(
2094
+ loggerProxySpy,
2095
+ 'Failed to retrieve user information. No preferredWebexSite will be set'
2096
+ );
2097
+ });
2098
+
2099
+ const setup = ({me = {type: 'validuser'}, user} = {}) => {
2081
2100
  loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
2082
2101
  assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
2083
2102
 
@@ -2093,14 +2112,14 @@ describe('plugin-meetings', () => {
2093
2112
 
2094
2113
  Object.assign(webex.people, {
2095
2114
  _getMe: sinon.stub().returns(Promise.resolve(me)),
2096
- });
2115
+ });
2097
2116
  };
2098
2117
 
2099
2118
  it('should not call request.getMeetingPreferences if user is a guest', async () => {
2100
2119
  setup({me: {type: 'appuser'}});
2101
-
2120
+
2102
2121
  await webex.meetings.fetchUserPreferredWebexSite();
2103
-
2122
+
2104
2123
  assert.equal(webex.meetings.preferredWebexSite, '');
2105
2124
  assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
2106
2125
  assert.notCalled(webex.internal.services.getMeetingPreferences);
@@ -660,17 +660,20 @@ describe('plugin-meetings', () => {
660
660
  resultPromise,
661
661
  spies,
662
662
  expectedRequestingMemberId,
663
- expectedLocusUrl
663
+ expectedLocusUrl,
664
+ expectedRoles,
664
665
  ) => {
665
666
  await assert.isFulfilled(resultPromise);
666
667
  assert.calledOnceWithExactly(
667
668
  spies.generateLowerAllHandsMemberOptions,
668
669
  expectedRequestingMemberId,
669
- expectedLocusUrl
670
+ expectedLocusUrl,
671
+ expectedRoles,
670
672
  );
671
673
  assert.calledOnceWithExactly(spies.lowerAllHandsMember, {
672
674
  requestingParticipantId: expectedRequestingMemberId,
673
675
  locusUrl: expectedLocusUrl,
676
+ ...(expectedRoles !== undefined && { roles: expectedRoles })
674
677
  });
675
678
  assert.strictEqual(resultPromise, spies.lowerAllHandsMember.getCall(0).returnValue);
676
679
  };
@@ -707,6 +710,26 @@ describe('plugin-meetings', () => {
707
710
 
708
711
  await checkValid(resultPromise, spies, requestingMemberId, url1);
709
712
  });
713
+
714
+ it('should make the correct request when called with valid requestingMemberId and roles', async () => {
715
+ const requestingMemberId = 'test-member-id';
716
+ const roles = ['panelist', 'attendee'];
717
+ const { members, spies } = setup('test-locus-url');
718
+
719
+ const resultPromise = members.lowerAllHands(requestingMemberId, roles);
720
+
721
+ await checkValid(resultPromise, spies, requestingMemberId, 'test-locus-url', roles);
722
+ });
723
+
724
+ it('should handle an empty roles array correctly', async () => {
725
+ const requestingMemberId = 'test-member-id';
726
+ const roles = [];
727
+ const { members, spies } = setup('test-locus-url');
728
+
729
+ const resultPromise = members.lowerAllHands(requestingMemberId, roles);
730
+
731
+ await checkValid(resultPromise, spies, requestingMemberId, 'test-locus-url', roles);
732
+ });
710
733
  });
711
734
 
712
735
  describe('#editDisplayName', () => {
@@ -225,7 +225,7 @@ describe('plugin-meetings', () => {
225
225
  });
226
226
 
227
227
  describe('#assignRolesMember', () => {
228
- it('sends a PATCH to the locus endpoint', async () => {
228
+ it('sends a assignRolesMember PATCH to the locus endpoint', async () => {
229
229
  const locusUrl = url1;
230
230
  const memberId = 'test1';
231
231
  const roles = [
@@ -255,7 +255,7 @@ describe('plugin-meetings', () => {
255
255
  });
256
256
 
257
257
  describe('#raiseHand', () => {
258
- it('sends a PATCH to the locus endpoint', async () => {
258
+ it('sends a raiseOrLowerHandMember PATCH to the locus endpoint', async () => {
259
259
  const locusUrl = url1;
260
260
  const memberId = 'test1';
261
261
 
@@ -319,7 +319,7 @@ describe('plugin-meetings', () => {
319
319
  assert.strictEqual(result, requestResponse);
320
320
  });
321
321
 
322
- it('sends a PATCH to the locus endpoint', async () => {
322
+ it('sends a lowerAllHandsMember PATCH to the locus endpoint', async () => {
323
323
  const locusUrl = url1;
324
324
  const memberId = 'test1';
325
325
 
@@ -348,6 +348,40 @@ describe('plugin-meetings', () => {
348
348
  },
349
349
  });
350
350
  });
351
+
352
+ it('sends a lowerAllHandsMember PATCH to the locus endpoint with roles', async () => {
353
+ const locusUrl = url1;
354
+ const memberId = 'test1';
355
+ const roles = ['attendee'];
356
+
357
+ const options = {
358
+ requestingParticipantId: memberId,
359
+ locusUrl,
360
+ roles,
361
+ };
362
+
363
+ const getRequestParamsSpy = sandbox.spy(membersUtil, 'getLowerAllHandsMemberRequestParams');
364
+
365
+ await membersRequest.lowerAllHandsMember(options);
366
+
367
+ assert.calledOnceWithExactly(getRequestParamsSpy, {
368
+ requestingParticipantId: memberId,
369
+ locusUrl: url1,
370
+ roles: ['attendee'],
371
+ });
372
+
373
+ checkRequest({
374
+ method: 'PATCH',
375
+ uri: `${locusUrl}/controls`,
376
+ body: {
377
+ hand: {
378
+ raised: false,
379
+ roles: ['attendee'],
380
+ },
381
+ requestingParticipantId: memberId,
382
+ },
383
+ });
384
+ });
351
385
  });
352
386
 
353
387
  describe('#editDisplayName', () => {
@@ -101,7 +101,7 @@ describe('plugin-meetings', () => {
101
101
  });
102
102
  });
103
103
  describe('#generateLowerAllHandsMemberOptions', () => {
104
- it('returns the correct options', () => {
104
+ it('returns the correct options without roles', () => {
105
105
  const requestingParticipantId = 'test';
106
106
  const locusUrl = 'urlTest1';
107
107
 
@@ -113,6 +113,20 @@ describe('plugin-meetings', () => {
113
113
  }
114
114
  );
115
115
  });
116
+ it('returns the correct options with roles', () => {
117
+ const requestingParticipantId = 'test';
118
+ const locusUrl = 'urlTest1';
119
+ const roles = ['panelist'];
120
+
121
+ assert.deepEqual(
122
+ MembersUtil.generateLowerAllHandsMemberOptions(requestingParticipantId, locusUrl, roles),
123
+ {
124
+ requestingParticipantId,
125
+ locusUrl,
126
+ roles,
127
+ }
128
+ );
129
+ });
116
130
  });
117
131
  describe('#generateEditDisplayNameMemberOptions', () => {
118
132
  it('returns the correct options', () => {
@@ -248,5 +262,100 @@ describe('plugin-meetings', () => {
248
262
  testParams(false);
249
263
  });
250
264
  });
265
+
266
+ describe('#getAddMemberBody', () => {
267
+ it('returns the correct body with email address and roles', () => {
268
+ const options = {
269
+ invitee: {
270
+ emailAddress: 'test@example.com',
271
+ roles: ['role1', 'role2'],
272
+ },
273
+ alertIfActive: true,
274
+ };
275
+
276
+ assert.deepEqual(MembersUtil.getAddMemberBody(options), {
277
+ invitees: [
278
+ {
279
+ address: 'test@example.com',
280
+ roles: ['role1', 'role2'],
281
+ },
282
+ ],
283
+ alertIfActive: true,
284
+ });
285
+ });
286
+
287
+ it('returns the correct body with phone number and no roles', () => {
288
+ const options = {
289
+ invitee: {
290
+ phoneNumber: '1234567890',
291
+ },
292
+ alertIfActive: false,
293
+ };
294
+
295
+ assert.deepEqual(MembersUtil.getAddMemberBody(options), {
296
+ invitees: [
297
+ {
298
+ address: '1234567890',
299
+ },
300
+ ],
301
+ alertIfActive: false,
302
+ });
303
+ });
304
+
305
+ it('returns the correct body with fallback to email', () => {
306
+ const options = {
307
+ invitee: {
308
+ email: 'fallback@example.com',
309
+ },
310
+ alertIfActive: true,
311
+ };
312
+
313
+ assert.deepEqual(MembersUtil.getAddMemberBody(options), {
314
+ invitees: [
315
+ {
316
+ address: 'fallback@example.com',
317
+ },
318
+ ],
319
+ alertIfActive: true,
320
+ });
321
+ });
322
+
323
+ it('handles missing `alertIfActive` gracefully', () => {
324
+ const options = {
325
+ invitee: {
326
+ emailAddress: 'test@example.com',
327
+ roles: ['role1'],
328
+ },
329
+ };
330
+
331
+ assert.deepEqual(MembersUtil.getAddMemberBody(options), {
332
+ invitees: [
333
+ {
334
+ address: 'test@example.com',
335
+ roles: ['role1'],
336
+ },
337
+ ],
338
+ alertIfActive: undefined,
339
+ });
340
+ });
341
+
342
+ it('ignores roles if not provided', () => {
343
+ const options = {
344
+ invitee: {
345
+ emailAddress: 'test@example.com',
346
+ },
347
+ alertIfActive: false,
348
+ };
349
+
350
+ assert.deepEqual(MembersUtil.getAddMemberBody(options), {
351
+ invitees: [
352
+ {
353
+ address: 'test@example.com',
354
+ },
355
+ ],
356
+ alertIfActive: false,
357
+ });
358
+ });
359
+ });
251
360
  });
252
361
  });
@@ -249,14 +249,18 @@ describe('RemoteMedia', () => {
249
249
 
250
250
  forEach(
251
251
  [
252
- {height: 134, fs: 60},
253
- {height: 135, fs: 240},
254
- {height: 269, fs: 240},
255
- {height: 270, fs: 920},
256
- {height: 539, fs: 920},
257
- {height: 540, fs: 3600},
252
+ {height: 90, fs: 60}, // 90p
253
+ {height: 98, fs: 60},
254
+ {height: 99, fs: 240}, // 180p
255
+ {height: 180, fs: 240},
256
+ {height: 197, fs: 240},
257
+ {height: 198, fs: 920}, // 360p
258
+ {height: 360, fs: 920},
259
+ {height: 395, fs: 920},
260
+ {height: 396, fs: 3600}, // 720p
258
261
  {height: 720, fs: 3600},
259
- {height: 721, fs: 8192},
262
+ {height: 721, fs: 8192}, // 1080p
263
+ {height: 1080, fs: 8192},
260
264
  ],
261
265
  ({height, fs}) => {
262
266
  it(`sets the max fs to ${fs} correctly when height is ${height}`, () => {
@@ -15,6 +15,7 @@ describe('ClusterReachability', () => {
15
15
  let previousRTCPeerConnection;
16
16
  let clusterReachability;
17
17
  let fakePeerConnection;
18
+ let gatherIceCandidatesSpy;
18
19
 
19
20
  const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData)[]> = {
20
21
  [Events.resultReady]: [],
@@ -44,6 +45,8 @@ describe('ClusterReachability', () => {
44
45
  xtls: ['stun:xtls1.webex.com', 'stun:xtls2.webex.com:443'],
45
46
  });
46
47
 
48
+ gatherIceCandidatesSpy = sinon.spy(clusterReachability, 'gatherIceCandidates');
49
+
47
50
  resetEmittedEvents();
48
51
 
49
52
  clusterReachability.on(Events.resultReady, (data: ResultEventData) => {
@@ -151,6 +154,10 @@ describe('ClusterReachability', () => {
151
154
  assert.calledOnceWithExactly(fakePeerConnection.createOffer, {offerToReceiveAudio: true});
152
155
  assert.calledOnce(fakePeerConnection.setLocalDescription);
153
156
 
157
+ // Make sure that gatherIceCandidates is called before setLocalDescription
158
+ // as setLocalDescription triggers the ICE gathering process
159
+ assert.isTrue(gatherIceCandidatesSpy.calledBefore(fakePeerConnection.setLocalDescription));
160
+
154
161
  clusterReachability.abort();
155
162
  await promise;
156
163