@webex/webex-core 2.59.2 → 2.59.3-next.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/.eslintrc.js +6 -6
- package/README.md +79 -79
- package/babel.config.js +3 -3
- package/dist/config.js +24 -24
- package/dist/config.js.map +1 -1
- package/dist/credentials-config.js +56 -56
- package/dist/credentials-config.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interceptors/auth.js +28 -28
- package/dist/interceptors/auth.js.map +1 -1
- package/dist/interceptors/default-options.js +24 -24
- package/dist/interceptors/default-options.js.map +1 -1
- package/dist/interceptors/embargo.js +9 -9
- package/dist/interceptors/embargo.js.map +1 -1
- package/dist/interceptors/network-timing.js +19 -19
- package/dist/interceptors/network-timing.js.map +1 -1
- package/dist/interceptors/payload-transformer.js +19 -19
- package/dist/interceptors/payload-transformer.js.map +1 -1
- package/dist/interceptors/rate-limit.js +40 -40
- package/dist/interceptors/rate-limit.js.map +1 -1
- package/dist/interceptors/redirect.js +13 -13
- package/dist/interceptors/redirect.js.map +1 -1
- package/dist/interceptors/request-event.js +23 -23
- package/dist/interceptors/request-event.js.map +1 -1
- package/dist/interceptors/request-logger.js +13 -13
- package/dist/interceptors/request-logger.js.map +1 -1
- package/dist/interceptors/request-timing.js +23 -23
- package/dist/interceptors/request-timing.js.map +1 -1
- package/dist/interceptors/response-logger.js +19 -19
- package/dist/interceptors/response-logger.js.map +1 -1
- package/dist/interceptors/user-agent.js +29 -29
- package/dist/interceptors/user-agent.js.map +1 -1
- package/dist/interceptors/webex-tracking-id.js +15 -15
- package/dist/interceptors/webex-tracking-id.js.map +1 -1
- package/dist/interceptors/webex-user-agent.js +13 -13
- package/dist/interceptors/webex-user-agent.js.map +1 -1
- package/dist/lib/batcher.js +83 -83
- package/dist/lib/batcher.js.map +1 -1
- package/dist/lib/credentials/credentials.js +103 -103
- package/dist/lib/credentials/credentials.js.map +1 -1
- package/dist/lib/credentials/grant-errors.js +17 -17
- package/dist/lib/credentials/grant-errors.js.map +1 -1
- package/dist/lib/credentials/index.js +2 -2
- package/dist/lib/credentials/index.js.map +1 -1
- package/dist/lib/credentials/scope.js +11 -11
- package/dist/lib/credentials/scope.js.map +1 -1
- package/dist/lib/credentials/token-collection.js +2 -2
- package/dist/lib/credentials/token-collection.js.map +1 -1
- package/dist/lib/credentials/token.js +145 -145
- package/dist/lib/credentials/token.js.map +1 -1
- package/dist/lib/page.js +49 -49
- package/dist/lib/page.js.map +1 -1
- package/dist/lib/services/constants.js.map +1 -1
- package/dist/lib/services/index.js +2 -2
- package/dist/lib/services/index.js.map +1 -1
- package/dist/lib/services/interceptors/server-error.js +9 -9
- package/dist/lib/services/interceptors/server-error.js.map +1 -1
- package/dist/lib/services/interceptors/service.js +24 -24
- package/dist/lib/services/interceptors/service.js.map +1 -1
- package/dist/lib/services/metrics.js.map +1 -1
- package/dist/lib/services/service-catalog.js +104 -104
- 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 +134 -134
- package/dist/lib/services/service-host.js.map +1 -1
- package/dist/lib/services/service-registry.js +175 -175
- package/dist/lib/services/service-registry.js.map +1 -1
- package/dist/lib/services/service-state.js +38 -38
- package/dist/lib/services/service-state.js.map +1 -1
- package/dist/lib/services/service-url.js +31 -31
- package/dist/lib/services/service-url.js.map +1 -1
- package/dist/lib/services/services.js +245 -245
- package/dist/lib/services/services.js.map +1 -1
- package/dist/lib/stateless-webex-plugin.js +28 -28
- package/dist/lib/stateless-webex-plugin.js.map +1 -1
- package/dist/lib/storage/decorators.js +27 -27
- package/dist/lib/storage/decorators.js.map +1 -1
- package/dist/lib/storage/errors.js +4 -4
- 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 +44 -44
- package/dist/lib/storage/make-webex-plugin-store.js.map +1 -1
- package/dist/lib/storage/make-webex-store.js +40 -40
- package/dist/lib/storage/make-webex-store.js.map +1 -1
- package/dist/lib/storage/memory-store-adapter.js +9 -9
- package/dist/lib/storage/memory-store-adapter.js.map +1 -1
- package/dist/lib/webex-core-plugin-mixin.js +13 -13
- package/dist/lib/webex-core-plugin-mixin.js.map +1 -1
- package/dist/lib/webex-http-error.js +9 -9
- package/dist/lib/webex-http-error.js.map +1 -1
- package/dist/lib/webex-internal-core-plugin-mixin.js +13 -13
- package/dist/lib/webex-internal-core-plugin-mixin.js.map +1 -1
- package/dist/lib/webex-plugin.js +36 -36
- package/dist/lib/webex-plugin.js.map +1 -1
- package/dist/plugins/logger.js +9 -9
- package/dist/plugins/logger.js.map +1 -1
- package/dist/webex-core.js +104 -104
- package/dist/webex-core.js.map +1 -1
- package/dist/webex-internal-core.js +12 -12
- package/dist/webex-internal-core.js.map +1 -1
- package/jest.config.js +3 -3
- package/package.json +20 -19
- package/process +1 -1
- package/src/config.js +90 -90
- package/src/credentials-config.js +212 -212
- package/src/index.js +62 -62
- package/src/interceptors/auth.js +186 -186
- package/src/interceptors/default-options.js +55 -55
- package/src/interceptors/embargo.js +43 -43
- package/src/interceptors/network-timing.js +54 -54
- package/src/interceptors/payload-transformer.js +55 -55
- package/src/interceptors/rate-limit.js +169 -169
- package/src/interceptors/redirect.js +106 -106
- package/src/interceptors/request-event.js +93 -93
- package/src/interceptors/request-logger.js +78 -78
- package/src/interceptors/request-timing.js +65 -65
- package/src/interceptors/response-logger.js +98 -98
- package/src/interceptors/user-agent.js +77 -77
- package/src/interceptors/webex-tracking-id.js +73 -73
- package/src/interceptors/webex-user-agent.js +79 -79
- package/src/lib/batcher.js +307 -307
- package/src/lib/credentials/credentials.js +552 -552
- package/src/lib/credentials/grant-errors.js +92 -92
- package/src/lib/credentials/index.js +16 -16
- package/src/lib/credentials/scope.js +34 -34
- package/src/lib/credentials/token-collection.js +17 -17
- package/src/lib/credentials/token.js +559 -559
- package/src/lib/page.js +159 -159
- package/src/lib/services/constants.js +9 -9
- package/src/lib/services/index.js +26 -26
- package/src/lib/services/interceptors/server-error.js +48 -48
- package/src/lib/services/interceptors/service.js +101 -101
- package/src/lib/services/metrics.js +4 -4
- package/src/lib/services/service-catalog.js +435 -435
- package/src/lib/services/service-fed-ramp.js +4 -4
- package/src/lib/services/service-host.js +267 -267
- package/src/lib/services/service-registry.js +465 -465
- package/src/lib/services/service-state.js +78 -78
- package/src/lib/services/service-url.js +124 -124
- package/src/lib/services/services.js +1018 -1018
- package/src/lib/stateless-webex-plugin.js +98 -98
- package/src/lib/storage/decorators.js +220 -220
- package/src/lib/storage/errors.js +15 -15
- package/src/lib/storage/index.js +10 -10
- package/src/lib/storage/make-webex-plugin-store.js +211 -211
- package/src/lib/storage/make-webex-store.js +140 -140
- package/src/lib/storage/memory-store-adapter.js +79 -79
- package/src/lib/webex-core-plugin-mixin.js +114 -114
- package/src/lib/webex-http-error.js +61 -61
- package/src/lib/webex-internal-core-plugin-mixin.js +107 -107
- package/src/lib/webex-plugin.js +222 -222
- package/src/plugins/logger.js +60 -60
- package/src/webex-core.js +745 -745
- package/src/webex-internal-core.js +46 -46
- package/test/integration/spec/credentials/credentials.js +139 -139
- package/test/integration/spec/credentials/token.js +102 -102
- package/test/integration/spec/services/service-catalog.js +838 -838
- package/test/integration/spec/services/services.js +1221 -1221
- package/test/integration/spec/webex-core.js +178 -178
- package/test/unit/spec/_setup.js +44 -44
- package/test/unit/spec/credentials/credentials.js +1017 -1017
- package/test/unit/spec/credentials/token.js +441 -441
- package/test/unit/spec/interceptors/auth.js +521 -521
- package/test/unit/spec/interceptors/default-options.js +84 -84
- package/test/unit/spec/interceptors/embargo.js +144 -144
- package/test/unit/spec/interceptors/network-timing.js +49 -49
- package/test/unit/spec/interceptors/payload-transformer.js +155 -155
- package/test/unit/spec/interceptors/rate-limit.js +302 -302
- package/test/unit/spec/interceptors/redirect.js +102 -102
- package/test/unit/spec/interceptors/request-timing.js +92 -92
- package/test/unit/spec/interceptors/user-agent.js +76 -76
- package/test/unit/spec/interceptors/webex-tracking-id.js +76 -76
- package/test/unit/spec/interceptors/webex-user-agent.js +159 -159
- package/test/unit/spec/lib/batcher.js +330 -330
- package/test/unit/spec/lib/page.js +148 -148
- package/test/unit/spec/lib/webex-plugin.js +48 -48
- package/test/unit/spec/services/interceptors/server-error.js +204 -204
- package/test/unit/spec/services/interceptors/service.js +188 -188
- package/test/unit/spec/services/service-catalog.js +194 -194
- package/test/unit/spec/services/service-host.js +260 -260
- package/test/unit/spec/services/service-registry.js +747 -747
- package/test/unit/spec/services/service-state.js +60 -60
- package/test/unit/spec/services/service-url.js +258 -258
- package/test/unit/spec/services/services.js +348 -348
- package/test/unit/spec/storage/persist.js +50 -50
- package/test/unit/spec/storage/storage-adapter.js +12 -12
- package/test/unit/spec/storage/wait-for-value.js +81 -81
- package/test/unit/spec/webex-core.js +253 -253
- package/test/unit/spec/webex-internal-core.js +91 -91
package/src/lib/batcher.js
CHANGED
|
@@ -1,307 +1,307 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {has} from 'lodash';
|
|
6
|
-
import {cappedDebounce, Defer, tap} from '@webex/common';
|
|
7
|
-
|
|
8
|
-
import WebexPlugin from './webex-plugin';
|
|
9
|
-
import WebexHttpError from './webex-http-error';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Base class for coalescing requests to batched APIs
|
|
13
|
-
* @class Batcher
|
|
14
|
-
*/
|
|
15
|
-
const Batcher = WebexPlugin.extend({
|
|
16
|
-
session: {
|
|
17
|
-
deferreds: {
|
|
18
|
-
type: 'object',
|
|
19
|
-
default() {
|
|
20
|
-
return new Map();
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
queue: {
|
|
24
|
-
type: 'array',
|
|
25
|
-
default() {
|
|
26
|
-
return [];
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
derived: {
|
|
32
|
-
bounce: {
|
|
33
|
-
fn() {
|
|
34
|
-
return cappedDebounce((...args) => this.executeQueue(...args), this.config.batcherWait, {
|
|
35
|
-
maxCalls: this.config.batcherMaxCalls,
|
|
36
|
-
maxWait: this.config.batcherMaxWait,
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Requests an item from a batched API
|
|
44
|
-
* @param {Object} item
|
|
45
|
-
* @returns {Promise<mixed>}
|
|
46
|
-
*/
|
|
47
|
-
request(item) {
|
|
48
|
-
// So far, I can't find a way to avoid three layers of nesting here.
|
|
49
|
-
/* eslint max-nested-callbacks: [0] */
|
|
50
|
-
const defer = new Defer();
|
|
51
|
-
|
|
52
|
-
this.fingerprintRequest(item)
|
|
53
|
-
.then((idx) => {
|
|
54
|
-
if (this.deferreds.has(idx)) {
|
|
55
|
-
defer.resolve(this.deferreds.get(idx).promise);
|
|
56
|
-
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
this.deferreds.set(idx, defer);
|
|
60
|
-
this.prepareItem(item)
|
|
61
|
-
.then((req) => {
|
|
62
|
-
defer.promise = defer.promise
|
|
63
|
-
.then(tap(() => this.deferreds.delete(idx)))
|
|
64
|
-
.catch((reason) => {
|
|
65
|
-
this.deferreds.delete(idx);
|
|
66
|
-
|
|
67
|
-
return Promise.reject(reason);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
this.enqueue(req)
|
|
71
|
-
.then(() => this.bounce())
|
|
72
|
-
.catch((reason) => defer.reject(reason));
|
|
73
|
-
})
|
|
74
|
-
.catch((reason) => defer.reject(reason));
|
|
75
|
-
})
|
|
76
|
-
.catch((reason) => defer.reject(reason));
|
|
77
|
-
|
|
78
|
-
return defer.promise;
|
|
79
|
-
},
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Adds an item to the queue.
|
|
83
|
-
* Intended to be overridden
|
|
84
|
-
* @param {mixed} req
|
|
85
|
-
* @returns {Promise<undefined>}
|
|
86
|
-
*/
|
|
87
|
-
enqueue(req) {
|
|
88
|
-
this.queue.push(req);
|
|
89
|
-
|
|
90
|
-
return Promise.resolve();
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Transform the item before adding it to the queue
|
|
95
|
-
* Intended to be overridden
|
|
96
|
-
* @param {mixed} item
|
|
97
|
-
* @returns {Promise<mixed>}
|
|
98
|
-
*/
|
|
99
|
-
prepareItem(item) {
|
|
100
|
-
return Promise.resolve(item);
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Detaches the current queue, does any appropriate transforms, and submits it
|
|
105
|
-
* to the API.
|
|
106
|
-
* @returns {Promise<undefined>}
|
|
107
|
-
*/
|
|
108
|
-
executeQueue() {
|
|
109
|
-
const queue = this.queue.splice(0, this.config.batcherMaxCalls);
|
|
110
|
-
|
|
111
|
-
return new Promise((resolve) => {
|
|
112
|
-
resolve(
|
|
113
|
-
this.prepareRequest(queue)
|
|
114
|
-
.then((payload) =>
|
|
115
|
-
this.submitHttpRequest(payload).then((res) => this.handleHttpSuccess(res))
|
|
116
|
-
)
|
|
117
|
-
.catch((reason) => {
|
|
118
|
-
if (reason instanceof WebexHttpError) {
|
|
119
|
-
return this.handleHttpError(reason);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return Promise.all(
|
|
123
|
-
queue.map((item) =>
|
|
124
|
-
this.getDeferredForRequest(item).then((defer) => {
|
|
125
|
-
defer.reject(reason);
|
|
126
|
-
})
|
|
127
|
-
)
|
|
128
|
-
);
|
|
129
|
-
})
|
|
130
|
-
);
|
|
131
|
-
}).catch((reason) => {
|
|
132
|
-
this.logger.error(process.env.NODE_ENV === 'production' ? reason : reason.stack);
|
|
133
|
-
|
|
134
|
-
return Promise.reject(reason);
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Performs any final transforms on the queue before submitting it to the API
|
|
140
|
-
* Intended to be overridden
|
|
141
|
-
* @param {Object|Array} queue
|
|
142
|
-
* @returns {Promise<Object>}
|
|
143
|
-
*/
|
|
144
|
-
prepareRequest(queue) {
|
|
145
|
-
return Promise.resolve(queue);
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Submits the prepared request body to the API.
|
|
150
|
-
* This method *must* be overridden
|
|
151
|
-
* @param {Object} payload
|
|
152
|
-
* @returns {Promise<HttpResponseObject>}
|
|
153
|
-
*/
|
|
154
|
-
// eslint-disable-next-line no-unused-vars
|
|
155
|
-
submitHttpRequest(payload) {
|
|
156
|
-
throw new Error('request() must be implemented');
|
|
157
|
-
},
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Actions taken when the http request returns a success
|
|
161
|
-
* Intended to be overridden
|
|
162
|
-
* @param {Promise<HttpResponseObject>} res
|
|
163
|
-
* @returns {Promise<undefined>}
|
|
164
|
-
*/
|
|
165
|
-
handleHttpSuccess(res) {
|
|
166
|
-
return Promise.all(
|
|
167
|
-
((res.body && res.body.items) || res.body).map((item) => this.acceptItem(item))
|
|
168
|
-
);
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Actions taken when the http request returns a failure. Typically, this
|
|
173
|
-
* means failing the entire queue, but could be overridden in some
|
|
174
|
-
* implementations to e.g. reenqueue.
|
|
175
|
-
* Intended to be overridden
|
|
176
|
-
* @param {WebexHttpError} reason
|
|
177
|
-
* @returns {Promise<undefined>}
|
|
178
|
-
*/
|
|
179
|
-
handleHttpError(reason) {
|
|
180
|
-
if (reason instanceof WebexHttpError) {
|
|
181
|
-
if (has(reason, 'options.body.map')) {
|
|
182
|
-
return Promise.all(
|
|
183
|
-
reason.options.body.map((item) =>
|
|
184
|
-
this.getDeferredForRequest(item).then((defer) => {
|
|
185
|
-
defer.reject(reason);
|
|
186
|
-
})
|
|
187
|
-
)
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
this.logger.error('http error handler called without a WebexHttpError object', reason);
|
|
192
|
-
|
|
193
|
-
return Promise.reject(reason);
|
|
194
|
-
},
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Determines if the item succeeded or failed and delegates accordingly
|
|
198
|
-
* @param {Object} item
|
|
199
|
-
* @returns {Promise<undefined>}
|
|
200
|
-
*/
|
|
201
|
-
acceptItem(item) {
|
|
202
|
-
return this.didItemFail(item).then((didFail) => {
|
|
203
|
-
if (didFail) {
|
|
204
|
-
return this.handleItemFailure(item);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return this.handleItemSuccess(item);
|
|
208
|
-
});
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Indicates if the specified response item implies a success or a failure
|
|
213
|
-
* Intended to be overridden
|
|
214
|
-
* @param {Object} item
|
|
215
|
-
* @returns {Promise<Boolean>}
|
|
216
|
-
*/
|
|
217
|
-
// eslint-disable-next-line no-unused-vars
|
|
218
|
-
didItemFail(item) {
|
|
219
|
-
return Promise.resolve(false);
|
|
220
|
-
},
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Finds the Defer for the specified item and rejects its promise
|
|
224
|
-
* Intended to be overridden
|
|
225
|
-
* @param {Object} item
|
|
226
|
-
* @returns {Promise<undefined>}
|
|
227
|
-
*/
|
|
228
|
-
handleItemFailure(item) {
|
|
229
|
-
return this.getDeferredForResponse(item).then((defer) => {
|
|
230
|
-
defer.reject(item);
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Finds the Defer for the specified item and resolves its promise
|
|
236
|
-
* Intended to be overridden
|
|
237
|
-
* @param {Object} item
|
|
238
|
-
* @returns {Promise<undefined>}
|
|
239
|
-
*/
|
|
240
|
-
handleItemSuccess(item) {
|
|
241
|
-
return this.getDeferredForResponse(item).then((defer) => {
|
|
242
|
-
defer.resolve(item);
|
|
243
|
-
});
|
|
244
|
-
},
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Returns the Deferred for the specified request item
|
|
248
|
-
* @param {Object} item
|
|
249
|
-
* @returns {Promise<Defer>}
|
|
250
|
-
*/
|
|
251
|
-
getDeferredForRequest(item) {
|
|
252
|
-
return this.fingerprintRequest(item).then((idx) => {
|
|
253
|
-
const defer = this.deferreds.get(idx);
|
|
254
|
-
|
|
255
|
-
/* istanbul ignore if */
|
|
256
|
-
if (!defer) {
|
|
257
|
-
throw new Error('Could not find pending request for received response');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return defer;
|
|
261
|
-
});
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Returns the Deferred for the specified response item
|
|
266
|
-
* @param {Object} item
|
|
267
|
-
* @returns {Promise<Defer>}
|
|
268
|
-
*/
|
|
269
|
-
getDeferredForResponse(item) {
|
|
270
|
-
return this.fingerprintResponse(item).then((idx) => {
|
|
271
|
-
const defer = this.deferreds.get(idx);
|
|
272
|
-
|
|
273
|
-
/* istanbul ignore if */
|
|
274
|
-
if (!defer) {
|
|
275
|
-
throw new Error('Could not find pending request for received response');
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return defer;
|
|
279
|
-
});
|
|
280
|
-
},
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Generates a unique identifier for the item in a request payload
|
|
284
|
-
* Intended to be overridden
|
|
285
|
-
* Note that overrides must return a primitive.
|
|
286
|
-
* @param {Object} item
|
|
287
|
-
* @returns {Promise<primitive>}
|
|
288
|
-
*/
|
|
289
|
-
// eslint-disable-next-line no-unused-vars
|
|
290
|
-
fingerprintRequest(item) {
|
|
291
|
-
throw new Error('fingerprintRequest() must be implemented');
|
|
292
|
-
},
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Generates a unique identifier for the item in a response payload
|
|
296
|
-
* Intended to be overridden
|
|
297
|
-
* Note that overrides must return a primitive.
|
|
298
|
-
* @param {Object} item
|
|
299
|
-
* @returns {Promise<primitive>}
|
|
300
|
-
*/
|
|
301
|
-
// eslint-disable-next-line no-unused-vars
|
|
302
|
-
fingerprintResponse(item) {
|
|
303
|
-
throw new Error('fingerprintResponse() must be implemented');
|
|
304
|
-
},
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
export default Batcher;
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {has} from 'lodash';
|
|
6
|
+
import {cappedDebounce, Defer, tap} from '@webex/common';
|
|
7
|
+
|
|
8
|
+
import WebexPlugin from './webex-plugin';
|
|
9
|
+
import WebexHttpError from './webex-http-error';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Base class for coalescing requests to batched APIs
|
|
13
|
+
* @class Batcher
|
|
14
|
+
*/
|
|
15
|
+
const Batcher = WebexPlugin.extend({
|
|
16
|
+
session: {
|
|
17
|
+
deferreds: {
|
|
18
|
+
type: 'object',
|
|
19
|
+
default() {
|
|
20
|
+
return new Map();
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
queue: {
|
|
24
|
+
type: 'array',
|
|
25
|
+
default() {
|
|
26
|
+
return [];
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
derived: {
|
|
32
|
+
bounce: {
|
|
33
|
+
fn() {
|
|
34
|
+
return cappedDebounce((...args) => this.executeQueue(...args), this.config.batcherWait, {
|
|
35
|
+
maxCalls: this.config.batcherMaxCalls,
|
|
36
|
+
maxWait: this.config.batcherMaxWait,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Requests an item from a batched API
|
|
44
|
+
* @param {Object} item
|
|
45
|
+
* @returns {Promise<mixed>}
|
|
46
|
+
*/
|
|
47
|
+
request(item) {
|
|
48
|
+
// So far, I can't find a way to avoid three layers of nesting here.
|
|
49
|
+
/* eslint max-nested-callbacks: [0] */
|
|
50
|
+
const defer = new Defer();
|
|
51
|
+
|
|
52
|
+
this.fingerprintRequest(item)
|
|
53
|
+
.then((idx) => {
|
|
54
|
+
if (this.deferreds.has(idx)) {
|
|
55
|
+
defer.resolve(this.deferreds.get(idx).promise);
|
|
56
|
+
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.deferreds.set(idx, defer);
|
|
60
|
+
this.prepareItem(item)
|
|
61
|
+
.then((req) => {
|
|
62
|
+
defer.promise = defer.promise
|
|
63
|
+
.then(tap(() => this.deferreds.delete(idx)))
|
|
64
|
+
.catch((reason) => {
|
|
65
|
+
this.deferreds.delete(idx);
|
|
66
|
+
|
|
67
|
+
return Promise.reject(reason);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.enqueue(req)
|
|
71
|
+
.then(() => this.bounce())
|
|
72
|
+
.catch((reason) => defer.reject(reason));
|
|
73
|
+
})
|
|
74
|
+
.catch((reason) => defer.reject(reason));
|
|
75
|
+
})
|
|
76
|
+
.catch((reason) => defer.reject(reason));
|
|
77
|
+
|
|
78
|
+
return defer.promise;
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Adds an item to the queue.
|
|
83
|
+
* Intended to be overridden
|
|
84
|
+
* @param {mixed} req
|
|
85
|
+
* @returns {Promise<undefined>}
|
|
86
|
+
*/
|
|
87
|
+
enqueue(req) {
|
|
88
|
+
this.queue.push(req);
|
|
89
|
+
|
|
90
|
+
return Promise.resolve();
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Transform the item before adding it to the queue
|
|
95
|
+
* Intended to be overridden
|
|
96
|
+
* @param {mixed} item
|
|
97
|
+
* @returns {Promise<mixed>}
|
|
98
|
+
*/
|
|
99
|
+
prepareItem(item) {
|
|
100
|
+
return Promise.resolve(item);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Detaches the current queue, does any appropriate transforms, and submits it
|
|
105
|
+
* to the API.
|
|
106
|
+
* @returns {Promise<undefined>}
|
|
107
|
+
*/
|
|
108
|
+
executeQueue() {
|
|
109
|
+
const queue = this.queue.splice(0, this.config.batcherMaxCalls);
|
|
110
|
+
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
resolve(
|
|
113
|
+
this.prepareRequest(queue)
|
|
114
|
+
.then((payload) =>
|
|
115
|
+
this.submitHttpRequest(payload).then((res) => this.handleHttpSuccess(res))
|
|
116
|
+
)
|
|
117
|
+
.catch((reason) => {
|
|
118
|
+
if (reason instanceof WebexHttpError) {
|
|
119
|
+
return this.handleHttpError(reason);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return Promise.all(
|
|
123
|
+
queue.map((item) =>
|
|
124
|
+
this.getDeferredForRequest(item).then((defer) => {
|
|
125
|
+
defer.reject(reason);
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
}).catch((reason) => {
|
|
132
|
+
this.logger.error(process.env.NODE_ENV === 'production' ? reason : reason.stack);
|
|
133
|
+
|
|
134
|
+
return Promise.reject(reason);
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Performs any final transforms on the queue before submitting it to the API
|
|
140
|
+
* Intended to be overridden
|
|
141
|
+
* @param {Object|Array} queue
|
|
142
|
+
* @returns {Promise<Object>}
|
|
143
|
+
*/
|
|
144
|
+
prepareRequest(queue) {
|
|
145
|
+
return Promise.resolve(queue);
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Submits the prepared request body to the API.
|
|
150
|
+
* This method *must* be overridden
|
|
151
|
+
* @param {Object} payload
|
|
152
|
+
* @returns {Promise<HttpResponseObject>}
|
|
153
|
+
*/
|
|
154
|
+
// eslint-disable-next-line no-unused-vars
|
|
155
|
+
submitHttpRequest(payload) {
|
|
156
|
+
throw new Error('request() must be implemented');
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Actions taken when the http request returns a success
|
|
161
|
+
* Intended to be overridden
|
|
162
|
+
* @param {Promise<HttpResponseObject>} res
|
|
163
|
+
* @returns {Promise<undefined>}
|
|
164
|
+
*/
|
|
165
|
+
handleHttpSuccess(res) {
|
|
166
|
+
return Promise.all(
|
|
167
|
+
((res.body && res.body.items) || res.body).map((item) => this.acceptItem(item))
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Actions taken when the http request returns a failure. Typically, this
|
|
173
|
+
* means failing the entire queue, but could be overridden in some
|
|
174
|
+
* implementations to e.g. reenqueue.
|
|
175
|
+
* Intended to be overridden
|
|
176
|
+
* @param {WebexHttpError} reason
|
|
177
|
+
* @returns {Promise<undefined>}
|
|
178
|
+
*/
|
|
179
|
+
handleHttpError(reason) {
|
|
180
|
+
if (reason instanceof WebexHttpError) {
|
|
181
|
+
if (has(reason, 'options.body.map')) {
|
|
182
|
+
return Promise.all(
|
|
183
|
+
reason.options.body.map((item) =>
|
|
184
|
+
this.getDeferredForRequest(item).then((defer) => {
|
|
185
|
+
defer.reject(reason);
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.logger.error('http error handler called without a WebexHttpError object', reason);
|
|
192
|
+
|
|
193
|
+
return Promise.reject(reason);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Determines if the item succeeded or failed and delegates accordingly
|
|
198
|
+
* @param {Object} item
|
|
199
|
+
* @returns {Promise<undefined>}
|
|
200
|
+
*/
|
|
201
|
+
acceptItem(item) {
|
|
202
|
+
return this.didItemFail(item).then((didFail) => {
|
|
203
|
+
if (didFail) {
|
|
204
|
+
return this.handleItemFailure(item);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return this.handleItemSuccess(item);
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Indicates if the specified response item implies a success or a failure
|
|
213
|
+
* Intended to be overridden
|
|
214
|
+
* @param {Object} item
|
|
215
|
+
* @returns {Promise<Boolean>}
|
|
216
|
+
*/
|
|
217
|
+
// eslint-disable-next-line no-unused-vars
|
|
218
|
+
didItemFail(item) {
|
|
219
|
+
return Promise.resolve(false);
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Finds the Defer for the specified item and rejects its promise
|
|
224
|
+
* Intended to be overridden
|
|
225
|
+
* @param {Object} item
|
|
226
|
+
* @returns {Promise<undefined>}
|
|
227
|
+
*/
|
|
228
|
+
handleItemFailure(item) {
|
|
229
|
+
return this.getDeferredForResponse(item).then((defer) => {
|
|
230
|
+
defer.reject(item);
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Finds the Defer for the specified item and resolves its promise
|
|
236
|
+
* Intended to be overridden
|
|
237
|
+
* @param {Object} item
|
|
238
|
+
* @returns {Promise<undefined>}
|
|
239
|
+
*/
|
|
240
|
+
handleItemSuccess(item) {
|
|
241
|
+
return this.getDeferredForResponse(item).then((defer) => {
|
|
242
|
+
defer.resolve(item);
|
|
243
|
+
});
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Returns the Deferred for the specified request item
|
|
248
|
+
* @param {Object} item
|
|
249
|
+
* @returns {Promise<Defer>}
|
|
250
|
+
*/
|
|
251
|
+
getDeferredForRequest(item) {
|
|
252
|
+
return this.fingerprintRequest(item).then((idx) => {
|
|
253
|
+
const defer = this.deferreds.get(idx);
|
|
254
|
+
|
|
255
|
+
/* istanbul ignore if */
|
|
256
|
+
if (!defer) {
|
|
257
|
+
throw new Error('Could not find pending request for received response');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return defer;
|
|
261
|
+
});
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Returns the Deferred for the specified response item
|
|
266
|
+
* @param {Object} item
|
|
267
|
+
* @returns {Promise<Defer>}
|
|
268
|
+
*/
|
|
269
|
+
getDeferredForResponse(item) {
|
|
270
|
+
return this.fingerprintResponse(item).then((idx) => {
|
|
271
|
+
const defer = this.deferreds.get(idx);
|
|
272
|
+
|
|
273
|
+
/* istanbul ignore if */
|
|
274
|
+
if (!defer) {
|
|
275
|
+
throw new Error('Could not find pending request for received response');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return defer;
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Generates a unique identifier for the item in a request payload
|
|
284
|
+
* Intended to be overridden
|
|
285
|
+
* Note that overrides must return a primitive.
|
|
286
|
+
* @param {Object} item
|
|
287
|
+
* @returns {Promise<primitive>}
|
|
288
|
+
*/
|
|
289
|
+
// eslint-disable-next-line no-unused-vars
|
|
290
|
+
fingerprintRequest(item) {
|
|
291
|
+
throw new Error('fingerprintRequest() must be implemented');
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generates a unique identifier for the item in a response payload
|
|
296
|
+
* Intended to be overridden
|
|
297
|
+
* Note that overrides must return a primitive.
|
|
298
|
+
* @param {Object} item
|
|
299
|
+
* @returns {Promise<primitive>}
|
|
300
|
+
*/
|
|
301
|
+
// eslint-disable-next-line no-unused-vars
|
|
302
|
+
fingerprintResponse(item) {
|
|
303
|
+
throw new Error('fingerprintResponse() must be implemented');
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
export default Batcher;
|