@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.
- package/README.md +87 -27
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -1
- package/dist/interceptors/redirect.js +18 -0
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/lib/batcher.js +1 -1
- package/dist/lib/credentials/credentials.js +1 -1
- package/dist/lib/credentials/token.js +1 -1
- package/dist/lib/services/services.js +1 -1
- package/dist/lib/services-v2/constants.js +17 -0
- package/dist/lib/services-v2/constants.js.map +1 -0
- package/dist/lib/services-v2/index.js +58 -0
- package/dist/lib/services-v2/index.js.map +1 -0
- package/dist/lib/services-v2/interceptors/hostmap.js +64 -0
- package/dist/lib/services-v2/interceptors/hostmap.js.map +1 -0
- package/dist/lib/services-v2/interceptors/server-error.js +77 -0
- package/dist/lib/services-v2/interceptors/server-error.js.map +1 -0
- package/dist/lib/services-v2/interceptors/service.js +137 -0
- package/dist/lib/services-v2/interceptors/service.js.map +1 -0
- package/dist/lib/services-v2/metrics.js +12 -0
- package/dist/lib/services-v2/metrics.js.map +1 -0
- package/dist/lib/services-v2/service-catalog.js +346 -0
- package/dist/lib/services-v2/service-catalog.js.map +1 -0
- package/dist/lib/services-v2/service-detail.js +94 -0
- package/dist/lib/services-v2/service-detail.js.map +1 -0
- package/dist/lib/services-v2/service-fed-ramp.js +13 -0
- package/dist/lib/services-v2/service-fed-ramp.js.map +1 -0
- package/dist/lib/services-v2/services-v2.js +985 -0
- package/dist/lib/services-v2/services-v2.js.map +1 -0
- package/dist/lib/services-v2/types.js +7 -0
- package/dist/lib/services-v2/types.js.map +1 -0
- 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/index.js +10 -0
- package/src/interceptors/redirect.js +28 -0
- package/src/lib/services-v2/README.md +3 -0
- package/src/lib/services-v2/constants.ts +21 -0
- package/src/lib/services-v2/index.ts +23 -0
- package/src/lib/services-v2/interceptors/hostmap.js +36 -0
- package/src/lib/services-v2/interceptors/server-error.js +48 -0
- package/src/lib/services-v2/interceptors/service.js +101 -0
- package/src/lib/services-v2/metrics.js +4 -0
- package/src/lib/services-v2/service-catalog.ts +360 -0
- package/src/lib/services-v2/service-detail.ts +97 -0
- package/src/lib/services-v2/service-fed-ramp.ts +5 -0
- package/src/lib/services-v2/services-v2.js +988 -0
- package/src/lib/services-v2/types.ts +13 -0
- package/test/fixtures/host-catalog-v2.ts +249 -0
- package/test/integration/spec/services/services.js +12 -10
- package/test/unit/spec/interceptors/redirect.js +72 -0
- package/test/unit/spec/services-v2/service-catalog.ts +288 -0
- package/test/unit/spec/services-v2/service-detail.ts +147 -0
- 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,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;
|