@webex/plugin-meetings 3.11.0-next.2 → 3.11.0-next.21

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 (64) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/hashTree/hashTree.js +18 -0
  4. package/dist/hashTree/hashTree.js.map +1 -1
  5. package/dist/hashTree/hashTreeParser.js +307 -139
  6. package/dist/hashTree/hashTreeParser.js.map +1 -1
  7. package/dist/hashTree/types.js +2 -1
  8. package/dist/hashTree/types.js.map +1 -1
  9. package/dist/hashTree/utils.js +10 -0
  10. package/dist/hashTree/utils.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/locus-info/index.js +55 -42
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/media/MediaConnectionAwaiter.js +57 -1
  16. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  17. package/dist/media/properties.js +4 -2
  18. package/dist/media/properties.js.map +1 -1
  19. package/dist/meeting/index.js +33 -22
  20. package/dist/meeting/index.js.map +1 -1
  21. package/dist/meeting/util.js +108 -2
  22. package/dist/meeting/util.js.map +1 -1
  23. package/dist/meetings/index.js +76 -26
  24. package/dist/meetings/index.js.map +1 -1
  25. package/dist/metrics/constants.js +2 -1
  26. package/dist/metrics/constants.js.map +1 -1
  27. package/dist/multistream/mediaRequestManager.js +1 -1
  28. package/dist/multistream/mediaRequestManager.js.map +1 -1
  29. package/dist/reactions/reactions.type.js.map +1 -1
  30. package/dist/types/hashTree/hashTree.d.ts +7 -0
  31. package/dist/types/hashTree/hashTreeParser.d.ts +47 -12
  32. package/dist/types/hashTree/types.d.ts +1 -0
  33. package/dist/types/hashTree/utils.d.ts +6 -0
  34. package/dist/types/locus-info/index.d.ts +9 -2
  35. package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
  36. package/dist/types/media/properties.d.ts +2 -1
  37. package/dist/types/meeting/index.d.ts +8 -5
  38. package/dist/types/meeting/util.d.ts +28 -0
  39. package/dist/types/meetings/index.d.ts +3 -1
  40. package/dist/types/metrics/constants.d.ts +1 -0
  41. package/dist/types/reactions/reactions.type.d.ts +1 -0
  42. package/dist/webinar/index.js +1 -1
  43. package/package.json +22 -22
  44. package/src/hashTree/hashTree.ts +17 -0
  45. package/src/hashTree/hashTreeParser.ts +294 -96
  46. package/src/hashTree/types.ts +1 -0
  47. package/src/hashTree/utils.ts +9 -0
  48. package/src/locus-info/index.ts +83 -35
  49. package/src/media/MediaConnectionAwaiter.ts +41 -1
  50. package/src/media/properties.ts +3 -1
  51. package/src/meeting/index.ts +24 -11
  52. package/src/meeting/util.ts +132 -1
  53. package/src/meetings/index.ts +93 -8
  54. package/src/metrics/constants.ts +1 -0
  55. package/src/multistream/mediaRequestManager.ts +1 -1
  56. package/src/reactions/reactions.type.ts +1 -0
  57. package/test/unit/spec/hashTree/hashTree.ts +66 -0
  58. package/test/unit/spec/hashTree/hashTreeParser.ts +942 -110
  59. package/test/unit/spec/locus-info/index.js +88 -17
  60. package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
  61. package/test/unit/spec/media/properties.ts +12 -3
  62. package/test/unit/spec/meeting/index.js +160 -2
  63. package/test/unit/spec/meeting/utils.js +294 -22
  64. package/test/unit/spec/meetings/index.js +594 -17
@@ -61,8 +61,9 @@ describe('plugin-meetings', () => {
61
61
  meeting.trigger = sinon.stub();
62
62
  meeting.webex = webex;
63
63
  meeting.webex.internal.newMetrics.callDiagnosticMetrics =
64
- meeting.webex.internal.newMetrics.callDiagnosticMetrics || {};
65
- meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId = sinon.stub();
64
+ meeting.webex.internal.newMetrics.callDiagnosticMetrics || {};
65
+ meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId =
66
+ sinon.stub();
66
67
  });
67
68
 
68
69
  afterEach(() => {
@@ -245,7 +246,11 @@ describe('plugin-meetings', () => {
245
246
  const response = MeetingUtil.updateLocusFromApiResponse(meeting, originalResponse);
246
247
 
247
248
  assert.deepEqual(response, originalResponse);
248
- assert.calledOnceWithExactly(meeting.locusInfo.handleLocusAPIResponse, meeting, originalResponse.body);
249
+ assert.calledOnceWithExactly(
250
+ meeting.locusInfo.handleLocusAPIResponse,
251
+ meeting,
252
+ originalResponse.body
253
+ );
249
254
  });
250
255
 
251
256
  it('should handle locus being missing from the response', () => {
@@ -361,8 +366,8 @@ describe('plugin-meetings', () => {
361
366
  describe('remoteUpdateAudioVideo', () => {
362
367
  it('#Should call meetingRequest.locusMediaRequest with correct parameters and return the full response', async () => {
363
368
  const fakeResponse = {
364
- body: { locus: { url: 'locusUrl'}},
365
- headers: { },
369
+ body: {locus: {url: 'locusUrl'}},
370
+ headers: {},
366
371
  };
367
372
  const meeting = {
368
373
  id: 'meeting-id',
@@ -480,6 +485,11 @@ describe('plugin-meetings', () => {
480
485
  identifiers: {
481
486
  trackingId: 'trackingId',
482
487
  },
488
+ eventData: {
489
+ hasMismatchedSocket: false,
490
+ mercurySocketUrl: '',
491
+ deviceSocketUrl: 'ws://example.com',
492
+ },
483
493
  },
484
494
  options: {
485
495
  meetingId: meeting.id,
@@ -649,21 +659,26 @@ describe('plugin-meetings', () => {
649
659
  it('should post client event with error when join fails', async () => {
650
660
  const joinError = new Error('Join failed');
651
661
  meeting.meetingRequest.joinMeeting.rejects(joinError);
652
- meeting.meetingInfo = { meetingLookupUrl: 'test-lookup-url' };
662
+ meeting.meetingInfo = {meetingLookupUrl: 'test-lookup-url'};
653
663
 
654
664
  try {
655
665
  await MeetingUtil.joinMeeting(meeting, {});
656
666
  assert.fail('Expected joinMeeting to throw an error');
657
667
  } catch (error) {
658
668
  assert.equal(error, joinError);
659
-
669
+
660
670
  // Verify error client event was submitted
661
671
  assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
662
672
  name: 'client.locus.join.response',
663
673
  payload: {
664
- identifiers: { meetingLookupUrl: 'test-lookup-url' },
674
+ identifiers: {meetingLookupUrl: 'test-lookup-url'},
675
+ eventData: {
676
+ hasMismatchedSocket: false,
677
+ mercurySocketUrl: '',
678
+ deviceSocketUrl: 'ws://example.com',
679
+ },
665
680
  },
666
- options: { meetingId: meeting.id, rawError: joinError },
681
+ options: {meetingId: meeting.id, rawError: joinError},
667
682
  });
668
683
  }
669
684
  });
@@ -721,7 +736,7 @@ describe('plugin-meetings', () => {
721
736
  assert.fail('Expected joinMeetingOptions to throw PasswordError');
722
737
  } catch (error) {
723
738
  assert.instanceOf(error, PasswordError);
724
-
739
+
725
740
  // Verify client event was submitted with error details
726
741
  assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
727
742
  name: 'client.meetinginfo.response',
@@ -759,7 +774,7 @@ describe('plugin-meetings', () => {
759
774
  assert.fail('Expected joinMeetingOptions to throw CaptchaError');
760
775
  } catch (error) {
761
776
  assert.instanceOf(error, CaptchaError);
762
-
777
+
763
778
  // Verify client event was submitted with error details
764
779
  assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
765
780
  name: 'client.meetinginfo.response',
@@ -972,15 +987,18 @@ describe('plugin-meetings', () => {
972
987
  {functionName: 'canStartManualCaption', displayHint: 'MANUAL_CAPTION_START'},
973
988
  {functionName: 'canStopManualCaption', displayHint: 'MANUAL_CAPTION_STOP'},
974
989
 
975
- {functionName: 'isLocalRecordingStarted',displayHint:'LOCAL_RECORDING_STATUS_STARTED'},
990
+ {functionName: 'isLocalRecordingStarted', displayHint: 'LOCAL_RECORDING_STATUS_STARTED'},
976
991
  {functionName: 'isLocalRecordingStopped', displayHint: 'LOCAL_RECORDING_STATUS_STOPPED'},
977
992
  {functionName: 'isLocalRecordingPaused', displayHint: 'LOCAL_RECORDING_STATUS_PAUSED'},
978
- {functionName: 'isLocalStreamingStarted',displayHint:'STREAMING_STATUS_STARTED'},
993
+ {functionName: 'isLocalStreamingStarted', displayHint: 'STREAMING_STATUS_STARTED'},
979
994
  {functionName: 'isLocalStreamingStopped', displayHint: 'STREAMING_STATUS_STOPPED'},
980
995
 
981
996
  {functionName: 'isManualCaptionActive', displayHint: 'MANUAL_CAPTION_STATUS_ACTIVE'},
982
997
 
983
- {functionName: 'isSpokenLanguageAutoDetectionEnabled', displayHint: 'SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED'},
998
+ {
999
+ functionName: 'isSpokenLanguageAutoDetectionEnabled',
1000
+ displayHint: 'SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED',
1001
+ },
984
1002
 
985
1003
  {functionName: 'isWebexAssistantActive', displayHint: 'WEBEX_ASSISTANT_STATUS_ACTIVE'},
986
1004
  {functionName: 'canViewCaptionPanel', displayHint: 'ENABLE_CAPTION_PANEL'},
@@ -1446,11 +1464,9 @@ describe('plugin-meetings', () => {
1446
1464
  id: 'selfId123',
1447
1465
  },
1448
1466
  },
1467
+ metaData: {id: 'some hash tree metadata'},
1449
1468
  dataSets: [{name: 'dataset1', url: 'http://dataset.com'}],
1450
- mediaConnections: [
1451
- {mediaId: 'mediaId456'},
1452
- {someOtherField: 'value'},
1453
- ],
1469
+ mediaConnections: [{mediaId: 'mediaId456'}, {someOtherField: 'value'}],
1454
1470
  },
1455
1471
  };
1456
1472
  });
@@ -1466,6 +1482,7 @@ describe('plugin-meetings', () => {
1466
1482
  locusId: '12345',
1467
1483
  selfId: 'selfId123',
1468
1484
  mediaId: 'mediaId456',
1485
+ metadata: {id: 'some hash tree metadata'},
1469
1486
  });
1470
1487
  });
1471
1488
 
@@ -1495,20 +1512,275 @@ describe('plugin-meetings', () => {
1495
1512
  locusUrl: 'https://locus-a.wbx2.com/locus/api/v1/loci/12345',
1496
1513
  locusId: '12345',
1497
1514
  selfId: 'selfId123',
1515
+ metadata: {id: 'some hash tree metadata'},
1498
1516
  });
1499
1517
  assert.isUndefined(result.mediaId);
1500
1518
  });
1501
1519
 
1502
1520
  it('handles mediaConnections without mediaId', () => {
1503
- response.body.mediaConnections = [
1504
- {someField: 'value1'},
1505
- {anotherField: 'value2'},
1506
- ];
1521
+ response.body.mediaConnections = [{someField: 'value1'}, {anotherField: 'value2'}];
1507
1522
 
1508
1523
  const result = MeetingUtil.parseLocusJoin(response);
1509
1524
 
1510
1525
  assert.isUndefined(result.mediaId);
1511
1526
  });
1512
1527
  });
1528
+
1529
+ describe('#sanitizeWebSocketUrl', () => {
1530
+ it('extracts protocol, host, and pathname from URL', () => {
1531
+ const url = 'wss://example.com:443/mercury/path?token=secret&key=value#fragment';
1532
+ const result = MeetingUtil.sanitizeWebSocketUrl(url);
1533
+
1534
+ assert.equal(result, 'wss://example.com:443/mercury/path');
1535
+ });
1536
+
1537
+ it('handles URL without query string or hash', () => {
1538
+ const url = 'wss://example.com/path';
1539
+ const result = MeetingUtil.sanitizeWebSocketUrl(url);
1540
+
1541
+ assert.equal(result, 'wss://example.com/path');
1542
+ });
1543
+
1544
+ it('removes authentication from URL', () => {
1545
+ const url = 'wss://user:password@example.com/path?token=secret';
1546
+ const result = MeetingUtil.sanitizeWebSocketUrl(url);
1547
+
1548
+ assert.equal(result, 'wss://example.com/path');
1549
+ });
1550
+
1551
+ it('returns empty string for null or undefined', () => {
1552
+ assert.equal(MeetingUtil.sanitizeWebSocketUrl(null), '');
1553
+ assert.equal(MeetingUtil.sanitizeWebSocketUrl(undefined), '');
1554
+ });
1555
+
1556
+ it('returns empty string for non-string input', () => {
1557
+ assert.equal(MeetingUtil.sanitizeWebSocketUrl(123), '');
1558
+ assert.equal(MeetingUtil.sanitizeWebSocketUrl({}), '');
1559
+ });
1560
+
1561
+ it('returns empty string for invalid URL', () => {
1562
+ const result = MeetingUtil.sanitizeWebSocketUrl('not a valid url');
1563
+
1564
+ assert.equal(result, '');
1565
+ });
1566
+
1567
+ it('handles URL without pathname', () => {
1568
+ const url = 'wss://example.com?query=value';
1569
+ const result = MeetingUtil.sanitizeWebSocketUrl(url);
1570
+
1571
+ assert.equal(result, 'wss://example.com');
1572
+ });
1573
+ });
1574
+
1575
+ describe('#_urlsPartiallyMatch', () => {
1576
+ it('returns true when URLs match exactly (ignoring query and hash)', () => {
1577
+ const url1 = 'wss://example.com:443/path?token=abc#fragment1';
1578
+ const url2 = 'wss://example.com:443/path?token=xyz#fragment2';
1579
+
1580
+ assert.isTrue(MeetingUtil._urlsPartiallyMatch(url1, url2));
1581
+ });
1582
+
1583
+ it('returns true when one URL is proxied and ends with the other', () => {
1584
+ const url1 = 'wss://other.example.com/somepath/mercury.example.com/v1/path';
1585
+ const url2 = 'wss://mercury.example.com/v1/path';
1586
+
1587
+ assert.isTrue(MeetingUtil._urlsPartiallyMatch(url1, url2));
1588
+ });
1589
+
1590
+ it('returns true when the second URL is proxied', () => {
1591
+ const url1 = 'wss://mercury.example.com/v1/path';
1592
+ const url2 = 'wss://other.example.com/somepath/mercury.example.com/v1/path';
1593
+
1594
+ assert.isTrue(MeetingUtil._urlsPartiallyMatch(url1, url2));
1595
+ });
1596
+
1597
+ it('returns false when hosts differ and no partial match', () => {
1598
+ const url1 = 'wss://example1.com/path';
1599
+ const url2 = 'wss://example2.com/path';
1600
+
1601
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(url1, url2));
1602
+ });
1603
+
1604
+ it('returns false when pathnames differ and no partial match', () => {
1605
+ const url1 = 'wss://example.com/path1';
1606
+ const url2 = 'wss://example.com/path2';
1607
+
1608
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(url1, url2));
1609
+ });
1610
+
1611
+ it('returns false when either URL is null or undefined', () => {
1612
+ const url = 'wss://example.com/path';
1613
+
1614
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(null, url));
1615
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(url, null));
1616
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(undefined, url));
1617
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(url, undefined));
1618
+ });
1619
+
1620
+ it('returns false when both URLs are null', () => {
1621
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(null, null));
1622
+ });
1623
+
1624
+ it('returns false when URL parsing fails', () => {
1625
+ const url1 = 'invalid url';
1626
+ const url2 = 'wss://example.com/path';
1627
+
1628
+ assert.isFalse(MeetingUtil._urlsPartiallyMatch(url1, url2));
1629
+ });
1630
+ });
1631
+
1632
+ describe('#getSocketUrlInfo', () => {
1633
+ it('returns socket URL info when URLs differ', () => {
1634
+ const testWebex = {
1635
+ internal: {
1636
+ mercury: {
1637
+ socket: {
1638
+ url: 'wss://mercury.example.com:443/path?token=abc',
1639
+ },
1640
+ },
1641
+ device: {
1642
+ webSocketUrl: 'wss://device.example.com:443/path?token=xyz',
1643
+ },
1644
+ },
1645
+ };
1646
+
1647
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1648
+
1649
+ assert.isTrue(result.hasMismatchedSocket);
1650
+ assert.equal(result.mercurySocketUrl, 'wss://mercury.example.com:443/path');
1651
+ assert.equal(result.deviceSocketUrl, 'wss://device.example.com:443/path');
1652
+ });
1653
+
1654
+ it('returns socket URL info when URLs match', () => {
1655
+ const testWebex = {
1656
+ internal: {
1657
+ mercury: {
1658
+ socket: {
1659
+ url: 'wss://example.com:443/path?token=abc',
1660
+ },
1661
+ },
1662
+ device: {
1663
+ webSocketUrl: 'wss://example.com:443/path?token=xyz',
1664
+ },
1665
+ },
1666
+ };
1667
+
1668
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1669
+
1670
+ assert.isFalse(result.hasMismatchedSocket);
1671
+ assert.equal(result.mercurySocketUrl, 'wss://example.com:443/path');
1672
+ assert.equal(result.deviceSocketUrl, 'wss://example.com:443/path');
1673
+ });
1674
+
1675
+ it('returns hasMismatchedSocket as false when one URL is proxied (partial match)', () => {
1676
+ const testWebex = {
1677
+ internal: {
1678
+ mercury: {
1679
+ socket: {
1680
+ url: 'wss://other.example.com/somepath/mercury.example.com/v1/apps/wx2/registrations/00000000-0000-0000-0000-000000000000/messages',
1681
+ },
1682
+ },
1683
+ device: {
1684
+ webSocketUrl:
1685
+ 'wss://mercury.example.com/v1/apps/wx2/registrations/00000000-0000-0000-0000-000000000000/messages',
1686
+ },
1687
+ },
1688
+ };
1689
+
1690
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1691
+
1692
+ assert.isFalse(result.hasMismatchedSocket);
1693
+ assert.equal(
1694
+ result.mercurySocketUrl,
1695
+ 'wss://other.example.com/somepath/mercury.example.com/v1/apps/wx2/registrations/00000000-0000-0000-0000-000000000000/messages'
1696
+ );
1697
+ assert.equal(
1698
+ result.deviceSocketUrl,
1699
+ 'wss://mercury.example.com/v1/apps/wx2/registrations/00000000-0000-0000-0000-000000000000/messages'
1700
+ );
1701
+ });
1702
+
1703
+ it('returns false for hasMismatchedSocket when mercury socket URL is missing', () => {
1704
+ const testWebex = {
1705
+ internal: {
1706
+ mercury: {
1707
+ socket: {},
1708
+ },
1709
+ device: {
1710
+ webSocketUrl: 'wss://device.example.com:443/path',
1711
+ },
1712
+ },
1713
+ };
1714
+
1715
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1716
+
1717
+ assert.isFalse(result.hasMismatchedSocket);
1718
+ assert.equal(result.mercurySocketUrl, '');
1719
+ assert.equal(result.deviceSocketUrl, 'wss://device.example.com:443/path');
1720
+ });
1721
+
1722
+ it('returns false for hasMismatchedSocket when device socket URL is missing', () => {
1723
+ const testWebex = {
1724
+ internal: {
1725
+ mercury: {
1726
+ socket: {
1727
+ url: 'wss://mercury.example.com:443/path',
1728
+ },
1729
+ },
1730
+ device: {},
1731
+ },
1732
+ };
1733
+
1734
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1735
+
1736
+ assert.isFalse(result.hasMismatchedSocket);
1737
+ assert.equal(result.mercurySocketUrl, 'wss://mercury.example.com:443/path');
1738
+ assert.equal(result.deviceSocketUrl, '');
1739
+ });
1740
+
1741
+ it('returns default values when webex object is missing properties', () => {
1742
+ const testWebex = {
1743
+ internal: {},
1744
+ };
1745
+
1746
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1747
+
1748
+ assert.isFalse(result.hasMismatchedSocket);
1749
+ assert.equal(result.mercurySocketUrl, '');
1750
+ assert.equal(result.deviceSocketUrl, '');
1751
+ });
1752
+
1753
+ it('handles error gracefully and returns default values', () => {
1754
+ const testWebex = null;
1755
+
1756
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1757
+
1758
+ assert.isFalse(result.hasMismatchedSocket);
1759
+ assert.equal(result.mercurySocketUrl, '');
1760
+ assert.equal(result.deviceSocketUrl, '');
1761
+ });
1762
+
1763
+ it('sanitizes URLs by removing query parameters', () => {
1764
+ const testWebex = {
1765
+ internal: {
1766
+ mercury: {
1767
+ socket: {
1768
+ url: 'wss://mercury.example.com/path?secret=token123&key=value',
1769
+ },
1770
+ },
1771
+ device: {
1772
+ webSocketUrl: 'wss://device.example.com/path?secret=differenttoken&key=value',
1773
+ },
1774
+ },
1775
+ };
1776
+
1777
+ const result = MeetingUtil.getSocketUrlInfo(testWebex);
1778
+
1779
+ assert.notInclude(result.mercurySocketUrl, 'secret');
1780
+ assert.notInclude(result.mercurySocketUrl, 'token123');
1781
+ assert.notInclude(result.deviceSocketUrl, 'secret');
1782
+ assert.notInclude(result.deviceSocketUrl, 'differenttoken');
1783
+ });
1784
+ });
1513
1785
  });
1514
1786
  });