@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
@@ -44,6 +44,22 @@ const Services = WebexPlugin.extend({
44
44
  initFailed: ['boolean', false, false],
45
45
  },
46
46
 
47
+ session: {
48
+ /**
49
+ * Becomes `true` once the initial catalog collection has completed
50
+ * (successfully or otherwise) and any in-flight credentials refresh has
51
+ * settled. Blocks `webex.ready` so consumers can rely on `webex.ready`
52
+ * implying "catalogs populated AND credential state stable".
53
+ * @instance
54
+ * @memberof Services
55
+ * @type {boolean}
56
+ */
57
+ ready: {
58
+ default: false,
59
+ type: 'boolean',
60
+ },
61
+ },
62
+
47
63
  _catalogs: new WeakMap(),
48
64
 
49
65
  _activeServices: {},
@@ -198,6 +214,27 @@ const Services = WebexPlugin.extend({
198
214
  });
199
215
  });
200
216
  },
217
+
218
+ /**
219
+ * Checks if the current environment is an integration (INT) environment
220
+ * by examining the u2c discovery URL from webex config.
221
+ * INT environments use discovery URLs containing 'intb' (e.g., u2c-intb.ciscospark.com).
222
+ * @returns {boolean} True if INT environment, false otherwise
223
+ */
224
+ isIntegrationEnvironment(): boolean {
225
+ try {
226
+ const u2cUrl = this.webex?.config?.services?.discovery?.u2c || '';
227
+ const isInt = u2cUrl.includes('intb');
228
+
229
+ this.logger.info(`services: isIntegrationEnvironment: ${isInt}`);
230
+
231
+ return isInt;
232
+ } catch (error) {
233
+ this.logger.error('services: failed to determine integration environment', error);
234
+
235
+ return false;
236
+ }
237
+ },
201
238
  /**
202
239
  * saves all the services from the pre and post catalog service
203
240
  * @param {ActiveServices} activeServices
@@ -1198,7 +1235,11 @@ const Services = WebexPlugin.extend({
1198
1235
  ): Promise<object> {
1199
1236
  const service = 'u2c';
1200
1237
  const resource = from ? `/${from}/catalog` : '/catalog';
1201
- const qs = {...(query || {}), format: 'U2CV2'};
1238
+ const qs = {
1239
+ ...(query || {}),
1240
+ format: 'U2CV2',
1241
+ ...(this.webex.config?.services?.useCatalogOverride && {useCatalogOverride: true}),
1242
+ };
1202
1243
 
1203
1244
  if (forceRefresh) {
1204
1245
  qs.timestamp = new Date().getTime();
@@ -1291,6 +1332,7 @@ const Services = WebexPlugin.extend({
1291
1332
 
1292
1333
  // Destructure the credentials plugin.
1293
1334
  const {credentials} = this.webex;
1335
+ const catalog = this._getCatalog();
1294
1336
 
1295
1337
  // Init a promise chain. Must be done as a Promise.resolve() to allow
1296
1338
  // credentials#getOrgId() to properly throw.
@@ -1303,11 +1345,18 @@ const Services = WebexPlugin.extend({
1303
1345
  .then(() => {
1304
1346
  // Validate if the token is authorized.
1305
1347
  if (credentials.canAuthorize) {
1306
- // Attempt to collect the postauth catalog.
1307
- return this.updateServices({forceRefresh: refresh}).catch(() => {
1308
- this.initFailed = true;
1309
- this.logger.warn('services: cannot retrieve postauth catalog');
1310
- });
1348
+ // Attempt to collect the postauth catalog, then mark the catalog
1349
+ // ready. Setting `isReady` here - rather than only in the init
1350
+ // callers - means a slow postauth fetch that loses the gated-init
1351
+ // timeout race still marks the catalog ready once it completes.
1352
+ return this.updateServices({forceRefresh: refresh})
1353
+ .then(() => {
1354
+ catalog.isReady = true;
1355
+ })
1356
+ .catch(() => {
1357
+ this.initFailed = true;
1358
+ this.logger.warn('services: cannot retrieve postauth catalog');
1359
+ });
1311
1360
  }
1312
1361
 
1313
1362
  // Return a resolved promise for consistent return value.
@@ -1316,6 +1365,51 @@ const Services = WebexPlugin.extend({
1316
1365
  );
1317
1366
  },
1318
1367
 
1368
+ /**
1369
+ * Await any in-flight credentials refresh, then flip `services.ready` so
1370
+ * `webex.ready` can fire. Closes the parallel-refresh window: if a credential
1371
+ * refresh is in flight when initial catalog collection settles, we must not
1372
+ * signal ready until the refresh has resolved - otherwise downstream
1373
+ * consumers may observe `canAuthorize`/token state that is about to change
1374
+ * under them.
1375
+ *
1376
+ * @private
1377
+ * @returns {Promise<void>}
1378
+ */
1379
+ async _finalizeReady(): Promise<void> {
1380
+ const {credentials} = this.webex;
1381
+
1382
+ if (credentials && credentials.isRefreshing) {
1383
+ await new Promise<void>((resolve) => {
1384
+ credentials.once('change:isRefreshing', resolve);
1385
+ });
1386
+ }
1387
+
1388
+ this.ready = true;
1389
+ },
1390
+
1391
+ /**
1392
+ * Build a promise that rejects once the catalog init timeout elapses. Race
1393
+ * this against catalog collection so a hung request never leaves
1394
+ * `services.ready` false forever - that would stall `webex.ready` and leave
1395
+ * consumers waiting on it indefinitely. Timeout is configurable via
1396
+ * `config.services.catalogInitTimeout` (defaults to 15s in config). Created
1397
+ * lazily so paths that skip catalog collection never schedule a stray timer.
1398
+ *
1399
+ * @private
1400
+ * @returns {Promise<never>}
1401
+ */
1402
+ _makeInitTimeout(): Promise<never> {
1403
+ const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;
1404
+
1405
+ return new Promise<never>((_, reject) => {
1406
+ setTimeout(
1407
+ () => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
1408
+ initTimeoutMs
1409
+ );
1410
+ });
1411
+ },
1412
+
1319
1413
  /**
1320
1414
  * Initializer
1321
1415
  *
@@ -1327,11 +1421,39 @@ const Services = WebexPlugin.extend({
1327
1421
  const catalog = new ServiceCatalog();
1328
1422
  this._catalogs.set(this.webex, catalog);
1329
1423
 
1330
- // Listen for configuration changes once.
1424
+ // Listen for configuration changes once. The config is not populated on the
1425
+ // webex instance until the `change:config` event fires, so any decision that
1426
+ // depends on config values (such as the gated-vs-ungated init below) must be
1427
+ // made from within this handler rather than synchronously in `initialize()`.
1331
1428
  this.listenToOnce(this.webex, 'change:config', () => {
1332
1429
  this.initConfig();
1430
+
1431
+ // Feature flag: when enabled, `webex.ready` is blocked until the initial
1432
+ // catalog collection has settled AND any in-flight credentials refresh has
1433
+ // completed. When disabled (the default), preserves the pre-existing
1434
+ // behavior where `webex.ready` fires as soon as `webex.loaded` does and
1435
+ // the catalog is collected out-of-band.
1436
+ const waitForCatalogInit = this.webex.config?.services?.waitForCatalogInit === true;
1437
+
1438
+ if (waitForCatalogInit) {
1439
+ this._initializeCatalogsGated(catalog);
1440
+ } else {
1441
+ // Not gating - immediately mark ready so we do not block webex.ready.
1442
+ this.ready = true;
1443
+ this._initializeCatalogsUngated(catalog);
1444
+ }
1333
1445
  });
1446
+ },
1334
1447
 
1448
+ /**
1449
+ * Original (pre-verified-ready) initialization path. Runs on `webex.ready`
1450
+ * and collects catalogs opportunistically without blocking anything.
1451
+ *
1452
+ * @private
1453
+ * @param {ServiceCatalog} catalog
1454
+ * @returns {void}
1455
+ */
1456
+ _initializeCatalogsUngated(catalog: ServiceCatalog): void {
1335
1457
  // wait for webex instance to be ready before attempting
1336
1458
  // to update the service catalogs
1337
1459
  this.listenToOnce(this.webex, 'ready', async () => {
@@ -1344,19 +1466,25 @@ const Services = WebexPlugin.extend({
1344
1466
  const {supertoken} = this.webex.credentials;
1345
1467
  // Validate if the supertoken exists.
1346
1468
  if (supertoken && supertoken.access_token) {
1347
- this.initServiceCatalogs()
1348
- .then(() => {
1349
- catalog.isReady = true;
1350
- })
1351
- .catch((error) => {
1352
- this.initFailed = true;
1353
- this.logger.error(
1354
- `services: failed to init initial services when credentials available, ${error?.message}`
1355
- );
1356
- });
1469
+ // `initServiceCatalogs` marks the catalog ready internally once the
1470
+ // postauth catalog is collected.
1471
+ this.initServiceCatalogs().catch((error) => {
1472
+ this.initFailed = true;
1473
+ this.logger.error(
1474
+ `services: failed to init initial services when credentials available, ${error?.message}`
1475
+ );
1476
+ });
1357
1477
  } else {
1358
1478
  const {email} = this.webex.config;
1359
1479
 
1480
+ if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
1481
+ this.logger.info(
1482
+ 'services: skipping preauth catalog collection while unauthenticated as per the config'
1483
+ );
1484
+
1485
+ return;
1486
+ }
1487
+
1360
1488
  this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => {
1361
1489
  this.initFailed = true;
1362
1490
  this.logger.error(
@@ -1366,6 +1494,85 @@ const Services = WebexPlugin.extend({
1366
1494
  }
1367
1495
  });
1368
1496
  },
1497
+
1498
+ /**
1499
+ * Verified-ready initialization path. Blocks `webex.ready` until the initial
1500
+ * catalog fetch has settled (or timed out) AND any in-flight credentials
1501
+ * refresh has completed. Also handles the fresh-login case where OAuth
1502
+ * completes after `loaded` fires.
1503
+ *
1504
+ * @private
1505
+ * @param {ServiceCatalog} catalog
1506
+ * @returns {void}
1507
+ */
1508
+ _initializeCatalogsGated(catalog: ServiceCatalog): void {
1509
+ // Wait for storage to be loaded before attempting to update the service
1510
+ // catalogs. We listen for 'loaded' instead of 'ready' because `services.ready`
1511
+ // now blocks `webex.ready` - listening to 'ready' would deadlock.
1512
+ this.listenToOnce(this.webex, 'loaded', async () => {
1513
+ const warmed = await this._loadCatalogFromCache();
1514
+ if (warmed) {
1515
+ catalog.isReady = true;
1516
+ await this._finalizeReady();
1517
+
1518
+ return;
1519
+ }
1520
+ const {supertoken} = this.webex.credentials;
1521
+
1522
+ // Validate if the supertoken exists.
1523
+ if (supertoken && supertoken.access_token) {
1524
+ // `initServiceCatalogs` marks the catalog ready internally once the
1525
+ // postauth catalog is collected - even if it loses the timeout race
1526
+ // below, so a slow fetch still eventually flips `catalog.isReady`.
1527
+ Promise.race([this.initServiceCatalogs(), this._makeInitTimeout()])
1528
+ .catch((error) => {
1529
+ this.initFailed = true;
1530
+ this.logger.error(
1531
+ `services: failed to init initial services when credentials available, ${error?.message}`
1532
+ );
1533
+ })
1534
+ .finally(() => this._finalizeReady());
1535
+ } else {
1536
+ const {email} = this.webex.config;
1537
+
1538
+ // Handle fresh login: 'loaded' fires before OAuth completes, so listen
1539
+ // for `canAuthorize` flipping true and then collect the postauth catalog.
1540
+ this.listenToOnce(this.webex, 'change:canAuthorize', () => {
1541
+ if (this.webex.canAuthorize && !catalog.status.postauth.ready) {
1542
+ // `initServiceCatalogs` marks the catalog ready internally.
1543
+ this.initServiceCatalogs().catch((error) => {
1544
+ this.logger.error(
1545
+ `services: failed to init service catalogs after auth, ${error?.message}`
1546
+ );
1547
+ });
1548
+ }
1549
+ });
1550
+
1551
+ if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
1552
+ // Skip the preauth catalog fetch (it will be collected manually
1553
+ // later), but still finalize `services.ready` so `webex.ready` is not
1554
+ // stalled while unauthenticated. No timeout is created here so there
1555
+ // is no stray timer or unhandled rejection.
1556
+ this.logger.info(
1557
+ 'services: skipping preauth catalog collection while unauthenticated as per the config'
1558
+ );
1559
+ this._finalizeReady();
1560
+ } else {
1561
+ Promise.race([
1562
+ this.collectPreauthCatalog(email ? {email} : undefined),
1563
+ this._makeInitTimeout(),
1564
+ ])
1565
+ .catch((error) => {
1566
+ this.initFailed = true;
1567
+ this.logger.error(
1568
+ `services: failed to init initial services when no credentials available, ${error?.message}`
1569
+ );
1570
+ })
1571
+ .finally(() => this._finalizeReady());
1572
+ }
1573
+ }
1574
+ });
1575
+ },
1369
1576
  });
1370
1577
  /* eslint-enable no-underscore-dangle */
1371
1578
 
@@ -0,0 +1,22 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import uuid from 'uuid';
6
+
7
+ /**
8
+ * Generates a unique test email for user-activation/validation specs.
9
+ *
10
+ * The local part is kept short on purpose: for a brand-new self-signup user the
11
+ * backend derives the "given name" from the email local part, and self-signup
12
+ * orgs cap the given name at 50 characters. A full UUID would push the local
13
+ * part to 59 chars and fail with errorCode 100018, so we use 20 hex characters
14
+ * of entropy (local part = 43 chars).
15
+ *
16
+ * @returns {string} e.g. `Collabctg+webex-js-sdk-1a2b3c4d5e6f7a8b9c0d@gmail.com`
17
+ */
18
+ export function createActivationEmail(): string {
19
+ return `Collabctg+webex-js-sdk-${uuid.v4().replace(/-/g, '').slice(0, 20)}@gmail.com`;
20
+ }
21
+
22
+ export default createActivationEmail;