@webex/plugin-meetings 3.0.0-stream-classes.3 → 3.0.0-stream-classes.5

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 (77) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +5 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/locus-info/index.js +25 -1
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/parser.js +5 -0
  10. package/dist/locus-info/parser.js.map +1 -1
  11. package/dist/meeting/index.js +72 -15
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meeting/locusMediaRequest.js +1 -1
  14. package/dist/meeting/locusMediaRequest.js.map +1 -1
  15. package/dist/meeting/request.js +24 -14
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +33 -3
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meetings/index.js +3 -4
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/metrics/constants.js +4 -1
  22. package/dist/metrics/constants.js.map +1 -1
  23. package/dist/multistream/remoteMediaManager.js +46 -9
  24. package/dist/multistream/remoteMediaManager.js.map +1 -1
  25. package/dist/reachability/index.js +2 -11
  26. package/dist/reachability/index.js.map +1 -1
  27. package/dist/reachability/request.js.map +1 -1
  28. package/dist/reconnection-manager/index.js +26 -22
  29. package/dist/reconnection-manager/index.js.map +1 -1
  30. package/dist/roap/index.js +5 -7
  31. package/dist/roap/index.js.map +1 -1
  32. package/dist/roap/request.js +1 -0
  33. package/dist/roap/request.js.map +1 -1
  34. package/dist/roap/turnDiscovery.js +3 -4
  35. package/dist/roap/turnDiscovery.js.map +1 -1
  36. package/dist/types/constants.d.ts +1 -0
  37. package/dist/types/meeting/index.d.ts +16 -2
  38. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  39. package/dist/types/meeting/request.d.ts +2 -1
  40. package/dist/types/meeting/util.d.ts +9 -1
  41. package/dist/types/metrics/constants.d.ts +3 -0
  42. package/dist/types/multistream/remoteMediaManager.d.ts +9 -1
  43. package/dist/types/reachability/index.d.ts +0 -6
  44. package/dist/types/reachability/request.d.ts +1 -1
  45. package/dist/types/roap/request.d.ts +2 -1
  46. package/package.json +1 -1
  47. package/src/constants.ts +3 -2
  48. package/src/locus-info/index.ts +33 -2
  49. package/src/locus-info/parser.ts +7 -0
  50. package/src/meeting/index.ts +59 -16
  51. package/src/meeting/locusMediaRequest.ts +2 -3
  52. package/src/meeting/request.ts +16 -6
  53. package/src/meeting/util.ts +36 -2
  54. package/src/meetings/index.ts +2 -2
  55. package/src/metrics/constants.ts +3 -0
  56. package/src/multistream/remoteMediaManager.ts +41 -4
  57. package/src/reachability/index.ts +4 -10
  58. package/src/reachability/request.ts +1 -1
  59. package/src/reconnection-manager/index.ts +13 -11
  60. package/src/roap/index.ts +2 -4
  61. package/src/roap/request.ts +2 -1
  62. package/src/roap/turnDiscovery.ts +2 -3
  63. package/test/integration/spec/converged-space-meetings.js +7 -7
  64. package/test/integration/spec/journey.js +85 -103
  65. package/test/integration/spec/space-meeting.js +9 -9
  66. package/test/unit/spec/locus-info/index.js +118 -1
  67. package/test/unit/spec/meeting/index.js +95 -30
  68. package/test/unit/spec/meeting/locusMediaRequest.ts +1 -2
  69. package/test/unit/spec/meeting/request.js +23 -0
  70. package/test/unit/spec/meeting/utils.js +73 -4
  71. package/test/unit/spec/meetings/index.js +68 -3
  72. package/test/unit/spec/multistream/remoteMediaManager.ts +10 -2
  73. package/test/unit/spec/reachability/index.ts +8 -8
  74. package/test/unit/spec/reconnection-manager/index.js +21 -0
  75. package/test/unit/spec/roap/index.ts +5 -1
  76. package/test/unit/spec/roap/turnDiscovery.ts +11 -4
  77. package/test/utils/integrationTestUtils.js +4 -4
@@ -10,6 +10,7 @@ import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
10
10
  import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
11
11
  import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
12
12
  import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
13
+ import Metrics from '@webex/plugin-meetings/src/metrics';
13
14
 
14
15
  import {
15
16
  LOCUSINFO,
@@ -24,7 +25,6 @@ import {
24
25
  } from '../../../../src/constants';
25
26
 
26
27
  import {self, selfWithInactivity} from './selfConstant';
27
- import uuid from 'uuid';
28
28
 
29
29
  describe('plugin-meetings', () => {
30
30
  describe('LocusInfo index', () => {
@@ -39,6 +39,7 @@ describe('plugin-meetings', () => {
39
39
  const locus = {};
40
40
  const meetingId = 'meetingId';
41
41
  let locusInfo;
42
+ let sendBehavioralMetricStub;
42
43
 
43
44
  const webex = new MockWebex({
44
45
  children: {
@@ -65,6 +66,12 @@ describe('plugin-meetings', () => {
65
66
  },
66
67
  },
67
68
  };
69
+
70
+ sendBehavioralMetricStub = sinon.stub(Metrics, 'sendBehavioralMetric');
71
+ });
72
+
73
+ afterEach(() => {
74
+ sinon.restore();
68
75
  });
69
76
 
70
77
  describe('#updateControls', () => {
@@ -1877,6 +1884,114 @@ describe('plugin-meetings', () => {
1877
1884
  });
1878
1885
  });
1879
1886
 
1887
+ describe('edge cases for sync failing', () => {
1888
+ const {DESYNC} = LocusDeltaParser.loci;
1889
+ const fakeFullLocusDto = {id: 'fake full locus dto'};
1890
+ let meeting;
1891
+
1892
+ beforeEach(() => {
1893
+ sinon.stub(locusInfo.locusParser, 'resume');
1894
+ sinon.stub(webex.meetings, 'destroy');
1895
+
1896
+ meeting = {
1897
+ meetingRequest: {
1898
+ getLocusDTO: sandbox.stub(),
1899
+ },
1900
+ locusInfo: {
1901
+ handleLocusDelta: sandbox.stub(),
1902
+ onFullLocus: sandbox.stub(),
1903
+ },
1904
+ locusUrl: 'fullSyncUrl',
1905
+ };
1906
+
1907
+ locusInfo.locusParser.workingCopy = {
1908
+ syncUrl: 'deltaSyncUrl',
1909
+ };
1910
+ });
1911
+
1912
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
1913
+ meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
1914
+
1915
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl
1916
+
1917
+ // Since we have a promise inside a function we want to test that's not returned,
1918
+ // we will wait and stub it's last function to resolve this waiting promise.
1919
+ return new Promise((resolve) => {
1920
+ webex.meetings.destroy.callsFake(() => resolve());
1921
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1922
+ }).then(() => {
1923
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
1924
+
1925
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1926
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1927
+ assert.notCalled(locusInfo.locusParser.resume);
1928
+
1929
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
1930
+ });
1931
+ });
1932
+
1933
+ it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
1934
+ meeting.meetingRequest.getLocusDTO.onCall(0).rejects(new Error('fake error'));
1935
+ meeting.meetingRequest.getLocusDTO.onCall(1).resolves({body: fakeFullLocusDto});
1936
+
1937
+ // Since we have a promise inside a function we want to test that's not returned,
1938
+ // we will wait and stub it's last function to resolve this waiting promise.
1939
+ return new Promise((resolve) => {
1940
+ locusInfo.locusParser.resume.callsFake(() => resolve());
1941
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1942
+ }).then(() => {
1943
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
1944
+
1945
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [{url: 'deltaSyncUrl'}]);
1946
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [{url: 'fullSyncUrl'}]);
1947
+
1948
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
1949
+ correlationId: meeting.correlationId,
1950
+ url: 'deltaSyncUrl',
1951
+ reason: 'fake error',
1952
+ errorName: 'Error',
1953
+ stack: sinon.match.any,
1954
+ code: sinon.match.any,
1955
+ });
1956
+
1957
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1958
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
1959
+ assert.calledOnce(locusInfo.locusParser.resume);
1960
+ });
1961
+ });
1962
+
1963
+ it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', () => {
1964
+ meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
1965
+
1966
+ // Since we have a promise inside a function we want to test that's not returned,
1967
+ // we will wait and stub it's last function to resolve this waiting promise.
1968
+ return new Promise((resolve) => {
1969
+ webex.meetings.destroy.callsFake(() => resolve());
1970
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
1971
+ }).then(() => {
1972
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
1973
+
1974
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [{url: 'deltaSyncUrl'}]);
1975
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [{url: 'fullSyncUrl'}]);
1976
+
1977
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
1978
+ correlationId: meeting.correlationId,
1979
+ url: 'deltaSyncUrl',
1980
+ reason: 'fake error',
1981
+ errorName: 'Error',
1982
+ stack: sinon.match.any,
1983
+ code: sinon.match.any,
1984
+ });
1985
+
1986
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
1987
+ assert.notCalled(meeting.locusInfo.onFullLocus);
1988
+ assert.notCalled(locusInfo.locusParser.resume);
1989
+
1990
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
1991
+ });
1992
+ });
1993
+ });
1994
+
1880
1995
  it('onDeltaLocus handle delta data', () => {
1881
1996
  fakeLocus.participants = {};
1882
1997
  const fakeBreakout = {
@@ -2420,6 +2535,8 @@ describe('plugin-meetings', () => {
2420
2535
  // send an out-of-order delta
2421
2536
  locusInfo.handleLocusDelta(oooDelta, mockMeeting);
2422
2537
 
2538
+ assert.calledOnceWithExactly(sendBehavioralMetricStub, 'js_sdk_locus_delta_ooo', { stack: sinon.match.any})
2539
+
2423
2540
  await clock.tickAsync(12499);
2424
2541
  await testUtils.flushPromises();
2425
2542
  assert.notCalled(syncRequestStub);
@@ -1406,9 +1406,10 @@ describe('plugin-meetings', () => {
1406
1406
  });
1407
1407
 
1408
1408
  it('should reject if waitForMediaConnectionConnected() rejects', async () => {
1409
- webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
1409
+ const FAKE_ERROR = {fatal: true};
1410
+ const getErrorPayloadForClientErrorCodeStub = webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
1410
1411
  .stub()
1411
- .returns({});
1412
+ .returns(FAKE_ERROR);
1412
1413
  meeting.meetingState = 'ACTIVE';
1413
1414
  meeting.mediaProperties.waitForMediaConnectionConnected.rejects(new Error('fake error'));
1414
1415
 
@@ -1422,14 +1423,14 @@ describe('plugin-meetings', () => {
1422
1423
  errorThrown = true;
1423
1424
  });
1424
1425
 
1426
+ assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {clientErrorCode: 2004});
1425
1427
  assert.calledTwice(webex.internal.newMetrics.submitClientEvent);
1426
-
1427
1428
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
1428
1429
  name: 'client.ice.end',
1429
1430
  payload: {
1430
1431
  canProceed: false,
1431
1432
  icePhase: 'JOIN_MEETING_FINAL',
1432
- errors: [{}],
1433
+ errors: [FAKE_ERROR],
1433
1434
  },
1434
1435
  options: {
1435
1436
  meetingId: meeting.id,
@@ -1674,6 +1675,8 @@ describe('plugin-meetings', () => {
1674
1675
  beforeEach(() => {
1675
1676
  clock = sinon.useFakeTimers();
1676
1677
 
1678
+ sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
1679
+
1677
1680
  meeting.deviceUrl = 'deviceUrl';
1678
1681
  meeting.config.deviceType = 'web';
1679
1682
  meeting.isMultistream = isMultistream;
@@ -1687,12 +1690,11 @@ describe('plugin-meetings', () => {
1687
1690
  meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
1688
1691
  meeting.webex.meetings.reachability = {
1689
1692
  isAnyClusterReachable: sinon.stub().resolves(true),
1690
- getIpVersion: () => IP_VERSION.unknown,
1691
1693
  };
1692
1694
  meeting.roap.doTurnDiscovery = sinon
1693
1695
  .stub()
1694
1696
  .resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
1695
-
1697
+
1696
1698
  StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
1697
1699
 
1698
1700
  // setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
@@ -1768,6 +1770,7 @@ describe('plugin-meetings', () => {
1768
1770
 
1769
1771
  afterEach(() => {
1770
1772
  clock.restore();
1773
+ sinon.restore();
1771
1774
  });
1772
1775
 
1773
1776
  // helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
@@ -1855,7 +1858,7 @@ describe('plugin-meetings', () => {
1855
1858
  ],
1856
1859
  clientMediaPreferences: {
1857
1860
  preferTranscoding: !meeting.isMultistream,
1858
- ipver: 0
1861
+ ipver: undefined
1859
1862
  },
1860
1863
  respOnlySdp: true,
1861
1864
  usingResource: null,
@@ -1936,7 +1939,7 @@ describe('plugin-meetings', () => {
1936
1939
  // and that it was the only /media request that was sent
1937
1940
  assert.calledOnce(locusMediaRequestStub);
1938
1941
  });
1939
-
1942
+
1940
1943
  it('addMedia() works correctly when media is enabled with streams to publish', async () => {
1941
1944
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
1942
1945
  await simulateRoapOffer();
@@ -2083,7 +2086,7 @@ describe('plugin-meetings', () => {
2083
2086
  } else {
2084
2087
  assert.notCalled(locusMediaRequestStub);
2085
2088
  }
2086
-
2089
+
2087
2090
  if (isMultistream) {
2088
2091
  assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream, fakeMicrophoneStream);
2089
2092
  } else {
@@ -4485,20 +4488,22 @@ describe('plugin-meetings', () => {
4485
4488
 
4486
4489
  describe('submitClientEvent on connectionFailed', () => {
4487
4490
  it('sends client.ice.end when connectionFailed on CONNECTION_STATE_CHANGED event', () => {
4488
- webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
4489
- sinon.stub().returns({});
4491
+ const FAKE_ERROR = {fatal: true};
4492
+ const getErrorPayloadForClientErrorCodeStub = webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode = sinon
4493
+ .stub()
4494
+ .returns(FAKE_ERROR);
4490
4495
  meeting.setupMediaConnectionListeners();
4491
4496
  eventListeners[Event.CONNECTION_STATE_CHANGED]({
4492
4497
  state: 'Failed',
4493
4498
  });
4499
+ assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {clientErrorCode: 2004});
4494
4500
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
4495
-
4496
4501
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
4497
4502
  name: 'client.ice.end',
4498
4503
  payload: {
4499
4504
  canProceed: false,
4500
4505
  icePhase: 'IN_MEETING',
4501
- errors: [{}],
4506
+ errors: [FAKE_ERROR],
4502
4507
  },
4503
4508
  options: {
4504
4509
  meetingId: meeting.id,
@@ -5557,29 +5562,40 @@ describe('plugin-meetings', () => {
5557
5562
  });
5558
5563
  });
5559
5564
 
5565
+ describe('#setPermissionTokenPayload', () => {
5566
+ it('sets correctly', () => {
5567
+ assert.notOk(meeting.permissionTokenPayload);
5568
+
5569
+ const permissionTokenPayloadData = {permission: {userPolicies: {a: true}}, exp: '1234'};
5570
+
5571
+ const jwtDecodeStub = sinon.stub(jwt, 'decode').returns(permissionTokenPayloadData);
5572
+
5573
+ meeting.setPermissionTokenPayload();
5574
+
5575
+ assert.calledOnce(jwtDecodeStub);
5576
+ assert.deepEqual(meeting.permissionTokenPayload, permissionTokenPayloadData);
5577
+ });
5578
+ });
5579
+
5560
5580
  describe('#setSelfUserPolicies', () => {
5561
5581
  it('sets correctly when policy data is present in token', () => {
5562
5582
  assert.notOk(meeting.selfUserPolicies);
5563
5583
 
5564
- const dummyToken = 'some data';
5565
5584
  const policyData = {permission: {userPolicies: {a: true}}};
5585
+ meeting.permissionTokenPayload = policyData;
5566
5586
 
5567
- sinon.stub(jwt, 'decode').returns(policyData);
5568
-
5569
- meeting.setSelfUserPolicies(dummyToken);
5587
+ meeting.setSelfUserPolicies();
5570
5588
 
5571
- assert.deepEqual(meeting.selfUserPolicies, {a: true});
5589
+ assert.deepEqual(meeting.selfUserPolicies, policyData.permission.userPolicies);
5572
5590
  });
5573
5591
 
5574
5592
  it('handles missing permission data', () => {
5575
5593
  assert.notOk(meeting.selfUserPolicies);
5576
5594
 
5577
- const dummyToken = 'some data';
5578
5595
  const policyData = {};
5596
+ meeting.permissionTokenPayload = policyData;
5579
5597
 
5580
- sinon.stub(jwt, 'decode').returns(policyData);
5581
-
5582
- meeting.setSelfUserPolicies(dummyToken);
5598
+ meeting.setSelfUserPolicies();
5583
5599
 
5584
5600
  assert.deepEqual(meeting.selfUserPolicies, undefined);
5585
5601
  });
@@ -5587,12 +5603,10 @@ describe('plugin-meetings', () => {
5587
5603
  it('handles missing policy data', () => {
5588
5604
  assert.notOk(meeting.selfUserPolicies);
5589
5605
 
5590
- const dummyToken = 'some data';
5591
5606
  const policyData = {permission: {}};
5607
+ meeting.permissionTokenPayload = policyData;
5592
5608
 
5593
- sinon.stub(jwt, 'decode').returns(policyData);
5594
-
5595
- meeting.setSelfUserPolicies(dummyToken);
5609
+ meeting.setSelfUserPolicies();
5596
5610
 
5597
5611
  assert.deepEqual(meeting.selfUserPolicies, undefined);
5598
5612
  });
@@ -5652,19 +5666,37 @@ describe('plugin-meetings', () => {
5652
5666
  assert.equal(meeting.owner, expectedInfoToParse.owner);
5653
5667
  assert.equal(meeting.permissionToken, expectedInfoToParse.permissionToken);
5654
5668
  assert.deepEqual(meeting.selfUserPolicies, expectedInfoToParse.selfUserPolicies);
5669
+
5670
+ if(expectedInfoToParse.permissionTokenPayload) {
5671
+ assert.deepEqual(meeting.permissionTokenPayload, expectedInfoToParse.permissionTokenPayload);
5672
+ }
5655
5673
  };
5656
5674
 
5657
5675
  it('should parse meeting info from api return when locus meeting object is not available, set values, and return null', () => {
5658
5676
  meeting.config.experimental = {enableMediaNegotiatedEvent: true};
5659
5677
  meeting.config.experimental.enableUnifiedMeetings = true;
5678
+
5679
+ const expectedPermissionTokenPayload = {
5680
+ exp: "123456",
5681
+ permission: {
5682
+ userPolicies: {
5683
+ a: true
5684
+ }
5685
+ }
5686
+ };
5687
+
5688
+ // generated permissionToken with secret `secret` and
5689
+ // value `JSON.stringify(expectedPermissionTokenPayload)`
5690
+ const permissionToken =
5691
+ 'eyJhbGciOiJIUzI1NiJ9.eyJleHAiOiIxMjM0NTYiLCJwZXJtaXNzaW9uIjp7InVzZXJQb2xpY2llcyI6eyJhIjp0cnVlfX19.wkTk0Hp8sUlq2wi2nP4-Ym4Xb7aEUHzyXA1kzk6f0V0';
5692
+
5660
5693
  const FAKE_MEETING_INFO = {
5661
5694
  body: {
5662
5695
  conversationUrl: uuid1,
5663
5696
  locusUrl: url1,
5664
5697
  meetingJoinUrl: url2,
5665
5698
  meetingNumber: '12345',
5666
- permissionToken:
5667
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtaXNzaW9uIjp7InVzZXJQb2xpY2llcyI6eyJhIjp0cnVlfX0sImlhdCI6MTY4OTE2NDEwMn0.9uL_U7QUdYyMerrgHC_gCKOax2j_bz04u8Ikbv9KiXU',
5699
+ permissionToken,
5668
5700
  sipMeetingUri: test1,
5669
5701
  sipUrl: test1,
5670
5702
  owner: test2,
@@ -5672,6 +5704,7 @@ describe('plugin-meetings', () => {
5672
5704
  };
5673
5705
 
5674
5706
  meeting.parseMeetingInfo(FAKE_MEETING_INFO);
5707
+
5675
5708
  const expectedInfoToParse = {
5676
5709
  conversationUrl: uuid1,
5677
5710
  locusUrl: url1,
@@ -5680,8 +5713,8 @@ describe('plugin-meetings', () => {
5680
5713
  meetingJoinUrl: url2,
5681
5714
  owner: test2,
5682
5715
  selfUserPolicies: {a: true},
5683
- permissionToken:
5684
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwZXJtaXNzaW9uIjp7InVzZXJQb2xpY2llcyI6eyJhIjp0cnVlfX0sImlhdCI6MTY4OTE2NDEwMn0.9uL_U7QUdYyMerrgHC_gCKOax2j_bz04u8Ikbv9KiXU',
5716
+ permissionToken,
5717
+ permissionTokenPayload: expectedPermissionTokenPayload
5685
5718
  };
5686
5719
 
5687
5720
  checkParseMeetingInfo(expectedInfoToParse);
@@ -7991,4 +8024,36 @@ describe('plugin-meetings', () => {
7991
8024
  });
7992
8025
  });
7993
8026
  });
8027
+
8028
+ describe('#getPermissionTokenTimeLeftInSec', () => {
8029
+ let now;
8030
+ let clock;
8031
+
8032
+ beforeEach(() => {
8033
+ now = Date.now();
8034
+
8035
+ // mock `new Date()` with constant `now`
8036
+ clock = sinon.useFakeTimers(now);
8037
+ });
8038
+
8039
+ afterEach(() => {
8040
+ clock.restore();
8041
+ })
8042
+
8043
+ it('should return undefined if exp is undefined', () => {
8044
+ assert.equal(meeting.getPermissionTokenTimeLeftInSec(), undefined)
8045
+ });
8046
+
8047
+ it('should return the expected positive exp', () => {
8048
+ // set permission token as now + 1 sec
8049
+ meeting.permissionTokenPayload = {exp: (now + 1000).toString()};
8050
+ assert.equal(meeting.getPermissionTokenTimeLeftInSec(), 1);
8051
+ });
8052
+
8053
+ it('should return the expected negative exp', () => {
8054
+ // set permission token as now - 1 sec
8055
+ meeting.permissionTokenPayload = {exp: (now - 1000).toString()};
8056
+ assert.equal(meeting.getPermissionTokenTimeLeftInSec(), -1);
8057
+ });
8058
+ });
7994
8059
  });
@@ -68,7 +68,6 @@ describe('LocusMediaRequest.send()', () => {
68
68
  mediaId: 'mediaId',
69
69
  selfUrl: 'fakeMeetingSelfUrl',
70
70
  muteOptions: {},
71
- ipVersion: IP_VERSION.only_ipv6
72
71
  };
73
72
 
74
73
  const createExpectedLocalMuteBody = (expectedMute:{audioMuted: boolean, videoMuted: boolean}, sequence = undefined) => {
@@ -89,7 +88,7 @@ describe('LocusMediaRequest.send()', () => {
89
88
  ],
90
89
  clientMediaPreferences: {
91
90
  preferTranscoding: true,
92
- ipver: 6,
91
+ ipver: undefined,
93
92
  },
94
93
  };
95
94
 
@@ -237,6 +237,29 @@ describe('plugin-meetings', () => {
237
237
  assert.equal(requestParams.body.invitee.address, 'sipUrl');
238
238
  });
239
239
 
240
+ it('sends uses the locusClusterUrl if available', async () => {
241
+ const deviceUrl = 'deviceUrl';
242
+ const correlationId = 'random-uuid';
243
+ const roapMessage = 'roap-message';
244
+ const inviteeAddress = 'sipUrl';
245
+ const locusClusterUrl = 'locusClusterUrl';
246
+
247
+ await meetingsRequest.joinMeeting({
248
+ deviceUrl,
249
+ correlationId,
250
+ roapMessage,
251
+ locusClusterUrl,
252
+ inviteeAddress,
253
+ });
254
+ const requestParams = meetingsRequest.request.getCall(0).args[0];
255
+
256
+ assert.equal(requestParams.method, 'POST');
257
+ assert.equal(
258
+ requestParams.uri,
259
+ 'https://locusClusterUrl/locus/api/v1/loci/call?alternateRedirect=true'
260
+ );
261
+ });
262
+
240
263
  it('adds deviceCapabilities to request when breakouts are supported', async () => {
241
264
  await meetingsRequest.joinMeeting({
242
265
  breakoutsSupported: true,
@@ -4,10 +4,9 @@ import Meetings from '@webex/plugin-meetings';
4
4
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
5
5
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
6
6
  import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
7
- import Metrics from '@webex/plugin-meetings/src/metrics/index';
8
- import {SELF_POLICY} from '@webex/plugin-meetings/src/constants';
9
- import {DISPLAY_HINTS} from '@webex/plugin-meetings/src/constants';
7
+ import {SELF_POLICY, IP_VERSION} from '@webex/plugin-meetings/src/constants';
10
8
  import MockWebex from '@webex/test-helper-mock-webex';
9
+ import * as BrowserDetectionModule from '@webex/plugin-meetings/src/common/browser-detection';
11
10
 
12
11
  describe('plugin-meetings', () => {
13
12
  let webex;
@@ -337,7 +336,6 @@ describe('plugin-meetings', () => {
337
336
  selfUrl: 'self url',
338
337
  sequence: {},
339
338
  type: 'LocalMute',
340
- ipVersion: 0,
341
339
  });
342
340
 
343
341
  assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
@@ -517,6 +515,26 @@ describe('plugin-meetings', () => {
517
515
  assert.isUndefined(parameter.inviteeAddress);
518
516
  assert.equal(parameter.meetingNumber, 'meetingNumber');
519
517
  });
518
+
519
+ it('should pass in the locusClusterUrl from meetingInfo', async () => {
520
+ const meeting = {
521
+ meetingInfo: {
522
+ locusClusterUrl: 'locusClusterUrl',
523
+ },
524
+ meetingRequest: {
525
+ joinMeeting: sinon.stub().returns(Promise.resolve({body: {}, headers: {}})),
526
+ },
527
+ getWebexObject: sinon.stub().returns(webex),
528
+ };
529
+
530
+ MeetingUtil.parseLocusJoin = sinon.stub();
531
+ await MeetingUtil.joinMeeting(meeting, {});
532
+
533
+ assert.calledOnce(meeting.meetingRequest.joinMeeting);
534
+ const parameter = meeting.meetingRequest.joinMeeting.getCall(0).args[0];
535
+
536
+ assert.equal(parameter.locusClusterUrl, 'locusClusterUrl');
537
+ });
520
538
  });
521
539
 
522
540
  describe('joinMeetingOptions', () => {
@@ -963,5 +981,56 @@ describe('plugin-meetings', () => {
963
981
  assert.equal(result, input);
964
982
  });
965
983
  });
984
+
985
+ describe('getIpVersion', () => {
986
+ let isBrowserStub;
987
+ beforeEach(() => {
988
+ isBrowserStub = sinon.stub().returns(false);
989
+
990
+ sinon.stub(BrowserDetectionModule, 'default').returns({
991
+ isBrowser: isBrowserStub,
992
+ });
993
+ });
994
+
995
+ afterEach(() => {
996
+ sinon.restore();
997
+ });
998
+
999
+ [
1000
+ {supportsIpV4: undefined, supportsIpV6: undefined, expectedOutput: IP_VERSION.unknown},
1001
+ {supportsIpV4: undefined, supportsIpV6: true, expectedOutput: IP_VERSION.only_ipv6},
1002
+ {supportsIpV4: undefined, supportsIpV6: false, expectedOutput: IP_VERSION.unknown},
1003
+ {supportsIpV4: true, supportsIpV6: undefined, expectedOutput: IP_VERSION.only_ipv4},
1004
+ {supportsIpV4: true, supportsIpV6: true, expectedOutput: IP_VERSION.ipv4_and_ipv6},
1005
+ {supportsIpV4: true, supportsIpV6: false, expectedOutput: IP_VERSION.only_ipv4},
1006
+ {supportsIpV4: false, supportsIpV6: undefined, expectedOutput: IP_VERSION.unknown},
1007
+ {supportsIpV4: false, supportsIpV6: true, expectedOutput: IP_VERSION.only_ipv6},
1008
+ {supportsIpV4: false, supportsIpV6: false, expectedOutput: IP_VERSION.unknown},
1009
+ ].forEach(({supportsIpV4, supportsIpV6, expectedOutput}) => {
1010
+ it(`returns ${expectedOutput} when supportsIpV4=${supportsIpV4} and supportsIpV6=${supportsIpV6}`, () => {
1011
+ sinon
1012
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
1013
+ .get(() => supportsIpV4);
1014
+ sinon
1015
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
1016
+ .get(() => supportsIpV6);
1017
+
1018
+ assert.equal(MeetingUtil.getIpVersion(webex), expectedOutput);
1019
+ });
1020
+
1021
+ it(`returns undefined when supportsIpV4=${supportsIpV4} and supportsIpV6=${supportsIpV6} and browser is firefox`, () => {
1022
+ sinon
1023
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV4')
1024
+ .get(() => supportsIpV4);
1025
+ sinon
1026
+ .stub(webex.internal.device.ipNetworkDetector, 'supportsIpV6')
1027
+ .get(() => supportsIpV6);
1028
+
1029
+ isBrowserStub.callsFake((name) => name === 'firefox');
1030
+
1031
+ assert.equal(MeetingUtil.getIpVersion(webex), undefined);
1032
+ });
1033
+ });
1034
+ });
966
1035
  });
967
1036
  });