@webex/webex-core 3.11.0 → 3.12.0-llmrefactor.2

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 (46) hide show
  1. package/README.md +5 -2
  2. package/dist/config.js +24 -0
  3. package/dist/config.js.map +1 -1
  4. package/dist/credentials-config.js +12 -0
  5. package/dist/credentials-config.js.map +1 -1
  6. package/dist/interceptors/redirect.js +1 -1
  7. package/dist/interceptors/redirect.js.map +1 -1
  8. package/dist/lib/batcher.js +23 -7
  9. package/dist/lib/batcher.js.map +1 -1
  10. package/dist/lib/credentials/credentials.js +48 -4
  11. package/dist/lib/credentials/credentials.js.map +1 -1
  12. package/dist/lib/credentials/token.js +1 -1
  13. package/dist/lib/domains.js +90 -0
  14. package/dist/lib/domains.js.map +1 -0
  15. package/dist/lib/services/service-catalog.js +6 -10
  16. package/dist/lib/services/service-catalog.js.map +1 -1
  17. package/dist/lib/services/services.js +249 -35
  18. package/dist/lib/services/services.js.map +1 -1
  19. package/dist/lib/services-v2/service-catalog.js +6 -12
  20. package/dist/lib/services-v2/service-catalog.js.map +1 -1
  21. package/dist/lib/services-v2/services-v2.js +253 -34
  22. package/dist/lib/services-v2/services-v2.js.map +1 -1
  23. package/dist/plugins/logger.js +1 -1
  24. package/dist/webex-core.js +2 -2
  25. package/dist/webex-core.js.map +1 -1
  26. package/package.json +13 -13
  27. package/src/config.js +27 -0
  28. package/src/credentials-config.js +13 -0
  29. package/src/interceptors/redirect.js +4 -1
  30. package/src/lib/batcher.js +25 -10
  31. package/src/lib/credentials/credentials.js +50 -3
  32. package/src/lib/domains.ts +94 -0
  33. package/src/lib/services/service-catalog.js +6 -10
  34. package/src/lib/services/services.js +220 -17
  35. package/src/lib/services-v2/service-catalog.ts +6 -11
  36. package/src/lib/services-v2/services-v2.ts +224 -17
  37. package/test/fixtures/activation-email.ts +22 -0
  38. package/test/integration/spec/services/services.js +183 -112
  39. package/test/integration/spec/services-v2/services-v2.js +169 -98
  40. package/test/unit/spec/credentials/credentials.js +133 -2
  41. package/test/unit/spec/interceptors/auth.js +56 -0
  42. package/test/unit/spec/lib/batcher.js +56 -0
  43. package/test/unit/spec/services/service-catalog.js +93 -11
  44. package/test/unit/spec/services/services.js +491 -2
  45. package/test/unit/spec/services-v2/service-catalog.ts +93 -11
  46. package/test/unit/spec/services-v2/services-v2.ts +498 -0
@@ -73,6 +73,21 @@ var Services = _webexPlugin.default.extend({
73
73
  validateDomains: ['boolean', false, true],
74
74
  initFailed: ['boolean', false, false]
75
75
  },
76
+ session: {
77
+ /**
78
+ * Becomes `true` once the initial catalog collection has completed
79
+ * (successfully or otherwise) and any in-flight credentials refresh has
80
+ * settled. Blocks `webex.ready` so consumers can rely on `webex.ready`
81
+ * implying "catalogs populated AND credential state stable".
82
+ * @instance
83
+ * @memberof Services
84
+ * @type {boolean}
85
+ */
86
+ ready: {
87
+ default: false,
88
+ type: 'boolean'
89
+ }
90
+ },
76
91
  _catalogs: new _weakMap.default(),
77
92
  _serviceUrls: null,
78
93
  _hostCatalog: null,
@@ -243,6 +258,24 @@ var Services = _webexPlugin.default.extend({
243
258
  var hostCatalog = this._hostCatalog || {};
244
259
  return !!((_hostCatalog$host = hostCatalog[host]) !== null && _hostCatalog$host !== void 0 && _hostCatalog$host.length);
245
260
  },
261
+ /**
262
+ * Checks if the current environment is an integration (INT) environment
263
+ * by examining the u2c discovery URL from webex config.
264
+ * INT environments use discovery URLs containing 'intb' (e.g., u2c-intb.ciscospark.com).
265
+ * @returns {boolean} True if INT environment, false otherwise
266
+ */
267
+ isIntegrationEnvironment: function isIntegrationEnvironment() {
268
+ try {
269
+ var _this$webex, _this$webex$config, _this$webex$config$se, _this$webex$config$se2;
270
+ var u2cUrl = ((_this$webex = this.webex) === null || _this$webex === void 0 ? void 0 : (_this$webex$config = _this$webex.config) === null || _this$webex$config === void 0 ? void 0 : (_this$webex$config$se = _this$webex$config.services) === null || _this$webex$config$se === void 0 ? void 0 : (_this$webex$config$se2 = _this$webex$config$se.discovery) === null || _this$webex$config$se2 === void 0 ? void 0 : _this$webex$config$se2.u2c) || '';
271
+ var isInt = u2cUrl.includes('intb');
272
+ this.logger.info("services: isIntegrationEnvironment: ".concat(isInt));
273
+ return isInt;
274
+ } catch (error) {
275
+ this.logger.error('services: failed to determine integration environment', error);
276
+ return false;
277
+ }
278
+ },
246
279
  /**
247
280
  * Merge provided active cluster mappings into current state.
248
281
  * @param {Record<string,string>} activeServices
@@ -1271,9 +1304,9 @@ var Services = _webexPlugin.default.extend({
1271
1304
  initConfig: function initConfig() {
1272
1305
  // Get the catalog and destructure the services config.
1273
1306
  var catalog = this._getCatalog();
1274
- var _this$webex$config = this.webex.config,
1275
- services = _this$webex$config.services,
1276
- fedramp = _this$webex$config.fedramp;
1307
+ var _this$webex$config2 = this.webex.config,
1308
+ services = _this$webex$config2.services,
1309
+ fedramp = _this$webex$config2.fedramp;
1277
1310
 
1278
1311
  // Validate that the services configuration exists.
1279
1312
  if (services) {
@@ -1332,6 +1365,7 @@ var Services = _webexPlugin.default.extend({
1332
1365
 
1333
1366
  // Destructure the credentials plugin.
1334
1367
  var credentials = this.webex.credentials;
1368
+ var catalog = this._getCatalog();
1335
1369
 
1336
1370
  // Init a promise chain. Must be done as a Promise.resolve() to allow
1337
1371
  // credentials#getOrgId() to properly throw.
@@ -1348,9 +1382,13 @@ var Services = _webexPlugin.default.extend({
1348
1382
  }).then(function () {
1349
1383
  // Validate if the token is authorized.
1350
1384
  if (credentials.canAuthorize) {
1351
- // Attempt to collect the postauth catalog.
1352
-
1353
- return _this0.updateServices().catch(function () {
1385
+ // Attempt to collect the postauth catalog, then mark the catalog
1386
+ // ready. Setting `isReady` here - rather than only in the init
1387
+ // callers - means a slow postauth fetch that loses the gated-init
1388
+ // timeout race still marks the catalog ready once it completes.
1389
+ return _this0.updateServices().then(function () {
1390
+ catalog.isReady = true;
1391
+ }).catch(function () {
1354
1392
  _this0.initFailed = true;
1355
1393
  _this0.logger.warn('services: cannot retrieve postauth catalog');
1356
1394
  });
@@ -1360,6 +1398,62 @@ var Services = _webexPlugin.default.extend({
1360
1398
  return _promise.default.resolve();
1361
1399
  });
1362
1400
  },
1401
+ /**
1402
+ * Await any in-flight credentials refresh, then flip `services.ready` so
1403
+ * `webex.ready` can fire. Closes the parallel-refresh window: if a credential
1404
+ * refresh is in flight when initial catalog collection settles, we must not
1405
+ * signal ready until the refresh has resolved - otherwise downstream
1406
+ * consumers may observe `canAuthorize`/token state that is about to change
1407
+ * under them.
1408
+ *
1409
+ * @private
1410
+ * @returns {Promise<void>}
1411
+ */
1412
+ _finalizeReady: function _finalizeReady() {
1413
+ var _this1 = this;
1414
+ return (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
1415
+ var credentials;
1416
+ return _regenerator.default.wrap(function (_context4) {
1417
+ while (1) switch (_context4.prev = _context4.next) {
1418
+ case 0:
1419
+ credentials = _this1.webex.credentials;
1420
+ if (!(credentials && credentials.isRefreshing)) {
1421
+ _context4.next = 1;
1422
+ break;
1423
+ }
1424
+ _context4.next = 1;
1425
+ return new _promise.default(function (resolve) {
1426
+ credentials.once('change:isRefreshing', resolve);
1427
+ });
1428
+ case 1:
1429
+ _this1.ready = true;
1430
+ case 2:
1431
+ case "end":
1432
+ return _context4.stop();
1433
+ }
1434
+ }, _callee4);
1435
+ }))();
1436
+ },
1437
+ /**
1438
+ * Build a promise that rejects once the catalog init timeout elapses. Race
1439
+ * this against catalog collection so a hung request never leaves
1440
+ * `services.ready` false forever - that would stall `webex.ready` and leave
1441
+ * consumers waiting on it indefinitely. Timeout is configurable via
1442
+ * `config.services.catalogInitTimeout` (defaults to 15s in config). Created
1443
+ * lazily so paths that skip catalog collection never schedule a stray timer.
1444
+ *
1445
+ * @private
1446
+ * @returns {Promise<never>}
1447
+ */
1448
+ _makeInitTimeout: function _makeInitTimeout() {
1449
+ var _this$webex$config3, _this$webex$config3$s;
1450
+ var initTimeoutMs = (_this$webex$config3 = this.webex.config) === null || _this$webex$config3 === void 0 ? void 0 : (_this$webex$config3$s = _this$webex$config3.services) === null || _this$webex$config3$s === void 0 ? void 0 : _this$webex$config3$s.catalogInitTimeout;
1451
+ return new _promise.default(function (_, reject) {
1452
+ setTimeout(function () {
1453
+ return reject(new Error("services: init timed out after ".concat(initTimeoutMs, "ms")));
1454
+ }, initTimeoutMs);
1455
+ });
1456
+ },
1363
1457
  /**
1364
1458
  * Initializer
1365
1459
  *
@@ -1368,7 +1462,7 @@ var Services = _webexPlugin.default.extend({
1368
1462
  * @returns {Services}
1369
1463
  */
1370
1464
  initialize: function initialize() {
1371
- var _this1 = this;
1465
+ var _this10 = this;
1372
1466
  var catalog = new _serviceCatalog.default();
1373
1467
  var registry = new _serviceRegistry.default();
1374
1468
  var state = new _serviceState.default();
@@ -1376,56 +1470,176 @@ var Services = _webexPlugin.default.extend({
1376
1470
  this.registries.set(this.webex, registry);
1377
1471
  this.states.set(this.webex, state);
1378
1472
 
1379
- // Listen for configuration changes once.
1473
+ // Listen for configuration changes once. The config is not populated on the
1474
+ // webex instance until the `change:config` event fires, so any decision that
1475
+ // depends on config values (such as the gated-vs-ungated init below) must be
1476
+ // made from within this handler rather than synchronously in `initialize()`.
1380
1477
  this.listenToOnce(this.webex, 'change:config', function () {
1381
- _this1.initConfig();
1382
- });
1478
+ var _this10$webex$config, _this10$webex$config$;
1479
+ _this10.initConfig();
1383
1480
 
1481
+ // Feature flag: when enabled, `webex.ready` is blocked until the initial
1482
+ // catalog collection has settled AND any in-flight credentials refresh has
1483
+ // completed. When disabled (the default), preserves the pre-existing
1484
+ // behavior where `webex.ready` fires as soon as `webex.loaded` does and
1485
+ // the catalog is collected out-of-band.
1486
+ var waitForCatalogInit = ((_this10$webex$config = _this10.webex.config) === null || _this10$webex$config === void 0 ? void 0 : (_this10$webex$config$ = _this10$webex$config.services) === null || _this10$webex$config$ === void 0 ? void 0 : _this10$webex$config$.waitForCatalogInit) === true;
1487
+ if (waitForCatalogInit) {
1488
+ _this10._initializeCatalogsGated(catalog);
1489
+ } else {
1490
+ // Not gating - immediately mark ready so we do not block webex.ready.
1491
+ _this10.ready = true;
1492
+ _this10._initializeCatalogsUngated(catalog);
1493
+ }
1494
+ });
1495
+ },
1496
+ /**
1497
+ * Original (pre-verified-ready) initialization path. Runs on `webex.ready`
1498
+ * and collects catalogs opportunistically without blocking anything.
1499
+ *
1500
+ * @private
1501
+ * @param {ServiceCatalog} catalog
1502
+ * @returns {void}
1503
+ */
1504
+ _initializeCatalogsUngated: function _initializeCatalogsUngated(catalog) {
1505
+ var _this11 = this;
1384
1506
  // wait for webex instance to be ready before attempting
1385
1507
  // to update the service catalogs
1386
1508
  // this can cause a race condition because credentials may
1387
1509
  // not be valid when services is initialized
1388
- this.listenToOnce(this.webex, 'ready', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
1389
- var cachedCatalog, supertoken, email;
1390
- return _regenerator.default.wrap(function (_context4) {
1391
- while (1) switch (_context4.prev = _context4.next) {
1510
+ this.listenToOnce(this.webex, 'ready', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5() {
1511
+ var cachedCatalog, supertoken, _this11$webex$config, _this11$webex$config$, email;
1512
+ return _regenerator.default.wrap(function (_context5) {
1513
+ while (1) switch (_context5.prev = _context5.next) {
1392
1514
  case 0:
1393
- _context4.next = 1;
1394
- return _this1._loadCatalogFromCache();
1515
+ _context5.next = 1;
1516
+ return _this11._loadCatalogFromCache();
1517
+ case 1:
1518
+ cachedCatalog = _context5.sent;
1519
+ if (!cachedCatalog) {
1520
+ _context5.next = 2;
1521
+ break;
1522
+ }
1523
+ catalog.isReady = true;
1524
+ return _context5.abrupt("return");
1525
+ case 2:
1526
+ supertoken = _this11.webex.credentials.supertoken; // Validate if the supertoken exists.
1527
+ if (!(supertoken && supertoken.access_token)) {
1528
+ _context5.next = 3;
1529
+ break;
1530
+ }
1531
+ // `initServiceCatalogs` marks the catalog ready internally once the
1532
+ // postauth catalog is collected.
1533
+ _this11.initServiceCatalogs().catch(function (error) {
1534
+ _this11.initFailed = true;
1535
+ _this11.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1536
+ });
1537
+ _context5.next = 5;
1538
+ break;
1539
+ case 3:
1540
+ email = _this11.webex.config.email;
1541
+ if (!(((_this11$webex$config = _this11.webex.config) === null || _this11$webex$config === void 0 ? void 0 : (_this11$webex$config$ = _this11$webex$config.services) === null || _this11$webex$config$ === void 0 ? void 0 : _this11$webex$config$.skipPreauthCatalogOnUnauthenticated) === true)) {
1542
+ _context5.next = 4;
1543
+ break;
1544
+ }
1545
+ _this11.logger.info('services: skipping preauth catalog collection while unauthenticated as per the config');
1546
+ return _context5.abrupt("return");
1547
+ case 4:
1548
+ _this11.collectPreauthCatalog(email ? {
1549
+ email: email
1550
+ } : undefined).catch(function (error) {
1551
+ _this11.initFailed = true;
1552
+ _this11.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1553
+ });
1554
+ case 5:
1555
+ case "end":
1556
+ return _context5.stop();
1557
+ }
1558
+ }, _callee5);
1559
+ })));
1560
+ },
1561
+ /**
1562
+ * Verified-ready initialization path. Blocks `webex.ready` until the initial
1563
+ * catalog fetch has settled (or timed out) AND any in-flight credentials
1564
+ * refresh has completed. Also handles the fresh-login case where OAuth
1565
+ * completes after `loaded` fires.
1566
+ *
1567
+ * @private
1568
+ * @param {ServiceCatalog} catalog
1569
+ * @returns {void}
1570
+ */
1571
+ _initializeCatalogsGated: function _initializeCatalogsGated(catalog) {
1572
+ var _this12 = this;
1573
+ // Wait for storage to be loaded before attempting to update the service
1574
+ // catalogs. We listen for 'loaded' instead of 'ready' because `services.ready`
1575
+ // now blocks `webex.ready` - listening to 'ready' would deadlock.
1576
+ this.listenToOnce(this.webex, 'loaded', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6() {
1577
+ var cachedCatalog, supertoken, _this12$webex$config, _this12$webex$config$, email;
1578
+ return _regenerator.default.wrap(function (_context6) {
1579
+ while (1) switch (_context6.prev = _context6.next) {
1580
+ case 0:
1581
+ _context6.next = 1;
1582
+ return _this12._loadCatalogFromCache();
1395
1583
  case 1:
1396
- cachedCatalog = _context4.sent;
1584
+ cachedCatalog = _context6.sent;
1397
1585
  if (!cachedCatalog) {
1398
- _context4.next = 2;
1586
+ _context6.next = 3;
1399
1587
  break;
1400
1588
  }
1401
1589
  catalog.isReady = true;
1402
- return _context4.abrupt("return");
1590
+ _context6.next = 2;
1591
+ return _this12._finalizeReady();
1403
1592
  case 2:
1404
- supertoken = _this1.webex.credentials.supertoken; // Validate if the supertoken exists.
1593
+ return _context6.abrupt("return");
1594
+ case 3:
1595
+ supertoken = _this12.webex.credentials.supertoken; // Validate if the supertoken exists.
1405
1596
  if (supertoken && supertoken.access_token) {
1406
- _this1.initServiceCatalogs().then(function () {
1407
- catalog.isReady = true;
1408
- }).catch(function (error) {
1409
- _this1.initFailed = true;
1410
- _this1.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1597
+ // `initServiceCatalogs` marks the catalog ready internally once the
1598
+ // postauth catalog is collected - even if it loses the timeout race
1599
+ // below, so a slow fetch still eventually flips `catalog.isReady`.
1600
+ _promise.default.race([_this12.initServiceCatalogs(), _this12._makeInitTimeout()]).catch(function (error) {
1601
+ _this12.initFailed = true;
1602
+ _this12.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1603
+ }).finally(function () {
1604
+ return _this12._finalizeReady();
1411
1605
  });
1412
1606
  } else {
1413
- email = _this1.webex.config.email;
1414
- _this1.collectPreauthCatalog(email ? {
1415
- email: email
1416
- } : undefined).catch(function (error) {
1417
- _this1.initFailed = true;
1418
- _this1.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1607
+ email = _this12.webex.config.email; // Handle fresh login: 'loaded' fires before OAuth completes, so listen
1608
+ // for `canAuthorize` flipping true and then collect the postauth catalog.
1609
+ _this12.listenToOnce(_this12.webex, 'change:canAuthorize', function () {
1610
+ if (_this12.webex.canAuthorize && !catalog.status.postauth.ready) {
1611
+ // `initServiceCatalogs` marks the catalog ready internally.
1612
+ _this12.initServiceCatalogs().catch(function (error) {
1613
+ _this12.logger.error("services: failed to init service catalogs after auth, ".concat(error === null || error === void 0 ? void 0 : error.message));
1614
+ });
1615
+ }
1419
1616
  });
1617
+ if (((_this12$webex$config = _this12.webex.config) === null || _this12$webex$config === void 0 ? void 0 : (_this12$webex$config$ = _this12$webex$config.services) === null || _this12$webex$config$ === void 0 ? void 0 : _this12$webex$config$.skipPreauthCatalogOnUnauthenticated) === true) {
1618
+ // Skip the preauth catalog fetch (it will be collected manually
1619
+ // later), but still finalize `services.ready` so `webex.ready` is not
1620
+ // stalled while unauthenticated. No timeout is created here so there
1621
+ // is no stray timer or unhandled rejection.
1622
+ _this12.logger.info('services: skipping preauth catalog collection while unauthenticated as per the config');
1623
+ _this12._finalizeReady();
1624
+ } else {
1625
+ _promise.default.race([_this12.collectPreauthCatalog(email ? {
1626
+ email: email
1627
+ } : undefined), _this12._makeInitTimeout()]).catch(function (error) {
1628
+ _this12.initFailed = true;
1629
+ _this12.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1630
+ }).finally(function () {
1631
+ return _this12._finalizeReady();
1632
+ });
1633
+ }
1420
1634
  }
1421
- case 3:
1635
+ case 4:
1422
1636
  case "end":
1423
- return _context4.stop();
1637
+ return _context6.stop();
1424
1638
  }
1425
- }, _callee4);
1639
+ }, _callee6);
1426
1640
  })));
1427
1641
  },
1428
- version: "0.0.0"
1642
+ version: "3.12.0-llmrefactor.2"
1429
1643
  });
1430
1644
  /* eslint-enable no-underscore-dangle */
1431
1645
  var _default = exports.default = Services;