@webex/webex-core 3.11.0 → 3.12.0-auth-prejoin-fetch.1
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.
- package/README.md +5 -2
- package/dist/config.js +24 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +12 -0
- package/dist/credentials-config.js.map +1 -1
- package/dist/interceptors/redirect.js +1 -1
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/lib/batcher.js +23 -7
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +48 -4
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/domains.js +90 -0
- package/dist/lib/domains.js.map +1 -0
- package/dist/lib/services/service-catalog.js +6 -10
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/services.js +253 -37
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +6 -12
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +257 -36
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/webex-core.js +2 -2
- package/dist/webex-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +27 -0
- package/src/credentials-config.js +13 -0
- package/src/interceptors/redirect.js +4 -1
- package/src/lib/batcher.js +25 -10
- package/src/lib/credentials/credentials.js +50 -3
- package/src/lib/domains.ts +94 -0
- package/src/lib/services/service-catalog.js +6 -10
- package/src/lib/services/services.js +230 -20
- package/src/lib/services-v2/service-catalog.ts +6 -11
- package/src/lib/services-v2/services-v2.ts +234 -20
- package/test/fixtures/activation-email.ts +22 -0
- package/test/integration/spec/services/services.js +183 -112
- package/test/integration/spec/services-v2/services-v2.js +169 -98
- package/test/unit/spec/credentials/credentials.js +133 -2
- package/test/unit/spec/interceptors/auth.js +56 -0
- package/test/unit/spec/lib/batcher.js +56 -0
- package/test/unit/spec/services/service-catalog.js +93 -11
- package/test/unit/spec/services/services.js +491 -2
- package/test/unit/spec/services-v2/service-catalog.ts +93 -11
- 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 = {
|
|
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();
|
|
@@ -1216,9 +1257,16 @@ const Services = WebexPlugin.extend({
|
|
|
1216
1257
|
requestObject.headers = {authorization: token};
|
|
1217
1258
|
}
|
|
1218
1259
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1260
|
+
const sendRequest = () => this.request(requestObject);
|
|
1261
|
+
|
|
1262
|
+
const responsePromise = this.webex.internal.newMetrics
|
|
1263
|
+
? this.webex.internal.newMetrics.callDiagnosticLatencies.measureLatency(
|
|
1264
|
+
sendRequest,
|
|
1265
|
+
'internal.get.u2c.time'
|
|
1266
|
+
)
|
|
1267
|
+
: sendRequest();
|
|
1268
|
+
|
|
1269
|
+
return responsePromise.then(({body}) => this._formatReceivedHostmap(body || {}));
|
|
1222
1270
|
},
|
|
1223
1271
|
|
|
1224
1272
|
/**
|
|
@@ -1291,6 +1339,7 @@ const Services = WebexPlugin.extend({
|
|
|
1291
1339
|
|
|
1292
1340
|
// Destructure the credentials plugin.
|
|
1293
1341
|
const {credentials} = this.webex;
|
|
1342
|
+
const catalog = this._getCatalog();
|
|
1294
1343
|
|
|
1295
1344
|
// Init a promise chain. Must be done as a Promise.resolve() to allow
|
|
1296
1345
|
// credentials#getOrgId() to properly throw.
|
|
@@ -1303,11 +1352,18 @@ const Services = WebexPlugin.extend({
|
|
|
1303
1352
|
.then(() => {
|
|
1304
1353
|
// Validate if the token is authorized.
|
|
1305
1354
|
if (credentials.canAuthorize) {
|
|
1306
|
-
// Attempt to collect the postauth catalog
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
})
|
|
1355
|
+
// Attempt to collect the postauth catalog, then mark the catalog
|
|
1356
|
+
// ready. Setting `isReady` here - rather than only in the init
|
|
1357
|
+
// callers - means a slow postauth fetch that loses the gated-init
|
|
1358
|
+
// timeout race still marks the catalog ready once it completes.
|
|
1359
|
+
return this.updateServices({forceRefresh: refresh})
|
|
1360
|
+
.then(() => {
|
|
1361
|
+
catalog.isReady = true;
|
|
1362
|
+
})
|
|
1363
|
+
.catch(() => {
|
|
1364
|
+
this.initFailed = true;
|
|
1365
|
+
this.logger.warn('services: cannot retrieve postauth catalog');
|
|
1366
|
+
});
|
|
1311
1367
|
}
|
|
1312
1368
|
|
|
1313
1369
|
// Return a resolved promise for consistent return value.
|
|
@@ -1316,6 +1372,51 @@ const Services = WebexPlugin.extend({
|
|
|
1316
1372
|
);
|
|
1317
1373
|
},
|
|
1318
1374
|
|
|
1375
|
+
/**
|
|
1376
|
+
* Await any in-flight credentials refresh, then flip `services.ready` so
|
|
1377
|
+
* `webex.ready` can fire. Closes the parallel-refresh window: if a credential
|
|
1378
|
+
* refresh is in flight when initial catalog collection settles, we must not
|
|
1379
|
+
* signal ready until the refresh has resolved - otherwise downstream
|
|
1380
|
+
* consumers may observe `canAuthorize`/token state that is about to change
|
|
1381
|
+
* under them.
|
|
1382
|
+
*
|
|
1383
|
+
* @private
|
|
1384
|
+
* @returns {Promise<void>}
|
|
1385
|
+
*/
|
|
1386
|
+
async _finalizeReady(): Promise<void> {
|
|
1387
|
+
const {credentials} = this.webex;
|
|
1388
|
+
|
|
1389
|
+
if (credentials && credentials.isRefreshing) {
|
|
1390
|
+
await new Promise<void>((resolve) => {
|
|
1391
|
+
credentials.once('change:isRefreshing', resolve);
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
this.ready = true;
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Build a promise that rejects once the catalog init timeout elapses. Race
|
|
1400
|
+
* this against catalog collection so a hung request never leaves
|
|
1401
|
+
* `services.ready` false forever - that would stall `webex.ready` and leave
|
|
1402
|
+
* consumers waiting on it indefinitely. Timeout is configurable via
|
|
1403
|
+
* `config.services.catalogInitTimeout` (defaults to 15s in config). Created
|
|
1404
|
+
* lazily so paths that skip catalog collection never schedule a stray timer.
|
|
1405
|
+
*
|
|
1406
|
+
* @private
|
|
1407
|
+
* @returns {Promise<never>}
|
|
1408
|
+
*/
|
|
1409
|
+
_makeInitTimeout(): Promise<never> {
|
|
1410
|
+
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;
|
|
1411
|
+
|
|
1412
|
+
return new Promise<never>((_, reject) => {
|
|
1413
|
+
setTimeout(
|
|
1414
|
+
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
|
|
1415
|
+
initTimeoutMs
|
|
1416
|
+
);
|
|
1417
|
+
});
|
|
1418
|
+
},
|
|
1419
|
+
|
|
1319
1420
|
/**
|
|
1320
1421
|
* Initializer
|
|
1321
1422
|
*
|
|
@@ -1327,11 +1428,39 @@ const Services = WebexPlugin.extend({
|
|
|
1327
1428
|
const catalog = new ServiceCatalog();
|
|
1328
1429
|
this._catalogs.set(this.webex, catalog);
|
|
1329
1430
|
|
|
1330
|
-
// Listen for configuration changes once.
|
|
1431
|
+
// Listen for configuration changes once. The config is not populated on the
|
|
1432
|
+
// webex instance until the `change:config` event fires, so any decision that
|
|
1433
|
+
// depends on config values (such as the gated-vs-ungated init below) must be
|
|
1434
|
+
// made from within this handler rather than synchronously in `initialize()`.
|
|
1331
1435
|
this.listenToOnce(this.webex, 'change:config', () => {
|
|
1332
1436
|
this.initConfig();
|
|
1437
|
+
|
|
1438
|
+
// Feature flag: when enabled, `webex.ready` is blocked until the initial
|
|
1439
|
+
// catalog collection has settled AND any in-flight credentials refresh has
|
|
1440
|
+
// completed. When disabled (the default), preserves the pre-existing
|
|
1441
|
+
// behavior where `webex.ready` fires as soon as `webex.loaded` does and
|
|
1442
|
+
// the catalog is collected out-of-band.
|
|
1443
|
+
const waitForCatalogInit = this.webex.config?.services?.waitForCatalogInit === true;
|
|
1444
|
+
|
|
1445
|
+
if (waitForCatalogInit) {
|
|
1446
|
+
this._initializeCatalogsGated(catalog);
|
|
1447
|
+
} else {
|
|
1448
|
+
// Not gating - immediately mark ready so we do not block webex.ready.
|
|
1449
|
+
this.ready = true;
|
|
1450
|
+
this._initializeCatalogsUngated(catalog);
|
|
1451
|
+
}
|
|
1333
1452
|
});
|
|
1453
|
+
},
|
|
1334
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* Original (pre-verified-ready) initialization path. Runs on `webex.ready`
|
|
1457
|
+
* and collects catalogs opportunistically without blocking anything.
|
|
1458
|
+
*
|
|
1459
|
+
* @private
|
|
1460
|
+
* @param {ServiceCatalog} catalog
|
|
1461
|
+
* @returns {void}
|
|
1462
|
+
*/
|
|
1463
|
+
_initializeCatalogsUngated(catalog: ServiceCatalog): void {
|
|
1335
1464
|
// wait for webex instance to be ready before attempting
|
|
1336
1465
|
// to update the service catalogs
|
|
1337
1466
|
this.listenToOnce(this.webex, 'ready', async () => {
|
|
@@ -1344,19 +1473,25 @@ const Services = WebexPlugin.extend({
|
|
|
1344
1473
|
const {supertoken} = this.webex.credentials;
|
|
1345
1474
|
// Validate if the supertoken exists.
|
|
1346
1475
|
if (supertoken && supertoken.access_token) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
.
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
);
|
|
1356
|
-
});
|
|
1476
|
+
// `initServiceCatalogs` marks the catalog ready internally once the
|
|
1477
|
+
// postauth catalog is collected.
|
|
1478
|
+
this.initServiceCatalogs().catch((error) => {
|
|
1479
|
+
this.initFailed = true;
|
|
1480
|
+
this.logger.error(
|
|
1481
|
+
`services: failed to init initial services when credentials available, ${error?.message}`
|
|
1482
|
+
);
|
|
1483
|
+
});
|
|
1357
1484
|
} else {
|
|
1358
1485
|
const {email} = this.webex.config;
|
|
1359
1486
|
|
|
1487
|
+
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
|
|
1488
|
+
this.logger.info(
|
|
1489
|
+
'services: skipping preauth catalog collection while unauthenticated as per the config'
|
|
1490
|
+
);
|
|
1491
|
+
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1360
1495
|
this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => {
|
|
1361
1496
|
this.initFailed = true;
|
|
1362
1497
|
this.logger.error(
|
|
@@ -1366,6 +1501,85 @@ const Services = WebexPlugin.extend({
|
|
|
1366
1501
|
}
|
|
1367
1502
|
});
|
|
1368
1503
|
},
|
|
1504
|
+
|
|
1505
|
+
/**
|
|
1506
|
+
* Verified-ready initialization path. Blocks `webex.ready` until the initial
|
|
1507
|
+
* catalog fetch has settled (or timed out) AND any in-flight credentials
|
|
1508
|
+
* refresh has completed. Also handles the fresh-login case where OAuth
|
|
1509
|
+
* completes after `loaded` fires.
|
|
1510
|
+
*
|
|
1511
|
+
* @private
|
|
1512
|
+
* @param {ServiceCatalog} catalog
|
|
1513
|
+
* @returns {void}
|
|
1514
|
+
*/
|
|
1515
|
+
_initializeCatalogsGated(catalog: ServiceCatalog): void {
|
|
1516
|
+
// Wait for storage to be loaded before attempting to update the service
|
|
1517
|
+
// catalogs. We listen for 'loaded' instead of 'ready' because `services.ready`
|
|
1518
|
+
// now blocks `webex.ready` - listening to 'ready' would deadlock.
|
|
1519
|
+
this.listenToOnce(this.webex, 'loaded', async () => {
|
|
1520
|
+
const warmed = await this._loadCatalogFromCache();
|
|
1521
|
+
if (warmed) {
|
|
1522
|
+
catalog.isReady = true;
|
|
1523
|
+
await this._finalizeReady();
|
|
1524
|
+
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
const {supertoken} = this.webex.credentials;
|
|
1528
|
+
|
|
1529
|
+
// Validate if the supertoken exists.
|
|
1530
|
+
if (supertoken && supertoken.access_token) {
|
|
1531
|
+
// `initServiceCatalogs` marks the catalog ready internally once the
|
|
1532
|
+
// postauth catalog is collected - even if it loses the timeout race
|
|
1533
|
+
// below, so a slow fetch still eventually flips `catalog.isReady`.
|
|
1534
|
+
Promise.race([this.initServiceCatalogs(), this._makeInitTimeout()])
|
|
1535
|
+
.catch((error) => {
|
|
1536
|
+
this.initFailed = true;
|
|
1537
|
+
this.logger.error(
|
|
1538
|
+
`services: failed to init initial services when credentials available, ${error?.message}`
|
|
1539
|
+
);
|
|
1540
|
+
})
|
|
1541
|
+
.finally(() => this._finalizeReady());
|
|
1542
|
+
} else {
|
|
1543
|
+
const {email} = this.webex.config;
|
|
1544
|
+
|
|
1545
|
+
// Handle fresh login: 'loaded' fires before OAuth completes, so listen
|
|
1546
|
+
// for `canAuthorize` flipping true and then collect the postauth catalog.
|
|
1547
|
+
this.listenToOnce(this.webex, 'change:canAuthorize', () => {
|
|
1548
|
+
if (this.webex.canAuthorize && !catalog.status.postauth.ready) {
|
|
1549
|
+
// `initServiceCatalogs` marks the catalog ready internally.
|
|
1550
|
+
this.initServiceCatalogs().catch((error) => {
|
|
1551
|
+
this.logger.error(
|
|
1552
|
+
`services: failed to init service catalogs after auth, ${error?.message}`
|
|
1553
|
+
);
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
|
|
1559
|
+
// Skip the preauth catalog fetch (it will be collected manually
|
|
1560
|
+
// later), but still finalize `services.ready` so `webex.ready` is not
|
|
1561
|
+
// stalled while unauthenticated. No timeout is created here so there
|
|
1562
|
+
// is no stray timer or unhandled rejection.
|
|
1563
|
+
this.logger.info(
|
|
1564
|
+
'services: skipping preauth catalog collection while unauthenticated as per the config'
|
|
1565
|
+
);
|
|
1566
|
+
this._finalizeReady();
|
|
1567
|
+
} else {
|
|
1568
|
+
Promise.race([
|
|
1569
|
+
this.collectPreauthCatalog(email ? {email} : undefined),
|
|
1570
|
+
this._makeInitTimeout(),
|
|
1571
|
+
])
|
|
1572
|
+
.catch((error) => {
|
|
1573
|
+
this.initFailed = true;
|
|
1574
|
+
this.logger.error(
|
|
1575
|
+
`services: failed to init initial services when no credentials available, ${error?.message}`
|
|
1576
|
+
);
|
|
1577
|
+
})
|
|
1578
|
+
.finally(() => this._finalizeReady());
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
});
|
|
1582
|
+
},
|
|
1369
1583
|
});
|
|
1370
1584
|
/* eslint-enable no-underscore-dangle */
|
|
1371
1585
|
|
|
@@ -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;
|