@webex/webex-core 3.8.0 → 3.8.1-next.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 (55) hide show
  1. package/README.md +87 -27
  2. package/dist/index.js +43 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/interceptors/redirect.js +18 -0
  5. package/dist/interceptors/redirect.js.map +1 -1
  6. package/dist/lib/batcher.js +1 -1
  7. package/dist/lib/credentials/credentials.js +1 -1
  8. package/dist/lib/credentials/token.js +1 -1
  9. package/dist/lib/services/services.js +1 -1
  10. package/dist/lib/services-v2/constants.js +17 -0
  11. package/dist/lib/services-v2/constants.js.map +1 -0
  12. package/dist/lib/services-v2/index.js +58 -0
  13. package/dist/lib/services-v2/index.js.map +1 -0
  14. package/dist/lib/services-v2/interceptors/hostmap.js +64 -0
  15. package/dist/lib/services-v2/interceptors/hostmap.js.map +1 -0
  16. package/dist/lib/services-v2/interceptors/server-error.js +77 -0
  17. package/dist/lib/services-v2/interceptors/server-error.js.map +1 -0
  18. package/dist/lib/services-v2/interceptors/service.js +137 -0
  19. package/dist/lib/services-v2/interceptors/service.js.map +1 -0
  20. package/dist/lib/services-v2/metrics.js +12 -0
  21. package/dist/lib/services-v2/metrics.js.map +1 -0
  22. package/dist/lib/services-v2/service-catalog.js +346 -0
  23. package/dist/lib/services-v2/service-catalog.js.map +1 -0
  24. package/dist/lib/services-v2/service-detail.js +94 -0
  25. package/dist/lib/services-v2/service-detail.js.map +1 -0
  26. package/dist/lib/services-v2/service-fed-ramp.js +13 -0
  27. package/dist/lib/services-v2/service-fed-ramp.js.map +1 -0
  28. package/dist/lib/services-v2/services-v2.js +985 -0
  29. package/dist/lib/services-v2/services-v2.js.map +1 -0
  30. package/dist/lib/services-v2/types.js +7 -0
  31. package/dist/lib/services-v2/types.js.map +1 -0
  32. package/dist/plugins/logger.js +1 -1
  33. package/dist/webex-core.js +2 -2
  34. package/dist/webex-core.js.map +1 -1
  35. package/package.json +13 -13
  36. package/src/index.js +10 -0
  37. package/src/interceptors/redirect.js +28 -0
  38. package/src/lib/services-v2/README.md +3 -0
  39. package/src/lib/services-v2/constants.ts +21 -0
  40. package/src/lib/services-v2/index.ts +23 -0
  41. package/src/lib/services-v2/interceptors/hostmap.js +36 -0
  42. package/src/lib/services-v2/interceptors/server-error.js +48 -0
  43. package/src/lib/services-v2/interceptors/service.js +101 -0
  44. package/src/lib/services-v2/metrics.js +4 -0
  45. package/src/lib/services-v2/service-catalog.ts +360 -0
  46. package/src/lib/services-v2/service-detail.ts +97 -0
  47. package/src/lib/services-v2/service-fed-ramp.ts +5 -0
  48. package/src/lib/services-v2/services-v2.js +988 -0
  49. package/src/lib/services-v2/types.ts +13 -0
  50. package/test/fixtures/host-catalog-v2.ts +249 -0
  51. package/test/integration/spec/services/services.js +12 -10
  52. package/test/unit/spec/interceptors/redirect.js +72 -0
  53. package/test/unit/spec/services-v2/service-catalog.ts +288 -0
  54. package/test/unit/spec/services-v2/service-detail.ts +147 -0
  55. package/test/unit/spec/services-v2/services-v2.ts +562 -0
@@ -0,0 +1,36 @@
1
+ /*!
2
+ * Copyright (c) 2015-2024 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+
7
+ /**
8
+ * This interceptor replaces the host in the request uri with the host from the hostmap
9
+ * It will attempt to do this for every request, but not all URIs will be in the hostmap
10
+ * URIs with hosts that are not in the hostmap will be left unchanged
11
+ */
12
+ export default class HostMapInterceptor extends Interceptor {
13
+ /**
14
+ * @returns {HostMapInterceptor}
15
+ */
16
+ static create() {
17
+ return new HostMapInterceptor({webex: this});
18
+ }
19
+
20
+ /**
21
+ * @see Interceptor#onRequest
22
+ * @param {Object} options
23
+ * @returns {Object}
24
+ */
25
+ onRequest(options) {
26
+ if (options.uri) {
27
+ try {
28
+ options.uri = this.webex.internal.services.replaceHostFromHostmap(options.uri);
29
+ } catch (error) {
30
+ /* empty */
31
+ }
32
+ }
33
+
34
+ return options;
35
+ }
36
+ }
@@ -0,0 +1,48 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+ import WebexHttpError from '../../webex-http-error';
7
+ /**
8
+ * Changes server url when it fails
9
+ */
10
+ export default class ServerErrorInterceptor extends Interceptor {
11
+ /**
12
+ * @returns {HAMessagingInterceptor}
13
+ */
14
+ static create() {
15
+ // eslint-disable-next-line no-invalid-this
16
+ return new ServerErrorInterceptor({webex: this});
17
+ }
18
+
19
+ /**
20
+ * @see Interceptor#onResponseError
21
+ * @param {Object} options
22
+ * @param {Object} reason
23
+ * @returns {Object}
24
+ */
25
+ onResponseError(options, reason) {
26
+ if (
27
+ (reason instanceof WebexHttpError.InternalServerError ||
28
+ reason instanceof WebexHttpError.BadGateway ||
29
+ reason instanceof WebexHttpError.ServiceUnavailable) &&
30
+ options.uri
31
+ ) {
32
+ const feature = this.webex.internal.device.features.developer.get('web-high-availability');
33
+
34
+ if (feature && feature.value) {
35
+ this.webex.internal.metrics.submitClientMetrics('web-ha', {
36
+ fields: {success: false},
37
+ tags: {action: 'failed', error: reason.message, url: options.uri},
38
+ });
39
+
40
+ return Promise.resolve(this.webex.internal.services.markFailedUrl(options.uri)).then(() =>
41
+ Promise.reject(reason)
42
+ );
43
+ }
44
+ }
45
+
46
+ return Promise.reject(reason);
47
+ }
48
+ }
@@ -0,0 +1,101 @@
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+
7
+ const trailingSlashes = /(?:^\/)|(?:\/$)/;
8
+
9
+ /**
10
+ * @class
11
+ */
12
+ export default class ServiceInterceptor extends Interceptor {
13
+ /**
14
+ * @returns {ServiceInterceptor}
15
+ */
16
+ static create() {
17
+ /* eslint no-invalid-this: [0] */
18
+ return new ServiceInterceptor({webex: this});
19
+ }
20
+
21
+ /* eslint-disable no-param-reassign */
22
+ /**
23
+ * @see Interceptor#onRequest
24
+ * @param {Object} options - The request PTO.
25
+ * @returns {Object} - The mutated request PTO.
26
+ */
27
+ onRequest(options) {
28
+ // Validate that the PTO includes a uri property.
29
+ if (options.uri) {
30
+ return options;
31
+ }
32
+
33
+ // Normalize and validate the PTO.
34
+ this.normalizeOptions(options);
35
+ this.validateOptions(options);
36
+
37
+ // Destructure commonly referenced namespaces.
38
+ const {services} = this.webex.internal;
39
+ const {service, resource, waitForServiceTimeout} = options;
40
+
41
+ // Attempt to collect the service url.
42
+ return services
43
+ .waitForService({name: service, timeout: waitForServiceTimeout})
44
+ .then((serviceUrl) => {
45
+ // Generate the combined service url and resource.
46
+ options.uri = this.generateUri(serviceUrl, resource);
47
+
48
+ return options;
49
+ })
50
+ .catch(() =>
51
+ Promise.reject(new Error(`service-interceptor: '${service}' is not a known service`))
52
+ );
53
+ }
54
+
55
+ /* eslint-disable class-methods-use-this */
56
+ /**
57
+ * Generate a usable request uri string from a service url and a resouce.
58
+ *
59
+ * @param {string} serviceUrl - The service url.
60
+ * @param {string} [resource] - The resouce to be appended to the service url.
61
+ * @returns {string} - The combined service url and resource.
62
+ */
63
+ generateUri(serviceUrl, resource = '') {
64
+ const formattedService = serviceUrl.replace(trailingSlashes, '');
65
+ const formattedResource = resource.replace(trailingSlashes, '');
66
+
67
+ return `${formattedService}/${formattedResource}`;
68
+ }
69
+
70
+ /**
71
+ * Normalizes request options relative to service identification.
72
+ *
73
+ * @param {Object} options - The request PTO.
74
+ * @returns {Object} - The mutated request PTO.
75
+ */
76
+ normalizeOptions(options) {
77
+ // Validate if the api property is used.
78
+ if (options.api) {
79
+ // Assign the service property the value of the api property if necessary.
80
+ options.service = options.service || options.api;
81
+ delete options.api;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Validates that the appropriate options for this interceptor are present.
87
+ *
88
+ * @param {Object} options - The request PTO.
89
+ * @returns {Object} - The mutated request PTO.
90
+ */
91
+ validateOptions(options) {
92
+ if (!options.resource) {
93
+ throw new Error('a `resource` parameter is required');
94
+ }
95
+
96
+ if (!options.service) {
97
+ throw new Error("a valid 'service' parameter is required");
98
+ }
99
+ }
100
+ /* eslint-enable class-methods-use-this, no-param-reassign */
101
+ }
@@ -0,0 +1,4 @@
1
+ // Metrics for service catalog
2
+ export default {
3
+ JS_SDK_SERVICE_NOT_FOUND: 'JS_SDK_SERVICE_NOT_FOUND',
4
+ };
@@ -0,0 +1,360 @@
1
+ import AmpState from 'ampersand-state';
2
+
3
+ import {union} from 'lodash';
4
+ import ServiceDetail from './service-detail';
5
+ import {IServiceDetail} from './types';
6
+
7
+ /**
8
+ * @class
9
+ */
10
+ const ServiceCatalog = AmpState.extend({
11
+ namespace: 'ServiceCatalog',
12
+
13
+ props: {
14
+ serviceGroups: [
15
+ 'object',
16
+ true,
17
+ () => ({
18
+ discovery: [],
19
+ override: [],
20
+ preauth: [],
21
+ postauth: [],
22
+ signin: [],
23
+ }),
24
+ ],
25
+ status: [
26
+ 'object',
27
+ true,
28
+ () => ({
29
+ discovery: {
30
+ ready: false,
31
+ collecting: false,
32
+ },
33
+ override: {
34
+ ready: false,
35
+ collecting: false,
36
+ },
37
+ preauth: {
38
+ ready: false,
39
+ collecting: false,
40
+ },
41
+ postauth: {
42
+ ready: false,
43
+ collecting: false,
44
+ },
45
+ signin: {
46
+ ready: false,
47
+ collecting: false,
48
+ },
49
+ }),
50
+ ],
51
+ isReady: ['boolean', false, false],
52
+ allowedDomains: ['array', false, () => []],
53
+ },
54
+
55
+ /**
56
+ * @private
57
+ * Get all service details for a given service group or return all details if no group is specified.
58
+ * @param {string} serviceGroup - The name of the service group to retrieve details for.
59
+ * @returns {Array<IServiceDetail>} - An array of service details.
60
+ */
61
+ _getAllServiceDetails(serviceGroup: string): Array<IServiceDetail> {
62
+ const serviceDetails =
63
+ typeof serviceGroup === 'string'
64
+ ? this.serviceGroups[serviceGroup] || []
65
+ : [
66
+ ...this.serviceGroups.override,
67
+ ...this.serviceGroups.postauth,
68
+ ...this.serviceGroups.signin,
69
+ ...this.serviceGroups.preauth,
70
+ ...this.serviceGroups.discovery,
71
+ ];
72
+
73
+ return serviceDetails;
74
+ },
75
+
76
+ /**
77
+ * @private
78
+ * Search the service details array to locate a `ServiceDetails`
79
+ * class object based on its id.
80
+ * @param {string} clusterId
81
+ * @param {string} [serviceGroup]
82
+ * @returns {IServiceDetail}
83
+ */
84
+ _getServiceDetail(clusterId: string, serviceGroup: string): IServiceDetail | undefined {
85
+ const serviceDetails = this._getAllServiceDetails(serviceGroup);
86
+
87
+ return serviceDetails.find((serviceDetail: IServiceDetail) => serviceDetail.id === clusterId);
88
+ },
89
+
90
+ /**
91
+ * @private
92
+ * Safely load one or more `ServiceDetail`s into this `ServiceCatalog` instance.
93
+ * @param {string} serviceGroup
94
+ * @param {Array<ServiceDetail>} serviceDetails
95
+ * @returns {void}
96
+ */
97
+ _loadServiceDetails(serviceGroup: string, serviceDetails: Array<IServiceDetail>): void {
98
+ // declare namespaces outside of loop
99
+ let existingService: IServiceDetail | undefined;
100
+
101
+ serviceDetails.forEach((service) => {
102
+ existingService = this._getServiceDetail(service.id, serviceGroup);
103
+
104
+ if (!existingService) {
105
+ this.serviceGroups[serviceGroup].push(service);
106
+ }
107
+ });
108
+ },
109
+
110
+ /**
111
+ * @private
112
+ * Safely unload one or more `ServiceDetail`s into this `Services` instance
113
+ * @param {string} serviceGroup
114
+ * @param {Array<ServiceDetail>} serviceDetails
115
+ * @returns {void}
116
+ */
117
+ _unloadServiceDetails(serviceGroup: string, serviceDetails: Array<IServiceDetail>): void {
118
+ // declare namespaces outside of loop
119
+ let existingService: IServiceDetail | undefined;
120
+
121
+ serviceDetails.forEach((service) => {
122
+ existingService = this._getServiceDetail(service.id, serviceGroup);
123
+
124
+ if (existingService) {
125
+ this.serviceGroups[serviceGroup].splice(
126
+ this.serviceGroups[serviceGroup].indexOf(existingService),
127
+ 1
128
+ );
129
+ }
130
+ });
131
+ },
132
+
133
+ /**
134
+ * Clear all collected catalog data and reset catalog status.
135
+ *
136
+ * @returns {void}
137
+ */
138
+ clean(): void {
139
+ this.serviceGroups.preauth.length = 0;
140
+ this.serviceGroups.signin.length = 0;
141
+ this.serviceGroups.postauth.length = 0;
142
+ this.status.preauth = {ready: false};
143
+ this.status.signin = {ready: false};
144
+ this.status.postauth = {ready: false};
145
+ },
146
+
147
+ /**
148
+ * Search over all service groups to find a cluster id based
149
+ * on a given url.
150
+ * @param {string} url - Must be parsable by `Url`
151
+ * @returns {string | undefined} - ClusterId of a given url
152
+ */
153
+ findClusterId(url: string): string | undefined {
154
+ try {
155
+ const incomingUrlObj = new URL(url);
156
+ const allServiceDetails = this._getAllServiceDetails();
157
+
158
+ return allServiceDetails.find((serviceDetail: IServiceDetail) =>
159
+ serviceDetail.serviceUrls.find(({host}) => host === incomingUrlObj.host)
160
+ )?.id;
161
+ } catch {
162
+ // If the URL is invalid or can't be found, return undefined
163
+ return undefined;
164
+ }
165
+ },
166
+
167
+ /**
168
+ * Search over all service groups and return a service value from a provided
169
+ * clusterId.
170
+ * @param {object} params
171
+ * @param {string} params.clusterId - clusterId of found service
172
+ * @param {string} [params.serviceGroup] - specify service group
173
+ * @returns {object} service
174
+ * @returns {string} service.name
175
+ * @returns {string} service.url
176
+ */
177
+ findServiceFromClusterId(
178
+ {clusterId, serviceGroup} = {} as {clusterId: string; serviceGroup: string}
179
+ ): {name: string; url: string} | undefined {
180
+ const serviceDetails = this._getServiceDetail(clusterId, serviceGroup);
181
+
182
+ if (serviceDetails) {
183
+ return {
184
+ name: serviceDetails.serviceName,
185
+ url: serviceDetails.get(),
186
+ };
187
+ }
188
+
189
+ return undefined;
190
+ },
191
+
192
+ /**
193
+ * Find a service based on the provided url.
194
+ * @param {string} url - Must be parsable by `Url`
195
+ * @returns {IServiceDetail} - ServiceDetail assocated with provided url
196
+ */
197
+ findServiceDetailFromUrl(url: string): IServiceDetail | undefined {
198
+ const serviceDetails = this._getAllServiceDetails();
199
+
200
+ return serviceDetails.find(({serviceUrls}) => {
201
+ for (const serviceUrl of serviceUrls) {
202
+ if (url.startsWith(serviceUrl.baseUrl)) {
203
+ return true;
204
+ }
205
+ }
206
+
207
+ return false;
208
+ });
209
+ },
210
+
211
+ /**
212
+ * Finds an allowed domain that matches a specific url.
213
+ *
214
+ * @param {string} url - The url to match the allowed domains against.
215
+ * @returns {string} - The matching allowed domain.
216
+ */
217
+ findAllowedDomain(url: string): string {
218
+ try {
219
+ const urlObj = new URL(url);
220
+
221
+ return this.allowedDomains.find((allowedDomain) => urlObj.host.includes(allowedDomain));
222
+ } catch {
223
+ // If the URL is invalid or can't be found, return undefined
224
+ return undefined;
225
+ }
226
+ },
227
+
228
+ /**
229
+ * Get a service url from the current services list by name.
230
+ * @param {string} clusterId
231
+ * @param {string} serviceGroup
232
+ * @returns {string}
233
+ */
234
+ get(clusterId: string, serviceGroup: string): string | undefined {
235
+ const serviceDetail = this._getServiceDetail(clusterId, serviceGroup);
236
+
237
+ return serviceDetail ? serviceDetail.get() : undefined;
238
+ },
239
+
240
+ /**
241
+ * Get the current allowed domains list.
242
+ *
243
+ * @returns {Array<string>} - the current allowed domains list.
244
+ */
245
+ getAllowedDomains(): Array<string> {
246
+ return [...this.allowedDomains];
247
+ },
248
+
249
+ /**
250
+ * Mark a priority host service url as failed.
251
+ * This will mark the host associated with the
252
+ * `ServiceDetail` to be removed from the its
253
+ * respective host array, and then return the next
254
+ * viable host from the `ServiceDetail` host array,
255
+ * or the `ServiceDetail` default url if no other priority
256
+ * hosts are available, or if `noPriorityHosts` is set to
257
+ * `true`.
258
+ * @param {string} url
259
+ * @returns {string}
260
+ */
261
+ markFailedServiceUrl(url: string): string | undefined {
262
+ const serviceDetails = this._getAllServiceDetails();
263
+
264
+ const serviceDetailWithFailedHost = serviceDetails.find((serviceDetail: IServiceDetail) =>
265
+ serviceDetail.failHost(url)
266
+ );
267
+
268
+ // if we couldn't find the url we wanted to fail, return undefined
269
+ if (!serviceDetailWithFailedHost) {
270
+ return undefined;
271
+ }
272
+
273
+ return serviceDetailWithFailedHost.get();
274
+ },
275
+
276
+ /**
277
+ * Set the allowed domains for the catalog.
278
+ *
279
+ * @param {Array<string>} allowedDomains - allowed domains to be assigned.
280
+ * @returns {void}
281
+ */
282
+ setAllowedDomains(allowedDomains: Array<string>): void {
283
+ this.allowedDomains = [...allowedDomains];
284
+ },
285
+
286
+ /**
287
+ *
288
+ * @param {Array<string>} newAllowedDomains - new allowed domains to add to existing set of allowed domains
289
+ * @returns {void}
290
+ */
291
+ addAllowedDomains(newAllowedDomains: Array<string>): void {
292
+ this.allowedDomains = union(this.allowedDomains, newAllowedDomains);
293
+ },
294
+
295
+ /**
296
+ * Update the current list of `ServiceDetail`s against a provided
297
+ * service hostmap.
298
+ * @emits ServiceCatalog#preauthorized
299
+ * @emits ServiceCatalog#postauthorized
300
+ * @param {string} serviceGroup
301
+ * @param {Array<IServiceDetail>} serviceDetails
302
+ * @returns {void}
303
+ */
304
+ updateServiceGroups(serviceGroup: string, serviceDetails: Array<IServiceDetail>) {
305
+ const currentServiceDetails = this.serviceGroups[serviceGroup];
306
+
307
+ const unusedServicesDetails = currentServiceDetails.filter((serviceDetail) =>
308
+ serviceDetails.every(({id}) => id !== serviceDetail.id)
309
+ );
310
+
311
+ this._unloadServiceDetails(serviceGroup, unusedServicesDetails);
312
+
313
+ serviceDetails.forEach((serviceObj) => {
314
+ const serviceDetail = this._getServiceDetail(serviceObj.id, serviceGroup);
315
+
316
+ if (serviceDetail) {
317
+ serviceDetail.serviceUrls = serviceObj.serviceUrls || [];
318
+ } else {
319
+ this._loadServiceDetails(serviceGroup, [new ServiceDetail(serviceObj)]);
320
+ }
321
+ });
322
+
323
+ this.status[serviceGroup].ready = true;
324
+ this.trigger(serviceGroup);
325
+ },
326
+
327
+ /**
328
+ * Wait until the service catalog is available,
329
+ * or reject after a timeout of 60 seconds.
330
+ * @param {string} serviceGroup
331
+ * @param {number} [timeout] - in seconds
332
+ * @returns {Promise<void>}
333
+ */
334
+ waitForCatalog(serviceGroup: string, timeout: number): Promise<void> {
335
+ return new Promise<void>((resolve, reject) => {
336
+ if (this.status[serviceGroup].ready) {
337
+ resolve();
338
+ }
339
+
340
+ const validatedTimeout = typeof timeout === 'number' && timeout >= 0 ? timeout : 60;
341
+
342
+ const timeoutTimer = setTimeout(
343
+ () =>
344
+ reject(
345
+ new Error(
346
+ `services: timeout occured while waiting for '${serviceGroup}' catalog to populate`
347
+ )
348
+ ),
349
+ validatedTimeout * 1000
350
+ );
351
+
352
+ this.once(serviceGroup, () => {
353
+ clearTimeout(timeoutTimer);
354
+ resolve();
355
+ });
356
+ });
357
+ },
358
+ });
359
+
360
+ export default ServiceCatalog;
@@ -0,0 +1,97 @@
1
+ import AmpState from 'ampersand-state';
2
+ import {ServiceUrl} from './types';
3
+
4
+ /**
5
+ * @class
6
+ */
7
+ const ServiceDetail = AmpState.extend({
8
+ namespace: 'ServiceDetail',
9
+
10
+ props: {
11
+ serviceUrls: ['array', false, () => []],
12
+ serviceName: ['string', true, undefined],
13
+ id: ['string', true, undefined],
14
+ },
15
+
16
+ /**
17
+ * Generate a host url based on the host
18
+ * uri provided.
19
+ * @param {ServiceUrl} serviceUrl
20
+ * @returns {string}
21
+ */
22
+ _generateHostUrl(serviceUrl: ServiceUrl): string {
23
+ const url = new URL(serviceUrl.baseUrl);
24
+
25
+ // setting url.hostname will not apply during Url.format(), set host via
26
+ // a string literal instead.
27
+ url.host = `${serviceUrl.host}${url.port ? `:${url.port}` : ''}`;
28
+
29
+ return url.href;
30
+ },
31
+
32
+ /**
33
+ * Get the current host url with the highest priority. This will only return a URL with a filtered host that has the
34
+ * `homeCluster` value set to `true`.
35
+ * @returns {string} - The priority host url.
36
+ */
37
+ _getPriorityHostUrl(): string {
38
+ // format of catalog ensures that array is sorted by highest priority
39
+ let priorityServiceUrl = this._searchForValidPriorityHost();
40
+
41
+ if (!priorityServiceUrl) {
42
+ this.serviceUrls = this.serviceUrls.map((serviceUrl) => {
43
+ serviceUrl.failed = false;
44
+
45
+ return serviceUrl;
46
+ });
47
+
48
+ priorityServiceUrl = this._searchForValidPriorityHost();
49
+ }
50
+
51
+ return priorityServiceUrl ? this._generateHostUrl(priorityServiceUrl) : '';
52
+ },
53
+
54
+ /**
55
+ * Searches for a valid service URL with a priority greater than 0 that has not failed.
56
+ * @returns {ServiceUrl | undefined} - The first valid service URL found, or undefined if none exist.
57
+ */
58
+ _searchForValidPriorityHost(): ServiceUrl | undefined {
59
+ return this.serviceUrls.find((serviceUrl) => serviceUrl.priority > 0 && !serviceUrl.failed);
60
+ },
61
+
62
+ /**
63
+ * Attempt to mark a host from this `ServiceDetail` as failed and return true
64
+ * if the provided url has a host that could be successfully marked as failed.
65
+ *
66
+ * @param {string} url
67
+ * @returns {boolean}
68
+ */
69
+ failHost(url: string): boolean {
70
+ const failedUrl = new URL(url);
71
+
72
+ const foundHost = this.serviceUrls.find((serviceUrl) => serviceUrl.host === failedUrl.host);
73
+
74
+ if (foundHost) {
75
+ foundHost.failed = true;
76
+ }
77
+
78
+ return foundHost !== undefined;
79
+ },
80
+
81
+ /**
82
+ * Generate a url using the host with the
83
+ * highest priority via host rendering.
84
+ *
85
+ * @returns {string} - The full service url.
86
+ */
87
+ get(): string {
88
+ // return empty string to indicate that no service url is available
89
+ if (!this.serviceUrls || this.serviceUrls.length === 0) {
90
+ return '';
91
+ }
92
+
93
+ return this._getPriorityHostUrl();
94
+ },
95
+ });
96
+
97
+ export default ServiceDetail;
@@ -0,0 +1,5 @@
1
+ export default {
2
+ hydra: 'https://api-usgov.webex.com/v1',
3
+ u2c: 'https://u2c.gov.ciscospark.com/u2c/api/v1',
4
+ sqdiscovery: 'https://ds.ciscospark.com/v1/region', // TODO: fedramp load balanced URL? this has been here for years as of now but now explicitly done
5
+ };