@percy/client 1.0.0-beta.9 → 1.0.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/README.md CHANGED
@@ -1,51 +1,82 @@
1
1
  # @percy/client
2
2
 
3
3
  Communicate with Percy's API to create builds and snapshots, upload resources, and finalize builds
4
- and snapshots. Uses `@percy/env` to send environment information with new builds. Can also be used
5
- to query for a project's builds using a read access token.
4
+ and snapshots. Uses [`@percy/env`](.packages/env) to send environment information with new
5
+ builds. Can also be used to query for a project's builds using a read access token.
6
6
 
7
- ## Usage
7
+ - [Usage](#usage)
8
+ - [Create a build](#create-a-build)
9
+ - [Create, upload, and finalize snapshots](#create-upload-and-finalize-snapshots)
10
+ - [Finalize a build](#finalize-a-build)
11
+ - [Query for a build*](#query-for-a-build)
12
+ - [Query for a project's builds*](#query-for-a-projects-builds)
13
+ - [Wait for a build to be finished*](#wait-for-a-build-to-be-finished)
8
14
 
9
- ### `new PercyClient([options])`
15
+ ## Usage
10
16
 
11
17
  ``` js
12
- import PercyClient from '@percy/client';
18
+ import PercyClient from '@percy/client'
13
19
 
14
- // provide a read or write token, defaults to PERCY_TOKEN environment variable
15
- const client = new PercyClient({ token: 'abcdef123456' })
20
+ const client = new PercyClient(options)
16
21
  ```
17
22
 
18
- ### Create a build
23
+ #### Options
24
+
25
+ - `token` — Your project's `PERCY_TOKEN` (**default** `process.env.PERCY_TOKEN`)
26
+ - `clientInfo` — Client info sent to Percy via a user-agent string
27
+ - `environmentInfo` — Environment info also sent with the user-agent string
28
+
29
+ ## Create a build
30
+
31
+ Creates a percy build. Only one build can be created at a time per instance. During this step,
32
+ various environment information is collected via [`@percy/env`](./packages/env#readme) and
33
+ associated with the new build. If `PERCY_PARALLEL_TOTAL` and `PERCY_PARALLEL_NONCE` are present, a
34
+ build shard is created as part of a parallelized Percy build.
19
35
 
20
36
  ``` js
21
37
  await client.createBuild()
22
38
  ```
23
39
 
24
- ### Create, upload, and finalize snapshots
40
+ ## Create, upload, and finalize snapshots
41
+
42
+ This method combines the work of creating a snapshot, uploading any missing resources, and finally
43
+ finalizng the snapshot.
25
44
 
26
45
  ``` js
27
- await client.sendSnapshot({
28
- name,
29
- widths,
30
- minHeight,
31
- enableJavaScript,
32
- clientInfo,
33
- environmentInfo,
34
- // `sha` falls back to `content` sha
35
- resources: [{ url, sha, content, mimetype, root }]
36
- })
46
+ await client.sendSnapshot(buildId, snapshotOptions)
37
47
  ```
38
48
 
39
- ### Finalize a build
49
+ #### Options
50
+
51
+ - `name` — Snapshot name
52
+ - `widths` — Widths to take screenshots at
53
+ - `minHeight` — Miniumum screenshot height
54
+ - `enableJavaScript` — Enable JavaScript for screenshots
55
+ - `clientInfo` — Additional client info
56
+ - `environmentInfo` — Additional environment info
57
+ - `resources` — Array of snapshot resources
58
+ - `url` — Resource URL (**required**)
59
+ - `mimetype` — Resource mimetype (**required**)
60
+ - `content` — Resource content (**required**)
61
+ - `sha` — Resource content sha
62
+ - `root` — Boolean indicating a root resource
63
+
64
+ ## Finalize a build
65
+
66
+ Finalizes a build. When `all` is true, `all-shards=true` is added as a query param so the
67
+ API finalizes all other parallel build shards associated with the build.
40
68
 
41
69
  ``` js
42
- await client.finalizeBuild()
70
+ // finalize a build
71
+ await client.finalizeBuild(buildId)
43
72
 
44
73
  // finalize all parallel build shards
45
- await client.finalizeBuild({ all: true })
74
+ await client.finalizeBuild(buildId, { all: true })
46
75
  ```
47
76
 
48
- ### Query for a build
77
+ ## Query for a build
78
+
79
+ Retrieves build data by id.
49
80
 
50
81
  **Requires a read access token**
51
82
 
@@ -53,19 +84,51 @@ await client.finalizeBuild({ all: true })
53
84
  await client.getBuild(buildId)
54
85
  ```
55
86
 
56
- ### Query for a project's builds
87
+ ## Query for a project's builds
88
+
89
+ Retrieves project builds, optionally filtered. The project slug can be found as part of the
90
+ project's URL. For example, the project slug for `https://percy.io/percy/example` is
91
+ `"percy/example"`.
57
92
 
58
93
  **Requires a read access token**
59
94
 
60
95
  ``` js
61
- await client.getBuilds(projectSlug/*, filters*/)
96
+ // get all builds for a project
97
+ await client.getBuilds(projectSlug)
98
+
99
+ // get all builds for a project's "master" branch
100
+ await client.getBuilds(projectSlug, { branch: 'master' })
62
101
  ```
63
102
 
64
- ### Wait for a build to be finished
103
+ #### Filters
104
+
105
+ - `sha` — A single commit sha
106
+ - `shas` — An array of commit shas
107
+ - `branch` — The name of a branch
108
+ - `state` — The build state (`"pending"`, `"finished"`, etc.)
109
+
110
+ ## Wait for a build to be finished
111
+
112
+ This method resolves when the build has finished and is no longer pending or processing. By default,
113
+ it will time out if there is no update after 10 minutes.
65
114
 
66
115
  **Requires a read access token**
67
116
 
68
117
  ``` js
69
- await client.waitForBuild({ build: 'build-id' })
70
- await client.waitForBuild({ project: 'project-slug', commit: '40-char-sha' })
118
+ // wait for a specific project build by commit sha
119
+ await client.waitForBuild({
120
+ project: 'percy/example',
121
+ commit: '40-char-sha'
122
+ }, data => {
123
+ // called whenever data changes
124
+ console.log(JSON.stringify(data));
125
+ })
71
126
  ```
127
+
128
+ #### Options
129
+
130
+ - `build` — Build ID (**required** when missing `commit`)
131
+ - `commit` — Commit SHA (**required** when missing `build`)
132
+ - `project` — Project slug (**required** when using `commit`)
133
+ - `timeout` — Timeout in milliseconds to wait with no updates (**default** `10 * 60 * 1000`)
134
+ - `interval` — Interval in miliseconds to check for updates (**default** `1000`)
package/package.json CHANGED
@@ -1,28 +1,38 @@
1
1
  {
2
2
  "name": "@percy/client",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.0",
4
4
  "license": "MIT",
5
- "main": "dist/index.js",
6
- "files": [
7
- "dist"
8
- ],
9
- "scripts": {
10
- "build": "babel --root-mode upward src --out-dir dist",
11
- "lint": "eslint --ignore-path ../../.gitignore .",
12
- "test": "cross-env NODE_ENV=test mocha",
13
- "test:coverage": "nyc yarn test"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/percy/cli",
8
+ "directory": "packages/client"
14
9
  },
15
10
  "publishConfig": {
16
11
  "access": "public"
17
12
  },
18
- "mocha": {
19
- "require": "../../scripts/babel-register"
13
+ "engines": {
14
+ "node": ">=14"
20
15
  },
21
- "devDependencies": {
22
- "mock-require": "^3.0.3"
16
+ "files": [
17
+ "./dist",
18
+ "./test/helpers.js"
19
+ ],
20
+ "main": "./dist/index.js",
21
+ "type": "module",
22
+ "exports": {
23
+ ".": "./dist/index.js",
24
+ "./utils": "./dist/utils.js",
25
+ "./test/helpers": "./test/helpers.js"
26
+ },
27
+ "scripts": {
28
+ "build": "node ../../scripts/build",
29
+ "lint": "eslint --ignore-path ../../.gitignore .",
30
+ "test": "node ../../scripts/test",
31
+ "test:coverage": "yarn test --coverage"
23
32
  },
24
33
  "dependencies": {
25
- "@percy/env": "^1.0.0-beta.9"
34
+ "@percy/env": "1.0.0",
35
+ "@percy/logger": "1.0.0"
26
36
  },
27
- "gitHead": "57a2eeb90c7f5cdf8827c78be1e5c12df581f4b5"
37
+ "gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
28
38
  }
package/dist/client.js DELETED
@@ -1,357 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.default = void 0;
7
-
8
- var _env = _interopRequireDefault(require("@percy/env"));
9
-
10
- var _git = require("@percy/env/dist/git");
11
-
12
- var _package = _interopRequireDefault(require("../package.json"));
13
-
14
- var _utils = require("./utils");
15
-
16
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
-
18
- // PercyClient is used to communicate with the Percy API to create and finalize
19
- // builds and snapshot. Uses @percy/env to collect environment information used
20
- // during build creation.
21
- class PercyClient {
22
- constructor({
23
- // read or write token, defaults to PERCY_TOKEN environment variable
24
- token,
25
- // initial user agent info
26
- clientInfo = '',
27
- environmentInfo = '',
28
- // versioned percy api url
29
- apiUrl = 'https://percy.io/api/v1'
30
- } = {}) {
31
- Object.assign(this, {
32
- token,
33
- apiUrl,
34
- httpAgent: (0, _utils.httpAgentFor)(apiUrl),
35
- clientInfo: [].concat(clientInfo),
36
- environmentInfo: [].concat(environmentInfo),
37
- env: new _env.default(process.env),
38
- // build info is stored for reference
39
- build: {
40
- id: null,
41
- number: null,
42
- url: null
43
- }
44
- });
45
- } // Adds additional unique client info.
46
-
47
-
48
- addClientInfo(info) {
49
- if (info && this.clientInfo.indexOf(info) === -1) {
50
- this.clientInfo.push(info);
51
- }
52
- } // Adds additional unique environment info.
53
-
54
-
55
- addEnvironmentInfo(info) {
56
- if (info && this.environmentInfo.indexOf(info) === -1) {
57
- this.environmentInfo.push(info);
58
- }
59
- } // Stringifies client and environment info.
60
-
61
-
62
- userAgent() {
63
- let client = [`Percy/${/\w+$/.exec(this.apiUrl)}`].concat(`${_package.default.name}/${_package.default.version}`, this.clientInfo).filter(Boolean).join(' ');
64
- let environment = this.environmentInfo.concat([`node/${process.version}`, this.env.info]).filter(Boolean).join('; ');
65
- return `${client} (${environment})`;
66
- } // Checks for a Percy token and returns it.
67
-
68
-
69
- getToken() {
70
- let token = this.token || this.env.token;
71
- if (!token) throw new Error('Missing Percy token');
72
- return token;
73
- } // Returns common headers used for each request with additional
74
- // headers. Throws an error when the token is missing, which is a required
75
- // authorization header.
76
-
77
-
78
- headers(headers) {
79
- return Object.assign({
80
- Authorization: `Token token=${this.getToken()}`,
81
- 'User-Agent': this.userAgent()
82
- }, headers);
83
- } // Performs a GET request for an API endpoint with appropriate headers.
84
-
85
-
86
- get(path) {
87
- return (0, _utils.request)(`${this.apiUrl}/${path}`, {
88
- method: 'GET',
89
- agent: this.httpAgent,
90
- headers: this.headers()
91
- });
92
- } // Performs a POST request to a JSON API endpoint with appropriate headers.
93
-
94
-
95
- post(path, body = {}) {
96
- return (0, _utils.request)(`${this.apiUrl}/${path}`, {
97
- method: 'POST',
98
- agent: this.httpAgent,
99
- body: JSON.stringify(body),
100
- headers: this.headers({
101
- 'Content-Type': 'application/vnd.api+json'
102
- })
103
- });
104
- } // Sets build reference data or nullifies it when no data is provided.
105
-
106
-
107
- setBuildData(data) {
108
- var _data$attributes, _data$attributes2;
109
-
110
- return Object.assign(this, {
111
- build: {
112
- id: data === null || data === void 0 ? void 0 : data.id,
113
- number: data === null || data === void 0 ? void 0 : (_data$attributes = data.attributes) === null || _data$attributes === void 0 ? void 0 : _data$attributes['build-number'],
114
- url: data === null || data === void 0 ? void 0 : (_data$attributes2 = data.attributes) === null || _data$attributes2 === void 0 ? void 0 : _data$attributes2['web-url']
115
- }
116
- });
117
- } // Creates a build with optional build resources. Only one build can be
118
- // created at a time per instance so snapshots and build finalization can be
119
- // done more seemlessly without manually tracking build ids
120
-
121
-
122
- async createBuild({
123
- resources = []
124
- } = {}) {
125
- if (this.build.id) {
126
- throw new Error('This client instance has not finalized the previous build');
127
- }
128
-
129
- let body = await this.post('builds', {
130
- data: {
131
- type: 'builds',
132
- attributes: {
133
- branch: this.env.git.branch,
134
- 'target-branch': this.env.target.branch,
135
- 'target-commit-sha': this.env.target.commit,
136
- 'commit-sha': this.env.git.sha,
137
- 'commit-committed-at': this.env.git.committedAt,
138
- 'commit-author-name': this.env.git.authorName,
139
- 'commit-author-email': this.env.git.authorEmail,
140
- 'commit-committer-name': this.env.git.committerName,
141
- 'commit-committer-email': this.env.git.committerEmail,
142
- 'commit-message': this.env.git.message,
143
- 'pull-request-number': this.env.pullRequest,
144
- 'parallel-nonce': this.env.parallel.nonce,
145
- 'parallel-total-shards': this.env.parallel.total,
146
- partial: this.env.partial
147
- },
148
- relationships: {
149
- resources: {
150
- data: resources.map(r => ({
151
- type: 'resources',
152
- id: r.sha || (0, _utils.sha256hash)(r.content),
153
- attributes: {
154
- 'resource-url': r.url,
155
- 'is-root': r.root || null,
156
- mimetype: r.mimetype || null
157
- }
158
- }))
159
- }
160
- }
161
- }
162
- });
163
- this.setBuildData(body === null || body === void 0 ? void 0 : body.data);
164
- return body;
165
- } // Finalizes the active build. When `all` is true, `all-shards=true` is
166
- // added as a query param so the API finalizes all other build shards.
167
-
168
-
169
- async finalizeBuild({
170
- all = false
171
- } = {}) {
172
- if (!this.build.id) {
173
- throw new Error('This client instance has no active build');
174
- }
175
-
176
- let qs = all ? 'all-shards=true' : '';
177
- let body = await this.post(`builds/${this.build.id}/finalize?${qs}`);
178
- this.setBuildData();
179
- return body;
180
- } // Retrieves build data by id. Requires a read access token.
181
-
182
-
183
- async getBuild(buildId) {
184
- return this.get(`builds/${buildId}`);
185
- } // Retrieves project builds optionally filtered. Requires a read access token.
186
-
187
-
188
- async getBuilds(projectSlug, filters = {}) {
189
- let qs = Object.keys(filters).map(k => Array.isArray(filters[k]) ? filters[k].map(v => `filter[${k}][]=${v}`).join('&') : `filter[${k}]=${filters[k]}`).join('&');
190
- return this.get(`projects/${projectSlug}/builds?${qs}`);
191
- } // Resolves when the build has finished and is no longer pending or
192
- // processing. By default, will time out if no update after 10 minutes.
193
-
194
-
195
- waitForBuild({
196
- build,
197
- project,
198
- commit,
199
- progress,
200
- timeout = 600000,
201
- interval = 1000
202
- }) {
203
- if (commit && !project) {
204
- throw new Error('Missing project for commit');
205
- } else if (!commit && !build) {
206
- throw new Error('Missing build ID or commit SHA');
207
- } // get build data by id or project-commit combo
208
-
209
-
210
- let getBuildData = async () => {
211
- let sha = commit && ((0, _git.git)(`rev-parse ${commit}`) || commit);
212
- let body = build ? await this.getBuild(build) : await this.getBuilds(project, {
213
- sha
214
- });
215
- let data = build ? body === null || body === void 0 ? void 0 : body.data : body === null || body === void 0 ? void 0 : body.data[0];
216
- return [data, data === null || data === void 0 ? void 0 : data.attributes.state];
217
- }; // recursively poll every second until the build finishes
218
-
219
-
220
- return new Promise((resolve, reject) => async function poll(last, t) {
221
- try {
222
- let [data, state] = await getBuildData();
223
- let updated = JSON.stringify(data) !== JSON.stringify(last);
224
- let pending = !state || state === 'pending' || state === 'processing'; // new data recieved
225
-
226
- if (updated) {
227
- t = Date.now(); // no new data within the timeout
228
- } else if (Date.now() - t >= timeout) {
229
- throw new Error('Timeout exceeded without an update');
230
- } // call progress after the first update
231
-
232
-
233
- if ((last || pending) && updated && progress) {
234
- progress(data);
235
- } // not finished, poll again
236
-
237
-
238
- if (pending) {
239
- return setTimeout(poll, interval, data, t); // build finished
240
- } else {
241
- resolve(data);
242
- }
243
- } catch (err) {
244
- reject(err);
245
- }
246
- }(null, Date.now()));
247
- } // Uploads a single resource to the active build. If `filepath` is provided,
248
- // `content` is read from the filesystem. The sha is optional and will be
249
- // created from `content` if one is not provided.
250
-
251
-
252
- async uploadResource({
253
- sha,
254
- filepath,
255
- content
256
- }) {
257
- if (!this.build.id) {
258
- throw new Error('This client instance has no active build');
259
- }
260
-
261
- content = filepath ? require('fs').readFileSync(filepath) : content;
262
- return this.post(`builds/${this.build.id}/resources`, {
263
- data: {
264
- type: 'resources',
265
- id: sha || (0, _utils.sha256hash)(content),
266
- attributes: {
267
- 'base64-content': (0, _utils.base64encode)(content)
268
- }
269
- }
270
- });
271
- } // Uploads resources to the active build concurrently, two at a time.
272
-
273
-
274
- async uploadResources(resources) {
275
- if (!this.build.id) {
276
- throw new Error('This client instance has no active build');
277
- }
278
-
279
- return (0, _utils.pool)(function* () {
280
- for (let resource of resources) {
281
- yield this.uploadResource(resource);
282
- }
283
- }, this, 2);
284
- } // Creates a snapshot for the active build using the provided attributes.
285
-
286
-
287
- async createSnapshot({
288
- name,
289
- widths,
290
- minHeight,
291
- enableJavaScript,
292
- clientInfo,
293
- environmentInfo,
294
- resources = []
295
- } = {}) {
296
- if (!this.build.id) {
297
- throw new Error('This client instance has no active build');
298
- }
299
-
300
- this.addClientInfo(clientInfo);
301
- this.addEnvironmentInfo(environmentInfo);
302
- return this.post(`builds/${this.build.id}/snapshots`, {
303
- data: {
304
- type: 'snapshots',
305
- attributes: {
306
- name: name || null,
307
- widths: widths || null,
308
- 'minimum-height': minHeight || null,
309
- 'enable-javascript': enableJavaScript || null
310
- },
311
- relationships: {
312
- resources: {
313
- data: resources.map(r => ({
314
- type: 'resources',
315
- id: r.sha || (0, _utils.sha256hash)(r.content),
316
- attributes: {
317
- 'resource-url': r.url || null,
318
- 'is-root': r.root || null,
319
- mimetype: r.mimetype || null
320
- }
321
- }))
322
- }
323
- }
324
- }
325
- });
326
- } // Finalizes a snapshot.
327
-
328
-
329
- async finalizeSnapshot(snapshotId) {
330
- return this.post(`snapshots/${snapshotId}/finalize`);
331
- } // Convenience method for creating a snapshot for the active build, uploading
332
- // missing resources for the snapshot, and finalizing the snapshot.
333
-
334
-
335
- async sendSnapshot(options) {
336
- var _data$relationships, _data$relationships$m;
337
-
338
- let {
339
- data
340
- } = await this.createSnapshot(options);
341
- let missing = (_data$relationships = data.relationships) === null || _data$relationships === void 0 ? void 0 : (_data$relationships$m = _data$relationships['missing-resources']) === null || _data$relationships$m === void 0 ? void 0 : _data$relationships$m.data;
342
-
343
- if (missing === null || missing === void 0 ? void 0 : missing.length) {
344
- let resources = options.resources.reduce((acc, r) => Object.assign(acc, {
345
- [r.sha]: r
346
- }), {});
347
- await this.uploadResources(missing.map(({
348
- id
349
- }) => resources[id]));
350
- }
351
-
352
- await this.finalizeSnapshot(data.id);
353
- }
354
-
355
- }
356
-
357
- exports.default = PercyClient;
package/dist/index.js DELETED
@@ -1,15 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- Object.defineProperty(exports, "default", {
7
- enumerable: true,
8
- get: function () {
9
- return _client.default;
10
- }
11
- });
12
-
13
- var _client = _interopRequireDefault(require("./client"));
14
-
15
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
package/dist/utils.js DELETED
@@ -1,176 +0,0 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.sha256hash = sha256hash;
7
- exports.base64encode = base64encode;
8
- exports.pool = pool;
9
- exports.httpAgentFor = httpAgentFor;
10
- exports.request = request;
11
-
12
- var _crypto = _interopRequireDefault(require("crypto"));
13
-
14
- var _url = require("url");
15
-
16
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
-
18
- // Returns a sha256 hash of a string.
19
- function sha256hash(content) {
20
- return _crypto.default.createHash('sha256').update(content, 'utf-8').digest('hex');
21
- } // Returns a base64 encoding of a string or buffer.
22
-
23
-
24
- function base64encode(content) {
25
- return Buffer.from(content).toString('base64');
26
- } // Creates a concurrent pool of promises created by the given generator.
27
- // Resolves when the generator's final promise resolves and rejects when any
28
- // generated promise rejects.
29
-
30
-
31
- function pool(generator, context, concurrency) {
32
- return new Promise((resolve, reject) => {
33
- let iterator = generator.call(context);
34
- let queue = 0;
35
- let ret = [];
36
- let err; // generates concurrent promises
37
-
38
- let proceed = () => {
39
- while (queue < concurrency) {
40
- let {
41
- done,
42
- value: promise
43
- } = iterator.next();
44
-
45
- if (done || err) {
46
- if (!queue && err) reject(err);
47
- if (!queue) resolve(ret);
48
- return;
49
- }
50
-
51
- queue++;
52
- promise.then(value => {
53
- queue--;
54
- ret.push(value);
55
- proceed();
56
- }).catch(error => {
57
- queue--;
58
- err = error;
59
- proceed();
60
- });
61
- }
62
- }; // start generating promises
63
-
64
-
65
- proceed();
66
- });
67
- } // Returns a promise that resolves or rejects when the provided function calls
68
- // `resolve` or `reject` respectively. The third function argument, `retry`,
69
- // will recursively call the function at the specified interval until retries
70
- // are exhausted, at which point the promise will reject with the last error
71
- // passed to `retry`.
72
-
73
-
74
- function retry(fn, {
75
- retries = 5,
76
- interval = 50
77
- } = {}) {
78
- return new Promise((resolve, reject) => {
79
- // run the function, decrement retries
80
- let run = () => {
81
- fn(resolve, reject, retry);
82
- retries--;
83
- }; // wait an interval to try again or reject with the error
84
-
85
-
86
- let retry = err => {
87
- if (retries) {
88
- setTimeout(run, interval);
89
- } else {
90
- reject(err);
91
- }
92
- }; // start trying
93
-
94
-
95
- run();
96
- });
97
- } // Returns the appropriate http or https module for a given URL.
98
-
99
-
100
- function httpModuleFor(url) {
101
- return url.match(/^https:\/\//) ? require('https') : require('http');
102
- } // Returns the appropriate http or https Agent instance for a given URL.
103
-
104
-
105
- function httpAgentFor(url) {
106
- let {
107
- Agent
108
- } = httpModuleFor(url);
109
- return new Agent({
110
- keepAlive: true,
111
- maxSockets: 5
112
- });
113
- } // Returns a promise that resolves when the request is successful and rejects
114
- // when a non-successful response is received. The rejected error contains
115
- // response data and any received error details. Server 500 errors are retried
116
- // up to 5 times at 50ms intervals.
117
-
118
-
119
- function request(url, {
120
- body,
121
- ...options
122
- }) {
123
- let http = httpModuleFor(url);
124
- let {
125
- protocol,
126
- hostname,
127
- port,
128
- pathname,
129
- search
130
- } = new _url.URL(url);
131
- options = { ...options,
132
- protocol,
133
- hostname,
134
- port,
135
- path: pathname + search
136
- };
137
- return retry((resolve, reject, retry) => {
138
- http.request(options).on('response', res => {
139
- let status = res.statusCode;
140
- let raw = '';
141
- res.setEncoding('utf8');
142
- res.on('data', chunk => {
143
- raw += chunk;
144
- });
145
- res.on('end', () => {
146
- let body = raw; // attempt to parse json responses
147
-
148
- try {
149
- body = JSON.parse(raw);
150
- } catch (e) {} // success
151
-
152
-
153
- if (status >= 200 && status < 300) {
154
- resolve(body);
155
- } else {
156
- var _body, _body$errors, _body$errors$;
157
-
158
- // error
159
- let err = Object.assign(new Error(), {
160
- response: {
161
- status,
162
- body
163
- },
164
- message: ((_body = body) === null || _body === void 0 ? void 0 : (_body$errors = _body.errors) === null || _body$errors === void 0 ? void 0 : (_body$errors$ = _body$errors[0]) === null || _body$errors$ === void 0 ? void 0 : _body$errors$.detail) || `${status} ${res.statusMessage || raw}`
165
- }); // retry 500s
166
-
167
- if (status >= 500) {
168
- retry(err);
169
- } else {
170
- reject(err);
171
- }
172
- }
173
- });
174
- }).on('error', reject).end(body);
175
- });
176
- }