particle-api-js 10.3.0 → 10.3.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 +23 -21
- package/CHANGELOG.md +8 -0
- package/EventStream-e2e-browser.html +7 -7
- package/EventStream-e2e-node.js +14 -14
- package/dist/particle.min.js +1 -1
- package/dist/particle.min.js.map +1 -1
- package/docs/api.md +3448 -875
- package/karma.conf.js +59 -59
- package/package.json +1 -1
- package/src/Agent.js +387 -363
- package/src/Client.js +162 -162
- package/src/Defaults.js +5 -5
- package/src/EventStream.js +254 -253
- package/src/Library.js +21 -21
- package/src/Particle.js +2685 -2649
- package/test/.eslintrc +5 -5
- package/test/Agent.integration.js +14 -14
- package/test/Agent.spec.js +495 -495
- package/test/Client.spec.js +203 -203
- package/test/Defaults.spec.js +20 -20
- package/test/EventStream.spec.js +231 -231
- package/test/FakeAgent.js +18 -18
- package/test/Library.spec.js +29 -29
- package/test/Particle.integration.js +29 -29
- package/test/Particle.spec.js +3127 -3127
- package/test/fixtures/index.js +7 -7
- package/test/support/FixtureHttpServer.js +16 -16
- package/test/test-setup.js +3 -3
- package/tsconfig.json +3 -1
- package/webpack.config.js +39 -39
package/src/Agent.js
CHANGED
|
@@ -23,385 +23,409 @@ const qs = require('qs');
|
|
|
23
23
|
const fs = require('../fs');
|
|
24
24
|
const packageJson = require('../package.json');
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {string} AccessToken
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} BasicAuth
|
|
32
|
+
* @property {string} username
|
|
33
|
+
* @property {string} password
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {AccessToken | BasicAuth} Auth Prefer using an access token over basic auth for better security
|
|
38
|
+
*/
|
|
39
|
+
|
|
26
40
|
/**
|
|
27
41
|
* The object returned for a basic request
|
|
28
42
|
* @typedef {object} JSONResponse
|
|
29
|
-
* @property {number} statusCode
|
|
30
|
-
* @property {object} body
|
|
43
|
+
* @property {number} statusCode The HTTP response status
|
|
44
|
+
* @property {object} body The endpoint's response parsed as a JSON
|
|
31
45
|
*/
|
|
32
46
|
|
|
33
47
|
/**
|
|
34
48
|
* The possible response from an API request
|
|
35
|
-
* @typedef {JSONResponse|Buffer|ArrayBuffer} RequestResponse The type is based on
|
|
49
|
+
* @typedef {JSONResponse | Buffer | ArrayBuffer} RequestResponse The type is based on
|
|
36
50
|
* the request config and whether is on browser or node
|
|
37
51
|
*/
|
|
38
52
|
|
|
39
53
|
/**
|
|
40
54
|
* The error object generated in case of a failed request
|
|
41
55
|
* @typedef {object} RequestError
|
|
42
|
-
* @property {number} statusCode
|
|
43
|
-
* @property {string} errorDescription
|
|
44
|
-
* @property {string} shortErrorDescription
|
|
45
|
-
* @property {object} body
|
|
46
|
-
* @property {object} error
|
|
56
|
+
* @property {number} statusCode The HTTP response status
|
|
57
|
+
* @property {string} errorDescription Details on what caused the failed request
|
|
58
|
+
* @property {string} shortErrorDescription Summarized version of the fail reason
|
|
59
|
+
* @property {object} body The response object from the request
|
|
60
|
+
* @property {object} error The error object from the request
|
|
47
61
|
*/
|
|
48
62
|
|
|
49
63
|
class Agent {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
64
|
+
constructor(baseUrl){
|
|
65
|
+
this.setBaseUrl(baseUrl);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setBaseUrl(baseUrl) {
|
|
69
|
+
this.baseUrl = baseUrl;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Make a GET request
|
|
74
|
+
* @param {object} params Configurations to customize the request
|
|
75
|
+
* @param {string} params.uri The URI to request
|
|
76
|
+
* @param {Auth} [params.auth] Authorization token to use
|
|
77
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
78
|
+
* @param {object} [params.query] Key/Value pairs of query params
|
|
79
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
80
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
81
|
+
*/
|
|
82
|
+
get({ uri, auth, headers, query, context }) {
|
|
83
|
+
return this.request({ uri, method: 'get', auth, headers, query, context });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Make a HEAD request
|
|
88
|
+
* @param {object} params Configurations to customize the request
|
|
89
|
+
* @param {string} params.uri The URI to request
|
|
90
|
+
* @param {Auth} [params.auth] Authorization token to use
|
|
91
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
92
|
+
* @param {object} [params.query] Key/Value pairs of query params
|
|
93
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
94
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
95
|
+
*/
|
|
96
|
+
head({ uri, auth, headers, query, context }) {
|
|
97
|
+
return this.request({ uri, method: 'head', auth, headers, query, context });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Make a POST request
|
|
102
|
+
* @param {object} params Configurations to customize the request
|
|
103
|
+
* @param {string} params.uri The URI to request
|
|
104
|
+
* @param {Auth} [params.auth] Authorization token to use
|
|
105
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
106
|
+
* @param {object} [params.data] Key/Value pairs of query params
|
|
107
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
108
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
109
|
+
*/
|
|
110
|
+
post({ uri, headers, data, auth, context }) {
|
|
111
|
+
return this.request({ uri, method: 'post', auth, headers, data, context });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Make a PUT request
|
|
116
|
+
* @param {object} params Configurations to customize the request
|
|
117
|
+
* @param {string} params.uri The URI to request
|
|
118
|
+
* @param {Auth} [params.auth] Authorization token to use
|
|
119
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
120
|
+
* @param {object} [params.data] Key/VAlue pairs of query params
|
|
121
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
122
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
123
|
+
*/
|
|
124
|
+
put({ uri, auth, headers, data, context }) {
|
|
125
|
+
return this.request({ uri, method: 'put', auth, headers, data, context });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Make a DELETE request
|
|
130
|
+
* @param {object} params Configurations to customize the request
|
|
131
|
+
* @param {string} params.uri The URI to request
|
|
132
|
+
* @param {Auth} [params.auth] Authorization token to use
|
|
133
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
134
|
+
* @param {object} [params.data] Key/Value pairs of query params
|
|
135
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
136
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
137
|
+
*/
|
|
138
|
+
delete({ uri, auth, headers, data, context }) {
|
|
139
|
+
return this.request({ uri, method: 'delete', auth, headers, data, context });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
*
|
|
144
|
+
* @param {object} config An obj with all the possible request configurations
|
|
145
|
+
* @param {string} config.uri The URI to request
|
|
146
|
+
* @param {string} config.method The method used to request the URI, should be in uppercase.
|
|
147
|
+
* @param {object} [config.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
148
|
+
* @param {object} [config.data] Arbitrary data to send as the body.
|
|
149
|
+
* @param {Auth} [config.auth] Authorization
|
|
150
|
+
* @param {object} [config.query] Query parameters
|
|
151
|
+
* @param {object} [config.form] Form fields
|
|
152
|
+
* @param {object} [config.files] Array of file names and file content
|
|
153
|
+
* @param {object} [config.context] The invocation context, describing the tool and project.
|
|
154
|
+
* @param {boolean} [config.isBuffer=false] Indicate if the response should be treated as Buffer instead of JSON
|
|
155
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
156
|
+
*/
|
|
157
|
+
request({
|
|
158
|
+
uri,
|
|
159
|
+
method,
|
|
160
|
+
headers = undefined,
|
|
161
|
+
data = undefined,
|
|
162
|
+
auth,
|
|
163
|
+
query = undefined,
|
|
164
|
+
form = undefined,
|
|
165
|
+
files = undefined,
|
|
166
|
+
context = undefined,
|
|
167
|
+
isBuffer = false
|
|
168
|
+
}){
|
|
169
|
+
const requestFiles = this._sanitizeFiles(files);
|
|
170
|
+
const requestParams = this._buildRequest({ uri, method, headers, data, auth, query, form, context, files: requestFiles });
|
|
171
|
+
return this._promiseResponse(requestParams, isBuffer);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Promises to send the request and retrieve the response.
|
|
176
|
+
* @param {[string, object]} requestParams First argument is the URI to request, the second one are the options.
|
|
177
|
+
* @param {boolean} isBuffer Indicate if the response body should be returned as a Buffer (Node) / ArrayBuffer (browser) instead of JSON
|
|
178
|
+
* @param {function} [makerequest=fetch] The fetch function to use. Override for testing.
|
|
179
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
180
|
+
* @private
|
|
181
|
+
*/
|
|
182
|
+
_promiseResponse(requestParams, isBuffer, makerequest = fetch) {
|
|
183
|
+
let status;
|
|
184
|
+
return makerequest(...requestParams)
|
|
185
|
+
.then((resp) => {
|
|
186
|
+
status = resp.status;
|
|
187
|
+
if (!resp.ok) {
|
|
188
|
+
return resp.text().then((err) => {
|
|
189
|
+
const objError = JSON.parse(err);
|
|
190
|
+
// particle-commnds/src/cmd/api expects response.text. to be a string
|
|
191
|
+
const response = Object.assign(resp, { text: err });
|
|
192
|
+
throw Object.assign(objError, { response });
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (status === 204) { // Can't do resp.json() since there is no body to parse
|
|
196
|
+
return '';
|
|
197
|
+
}
|
|
198
|
+
if (isBuffer) {
|
|
199
|
+
return resp.blob();
|
|
200
|
+
}
|
|
201
|
+
return resp.json();
|
|
202
|
+
}).then((body) => {
|
|
203
|
+
if (isBuffer) {
|
|
204
|
+
return body.arrayBuffer().then((arrayBuffer) => {
|
|
205
|
+
if (!this.isForBrowser()) {
|
|
206
|
+
return Buffer.from(arrayBuffer);
|
|
207
|
+
}
|
|
208
|
+
return arrayBuffer;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
body,
|
|
213
|
+
statusCode: status
|
|
214
|
+
};
|
|
215
|
+
}).catch((error) => {
|
|
216
|
+
const errorType = status ? `HTTP error ${status}` : 'Network error';
|
|
217
|
+
let errorDescription = `${errorType} from ${requestParams[0]}`;
|
|
218
|
+
let shortErrorDescription;
|
|
219
|
+
if (error.error_description) { // Fetch responded with ok false
|
|
220
|
+
errorDescription = `${errorDescription} - ${error.error_description}`;
|
|
221
|
+
shortErrorDescription = error.error_description;
|
|
222
|
+
}
|
|
223
|
+
const reason = new Error(errorDescription);
|
|
224
|
+
Object.assign(reason, {
|
|
225
|
+
statusCode: status,
|
|
226
|
+
errorDescription,
|
|
227
|
+
shortErrorDescription,
|
|
228
|
+
error,
|
|
229
|
+
body: error
|
|
230
|
+
});
|
|
231
|
+
throw reason;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generate the params in a format valid for 'fetch'
|
|
237
|
+
* @param {object} config Configurations to customize the request
|
|
238
|
+
* @param {string} config.uri The URI to request
|
|
239
|
+
* @param {string} config.method The method used to request the URI, should be in uppercase.
|
|
240
|
+
* @param {object} [config.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
241
|
+
* @param {object} [config.data] Arbitrary data to send as the body.
|
|
242
|
+
* @param {Auth} [config.auth] Authorization
|
|
243
|
+
* @param {object} [config.query] Query parameters
|
|
244
|
+
* @param {object} [config.form] Form fields
|
|
245
|
+
* @param {object} [config.files] Array of file names and file content
|
|
246
|
+
* @param {object} [config.context] The invocation context, describing the tool and project.
|
|
247
|
+
* @returns {[string, object]} The uri to make the request too, and extra configs
|
|
248
|
+
* @private
|
|
249
|
+
*/
|
|
250
|
+
_buildRequest({ uri, method, headers, data, auth, query, form, files, context }){
|
|
251
|
+
let actualUri = uri;
|
|
252
|
+
if (this.baseUrl && uri[0] === '/') {
|
|
253
|
+
actualUri = `${this.baseUrl}${uri}`;
|
|
254
|
+
}
|
|
255
|
+
if (query) {
|
|
256
|
+
const queryParams = qs.stringify(query);
|
|
257
|
+
const hasParams = actualUri.includes('?');
|
|
258
|
+
actualUri = `${actualUri}${hasParams ? '&' : '?'}${queryParams}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const userAgentHeader = { 'User-Agent': `${packageJson.name}/${packageJson.version} (${packageJson.repository.url})` };
|
|
262
|
+
let body;
|
|
263
|
+
let contentTypeHeader;
|
|
264
|
+
if (files){
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
contentTypeHeader = {}; // Needed to allow fetch create its own
|
|
267
|
+
body = this._getFromData(files, form);
|
|
268
|
+
} else if (form){
|
|
269
|
+
contentTypeHeader = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
270
|
+
body = qs.stringify(form);
|
|
271
|
+
} else if (data){
|
|
272
|
+
contentTypeHeader = { 'Content-Type': 'application/json' };
|
|
273
|
+
body = JSON.stringify(data);
|
|
274
|
+
}
|
|
275
|
+
const finalHeaders = Object.assign({},
|
|
276
|
+
userAgentHeader,
|
|
277
|
+
contentTypeHeader,
|
|
278
|
+
this._getAuthorizationHeader(auth),
|
|
279
|
+
this._getContextHeaders(context),
|
|
280
|
+
headers
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return [actualUri, { method, body, headers: finalHeaders }];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
isForBrowser() {
|
|
287
|
+
return typeof window !== 'undefined';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
_getFromData(files, form) {
|
|
291
|
+
const formData = new FormData();
|
|
292
|
+
for (let [name, file] of Object.entries(files)){
|
|
293
|
+
let path = file.path;
|
|
294
|
+
let fileData = file.data;
|
|
295
|
+
if (!this.isForBrowser()) {
|
|
296
|
+
const nodeFormData = this._getNodeFormData(file);
|
|
297
|
+
path = nodeFormData.path;
|
|
298
|
+
fileData = nodeFormData.file;
|
|
299
|
+
}
|
|
300
|
+
formData.append(name, fileData, path);
|
|
301
|
+
}
|
|
302
|
+
if (form){
|
|
303
|
+
for (let [name, value] of Object.entries(form)){
|
|
304
|
+
formData.append(name, value);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return formData;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
_getNodeFormData(file) {
|
|
311
|
+
let fileData = file.data;
|
|
312
|
+
if (typeof file.data === 'string') {
|
|
313
|
+
fileData = fs.createReadStream(file.data);
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
file: fileData,
|
|
317
|
+
path: { filepath: file.path } // Different API for nodejs
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
_getContextHeaders(context = {}) {
|
|
322
|
+
return Object.assign({},
|
|
323
|
+
this._getToolContext(context.tool),
|
|
324
|
+
this._getProjectContext(context.project)
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
_getToolContext(tool = {}){
|
|
329
|
+
let value = '';
|
|
330
|
+
if (tool.name){
|
|
331
|
+
value += this._toolIdent(tool);
|
|
332
|
+
if (tool.components){
|
|
333
|
+
for (let component of tool.components){
|
|
334
|
+
value += ', '+this._toolIdent(component);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (value){
|
|
339
|
+
return { 'X-Particle-Tool': value };
|
|
340
|
+
}
|
|
341
|
+
return {};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_toolIdent(tool){
|
|
345
|
+
return this._nameAtVersion(tool.name, tool.version);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
_nameAtVersion(name, version){
|
|
349
|
+
let value = '';
|
|
350
|
+
if (name){
|
|
351
|
+
value += name;
|
|
352
|
+
if (version){
|
|
353
|
+
value += '@'+version;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return value;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
_getProjectContext(project = {}){
|
|
360
|
+
let value = this._buildSemicolonSeparatedProperties(project, 'name');
|
|
361
|
+
if (value){
|
|
362
|
+
return { 'X-Particle-Project': value };
|
|
363
|
+
}
|
|
364
|
+
return {};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Creates a string like primaryPropertyValue; name=value; name1=value
|
|
369
|
+
* from the properties of an object.
|
|
370
|
+
* @param {object} obj The object to create the string from
|
|
371
|
+
* @param {string} primaryProperty The name of the primary property which is the default value and must be defined.
|
|
372
|
+
* @private
|
|
373
|
+
* @return {string} The formatted string representing the object properties and the default property.
|
|
374
|
+
*/
|
|
375
|
+
_buildSemicolonSeparatedProperties(obj, primaryProperty){
|
|
376
|
+
let value = '';
|
|
377
|
+
if (obj[primaryProperty]){
|
|
378
|
+
value += obj[primaryProperty];
|
|
379
|
+
for (let prop in obj){
|
|
380
|
+
if (prop!==primaryProperty && obj.hasOwnProperty(prop)){
|
|
381
|
+
value += '; '+prop+'='+obj[prop];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return value;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Adds an authorization header.
|
|
390
|
+
* @param {Auth} [auth] The authorization bearer token.
|
|
391
|
+
* @returns {object} The original request.
|
|
392
|
+
*/
|
|
393
|
+
_getAuthorizationHeader(auth){
|
|
394
|
+
if (!auth) {
|
|
395
|
+
return {};
|
|
396
|
+
}
|
|
397
|
+
if (typeof auth === 'string') {
|
|
398
|
+
return { Authorization: `Bearer ${auth}` };
|
|
399
|
+
}
|
|
400
|
+
let encoded;
|
|
401
|
+
if (this.isForBrowser()) {
|
|
402
|
+
encoded = btoa(`${auth.username}:${auth.password}`);
|
|
403
|
+
} else {
|
|
404
|
+
encoded = Buffer.from(`${auth.username}:${auth.password}`)
|
|
405
|
+
.toString('base64');
|
|
406
|
+
}
|
|
407
|
+
return { Authorization: `Basic ${encoded}` };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
*
|
|
412
|
+
* @param {Object} files converts the file names to file, file1, file2.
|
|
413
|
+
* @returns {object} the renamed files.
|
|
414
|
+
*/
|
|
415
|
+
_sanitizeFiles(files){
|
|
416
|
+
let requestFiles;
|
|
417
|
+
if (files){
|
|
418
|
+
requestFiles = {};
|
|
419
|
+
Object.keys(files).forEach((k, i) => {
|
|
420
|
+
const name = i ? `file${i + 1}` : 'file';
|
|
421
|
+
requestFiles[name] = {
|
|
422
|
+
data: files[k],
|
|
423
|
+
path: k
|
|
424
|
+
};
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return requestFiles;
|
|
428
|
+
}
|
|
405
429
|
}
|
|
406
430
|
|
|
407
431
|
module.exports = Agent;
|