fetch-har 8.0.3 → 8.1.2
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/CHANGELOG.md +35 -5
- package/dist/index.js +84 -63
- package/package.json +13 -7
- package/src/index.ts +84 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
|
-
## <small>8.
|
|
1
|
+
## <small>8.1.2 (2022-07-27)</small>
|
|
2
2
|
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
* fix: updating the data-urls package to fix a problem with file case sensititivity ([42cadff](https://github.com/readmeio/fetch-har/commit/42cadff))
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## <small>8.1.1 (2022-07-27)</small>
|
|
8
|
+
|
|
9
|
+
* fix: swapping parse-data-url for @readme/data-urls (#307) ([a2306bd](https://github.com/readmeio/fetch-har/commit/a2306bd)), closes [#307](https://github.com/readmeio/fetch-har/issues/307)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## 8.1.0 (2022-07-26)
|
|
14
|
+
|
|
15
|
+
* chore: code comment cleanup ([bd344f1](https://github.com/readmeio/fetch-har/commit/bd344f1))
|
|
7
16
|
* chore(deps-dev): bump @jsdevtools/karma-config from 3.1.7 to 3.2.0 (#281) ([291f16d](https://github.com/readmeio/fetch-har/commit/291f16d)), closes [#281](https://github.com/readmeio/fetch-har/issues/281)
|
|
17
|
+
* chore(deps-dev): bump @jsdevtools/karma-config from 3.1.7 to 3.2.0 (#302) ([903f62f](https://github.com/readmeio/fetch-har/commit/903f62f)), closes [#302](https://github.com/readmeio/fetch-har/issues/302)
|
|
8
18
|
* chore(deps-dev): bump @readme/eslint-config from 8.7.3 to 8.8.0 (#285) ([47a83be](https://github.com/readmeio/fetch-har/commit/47a83be)), closes [#285](https://github.com/readmeio/fetch-har/issues/285)
|
|
19
|
+
* chore(deps-dev): bump @readme/eslint-config from 8.8.0 to 9.0.0 (#306) ([5511118](https://github.com/readmeio/fetch-har/commit/5511118)), closes [#306](https://github.com/readmeio/fetch-har/issues/306)
|
|
9
20
|
* chore(deps-dev): bump @types/node from 17.0.30 to 17.0.38 (#275) ([9ad835a](https://github.com/readmeio/fetch-har/commit/9ad835a)), closes [#275](https://github.com/readmeio/fetch-har/issues/275)
|
|
21
|
+
* chore(deps-dev): bump @types/node from 17.0.38 to 18.0.0 (#290) ([7c9c2df](https://github.com/readmeio/fetch-har/commit/7c9c2df)), closes [#290](https://github.com/readmeio/fetch-har/issues/290)
|
|
22
|
+
* chore(deps-dev): bump @types/node from 18.0.0 to 18.6.1 (#304) ([bcec171](https://github.com/readmeio/fetch-har/commit/bcec171)), closes [#304](https://github.com/readmeio/fetch-har/issues/304)
|
|
10
23
|
* chore(deps-dev): bump eslint from 8.14.0 to 8.16.0 (#279) ([e78536f](https://github.com/readmeio/fetch-har/commit/e78536f)), closes [#279](https://github.com/readmeio/fetch-har/issues/279)
|
|
24
|
+
* chore(deps-dev): bump eslint from 8.16.0 to 8.18.0 (#293) ([2c22a43](https://github.com/readmeio/fetch-har/commit/2c22a43)), closes [#293](https://github.com/readmeio/fetch-har/issues/293)
|
|
25
|
+
* chore(deps-dev): bump eslint from 8.18.0 to 8.20.0 (#300) ([5eb8e49](https://github.com/readmeio/fetch-har/commit/5eb8e49)), closes [#300](https://github.com/readmeio/fetch-har/issues/300)
|
|
11
26
|
* chore(deps-dev): bump mocha from 9.2.2 to 10.0.0 (#277) ([62cc6b6](https://github.com/readmeio/fetch-har/commit/62cc6b6)), closes [#277](https://github.com/readmeio/fetch-har/issues/277)
|
|
27
|
+
* chore(deps-dev): bump nock from 13.2.4 to 13.2.8 (#294) ([21e976f](https://github.com/readmeio/fetch-har/commit/21e976f)), closes [#294](https://github.com/readmeio/fetch-har/issues/294)
|
|
28
|
+
* chore(deps-dev): bump nock from 13.2.8 to 13.2.9 (#305) ([8e7c741](https://github.com/readmeio/fetch-har/commit/8e7c741)), closes [#305](https://github.com/readmeio/fetch-har/issues/305)
|
|
29
|
+
* chore(deps-dev): bump prettier from 2.6.2 to 2.7.1 (#295) ([589c94e](https://github.com/readmeio/fetch-har/commit/589c94e)), closes [#295](https://github.com/readmeio/fetch-har/issues/295)
|
|
30
|
+
* chore(deps-dev): bump ts-loader from 9.3.0 to 9.3.1 (#292) ([0716171](https://github.com/readmeio/fetch-har/commit/0716171)), closes [#292](https://github.com/readmeio/fetch-har/issues/292)
|
|
12
31
|
* chore(deps-dev): bump ts-node from 10.7.0 to 10.8.1 (#286) ([fe87394](https://github.com/readmeio/fetch-har/commit/fe87394)), closes [#286](https://github.com/readmeio/fetch-har/issues/286)
|
|
32
|
+
* chore(deps-dev): bump ts-node from 10.8.1 to 10.9.1 (#303) ([9188de6](https://github.com/readmeio/fetch-har/commit/9188de6)), closes [#303](https://github.com/readmeio/fetch-har/issues/303)
|
|
13
33
|
* chore(deps-dev): bump typescript from 4.6.4 to 4.7.4 (#287) ([a9e5b29](https://github.com/readmeio/fetch-har/commit/a9e5b29)), closes [#287](https://github.com/readmeio/fetch-har/issues/287)
|
|
14
34
|
* chore(deps-dev): bump undici from 5.0.0 to 5.5.1 (#284) ([6e013e2](https://github.com/readmeio/fetch-har/commit/6e013e2)), closes [#284](https://github.com/readmeio/fetch-har/issues/284)
|
|
35
|
+
* chore(deps-dev): bump undici from 5.5.1 to 5.8.0 (#297) ([4f220aa](https://github.com/readmeio/fetch-har/commit/4f220aa)), closes [#297](https://github.com/readmeio/fetch-har/issues/297)
|
|
15
36
|
* chore(deps): bump actions/cache from 3.0.2 to 3.0.3 (#274) ([17071a2](https://github.com/readmeio/fetch-har/commit/17071a2)), closes [#274](https://github.com/readmeio/fetch-har/issues/274)
|
|
37
|
+
* chore(deps): bump actions/cache from 3.0.3 to 3.0.4 (#289) ([a2c9255](https://github.com/readmeio/fetch-har/commit/a2c9255)), closes [#289](https://github.com/readmeio/fetch-har/issues/289)
|
|
38
|
+
* chore(deps): bump actions/cache from 3.0.4 to 3.0.5 (#299) ([b4a8229](https://github.com/readmeio/fetch-har/commit/b4a8229)), closes [#299](https://github.com/readmeio/fetch-har/issues/299)
|
|
39
|
+
* chore(deps): bump formdata-node from 4.3.2 to 4.3.3 (#288) ([2226cd7](https://github.com/readmeio/fetch-har/commit/2226cd7)), closes [#288](https://github.com/readmeio/fetch-har/issues/288)
|
|
40
|
+
* chore(deps): bump terser from 4.8.0 to 4.8.1 (#296) ([9fb708f](https://github.com/readmeio/fetch-har/commit/9fb708f)), closes [#296](https://github.com/readmeio/fetch-har/issues/296)
|
|
16
41
|
* fix: don't look for a length on `postData.text` if it's undefined (#272) ([cd70c2f](https://github.com/readmeio/fetch-har/commit/cd70c2f)), closes [#272](https://github.com/readmeio/fetch-har/issues/272)
|
|
17
42
|
* fix: pin to node 18.0 (#273) ([9ab5991](https://github.com/readmeio/fetch-har/commit/9ab5991)), closes [#273](https://github.com/readmeio/fetch-har/issues/273)
|
|
43
|
+
* fix: support for uploading multiple files through the same parameter (#298) ([b3f4877](https://github.com/readmeio/fetch-har/commit/b3f4877)), closes [#298](https://github.com/readmeio/fetch-har/issues/298)
|
|
44
|
+
* ci: pin ci to node 18.0 ([6ce62ac](https://github.com/readmeio/fetch-har/commit/6ce62ac))
|
|
45
|
+
* ci: pin to node 18.1 because 18.2 has broken cookie header support ([05748b4](https://github.com/readmeio/fetch-har/commit/05748b4))
|
|
46
|
+
* ci: running ci tests on the latest node 18 again ([0465075](https://github.com/readmeio/fetch-har/commit/0465075))
|
|
47
|
+
* build: 8.0.2 release ([491eedf](https://github.com/readmeio/fetch-har/commit/491eedf))
|
|
18
48
|
|
|
19
49
|
|
|
20
50
|
|
package/dist/index.js
CHANGED
|
@@ -10,12 +10,9 @@ var __assign = (this && this.__assign) || function () {
|
|
|
10
10
|
};
|
|
11
11
|
return __assign.apply(this, arguments);
|
|
12
12
|
};
|
|
13
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
14
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
|
-
};
|
|
16
13
|
exports.__esModule = true;
|
|
17
14
|
var readable_stream_1 = require("readable-stream");
|
|
18
|
-
var
|
|
15
|
+
var data_urls_1 = require("@readme/data-urls");
|
|
19
16
|
if (!globalThis.Blob) {
|
|
20
17
|
try {
|
|
21
18
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
|
|
@@ -51,10 +48,12 @@ function isBuffer(value) {
|
|
|
51
48
|
}
|
|
52
49
|
function isFile(value) {
|
|
53
50
|
if (value instanceof File) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The `Blob` polyfill on Node comes back as being an instanceof `File`. Because passing a Blob
|
|
53
|
+
* into a File will end up with a corrupted file we want to prevent this.
|
|
54
|
+
*
|
|
55
|
+
* This object identity crisis does not happen in the browser.
|
|
56
|
+
*/
|
|
58
57
|
return value.constructor.name === 'File';
|
|
59
58
|
}
|
|
60
59
|
return false;
|
|
@@ -67,8 +66,9 @@ function isFunction(value) {
|
|
|
67
66
|
return typeof value === 'function';
|
|
68
67
|
}
|
|
69
68
|
/**
|
|
70
|
-
* We're
|
|
71
|
-
* regex in its main encoder that Safari doesn't support so it throws a fatal page
|
|
69
|
+
* We're using this library in here instead of loading it from `form-data-encoder` because that
|
|
70
|
+
* uses lookbehind regex in its main encoder that Safari doesn't support so it throws a fatal page
|
|
71
|
+
* exception.
|
|
72
72
|
*
|
|
73
73
|
* @license MIT
|
|
74
74
|
* @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFormData.ts}
|
|
@@ -107,16 +107,22 @@ function fetchHAR(har, opts) {
|
|
|
107
107
|
return headers.append(header.name, header.value);
|
|
108
108
|
}
|
|
109
109
|
catch (err) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
/**
|
|
111
|
+
* `Headers.append()` will throw errors if the header name is not a legal HTTP header name,
|
|
112
|
+
* like `X-API-KEY (Header)`. If that happens instead of tossing the error back out, we
|
|
113
|
+
* should silently just ignore
|
|
114
|
+
* it.
|
|
115
|
+
*/
|
|
113
116
|
}
|
|
114
117
|
});
|
|
115
118
|
}
|
|
116
119
|
if ('cookies' in request && request.cookies.length) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
/**
|
|
121
|
+
* As the browser fetch API can't set custom cookies for requests, they instead need to be
|
|
122
|
+
* defined on the document and passed into the request via `credentials: include`. Since this
|
|
123
|
+
* is a browser-specific quirk, that should only
|
|
124
|
+
* happen in browsers!
|
|
125
|
+
*/
|
|
120
126
|
if (isBrowser()) {
|
|
121
127
|
request.cookies.forEach(function (cookie) {
|
|
122
128
|
document.cookie = "".concat(encodeURIComponent(cookie.name), "=").concat(encodeURIComponent(cookie.value));
|
|
@@ -137,11 +143,14 @@ function fetchHAR(har, opts) {
|
|
|
137
143
|
}
|
|
138
144
|
switch (request.postData.mimeType) {
|
|
139
145
|
case 'application/x-www-form-urlencoded':
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Since the content we're handling here is to be encoded as
|
|
148
|
+
* `application/x-www-form-urlencoded`, this should override any other `Content-Type`
|
|
149
|
+
* headers that are present in the HAR. This is how Postman handles this case when
|
|
150
|
+
* building code snippets!
|
|
151
|
+
*
|
|
152
|
+
* @see {@link https://github.com/github/fetch/issues/263#issuecomment-209530977}
|
|
153
|
+
*/
|
|
145
154
|
headers.set('Content-Type', request.postData.mimeType);
|
|
146
155
|
var encodedParams_1 = new URLSearchParams();
|
|
147
156
|
request.postData.params.forEach(function (param) { return encodedParams_1.set(param.name, param.value); });
|
|
@@ -151,59 +160,67 @@ function fetchHAR(har, opts) {
|
|
|
151
160
|
case 'multipart/form-data':
|
|
152
161
|
case 'multipart/mixed':
|
|
153
162
|
case 'multipart/related':
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
/**
|
|
164
|
+
* If there's a `Content-Type` header set we need to remove it. We're doing this because
|
|
165
|
+
* when we pass the form data object into `fetch` that'll set a proper `Content-Type`
|
|
166
|
+
* header for this request that also includes the boundary used on the content.
|
|
167
|
+
*
|
|
168
|
+
* If we don't do this, then consumers won't be able to parse out the payload because
|
|
169
|
+
* they won't know what the boundary to split on it.
|
|
170
|
+
*/
|
|
160
171
|
if (headers.has('Content-Type')) {
|
|
161
172
|
headers["delete"]('Content-Type');
|
|
162
173
|
}
|
|
163
174
|
var form_1 = new FormData();
|
|
164
175
|
if (!isFormData(form_1)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The `form-data` NPM module returns one of two things: a native `FormData` API or its
|
|
178
|
+
* own polyfill. Unfortunately this polyfill does not support the full API of the native
|
|
179
|
+
* FormData object so when you load `form-data` within a browser environment you'll
|
|
180
|
+
* have two major differences in API:
|
|
181
|
+
*
|
|
182
|
+
* - The `.append()` API in `form-data` requires that the third argument is an object
|
|
183
|
+
* containing various, undocumented, options. In the browser, `.append()`'s third
|
|
184
|
+
* argument should only be present when the second is a `Blob` or `USVString`, and
|
|
185
|
+
* when it is present, it should be a filename string.
|
|
186
|
+
* - `form-data` does not expose an `.entries()` API, so the only way to retrieve data
|
|
187
|
+
* out of it for construction of boundary-separated payload content is to use its
|
|
188
|
+
* `.pipe()` API. Since the browser doesn't have this API, you'll be unable to
|
|
189
|
+
* retrieve data out of it.
|
|
190
|
+
*
|
|
191
|
+
* Now since the native `FormData` API is iterable, and has the `.entries()` iterator,
|
|
192
|
+
* we can easily detect if we have a native copy of the FormData API. It's for all of
|
|
193
|
+
* these reasons that we're opting to hard crash here because supporting this
|
|
194
|
+
* non-compliant API is more trouble than its worth.
|
|
195
|
+
*
|
|
196
|
+
* @see {@link https://github.com/form-data/form-data/issues/124}
|
|
197
|
+
*/
|
|
181
198
|
throw new Error("We've detected you're using a non-spec compliant FormData library. We recommend polyfilling FormData with https://npm.im/formdata-node");
|
|
182
199
|
}
|
|
183
200
|
request.postData.params.forEach(function (param) {
|
|
184
201
|
if ('fileName' in param) {
|
|
185
202
|
if (opts.files && param.fileName in opts.files) {
|
|
186
203
|
var fileContents = opts.files[param.fileName];
|
|
187
|
-
// If the file we've got available to us is a Buffer then we need to convert it so
|
|
188
|
-
// API can use it.
|
|
204
|
+
// If the file we've got available to us is a Buffer then we need to convert it so
|
|
205
|
+
// that the FormData API can use it.
|
|
189
206
|
if (isBuffer(fileContents)) {
|
|
190
|
-
form_1.
|
|
207
|
+
form_1.append(param.name, new File([fileContents], param.fileName, {
|
|
191
208
|
type: param.contentType || null
|
|
192
209
|
}), param.fileName);
|
|
193
210
|
return;
|
|
194
211
|
}
|
|
195
212
|
else if (isFile(fileContents)) {
|
|
196
|
-
form_1.
|
|
213
|
+
form_1.append(param.name, fileContents, param.fileName);
|
|
197
214
|
return;
|
|
198
215
|
}
|
|
199
216
|
throw new TypeError('An unknown object has been supplied into the `files` config for use. We only support instances of the File API and Node Buffer objects.');
|
|
200
217
|
}
|
|
201
218
|
else if ('value' in param) {
|
|
202
219
|
var paramBlob = void 0;
|
|
203
|
-
var parsed = (0,
|
|
220
|
+
var parsed = (0, data_urls_1.parse)(param.value);
|
|
204
221
|
if (parsed) {
|
|
205
|
-
// If we were able to parse out this data URL we don't need to transform its data
|
|
206
|
-
// `Blob` because that supports data URLs already.
|
|
222
|
+
// If we were able to parse out this data URL we don't need to transform its data
|
|
223
|
+
// into a buffer for `Blob` because that supports data URLs already.
|
|
207
224
|
paramBlob = new Blob([param.value], { type: parsed.contentType || param.contentType || null });
|
|
208
225
|
}
|
|
209
226
|
else {
|
|
@@ -216,11 +233,14 @@ function fetchHAR(har, opts) {
|
|
|
216
233
|
}
|
|
217
234
|
form_1.append(param.name, param.value);
|
|
218
235
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
236
|
+
/**
|
|
237
|
+
* If a the `fetch` polyfill that's being used here doesn't have spec-compliant handling
|
|
238
|
+
* for the `FormData` API (like `node-fetch@2`), then you should pass in a handler (like
|
|
239
|
+
* the `form-data-encoder` library) to transform its contents into something that can be
|
|
240
|
+
* used with the `Request` object.
|
|
241
|
+
*
|
|
242
|
+
* @see {@link https://www.npmjs.com/package/formdata-node}
|
|
243
|
+
*/
|
|
224
244
|
if (opts.multipartEncoder) {
|
|
225
245
|
// eslint-disable-next-line new-cap
|
|
226
246
|
var encoder_1 = new opts.multipartEncoder(form_1);
|
|
@@ -249,10 +269,10 @@ function fetchHAR(har, opts) {
|
|
|
249
269
|
}
|
|
250
270
|
}
|
|
251
271
|
else if ((_a = request.postData.text) === null || _a === void 0 ? void 0 : _a.length) {
|
|
252
|
-
// If we've got `files` map content present, and this post data content contains a valid data
|
|
253
|
-
// substitute the payload with that file instead of the using data URL.
|
|
272
|
+
// If we've got `files` map content present, and this post data content contains a valid data
|
|
273
|
+
// URL then we can substitute the payload with that file instead of the using data URL.
|
|
254
274
|
if (opts.files) {
|
|
255
|
-
var parsed = (0,
|
|
275
|
+
var parsed = (0, data_urls_1.parse)(request.postData.text);
|
|
256
276
|
if (parsed) {
|
|
257
277
|
if ((parsed === null || parsed === void 0 ? void 0 : parsed.name) && parsed.name in opts.files) {
|
|
258
278
|
var fileContents = opts.files[parsed.name];
|
|
@@ -260,15 +280,16 @@ function fetchHAR(har, opts) {
|
|
|
260
280
|
options.body = fileContents;
|
|
261
281
|
}
|
|
262
282
|
else if (isFile(fileContents)) {
|
|
263
|
-
// `Readable.from` isn't available in browsers but the browser `Request` object can
|
|
264
|
-
// just fine without us having to mold it into shape.
|
|
283
|
+
// `Readable.from` isn't available in browsers but the browser `Request` object can
|
|
284
|
+
// handle `File` objects just fine without us having to mold it into shape.
|
|
265
285
|
if (isBrowser()) {
|
|
266
286
|
options.body = fileContents;
|
|
267
287
|
}
|
|
268
288
|
else {
|
|
269
289
|
// @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
|
|
270
290
|
options.body = readable_stream_1.Readable.from(fileContents.stream());
|
|
271
|
-
// Supplying a polyfilled `File` stream into `Request.body` doesn't automatically
|
|
291
|
+
// Supplying a polyfilled `File` stream into `Request.body` doesn't automatically
|
|
292
|
+
// add `Content-Length`.
|
|
272
293
|
if (!headers.has('content-length')) {
|
|
273
294
|
headers.set('content-length', String(fileContents.size));
|
|
274
295
|
}
|
|
@@ -282,8 +303,8 @@ function fetchHAR(har, opts) {
|
|
|
282
303
|
}
|
|
283
304
|
}
|
|
284
305
|
}
|
|
285
|
-
// We automaticaly assume that the HAR that we have already has query parameters encoded within
|
|
286
|
-
// use the `URLSearchParams` API here for composing the query string.
|
|
306
|
+
// We automaticaly assume that the HAR that we have already has query parameters encoded within
|
|
307
|
+
// it so we do **not** use the `URLSearchParams` API here for composing the query string.
|
|
287
308
|
if ('queryString' in request && request.queryString.length) {
|
|
288
309
|
var urlObj = new URL(url);
|
|
289
310
|
var queryParams_1 = Array.from(urlObj.searchParams).map(function (_a) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetch-har",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.2",
|
|
4
4
|
"description": "Make a fetch request from a HAR definition",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
},
|
|
31
31
|
"homepage": "https://github.com/readmeio/fetch-har#readme",
|
|
32
32
|
"dependencies": {
|
|
33
|
+
"@readme/data-urls": "^1.0.1",
|
|
33
34
|
"@types/har-format": "^1.2.8",
|
|
34
|
-
"parse-data-url": "^4.0.1",
|
|
35
35
|
"readable-stream": "^3.6.0"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
@@ -40,14 +40,17 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@jsdevtools/host-environment": "^2.1.2",
|
|
42
42
|
"@jsdevtools/karma-config": "^3.1.7",
|
|
43
|
-
"@readme/eslint-config": "^
|
|
43
|
+
"@readme/eslint-config": "^9.0.0",
|
|
44
44
|
"@types/chai": "^4.3.1",
|
|
45
|
+
"@types/express": "^4.17.13",
|
|
45
46
|
"@types/mocha": "^9.1.1",
|
|
46
|
-
"@types/
|
|
47
|
-
"@types/
|
|
47
|
+
"@types/multer": "^1.4.7",
|
|
48
|
+
"@types/node": "^18.0.0",
|
|
48
49
|
"@types/readable-stream": "^2.3.13",
|
|
49
50
|
"chai": "^4.3.4",
|
|
51
|
+
"datauri": "^4.1.0",
|
|
50
52
|
"eslint": "^8.14.0",
|
|
53
|
+
"express": "^4.18.1",
|
|
51
54
|
"fetch-mock": "^9.11.0",
|
|
52
55
|
"form-data": "^4.0.0",
|
|
53
56
|
"form-data-encoder": "^1.7.1",
|
|
@@ -55,14 +58,17 @@
|
|
|
55
58
|
"har-examples": "^3.1.1",
|
|
56
59
|
"isomorphic-fetch": "^3.0.0",
|
|
57
60
|
"mocha": "^10.0.0",
|
|
61
|
+
"multer": "^1.4.5-lts.1",
|
|
58
62
|
"nock": "^13.2.4",
|
|
59
63
|
"node-fetch": "^2.6.0",
|
|
60
64
|
"nyc": "^15.1.0",
|
|
61
65
|
"prettier": "^2.6.2",
|
|
62
|
-
"
|
|
66
|
+
"temp-dir": "^2.0.0",
|
|
67
|
+
"ts-loader": "^8.4.0",
|
|
63
68
|
"ts-node": "^10.7.0",
|
|
64
69
|
"typescript": "^4.6.3",
|
|
65
|
-
"undici": "^5.0.0"
|
|
70
|
+
"undici": "^5.0.0",
|
|
71
|
+
"webpack": "^4.46.0"
|
|
66
72
|
},
|
|
67
73
|
"browserslist": [
|
|
68
74
|
"last 2 versions"
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Har } from 'har-format';
|
|
2
|
+
import type { DataURL as npmDataURL } from '@readme/data-urls';
|
|
2
3
|
import { Readable } from 'readable-stream';
|
|
3
|
-
import parseDataUrl from '
|
|
4
|
+
import { parse as parseDataUrl } from '@readme/data-urls';
|
|
4
5
|
|
|
5
6
|
if (!globalThis.Blob) {
|
|
6
7
|
try {
|
|
@@ -45,10 +46,12 @@ function isBuffer(value: any) {
|
|
|
45
46
|
|
|
46
47
|
function isFile(value: any) {
|
|
47
48
|
if (value instanceof File) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The `Blob` polyfill on Node comes back as being an instanceof `File`. Because passing a Blob
|
|
51
|
+
* into a File will end up with a corrupted file we want to prevent this.
|
|
52
|
+
*
|
|
53
|
+
* This object identity crisis does not happen in the browser.
|
|
54
|
+
*/
|
|
52
55
|
return value.constructor.name === 'File';
|
|
53
56
|
}
|
|
54
57
|
|
|
@@ -64,8 +67,9 @@ function isFunction(value: any) {
|
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
|
-
* We're
|
|
68
|
-
* regex in its main encoder that Safari doesn't support so it throws a fatal page
|
|
70
|
+
* We're using this library in here instead of loading it from `form-data-encoder` because that
|
|
71
|
+
* uses lookbehind regex in its main encoder that Safari doesn't support so it throws a fatal page
|
|
72
|
+
* exception.
|
|
69
73
|
*
|
|
70
74
|
* @license MIT
|
|
71
75
|
* @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFormData.ts}
|
|
@@ -89,7 +93,7 @@ type FetchHAROptions = {
|
|
|
89
93
|
init?: RequestInit;
|
|
90
94
|
};
|
|
91
95
|
|
|
92
|
-
type DataURL =
|
|
96
|
+
type DataURL = npmDataURL & {
|
|
93
97
|
// `parse-data-url` doesn't explicitly support `name` in data URLs but if it's there it'll be
|
|
94
98
|
// returned back to us.
|
|
95
99
|
name?: string;
|
|
@@ -123,17 +127,23 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
123
127
|
try {
|
|
124
128
|
return headers.append(header.name, header.value);
|
|
125
129
|
} catch (err) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
/**
|
|
131
|
+
* `Headers.append()` will throw errors if the header name is not a legal HTTP header name,
|
|
132
|
+
* like `X-API-KEY (Header)`. If that happens instead of tossing the error back out, we
|
|
133
|
+
* should silently just ignore
|
|
134
|
+
* it.
|
|
135
|
+
*/
|
|
129
136
|
}
|
|
130
137
|
});
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
if ('cookies' in request && request.cookies.length) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
/**
|
|
142
|
+
* As the browser fetch API can't set custom cookies for requests, they instead need to be
|
|
143
|
+
* defined on the document and passed into the request via `credentials: include`. Since this
|
|
144
|
+
* is a browser-specific quirk, that should only
|
|
145
|
+
* happen in browsers!
|
|
146
|
+
*/
|
|
137
147
|
if (isBrowser()) {
|
|
138
148
|
request.cookies.forEach(cookie => {
|
|
139
149
|
document.cookie = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
@@ -159,11 +169,14 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
159
169
|
|
|
160
170
|
switch (request.postData.mimeType) {
|
|
161
171
|
case 'application/x-www-form-urlencoded':
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Since the content we're handling here is to be encoded as
|
|
174
|
+
* `application/x-www-form-urlencoded`, this should override any other `Content-Type`
|
|
175
|
+
* headers that are present in the HAR. This is how Postman handles this case when
|
|
176
|
+
* building code snippets!
|
|
177
|
+
*
|
|
178
|
+
* @see {@link https://github.com/github/fetch/issues/263#issuecomment-209530977}
|
|
179
|
+
*/
|
|
167
180
|
headers.set('Content-Type', request.postData.mimeType);
|
|
168
181
|
|
|
169
182
|
const encodedParams = new URLSearchParams();
|
|
@@ -176,34 +189,42 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
176
189
|
case 'multipart/form-data':
|
|
177
190
|
case 'multipart/mixed':
|
|
178
191
|
case 'multipart/related':
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
/**
|
|
193
|
+
* If there's a `Content-Type` header set we need to remove it. We're doing this because
|
|
194
|
+
* when we pass the form data object into `fetch` that'll set a proper `Content-Type`
|
|
195
|
+
* header for this request that also includes the boundary used on the content.
|
|
196
|
+
*
|
|
197
|
+
* If we don't do this, then consumers won't be able to parse out the payload because
|
|
198
|
+
* they won't know what the boundary to split on it.
|
|
199
|
+
*/
|
|
185
200
|
if (headers.has('Content-Type')) {
|
|
186
201
|
headers.delete('Content-Type');
|
|
187
202
|
}
|
|
188
203
|
|
|
189
204
|
const form = new FormData();
|
|
190
205
|
if (!isFormData(form)) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
/**
|
|
207
|
+
* The `form-data` NPM module returns one of two things: a native `FormData` API or its
|
|
208
|
+
* own polyfill. Unfortunately this polyfill does not support the full API of the native
|
|
209
|
+
* FormData object so when you load `form-data` within a browser environment you'll
|
|
210
|
+
* have two major differences in API:
|
|
211
|
+
*
|
|
212
|
+
* - The `.append()` API in `form-data` requires that the third argument is an object
|
|
213
|
+
* containing various, undocumented, options. In the browser, `.append()`'s third
|
|
214
|
+
* argument should only be present when the second is a `Blob` or `USVString`, and
|
|
215
|
+
* when it is present, it should be a filename string.
|
|
216
|
+
* - `form-data` does not expose an `.entries()` API, so the only way to retrieve data
|
|
217
|
+
* out of it for construction of boundary-separated payload content is to use its
|
|
218
|
+
* `.pipe()` API. Since the browser doesn't have this API, you'll be unable to
|
|
219
|
+
* retrieve data out of it.
|
|
220
|
+
*
|
|
221
|
+
* Now since the native `FormData` API is iterable, and has the `.entries()` iterator,
|
|
222
|
+
* we can easily detect if we have a native copy of the FormData API. It's for all of
|
|
223
|
+
* these reasons that we're opting to hard crash here because supporting this
|
|
224
|
+
* non-compliant API is more trouble than its worth.
|
|
225
|
+
*
|
|
226
|
+
* @see {@link https://github.com/form-data/form-data/issues/124}
|
|
227
|
+
*/
|
|
207
228
|
throw new Error(
|
|
208
229
|
"We've detected you're using a non-spec compliant FormData library. We recommend polyfilling FormData with https://npm.im/formdata-node"
|
|
209
230
|
);
|
|
@@ -214,10 +235,10 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
214
235
|
if (opts.files && param.fileName in opts.files) {
|
|
215
236
|
const fileContents = opts.files[param.fileName];
|
|
216
237
|
|
|
217
|
-
// If the file we've got available to us is a Buffer then we need to convert it so
|
|
218
|
-
// API can use it.
|
|
238
|
+
// If the file we've got available to us is a Buffer then we need to convert it so
|
|
239
|
+
// that the FormData API can use it.
|
|
219
240
|
if (isBuffer(fileContents)) {
|
|
220
|
-
form.
|
|
241
|
+
form.append(
|
|
221
242
|
param.name,
|
|
222
243
|
new File([fileContents], param.fileName, {
|
|
223
244
|
type: param.contentType || null,
|
|
@@ -227,7 +248,7 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
227
248
|
|
|
228
249
|
return;
|
|
229
250
|
} else if (isFile(fileContents)) {
|
|
230
|
-
form.
|
|
251
|
+
form.append(param.name, fileContents as Blob, param.fileName);
|
|
231
252
|
return;
|
|
232
253
|
}
|
|
233
254
|
|
|
@@ -238,8 +259,8 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
238
259
|
let paramBlob;
|
|
239
260
|
const parsed = parseDataUrl(param.value);
|
|
240
261
|
if (parsed) {
|
|
241
|
-
// If we were able to parse out this data URL we don't need to transform its data
|
|
242
|
-
// `Blob` because that supports data URLs already.
|
|
262
|
+
// If we were able to parse out this data URL we don't need to transform its data
|
|
263
|
+
// into a buffer for `Blob` because that supports data URLs already.
|
|
243
264
|
paramBlob = new Blob([param.value], { type: parsed.contentType || param.contentType || null });
|
|
244
265
|
} else {
|
|
245
266
|
paramBlob = new Blob([param.value], { type: param.contentType || null });
|
|
@@ -257,11 +278,14 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
257
278
|
form.append(param.name, param.value);
|
|
258
279
|
});
|
|
259
280
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
281
|
+
/**
|
|
282
|
+
* If a the `fetch` polyfill that's being used here doesn't have spec-compliant handling
|
|
283
|
+
* for the `FormData` API (like `node-fetch@2`), then you should pass in a handler (like
|
|
284
|
+
* the `form-data-encoder` library) to transform its contents into something that can be
|
|
285
|
+
* used with the `Request` object.
|
|
286
|
+
*
|
|
287
|
+
* @see {@link https://www.npmjs.com/package/formdata-node}
|
|
288
|
+
*/
|
|
265
289
|
if (opts.multipartEncoder) {
|
|
266
290
|
// eslint-disable-next-line new-cap
|
|
267
291
|
const encoder = new opts.multipartEncoder(form);
|
|
@@ -291,8 +315,8 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
291
315
|
options.body = JSON.stringify(formBody);
|
|
292
316
|
}
|
|
293
317
|
} else if (request.postData.text?.length) {
|
|
294
|
-
// If we've got `files` map content present, and this post data content contains a valid data
|
|
295
|
-
// substitute the payload with that file instead of the using data URL.
|
|
318
|
+
// If we've got `files` map content present, and this post data content contains a valid data
|
|
319
|
+
// URL then we can substitute the payload with that file instead of the using data URL.
|
|
296
320
|
if (opts.files) {
|
|
297
321
|
const parsed = parseDataUrl(request.postData.text) as DataURL;
|
|
298
322
|
if (parsed) {
|
|
@@ -301,15 +325,16 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
301
325
|
if (isBuffer(fileContents)) {
|
|
302
326
|
options.body = fileContents;
|
|
303
327
|
} else if (isFile(fileContents)) {
|
|
304
|
-
// `Readable.from` isn't available in browsers but the browser `Request` object can
|
|
305
|
-
// just fine without us having to mold it into shape.
|
|
328
|
+
// `Readable.from` isn't available in browsers but the browser `Request` object can
|
|
329
|
+
// handle `File` objects just fine without us having to mold it into shape.
|
|
306
330
|
if (isBrowser()) {
|
|
307
331
|
options.body = fileContents;
|
|
308
332
|
} else {
|
|
309
333
|
// @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
|
|
310
334
|
options.body = Readable.from((fileContents as File).stream());
|
|
311
335
|
|
|
312
|
-
// Supplying a polyfilled `File` stream into `Request.body` doesn't automatically
|
|
336
|
+
// Supplying a polyfilled `File` stream into `Request.body` doesn't automatically
|
|
337
|
+
// add `Content-Length`.
|
|
313
338
|
if (!headers.has('content-length')) {
|
|
314
339
|
headers.set('content-length', String((fileContents as File).size));
|
|
315
340
|
}
|
|
@@ -325,8 +350,8 @@ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
|
|
|
325
350
|
}
|
|
326
351
|
}
|
|
327
352
|
|
|
328
|
-
// We automaticaly assume that the HAR that we have already has query parameters encoded within
|
|
329
|
-
// use the `URLSearchParams` API here for composing the query string.
|
|
353
|
+
// We automaticaly assume that the HAR that we have already has query parameters encoded within
|
|
354
|
+
// it so we do **not** use the `URLSearchParams` API here for composing the query string.
|
|
330
355
|
if ('queryString' in request && request.queryString.length) {
|
|
331
356
|
const urlObj = new URL(url);
|
|
332
357
|
|