@webex/plugin-meetings 3.6.0-next.9 → 3.7.0-next.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 (118) 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/config.js +2 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/constants.js +24 -2
  7. package/dist/constants.js.map +1 -1
  8. package/dist/controls-options-manager/enums.js +1 -0
  9. package/dist/controls-options-manager/enums.js.map +1 -1
  10. package/dist/controls-options-manager/index.js +10 -3
  11. package/dist/controls-options-manager/index.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +12 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +28 -4
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/fullState.js +2 -1
  20. package/dist/locus-info/fullState.js.map +1 -1
  21. package/dist/locus-info/index.js +61 -3
  22. package/dist/locus-info/index.js.map +1 -1
  23. package/dist/meeting/in-meeting-actions.js +19 -1
  24. package/dist/meeting/in-meeting-actions.js.map +1 -1
  25. package/dist/meeting/index.js +564 -441
  26. package/dist/meeting/index.js.map +1 -1
  27. package/dist/meeting/locusMediaRequest.js +2 -6
  28. package/dist/meeting/locusMediaRequest.js.map +1 -1
  29. package/dist/meeting/request.js +21 -29
  30. package/dist/meeting/request.js.map +1 -1
  31. package/dist/meeting/util.js +94 -59
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meetings/index.js +2 -0
  34. package/dist/meetings/index.js.map +1 -1
  35. package/dist/members/index.js +3 -2
  36. package/dist/members/index.js.map +1 -1
  37. package/dist/members/util.js +9 -5
  38. package/dist/members/util.js.map +1 -1
  39. package/dist/reachability/clusterReachability.js +0 -4
  40. package/dist/reachability/clusterReachability.js.map +1 -1
  41. package/dist/reachability/index.js +433 -136
  42. package/dist/reachability/index.js.map +1 -1
  43. package/dist/reachability/reachability.types.js +7 -0
  44. package/dist/reachability/reachability.types.js.map +1 -0
  45. package/dist/reachability/request.js +23 -9
  46. package/dist/reachability/request.js.map +1 -1
  47. package/dist/roap/index.js +5 -7
  48. package/dist/roap/index.js.map +1 -1
  49. package/dist/roap/request.js +45 -79
  50. package/dist/roap/request.js.map +1 -1
  51. package/dist/roap/turnDiscovery.js +3 -6
  52. package/dist/roap/turnDiscovery.js.map +1 -1
  53. package/dist/types/config.d.ts +1 -0
  54. package/dist/types/constants.d.ts +19 -0
  55. package/dist/types/controls-options-manager/enums.d.ts +2 -1
  56. package/dist/types/controls-options-manager/index.d.ts +2 -1
  57. package/dist/types/controls-options-manager/types.d.ts +2 -0
  58. package/dist/types/locus-info/index.d.ts +9 -0
  59. package/dist/types/meeting/in-meeting-actions.d.ts +18 -0
  60. package/dist/types/meeting/index.d.ts +14 -3
  61. package/dist/types/meeting/locusMediaRequest.d.ts +2 -3
  62. package/dist/types/meeting/request.d.ts +2 -2
  63. package/dist/types/meeting/util.d.ts +2 -2
  64. package/dist/types/meetings/index.d.ts +1 -1
  65. package/dist/types/members/index.d.ts +2 -1
  66. package/dist/types/members/util.d.ts +3 -1
  67. package/dist/types/reachability/clusterReachability.d.ts +1 -10
  68. package/dist/types/reachability/index.d.ts +74 -35
  69. package/dist/types/reachability/reachability.types.d.ts +64 -0
  70. package/dist/types/reachability/request.d.ts +5 -1
  71. package/dist/types/roap/request.d.ts +1 -13
  72. package/dist/webinar/index.js +32 -19
  73. package/dist/webinar/index.js.map +1 -1
  74. package/package.json +22 -22
  75. package/src/config.ts +1 -0
  76. package/src/constants.ts +25 -0
  77. package/src/controls-options-manager/enums.ts +1 -0
  78. package/src/controls-options-manager/index.ts +19 -2
  79. package/src/controls-options-manager/types.ts +2 -0
  80. package/src/controls-options-manager/util.ts +12 -0
  81. package/src/locus-info/controlsUtils.ts +46 -2
  82. package/src/locus-info/fullState.ts +1 -0
  83. package/src/locus-info/index.ts +60 -0
  84. package/src/meeting/in-meeting-actions.ts +37 -0
  85. package/src/meeting/index.ts +114 -11
  86. package/src/meeting/locusMediaRequest.ts +4 -8
  87. package/src/meeting/request.ts +4 -11
  88. package/src/meeting/util.ts +24 -4
  89. package/src/meetings/index.ts +46 -39
  90. package/src/members/index.ts +4 -2
  91. package/src/members/util.ts +3 -1
  92. package/src/reachability/clusterReachability.ts +1 -14
  93. package/src/reachability/index.ts +285 -77
  94. package/src/reachability/reachability.types.ts +85 -0
  95. package/src/reachability/request.ts +55 -30
  96. package/src/roap/index.ts +4 -5
  97. package/src/roap/request.ts +30 -44
  98. package/src/roap/turnDiscovery.ts +2 -4
  99. package/src/webinar/index.ts +31 -17
  100. package/test/unit/spec/controls-options-manager/index.js +56 -32
  101. package/test/unit/spec/controls-options-manager/util.js +44 -0
  102. package/test/unit/spec/locus-info/controlsUtils.js +80 -4
  103. package/test/unit/spec/locus-info/index.js +59 -2
  104. package/test/unit/spec/meeting/in-meeting-actions.ts +18 -0
  105. package/test/unit/spec/meeting/index.js +231 -100
  106. package/test/unit/spec/meeting/locusMediaRequest.ts +18 -11
  107. package/test/unit/spec/meeting/request.js +3 -26
  108. package/test/unit/spec/meeting/utils.js +53 -13
  109. package/test/unit/spec/meetings/index.js +16 -1
  110. package/test/unit/spec/members/index.js +25 -2
  111. package/test/unit/spec/members/request.js +37 -3
  112. package/test/unit/spec/members/utils.js +15 -1
  113. package/test/unit/spec/reachability/index.ts +265 -1
  114. package/test/unit/spec/reachability/request.js +56 -15
  115. package/test/unit/spec/roap/index.ts +1 -1
  116. package/test/unit/spec/roap/request.ts +51 -109
  117. package/test/unit/spec/roap/turnDiscovery.ts +202 -147
  118. package/test/unit/spec/webinar/index.ts +82 -16
@@ -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(),
@@ -408,17 +414,39 @@ describe('plugin-meetings', () => {
408
414
  });
409
415
 
410
416
  it('#Should call `meetingRequest.joinMeeting', async () => {
417
+ meeting.isMultistream = true;
418
+
419
+ const FAKE_REACHABILITY_REPORT = {
420
+ id: 'fake reachability report',
421
+ };
422
+ const FAKE_CLIENT_MEDIA_PREFERENCES = {
423
+ id: 'fake client media preferences',
424
+ };
425
+
426
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.resolves(FAKE_REACHABILITY_REPORT);
427
+ webex.meetings.reachability.getClientMediaPreferences.resolves(FAKE_CLIENT_MEDIA_PREFERENCES);
428
+
429
+ sinon
430
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
431
+ .get(() => true);
432
+ sinon
433
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
434
+ .get(() => true);
435
+
411
436
  await MeetingUtil.joinMeeting(meeting, {
412
437
  reachability: 'reachability',
413
438
  roapMessage: 'roapMessage',
414
439
  });
415
440
 
441
+ assert.calledOnceWithExactly(webex.meetings.reachability.getReachabilityReportToAttachToRoap);
442
+ assert.calledOnceWithExactly(webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, IP_VERSION.ipv4_and_ipv6);
443
+
416
444
  assert.calledOnce(meeting.meetingRequest.joinMeeting);
417
445
  const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
418
446
 
419
447
  assert.equal(parameter.inviteeAddress, 'meetingJoinUrl');
420
- assert.equal(parameter.preferTranscoding, true);
421
- assert.equal(parameter.reachability, 'reachability');
448
+ assert.equal(parameter.reachability, FAKE_REACHABILITY_REPORT);
449
+ assert.equal(parameter.clientMediaPreferences, FAKE_CLIENT_MEDIA_PREFERENCES);
422
450
  assert.equal(parameter.roapMessage, 'roapMessage');
423
451
 
424
452
  assert.calledOnce(meeting.setLocus)
@@ -445,6 +473,29 @@ describe('plugin-meetings', () => {
445
473
  });
446
474
  });
447
475
 
476
+ it('should handle failed reachability report retrieval', async () => {
477
+ webex.meetings.reachability.getReachabilityReportToAttachToRoap.rejects(
478
+ new Error('fake error')
479
+ );
480
+ await MeetingUtil.joinMeeting(meeting, {});
481
+ // Verify meeting join still proceeds
482
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
483
+ });
484
+
485
+ it('should handle failed clientMediaPreferences retrieval', async () => {
486
+ webex.meetings.reachability.getClientMediaPreferences.rejects(new Error('fake error'));
487
+ meeting.isMultistream = true;
488
+ await MeetingUtil.joinMeeting(meeting, {});
489
+ // Verify meeting join still proceeds
490
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
491
+ const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
492
+ assert.deepEqual(parameter.clientMediaPreferences, {
493
+ preferTranscoding: false,
494
+ ipver: 0,
495
+ joinCookie: undefined,
496
+ });
497
+ });
498
+
448
499
  it('#Should call meetingRequest.joinMeeting with breakoutsSupported=true when passed in as true', async () => {
449
500
  await MeetingUtil.joinMeeting(meeting, {
450
501
  breakoutsSupported: true,
@@ -480,17 +531,6 @@ describe('plugin-meetings', () => {
480
531
  assert.deepEqual(parameter.deviceCapabilities, ['TEST']);
481
532
  });
482
533
 
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
534
  it('#Should fallback sipUrl if meetingJoinUrl does not exists', async () => {
495
535
  meeting.meetingJoinUrl = undefined;
496
536
  meeting.sipUri = 'sipUri';
@@ -2077,6 +2077,21 @@ describe('plugin-meetings', () => {
2077
2077
  ]);
2078
2078
  });
2079
2079
 
2080
+ it('should handle failure to get user information if scopes are insufficient', async () => {
2081
+ loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
2082
+ Object.assign(webex.people, {
2083
+ _getMe: sinon.stub().returns(Promise.reject()),
2084
+ });
2085
+
2086
+ await webex.meetings.fetchUserPreferredWebexSite();
2087
+
2088
+ assert.equal(webex.meetings.preferredWebexSite, '');
2089
+ assert.calledOnceWithExactly(
2090
+ loggerProxySpy,
2091
+ 'Failed to retrieve user information. No preferredWebexSite will be set'
2092
+ );
2093
+ });
2094
+
2080
2095
  const setup = ({me = { type: 'validuser'}, user} = {}) => {
2081
2096
  loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
2082
2097
  assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
@@ -2093,7 +2108,7 @@ describe('plugin-meetings', () => {
2093
2108
 
2094
2109
  Object.assign(webex.people, {
2095
2110
  _getMe: sinon.stub().returns(Promise.resolve(me)),
2096
- });
2111
+ });
2097
2112
  };
2098
2113
 
2099
2114
  it('should not call request.getMeetingPreferences if user is a guest', async () => {
@@ -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', () => {
@@ -1234,7 +1234,7 @@ describe('gatherReachability', () => {
1234
1234
  assert.equal(receivedEvents['done'], 1);
1235
1235
 
1236
1236
  // and that ip network detection was started
1237
- assert.calledOnceWithExactly(webex.internal.device.ipNetworkDetector.detect);
1237
+ assert.calledOnceWithExactly(webex.internal.device.ipNetworkDetector.detect, true);
1238
1238
 
1239
1239
  // finally, check the metrics - they should contain values from ipNetworkDetector
1240
1240
  assert.calledWith(Metrics.sendBehavioralMetric, 'js_sdk_reachability_completed', {
@@ -1664,6 +1664,270 @@ describe('gatherReachability', () => {
1664
1664
 
1665
1665
  assert.neverCalledWith(clusterReachabilityCtorStub);
1666
1666
  });
1667
+
1668
+ describe('fallback mechanism and multiple calls to getClusters', () => {
1669
+ let receivedEvents;
1670
+
1671
+ const mockGetClustersEmptyResult = {
1672
+ discoveryOptions: {
1673
+ ['early-call-min-clusters']: 0,
1674
+ ['report-version']: 1,
1675
+ },
1676
+ clusters: {}, // empty cluster list
1677
+ joinCookie: {id: 'cookie'},
1678
+ };
1679
+
1680
+ beforeEach(() => {
1681
+ webex.config.meetings.experimental = {
1682
+ enableTcpReachability: true,
1683
+ enableTlsReachability: true,
1684
+ };
1685
+
1686
+ receivedEvents = {
1687
+ done: 0,
1688
+ };
1689
+ });
1690
+
1691
+ it('keeps retrying if minimum required clusters are not reached', async () => {
1692
+ const reachability = new Reachability(webex);
1693
+
1694
+ reachability.on('reachability:done', () => {
1695
+ receivedEvents.done += 1;
1696
+ });
1697
+
1698
+ const mockGetClustersResult1 = {
1699
+ discoveryOptions: {
1700
+ ['early-call-min-clusters']: 2,
1701
+ ['report-version']: 1,
1702
+ },
1703
+ clusters: {
1704
+ clusterA0: {
1705
+ udp: ['udp-urlA'],
1706
+ tcp: ['tcp-urlA'],
1707
+ xtls: ['xtls-urlA'],
1708
+ isVideoMesh: false,
1709
+ },
1710
+ clusterB0: {
1711
+ udp: ['udp-urlB'],
1712
+ tcp: ['tcp-urlB'],
1713
+ xtls: ['xtls-urlB'],
1714
+ isVideoMesh: false,
1715
+ },
1716
+ },
1717
+ joinCookie: {id: 'cookie1'},
1718
+ };
1719
+ const mockGetClustersResult2 = {
1720
+ discoveryOptions: {
1721
+ ['early-call-min-clusters']: 2,
1722
+ ['report-version']: 1,
1723
+ },
1724
+ clusters: {
1725
+ clusterA1: {
1726
+ udp: ['udp-urlA'],
1727
+ tcp: ['tcp-urlA'],
1728
+ xtls: ['xtls-urlA'],
1729
+ isVideoMesh: false,
1730
+ },
1731
+ clusterB1: {
1732
+ udp: ['udp-urlB'],
1733
+ tcp: ['tcp-urlB'],
1734
+ xtls: ['xtls-urlB'],
1735
+ isVideoMesh: false,
1736
+ },
1737
+ },
1738
+ joinCookie: {id: 'cookie2'},
1739
+ };
1740
+ const mockGetClustersResult3 = {
1741
+ discoveryOptions: {
1742
+ ['early-call-min-clusters']: 1,
1743
+ ['report-version']: 1,
1744
+ },
1745
+ clusters: {
1746
+ clusterA2: {
1747
+ udp: ['udp-urlA'],
1748
+ tcp: ['tcp-urlA'],
1749
+ xtls: ['xtls-urlA'],
1750
+ isVideoMesh: false,
1751
+ },
1752
+ clusterB2: {
1753
+ udp: ['udp-urlB'],
1754
+ tcp: ['tcp-urlB'],
1755
+ xtls: ['xtls-urlB'],
1756
+ isVideoMesh: false,
1757
+ },
1758
+ },
1759
+ joinCookie: {id: 'cookie3'},
1760
+ };
1761
+
1762
+ reachability.reachabilityRequest.getClusters = sinon.stub();
1763
+ reachability.reachabilityRequest.getClusters.onCall(0).returns(mockGetClustersResult1);
1764
+ reachability.reachabilityRequest.getClusters.onCall(1).returns(mockGetClustersResult2);
1765
+
1766
+ reachability.reachabilityRequest.getClusters.onCall(2).returns(mockGetClustersResult3);
1767
+
1768
+ const resultPromise = reachability.gatherReachability('test');
1769
+
1770
+ await testUtils.flushPromises();
1771
+
1772
+ // trigger some mock result events from ClusterReachability instances,
1773
+ // but only from 1 cluster, so not enough to reach the minimum required
1774
+ mockClusterReachabilityInstances['clusterA0'].emitFakeResult('udp', {
1775
+ result: 'reachable',
1776
+ clientMediaIPs: ['1.2.3.4'],
1777
+ latencyInMilliseconds: 11,
1778
+ });
1779
+
1780
+ clock.tick(3000);
1781
+ await resultPromise;
1782
+ await testUtils.flushPromises();
1783
+
1784
+ // because the minimum was not reached, another call to getClusters should be made
1785
+ assert.calledTwice(reachability.reachabilityRequest.getClusters);
1786
+
1787
+ // simulate no results this time
1788
+
1789
+ // check that while the 2nd attempt is in progress, the join cookie is already available from the 2nd call to getClusters
1790
+ const clientMediaPreferences = await reachability.getClientMediaPreferences(
1791
+ true,
1792
+ IP_VERSION.unknown
1793
+ );
1794
+
1795
+ assert.deepEqual(clientMediaPreferences.joinCookie, mockGetClustersResult2.joinCookie);
1796
+
1797
+ clock.tick(3000);
1798
+ await testUtils.flushPromises();
1799
+
1800
+ assert.calledThrice(reachability.reachabilityRequest.getClusters);
1801
+
1802
+ await testUtils.flushPromises();
1803
+
1804
+ // this time 1 result will be enough to reach the minimum
1805
+ mockClusterReachabilityInstances['clusterA2'].emitFakeResult('udp', {
1806
+ result: 'reachable',
1807
+ clientMediaIPs: ['1.2.3.4'],
1808
+ latencyInMilliseconds: 11,
1809
+ });
1810
+ clock.tick(3000);
1811
+
1812
+ // the reachability results should include only results from the last attempt
1813
+ await checkResults(
1814
+ {
1815
+ clusterA2: {
1816
+ udp: {result: 'reachable', clientMediaIPs: ['1.2.3.4'], latencyInMilliseconds: 11},
1817
+ tcp: {result: 'unreachable'},
1818
+ xtls: {result: 'unreachable'},
1819
+ isVideoMesh: false,
1820
+ },
1821
+ clusterB2: {
1822
+ udp: {result: 'unreachable'},
1823
+ tcp: {result: 'unreachable'},
1824
+ xtls: {result: 'unreachable'},
1825
+ isVideoMesh: false,
1826
+ },
1827
+ },
1828
+ mockGetClustersResult3.joinCookie
1829
+ );
1830
+
1831
+ // wait some more time to make sure that there are no timers that fire from one of the previous checks
1832
+ clock.tick(20000);
1833
+
1834
+ // as the first 2 attempts failed and didn't reach the overall timeout, there should be only 1 done event emitted
1835
+ assert.equal(receivedEvents.done, 1);
1836
+ });
1837
+
1838
+ it('handles getClusters() returning empty list on 1st call', async () => {
1839
+ const reachability = new Reachability(webex);
1840
+
1841
+ reachability.on('reachability:done', () => {
1842
+ receivedEvents.done += 1;
1843
+ });
1844
+
1845
+ reachability.reachabilityRequest.getClusters = sinon
1846
+ .stub()
1847
+ .resolves(mockGetClustersEmptyResult);
1848
+
1849
+ const resultPromise = reachability.gatherReachability('test');
1850
+
1851
+ await testUtils.flushPromises();
1852
+
1853
+ clock.tick(3000);
1854
+ await resultPromise;
1855
+ await testUtils.flushPromises();
1856
+
1857
+ assert.calledOnce(reachability.reachabilityRequest.getClusters);
1858
+ reachability.reachabilityRequest.getClusters.resetHistory();
1859
+
1860
+ assert.equal(receivedEvents.done, 1);
1861
+ await checkResults({}, mockGetClustersEmptyResult.joinCookie);
1862
+
1863
+ // because we didn't actually test anything (we got empty cluster list from getClusters()), we should
1864
+ // not say that webex backend is unreachable
1865
+ assert.equal(await reachability.isWebexMediaBackendUnreachable(), false);
1866
+
1867
+ // wait to check that there are no other things happening
1868
+ clock.tick(20000);
1869
+ await testUtils.flushPromises();
1870
+
1871
+ assert.notCalled(reachability.reachabilityRequest.getClusters);
1872
+ assert.equal(receivedEvents.done, 1);
1873
+ });
1874
+
1875
+ it('handles getClusters() returning empty list on 2nd call', async () => {
1876
+ const reachability = new Reachability(webex);
1877
+
1878
+ reachability.on('reachability:done', () => {
1879
+ receivedEvents.done += 1;
1880
+ });
1881
+
1882
+ const mockGetClustersResult1 = {
1883
+ discoveryOptions: {
1884
+ ['early-call-min-clusters']: 2,
1885
+ ['report-version']: 1,
1886
+ },
1887
+ clusters: {
1888
+ clusterA0: {
1889
+ udp: ['udp-urlA'],
1890
+ tcp: ['tcp-urlA'],
1891
+ xtls: ['xtls-urlA'],
1892
+ isVideoMesh: false,
1893
+ },
1894
+ clusterB0: {
1895
+ udp: ['udp-urlB'],
1896
+ tcp: ['tcp-urlB'],
1897
+ xtls: ['xtls-urlB'],
1898
+ isVideoMesh: false,
1899
+ },
1900
+ },
1901
+ joinCookie: {id: 'cookie1'},
1902
+ };
1903
+
1904
+ reachability.reachabilityRequest.getClusters = sinon.stub();
1905
+ reachability.reachabilityRequest.getClusters.onCall(0).returns(mockGetClustersResult1);
1906
+ reachability.reachabilityRequest.getClusters.onCall(1).returns(mockGetClustersEmptyResult);
1907
+
1908
+ const resultPromise = reachability.gatherReachability('test');
1909
+
1910
+ await testUtils.flushPromises();
1911
+
1912
+ clock.tick(3000);
1913
+ await resultPromise;
1914
+ await testUtils.flushPromises();
1915
+
1916
+ // because the minimum was not reached, another call to getClusters should be made
1917
+ assert.calledTwice(reachability.reachabilityRequest.getClusters);
1918
+
1919
+ // the reachability results should include only results from the last attempt
1920
+ await checkResults({}, mockGetClustersEmptyResult.joinCookie);
1921
+
1922
+ // as the first 2 attempts failed and didn't reach the overall timeout, there should be only 1 done event emitted
1923
+ assert.equal(receivedEvents.done, 1);
1924
+ // because we didn't actually test anything (we got empty cluster list from getClusters()), we should
1925
+ // not say that webex backend is unreachable
1926
+ assert.equal(await reachability.isWebexMediaBackendUnreachable(), false);
1927
+ });
1928
+ });
1929
+
1930
+
1667
1931
  });
1668
1932
 
1669
1933
  describe('getReachabilityResults', () => {