@webex/webex-core 3.10.0 → 3.11.0
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/dist/config.js +14 -0
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/auth.js +6 -8
- package/dist/interceptors/auth.js.map +1 -1
- package/dist/interceptors/default-options.js +6 -8
- package/dist/interceptors/default-options.js.map +1 -1
- package/dist/interceptors/embargo.js +6 -8
- package/dist/interceptors/embargo.js.map +1 -1
- package/dist/interceptors/network-timing.js +6 -8
- package/dist/interceptors/network-timing.js.map +1 -1
- package/dist/interceptors/payload-transformer.js +6 -8
- package/dist/interceptors/payload-transformer.js.map +1 -1
- package/dist/interceptors/proxy.js +7 -10
- package/dist/interceptors/proxy.js.map +1 -1
- package/dist/interceptors/rate-limit.js +7 -10
- package/dist/interceptors/rate-limit.js.map +1 -1
- package/dist/interceptors/redirect.js +9 -8
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/interceptors/request-event.js +6 -8
- package/dist/interceptors/request-event.js.map +1 -1
- package/dist/interceptors/request-logger.js +6 -8
- package/dist/interceptors/request-logger.js.map +1 -1
- package/dist/interceptors/request-timing.js +6 -8
- package/dist/interceptors/request-timing.js.map +1 -1
- package/dist/interceptors/response-logger.js +6 -8
- package/dist/interceptors/response-logger.js.map +1 -1
- package/dist/interceptors/user-agent.js +8 -11
- package/dist/interceptors/user-agent.js.map +1 -1
- package/dist/interceptors/webex-tracking-id.js +6 -8
- package/dist/interceptors/webex-tracking-id.js.map +1 -1
- package/dist/interceptors/webex-user-agent.js +7 -10
- package/dist/interceptors/webex-user-agent.js.map +1 -1
- package/dist/lib/batcher.js +1 -1
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/credentials/credentials.js +4 -6
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/grant-errors.js +18 -26
- package/dist/lib/credentials/grant-errors.js.map +1 -1
- package/dist/lib/credentials/index.js.map +1 -1
- package/dist/lib/credentials/scope.js.map +1 -1
- package/dist/lib/credentials/token-collection.js.map +1 -1
- package/dist/lib/credentials/token.js +5 -5
- package/dist/lib/credentials/token.js.map +1 -1
- package/dist/lib/interceptors/hostmap.js +6 -8
- package/dist/lib/interceptors/hostmap.js.map +1 -1
- package/dist/lib/interceptors/server-error.js +6 -8
- package/dist/lib/interceptors/server-error.js.map +1 -1
- package/dist/lib/interceptors/service.js +6 -8
- package/dist/lib/interceptors/service.js.map +1 -1
- package/dist/lib/metrics.js.map +1 -1
- package/dist/lib/page.js +5 -6
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/services/index.js.map +1 -1
- package/dist/lib/services/service-catalog.js +3 -3
- package/dist/lib/services/service-catalog.js.map +1 -1
- package/dist/lib/services/service-fed-ramp.js.map +1 -1
- package/dist/lib/services/service-host.js +1 -2
- package/dist/lib/services/service-host.js.map +1 -1
- package/dist/lib/services/service-registry.js +1 -2
- package/dist/lib/services/service-registry.js.map +1 -1
- package/dist/lib/services/service-state.js +1 -2
- package/dist/lib/services/service-state.js.map +1 -1
- package/dist/lib/services/service-url.js +11 -1
- package/dist/lib/services/service-url.js.map +1 -1
- package/dist/lib/services/services.js +485 -127
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/services-v2/index.js.map +1 -1
- package/dist/lib/services-v2/metrics.js.map +1 -1
- package/dist/lib/services-v2/service-catalog.js +7 -7
- package/dist/lib/services-v2/service-catalog.js.map +1 -1
- package/dist/lib/services-v2/service-detail.js.map +1 -1
- package/dist/lib/services-v2/service-fed-ramp.js.map +1 -1
- package/dist/lib/services-v2/services-v2.js +379 -51
- package/dist/lib/services-v2/services-v2.js.map +1 -1
- package/dist/lib/services-v2/types.js.map +1 -1
- package/dist/lib/stateless-webex-plugin.js +3 -4
- package/dist/lib/stateless-webex-plugin.js.map +1 -1
- package/dist/lib/storage/decorators.js.map +1 -1
- package/dist/lib/storage/errors.js +7 -9
- package/dist/lib/storage/errors.js.map +1 -1
- package/dist/lib/storage/index.js.map +1 -1
- package/dist/lib/storage/make-webex-plugin-store.js +14 -5
- package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
- package/dist/lib/storage/make-webex-store.js +13 -5
- package/dist/lib/storage/make-webex-store.js.map +1 -1
- package/dist/lib/storage/memory-store-adapter.js.map +1 -1
- package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
- package/dist/lib/webex-http-error.js +8 -11
- package/dist/lib/webex-http-error.js.map +1 -1
- package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
- package/dist/lib/webex-plugin.js.map +1 -1
- package/dist/plugins/logger.js +1 -1
- package/dist/plugins/logger.js.map +1 -1
- package/dist/webex-core.js +11 -11
- package/dist/webex-core.js.map +1 -1
- package/dist/webex-internal-core.js.map +1 -1
- package/package.json +13 -13
- package/src/config.js +15 -0
- package/src/interceptors/redirect.js +4 -0
- package/src/lib/services/service-url.js +9 -1
- package/src/lib/services/services.js +315 -7
- package/src/lib/services-v2/index.ts +0 -1
- package/src/lib/services-v2/service-catalog.ts +4 -4
- package/src/lib/services-v2/services-v2.ts +307 -7
- package/src/lib/services-v2/types.ts +13 -0
- package/test/fixtures/host-catalog-v2.ts +1 -1
- package/test/integration/spec/services/service-catalog.js +10 -4
- package/test/integration/spec/services/services.js +65 -9
- package/test/integration/spec/services-v2/service-catalog.js +2 -2
- package/test/integration/spec/services-v2/services-v2.js +56 -6
- package/test/unit/spec/interceptors/redirect.js +98 -0
- package/test/unit/spec/services/service-url.js +110 -0
- package/test/unit/spec/services/services.js +411 -2
- package/test/unit/spec/services-v2/services-v2.ts +316 -0
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
Service,
|
|
15
15
|
ServiceHostmap,
|
|
16
16
|
ServiceGroup,
|
|
17
|
+
ServiceHost,
|
|
18
|
+
SelectionMeta,
|
|
17
19
|
} from './types';
|
|
18
20
|
|
|
19
21
|
const trailingSlashes = /(?:^\/)|(?:\/$)/;
|
|
@@ -27,6 +29,9 @@ const CLUSTER_SERVICE = process.env.WEBEX_CONVERSATION_CLUSTER_SERVICE || DEFAUL
|
|
|
27
29
|
const DEFAULT_CLUSTER_IDENTIFIER =
|
|
28
30
|
process.env.WEBEX_CONVERSATION_DEFAULT_CLUSTER || `${DEFAULT_CLUSTER}:${CLUSTER_SERVICE}`;
|
|
29
31
|
|
|
32
|
+
const CATALOG_CACHE_KEY_V2 = 'services.v2.u2cHostMap';
|
|
33
|
+
const CATALOG_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
34
|
+
|
|
30
35
|
/* eslint-disable no-underscore-dangle */
|
|
31
36
|
/**
|
|
32
37
|
* @class
|
|
@@ -55,6 +60,46 @@ const Services = WebexPlugin.extend({
|
|
|
55
60
|
return this._catalogs.get(this.webex);
|
|
56
61
|
},
|
|
57
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Safely access localStorage if available; returns the Storage or null.
|
|
65
|
+
* @returns {Storage | null}
|
|
66
|
+
*/
|
|
67
|
+
_getLocalStorageSafe(): Storage | null {
|
|
68
|
+
if (typeof window !== 'undefined' && (window as any).localStorage) {
|
|
69
|
+
return (window as any).localStorage as Storage;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Determine the intended preauth selection based on the current context.
|
|
77
|
+
* @param {string} [currentOrgId]
|
|
78
|
+
* @returns {{selectionType: string, selectionValue: string}}
|
|
79
|
+
*/
|
|
80
|
+
getIntendedPreauthSelection(currentOrgId?: string): {
|
|
81
|
+
selectionType: string;
|
|
82
|
+
selectionValue: string;
|
|
83
|
+
} {
|
|
84
|
+
if (this.webex.credentials?.canAuthorize) {
|
|
85
|
+
if (currentOrgId) {
|
|
86
|
+
return {selectionType: 'orgId', selectionValue: currentOrgId};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const emailConfig = this.webex.config && this.webex.config.email;
|
|
91
|
+
|
|
92
|
+
if (typeof emailConfig === 'string' && emailConfig.trim()) {
|
|
93
|
+
return {
|
|
94
|
+
selectionType: 'emailhash',
|
|
95
|
+
selectionValue: sha256(emailConfig.toLowerCase()).toString(),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// fall back to proximity mode when no orgId or email available
|
|
100
|
+
return {selectionType: 'mode', selectionValue: 'DEFAULT_BY_PROXIMITY'};
|
|
101
|
+
},
|
|
102
|
+
|
|
58
103
|
/**
|
|
59
104
|
* Get a service url from the current services list by name
|
|
60
105
|
* from the associated instance catalog.
|
|
@@ -104,6 +149,55 @@ const Services = WebexPlugin.extend({
|
|
|
104
149
|
return catalog.markFailedServiceUrl(url);
|
|
105
150
|
},
|
|
106
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Get all Mobius cluster host entries from the v2 services list.
|
|
154
|
+
* @returns {Array<ServiceHost>} - An array of `ServiceHost` objects.
|
|
155
|
+
*/
|
|
156
|
+
getMobiusClusters(): Array<ServiceHost> {
|
|
157
|
+
this.logger.info('services: fetching mobius clusters');
|
|
158
|
+
const clusters: Array<ServiceHost> = [];
|
|
159
|
+
const services: Array<Service> = this._services || [];
|
|
160
|
+
|
|
161
|
+
services
|
|
162
|
+
.filter(
|
|
163
|
+
(service) =>
|
|
164
|
+
service?.serviceName === 'mobius' &&
|
|
165
|
+
Array.isArray(service.serviceUrls) &&
|
|
166
|
+
service.serviceUrls.length > 0
|
|
167
|
+
)
|
|
168
|
+
.forEach((service) => {
|
|
169
|
+
service.serviceUrls.forEach((serviceUrl) => {
|
|
170
|
+
const modifiedHost = serviceUrl.baseUrl.replace('https://', '').replace('/api/v1', '');
|
|
171
|
+
if (!clusters.find((c) => c && c.host === modifiedHost)) {
|
|
172
|
+
clusters.push({
|
|
173
|
+
host: modifiedHost,
|
|
174
|
+
priority: serviceUrl.priority,
|
|
175
|
+
id: service.id,
|
|
176
|
+
ttl: 0,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return clusters;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check is valid host from services list.
|
|
187
|
+
* @param {string} host
|
|
188
|
+
* @returns {Boolean}
|
|
189
|
+
*/
|
|
190
|
+
isValidHost(host: string): boolean {
|
|
191
|
+
const services: Array<Service> = this._services || [];
|
|
192
|
+
|
|
193
|
+
return services.some((service) => {
|
|
194
|
+
return service.serviceUrls.some((serviceUrl) => {
|
|
195
|
+
const serviceHost = serviceUrl?.baseUrl && new URL(serviceUrl.baseUrl)?.host;
|
|
196
|
+
|
|
197
|
+
return serviceHost === host;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
},
|
|
107
201
|
/**
|
|
108
202
|
* saves all the services from the pre and post catalog service
|
|
109
203
|
* @param {ActiveServices} activeServices
|
|
@@ -200,6 +294,18 @@ const Services = WebexPlugin.extend({
|
|
|
200
294
|
serviceHostMap?.services,
|
|
201
295
|
serviceHostMap?.timestamp
|
|
202
296
|
);
|
|
297
|
+
// Build selection metadata for caching discrimination (preauth/signin)
|
|
298
|
+
let selectionMeta: SelectionMeta | undefined;
|
|
299
|
+
if (serviceGroup === 'preauth' || serviceGroup === 'signin') {
|
|
300
|
+
const key = formattedQuery && Object.keys(formattedQuery || {})[0];
|
|
301
|
+
if (key) {
|
|
302
|
+
selectionMeta = {
|
|
303
|
+
selectionType: key,
|
|
304
|
+
selectionValue: formattedQuery[key],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
this._cacheCatalog(serviceGroup, serviceHostMap, selectionMeta);
|
|
203
309
|
this.updateCredentialsConfig();
|
|
204
310
|
catalog.status[serviceGroup].collecting = false;
|
|
205
311
|
})
|
|
@@ -476,10 +582,14 @@ const Services = WebexPlugin.extend({
|
|
|
476
582
|
({countryCode, timezone} = clientRegionInfo);
|
|
477
583
|
}
|
|
478
584
|
|
|
479
|
-
// Send the user activation request
|
|
585
|
+
// Send the user activation request.
|
|
586
|
+
// Use user-onboarding service if configured, otherwise use license service.
|
|
587
|
+
const useUserOnboarding =
|
|
588
|
+
this.webex.config.services?.useUserOnboardingServiceForActivations;
|
|
589
|
+
|
|
480
590
|
return this.request({
|
|
481
|
-
service: 'license',
|
|
482
|
-
resource: 'users/activations',
|
|
591
|
+
service: useUserOnboarding ? 'user-onboarding' : 'license',
|
|
592
|
+
resource: useUserOnboarding ? 'api/v1/users/activations' : 'users/activations',
|
|
483
593
|
method: 'POST',
|
|
484
594
|
headers: {
|
|
485
595
|
accept: 'application/json',
|
|
@@ -512,7 +622,7 @@ const Services = WebexPlugin.extend({
|
|
|
512
622
|
updateCatalog(serviceGroup: ServiceGroup, hostMap: ServiceHostmap): Promise<void> {
|
|
513
623
|
const catalog = this._getCatalog();
|
|
514
624
|
|
|
515
|
-
const serviceHostMap = this._formatReceivedHostmap(hostMap);
|
|
625
|
+
const serviceHostMap = this._formatReceivedHostmap(hostMap || {});
|
|
516
626
|
|
|
517
627
|
return catalog.updateServiceGroups(
|
|
518
628
|
serviceGroup,
|
|
@@ -757,7 +867,7 @@ const Services = WebexPlugin.extend({
|
|
|
757
867
|
_formatReceivedHostmap({services, activeServices, timestamp, orgId, format}) {
|
|
758
868
|
const formattedHostmap: ServiceHostmap = {
|
|
759
869
|
activeServices,
|
|
760
|
-
services: services
|
|
870
|
+
services: services?.map((service) => this._formatHostMapEntry(service)),
|
|
761
871
|
timestamp,
|
|
762
872
|
orgId,
|
|
763
873
|
format,
|
|
@@ -881,6 +991,190 @@ const Services = WebexPlugin.extend({
|
|
|
881
991
|
return url.replace(data.defaultUrl, data.priorityUrl);
|
|
882
992
|
},
|
|
883
993
|
|
|
994
|
+
/**
|
|
995
|
+
* @private
|
|
996
|
+
* Cache the catalog in the bounded storage.
|
|
997
|
+
* @param {ServiceGroup} serviceGroup - preauth, signin, postauth
|
|
998
|
+
* @param {ServiceHostmap} hostMap - The hostmap to cache
|
|
999
|
+
* @param {object} [meta] - Optional selection metadata for cache discrimination
|
|
1000
|
+
* @returns {Promise<void>}
|
|
1001
|
+
*/
|
|
1002
|
+
async _cacheCatalog(
|
|
1003
|
+
serviceGroup: ServiceGroup,
|
|
1004
|
+
hostMap: ServiceHostmap,
|
|
1005
|
+
meta?: SelectionMeta
|
|
1006
|
+
): Promise<void> {
|
|
1007
|
+
let current: {orgId?: string; env?: {fedramp?: boolean; u2cDiscoveryUrl?: string}} = {};
|
|
1008
|
+
let orgId: string | undefined;
|
|
1009
|
+
try {
|
|
1010
|
+
// Respect calling.cacheU2C toggle; if disabled, skip writing cache
|
|
1011
|
+
if (!this.webex.config?.calling?.cacheU2C) {
|
|
1012
|
+
this.logger.info(`services: skipping cache write for ${serviceGroup} as per the config`);
|
|
1013
|
+
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
try {
|
|
1018
|
+
const ls = this._getLocalStorageSafe();
|
|
1019
|
+
const cachedJson = ls ? ls.getItem(CATALOG_CACHE_KEY_V2) : null;
|
|
1020
|
+
current = cachedJson ? JSON.parse(cachedJson) : {};
|
|
1021
|
+
} catch {
|
|
1022
|
+
current = {};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
try {
|
|
1026
|
+
const {credentials} = this.webex;
|
|
1027
|
+
orgId = credentials.getOrgId();
|
|
1028
|
+
} catch {
|
|
1029
|
+
orgId = current.orgId;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Capture environment fingerprint to invalidate cache across env changes
|
|
1033
|
+
let {env} = current;
|
|
1034
|
+
const fedramp = !!this.webex?.config?.fedramp;
|
|
1035
|
+
const u2cDiscoveryUrl = this.webex?.config?.services?.discovery?.u2c;
|
|
1036
|
+
env = {fedramp, u2cDiscoveryUrl};
|
|
1037
|
+
|
|
1038
|
+
const updated = {
|
|
1039
|
+
...current,
|
|
1040
|
+
orgId: orgId || current.orgId,
|
|
1041
|
+
env: env || current.env,
|
|
1042
|
+
// When selection meta is provided, store as an object; otherwise keep legacy shape
|
|
1043
|
+
[serviceGroup]: meta ? {hostMap, meta} : hostMap,
|
|
1044
|
+
cachedAt: Date.now(),
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const ls = this._getLocalStorageSafe();
|
|
1048
|
+
if (ls) {
|
|
1049
|
+
ls.setItem(CATALOG_CACHE_KEY_V2, JSON.stringify(updated));
|
|
1050
|
+
}
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
this.logger.warn('services: error caching catalog', e);
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
|
|
1056
|
+
/**
|
|
1057
|
+
* @private
|
|
1058
|
+
* Load the catalog from cache and hydrate the in-memory ServiceCatalog.
|
|
1059
|
+
* @returns {Promise<boolean>} true if cache was loaded, false otherwise
|
|
1060
|
+
*/
|
|
1061
|
+
async _loadCatalogFromCache(): Promise<boolean> {
|
|
1062
|
+
let currentOrgId: string | undefined;
|
|
1063
|
+
try {
|
|
1064
|
+
// Respect calling.cacheU2C toggle; if disabled, skip using cache
|
|
1065
|
+
if (!this.webex.config?.calling?.cacheU2C) {
|
|
1066
|
+
this.logger.info('services: skipping cache warm-up as per the cache config');
|
|
1067
|
+
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const ls = this._getLocalStorageSafe();
|
|
1072
|
+
if (!ls) {
|
|
1073
|
+
this.logger.info('services: skipping cache warm-up as no localStorage is available');
|
|
1074
|
+
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
const cachedJson = ls.getItem(CATALOG_CACHE_KEY_V2);
|
|
1078
|
+
const cached = cachedJson ? JSON.parse(cachedJson) : undefined;
|
|
1079
|
+
if (!cached) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// TTL enforcement
|
|
1084
|
+
const cachedAt = Number(cached.cachedAt) || 0;
|
|
1085
|
+
if (!cachedAt || Date.now() - cachedAt > CATALOG_TTL_MS) {
|
|
1086
|
+
this.clearCatalogCache();
|
|
1087
|
+
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// If authorized, ensure cached org matches
|
|
1092
|
+
try {
|
|
1093
|
+
if (this.webex.credentials?.canAuthorize) {
|
|
1094
|
+
const {credentials} = this.webex;
|
|
1095
|
+
currentOrgId = credentials.getOrgId();
|
|
1096
|
+
if (cached.orgId && cached.orgId !== currentOrgId) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
} catch (e) {
|
|
1101
|
+
this.logger.warn('services: error checking orgId', e);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Ensure cached environment matches current environment
|
|
1105
|
+
|
|
1106
|
+
const fedramp = !!this.webex.config?.fedramp;
|
|
1107
|
+
const u2cDiscoveryUrl = this.webex.config?.services?.discovery?.u2c;
|
|
1108
|
+
const currentEnv = {fedramp, u2cDiscoveryUrl};
|
|
1109
|
+
if (cached.env) {
|
|
1110
|
+
const sameEnv =
|
|
1111
|
+
cached.env.fedramp === currentEnv.fedramp &&
|
|
1112
|
+
cached.env.u2cDiscoveryUrl === currentEnv.u2cDiscoveryUrl;
|
|
1113
|
+
if (!sameEnv) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const catalog = this._getCatalog();
|
|
1119
|
+
const groups: Array<ServiceGroup> = ['preauth', 'signin', 'postauth'];
|
|
1120
|
+
|
|
1121
|
+
groups.forEach((serviceGroup) => {
|
|
1122
|
+
const cachedGroup = cached[serviceGroup];
|
|
1123
|
+
if (!cachedGroup) {
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Support legacy (hostMap) and new ({hostMap, meta}) shapes
|
|
1128
|
+
const hostMap: ServiceHostmap =
|
|
1129
|
+
cachedGroup && cachedGroup.hostMap ? cachedGroup.hostMap : cachedGroup;
|
|
1130
|
+
const meta: SelectionMeta | undefined = cachedGroup?.meta;
|
|
1131
|
+
|
|
1132
|
+
if (serviceGroup === 'preauth' && meta) {
|
|
1133
|
+
// For proximity-based selection, always fetch fresh to respect IP/region changes
|
|
1134
|
+
if (meta.selectionType === 'mode') {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const intended = this.getIntendedPreauthSelection(currentOrgId);
|
|
1139
|
+
const matches =
|
|
1140
|
+
intended &&
|
|
1141
|
+
intended.selectionType === meta.selectionType &&
|
|
1142
|
+
intended.selectionValue === meta.selectionValue;
|
|
1143
|
+
if (!matches) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (hostMap) {
|
|
1149
|
+
catalog.updateServiceGroups(serviceGroup, hostMap?.services, hostMap?.timestamp);
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
this.updateCredentialsConfig();
|
|
1154
|
+
|
|
1155
|
+
return true;
|
|
1156
|
+
} catch (e) {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
},
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Clear the catalog cache from the bounded storage (v2).
|
|
1163
|
+
* @returns {Promise<void>}
|
|
1164
|
+
*/
|
|
1165
|
+
clearCatalogCache(): Promise<void> {
|
|
1166
|
+
try {
|
|
1167
|
+
const ls = this._getLocalStorageSafe();
|
|
1168
|
+
if (ls) {
|
|
1169
|
+
ls.removeItem(CATALOG_CACHE_KEY_V2);
|
|
1170
|
+
}
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
this.logger.warn('services: error clearing catalog cache', e);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return Promise.resolve();
|
|
1176
|
+
},
|
|
1177
|
+
|
|
884
1178
|
/**
|
|
885
1179
|
* @private
|
|
886
1180
|
* Simplified method wrapper for sending a request to get
|
|
@@ -924,7 +1218,7 @@ const Services = WebexPlugin.extend({
|
|
|
924
1218
|
|
|
925
1219
|
return this.webex.internal.newMetrics.callDiagnosticLatencies
|
|
926
1220
|
.measureLatency(() => this.request(requestObject), 'internal.get.u2c.time')
|
|
927
|
-
.then(({body}) => this._formatReceivedHostmap(body));
|
|
1221
|
+
.then(({body}) => this._formatReceivedHostmap(body || {}));
|
|
928
1222
|
},
|
|
929
1223
|
|
|
930
1224
|
/**
|
|
@@ -1040,7 +1334,13 @@ const Services = WebexPlugin.extend({
|
|
|
1040
1334
|
|
|
1041
1335
|
// wait for webex instance to be ready before attempting
|
|
1042
1336
|
// to update the service catalogs
|
|
1043
|
-
this.listenToOnce(this.webex, 'ready', () => {
|
|
1337
|
+
this.listenToOnce(this.webex, 'ready', async () => {
|
|
1338
|
+
const warmed = await this._loadCatalogFromCache();
|
|
1339
|
+
if (warmed) {
|
|
1340
|
+
catalog.isReady = true;
|
|
1341
|
+
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1044
1344
|
const {supertoken} = this.webex.credentials;
|
|
1045
1345
|
// Validate if the supertoken exists.
|
|
1046
1346
|
if (supertoken && supertoken.access_token) {
|
|
@@ -2,6 +2,19 @@ type ServiceName = string;
|
|
|
2
2
|
type ClusterId = string;
|
|
3
3
|
export type ServiceGroup = 'discovery' | 'override' | 'preauth' | 'postauth' | 'signin';
|
|
4
4
|
|
|
5
|
+
export type SelectionMeta = {
|
|
6
|
+
selectionType: string;
|
|
7
|
+
selectionValue: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ServiceHost = {
|
|
11
|
+
host: string;
|
|
12
|
+
ttl: number;
|
|
13
|
+
priority: number;
|
|
14
|
+
id: string;
|
|
15
|
+
homeCluster?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
5
18
|
export type ServiceUrl = {
|
|
6
19
|
baseUrl: string;
|
|
7
20
|
host: string;
|
|
@@ -510,13 +510,19 @@ describe('webex-core', () => {
|
|
|
510
510
|
);
|
|
511
511
|
|
|
512
512
|
it('resolves to an authed u2c hostmap when no params specified', () => {
|
|
513
|
-
assert.typeOf(fullRemoteHM, '
|
|
514
|
-
assert.
|
|
513
|
+
assert.typeOf(fullRemoteHM, 'object');
|
|
514
|
+
assert.property(fullRemoteHM, 'serviceLinks');
|
|
515
|
+
assert.property(fullRemoteHM, 'hostCatalog');
|
|
516
|
+
assert.equal(fullRemoteHM.format, 'hostmap');
|
|
517
|
+
assert.isAbove(Object.keys(fullRemoteHM.serviceLinks).length, 0);
|
|
515
518
|
});
|
|
516
519
|
|
|
517
520
|
it('resolves to a limited u2c hostmap when params specified', () => {
|
|
518
|
-
assert.typeOf(limitedRemoteHM, '
|
|
519
|
-
assert.
|
|
521
|
+
assert.typeOf(limitedRemoteHM, 'object');
|
|
522
|
+
assert.property(limitedRemoteHM, 'serviceLinks');
|
|
523
|
+
assert.property(limitedRemoteHM, 'hostCatalog');
|
|
524
|
+
assert.equal(limitedRemoteHM.format, 'hostmap');
|
|
525
|
+
assert.isAbove(Object.keys(limitedRemoteHM.serviceLinks).length, 0);
|
|
520
526
|
});
|
|
521
527
|
|
|
522
528
|
it('rejects if the params provided are invalid', () =>
|
|
@@ -404,10 +404,13 @@ describe('webex-core', () => {
|
|
|
404
404
|
assert.isTrue(catalog.isReady);
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
-
it('should call services#initServiceCatalogs() on webex ready', () => {
|
|
407
|
+
it('should call services#initServiceCatalogs() on webex ready', async () => {
|
|
408
|
+
services._loadCatalogFromCache = sinon.stub().resolves(false);
|
|
408
409
|
services.initServiceCatalogs = sinon.stub().resolves();
|
|
409
410
|
services.initialize();
|
|
410
411
|
webex.trigger('ready');
|
|
412
|
+
// Wait for the async 'ready' handler to complete
|
|
413
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
411
414
|
assert.called(services.initServiceCatalogs);
|
|
412
415
|
assert.isTrue(catalog.isReady);
|
|
413
416
|
});
|
|
@@ -697,7 +700,11 @@ describe('webex-core', () => {
|
|
|
697
700
|
|
|
698
701
|
it('updates query.email to be emailhash-ed using SHA256', (done) => {
|
|
699
702
|
catalog.updateServiceUrls = sinon.stub().returns({}); // returns `this`
|
|
700
|
-
services._fetchNewServiceHostmap = sinon.stub().resolves(
|
|
703
|
+
services._fetchNewServiceHostmap = sinon.stub().resolves({
|
|
704
|
+
serviceLinks: {},
|
|
705
|
+
hostCatalog: {},
|
|
706
|
+
format: 'hostmap',
|
|
707
|
+
});
|
|
701
708
|
|
|
702
709
|
services
|
|
703
710
|
.updateServices({
|
|
@@ -825,9 +832,14 @@ describe('webex-core', () => {
|
|
|
825
832
|
const unauthServices = unauthWebex.internal.services;
|
|
826
833
|
let sandbox = null;
|
|
827
834
|
|
|
828
|
-
const getActivationRequest = (requestStub) => {
|
|
835
|
+
const getActivationRequest = (requestStub, useUserOnboarding = false) => {
|
|
836
|
+
const expectedService = useUserOnboarding ? 'user-onboarding' : 'license';
|
|
837
|
+
const expectedResource = useUserOnboarding
|
|
838
|
+
? 'api/v1/users/activations'
|
|
839
|
+
: 'users/activations';
|
|
829
840
|
const requests = requestStub.args.filter(
|
|
830
|
-
([request]) =>
|
|
841
|
+
([request]) =>
|
|
842
|
+
request.service === expectedService && request.resource === expectedResource
|
|
831
843
|
);
|
|
832
844
|
|
|
833
845
|
assert.strictEqual(requests.length, 1);
|
|
@@ -906,7 +918,7 @@ describe('webex-core', () => {
|
|
|
906
918
|
assert.equal(Object.keys(unauthServices.list(false, 'postauth')).length, 0);
|
|
907
919
|
}));
|
|
908
920
|
|
|
909
|
-
it
|
|
921
|
+
it('validates new user with activationOptions suppressEmail true', () =>
|
|
910
922
|
unauthServices
|
|
911
923
|
.validateUser({
|
|
912
924
|
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
@@ -990,6 +1002,44 @@ describe('webex-core', () => {
|
|
|
990
1002
|
);
|
|
991
1003
|
});
|
|
992
1004
|
});
|
|
1005
|
+
|
|
1006
|
+
it('uses the license service by default', () => {
|
|
1007
|
+
const requestStub = sandbox.spy(unauthServices, 'request');
|
|
1008
|
+
|
|
1009
|
+
return unauthServices
|
|
1010
|
+
.validateUser({
|
|
1011
|
+
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
1012
|
+
activationOptions: {suppressEmail: true},
|
|
1013
|
+
})
|
|
1014
|
+
.then(() => {
|
|
1015
|
+
const request = getActivationRequest(requestStub, false);
|
|
1016
|
+
assert.strictEqual(request.service, 'license');
|
|
1017
|
+
assert.strictEqual(request.resource, 'users/activations');
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('uses the user-onboarding service when useUserOnboardingServiceForActivations config is true', () => {
|
|
1022
|
+
const userOnboardingWebex = new WebexCore({
|
|
1023
|
+
config: {
|
|
1024
|
+
services: {
|
|
1025
|
+
useUserOnboardingServiceForActivations: true,
|
|
1026
|
+
},
|
|
1027
|
+
},
|
|
1028
|
+
});
|
|
1029
|
+
const userOnboardingServices = userOnboardingWebex.internal.services;
|
|
1030
|
+
const requestStub = sandbox.spy(userOnboardingServices, 'request');
|
|
1031
|
+
|
|
1032
|
+
return userOnboardingServices
|
|
1033
|
+
.validateUser({
|
|
1034
|
+
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
1035
|
+
activationOptions: {suppressEmail: true},
|
|
1036
|
+
})
|
|
1037
|
+
.then(() => {
|
|
1038
|
+
const request = getActivationRequest(requestStub, true);
|
|
1039
|
+
assert.strictEqual(request.service, 'user-onboarding');
|
|
1040
|
+
assert.strictEqual(request.resource, 'api/v1/users/activations');
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
993
1043
|
});
|
|
994
1044
|
|
|
995
1045
|
describe('#waitForService()', () => {
|
|
@@ -1214,13 +1264,19 @@ describe('webex-core', () => {
|
|
|
1214
1264
|
);
|
|
1215
1265
|
|
|
1216
1266
|
it('resolves to an authed u2c hostmap when no params specified', () => {
|
|
1217
|
-
assert.typeOf(fullRemoteHM, '
|
|
1218
|
-
assert.
|
|
1267
|
+
assert.typeOf(fullRemoteHM, 'object');
|
|
1268
|
+
assert.property(fullRemoteHM, 'serviceLinks');
|
|
1269
|
+
assert.property(fullRemoteHM, 'hostCatalog');
|
|
1270
|
+
assert.equal(fullRemoteHM.format, 'hostmap');
|
|
1271
|
+
assert.isAbove(Object.keys(fullRemoteHM.serviceLinks).length, 0);
|
|
1219
1272
|
});
|
|
1220
1273
|
|
|
1221
1274
|
it('resolves to a limited u2c hostmap when params specified', () => {
|
|
1222
|
-
assert.typeOf(limitedRemoteHM, '
|
|
1223
|
-
assert.
|
|
1275
|
+
assert.typeOf(limitedRemoteHM, 'object');
|
|
1276
|
+
assert.property(limitedRemoteHM, 'serviceLinks');
|
|
1277
|
+
assert.property(limitedRemoteHM, 'hostCatalog');
|
|
1278
|
+
assert.equal(limitedRemoteHM.format, 'hostmap');
|
|
1279
|
+
assert.isAbove(Object.keys(limitedRemoteHM.serviceLinks).length, 0);
|
|
1224
1280
|
});
|
|
1225
1281
|
|
|
1226
1282
|
it('rejects if the params provided are invalid', () =>
|
|
@@ -599,7 +599,7 @@ describe('webex-core', () => {
|
|
|
599
599
|
],
|
|
600
600
|
orgId: '3e0e410f-f83f-4ee4-ac32-12692e99355c',
|
|
601
601
|
timestamp: '1745533341',
|
|
602
|
-
format: '
|
|
602
|
+
format: 'U2CV2',
|
|
603
603
|
};
|
|
604
604
|
|
|
605
605
|
catalog.updateServiceGroups('preauth', formattedHM.services);
|
|
@@ -686,7 +686,7 @@ describe('webex-core', () => {
|
|
|
686
686
|
],
|
|
687
687
|
orgId: '3e0e410f-f83f-4ee4-ac32-12692e99355c',
|
|
688
688
|
timestamp: '1745533341',
|
|
689
|
-
format: '
|
|
689
|
+
format: 'U2CV2',
|
|
690
690
|
};
|
|
691
691
|
const notInOrderFormattedHM = services._formatReceivedHostmap(notInOrderServiceHM);
|
|
692
692
|
const checkFormattedHM = cloneDeep(notInOrderFormattedHM);
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
formattedServiceHostmapEntryConv,
|
|
24
24
|
formattedServiceHostmapEntryMercury,
|
|
25
25
|
formattedServiceHostmapEntryTest,
|
|
26
|
-
serviceHostmapV2
|
|
26
|
+
serviceHostmapV2,
|
|
27
27
|
} from '../../../fixtures/host-catalog-v2';
|
|
28
28
|
|
|
29
29
|
// /* eslint-disable no-underscore-dangle */
|
|
@@ -316,10 +316,13 @@ describe('webex-core', () => {
|
|
|
316
316
|
assert.isTrue(catalog.isReady);
|
|
317
317
|
});
|
|
318
318
|
|
|
319
|
-
it('should call services#initServiceCatalogs() on webex ready', () => {
|
|
319
|
+
it('should call services#initServiceCatalogs() on webex ready', async () => {
|
|
320
|
+
services._loadCatalogFromCache = sinon.stub().resolves(false);
|
|
320
321
|
services.initServiceCatalogs = sinon.stub().resolves();
|
|
321
322
|
services.initialize();
|
|
322
323
|
webex.trigger('ready');
|
|
324
|
+
// Wait for the async 'ready' handler to complete
|
|
325
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
323
326
|
assert.called(services.initServiceCatalogs);
|
|
324
327
|
assert.isTrue(catalog.isReady);
|
|
325
328
|
});
|
|
@@ -413,7 +416,11 @@ describe('webex-core', () => {
|
|
|
413
416
|
.initServiceCatalogs(true)
|
|
414
417
|
// services#updateServices() gets called once by the limited catalog
|
|
415
418
|
// retrieval and should get called again when authorized.
|
|
416
|
-
.then(
|
|
419
|
+
.then(
|
|
420
|
+
() =>
|
|
421
|
+
assert.calledTwice(services.updateServices) &&
|
|
422
|
+
assert.calledWith(services.updateServices, sinon.match({forceRefresh: true}))
|
|
423
|
+
)
|
|
417
424
|
);
|
|
418
425
|
});
|
|
419
426
|
});
|
|
@@ -748,9 +755,14 @@ describe('webex-core', () => {
|
|
|
748
755
|
const unauthServices = unauthWebex.internal.services;
|
|
749
756
|
let sandbox = null;
|
|
750
757
|
|
|
751
|
-
const getActivationRequest = (requestStub) => {
|
|
758
|
+
const getActivationRequest = (requestStub, useUserOnboarding = false) => {
|
|
759
|
+
const expectedService = useUserOnboarding ? 'user-onboarding' : 'license';
|
|
760
|
+
const expectedResource = useUserOnboarding
|
|
761
|
+
? 'api/v1/users/activations'
|
|
762
|
+
: 'users/activations';
|
|
752
763
|
const requests = requestStub.args.filter(
|
|
753
|
-
([request]) =>
|
|
764
|
+
([request]) =>
|
|
765
|
+
request.service === expectedService && request.resource === expectedResource
|
|
754
766
|
);
|
|
755
767
|
|
|
756
768
|
assert.strictEqual(requests.length, 1);
|
|
@@ -823,7 +835,7 @@ describe('webex-core', () => {
|
|
|
823
835
|
assert.equal(r.user.verificationEmailTriggered, true);
|
|
824
836
|
}));
|
|
825
837
|
|
|
826
|
-
it
|
|
838
|
+
it('validates new user with activationOptions suppressEmail true', () =>
|
|
827
839
|
unauthServices
|
|
828
840
|
.validateUser({
|
|
829
841
|
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
@@ -895,6 +907,44 @@ describe('webex-core', () => {
|
|
|
895
907
|
);
|
|
896
908
|
});
|
|
897
909
|
});
|
|
910
|
+
|
|
911
|
+
it('uses the license service by default', () => {
|
|
912
|
+
const requestStub = sandbox.spy(unauthServices, 'request');
|
|
913
|
+
|
|
914
|
+
return unauthServices
|
|
915
|
+
.validateUser({
|
|
916
|
+
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
917
|
+
activationOptions: {suppressEmail: true},
|
|
918
|
+
})
|
|
919
|
+
.then(() => {
|
|
920
|
+
const request = getActivationRequest(requestStub, false);
|
|
921
|
+
assert.strictEqual(request.service, 'license');
|
|
922
|
+
assert.strictEqual(request.resource, 'users/activations');
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
it('uses the user-onboarding service when useUserOnboardingServiceForActivations config is true', () => {
|
|
927
|
+
const userOnboardingWebex = new WebexCore({
|
|
928
|
+
config: {
|
|
929
|
+
services: {
|
|
930
|
+
useUserOnboardingServiceForActivations: true,
|
|
931
|
+
},
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
const userOnboardingServices = userOnboardingWebex.internal.services;
|
|
935
|
+
const requestStub = sandbox.spy(userOnboardingServices, 'request');
|
|
936
|
+
|
|
937
|
+
return userOnboardingServices
|
|
938
|
+
.validateUser({
|
|
939
|
+
email: `Collabctg+webex-js-sdk-${uuid.v4()}@gmail.com`,
|
|
940
|
+
activationOptions: {suppressEmail: true},
|
|
941
|
+
})
|
|
942
|
+
.then(() => {
|
|
943
|
+
const request = getActivationRequest(requestStub, true);
|
|
944
|
+
assert.strictEqual(request.service, 'user-onboarding');
|
|
945
|
+
assert.strictEqual(request.resource, 'api/v1/users/activations');
|
|
946
|
+
});
|
|
947
|
+
});
|
|
898
948
|
});
|
|
899
949
|
|
|
900
950
|
describe('#waitForService()', () => {
|