@webex/webex-core 3.12.0-next.5 → 3.12.0-next.50

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 (56) hide show
  1. package/README.md +5 -2
  2. package/dist/config.js +38 -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/index.js +7 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/interceptors/catalog-url.js +87 -0
  9. package/dist/interceptors/catalog-url.js.map +1 -0
  10. package/dist/interceptors/payload-transformer.js +25 -4
  11. package/dist/interceptors/payload-transformer.js.map +1 -1
  12. package/dist/lib/batcher.js +23 -7
  13. package/dist/lib/batcher.js.map +1 -1
  14. package/dist/lib/credentials/credentials.js +103 -4
  15. package/dist/lib/credentials/credentials.js.map +1 -1
  16. package/dist/lib/credentials/token.js +1 -1
  17. package/dist/lib/domains.js +90 -0
  18. package/dist/lib/domains.js.map +1 -0
  19. package/dist/lib/services/service-catalog.js +108 -18
  20. package/dist/lib/services/service-catalog.js.map +1 -1
  21. package/dist/lib/services/services.js +232 -34
  22. package/dist/lib/services/services.js.map +1 -1
  23. package/dist/lib/services-v2/service-catalog.js +14 -13
  24. package/dist/lib/services-v2/service-catalog.js.map +1 -1
  25. package/dist/lib/services-v2/services-v2.js +241 -37
  26. package/dist/lib/services-v2/services-v2.js.map +1 -1
  27. package/dist/plugins/logger.js +1 -1
  28. package/dist/webex-core.js +11 -3
  29. package/dist/webex-core.js.map +1 -1
  30. package/package.json +13 -13
  31. package/src/config.js +42 -0
  32. package/src/credentials-config.js +13 -0
  33. package/src/index.js +1 -0
  34. package/src/interceptors/catalog-url.js +66 -0
  35. package/src/interceptors/payload-transformer.js +23 -1
  36. package/src/lib/batcher.js +25 -10
  37. package/src/lib/credentials/credentials.js +93 -3
  38. package/src/lib/domains.ts +94 -0
  39. package/src/lib/services/service-catalog.js +112 -16
  40. package/src/lib/services/services.js +208 -20
  41. package/src/lib/services-v2/service-catalog.ts +16 -12
  42. package/src/lib/services-v2/services-v2.ts +218 -21
  43. package/src/webex-core.js +8 -0
  44. package/test/fixtures/activation-email.ts +22 -0
  45. package/test/integration/spec/services/services.js +183 -112
  46. package/test/integration/spec/services-v2/services-v2.js +169 -98
  47. package/test/unit/spec/credentials/credentials.js +247 -3
  48. package/test/unit/spec/interceptors/auth.js +56 -0
  49. package/test/unit/spec/interceptors/catalog-url.js +224 -0
  50. package/test/unit/spec/interceptors/payload-transformer.js +119 -1
  51. package/test/unit/spec/lib/batcher.js +56 -0
  52. package/test/unit/spec/services/service-catalog.js +368 -11
  53. package/test/unit/spec/services/services.js +440 -2
  54. package/test/unit/spec/services-v2/service-catalog.ts +134 -11
  55. package/test/unit/spec/services-v2/services-v2.ts +467 -0
  56. package/test/unit/spec/webex-core.js +77 -1
@@ -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,
@@ -1060,9 +1075,11 @@ var Services = _webexPlugin.default.extend({
1060
1075
  authorization: token
1061
1076
  };
1062
1077
  }
1063
- return this.webex.internal.newMetrics.callDiagnosticLatencies.measureLatency(function () {
1078
+ var sendRequest = function sendRequest() {
1064
1079
  return _this7.request(requestObject);
1065
- }, 'internal.get.u2c.time').then(function (_ref12) {
1080
+ };
1081
+ var responsePromise = this.webex.internal.newMetrics ? this.webex.internal.newMetrics.callDiagnosticLatencies.measureLatency(sendRequest, 'internal.get.u2c.time') : sendRequest();
1082
+ return responsePromise.then(function (_ref12) {
1066
1083
  var body = _ref12.body;
1067
1084
  return body;
1068
1085
  });
@@ -1350,6 +1367,7 @@ var Services = _webexPlugin.default.extend({
1350
1367
 
1351
1368
  // Destructure the credentials plugin.
1352
1369
  var credentials = this.webex.credentials;
1370
+ var catalog = this._getCatalog();
1353
1371
 
1354
1372
  // Init a promise chain. Must be done as a Promise.resolve() to allow
1355
1373
  // credentials#getOrgId() to properly throw.
@@ -1366,9 +1384,13 @@ var Services = _webexPlugin.default.extend({
1366
1384
  }).then(function () {
1367
1385
  // Validate if the token is authorized.
1368
1386
  if (credentials.canAuthorize) {
1369
- // Attempt to collect the postauth catalog.
1370
-
1371
- return _this0.updateServices().catch(function () {
1387
+ // Attempt to collect the postauth catalog, then mark the catalog
1388
+ // ready. Setting `isReady` here - rather than only in the init
1389
+ // callers - means a slow postauth fetch that loses the gated-init
1390
+ // timeout race still marks the catalog ready once it completes.
1391
+ return _this0.updateServices().then(function () {
1392
+ catalog.isReady = true;
1393
+ }).catch(function () {
1372
1394
  _this0.initFailed = true;
1373
1395
  _this0.logger.warn('services: cannot retrieve postauth catalog');
1374
1396
  });
@@ -1378,6 +1400,62 @@ var Services = _webexPlugin.default.extend({
1378
1400
  return _promise.default.resolve();
1379
1401
  });
1380
1402
  },
1403
+ /**
1404
+ * Await any in-flight credentials refresh, then flip `services.ready` so
1405
+ * `webex.ready` can fire. Closes the parallel-refresh window: if a credential
1406
+ * refresh is in flight when initial catalog collection settles, we must not
1407
+ * signal ready until the refresh has resolved - otherwise downstream
1408
+ * consumers may observe `canAuthorize`/token state that is about to change
1409
+ * under them.
1410
+ *
1411
+ * @private
1412
+ * @returns {Promise<void>}
1413
+ */
1414
+ _finalizeReady: function _finalizeReady() {
1415
+ var _this1 = this;
1416
+ return (0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
1417
+ var credentials;
1418
+ return _regenerator.default.wrap(function (_context4) {
1419
+ while (1) switch (_context4.prev = _context4.next) {
1420
+ case 0:
1421
+ credentials = _this1.webex.credentials;
1422
+ if (!(credentials && credentials.isRefreshing)) {
1423
+ _context4.next = 1;
1424
+ break;
1425
+ }
1426
+ _context4.next = 1;
1427
+ return new _promise.default(function (resolve) {
1428
+ credentials.once('change:isRefreshing', resolve);
1429
+ });
1430
+ case 1:
1431
+ _this1.ready = true;
1432
+ case 2:
1433
+ case "end":
1434
+ return _context4.stop();
1435
+ }
1436
+ }, _callee4);
1437
+ }))();
1438
+ },
1439
+ /**
1440
+ * Build a promise that rejects once the catalog init timeout elapses. Race
1441
+ * this against catalog collection so a hung request never leaves
1442
+ * `services.ready` false forever - that would stall `webex.ready` and leave
1443
+ * consumers waiting on it indefinitely. Timeout is configurable via
1444
+ * `config.services.catalogInitTimeout` (defaults to 15s in config). Created
1445
+ * lazily so paths that skip catalog collection never schedule a stray timer.
1446
+ *
1447
+ * @private
1448
+ * @returns {Promise<never>}
1449
+ */
1450
+ _makeInitTimeout: function _makeInitTimeout() {
1451
+ var _this$webex$config3, _this$webex$config3$s;
1452
+ 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;
1453
+ return new _promise.default(function (_, reject) {
1454
+ setTimeout(function () {
1455
+ return reject(new Error("services: init timed out after ".concat(initTimeoutMs, "ms")));
1456
+ }, initTimeoutMs);
1457
+ });
1458
+ },
1381
1459
  /**
1382
1460
  * Initializer
1383
1461
  *
@@ -1386,7 +1464,7 @@ var Services = _webexPlugin.default.extend({
1386
1464
  * @returns {Services}
1387
1465
  */
1388
1466
  initialize: function initialize() {
1389
- var _this1 = this;
1467
+ var _this10 = this;
1390
1468
  var catalog = new _serviceCatalog.default();
1391
1469
  var registry = new _serviceRegistry.default();
1392
1470
  var state = new _serviceState.default();
@@ -1394,56 +1472,176 @@ var Services = _webexPlugin.default.extend({
1394
1472
  this.registries.set(this.webex, registry);
1395
1473
  this.states.set(this.webex, state);
1396
1474
 
1397
- // Listen for configuration changes once.
1475
+ // Listen for configuration changes once. The config is not populated on the
1476
+ // webex instance until the `change:config` event fires, so any decision that
1477
+ // depends on config values (such as the gated-vs-ungated init below) must be
1478
+ // made from within this handler rather than synchronously in `initialize()`.
1398
1479
  this.listenToOnce(this.webex, 'change:config', function () {
1399
- _this1.initConfig();
1400
- });
1480
+ var _this10$webex$config, _this10$webex$config$;
1481
+ _this10.initConfig();
1401
1482
 
1483
+ // Feature flag: when enabled, `webex.ready` is blocked until the initial
1484
+ // catalog collection has settled AND any in-flight credentials refresh has
1485
+ // completed. When disabled (the default), preserves the pre-existing
1486
+ // behavior where `webex.ready` fires as soon as `webex.loaded` does and
1487
+ // the catalog is collected out-of-band.
1488
+ 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;
1489
+ if (waitForCatalogInit) {
1490
+ _this10._initializeCatalogsGated(catalog);
1491
+ } else {
1492
+ // Not gating - immediately mark ready so we do not block webex.ready.
1493
+ _this10.ready = true;
1494
+ _this10._initializeCatalogsUngated(catalog);
1495
+ }
1496
+ });
1497
+ },
1498
+ /**
1499
+ * Original (pre-verified-ready) initialization path. Runs on `webex.ready`
1500
+ * and collects catalogs opportunistically without blocking anything.
1501
+ *
1502
+ * @private
1503
+ * @param {ServiceCatalog} catalog
1504
+ * @returns {void}
1505
+ */
1506
+ _initializeCatalogsUngated: function _initializeCatalogsUngated(catalog) {
1507
+ var _this11 = this;
1402
1508
  // wait for webex instance to be ready before attempting
1403
1509
  // to update the service catalogs
1404
1510
  // this can cause a race condition because credentials may
1405
1511
  // not be valid when services is initialized
1406
- this.listenToOnce(this.webex, 'ready', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee4() {
1407
- var cachedCatalog, supertoken, email;
1408
- return _regenerator.default.wrap(function (_context4) {
1409
- while (1) switch (_context4.prev = _context4.next) {
1512
+ this.listenToOnce(this.webex, 'ready', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee5() {
1513
+ var cachedCatalog, supertoken, _this11$webex$config, _this11$webex$config$, email;
1514
+ return _regenerator.default.wrap(function (_context5) {
1515
+ while (1) switch (_context5.prev = _context5.next) {
1410
1516
  case 0:
1411
- _context4.next = 1;
1412
- return _this1._loadCatalogFromCache();
1517
+ _context5.next = 1;
1518
+ return _this11._loadCatalogFromCache();
1519
+ case 1:
1520
+ cachedCatalog = _context5.sent;
1521
+ if (!cachedCatalog) {
1522
+ _context5.next = 2;
1523
+ break;
1524
+ }
1525
+ catalog.isReady = true;
1526
+ return _context5.abrupt("return");
1527
+ case 2:
1528
+ supertoken = _this11.webex.credentials.supertoken; // Validate if the supertoken exists.
1529
+ if (!(supertoken && supertoken.access_token)) {
1530
+ _context5.next = 3;
1531
+ break;
1532
+ }
1533
+ // `initServiceCatalogs` marks the catalog ready internally once the
1534
+ // postauth catalog is collected.
1535
+ _this11.initServiceCatalogs().catch(function (error) {
1536
+ _this11.initFailed = true;
1537
+ _this11.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1538
+ });
1539
+ _context5.next = 5;
1540
+ break;
1541
+ case 3:
1542
+ email = _this11.webex.config.email;
1543
+ 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)) {
1544
+ _context5.next = 4;
1545
+ break;
1546
+ }
1547
+ _this11.logger.info('services: skipping preauth catalog collection while unauthenticated as per the config');
1548
+ return _context5.abrupt("return");
1549
+ case 4:
1550
+ _this11.collectPreauthCatalog(email ? {
1551
+ email: email
1552
+ } : undefined).catch(function (error) {
1553
+ _this11.initFailed = true;
1554
+ _this11.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1555
+ });
1556
+ case 5:
1557
+ case "end":
1558
+ return _context5.stop();
1559
+ }
1560
+ }, _callee5);
1561
+ })));
1562
+ },
1563
+ /**
1564
+ * Verified-ready initialization path. Blocks `webex.ready` until the initial
1565
+ * catalog fetch has settled (or timed out) AND any in-flight credentials
1566
+ * refresh has completed. Also handles the fresh-login case where OAuth
1567
+ * completes after `loaded` fires.
1568
+ *
1569
+ * @private
1570
+ * @param {ServiceCatalog} catalog
1571
+ * @returns {void}
1572
+ */
1573
+ _initializeCatalogsGated: function _initializeCatalogsGated(catalog) {
1574
+ var _this12 = this;
1575
+ // Wait for storage to be loaded before attempting to update the service
1576
+ // catalogs. We listen for 'loaded' instead of 'ready' because `services.ready`
1577
+ // now blocks `webex.ready` - listening to 'ready' would deadlock.
1578
+ this.listenToOnce(this.webex, 'loaded', /*#__PURE__*/(0, _asyncToGenerator2.default)(/*#__PURE__*/_regenerator.default.mark(function _callee6() {
1579
+ var cachedCatalog, supertoken, _this12$webex$config, _this12$webex$config$, email;
1580
+ return _regenerator.default.wrap(function (_context6) {
1581
+ while (1) switch (_context6.prev = _context6.next) {
1582
+ case 0:
1583
+ _context6.next = 1;
1584
+ return _this12._loadCatalogFromCache();
1413
1585
  case 1:
1414
- cachedCatalog = _context4.sent;
1586
+ cachedCatalog = _context6.sent;
1415
1587
  if (!cachedCatalog) {
1416
- _context4.next = 2;
1588
+ _context6.next = 3;
1417
1589
  break;
1418
1590
  }
1419
1591
  catalog.isReady = true;
1420
- return _context4.abrupt("return");
1592
+ _context6.next = 2;
1593
+ return _this12._finalizeReady();
1421
1594
  case 2:
1422
- supertoken = _this1.webex.credentials.supertoken; // Validate if the supertoken exists.
1595
+ return _context6.abrupt("return");
1596
+ case 3:
1597
+ supertoken = _this12.webex.credentials.supertoken; // Validate if the supertoken exists.
1423
1598
  if (supertoken && supertoken.access_token) {
1424
- _this1.initServiceCatalogs().then(function () {
1425
- catalog.isReady = true;
1426
- }).catch(function (error) {
1427
- _this1.initFailed = true;
1428
- _this1.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1599
+ // `initServiceCatalogs` marks the catalog ready internally once the
1600
+ // postauth catalog is collected - even if it loses the timeout race
1601
+ // below, so a slow fetch still eventually flips `catalog.isReady`.
1602
+ _promise.default.race([_this12.initServiceCatalogs(), _this12._makeInitTimeout()]).catch(function (error) {
1603
+ _this12.initFailed = true;
1604
+ _this12.logger.error("services: failed to init initial services when credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1605
+ }).finally(function () {
1606
+ return _this12._finalizeReady();
1429
1607
  });
1430
1608
  } else {
1431
- email = _this1.webex.config.email;
1432
- _this1.collectPreauthCatalog(email ? {
1433
- email: email
1434
- } : undefined).catch(function (error) {
1435
- _this1.initFailed = true;
1436
- _this1.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1609
+ email = _this12.webex.config.email; // Handle fresh login: 'loaded' fires before OAuth completes, so listen
1610
+ // for `canAuthorize` flipping true and then collect the postauth catalog.
1611
+ _this12.listenToOnce(_this12.webex, 'change:canAuthorize', function () {
1612
+ if (_this12.webex.canAuthorize && !catalog.status.postauth.ready) {
1613
+ // `initServiceCatalogs` marks the catalog ready internally.
1614
+ _this12.initServiceCatalogs().catch(function (error) {
1615
+ _this12.logger.error("services: failed to init service catalogs after auth, ".concat(error === null || error === void 0 ? void 0 : error.message));
1616
+ });
1617
+ }
1437
1618
  });
1619
+ 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) {
1620
+ // Skip the preauth catalog fetch (it will be collected manually
1621
+ // later), but still finalize `services.ready` so `webex.ready` is not
1622
+ // stalled while unauthenticated. No timeout is created here so there
1623
+ // is no stray timer or unhandled rejection.
1624
+ _this12.logger.info('services: skipping preauth catalog collection while unauthenticated as per the config');
1625
+ _this12._finalizeReady();
1626
+ } else {
1627
+ _promise.default.race([_this12.collectPreauthCatalog(email ? {
1628
+ email: email
1629
+ } : undefined), _this12._makeInitTimeout()]).catch(function (error) {
1630
+ _this12.initFailed = true;
1631
+ _this12.logger.error("services: failed to init initial services when no credentials available, ".concat(error === null || error === void 0 ? void 0 : error.message));
1632
+ }).finally(function () {
1633
+ return _this12._finalizeReady();
1634
+ });
1635
+ }
1438
1636
  }
1439
- case 3:
1637
+ case 4:
1440
1638
  case "end":
1441
- return _context4.stop();
1639
+ return _context6.stop();
1442
1640
  }
1443
- }, _callee4);
1641
+ }, _callee6);
1444
1642
  })));
1445
1643
  },
1446
- version: "3.12.0-next.5"
1644
+ version: "3.12.0-next.50"
1447
1645
  });
1448
1646
  /* eslint-enable no-underscore-dangle */
1449
1647
  var _default = exports.default = Services;