@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
|
@@ -4,6 +4,7 @@ import AmpState from 'ampersand-state';
|
|
|
4
4
|
|
|
5
5
|
import {union} from 'lodash';
|
|
6
6
|
import ServiceUrl from './service-url';
|
|
7
|
+
import {matchAllowedDomain, normalizeAllowedDomains} from '../domains';
|
|
7
8
|
|
|
8
9
|
/* eslint-disable no-underscore-dangle */
|
|
9
10
|
/**
|
|
@@ -268,19 +269,14 @@ const ServiceCatalog = AmpState.extend({
|
|
|
268
269
|
},
|
|
269
270
|
|
|
270
271
|
/**
|
|
271
|
-
* Finds an allowed domain that matches a specific url.
|
|
272
|
+
* Finds an allowed domain that matches a specific url. The url's hostname
|
|
273
|
+
* must be the allowed domain itself or a subdomain of it.
|
|
272
274
|
*
|
|
273
275
|
* @param {string} url - The url to match the allowed domains against.
|
|
274
276
|
* @returns {string} - The matching allowed domain.
|
|
275
277
|
*/
|
|
276
278
|
findAllowedDomain(url) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!urlObj.host) {
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return this.allowedDomains.find((allowedDomain) => urlObj.host.includes(allowedDomain));
|
|
279
|
+
return matchAllowedDomain(url, this.allowedDomains);
|
|
284
280
|
},
|
|
285
281
|
|
|
286
282
|
/**
|
|
@@ -367,7 +363,7 @@ const ServiceCatalog = AmpState.extend({
|
|
|
367
363
|
* @returns {void}
|
|
368
364
|
*/
|
|
369
365
|
setAllowedDomains(allowedDomains) {
|
|
370
|
-
this.allowedDomains =
|
|
366
|
+
this.allowedDomains = normalizeAllowedDomains(allowedDomains);
|
|
371
367
|
},
|
|
372
368
|
|
|
373
369
|
/**
|
|
@@ -376,7 +372,7 @@ const ServiceCatalog = AmpState.extend({
|
|
|
376
372
|
* @returns {void}
|
|
377
373
|
*/
|
|
378
374
|
addAllowedDomains(newAllowedDomains) {
|
|
379
|
-
this.allowedDomains = union(this.allowedDomains, newAllowedDomains);
|
|
375
|
+
this.allowedDomains = union(this.allowedDomains, normalizeAllowedDomains(newAllowedDomains));
|
|
380
376
|
},
|
|
381
377
|
|
|
382
378
|
/**
|
|
@@ -57,6 +57,22 @@ const Services = WebexPlugin.extend({
|
|
|
57
57
|
initFailed: ['boolean', false, false],
|
|
58
58
|
},
|
|
59
59
|
|
|
60
|
+
session: {
|
|
61
|
+
/**
|
|
62
|
+
* Becomes `true` once the initial catalog collection has completed
|
|
63
|
+
* (successfully or otherwise) and any in-flight credentials refresh has
|
|
64
|
+
* settled. Blocks `webex.ready` so consumers can rely on `webex.ready`
|
|
65
|
+
* implying "catalogs populated AND credential state stable".
|
|
66
|
+
* @instance
|
|
67
|
+
* @memberof Services
|
|
68
|
+
* @type {boolean}
|
|
69
|
+
*/
|
|
70
|
+
ready: {
|
|
71
|
+
default: false,
|
|
72
|
+
type: 'boolean',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
|
|
60
76
|
_catalogs: new WeakMap(),
|
|
61
77
|
|
|
62
78
|
_serviceUrls: null,
|
|
@@ -243,6 +259,28 @@ const Services = WebexPlugin.extend({
|
|
|
243
259
|
|
|
244
260
|
return !!hostCatalog[host]?.length;
|
|
245
261
|
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Checks if the current environment is an integration (INT) environment
|
|
265
|
+
* by examining the u2c discovery URL from webex config.
|
|
266
|
+
* INT environments use discovery URLs containing 'intb' (e.g., u2c-intb.ciscospark.com).
|
|
267
|
+
* @returns {boolean} True if INT environment, false otherwise
|
|
268
|
+
*/
|
|
269
|
+
isIntegrationEnvironment() {
|
|
270
|
+
try {
|
|
271
|
+
const u2cUrl = this.webex?.config?.services?.discovery?.u2c || '';
|
|
272
|
+
const isInt = u2cUrl.includes('intb');
|
|
273
|
+
|
|
274
|
+
this.logger.info(`services: isIntegrationEnvironment: ${isInt}`);
|
|
275
|
+
|
|
276
|
+
return isInt;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
this.logger.error('services: failed to determine integration environment', error);
|
|
279
|
+
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
246
284
|
/**
|
|
247
285
|
* Merge provided active cluster mappings into current state.
|
|
248
286
|
* @param {Record<string,string>} activeServices
|
|
@@ -1065,9 +1103,16 @@ const Services = WebexPlugin.extend({
|
|
|
1065
1103
|
requestObject.headers = {authorization: token};
|
|
1066
1104
|
}
|
|
1067
1105
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1106
|
+
const sendRequest = () => this.request(requestObject);
|
|
1107
|
+
|
|
1108
|
+
const responsePromise = this.webex.internal.newMetrics
|
|
1109
|
+
? this.webex.internal.newMetrics.callDiagnosticLatencies.measureLatency(
|
|
1110
|
+
sendRequest,
|
|
1111
|
+
'internal.get.u2c.time'
|
|
1112
|
+
)
|
|
1113
|
+
: sendRequest();
|
|
1114
|
+
|
|
1115
|
+
return responsePromise.then(({body}) => body);
|
|
1071
1116
|
},
|
|
1072
1117
|
|
|
1073
1118
|
/**
|
|
@@ -1324,6 +1369,7 @@ const Services = WebexPlugin.extend({
|
|
|
1324
1369
|
|
|
1325
1370
|
// Destructure the credentials plugin.
|
|
1326
1371
|
const {credentials} = this.webex;
|
|
1372
|
+
const catalog = this._getCatalog();
|
|
1327
1373
|
|
|
1328
1374
|
// Init a promise chain. Must be done as a Promise.resolve() to allow
|
|
1329
1375
|
// credentials#getOrgId() to properly throw.
|
|
@@ -1336,12 +1382,18 @@ const Services = WebexPlugin.extend({
|
|
|
1336
1382
|
.then(() => {
|
|
1337
1383
|
// Validate if the token is authorized.
|
|
1338
1384
|
if (credentials.canAuthorize) {
|
|
1339
|
-
// Attempt to collect the postauth catalog
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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 this.updateServices()
|
|
1390
|
+
.then(() => {
|
|
1391
|
+
catalog.isReady = true;
|
|
1392
|
+
})
|
|
1393
|
+
.catch(() => {
|
|
1394
|
+
this.initFailed = true;
|
|
1395
|
+
this.logger.warn('services: cannot retrieve postauth catalog');
|
|
1396
|
+
});
|
|
1345
1397
|
}
|
|
1346
1398
|
|
|
1347
1399
|
// Return a resolved promise for consistent return value.
|
|
@@ -1350,6 +1402,51 @@ const Services = WebexPlugin.extend({
|
|
|
1350
1402
|
);
|
|
1351
1403
|
},
|
|
1352
1404
|
|
|
1405
|
+
/**
|
|
1406
|
+
* Await any in-flight credentials refresh, then flip `services.ready` so
|
|
1407
|
+
* `webex.ready` can fire. Closes the parallel-refresh window: if a credential
|
|
1408
|
+
* refresh is in flight when initial catalog collection settles, we must not
|
|
1409
|
+
* signal ready until the refresh has resolved - otherwise downstream
|
|
1410
|
+
* consumers may observe `canAuthorize`/token state that is about to change
|
|
1411
|
+
* under them.
|
|
1412
|
+
*
|
|
1413
|
+
* @private
|
|
1414
|
+
* @returns {Promise<void>}
|
|
1415
|
+
*/
|
|
1416
|
+
async _finalizeReady() {
|
|
1417
|
+
const {credentials} = this.webex;
|
|
1418
|
+
|
|
1419
|
+
if (credentials && credentials.isRefreshing) {
|
|
1420
|
+
await new Promise((resolve) => {
|
|
1421
|
+
credentials.once('change:isRefreshing', resolve);
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
this.ready = true;
|
|
1426
|
+
},
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Build a promise that rejects once the catalog init timeout elapses. Race
|
|
1430
|
+
* this against catalog collection so a hung request never leaves
|
|
1431
|
+
* `services.ready` false forever - that would stall `webex.ready` and leave
|
|
1432
|
+
* consumers waiting on it indefinitely. Timeout is configurable via
|
|
1433
|
+
* `config.services.catalogInitTimeout` (defaults to 15s in config). Created
|
|
1434
|
+
* lazily so paths that skip catalog collection never schedule a stray timer.
|
|
1435
|
+
*
|
|
1436
|
+
* @private
|
|
1437
|
+
* @returns {Promise<never>}
|
|
1438
|
+
*/
|
|
1439
|
+
_makeInitTimeout() {
|
|
1440
|
+
const initTimeoutMs = this.webex.config?.services?.catalogInitTimeout;
|
|
1441
|
+
|
|
1442
|
+
return new Promise((_, reject) => {
|
|
1443
|
+
setTimeout(
|
|
1444
|
+
() => reject(new Error(`services: init timed out after ${initTimeoutMs}ms`)),
|
|
1445
|
+
initTimeoutMs
|
|
1446
|
+
);
|
|
1447
|
+
});
|
|
1448
|
+
},
|
|
1449
|
+
|
|
1353
1450
|
/**
|
|
1354
1451
|
* Initializer
|
|
1355
1452
|
*
|
|
@@ -1366,11 +1463,39 @@ const Services = WebexPlugin.extend({
|
|
|
1366
1463
|
this.registries.set(this.webex, registry);
|
|
1367
1464
|
this.states.set(this.webex, state);
|
|
1368
1465
|
|
|
1369
|
-
// Listen for configuration changes once.
|
|
1466
|
+
// Listen for configuration changes once. The config is not populated on the
|
|
1467
|
+
// webex instance until the `change:config` event fires, so any decision that
|
|
1468
|
+
// depends on config values (such as the gated-vs-ungated init below) must be
|
|
1469
|
+
// made from within this handler rather than synchronously in `initialize()`.
|
|
1370
1470
|
this.listenToOnce(this.webex, 'change:config', () => {
|
|
1371
1471
|
this.initConfig();
|
|
1472
|
+
|
|
1473
|
+
// Feature flag: when enabled, `webex.ready` is blocked until the initial
|
|
1474
|
+
// catalog collection has settled AND any in-flight credentials refresh has
|
|
1475
|
+
// completed. When disabled (the default), preserves the pre-existing
|
|
1476
|
+
// behavior where `webex.ready` fires as soon as `webex.loaded` does and
|
|
1477
|
+
// the catalog is collected out-of-band.
|
|
1478
|
+
const waitForCatalogInit = this.webex.config?.services?.waitForCatalogInit === true;
|
|
1479
|
+
|
|
1480
|
+
if (waitForCatalogInit) {
|
|
1481
|
+
this._initializeCatalogsGated(catalog);
|
|
1482
|
+
} else {
|
|
1483
|
+
// Not gating - immediately mark ready so we do not block webex.ready.
|
|
1484
|
+
this.ready = true;
|
|
1485
|
+
this._initializeCatalogsUngated(catalog);
|
|
1486
|
+
}
|
|
1372
1487
|
});
|
|
1488
|
+
},
|
|
1373
1489
|
|
|
1490
|
+
/**
|
|
1491
|
+
* Original (pre-verified-ready) initialization path. Runs on `webex.ready`
|
|
1492
|
+
* and collects catalogs opportunistically without blocking anything.
|
|
1493
|
+
*
|
|
1494
|
+
* @private
|
|
1495
|
+
* @param {ServiceCatalog} catalog
|
|
1496
|
+
* @returns {void}
|
|
1497
|
+
*/
|
|
1498
|
+
_initializeCatalogsUngated(catalog) {
|
|
1374
1499
|
// wait for webex instance to be ready before attempting
|
|
1375
1500
|
// to update the service catalogs
|
|
1376
1501
|
// this can cause a race condition because credentials may
|
|
@@ -1385,19 +1510,25 @@ const Services = WebexPlugin.extend({
|
|
|
1385
1510
|
const {supertoken} = this.webex.credentials;
|
|
1386
1511
|
// Validate if the supertoken exists.
|
|
1387
1512
|
if (supertoken && supertoken.access_token) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
.
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
);
|
|
1397
|
-
});
|
|
1513
|
+
// `initServiceCatalogs` marks the catalog ready internally once the
|
|
1514
|
+
// postauth catalog is collected.
|
|
1515
|
+
this.initServiceCatalogs().catch((error) => {
|
|
1516
|
+
this.initFailed = true;
|
|
1517
|
+
this.logger.error(
|
|
1518
|
+
`services: failed to init initial services when credentials available, ${error?.message}`
|
|
1519
|
+
);
|
|
1520
|
+
});
|
|
1398
1521
|
} else {
|
|
1399
1522
|
const {email} = this.webex.config;
|
|
1400
1523
|
|
|
1524
|
+
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
|
|
1525
|
+
this.logger.info(
|
|
1526
|
+
'services: skipping preauth catalog collection while unauthenticated as per the config'
|
|
1527
|
+
);
|
|
1528
|
+
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1401
1532
|
this.collectPreauthCatalog(email ? {email} : undefined).catch((error) => {
|
|
1402
1533
|
this.initFailed = true;
|
|
1403
1534
|
this.logger.error(
|
|
@@ -1407,6 +1538,85 @@ const Services = WebexPlugin.extend({
|
|
|
1407
1538
|
}
|
|
1408
1539
|
});
|
|
1409
1540
|
},
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Verified-ready initialization path. Blocks `webex.ready` until the initial
|
|
1544
|
+
* catalog fetch has settled (or timed out) AND any in-flight credentials
|
|
1545
|
+
* refresh has completed. Also handles the fresh-login case where OAuth
|
|
1546
|
+
* completes after `loaded` fires.
|
|
1547
|
+
*
|
|
1548
|
+
* @private
|
|
1549
|
+
* @param {ServiceCatalog} catalog
|
|
1550
|
+
* @returns {void}
|
|
1551
|
+
*/
|
|
1552
|
+
_initializeCatalogsGated(catalog) {
|
|
1553
|
+
// Wait for storage to be loaded before attempting to update the service
|
|
1554
|
+
// catalogs. We listen for 'loaded' instead of 'ready' because `services.ready`
|
|
1555
|
+
// now blocks `webex.ready` - listening to 'ready' would deadlock.
|
|
1556
|
+
this.listenToOnce(this.webex, 'loaded', async () => {
|
|
1557
|
+
const cachedCatalog = await this._loadCatalogFromCache();
|
|
1558
|
+
if (cachedCatalog) {
|
|
1559
|
+
catalog.isReady = true;
|
|
1560
|
+
await this._finalizeReady();
|
|
1561
|
+
|
|
1562
|
+
return; // skip initServiceCatalogs() on reload when cache exists
|
|
1563
|
+
}
|
|
1564
|
+
const {supertoken} = this.webex.credentials;
|
|
1565
|
+
|
|
1566
|
+
// Validate if the supertoken exists.
|
|
1567
|
+
if (supertoken && supertoken.access_token) {
|
|
1568
|
+
// `initServiceCatalogs` marks the catalog ready internally once the
|
|
1569
|
+
// postauth catalog is collected - even if it loses the timeout race
|
|
1570
|
+
// below, so a slow fetch still eventually flips `catalog.isReady`.
|
|
1571
|
+
Promise.race([this.initServiceCatalogs(), this._makeInitTimeout()])
|
|
1572
|
+
.catch((error) => {
|
|
1573
|
+
this.initFailed = true;
|
|
1574
|
+
this.logger.error(
|
|
1575
|
+
`services: failed to init initial services when credentials available, ${error?.message}`
|
|
1576
|
+
);
|
|
1577
|
+
})
|
|
1578
|
+
.finally(() => this._finalizeReady());
|
|
1579
|
+
} else {
|
|
1580
|
+
const {email} = this.webex.config;
|
|
1581
|
+
|
|
1582
|
+
// Handle fresh login: 'loaded' fires before OAuth completes, so listen
|
|
1583
|
+
// for `canAuthorize` flipping true and then collect the postauth catalog.
|
|
1584
|
+
this.listenToOnce(this.webex, 'change:canAuthorize', () => {
|
|
1585
|
+
if (this.webex.canAuthorize && !catalog.status.postauth.ready) {
|
|
1586
|
+
// `initServiceCatalogs` marks the catalog ready internally.
|
|
1587
|
+
this.initServiceCatalogs().catch((error) => {
|
|
1588
|
+
this.logger.error(
|
|
1589
|
+
`services: failed to init service catalogs after auth, ${error?.message}`
|
|
1590
|
+
);
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
if (this.webex.config?.services?.skipPreauthCatalogOnUnauthenticated === true) {
|
|
1596
|
+
// Skip the preauth catalog fetch (it will be collected manually
|
|
1597
|
+
// later), but still finalize `services.ready` so `webex.ready` is not
|
|
1598
|
+
// stalled while unauthenticated. No timeout is created here so there
|
|
1599
|
+
// is no stray timer or unhandled rejection.
|
|
1600
|
+
this.logger.info(
|
|
1601
|
+
'services: skipping preauth catalog collection while unauthenticated as per the config'
|
|
1602
|
+
);
|
|
1603
|
+
this._finalizeReady();
|
|
1604
|
+
} else {
|
|
1605
|
+
Promise.race([
|
|
1606
|
+
this.collectPreauthCatalog(email ? {email} : undefined),
|
|
1607
|
+
this._makeInitTimeout(),
|
|
1608
|
+
])
|
|
1609
|
+
.catch((error) => {
|
|
1610
|
+
this.initFailed = true;
|
|
1611
|
+
this.logger.error(
|
|
1612
|
+
`services: failed to init initial services when no credentials available, ${error?.message}`
|
|
1613
|
+
);
|
|
1614
|
+
})
|
|
1615
|
+
.finally(() => this._finalizeReady());
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
},
|
|
1410
1620
|
});
|
|
1411
1621
|
/* eslint-enable no-underscore-dangle */
|
|
1412
1622
|
|
|
@@ -3,6 +3,7 @@ import AmpState from 'ampersand-state';
|
|
|
3
3
|
import {union} from 'lodash';
|
|
4
4
|
import ServiceDetail from './service-detail';
|
|
5
5
|
import {IServiceDetail, ServiceGroup} from './types';
|
|
6
|
+
import {matchAllowedDomain, normalizeAllowedDomains} from '../domains';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @class
|
|
@@ -210,20 +211,14 @@ const ServiceCatalog = AmpState.extend({
|
|
|
210
211
|
},
|
|
211
212
|
|
|
212
213
|
/**
|
|
213
|
-
* Finds an allowed domain that matches a specific url.
|
|
214
|
+
* Finds an allowed domain that matches a specific url. The url's hostname
|
|
215
|
+
* must be the allowed domain itself or a subdomain of it.
|
|
214
216
|
*
|
|
215
217
|
* @param {string} url - The url to match the allowed domains against.
|
|
216
218
|
* @returns {string} - The matching allowed domain.
|
|
217
219
|
*/
|
|
218
220
|
findAllowedDomain(url: string): string {
|
|
219
|
-
|
|
220
|
-
const urlObj = new URL(url);
|
|
221
|
-
|
|
222
|
-
return this.allowedDomains.find((allowedDomain) => urlObj.host.includes(allowedDomain));
|
|
223
|
-
} catch {
|
|
224
|
-
// If the URL is invalid or can't be found, return undefined
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
221
|
+
return matchAllowedDomain(url, this.allowedDomains);
|
|
227
222
|
},
|
|
228
223
|
|
|
229
224
|
/**
|
|
@@ -282,7 +277,7 @@ const ServiceCatalog = AmpState.extend({
|
|
|
282
277
|
* @returns {void}
|
|
283
278
|
*/
|
|
284
279
|
setAllowedDomains(allowedDomains: Array<string>): void {
|
|
285
|
-
this.allowedDomains =
|
|
280
|
+
this.allowedDomains = normalizeAllowedDomains(allowedDomains);
|
|
286
281
|
},
|
|
287
282
|
|
|
288
283
|
/**
|
|
@@ -291,7 +286,7 @@ const ServiceCatalog = AmpState.extend({
|
|
|
291
286
|
* @returns {void}
|
|
292
287
|
*/
|
|
293
288
|
addAllowedDomains(newAllowedDomains: Array<string>): void {
|
|
294
|
-
this.allowedDomains = union(this.allowedDomains, newAllowedDomains);
|
|
289
|
+
this.allowedDomains = union(this.allowedDomains, normalizeAllowedDomains(newAllowedDomains));
|
|
295
290
|
},
|
|
296
291
|
|
|
297
292
|
/**
|