@webex/webex-core 3.10.0 → 3.11.0

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/dist/config.js +14 -0
  2. package/dist/config.js.map +1 -1
  3. package/dist/credentials-config.js.map +1 -1
  4. package/dist/index.js +1 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/interceptors/auth.js +6 -8
  7. package/dist/interceptors/auth.js.map +1 -1
  8. package/dist/interceptors/default-options.js +6 -8
  9. package/dist/interceptors/default-options.js.map +1 -1
  10. package/dist/interceptors/embargo.js +6 -8
  11. package/dist/interceptors/embargo.js.map +1 -1
  12. package/dist/interceptors/network-timing.js +6 -8
  13. package/dist/interceptors/network-timing.js.map +1 -1
  14. package/dist/interceptors/payload-transformer.js +6 -8
  15. package/dist/interceptors/payload-transformer.js.map +1 -1
  16. package/dist/interceptors/proxy.js +7 -10
  17. package/dist/interceptors/proxy.js.map +1 -1
  18. package/dist/interceptors/rate-limit.js +7 -10
  19. package/dist/interceptors/rate-limit.js.map +1 -1
  20. package/dist/interceptors/redirect.js +9 -8
  21. package/dist/interceptors/redirect.js.map +1 -1
  22. package/dist/interceptors/request-event.js +6 -8
  23. package/dist/interceptors/request-event.js.map +1 -1
  24. package/dist/interceptors/request-logger.js +6 -8
  25. package/dist/interceptors/request-logger.js.map +1 -1
  26. package/dist/interceptors/request-timing.js +6 -8
  27. package/dist/interceptors/request-timing.js.map +1 -1
  28. package/dist/interceptors/response-logger.js +6 -8
  29. package/dist/interceptors/response-logger.js.map +1 -1
  30. package/dist/interceptors/user-agent.js +8 -11
  31. package/dist/interceptors/user-agent.js.map +1 -1
  32. package/dist/interceptors/webex-tracking-id.js +6 -8
  33. package/dist/interceptors/webex-tracking-id.js.map +1 -1
  34. package/dist/interceptors/webex-user-agent.js +7 -10
  35. package/dist/interceptors/webex-user-agent.js.map +1 -1
  36. package/dist/lib/batcher.js +1 -1
  37. package/dist/lib/batcher.js.map +1 -1
  38. package/dist/lib/constants.js.map +1 -1
  39. package/dist/lib/credentials/credentials.js +4 -6
  40. package/dist/lib/credentials/credentials.js.map +1 -1
  41. package/dist/lib/credentials/grant-errors.js +18 -26
  42. package/dist/lib/credentials/grant-errors.js.map +1 -1
  43. package/dist/lib/credentials/index.js.map +1 -1
  44. package/dist/lib/credentials/scope.js.map +1 -1
  45. package/dist/lib/credentials/token-collection.js.map +1 -1
  46. package/dist/lib/credentials/token.js +5 -5
  47. package/dist/lib/credentials/token.js.map +1 -1
  48. package/dist/lib/interceptors/hostmap.js +6 -8
  49. package/dist/lib/interceptors/hostmap.js.map +1 -1
  50. package/dist/lib/interceptors/server-error.js +6 -8
  51. package/dist/lib/interceptors/server-error.js.map +1 -1
  52. package/dist/lib/interceptors/service.js +6 -8
  53. package/dist/lib/interceptors/service.js.map +1 -1
  54. package/dist/lib/metrics.js.map +1 -1
  55. package/dist/lib/page.js +5 -6
  56. package/dist/lib/page.js.map +1 -1
  57. package/dist/lib/services/index.js.map +1 -1
  58. package/dist/lib/services/service-catalog.js +3 -3
  59. package/dist/lib/services/service-catalog.js.map +1 -1
  60. package/dist/lib/services/service-fed-ramp.js.map +1 -1
  61. package/dist/lib/services/service-host.js +1 -2
  62. package/dist/lib/services/service-host.js.map +1 -1
  63. package/dist/lib/services/service-registry.js +1 -2
  64. package/dist/lib/services/service-registry.js.map +1 -1
  65. package/dist/lib/services/service-state.js +1 -2
  66. package/dist/lib/services/service-state.js.map +1 -1
  67. package/dist/lib/services/service-url.js +11 -1
  68. package/dist/lib/services/service-url.js.map +1 -1
  69. package/dist/lib/services/services.js +485 -127
  70. package/dist/lib/services/services.js.map +1 -1
  71. package/dist/lib/services-v2/index.js.map +1 -1
  72. package/dist/lib/services-v2/metrics.js.map +1 -1
  73. package/dist/lib/services-v2/service-catalog.js +7 -7
  74. package/dist/lib/services-v2/service-catalog.js.map +1 -1
  75. package/dist/lib/services-v2/service-detail.js.map +1 -1
  76. package/dist/lib/services-v2/service-fed-ramp.js.map +1 -1
  77. package/dist/lib/services-v2/services-v2.js +379 -51
  78. package/dist/lib/services-v2/services-v2.js.map +1 -1
  79. package/dist/lib/services-v2/types.js.map +1 -1
  80. package/dist/lib/stateless-webex-plugin.js +3 -4
  81. package/dist/lib/stateless-webex-plugin.js.map +1 -1
  82. package/dist/lib/storage/decorators.js.map +1 -1
  83. package/dist/lib/storage/errors.js +7 -9
  84. package/dist/lib/storage/errors.js.map +1 -1
  85. package/dist/lib/storage/index.js.map +1 -1
  86. package/dist/lib/storage/make-webex-plugin-store.js +14 -5
  87. package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
  88. package/dist/lib/storage/make-webex-store.js +13 -5
  89. package/dist/lib/storage/make-webex-store.js.map +1 -1
  90. package/dist/lib/storage/memory-store-adapter.js.map +1 -1
  91. package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
  92. package/dist/lib/webex-http-error.js +8 -11
  93. package/dist/lib/webex-http-error.js.map +1 -1
  94. package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
  95. package/dist/lib/webex-plugin.js.map +1 -1
  96. package/dist/plugins/logger.js +1 -1
  97. package/dist/plugins/logger.js.map +1 -1
  98. package/dist/webex-core.js +11 -11
  99. package/dist/webex-core.js.map +1 -1
  100. package/dist/webex-internal-core.js.map +1 -1
  101. package/package.json +13 -13
  102. package/src/config.js +15 -0
  103. package/src/interceptors/redirect.js +4 -0
  104. package/src/lib/services/service-url.js +9 -1
  105. package/src/lib/services/services.js +315 -7
  106. package/src/lib/services-v2/index.ts +0 -1
  107. package/src/lib/services-v2/service-catalog.ts +4 -4
  108. package/src/lib/services-v2/services-v2.ts +307 -7
  109. package/src/lib/services-v2/types.ts +13 -0
  110. package/test/fixtures/host-catalog-v2.ts +1 -1
  111. package/test/integration/spec/services/service-catalog.js +10 -4
  112. package/test/integration/spec/services/services.js +65 -9
  113. package/test/integration/spec/services-v2/service-catalog.js +2 -2
  114. package/test/integration/spec/services-v2/services-v2.js +56 -6
  115. package/test/unit/spec/interceptors/redirect.js +98 -0
  116. package/test/unit/spec/services/service-url.js +110 -0
  117. package/test/unit/spec/services/services.js +411 -2
  118. package/test/unit/spec/services-v2/services-v2.ts +316 -0
@@ -331,6 +331,20 @@ describe('webex-core', () => {
331
331
 
332
332
  assert.deepEqual(result, [{some: 'value'}]);
333
333
  });
334
+ it('updates the catalog with empty hostmap', async () => {
335
+ const serviceGroup = 'postauth';
336
+ const hostmap = {};
337
+
338
+ services._formatReceivedHostmap = sinon.stub().returns({services : undefined});
339
+
340
+ catalog.updateServiceGroups = sinon.stub().returns(Promise.resolve([{some: 'value'}]));
341
+
342
+ const result = await services.updateCatalog(serviceGroup, hostmap);
343
+
344
+ assert.calledWith(services._formatReceivedHostmap, hostmap);
345
+
346
+ assert.calledWith(catalog.updateServiceGroups, serviceGroup, undefined);
347
+ });
334
348
  });
335
349
 
336
350
  describe('#_fetchNewServiceHostmap()', () => {
@@ -695,5 +709,307 @@ describe('webex-core', () => {
695
709
  assert.isUndefined(result);
696
710
  });
697
711
  });
712
+
713
+ describe('#getMobiusClusters', () => {
714
+ it('returns unique mobius entries derived from serviceUrls baseUrl', () => {
715
+ // Arrange: seed internal _services with mobius (including duplicate baseUrl)
716
+ services._services = [
717
+ {
718
+ "id": "urn:TEAM:us-east-2_a:mobius",
719
+ "serviceName": 'mobius',
720
+ "serviceUrls": [
721
+ {"baseUrl": 'https://mobius-us-east-2.prod.infra.webex.com/api/v1', "priority": 5},
722
+ {"baseUrl": 'https://mobius-eu-central-1.prod.infra.webex.com/api/v1', "priority": 10},
723
+ {"baseUrl": 'https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1', "priority": 15}, // duplicate
724
+ ],
725
+ },
726
+ {
727
+ "id": "urn:TEAM:ap-southeast-2_m:mobius",
728
+ "serviceName": "mobius",
729
+ "serviceUrls": [
730
+ {
731
+ "baseUrl": "https://mobius-me-central-1.prod.infra.webex.com/api/v1",
732
+ "priority": 5
733
+ },
734
+ {
735
+ "baseUrl": "https://mobius-eu-central-1.prod.infra.webex.com/api/v1",
736
+ "priority": 10
737
+ },
738
+ {
739
+ "baseUrl": "https://mobius-ap-southeast-2.prod.infra.webex.com/api/v1",
740
+ "priority": 15
741
+ },
742
+ ],
743
+ },
744
+ // Non-mobius service should be ignored by getMobiusClusters
745
+ {
746
+ id: 'urn:TEAM:us-east-2_a:wdm',
747
+ serviceName: 'wdm',
748
+ serviceUrls: [{baseUrl: 'https://wdm-a.webex.com/api/v1', priority: 5}],
749
+ },
750
+ ];
751
+
752
+ // Act
753
+ const clusters = services.getMobiusClusters();
754
+
755
+ // Assert (v2 currently pushes baseUrl into host field and dedups by baseUrl)
756
+ assert.deepEqual(
757
+ clusters.map(({host, id, ttl, priority}) => ({host, id, ttl, priority})),
758
+ [
759
+ {host: 'mobius-us-east-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 5},
760
+ {host: 'mobius-eu-central-1.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 10},
761
+ {host: 'mobius-ap-southeast-2.prod.infra.webex.com', id: 'urn:TEAM:us-east-2_a:mobius', ttl: 0, priority: 15},
762
+ {host: 'mobius-me-central-1.prod.infra.webex.com', id: 'urn:TEAM:ap-southeast-2_m:mobius', ttl: 0, priority: 5},
763
+ ]
764
+ );
765
+ });
766
+ });
767
+
768
+ describe('#isValidHost', () => {
769
+ beforeEach(() => {
770
+ // Setting up a mock services list
771
+ services._services = [{
772
+ "id": "urn:IDENTITY:PC75:adminAudit",
773
+ "serviceName": "adminAudit",
774
+ "serviceUrls": [
775
+ {
776
+ "baseUrl": "https://audit-ci-r.wbx2.com/audit-ci/api/v2",
777
+ "priority": 5
778
+ },
779
+ {
780
+ "baseUrl": "https://audit-ci-t.wbx2.com/audit-ci/api/v2",
781
+ "priority": 10
782
+ }
783
+ ]
784
+ },
785
+ {
786
+ "id": "urn:IDENTITY:PC75:cdf",
787
+ "serviceName": "cdf",
788
+ "serviceUrls": [
789
+ {
790
+ "baseUrl": "https://wapdavis.webex.com/davis/api/v1",
791
+ "priority": 5
792
+ }
793
+ ]
794
+ }];
795
+ });
796
+ afterAll(() => {
797
+ // Clean up the mock services list
798
+ services._services = [];
799
+ });
800
+ it('returns true if the host is in the services list', () => {
801
+ assert.isTrue(services.isValidHost('wapdavis.webex.com'));
802
+ });
803
+
804
+ it('returns false if the host is not in the services list', () => {
805
+ assert.isFalse(services.isValidHost('test.com'));
806
+ assert.isFalse(services.isValidHost(''));
807
+ assert.isFalse(services.isValidHost(null));
808
+ assert.isFalse(services.isValidHost(undefined));
809
+ });
810
+
811
+ it('returns false for non-string inputs', () => {
812
+ assert.isFalse(services.isValidHost(123));
813
+ assert.isFalse(services.isValidHost({}));
814
+ assert.isFalse(services.isValidHost([]));
815
+ });
816
+ });
817
+
818
+ describe('U2C catalog cache behavior (v2)', () => {
819
+ const CATALOG_CACHE_KEY_V2 = 'services.v2.u2cHostMap';
820
+ let windowBackup;
821
+ let localStorageBackup;
822
+
823
+ const makeLocalStorageShim = () => {
824
+ const store = new Map<string, string>();
825
+ return {
826
+ getItem: (k: string) => (store.has(k) ? store.get(k) : null),
827
+ setItem: (k: string, v: string) => store.set(k, v),
828
+ removeItem: (k: string) => store.delete(k),
829
+ _store: store,
830
+ };
831
+ };
832
+
833
+ beforeEach(() => {
834
+ // Stub window.localStorage
835
+ windowBackup = global.window;
836
+ if (!global.window) global.window = {} as Window & typeof globalThis;
837
+ localStorageBackup = global.window.localStorage;
838
+ global.window.localStorage = makeLocalStorageShim();
839
+ // Enable U2C caching feature flag for tests that depend on cache writes/reads
840
+ services.webex.config = services.webex.config || {};
841
+ services.webex.config.calling = {...(services.webex.config.calling || {}), cacheU2C: true};
842
+ // Ensure code under test uses our shim via util method
843
+ sinon.stub(services, '_getLocalStorageSafe').returns(global.window.localStorage);
844
+ // default current env
845
+ services.webex.config = services.webex.config || {};
846
+ services.webex.config.services = services.webex.config.services || {discovery: {}};
847
+ services.webex.config.services.discovery.u2c =
848
+ services.webex.config.services.discovery.u2c || 'https://u2c.wbx2.com/u2c/api/v1';
849
+ services.webex.config.fedramp =
850
+ typeof services.webex.config.fedramp === 'boolean'
851
+ ? services.webex.config.fedramp
852
+ : false;
853
+ });
854
+
855
+ afterEach(() => {
856
+ global.window.localStorage = localStorageBackup || undefined;
857
+ if (!windowBackup) {
858
+ delete global.window;
859
+ } else {
860
+ global.window = windowBackup;
861
+ }
862
+ // Restore util stub if present
863
+ if (services._getLocalStorageSafe && services._getLocalStorageSafe.restore) {
864
+ services._getLocalStorageSafe.restore();
865
+ }
866
+ });
867
+
868
+ it('stores selection metadata and env on cache write for preauth', async () => {
869
+ // Arrange env
870
+ services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
871
+ services.webex.config.fedramp = false;
872
+
873
+ // Act
874
+ await services._cacheCatalog(
875
+ 'preauth',
876
+ {services: [], timestamp: Date.now().toString()},
877
+ {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'}
878
+ );
879
+
880
+ // Assert
881
+ const raw = window.localStorage.getItem(CATALOG_CACHE_KEY_V2);
882
+ assert.isString(raw);
883
+ const parsed = JSON.parse(raw as string);
884
+ assert.deepEqual(parsed.env, {
885
+ fedramp: false,
886
+ u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1',
887
+ });
888
+ assert.isObject(parsed.preauth);
889
+ assert.deepEqual(parsed.preauth.meta, {
890
+ selectionType: 'orgId',
891
+ selectionValue: 'urn:EXAMPLE:org',
892
+ });
893
+ });
894
+
895
+ it('warms preauth from cache when selection meta matches intended orgId', async () => {
896
+ // Arrange current env and credentials
897
+ services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
898
+ services.webex.config.fedramp = false;
899
+ services.webex.credentials = {
900
+ canAuthorize: true,
901
+ getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
902
+ };
903
+ // Seed cache
904
+ window.localStorage.setItem(
905
+ CATALOG_CACHE_KEY_V2,
906
+ JSON.stringify({
907
+ cachedAt: Date.now(),
908
+ env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
909
+ preauth: {
910
+ hostMap: {services: [], timestamp: '1'},
911
+ meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
912
+ },
913
+ })
914
+ );
915
+ // Spy updateServiceGroups
916
+ const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
917
+
918
+ // Act
919
+ const warmed = await services._loadCatalogFromCache();
920
+
921
+ // Assert
922
+ assert.isTrue(warmed);
923
+ assert.isTrue(
924
+ spy.calledWith('preauth', [], '1'),
925
+ 'expected preauth to be warmed when selection matches'
926
+ );
927
+ spy.restore && spy.restore();
928
+ });
929
+
930
+ it('does not warm preauth when selection meta is proximity mode', async () => {
931
+ // Arrange env
932
+ services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
933
+ services.webex.config.fedramp = false;
934
+ window.localStorage.setItem(
935
+ CATALOG_CACHE_KEY_V2,
936
+ JSON.stringify({
937
+ cachedAt: Date.now(),
938
+ env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
939
+ preauth: {
940
+ hostMap: {services: [], timestamp: '1'},
941
+ meta: {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'},
942
+ },
943
+ })
944
+ );
945
+ const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
946
+
947
+ // Act
948
+ const warmed = await services._loadCatalogFromCache();
949
+
950
+ // Assert: overall warm-up succeeds, but preauth is skipped
951
+ assert.isTrue(warmed);
952
+ assert.isFalse(
953
+ spy.calledWith('preauth', sinon.match.any, sinon.match.any),
954
+ 'expected preauth not to be warmed for proximity mode'
955
+ );
956
+ spy.restore && spy.restore();
957
+ });
958
+
959
+ it('does not warm preauth when selection meta mismatches intended selection', async () => {
960
+ // Arrange env and credentials
961
+ services.webex.config.services.discovery.u2c = 'https://u2c.wbx2.com/u2c/api/v1';
962
+ services.webex.config.fedramp = false;
963
+ services.webex.credentials = {
964
+ canAuthorize: true,
965
+ getOrgId: sinon.stub().returns('urn:EXAMPLE:org'),
966
+ };
967
+ window.localStorage.setItem(
968
+ CATALOG_CACHE_KEY_V2,
969
+ JSON.stringify({
970
+ cachedAt: Date.now(),
971
+ env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.wbx2.com/u2c/api/v1'},
972
+ preauth: {
973
+ hostMap: {services: [], timestamp: '1'},
974
+ meta: {selectionType: 'orgId', selectionValue: 'urn:DIFF:org'},
975
+ },
976
+ })
977
+ );
978
+ const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
979
+
980
+ const warmed = await services._loadCatalogFromCache();
981
+
982
+ assert.isTrue(warmed);
983
+ assert.isFalse(
984
+ spy.calledWith('preauth', sinon.match.any, sinon.match.any),
985
+ 'expected preauth not to be warmed on selection mismatch'
986
+ );
987
+ spy.restore && spy.restore();
988
+ });
989
+
990
+ it('skips warm entirely when environment fingerprint mismatches', async () => {
991
+ // Cached env differs from current env
992
+ services.webex.config.services.discovery.u2c = 'https://u2c.current.com/u2c/api/v1';
993
+ services.webex.config.fedramp = false;
994
+ window.localStorage.setItem(
995
+ CATALOG_CACHE_KEY_V2,
996
+ JSON.stringify({
997
+ cachedAt: Date.now(),
998
+ env: {fedramp: false, u2cDiscoveryUrl: 'https://u2c.cached.com/u2c/api/v1'},
999
+ preauth: {
1000
+ hostMap: {services: [], timestamp: '1'},
1001
+ meta: {selectionType: 'orgId', selectionValue: 'urn:EXAMPLE:org'},
1002
+ },
1003
+ })
1004
+ );
1005
+ const spy = sinon.spy(services._getCatalog(), 'updateServiceGroups');
1006
+
1007
+ const warmed = await services._loadCatalogFromCache();
1008
+
1009
+ assert.isFalse(warmed, 'env mismatch should skip warm and return false');
1010
+ assert.isFalse(spy.called, 'no group should be warmed on env mismatch');
1011
+ spy.restore && spy.restore();
1012
+ });
1013
+ });
698
1014
  });
699
1015
  });