@percy/client 1.0.0-beta.76 → 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/package.json +15 -12
- package/dist/client.js +0 -371
- package/dist/index.js +0 -23
- package/dist/request.js +0 -316
- package/dist/utils.js +0 -122
- package/test/helpers.js +0 -81
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/client",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,13 +10,19 @@
|
|
|
10
10
|
"publishConfig": {
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
|
-
"
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=14"
|
|
15
|
+
},
|
|
14
16
|
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"test/helpers.js"
|
|
17
|
+
"./dist",
|
|
18
|
+
"./test/helpers.js"
|
|
17
19
|
],
|
|
18
|
-
"
|
|
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"
|
|
20
26
|
},
|
|
21
27
|
"scripts": {
|
|
22
28
|
"build": "node ../../scripts/build",
|
|
@@ -25,11 +31,8 @@
|
|
|
25
31
|
"test:coverage": "yarn test --coverage"
|
|
26
32
|
},
|
|
27
33
|
"dependencies": {
|
|
28
|
-
"@percy/env": "1.0.0
|
|
29
|
-
"@percy/logger": "1.0.0
|
|
30
|
-
},
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"mock-require": "^3.0.3"
|
|
34
|
+
"@percy/env": "1.0.0",
|
|
35
|
+
"@percy/logger": "1.0.0"
|
|
33
36
|
},
|
|
34
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
|
|
35
38
|
}
|
package/dist/client.js
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = exports.PercyClient = void 0;
|
|
7
|
-
|
|
8
|
-
var _env = _interopRequireDefault(require("@percy/env"));
|
|
9
|
-
|
|
10
|
-
var _utils = require("@percy/env/dist/utils");
|
|
11
|
-
|
|
12
|
-
var _logger = _interopRequireDefault(require("@percy/logger"));
|
|
13
|
-
|
|
14
|
-
var _package = _interopRequireDefault(require("../package.json"));
|
|
15
|
-
|
|
16
|
-
var _utils2 = require("./utils");
|
|
17
|
-
|
|
18
|
-
var _request = _interopRequireDefault(require("./request"));
|
|
19
|
-
|
|
20
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
21
|
-
|
|
22
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
23
|
-
|
|
24
|
-
// Default client API URL can be set with an env var for API development
|
|
25
|
-
const {
|
|
26
|
-
PERCY_CLIENT_API_URL = 'https://percy.io/api/v1'
|
|
27
|
-
} = process.env; // Validate build ID arguments
|
|
28
|
-
|
|
29
|
-
function validateBuildId(id) {
|
|
30
|
-
if (!id) throw new Error('Missing build ID');
|
|
31
|
-
|
|
32
|
-
if (!(typeof id === 'string' || typeof id === 'number')) {
|
|
33
|
-
throw new Error('Invalid build ID');
|
|
34
|
-
}
|
|
35
|
-
} // Validate project path arguments
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
function validateProjectPath(path) {
|
|
39
|
-
if (!path) throw new Error('Missing project path');
|
|
40
|
-
|
|
41
|
-
if (!/^[^/]+?\/.+/.test(path)) {
|
|
42
|
-
throw new Error(`Invalid project path. Expected "org/project" but received "${path}"`);
|
|
43
|
-
}
|
|
44
|
-
} // PercyClient is used to communicate with the Percy API to create and finalize
|
|
45
|
-
// builds and snapshot. Uses @percy/env to collect environment information used
|
|
46
|
-
// during build creation.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class PercyClient {
|
|
50
|
-
constructor({
|
|
51
|
-
// read or write token, defaults to PERCY_TOKEN environment variable
|
|
52
|
-
token,
|
|
53
|
-
// initial user agent info
|
|
54
|
-
clientInfo,
|
|
55
|
-
environmentInfo,
|
|
56
|
-
// versioned api url
|
|
57
|
-
apiUrl = PERCY_CLIENT_API_URL
|
|
58
|
-
} = {}) {
|
|
59
|
-
_defineProperty(this, "log", (0, _logger.default)('client'));
|
|
60
|
-
|
|
61
|
-
_defineProperty(this, "env", new _env.default(process.env));
|
|
62
|
-
|
|
63
|
-
_defineProperty(this, "clientInfo", new Set());
|
|
64
|
-
|
|
65
|
-
_defineProperty(this, "environmentInfo", new Set());
|
|
66
|
-
|
|
67
|
-
Object.assign(this, {
|
|
68
|
-
token,
|
|
69
|
-
apiUrl
|
|
70
|
-
});
|
|
71
|
-
this.addClientInfo(clientInfo);
|
|
72
|
-
this.addEnvironmentInfo(environmentInfo);
|
|
73
|
-
} // Adds additional unique client info.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
addClientInfo(info) {
|
|
77
|
-
for (let i of [].concat(info)) {
|
|
78
|
-
if (i) this.clientInfo.add(i);
|
|
79
|
-
}
|
|
80
|
-
} // Adds additional unique environment info.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
addEnvironmentInfo(info) {
|
|
84
|
-
for (let i of [].concat(info)) {
|
|
85
|
-
if (i) this.environmentInfo.add(i);
|
|
86
|
-
}
|
|
87
|
-
} // Stringifies client and environment info.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
userAgent() {
|
|
91
|
-
let client = new Set([`Percy/${/\w+$/.exec(this.apiUrl)}`].concat(`${_package.default.name}/${_package.default.version}`, ...this.clientInfo).filter(Boolean));
|
|
92
|
-
let environment = new Set([...this.environmentInfo].concat(`node/${process.version}`, this.env.info).filter(Boolean));
|
|
93
|
-
return `${[...client].join(' ')} (${[...environment].join('; ')})`;
|
|
94
|
-
} // Checks for a Percy token and returns it.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
getToken() {
|
|
98
|
-
let token = this.token || this.env.token;
|
|
99
|
-
if (!token) throw new Error('Missing Percy token');
|
|
100
|
-
return token;
|
|
101
|
-
} // Returns common headers used for each request with additional
|
|
102
|
-
// headers. Throws an error when the token is missing, which is a required
|
|
103
|
-
// authorization header.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
headers(headers) {
|
|
107
|
-
return Object.assign({
|
|
108
|
-
Authorization: `Token token=${this.getToken()}`,
|
|
109
|
-
'User-Agent': this.userAgent()
|
|
110
|
-
}, headers);
|
|
111
|
-
} // Performs a GET request for an API endpoint with appropriate headers.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
get(path) {
|
|
115
|
-
return (0, _request.default)(`${this.apiUrl}/${path}`, {
|
|
116
|
-
headers: this.headers(),
|
|
117
|
-
method: 'GET'
|
|
118
|
-
});
|
|
119
|
-
} // Performs a POST request to a JSON API endpoint with appropriate headers.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
post(path, body = {}) {
|
|
123
|
-
return (0, _request.default)(`${this.apiUrl}/${path}`, {
|
|
124
|
-
headers: this.headers({
|
|
125
|
-
'Content-Type': 'application/vnd.api+json'
|
|
126
|
-
}),
|
|
127
|
-
method: 'POST',
|
|
128
|
-
body
|
|
129
|
-
});
|
|
130
|
-
} // Creates a build with optional build resources. Only one build can be
|
|
131
|
-
// created at a time per instance so snapshots and build finalization can be
|
|
132
|
-
// done more seemlessly without manually tracking build ids
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async createBuild({
|
|
136
|
-
resources = []
|
|
137
|
-
} = {}) {
|
|
138
|
-
this.log.debug('Creating a new build...');
|
|
139
|
-
return this.post('builds', {
|
|
140
|
-
data: {
|
|
141
|
-
type: 'builds',
|
|
142
|
-
attributes: {
|
|
143
|
-
branch: this.env.git.branch,
|
|
144
|
-
'target-branch': this.env.target.branch,
|
|
145
|
-
'target-commit-sha': this.env.target.commit,
|
|
146
|
-
'commit-sha': this.env.git.sha,
|
|
147
|
-
'commit-committed-at': this.env.git.committedAt,
|
|
148
|
-
'commit-author-name': this.env.git.authorName,
|
|
149
|
-
'commit-author-email': this.env.git.authorEmail,
|
|
150
|
-
'commit-committer-name': this.env.git.committerName,
|
|
151
|
-
'commit-committer-email': this.env.git.committerEmail,
|
|
152
|
-
'commit-message': this.env.git.message,
|
|
153
|
-
'pull-request-number': this.env.pullRequest,
|
|
154
|
-
'parallel-nonce': this.env.parallel.nonce,
|
|
155
|
-
'parallel-total-shards': this.env.parallel.total,
|
|
156
|
-
partial: this.env.partial
|
|
157
|
-
},
|
|
158
|
-
relationships: {
|
|
159
|
-
resources: {
|
|
160
|
-
data: resources.map(r => ({
|
|
161
|
-
type: 'resources',
|
|
162
|
-
id: r.sha || (0, _utils2.sha256hash)(r.content),
|
|
163
|
-
attributes: {
|
|
164
|
-
'resource-url': r.url,
|
|
165
|
-
'is-root': r.root || null,
|
|
166
|
-
mimetype: r.mimetype || null
|
|
167
|
-
}
|
|
168
|
-
}))
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
} // Finalizes the active build. When `all` is true, `all-shards=true` is
|
|
174
|
-
// added as a query param so the API finalizes all other build shards.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
async finalizeBuild(buildId, {
|
|
178
|
-
all = false
|
|
179
|
-
} = {}) {
|
|
180
|
-
validateBuildId(buildId);
|
|
181
|
-
let qs = all ? 'all-shards=true' : '';
|
|
182
|
-
this.log.debug(`Finalizing build ${buildId}...`);
|
|
183
|
-
return this.post(`builds/${buildId}/finalize?${qs}`);
|
|
184
|
-
} // Retrieves build data by id. Requires a read access token.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
async getBuild(buildId) {
|
|
188
|
-
validateBuildId(buildId);
|
|
189
|
-
this.log.debug(`Get build ${buildId}`);
|
|
190
|
-
return this.get(`builds/${buildId}`);
|
|
191
|
-
} // Retrieves project builds optionally filtered. Requires a read access token.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
async getBuilds(project, filters = {}) {
|
|
195
|
-
validateProjectPath(project);
|
|
196
|
-
let qs = Object.keys(filters).map(k => Array.isArray(filters[k]) ? filters[k].map(v => `filter[${k}][]=${v}`).join('&') : `filter[${k}]=${filters[k]}`).join('&');
|
|
197
|
-
this.log.debug(`Fetching builds for ${project}`);
|
|
198
|
-
return this.get(`projects/${project}/builds?${qs}`);
|
|
199
|
-
} // Resolves when the build has finished and is no longer pending or
|
|
200
|
-
// processing. By default, will time out if no update after 10 minutes.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
waitForBuild({
|
|
204
|
-
build,
|
|
205
|
-
project,
|
|
206
|
-
commit,
|
|
207
|
-
timeout = 10 * 60 * 1000,
|
|
208
|
-
interval = 1000
|
|
209
|
-
}, onProgress) {
|
|
210
|
-
if (commit && !project) {
|
|
211
|
-
throw new Error('Missing project path for commit');
|
|
212
|
-
} else if (!commit && !build) {
|
|
213
|
-
throw new Error('Missing build ID or commit SHA');
|
|
214
|
-
} else if (project) {
|
|
215
|
-
validateProjectPath(project);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
let sha = commit && ((0, _utils.git)(`rev-parse ${commit}`) || commit);
|
|
219
|
-
|
|
220
|
-
let fetchData = async () => build ? (await this.getBuild(build)).data : (await this.getBuilds(project, {
|
|
221
|
-
sha
|
|
222
|
-
})).data[0];
|
|
223
|
-
|
|
224
|
-
this.log.debug(`Waiting for build ${build || `${project} (${commit})`}...`); // recursively poll every second until the build finishes
|
|
225
|
-
|
|
226
|
-
return new Promise((resolve, reject) => async function poll(last, t) {
|
|
227
|
-
try {
|
|
228
|
-
let data = await fetchData();
|
|
229
|
-
let state = data === null || data === void 0 ? void 0 : data.attributes.state;
|
|
230
|
-
let pending = !state || state === 'pending' || state === 'processing';
|
|
231
|
-
let updated = JSON.stringify(data) !== JSON.stringify(last); // new data received
|
|
232
|
-
|
|
233
|
-
if (updated) {
|
|
234
|
-
t = Date.now(); // no new data within the timeout
|
|
235
|
-
} else if (Date.now() - t >= timeout) {
|
|
236
|
-
throw new Error('Timeout exceeded without an update');
|
|
237
|
-
} // call progress every update after the first update
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if ((last || pending) && updated) {
|
|
241
|
-
onProgress === null || onProgress === void 0 ? void 0 : onProgress(data);
|
|
242
|
-
} // not finished, poll again
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (pending) {
|
|
246
|
-
return setTimeout(poll, interval, data, t); // build finished
|
|
247
|
-
} else {
|
|
248
|
-
// ensure progress is called at least once
|
|
249
|
-
if (!last) onProgress === null || onProgress === void 0 ? void 0 : onProgress(data);
|
|
250
|
-
resolve({
|
|
251
|
-
data
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
} catch (err) {
|
|
255
|
-
reject(err);
|
|
256
|
-
}
|
|
257
|
-
}(null, Date.now()));
|
|
258
|
-
} // Uploads a single resource to the active build. If `filepath` is provided,
|
|
259
|
-
// `content` is read from the filesystem. The sha is optional and will be
|
|
260
|
-
// created from `content` if one is not provided.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
async uploadResource(buildId, {
|
|
264
|
-
url,
|
|
265
|
-
sha,
|
|
266
|
-
filepath,
|
|
267
|
-
content
|
|
268
|
-
} = {}) {
|
|
269
|
-
validateBuildId(buildId);
|
|
270
|
-
this.log.debug(`Uploading resource: ${url}...`);
|
|
271
|
-
content = filepath ? require('fs').readFileSync(filepath) : content;
|
|
272
|
-
return this.post(`builds/${buildId}/resources`, {
|
|
273
|
-
data: {
|
|
274
|
-
type: 'resources',
|
|
275
|
-
id: sha || (0, _utils2.sha256hash)(content),
|
|
276
|
-
attributes: {
|
|
277
|
-
'base64-content': (0, _utils2.base64encode)(content)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
} // Uploads resources to the active build concurrently, two at a time.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
async uploadResources(buildId, resources) {
|
|
285
|
-
validateBuildId(buildId);
|
|
286
|
-
this.log.debug(`Uploading resources for ${buildId}...`);
|
|
287
|
-
return (0, _utils2.pool)(function* () {
|
|
288
|
-
for (let resource of resources) {
|
|
289
|
-
yield this.uploadResource(buildId, resource);
|
|
290
|
-
}
|
|
291
|
-
}, this, 2);
|
|
292
|
-
} // Creates a snapshot for the active build using the provided attributes.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
async createSnapshot(buildId, {
|
|
296
|
-
name,
|
|
297
|
-
widths,
|
|
298
|
-
minHeight,
|
|
299
|
-
enableJavaScript,
|
|
300
|
-
clientInfo,
|
|
301
|
-
environmentInfo,
|
|
302
|
-
resources = []
|
|
303
|
-
} = {}) {
|
|
304
|
-
validateBuildId(buildId);
|
|
305
|
-
this.addClientInfo(clientInfo);
|
|
306
|
-
this.addEnvironmentInfo(environmentInfo);
|
|
307
|
-
|
|
308
|
-
if (!this.clientInfo.size || !this.environmentInfo.size) {
|
|
309
|
-
this.log.warn('Warning: Missing `clientInfo` and/or `environmentInfo` properties');
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
this.log.debug(`Creating snapshot: ${name}...`);
|
|
313
|
-
return this.post(`builds/${buildId}/snapshots`, {
|
|
314
|
-
data: {
|
|
315
|
-
type: 'snapshots',
|
|
316
|
-
attributes: {
|
|
317
|
-
name: name || null,
|
|
318
|
-
widths: widths || null,
|
|
319
|
-
'minimum-height': minHeight || null,
|
|
320
|
-
'enable-javascript': enableJavaScript || null
|
|
321
|
-
},
|
|
322
|
-
relationships: {
|
|
323
|
-
resources: {
|
|
324
|
-
data: resources.map(r => ({
|
|
325
|
-
type: 'resources',
|
|
326
|
-
id: r.sha || (0, _utils2.sha256hash)(r.content),
|
|
327
|
-
attributes: {
|
|
328
|
-
'resource-url': r.url || null,
|
|
329
|
-
'is-root': r.root || null,
|
|
330
|
-
mimetype: r.mimetype || null
|
|
331
|
-
}
|
|
332
|
-
}))
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
});
|
|
337
|
-
} // Finalizes a snapshot.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
async finalizeSnapshot(snapshotId) {
|
|
341
|
-
if (!snapshotId) throw new Error('Missing snapshot ID');
|
|
342
|
-
this.log.debug(`Finalizing snapshot ${snapshotId}...`);
|
|
343
|
-
return this.post(`snapshots/${snapshotId}/finalize`);
|
|
344
|
-
} // Convenience method for creating a snapshot for the active build, uploading
|
|
345
|
-
// missing resources for the snapshot, and finalizing the snapshot.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
async sendSnapshot(buildId, options) {
|
|
349
|
-
var _snapshot$data$relati, _snapshot$data$relati2;
|
|
350
|
-
|
|
351
|
-
let snapshot = await this.createSnapshot(buildId, options);
|
|
352
|
-
let missing = (_snapshot$data$relati = snapshot.data.relationships) === null || _snapshot$data$relati === void 0 ? void 0 : (_snapshot$data$relati2 = _snapshot$data$relati['missing-resources']) === null || _snapshot$data$relati2 === void 0 ? void 0 : _snapshot$data$relati2.data;
|
|
353
|
-
|
|
354
|
-
if (missing !== null && missing !== void 0 && missing.length) {
|
|
355
|
-
let resources = options.resources.reduce((acc, r) => Object.assign(acc, {
|
|
356
|
-
[r.sha]: r
|
|
357
|
-
}), {});
|
|
358
|
-
await this.uploadResources(buildId, missing.map(({
|
|
359
|
-
id
|
|
360
|
-
}) => resources[id]));
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
await this.finalizeSnapshot(snapshot.data.id);
|
|
364
|
-
return snapshot;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
exports.PercyClient = PercyClient;
|
|
370
|
-
var _default = PercyClient;
|
|
371
|
-
exports.default = _default;
|
package/dist/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "PercyClient", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _client.PercyClient;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "default", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _client.default;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
var _client = _interopRequireWildcard(require("./client"));
|
|
20
|
-
|
|
21
|
-
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
22
|
-
|
|
23
|
-
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
package/dist/request.js
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = exports.ProxyHttpsAgent = exports.ProxyHttpAgent = void 0;
|
|
7
|
-
exports.getProxy = getProxy;
|
|
8
|
-
exports.href = href;
|
|
9
|
-
exports.port = port;
|
|
10
|
-
exports.proxyAgentFor = proxyAgentFor;
|
|
11
|
-
exports.request = request;
|
|
12
|
-
|
|
13
|
-
var _net = _interopRequireDefault(require("net"));
|
|
14
|
-
|
|
15
|
-
var _tls = _interopRequireDefault(require("tls"));
|
|
16
|
-
|
|
17
|
-
var _http = _interopRequireDefault(require("http"));
|
|
18
|
-
|
|
19
|
-
var _https = _interopRequireDefault(require("https"));
|
|
20
|
-
|
|
21
|
-
var _logger = _interopRequireDefault(require("@percy/logger"));
|
|
22
|
-
|
|
23
|
-
var _utils = require("./utils");
|
|
24
|
-
|
|
25
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
26
|
-
|
|
27
|
-
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
28
|
-
|
|
29
|
-
const CRLF = '\r\n';
|
|
30
|
-
const STATUS_REG = /^HTTP\/1.[01] (\d*)/;
|
|
31
|
-
const RETRY_ERROR_CODES = ['ECONNREFUSED', 'ECONNRESET', 'EPIPE', 'EHOSTUNREACH', 'EAI_AGAIN']; // Returns the port number of a URL object. Defaults to port 443 for https
|
|
32
|
-
// protocols or port 80 otherwise.
|
|
33
|
-
|
|
34
|
-
function port(options) {
|
|
35
|
-
if (options.port) return options.port;
|
|
36
|
-
return options.protocol === 'https:' ? 443 : 80;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function href(options) {
|
|
40
|
-
let {
|
|
41
|
-
protocol,
|
|
42
|
-
hostname,
|
|
43
|
-
path,
|
|
44
|
-
pathname,
|
|
45
|
-
search,
|
|
46
|
-
hash
|
|
47
|
-
} = options;
|
|
48
|
-
return `${protocol}//${hostname}:${port(options)}` + (path || `${pathname || ''}${search || ''}${hash || ''}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
;
|
|
52
|
-
|
|
53
|
-
function getProxy(options) {
|
|
54
|
-
let proxyUrl = options.protocol === 'https:' && (process.env.https_proxy || process.env.HTTPS_PROXY) || process.env.http_proxy || process.env.HTTP_PROXY;
|
|
55
|
-
let shouldProxy = !!proxyUrl && !(0, _utils.hostnameMatches)(process.env.no_proxy || process.env.NO_PROXY, href(options));
|
|
56
|
-
|
|
57
|
-
if (shouldProxy) {
|
|
58
|
-
proxyUrl = new URL(proxyUrl);
|
|
59
|
-
let isHttps = proxyUrl.protocol === 'https:';
|
|
60
|
-
|
|
61
|
-
if (!isHttps && proxyUrl.protocol !== 'http:') {
|
|
62
|
-
throw new Error(`Unsupported proxy protocol: ${proxyUrl.protocol}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
let proxy = {
|
|
66
|
-
isHttps
|
|
67
|
-
};
|
|
68
|
-
proxy.auth = !!proxyUrl.username && 'Basic ' + (proxyUrl.password ? Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`) : Buffer.from(proxyUrl.username)).toString('base64');
|
|
69
|
-
proxy.host = proxyUrl.hostname;
|
|
70
|
-
proxy.port = port(proxyUrl);
|
|
71
|
-
|
|
72
|
-
proxy.connect = () => (isHttps ? _tls.default : _net.default).connect({
|
|
73
|
-
rejectUnauthorized: options.rejectUnauthorized,
|
|
74
|
-
host: proxy.host,
|
|
75
|
-
port: proxy.port
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return proxy;
|
|
79
|
-
}
|
|
80
|
-
} // Proxified http agent
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class ProxyHttpAgent extends _http.default.Agent {
|
|
84
|
-
constructor(...args) {
|
|
85
|
-
super(...args);
|
|
86
|
-
|
|
87
|
-
_defineProperty(this, "httpsAgent", new _https.default.Agent({
|
|
88
|
-
keepAlive: true
|
|
89
|
-
}));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
addRequest(request, options) {
|
|
93
|
-
var _request$outputData;
|
|
94
|
-
|
|
95
|
-
let proxy = getProxy(options);
|
|
96
|
-
if (!proxy) return super.addRequest(request, options);
|
|
97
|
-
(0, _logger.default)('client:proxy').debug(`Proxying request: ${options.href}`); // modify the request for proxying
|
|
98
|
-
|
|
99
|
-
request.path = href(options);
|
|
100
|
-
|
|
101
|
-
if (proxy.auth) {
|
|
102
|
-
request.setHeader('Proxy-Authorization', proxy.auth);
|
|
103
|
-
} // regenerate headers since we just changed things
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
delete request._header;
|
|
107
|
-
|
|
108
|
-
request._implicitHeader();
|
|
109
|
-
|
|
110
|
-
if (((_request$outputData = request.outputData) === null || _request$outputData === void 0 ? void 0 : _request$outputData.length) > 0) {
|
|
111
|
-
let first = request.outputData[0].data;
|
|
112
|
-
let endOfHeaders = first.indexOf(CRLF.repeat(2)) + 4;
|
|
113
|
-
request.outputData[0].data = request._header + first.substring(endOfHeaders);
|
|
114
|
-
} // coerce the connection to the proxy
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
options.port = proxy.port;
|
|
118
|
-
options.host = proxy.host;
|
|
119
|
-
delete options.path;
|
|
120
|
-
|
|
121
|
-
if (proxy.isHttps) {
|
|
122
|
-
// use the underlying https agent to complete the connection
|
|
123
|
-
request.agent = this.httpsAgent;
|
|
124
|
-
return this.httpsAgent.addRequest(request, options);
|
|
125
|
-
} else {
|
|
126
|
-
return super.addRequest(request, options);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
} // Proxified https agent
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
exports.ProxyHttpAgent = ProxyHttpAgent;
|
|
134
|
-
|
|
135
|
-
class ProxyHttpsAgent extends _https.default.Agent {
|
|
136
|
-
constructor(options) {
|
|
137
|
-
// default keep-alive
|
|
138
|
-
super({
|
|
139
|
-
keepAlive: true,
|
|
140
|
-
...options
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
createConnection(options, callback) {
|
|
145
|
-
let proxy = getProxy(options);
|
|
146
|
-
if (!proxy) return super.createConnection(options, callback);
|
|
147
|
-
(0, _logger.default)('client:proxy').debug(`Proxying request: ${href(options)}`); // generate proxy connect message
|
|
148
|
-
|
|
149
|
-
let host = `${options.hostname}:${port(options)}`;
|
|
150
|
-
let connectMessage = [`CONNECT ${host} HTTP/1.1`, `Host: ${host}`];
|
|
151
|
-
|
|
152
|
-
if (proxy.auth) {
|
|
153
|
-
connectMessage.push(`Proxy-Authorization: ${proxy.auth}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
connectMessage = connectMessage.join(CRLF);
|
|
157
|
-
connectMessage += CRLF.repeat(2); // start the proxy connection and setup listeners
|
|
158
|
-
|
|
159
|
-
let socket = proxy.connect();
|
|
160
|
-
|
|
161
|
-
let handleError = err => {
|
|
162
|
-
socket.destroy(err);
|
|
163
|
-
callback(err);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
let handleClose = () => handleError(new Error('Connection closed while sending request to upstream proxy'));
|
|
167
|
-
|
|
168
|
-
let buffer = '';
|
|
169
|
-
|
|
170
|
-
let handleData = data => {
|
|
171
|
-
var _buffer$match;
|
|
172
|
-
|
|
173
|
-
buffer += data.toString(); // haven't received end of headers yet, keep buffering
|
|
174
|
-
|
|
175
|
-
if (!buffer.includes(CRLF.repeat(2))) return; // stop listening after end of headers
|
|
176
|
-
|
|
177
|
-
socket.off('data', handleData);
|
|
178
|
-
|
|
179
|
-
if (((_buffer$match = buffer.match(STATUS_REG)) === null || _buffer$match === void 0 ? void 0 : _buffer$match[1]) !== '200') {
|
|
180
|
-
return handleError(new Error('Error establishing proxy connection. ' + `Response from server was: ${buffer}`));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
options.socket = socket;
|
|
184
|
-
options.servername = options.hostname; // callback not passed in so not to be added as a listener
|
|
185
|
-
|
|
186
|
-
callback(null, super.createConnection(options));
|
|
187
|
-
}; // send and handle the connect message
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
socket.on('error', handleError).on('close', handleClose).on('data', handleData).write(connectMessage);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
exports.ProxyHttpsAgent = ProxyHttpsAgent;
|
|
196
|
-
|
|
197
|
-
function proxyAgentFor(url, options) {
|
|
198
|
-
let cache = proxyAgentFor.cache || (proxyAgentFor.cache = new Map());
|
|
199
|
-
let {
|
|
200
|
-
protocol,
|
|
201
|
-
hostname
|
|
202
|
-
} = new URL(url);
|
|
203
|
-
let cachekey = `${protocol}//${hostname}`;
|
|
204
|
-
|
|
205
|
-
if (!cache.has(cachekey)) {
|
|
206
|
-
cache.set(cachekey, protocol === 'https:' ? new ProxyHttpsAgent(options) : new ProxyHttpAgent(options));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return cache.get(cachekey);
|
|
210
|
-
} // Proxified request function that resolves with the response body when the request is successful
|
|
211
|
-
// and rejects when a non-successful response is received. The rejected error contains response data
|
|
212
|
-
// and any received error details. Server 500 errors are retried up to 5 times at 50ms intervals by
|
|
213
|
-
// default, and 404 errors may also be optionally retried. If a callback is provided, it is called
|
|
214
|
-
// with the parsed response body and response details. If the callback returns a value, that value
|
|
215
|
-
// will be returned in the final resolved promise instead of the response body.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
function request(url, options = {}, callback) {
|
|
219
|
-
// accept `request(url, callback)`
|
|
220
|
-
if (typeof options === 'function') [options, callback] = [{}, options]; // gather request options
|
|
221
|
-
|
|
222
|
-
let {
|
|
223
|
-
body,
|
|
224
|
-
headers,
|
|
225
|
-
retries,
|
|
226
|
-
retryNotFound,
|
|
227
|
-
interval,
|
|
228
|
-
noProxy,
|
|
229
|
-
...requestOptions
|
|
230
|
-
} = options;
|
|
231
|
-
let {
|
|
232
|
-
protocol,
|
|
233
|
-
hostname,
|
|
234
|
-
port,
|
|
235
|
-
pathname,
|
|
236
|
-
search,
|
|
237
|
-
hash
|
|
238
|
-
} = new URL(url); // automatically stringify body content
|
|
239
|
-
|
|
240
|
-
if (body && typeof body !== 'string') {
|
|
241
|
-
headers = {
|
|
242
|
-
'Content-Type': 'application/json',
|
|
243
|
-
...headers
|
|
244
|
-
};
|
|
245
|
-
body = JSON.stringify(body);
|
|
246
|
-
} // combine request options
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
Object.assign(requestOptions, {
|
|
250
|
-
agent: requestOptions.agent || !noProxy && proxyAgentFor(url) || null,
|
|
251
|
-
path: pathname + search + hash,
|
|
252
|
-
protocol,
|
|
253
|
-
hostname,
|
|
254
|
-
headers,
|
|
255
|
-
port
|
|
256
|
-
});
|
|
257
|
-
return (0, _utils.retry)((resolve, reject, retry) => {
|
|
258
|
-
let handleError = error => {
|
|
259
|
-
if (handleError.handled) return;
|
|
260
|
-
handleError.handled = true;
|
|
261
|
-
let shouldRetry = error.response // maybe retry 404s and always retry 500s
|
|
262
|
-
? retryNotFound && error.response.statusCode === 404 || error.response.statusCode >= 500 && error.response.statusCode < 600 // retry specific error codes
|
|
263
|
-
: !!error.code && RETRY_ERROR_CODES.includes(error.code);
|
|
264
|
-
return shouldRetry ? retry(error) : reject(error);
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
let handleFinished = async (body, res) => {
|
|
268
|
-
let raw = body; // attempt to parse the body as json
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
body = JSON.parse(body);
|
|
272
|
-
} catch (e) {}
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
276
|
-
var _await$callback, _callback;
|
|
277
|
-
|
|
278
|
-
// resolve successful statuses after the callback
|
|
279
|
-
resolve((_await$callback = await ((_callback = callback) === null || _callback === void 0 ? void 0 : _callback(body, res))) !== null && _await$callback !== void 0 ? _await$callback : body);
|
|
280
|
-
} else {
|
|
281
|
-
var _body, _body$errors, _body$errors$find;
|
|
282
|
-
|
|
283
|
-
// use the first error detail or the status message
|
|
284
|
-
throw new Error(((_body = body) === null || _body === void 0 ? void 0 : (_body$errors = _body.errors) === null || _body$errors === void 0 ? void 0 : (_body$errors$find = _body$errors.find(e => e.detail)) === null || _body$errors$find === void 0 ? void 0 : _body$errors$find.detail) || `${res.statusCode} ${res.statusMessage || raw}`);
|
|
285
|
-
}
|
|
286
|
-
} catch (error) {
|
|
287
|
-
handleError(Object.assign(error, {
|
|
288
|
-
response: {
|
|
289
|
-
statusCode: res.statusCode,
|
|
290
|
-
headers: res.headers,
|
|
291
|
-
body
|
|
292
|
-
}
|
|
293
|
-
}));
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
let handleResponse = res => {
|
|
298
|
-
let body = '';
|
|
299
|
-
res.setEncoding('utf8');
|
|
300
|
-
res.on('data', chunk => body += chunk);
|
|
301
|
-
res.on('end', () => handleFinished(body, res));
|
|
302
|
-
res.on('error', handleError);
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
let req = (protocol === 'https:' ? _https.default : _http.default).request(requestOptions);
|
|
306
|
-
req.on('response', handleResponse);
|
|
307
|
-
req.on('error', handleError);
|
|
308
|
-
req.end(body);
|
|
309
|
-
}, {
|
|
310
|
-
retries,
|
|
311
|
-
interval
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
var _default = request;
|
|
316
|
-
exports.default = _default;
|
package/dist/utils.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.base64encode = base64encode;
|
|
7
|
-
exports.hostnameMatches = hostnameMatches;
|
|
8
|
-
exports.pool = pool;
|
|
9
|
-
exports.retry = retry;
|
|
10
|
-
exports.sha256hash = sha256hash;
|
|
11
|
-
|
|
12
|
-
var _crypto = _interopRequireDefault(require("crypto"));
|
|
13
|
-
|
|
14
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
|
-
|
|
16
|
-
// Returns a sha256 hash of a string.
|
|
17
|
-
function sha256hash(content) {
|
|
18
|
-
return _crypto.default.createHash('sha256').update(content, 'utf-8').digest('hex');
|
|
19
|
-
} // Returns a base64 encoding of a string or buffer.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function base64encode(content) {
|
|
23
|
-
return Buffer.from(content).toString('base64');
|
|
24
|
-
} // Creates a concurrent pool of promises created by the given generator.
|
|
25
|
-
// Resolves when the generator's final promise resolves and rejects when any
|
|
26
|
-
// generated promise rejects.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
function pool(generator, context, concurrency) {
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
let iterator = generator.call(context);
|
|
32
|
-
let queue = 0;
|
|
33
|
-
let ret = [];
|
|
34
|
-
let err; // generates concurrent promises
|
|
35
|
-
|
|
36
|
-
let proceed = () => {
|
|
37
|
-
while (queue < concurrency) {
|
|
38
|
-
let {
|
|
39
|
-
done,
|
|
40
|
-
value: promise
|
|
41
|
-
} = iterator.next();
|
|
42
|
-
|
|
43
|
-
if (done || err) {
|
|
44
|
-
if (!queue && err) reject(err);
|
|
45
|
-
if (!queue) resolve(ret);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
queue++;
|
|
50
|
-
promise.then(value => {
|
|
51
|
-
queue--;
|
|
52
|
-
ret.push(value);
|
|
53
|
-
proceed();
|
|
54
|
-
}).catch(error => {
|
|
55
|
-
queue--;
|
|
56
|
-
err = error;
|
|
57
|
-
proceed();
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}; // start generating promises
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
proceed();
|
|
64
|
-
});
|
|
65
|
-
} // Returns a promise that resolves or rejects when the provided function calls
|
|
66
|
-
// `resolve` or `reject` respectively. The third function argument, `retry`,
|
|
67
|
-
// will recursively call the function at the specified interval until retries
|
|
68
|
-
// are exhausted, at which point the promise will reject with the last error
|
|
69
|
-
// passed to `retry`.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
function retry(fn, {
|
|
73
|
-
retries = 5,
|
|
74
|
-
interval = 50
|
|
75
|
-
}) {
|
|
76
|
-
return new Promise((resolve, reject) => {
|
|
77
|
-
let run = () => fn(resolve, reject, retry); // wait an interval to try again or reject with the error
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let retry = err => {
|
|
81
|
-
if (retries-- > 0) {
|
|
82
|
-
setTimeout(run, interval);
|
|
83
|
-
} else {
|
|
84
|
-
reject(err);
|
|
85
|
-
}
|
|
86
|
-
}; // start trying
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
run();
|
|
90
|
-
});
|
|
91
|
-
} // Returns true if the URL hostname matches any patterns
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
function hostnameMatches(patterns, url) {
|
|
95
|
-
let subject = new URL(url);
|
|
96
|
-
/* istanbul ignore next: only strings are provided internally by the client proxy; core (which
|
|
97
|
-
* borrows this util) sometimes provides an array of patterns or undefined */
|
|
98
|
-
|
|
99
|
-
patterns = typeof patterns === 'string' ? patterns.split(/[\s,]+/) : [].concat(patterns);
|
|
100
|
-
|
|
101
|
-
for (let pattern of patterns) {
|
|
102
|
-
if (pattern === '*') return true;
|
|
103
|
-
if (!pattern) continue; // parse pattern
|
|
104
|
-
|
|
105
|
-
let {
|
|
106
|
-
groups: rule
|
|
107
|
-
} = pattern.match(/^(?<hostname>.+?)(?::(?<port>\d+))?$/); // missing a hostname or ports do not match
|
|
108
|
-
|
|
109
|
-
if (!rule.hostname || rule.port && rule.port !== subject.port) {
|
|
110
|
-
continue;
|
|
111
|
-
} // wildcards are treated the same as leading dots
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
rule.hostname = rule.hostname.replace(/^\*/, ''); // hostnames are equal or end with a wildcard rule
|
|
115
|
-
|
|
116
|
-
if (rule.hostname === subject.hostname || rule.hostname.startsWith('.') && subject.hostname.endsWith(rule.hostname)) {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return false;
|
|
122
|
-
}
|
package/test/helpers.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const nock = require('nock');
|
|
2
|
-
|
|
3
|
-
const DEFAULT_REPLIES = {
|
|
4
|
-
'/builds': () => [201, {
|
|
5
|
-
data: {
|
|
6
|
-
id: '123',
|
|
7
|
-
attributes: {
|
|
8
|
-
'build-number': 1,
|
|
9
|
-
'web-url': 'https://percy.io/test/test/123'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}],
|
|
13
|
-
|
|
14
|
-
'/builds/123/snapshots': ({ body }) => [201, {
|
|
15
|
-
data: {
|
|
16
|
-
id: '4567',
|
|
17
|
-
attributes: body.attributes,
|
|
18
|
-
relationships: {
|
|
19
|
-
'missing-resources': {
|
|
20
|
-
data: body.data.relationships.resources
|
|
21
|
-
.data.map(({ id }) => ({ id }))
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}]
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const mockAPI = {
|
|
29
|
-
nock: null,
|
|
30
|
-
requests: null,
|
|
31
|
-
replies: null,
|
|
32
|
-
|
|
33
|
-
start(delay = 0) {
|
|
34
|
-
nock.cleanAll();
|
|
35
|
-
nock.disableNetConnect();
|
|
36
|
-
nock.enableNetConnect('storage.googleapis.com|localhost|127.0.0.1');
|
|
37
|
-
|
|
38
|
-
let n = this.nock = nock('https://percy.io/api/v1').persist();
|
|
39
|
-
let requests = this.requests = {};
|
|
40
|
-
let replies = this.replies = {};
|
|
41
|
-
|
|
42
|
-
function intercept(_, body) {
|
|
43
|
-
let { path, headers, method } = this.req;
|
|
44
|
-
|
|
45
|
-
try { body = JSON.parse(body); } catch {}
|
|
46
|
-
path = path.replace('/api/v1', '');
|
|
47
|
-
|
|
48
|
-
let req = { body, headers, method };
|
|
49
|
-
let reply = replies[path] && (
|
|
50
|
-
replies[path].length > 1
|
|
51
|
-
? replies[path].shift()
|
|
52
|
-
: replies[path][0]
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
requests[path] = requests[path] || [];
|
|
56
|
-
requests[path].push(req);
|
|
57
|
-
|
|
58
|
-
return reply ? reply(req) : (
|
|
59
|
-
DEFAULT_REPLIES[path]
|
|
60
|
-
? DEFAULT_REPLIES[path](req)
|
|
61
|
-
: [200]
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
n.get(/.*/).delay(delay).reply(intercept);
|
|
66
|
-
n.post(/.*/).delay(delay).reply(intercept);
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
reply(path, handler) {
|
|
70
|
-
this.replies[path] = this.replies[path] || [];
|
|
71
|
-
this.replies[path].push(handler);
|
|
72
|
-
return this;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
cleanAll() {
|
|
76
|
-
nock.cleanAll();
|
|
77
|
-
return this;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
module.exports = mockAPI;
|