particle-api-js 9.4.1 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +7 -5
- package/CHANGELOG.md +11 -0
- package/{test/EventStream-e2e-browser.html → EventStream-e2e-browser.html} +0 -1
- package/{test/EventStream-e2e-node.js → EventStream-e2e-node.js} +2 -3
- package/README.md +2 -2
- package/RELEASE.md +1 -1
- package/dist/particle.min.js +1 -399
- package/dist/particle.min.js.map +1 -1
- package/docs/api.md +5223 -115
- package/fs.js +2 -0
- package/karma.conf.js +18 -6
- package/package.json +23 -26
- package/src/Agent.js +407 -0
- package/src/Client.js +170 -0
- package/src/Defaults.js +7 -0
- package/src/EventStream.js +263 -0
- package/src/Library.js +33 -0
- package/src/Particle.js +2644 -0
- package/test/Agent.integration.js +5 -4
- package/test/Agent.spec.js +174 -291
- package/test/Client.spec.js +7 -7
- package/test/Defaults.spec.js +2 -2
- package/test/EventStream.spec.js +6 -4
- package/test/FakeAgent.js +2 -2
- package/test/Library.spec.js +2 -2
- package/test/Particle.integration.js +7 -7
- package/test/Particle.spec.js +332 -18
- package/test/fixtures/index.js +4 -18
- package/test/support/FixtureHttpServer.js +5 -3
- package/test/test-setup.js +5 -5
- package/tsconfig.json +14 -0
- package/webpack.config.js +45 -0
- package/.babelrc +0 -4
- package/lib/Agent.js +0 -516
- package/lib/Agent.js.map +0 -1
- package/lib/Client.js +0 -312
- package/lib/Client.js.map +0 -1
- package/lib/Defaults.js +0 -14
- package/lib/Defaults.js.map +0 -1
- package/lib/EventStream.js +0 -335
- package/lib/EventStream.js.map +0 -1
- package/lib/Library.js +0 -67
- package/lib/Library.js.map +0 -1
- package/lib/Particle.js +0 -3248
- package/lib/Particle.js.map +0 -1
- package/lib/superagent-binary-parser.js +0 -20
- package/lib/superagent-binary-parser.js.map +0 -1
- package/test/Client.integration.js +0 -69
- package/test/fixtures/tarball.tar.gz +0 -0
- package/test/fixtures/test-library-publish-0.0.1.tar.gz +0 -0
- package/test/fixtures/test-library-publish-0.0.2.tar.gz +0 -0
package/fs.js
ADDED
package/karma.conf.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Karma configuration
|
|
2
2
|
// Generated on Wed Jul 20 2016 12:00:09 GMT-0400 (EDT)
|
|
3
|
+
const webpackConf = require('./webpack.config.js');
|
|
4
|
+
const webpack = require('webpack');
|
|
3
5
|
|
|
4
6
|
module.exports = function karmaCfg(config){
|
|
5
7
|
config.set({
|
|
@@ -8,7 +10,7 @@ module.exports = function karmaCfg(config){
|
|
|
8
10
|
|
|
9
11
|
// frameworks to use
|
|
10
12
|
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
|
11
|
-
frameworks: ['
|
|
13
|
+
frameworks: ['webpack', 'mocha', 'chai'],
|
|
12
14
|
|
|
13
15
|
// list of files / patterns to load in the browser
|
|
14
16
|
files: [
|
|
@@ -24,14 +26,24 @@ module.exports = function karmaCfg(config){
|
|
|
24
26
|
// preprocess matching files before serving them to the browser
|
|
25
27
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
|
26
28
|
preprocessors: {
|
|
27
|
-
'src/**/*.js': ['
|
|
28
|
-
'test/**/*.js': ['
|
|
29
|
+
'src/**/*.js': ['webpack'],
|
|
30
|
+
'test/**/*.js': ['webpack']
|
|
29
31
|
},
|
|
30
32
|
|
|
31
33
|
// Transform test files to a single browser consumable file
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
webpack: {
|
|
35
|
+
mode: 'development',
|
|
36
|
+
target: 'web',
|
|
37
|
+
devtool: 'inline-source-map',
|
|
38
|
+
output: webpackConf.output,
|
|
39
|
+
externals: webpackConf.externals,
|
|
40
|
+
resolve: webpackConf.resolve,
|
|
41
|
+
plugins: [
|
|
42
|
+
new webpack.ProvidePlugin({ Buffer: ['buffer', 'Buffer'] }),
|
|
43
|
+
new webpack.EnvironmentPlugin({
|
|
44
|
+
SKIP_AGENT_TEST: process.env.SKIP_AGENT_TEST || false
|
|
45
|
+
})
|
|
46
|
+
]
|
|
35
47
|
},
|
|
36
48
|
|
|
37
49
|
// test results reporter to use
|
package/package.json
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "particle-api-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "Particle API Client",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/Particle.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"compile": "babel src -sd lib",
|
|
10
|
-
"test": "npm run lint && npm run test:unit",
|
|
7
|
+
"prepublish": "npm run lint && npm run build",
|
|
8
|
+
"test": "npm run lint && npm run typecheck && npm run test:unit",
|
|
11
9
|
"test:ci": "npm run lint && npm run test:unit -- --forbid-only && npm run coverage",
|
|
12
|
-
"test:unit": "mocha test/ -R spec
|
|
10
|
+
"test:unit": "mocha test/ -R spec",
|
|
13
11
|
"test:unit:silent": "npm run test:unit > tmp/test-unit-log.txt 2>&1",
|
|
14
12
|
"test:browser": "karma start --single-run",
|
|
15
13
|
"test:watch": "npm run test:unit -- --watch",
|
|
14
|
+
"typecheck": "tsc --noEmit",
|
|
16
15
|
"coverage": "nyc --reporter=text --include='src/**/*.js' --temp-dir=./tmp/ --check-coverage --lines 91 npm run test:unit:silent",
|
|
17
16
|
"lint": "eslint . --ext .js --format unix --ignore-path .gitignore --ignore-pattern \"dist/*\"",
|
|
18
17
|
"lint:fix": "npm run lint -- --fix",
|
|
19
18
|
"docs": "documentation build src/Particle.js --shallow -g -f md -o docs/api.md",
|
|
20
|
-
"build": "
|
|
21
|
-
"build-nomin": "
|
|
19
|
+
"build": "webpack --env mode=production",
|
|
20
|
+
"build-nomin": "webpack --env mode=development",
|
|
22
21
|
"preversion": "npm run test && npm run prepublish",
|
|
23
22
|
"reinstall": "rm -rf ./node_modules && npm i",
|
|
24
23
|
"version": "npm run build && npm run docs && npm run update-changelog && git add dist/* docs/*",
|
|
@@ -46,43 +45,41 @@
|
|
|
46
45
|
],
|
|
47
46
|
"license": "Apache-2.0",
|
|
48
47
|
"devDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"babel-plugin-add-module-exports": "^0.1.2",
|
|
52
|
-
"babel-plugin-transform-runtime": "^6.9.0",
|
|
53
|
-
"babel-preset-es2015": "^6.6.0",
|
|
54
|
-
"babel-register": "^6.5.2",
|
|
55
|
-
"babelify": "^7.3.0",
|
|
56
|
-
"brfs": "^1.4.3",
|
|
57
|
-
"browserify": "^13.0.0",
|
|
48
|
+
"@types/node": "^20.5.9",
|
|
49
|
+
"buffer": "^6.0.3",
|
|
58
50
|
"chai": "^4.3.6",
|
|
59
51
|
"chai-as-promised": "^7.1.1",
|
|
60
52
|
"documentation": "^4.0.0-rc.1",
|
|
61
53
|
"eslint": "^8.17.0",
|
|
62
54
|
"eslint-config-particle": "^2.2.1",
|
|
55
|
+
"events": "^3.3.0",
|
|
63
56
|
"karma": "^1.1.1",
|
|
64
|
-
"karma-browserify": "^5.1.0",
|
|
65
57
|
"karma-chai": "^0.1.0",
|
|
66
58
|
"karma-cli": "^1.0.1",
|
|
67
59
|
"karma-coverage": "^1.1.0",
|
|
68
60
|
"karma-firefox-launcher": "^1.0.0",
|
|
69
61
|
"karma-mocha": "^1.1.1",
|
|
70
|
-
"
|
|
62
|
+
"karma-webpack": "^5.0.0",
|
|
71
63
|
"mocha": "^2.5.1",
|
|
72
64
|
"nyc": "^15.1.0",
|
|
65
|
+
"process": "^0.11.10",
|
|
73
66
|
"should": "^9.0.0",
|
|
74
67
|
"sinon": "^7.2.5",
|
|
75
68
|
"sinon-chai": "^3.7.0",
|
|
76
|
-
"
|
|
69
|
+
"terser-webpack-plugin": "^5.3.9",
|
|
70
|
+
"typescript": "^5.2.2",
|
|
71
|
+
"url": "^0.11.3",
|
|
72
|
+
"webpack": "^5.88.2",
|
|
73
|
+
"webpack-cli": "^5.1.4"
|
|
77
74
|
},
|
|
78
75
|
"dependencies": {
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"superagent-prefix": "0.0.2"
|
|
76
|
+
"form-data": "^4.0.0",
|
|
77
|
+
"node-fetch": "^2.7.0",
|
|
78
|
+
"qs": "^6.11.2",
|
|
79
|
+
"stream-http": "^3.2.0"
|
|
84
80
|
},
|
|
85
81
|
"browser": {
|
|
82
|
+
"./fs": false,
|
|
86
83
|
"http": "stream-http",
|
|
87
84
|
"https": "stream-http"
|
|
88
85
|
},
|
package/src/Agent.js
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/*
|
|
2
|
+
******************************************************************************
|
|
3
|
+
Copyright (c) 2016 Particle Industries, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This program is free software; you can redistribute it and/or
|
|
6
|
+
modify it under the terms of the GNU Lesser General Public
|
|
7
|
+
License as published by the Free Software Foundation, either
|
|
8
|
+
version 3 of the License, or (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
13
|
+
Lesser General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Lesser General Public
|
|
16
|
+
License along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
******************************************************************************
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fetch = require('node-fetch');
|
|
21
|
+
const FormData = require('form-data');
|
|
22
|
+
const qs = require('qs');
|
|
23
|
+
const fs = require('../fs');
|
|
24
|
+
const packageJson = require('../package.json');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The object returned for a basic request
|
|
28
|
+
* @typedef {object} JSONResponse
|
|
29
|
+
* @property {number} statusCode The HTTP response status
|
|
30
|
+
* @property {object} body The endpoint's response parsed as a JSON
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The possible response from an API request
|
|
35
|
+
* @typedef {JSONResponse|Buffer|ArrayBuffer} RequestResponse The type is based on
|
|
36
|
+
* the request config and whether is on browser or node
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The error object generated in case of a failed request
|
|
41
|
+
* @typedef {object} RequestError
|
|
42
|
+
* @property {number} statusCode The HTTP response status
|
|
43
|
+
* @property {string} errorDescription Details on what caused the failed request
|
|
44
|
+
* @property {string} shortErrorDescription Summarized version of the fail reason
|
|
45
|
+
* @property {object} body The response object from the request
|
|
46
|
+
* @property {object} error The error object from the request
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
class Agent {
|
|
50
|
+
constructor(baseUrl){
|
|
51
|
+
this.setBaseUrl(baseUrl);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
setBaseUrl(baseUrl) {
|
|
55
|
+
this.baseUrl = baseUrl;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Make a GET request
|
|
60
|
+
* @param {object} params Configurations to customize the request
|
|
61
|
+
* @param {string} params.uri The URI to request
|
|
62
|
+
* @param {string|object} [params.auth] Authorization token to use
|
|
63
|
+
* @param {object} [params.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
64
|
+
* @param {string|object} [params.query] Key/Value pairs of query params or a correctly formatted string
|
|
65
|
+
* @param {object} [params.context] The invocation context, describing the tool and project
|
|
66
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
67
|
+
*/
|
|
68
|
+
get({ uri, auth, headers, query, context }) {
|
|
69
|
+
return this.request({ uri, method: 'get', auth, headers, query, context });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Make a HEAD request
|
|
74
|
+
* @param {object} params Configurations to customize the request
|
|
75
|
+
* @param {string} params.uri The URI to request
|
|
76
|
+
* @param {string|object} [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 {string|object} [params.query] Key/Value pairs of query params or a correctly formatted string
|
|
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
|
+
head({ uri, auth, headers, query, context }) {
|
|
83
|
+
return this.request({ uri, method: 'head', auth, headers, query, context });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Make a POST request
|
|
88
|
+
* @param {object} params Configurations to customize the request
|
|
89
|
+
* @param {string} params.uri The URI to request
|
|
90
|
+
* @param {string|object} [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.data] Key/Value pairs of query params or a correctly formatted string
|
|
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
|
+
post({ uri, headers, data, auth, context }) {
|
|
97
|
+
return this.request({ uri, method: 'post', auth, headers, data, context });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Make a PUT request
|
|
102
|
+
* @param {object} params Configurations to customize the request
|
|
103
|
+
* @param {string} params.uri The URI to request
|
|
104
|
+
* @param {string|object} [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 or a correctly formatted string
|
|
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
|
+
put({ uri, auth, headers, data, context }) {
|
|
111
|
+
return this.request({ uri, method: 'put', auth, headers, data, context });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Make a DELETE request
|
|
116
|
+
* @param {object} params Configurations to customize the request
|
|
117
|
+
* @param {string} params.uri The URI to request
|
|
118
|
+
* @param {string|object} [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 or a correctly formatted string
|
|
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
|
+
delete({ uri, auth, headers, data, context }) {
|
|
125
|
+
return this.request({ uri, method: 'delete', auth, headers, data, context });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @param {object} config An obj with all the possible request configurations
|
|
131
|
+
* @param {string} config.uri The URI to request
|
|
132
|
+
* @param {string} config.method The method used to request the URI, should be in uppercase.
|
|
133
|
+
* @param {object} [config.headers] Key/Value pairs like `{ 'X-FOO': 'foo', X-BAR: 'bar' }` to send as headers.
|
|
134
|
+
* @param {object} [config.data] Arbitrary data to send as the body.
|
|
135
|
+
* @param {string|object} [config.auth] Authorization
|
|
136
|
+
* @param {string|object} [config.query] Query parameters
|
|
137
|
+
* @param {object} [config.form] Form fields
|
|
138
|
+
* @param {object} [config.files] Array of file names and file content
|
|
139
|
+
* @param {object} [config.context] The invocation context, describing the tool and project.
|
|
140
|
+
* @param {boolean} [config.isBuffer=false] Indicate if the response should be treated as Buffer instead of JSON
|
|
141
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
142
|
+
*/
|
|
143
|
+
request({
|
|
144
|
+
uri,
|
|
145
|
+
method,
|
|
146
|
+
headers = undefined,
|
|
147
|
+
data = undefined,
|
|
148
|
+
auth,
|
|
149
|
+
query = undefined,
|
|
150
|
+
form = undefined,
|
|
151
|
+
files = undefined,
|
|
152
|
+
context = undefined,
|
|
153
|
+
isBuffer = false
|
|
154
|
+
}){
|
|
155
|
+
const requestFiles = this._sanitizeFiles(files);
|
|
156
|
+
const requestParams = this._buildRequest({ uri, method, headers, data, auth, query, form, context, files: requestFiles });
|
|
157
|
+
return this._promiseResponse(requestParams, isBuffer);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Promises to send the request and retreive the response.
|
|
162
|
+
* @param {[string, object]} requestParams First argument is the URI to request, the second one are the options.
|
|
163
|
+
* @param {boolean} isBuffer Indicate if the response body should be returned as a Buffer (Node) / ArrayBuffer (browser) instead of JSON
|
|
164
|
+
* @param {function} [makerequest=fetch] The fetch function to use. Override for testing.
|
|
165
|
+
* @returns {Promise<RequestResponse, RequestError>} A promise that resolves with either the requested data or an error object
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
_promiseResponse(requestParams, isBuffer, makerequest = fetch) {
|
|
169
|
+
let status;
|
|
170
|
+
return makerequest(...requestParams)
|
|
171
|
+
.then((resp) => {
|
|
172
|
+
status = resp.status;
|
|
173
|
+
if (!resp.ok) {
|
|
174
|
+
return resp.text().then((err) => {
|
|
175
|
+
const objError = JSON.parse(err);
|
|
176
|
+
// particle-commnds/src/cmd/api expects response.text. to be a string
|
|
177
|
+
const response = Object.assign(resp, { text: err });
|
|
178
|
+
throw Object.assign(objError, { response });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
if (status === 204) { // Can't do resp.json() since there is no body to parse
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
if (isBuffer) {
|
|
185
|
+
return resp.blob();
|
|
186
|
+
}
|
|
187
|
+
return resp.json();
|
|
188
|
+
}).then((body) => {
|
|
189
|
+
if (isBuffer) {
|
|
190
|
+
return body.arrayBuffer().then((arrayBuffer) => {
|
|
191
|
+
if (!this.isForBrowser()) {
|
|
192
|
+
return Buffer.from(arrayBuffer);
|
|
193
|
+
}
|
|
194
|
+
return arrayBuffer;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
body,
|
|
199
|
+
statusCode: status
|
|
200
|
+
};
|
|
201
|
+
}).catch((error) => {
|
|
202
|
+
const errorType = status ? `HTTP error ${status}` : 'Network error';
|
|
203
|
+
let errorDescription = `${errorType} from ${requestParams[0]}`;
|
|
204
|
+
let shortErrorDescription;
|
|
205
|
+
if (error.error_description) { // Fetch responded with ok false
|
|
206
|
+
errorDescription = `${errorDescription} - ${error.error_description}`;
|
|
207
|
+
shortErrorDescription = error.error_description;
|
|
208
|
+
}
|
|
209
|
+
const reason = new Error(errorDescription);
|
|
210
|
+
Object.assign(reason, {
|
|
211
|
+
statusCode: status,
|
|
212
|
+
errorDescription,
|
|
213
|
+
shortErrorDescription,
|
|
214
|
+
error,
|
|
215
|
+
body: error
|
|
216
|
+
});
|
|
217
|
+
throw reason;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Generate the params in a format valid for 'fetch'
|
|
223
|
+
* @returns {[string, object]} The uri to make the request too, and extra configs
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
_buildRequest({ uri, method, headers, data, auth, query, form, files, context }){
|
|
227
|
+
let actualUri = uri;
|
|
228
|
+
if (this.baseUrl && uri[0] === '/') {
|
|
229
|
+
actualUri = `${this.baseUrl}${uri}`;
|
|
230
|
+
}
|
|
231
|
+
if (query) {
|
|
232
|
+
const queryParams = qs.stringify(query);
|
|
233
|
+
const hasParams = actualUri.includes('?');
|
|
234
|
+
actualUri = `${actualUri}${hasParams ? '&' : '?'}${queryParams}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const userAgentHeader = { 'User-Agent': `${packageJson.name}/${packageJson.version} (${packageJson.repository.url})` };
|
|
238
|
+
let body;
|
|
239
|
+
let contentTypeHeader;
|
|
240
|
+
if (files){
|
|
241
|
+
// @ts-ignore
|
|
242
|
+
contentTypeHeader = {}; // Needed to allow fetch create its own
|
|
243
|
+
body = this._getFromData(files, form);
|
|
244
|
+
} else if (form){
|
|
245
|
+
contentTypeHeader = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
|
246
|
+
body = qs.stringify(form);
|
|
247
|
+
} else if (data){
|
|
248
|
+
contentTypeHeader = { 'Content-Type': 'application/json' };
|
|
249
|
+
body = JSON.stringify(data);
|
|
250
|
+
}
|
|
251
|
+
const finalHeaders = Object.assign({},
|
|
252
|
+
userAgentHeader,
|
|
253
|
+
contentTypeHeader,
|
|
254
|
+
this._getAuthorizationHeader(auth),
|
|
255
|
+
this._getContextHeaders(context),
|
|
256
|
+
headers
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
return [actualUri, { method, body, headers: finalHeaders }];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
isForBrowser() {
|
|
263
|
+
return typeof window !== 'undefined';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
_getFromData(files, form) {
|
|
267
|
+
const formData = new FormData();
|
|
268
|
+
for (let [name, file] of Object.entries(files)){
|
|
269
|
+
let path = file.path;
|
|
270
|
+
let fileData = file.data;
|
|
271
|
+
if (!this.isForBrowser()) {
|
|
272
|
+
const nodeFormData = this._getNodeFormData(file);
|
|
273
|
+
path = nodeFormData.path;
|
|
274
|
+
fileData = nodeFormData.file;
|
|
275
|
+
}
|
|
276
|
+
formData.append(name, fileData, path);
|
|
277
|
+
}
|
|
278
|
+
if (form){
|
|
279
|
+
for (let [name, value] of Object.entries(form)){
|
|
280
|
+
formData.append(name, value);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return formData;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
_getNodeFormData(file) {
|
|
287
|
+
let fileData = file.data;
|
|
288
|
+
if (typeof file.data === 'string') {
|
|
289
|
+
fileData = fs.createReadStream(file.data);
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
file: fileData,
|
|
293
|
+
path: { filepath: file.path } // Different API for nodejs
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_getContextHeaders(context = {}) {
|
|
298
|
+
return Object.assign({},
|
|
299
|
+
this._getToolContext(context.tool),
|
|
300
|
+
this._getProjectContext(context.project)
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
_getToolContext(tool = {}){
|
|
305
|
+
let value = '';
|
|
306
|
+
if (tool.name){
|
|
307
|
+
value += this._toolIdent(tool);
|
|
308
|
+
if (tool.components){
|
|
309
|
+
for (let component of tool.components){
|
|
310
|
+
value += ', '+this._toolIdent(component);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (value){
|
|
315
|
+
return { 'X-Particle-Tool': value };
|
|
316
|
+
}
|
|
317
|
+
return {};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
_toolIdent(tool){
|
|
321
|
+
return this._nameAtVersion(tool.name, tool.version);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
_nameAtVersion(name, version){
|
|
325
|
+
let value = '';
|
|
326
|
+
if (name){
|
|
327
|
+
value += name;
|
|
328
|
+
if (version){
|
|
329
|
+
value += '@'+version;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
_getProjectContext(project = {}){
|
|
336
|
+
let value = this._buildSemicolonSeparatedProperties(project, 'name');
|
|
337
|
+
if (value){
|
|
338
|
+
return { 'X-Particle-Project': value };
|
|
339
|
+
}
|
|
340
|
+
return {};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Creates a string like primaryPropertyValue; name=value; name1=value
|
|
345
|
+
* from the properties of an object.
|
|
346
|
+
* @param {object} obj The object to create the string from
|
|
347
|
+
* @param {string} primaryProperty The name of the primary property which is the default value and must be defined.
|
|
348
|
+
* @private
|
|
349
|
+
* @return {string} The formatted string representing the object properties and the default property.
|
|
350
|
+
*/
|
|
351
|
+
_buildSemicolonSeparatedProperties(obj, primaryProperty){
|
|
352
|
+
let value = '';
|
|
353
|
+
if (obj[primaryProperty]){
|
|
354
|
+
value += obj[primaryProperty];
|
|
355
|
+
for (let prop in obj){
|
|
356
|
+
if (prop!==primaryProperty && obj.hasOwnProperty(prop)){
|
|
357
|
+
value += '; '+prop+'='+obj[prop];
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return value;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Adds an authorization header.
|
|
366
|
+
* @param {string|object} auth The authorization bearer token.
|
|
367
|
+
* @returns {object} The original request.
|
|
368
|
+
*/
|
|
369
|
+
_getAuthorizationHeader(auth){
|
|
370
|
+
if (!auth) {
|
|
371
|
+
return {};
|
|
372
|
+
}
|
|
373
|
+
if (typeof auth === 'string') {
|
|
374
|
+
return { Authorization: `Bearer ${auth}` };
|
|
375
|
+
}
|
|
376
|
+
let encoded;
|
|
377
|
+
if (this.isForBrowser()) {
|
|
378
|
+
encoded = btoa(`${auth.username}:${auth.password}`);
|
|
379
|
+
} else {
|
|
380
|
+
encoded = Buffer.from(`${auth.username}:${auth.password}`)
|
|
381
|
+
.toString('base64');
|
|
382
|
+
}
|
|
383
|
+
return { Authorization: `Basic ${encoded}` };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
*
|
|
388
|
+
* @param {Object} files converts the file names to file, file1, file2.
|
|
389
|
+
* @returns {object} the renamed files.
|
|
390
|
+
*/
|
|
391
|
+
_sanitizeFiles(files){
|
|
392
|
+
let requestFiles;
|
|
393
|
+
if (files){
|
|
394
|
+
requestFiles = {};
|
|
395
|
+
Object.keys(files).forEach((k, i) => {
|
|
396
|
+
const name = i ? `file${i + 1}` : 'file';
|
|
397
|
+
requestFiles[name] = {
|
|
398
|
+
data: files[k],
|
|
399
|
+
path: k
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
return requestFiles;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
module.exports = Agent;
|