fetch-har 5.0.3 → 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/README.md +50 -17
- package/example.js +9 -6
- package/index.js +196 -55
- package/package.json +25 -30
- package/jest-puppeteer.config.js +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
+
## <small>6.0.1 (2022-01-26)</small>
|
|
2
|
+
|
|
3
|
+
* fix: regression in query parameter handling (#242) ([6c6a147](https://github.com/readmeio/fetch-har/commit/6c6a147)), closes [#242](https://github.com/readmeio/fetch-har/issues/242)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## <small>6.0.0 (2022-01-25)</small>
|
|
8
|
+
|
|
9
|
+
> **BREAKING CHANGE**
|
|
10
|
+
>
|
|
11
|
+
> With this release we no longer support the [form-data](https://npm.im/form-data) package as it's handling of `multipart/form-data` is non-compliant with the browser `FormData` API. Please use [formdata-node](https://npm.im/formdata-node) or [formdata-polyfill](https://npm.im/formdata-polyfill) instead.
|
|
12
|
+
>
|
|
13
|
+
> Additionally the optional `userAgent` option has been reworked into a new options object that also supports a new `files` mapping array for augmenting binaries or file uploads within a HAR with a file buffers or the `File` API. See the readme for documentation.
|
|
14
|
+
|
|
15
|
+
* fix: handling of multipart/form-data requests and how we interact with FormData ([#237](https://github.com/readmeio/fetch-har/pull/237))
|
|
16
|
+
* chore(deps-dev): bumping node-fetch ([#240]((https://github.com/readmeio/fetch-har/pull/240))
|
|
17
|
+
* fix: improper handling of query strings ([#239](https://github.com/readmeio/fetch-har/pull/239))
|
|
18
|
+
* feat: extending the new files option to allow overriding raw binary payloads ([#238](https://github.com/readmeio/fetch-har/pull/238))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## <small>5.0.5 (2022-01-03)</small>
|
|
24
|
+
|
|
25
|
+
* chore: removing the publishConfig from the package file ([05df1e7](https://github.com/readmeio/fetch-har/commit/05df1e7))
|
|
26
|
+
* chore(deps-dev): bump @readme/eslint-config from 8.0.2 to 8.1.1 (#235) ([f10d1dd](https://github.com/readmeio/fetch-har/commit/f10d1dd)), closes [#235](https://github.com/readmeio/fetch-har/issues/235)
|
|
27
|
+
* chore(deps-dev): bump body-parser from 1.19.0 to 1.19.1 (#225) ([a42b49d](https://github.com/readmeio/fetch-har/commit/a42b49d)), closes [#225](https://github.com/readmeio/fetch-har/issues/225)
|
|
28
|
+
* chore(deps-dev): bump eslint from 8.3.0 to 8.6.0 (#228) ([e592606](https://github.com/readmeio/fetch-har/commit/e592606)), closes [#228](https://github.com/readmeio/fetch-har/issues/228)
|
|
29
|
+
* chore(deps-dev): bump har-examples from 2.0.2 to 2.1.0 (#231) ([efaa16e](https://github.com/readmeio/fetch-har/commit/efaa16e)), closes [#231](https://github.com/readmeio/fetch-har/issues/231)
|
|
30
|
+
* chore(deps-dev): bump jest from 27.4.2 to 27.4.5 (#234) ([24f099e](https://github.com/readmeio/fetch-har/commit/24f099e)), closes [#234](https://github.com/readmeio/fetch-har/issues/234)
|
|
31
|
+
* chore(deps-dev): bump jest-puppeteer from 6.0.2 to 6.0.3 (#229) ([fcd9262](https://github.com/readmeio/fetch-har/commit/fcd9262)), closes [#229](https://github.com/readmeio/fetch-har/issues/229)
|
|
32
|
+
* chore(deps-dev): bump multer from 1.4.3 to 1.4.4 (#232) ([4119c42](https://github.com/readmeio/fetch-har/commit/4119c42)), closes [#232](https://github.com/readmeio/fetch-har/issues/232)
|
|
33
|
+
* chore(deps-dev): bump prettier from 2.5.0 to 2.5.1 (#233) ([4f96fae](https://github.com/readmeio/fetch-har/commit/4f96fae)), closes [#233](https://github.com/readmeio/fetch-har/issues/233)
|
|
34
|
+
* chore(deps-dev): bump webpack from 5.64.4 to 5.65.0 (#230) ([f06706b](https://github.com/readmeio/fetch-har/commit/f06706b)), closes [#230](https://github.com/readmeio/fetch-har/issues/230)
|
|
35
|
+
* chore(deps-dev): bump webpack-dev-middleware from 5.2.2 to 5.3.0 (#227) ([f68d06c](https://github.com/readmeio/fetch-har/commit/f68d06c)), closes [#227](https://github.com/readmeio/fetch-har/issues/227)
|
|
36
|
+
* chore(deps): bump actions/setup-node from 2.5.0 to 2.5.1 (#226) ([38fa52b](https://github.com/readmeio/fetch-har/commit/38fa52b)), closes [#226](https://github.com/readmeio/fetch-har/issues/226)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## <small>5.0.4 (2021-12-01)</small>
|
|
41
|
+
|
|
42
|
+
* chore(deps-dev): bump @readme/eslint-config from 7.1.1 to 7.2.2 (#211) ([1081874](https://github.com/readmeio/fetch-har/commit/1081874)), closes [#211](https://github.com/readmeio/fetch-har/issues/211)
|
|
43
|
+
* chore(deps-dev): bump cookie-parser from 1.4.5 to 1.4.6 (#219) ([41d8c60](https://github.com/readmeio/fetch-har/commit/41d8c60)), closes [#219](https://github.com/readmeio/fetch-har/issues/219)
|
|
44
|
+
* chore(deps-dev): bump eslint from 8.2.0 to 8.3.0 (#215) ([ab0e294](https://github.com/readmeio/fetch-har/commit/ab0e294)), closes [#215](https://github.com/readmeio/fetch-har/issues/215)
|
|
45
|
+
* chore(deps-dev): bump har-examples from 2.0.1 to 2.0.2 (#204) ([87b1742](https://github.com/readmeio/fetch-har/commit/87b1742)), closes [#204](https://github.com/readmeio/fetch-har/issues/204)
|
|
46
|
+
* chore(deps-dev): bump jest from 27.2.0 to 27.2.4 (#205) ([37446e8](https://github.com/readmeio/fetch-har/commit/37446e8)), closes [#205](https://github.com/readmeio/fetch-har/issues/205)
|
|
47
|
+
* chore(deps-dev): bump jest from 27.2.4 to 27.3.1 (#212) ([bc85194](https://github.com/readmeio/fetch-har/commit/bc85194)), closes [#212](https://github.com/readmeio/fetch-har/issues/212)
|
|
48
|
+
* chore(deps-dev): bump jest from 27.3.1 to 27.4.2 (#221) ([8c9dafd](https://github.com/readmeio/fetch-har/commit/8c9dafd)), closes [#221](https://github.com/readmeio/fetch-har/issues/221)
|
|
49
|
+
* chore(deps-dev): bump jest-puppeteer from 5.0.4 to 6.0.0 (#207) ([11ccb44](https://github.com/readmeio/fetch-har/commit/11ccb44)), closes [#207](https://github.com/readmeio/fetch-har/issues/207)
|
|
50
|
+
* chore(deps-dev): bump jest-puppeteer from 6.0.0 to 6.0.2 (#222) ([d17d217](https://github.com/readmeio/fetch-har/commit/d17d217)), closes [#222](https://github.com/readmeio/fetch-har/issues/222)
|
|
51
|
+
* chore(deps-dev): bump nock from 13.1.3 to 13.1.4 (#209) ([4f81357](https://github.com/readmeio/fetch-har/commit/4f81357)), closes [#209](https://github.com/readmeio/fetch-har/issues/209)
|
|
52
|
+
* chore(deps-dev): bump nock from 13.1.4 to 13.2.1 (#224) ([fefd729](https://github.com/readmeio/fetch-har/commit/fefd729)), closes [#224](https://github.com/readmeio/fetch-har/issues/224)
|
|
53
|
+
* chore(deps-dev): bump node-fetch from 2.6.1 to 2.6.5 (#208) ([eadd607](https://github.com/readmeio/fetch-har/commit/eadd607)), closes [#208](https://github.com/readmeio/fetch-har/issues/208)
|
|
54
|
+
* chore(deps-dev): bump node-fetch from 2.6.5 to 2.6.6 (#214) ([6bfe538](https://github.com/readmeio/fetch-har/commit/6bfe538)), closes [#214](https://github.com/readmeio/fetch-har/issues/214)
|
|
55
|
+
* chore(deps-dev): bump prettier from 2.4.1 to 2.5.0 (#216) ([dee54f6](https://github.com/readmeio/fetch-har/commit/dee54f6)), closes [#216](https://github.com/readmeio/fetch-har/issues/216)
|
|
56
|
+
* chore(deps-dev): bump webpack from 5.53.0 to 5.55.1 (#202) ([427332e](https://github.com/readmeio/fetch-har/commit/427332e)), closes [#202](https://github.com/readmeio/fetch-har/issues/202)
|
|
57
|
+
* chore(deps-dev): bump webpack from 5.55.1 to 5.61.0 (#213) ([8276f5f](https://github.com/readmeio/fetch-har/commit/8276f5f)), closes [#213](https://github.com/readmeio/fetch-har/issues/213)
|
|
58
|
+
* chore(deps-dev): bump webpack from 5.61.0 to 5.64.4 (#220) ([0dcfb6c](https://github.com/readmeio/fetch-har/commit/0dcfb6c)), closes [#220](https://github.com/readmeio/fetch-har/issues/220)
|
|
59
|
+
* chore(deps-dev): bump webpack-dev-middleware from 5.1.0 to 5.2.1 (#206) ([94f0ad2](https://github.com/readmeio/fetch-har/commit/94f0ad2)), closes [#206](https://github.com/readmeio/fetch-har/issues/206)
|
|
60
|
+
* chore(deps-dev): bump webpack-dev-middleware from 5.2.1 to 5.2.2 (#223) ([90c0501](https://github.com/readmeio/fetch-har/commit/90c0501)), closes [#223](https://github.com/readmeio/fetch-har/issues/223)
|
|
61
|
+
* chore(deps-dev): upgrading eslint to v8 ([f06d433](https://github.com/readmeio/fetch-har/commit/f06d433))
|
|
62
|
+
* chore(deps): bump actions/checkout from 2.3.4 to 2.3.5 (#210) ([5360ba9](https://github.com/readmeio/fetch-har/commit/5360ba9)), closes [#210](https://github.com/readmeio/fetch-har/issues/210)
|
|
63
|
+
* chore(deps): bump actions/checkout from 2.3.5 to 2.4.0 (#217) ([0e46da0](https://github.com/readmeio/fetch-har/commit/0e46da0)), closes [#217](https://github.com/readmeio/fetch-har/issues/217)
|
|
64
|
+
* chore(deps): bump actions/setup-node from 2.4.0 to 2.4.1 (#203) ([e6a4529](https://github.com/readmeio/fetch-har/commit/e6a4529)), closes [#203](https://github.com/readmeio/fetch-har/issues/203)
|
|
65
|
+
* chore(deps): bump actions/setup-node from 2.4.1 to 2.5.0 (#218) ([e2cab24](https://github.com/readmeio/fetch-har/commit/e2cab24)), closes [#218](https://github.com/readmeio/fetch-har/issues/218)
|
|
66
|
+
* docs: adding a code of conduct ([cbc9fa0](https://github.com/readmeio/fetch-har/commit/cbc9fa0))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
1
70
|
## <small>5.0.3 (2021-09-16)</small>
|
|
2
71
|
|
|
3
72
|
* ci: updating codeql to run tests against main ([f3afd13](https://github.com/readmeio/fetch-har/commit/f3afd13))
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity
|
|
10
|
+
and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the
|
|
26
|
+
overall community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or
|
|
31
|
+
advances of any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email
|
|
35
|
+
address, without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official e-mail address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[support+coc@readme.io](mailto:support+coc@readme.io).
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series
|
|
86
|
+
of actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or
|
|
93
|
+
permanent ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within
|
|
113
|
+
the community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.0, available at
|
|
119
|
+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
122
|
+
enforcement ladder](https://github.com/mozilla/diversity).
|
|
123
|
+
|
|
124
|
+
[homepage]: https://www.contributor-covenant.org
|
|
125
|
+
|
|
126
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
127
|
+
https://www.contributor-covenant.org/faq. Translations are available at
|
|
128
|
+
https://www.contributor-covenant.org/translations.
|
package/README.md
CHANGED
|
@@ -13,13 +13,17 @@ npm install --save fetch-har
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
```js
|
|
16
|
-
|
|
16
|
+
require('isomorphic-fetch');
|
|
17
|
+
const fetchHar = require('.');
|
|
17
18
|
|
|
18
|
-
// If executing from an environment
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// If executing from an environment that dodoesn't normally provide fetch()
|
|
20
|
+
// you'll need to polyfill some APIs in order to make `multipart/form-data`
|
|
21
|
+
// requests.
|
|
22
|
+
if (!globalThis.FormData) {
|
|
23
|
+
globalThis.Blob = require('formdata-node').Blob;
|
|
24
|
+
globalThis.File = require('formdata-node').File;
|
|
25
|
+
globalThis.FormData = require('formdata-node').FormData;
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
const har = {
|
|
25
29
|
log: {
|
|
@@ -36,7 +40,10 @@ const har = {
|
|
|
36
40
|
value: 'application/json',
|
|
37
41
|
},
|
|
38
42
|
],
|
|
39
|
-
queryString: [
|
|
43
|
+
queryString: [
|
|
44
|
+
{ name: 'a', value: 1 },
|
|
45
|
+
{ name: 'b', value: 2 },
|
|
46
|
+
],
|
|
40
47
|
postData: {
|
|
41
48
|
mimeType: 'application/json',
|
|
42
49
|
text: '{"id":8,"category":{"id":6,"name":"name"},"name":"name"}',
|
|
@@ -50,22 +57,48 @@ const har = {
|
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
fetchHar(har)
|
|
53
|
-
.then(
|
|
60
|
+
.then(res => res.json())
|
|
54
61
|
.then(console.log);
|
|
55
62
|
```
|
|
56
63
|
|
|
57
|
-
###
|
|
64
|
+
### API
|
|
65
|
+
If you are executing `fetch-har` in a browser environment that supports the [FormData API](https://developer.mozilla.org/en-US/docs/Web/API/FormData) then you don't need to do anything. If you arent, however, you'll need to polyfill it.
|
|
66
|
+
|
|
67
|
+
Unfortunately the most popular NPM package [form-data](https://npm.im/form-data) ships with a [non-spec compliant API](https://github.com/form-data/form-data/issues/124), and for this we don't recommend you use it, as if you use `fetch-har` to upload files it may not work.
|
|
68
|
+
|
|
69
|
+
We recommend either [formdata-node](https://npm.im/formdata-node) or [formdata-polyfill](https://npm.im/formdata-polyfill).
|
|
70
|
+
|
|
71
|
+
#### Options
|
|
72
|
+
##### userAgent
|
|
73
|
+
A custom `User-Agent` header to apply to your request. Please note that browsers have their own handling for these headers in `fetch()` calls so it may not work everywhere; it will always be sent in Node however.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
await fetchHar(har, { userAgent: 'my-client/1.0' });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
##### files
|
|
80
|
+
An optional object map you can supply to use for `multipart/form-data` file uploads in leu of relying on if the HAR you have has [data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). It supports Node file buffers and the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) API.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
await fetchHar(har, { files: {
|
|
84
|
+
'owlbert.png': await fs.readFile('./owlbert.png'),
|
|
85
|
+
'file.txt': document.querySelector('#some-file-input').files[0],
|
|
86
|
+
} });
|
|
87
|
+
```
|
|
58
88
|
|
|
59
|
-
|
|
60
|
-
- `userAgent` is an optional user agent string to let you declare where the request is coming from.
|
|
89
|
+
If you don't supply this option `fetch-har` will fallback to the data URL present within the supplied HAR. If no `files` option is present, and no data URL (via `param.value`) is present in the HAR, a fatal exception will be thrown.
|
|
61
90
|
|
|
62
|
-
|
|
91
|
+
##### multipartEncoder
|
|
92
|
+
> ❗ If you are using `fetch-har` in Node you may need this option!
|
|
63
93
|
|
|
64
|
-
|
|
94
|
+
If you are running `fetch-har` within a Node environment and you're using `node-fetch@2`, or another `fetch` polyfill that does not support a spec-compliant `FormData` API, you will need to specify an encoder that will transform your `FormData` object into something that can be used with [Request.body](https://developer.mozilla.org/en-US/docs/Web/API/Request/body).
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
- `userAgent` is an optional user agent string to let you declare where the request is coming from.
|
|
96
|
+
We recommend [form-data-encoder](https://npm.im/form-data-encoder).
|
|
68
97
|
|
|
69
|
-
|
|
98
|
+
```js
|
|
99
|
+
const { FormDataEncoder } = require('form-data-encoder');
|
|
100
|
+
|
|
101
|
+
await fetchHar(har, { multipartEncoder: FormDataEncoder });
|
|
102
|
+
```
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
You do **not**, and shouldn't, need to use this option in browser environments.
|
package/example.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/* eslint-disable import/no-extraneous-dependencies, no-console */
|
|
2
|
+
require('isomorphic-fetch');
|
|
2
3
|
const fetchHar = require('.');
|
|
3
4
|
|
|
4
|
-
// If executing from an environment
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// If executing from an environment that dodoesn't normally provide fetch() you'll need to polyfill some APIs in order
|
|
6
|
+
// to make `multipart/form-data` requests.
|
|
7
|
+
if (!globalThis.FormData) {
|
|
8
|
+
globalThis.Blob = require('formdata-node').Blob;
|
|
9
|
+
globalThis.File = require('formdata-node').File;
|
|
10
|
+
globalThis.FormData = require('formdata-node').FormData;
|
|
11
|
+
}
|
|
9
12
|
|
|
10
13
|
const har = {
|
|
11
14
|
log: {
|
|
@@ -39,5 +42,5 @@ const har = {
|
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
fetchHar(har)
|
|
42
|
-
.then(
|
|
45
|
+
.then(res => res.json())
|
|
43
46
|
.then(console.log);
|
package/index.js
CHANGED
|
@@ -1,7 +1,76 @@
|
|
|
1
|
-
|
|
1
|
+
const { Readable } = require('readable-stream');
|
|
2
2
|
const parseDataUrl = require('parse-data-url');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
if (!globalThis.Blob) {
|
|
5
|
+
try {
|
|
6
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
7
|
+
globalThis.Blob = require('formdata-node').Blob;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
'Since you do not have the Blob API available in this environment you must install the optional `formdata-node` dependency.'
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!globalThis.File) {
|
|
16
|
+
try {
|
|
17
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
18
|
+
globalThis.File = require('formdata-node').File;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'Since you do not have the File API available in this environment you must install the optional `formdata-node` dependency.'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isBrowser() {
|
|
27
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isBuffer(value) {
|
|
31
|
+
return typeof Buffer !== 'undefined' && Buffer.isBuffer(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isFile(value) {
|
|
35
|
+
if (value instanceof File) {
|
|
36
|
+
// The `Blob` polyfill on Node comes back as being an instanceof `File`. Because passing a Blob into
|
|
37
|
+
// a File will end up with a corrupted file we want to prevent this.
|
|
38
|
+
//
|
|
39
|
+
// This object identity crisis does not happen in the browser.
|
|
40
|
+
return value.constructor.name === 'File';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @license MIT
|
|
48
|
+
* @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFunction.ts}
|
|
49
|
+
*/
|
|
50
|
+
function isFunction(value) {
|
|
51
|
+
return typeof value === 'function';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* We're loading this library in here instead of loading it from `form-data-encoder` because that uses lookbehind
|
|
56
|
+
* regex in its main encoder that Safari doesn't support so it throws a fatal page exception.
|
|
57
|
+
*
|
|
58
|
+
* @license MIT
|
|
59
|
+
* @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFormData.ts}
|
|
60
|
+
*/
|
|
61
|
+
function isFormData(value) {
|
|
62
|
+
return (
|
|
63
|
+
value &&
|
|
64
|
+
isFunction(value.constructor) &&
|
|
65
|
+
value[Symbol.toStringTag] === 'FormData' && // eslint-disable-line compat/compat
|
|
66
|
+
isFunction(value.append) &&
|
|
67
|
+
isFunction(value.getAll) &&
|
|
68
|
+
isFunction(value.entries) &&
|
|
69
|
+
isFunction(value[Symbol.iterator]) // eslint-disable-line compat/compat
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function constructRequest(har, opts = { userAgent: false, files: false, multipartEncoder: false }) {
|
|
5
74
|
if (!har) throw new Error('Missing HAR definition');
|
|
6
75
|
if (!har.log || !har.log.entries || !har.log.entries.length) throw new Error('Missing log.entries array');
|
|
7
76
|
|
|
@@ -31,7 +100,7 @@ function constructRequest(har, userAgent = false) {
|
|
|
31
100
|
// As the browser fetch API can't set custom cookies for requests, they instead need to be defined on the document
|
|
32
101
|
// and passed into the request via `credentials: include`. Since this is a browser-specific quirk, that should only
|
|
33
102
|
// happen in browsers!
|
|
34
|
-
if (
|
|
103
|
+
if (isBrowser()) {
|
|
35
104
|
request.cookies.forEach(cookie => {
|
|
36
105
|
document.cookie = `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`;
|
|
37
106
|
});
|
|
@@ -68,7 +137,10 @@ function constructRequest(har, userAgent = false) {
|
|
|
68
137
|
options.body = encodedParams.toString();
|
|
69
138
|
break;
|
|
70
139
|
|
|
140
|
+
case 'multipart/alternative':
|
|
71
141
|
case 'multipart/form-data':
|
|
142
|
+
case 'multipart/mixed':
|
|
143
|
+
case 'multipart/related':
|
|
72
144
|
// If there's a Content-Type header set remove it. We're doing this because when we pass the form data object
|
|
73
145
|
// into `fetch` that'll set a proper `Content-Type` header for this request that also includes the boundary
|
|
74
146
|
// used on the content.
|
|
@@ -79,59 +151,93 @@ function constructRequest(har, userAgent = false) {
|
|
|
79
151
|
headers.delete('Content-Type');
|
|
80
152
|
}
|
|
81
153
|
|
|
82
|
-
// The `form-data` NPM module returns one of two things: a native `FormData` API, or its own polyfill. Since
|
|
83
|
-
// the polyfill does not support the full API of the native FormData object, when you load `form-data` within
|
|
84
|
-
// a browser environment you'll have two major differences in API:
|
|
85
|
-
//
|
|
86
|
-
// * The `.append()` API in `form-data` requires that the third argument is an object containing various,
|
|
87
|
-
// undocumented, options. In the browser, `.append()`'s third argument should only be present when the
|
|
88
|
-
// second is a `Blob` or `USVString`, and when it is present, it should be a filename string.
|
|
89
|
-
// * `form-data` does not expose an `.entries()` API, so the only way to retrieve data out of it for
|
|
90
|
-
// construction of boundary-separated payload content is to use its `.pipe()` API. Since the browser
|
|
91
|
-
// doesn't have this API, you'll be unable to retrieve data out of it.
|
|
92
|
-
//
|
|
93
|
-
// Now since the native `FormData` API is iterable, and has the `.entries()` iterator, we can easily detect
|
|
94
|
-
// what version of the FormData API we have access to by looking for this and constructing a simple wrapper
|
|
95
|
-
// to disconnect some of this logic so you can work against a single, consistent API.
|
|
96
|
-
//
|
|
97
|
-
// Having to do this isn't fun, but it's the only way you can write code to work with `multipart/form-data`
|
|
98
|
-
// content under a server and browser.
|
|
99
154
|
const form = new FormData();
|
|
100
|
-
|
|
155
|
+
if (!isFormData(form)) {
|
|
156
|
+
// The `form-data` NPM module returns one of two things: a native `FormData` API or its own polyfill.
|
|
157
|
+
// Unfortunately this polyfill does not support the full API of the native FormData object so when you load
|
|
158
|
+
// `form-data` within a browser environment you'll have two major differences in API:
|
|
159
|
+
//
|
|
160
|
+
// * The `.append()` API in `form-data` requires that the third argument is an object containing various,
|
|
161
|
+
// undocumented, options. In the browser, `.append()`'s third argument should only be present when the
|
|
162
|
+
// second is a `Blob` or `USVString`, and when it is present, it should be a filename string.
|
|
163
|
+
// * `form-data` does not expose an `.entries()` API, so the only way to retrieve data out of it for
|
|
164
|
+
// construction of boundary-separated payload content is to use its `.pipe()` API. Since the browser
|
|
165
|
+
// doesn't have this API, you'll be unable to retrieve data out of it.
|
|
166
|
+
//
|
|
167
|
+
// Now since the native `FormData` API is iterable, and has the `.entries()` iterator, we can easily detect
|
|
168
|
+
// if we have a native copy of the FormData API. It's for all of these reasons that we're opting to hard
|
|
169
|
+
// crash here because supporting this non-compliant API is more trouble than its worth.
|
|
170
|
+
//
|
|
171
|
+
// https://github.com/form-data/form-data/issues/124
|
|
172
|
+
throw new Error(
|
|
173
|
+
"We've detected you're using a non-spec compliant FormData library. We recommend polyfilling FormData with https://npm.im/formdata-node"
|
|
174
|
+
);
|
|
175
|
+
}
|
|
101
176
|
|
|
102
177
|
request.postData.params.forEach(param => {
|
|
103
|
-
if ('fileName' in param && !('value' in param)) {
|
|
104
|
-
throw new Error(
|
|
105
|
-
"The supplied HAR has a postData parameter with `fileName`, but no `value` content. Since this library doesn't have access to the filesystem, it can't fetch that file."
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// If the incoming parameter is a file, and that files value is a data URL, we should decode that and set
|
|
110
|
-
// the contents of the value in the HAR to the actual contents of the file.
|
|
111
178
|
if ('fileName' in param) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
179
|
+
if (opts.files && param.fileName in opts.files) {
|
|
180
|
+
const fileContents = opts.files[param.fileName];
|
|
181
|
+
|
|
182
|
+
// If the file we've got available to us is a Buffer then we need to convert it so that the FormData
|
|
183
|
+
// API can use it.
|
|
184
|
+
if (isBuffer(fileContents)) {
|
|
185
|
+
form.set(
|
|
186
|
+
param.name,
|
|
187
|
+
new File([fileContents], param.fileName, {
|
|
188
|
+
type: param.contentType || null,
|
|
189
|
+
}),
|
|
190
|
+
param.fileName
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return;
|
|
194
|
+
} else if (isFile(fileContents)) {
|
|
195
|
+
form.set(param.name, fileContents, param.fileName);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new TypeError(
|
|
200
|
+
'An unknown object has been supplied into the `files` config for use. We only support instances of the File API and Node Buffer objects.'
|
|
201
|
+
);
|
|
202
|
+
} else if ('value' in param) {
|
|
203
|
+
let paramBlob;
|
|
204
|
+
const parsed = parseDataUrl(param.value);
|
|
205
|
+
if (parsed) {
|
|
206
|
+
// If we were able to parse out this data URL we don't need to transform its data into a buffer for
|
|
207
|
+
// `Blob` because that supports data URLs already.
|
|
208
|
+
paramBlob = new Blob([param.value], { type: parsed.contentType || param.contentType || null });
|
|
209
|
+
} else {
|
|
210
|
+
paramBlob = new Blob([param.value], { type: param.contentType || null });
|
|
211
|
+
}
|
|
118
212
|
|
|
119
|
-
if (isNativeFormData) {
|
|
120
|
-
if ('fileName' in param) {
|
|
121
|
-
const paramBlob = new Blob([param.value], { type: param.contentType || null });
|
|
122
213
|
form.append(param.name, paramBlob, param.fileName);
|
|
123
|
-
|
|
124
|
-
form.append(param.name, param.value);
|
|
214
|
+
return;
|
|
125
215
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
});
|
|
216
|
+
|
|
217
|
+
throw new Error(
|
|
218
|
+
"The supplied HAR has a postData parameter with `fileName`, but neither `value` content within the HAR or any file buffers were supplied with the `files` option. Since this library doesn't have access to the filesystem, it can't fetch that file."
|
|
219
|
+
);
|
|
131
220
|
}
|
|
221
|
+
|
|
222
|
+
form.append(param.name, param.value);
|
|
132
223
|
});
|
|
133
224
|
|
|
134
|
-
|
|
225
|
+
// If a the `fetch` polyfill that's being used here doesn't have spec-compliant handling for the `FormData`
|
|
226
|
+
// API (like `node-fetch@2`), then you should pass in a handler (like the `form-data-encoder` library) to
|
|
227
|
+
// transform its contents into something that can be used with the `Request` object.
|
|
228
|
+
//
|
|
229
|
+
// https://www.npmjs.com/package/formdata-node
|
|
230
|
+
if (opts.multipartEncoder) {
|
|
231
|
+
// eslint-disable-next-line new-cap
|
|
232
|
+
const encoder = new opts.multipartEncoder(form);
|
|
233
|
+
Object.keys(encoder.headers).forEach(header => {
|
|
234
|
+
headers.set(header, encoder.headers[header]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
options.body = Readable.from(encoder);
|
|
238
|
+
} else {
|
|
239
|
+
options.body = form;
|
|
240
|
+
}
|
|
135
241
|
break;
|
|
136
242
|
|
|
137
243
|
default:
|
|
@@ -148,27 +254,62 @@ function constructRequest(har, userAgent = false) {
|
|
|
148
254
|
|
|
149
255
|
options.body = JSON.stringify(formBody);
|
|
150
256
|
}
|
|
151
|
-
} else {
|
|
152
|
-
|
|
257
|
+
} else if (request.postData.text.length) {
|
|
258
|
+
// If we've got `files` map content present, and this post data content contains a valid data URL then we can
|
|
259
|
+
// substitute the payload with that file instead of the using data URL.
|
|
260
|
+
if (opts.files) {
|
|
261
|
+
const parsed = parseDataUrl(request.postData.text);
|
|
262
|
+
if (parsed && 'name' in parsed && parsed.name in opts.files) {
|
|
263
|
+
const fileContents = opts.files[parsed.name];
|
|
264
|
+
if (isBuffer(fileContents)) {
|
|
265
|
+
options.body = fileContents;
|
|
266
|
+
} else if (isFile(fileContents)) {
|
|
267
|
+
// `Readable.from` isn't available in browsers but the browser `Request` object can handle `File` objects
|
|
268
|
+
// just fine without us having to mold it into shape.
|
|
269
|
+
if (isBrowser()) {
|
|
270
|
+
options.body = fileContents;
|
|
271
|
+
} else {
|
|
272
|
+
options.body = Readable.from(fileContents.stream());
|
|
273
|
+
|
|
274
|
+
// Supplying a polyfilled `File` stream into `Request.body` doesn't automatically add `Content-Length`.
|
|
275
|
+
if (!headers.has('content-length')) {
|
|
276
|
+
headers.set('content-length', fileContents.size);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof options.body === 'undefined') {
|
|
284
|
+
options.body = request.postData.text;
|
|
285
|
+
}
|
|
153
286
|
}
|
|
154
287
|
}
|
|
155
288
|
|
|
289
|
+
// We automaticaly assume that the HAR that we have already has query parameters encoded within it so we do **not**
|
|
290
|
+
// use the `URLSearchParams` API here for composing the query string.
|
|
156
291
|
if ('queryString' in request && request.queryString.length) {
|
|
157
|
-
const
|
|
158
|
-
|
|
292
|
+
const urlObj = new URL(url);
|
|
293
|
+
|
|
294
|
+
const queryParams = Array.from(urlObj.searchParams).map(([k, v]) => `${k}=${v}`);
|
|
295
|
+
request.queryString.forEach(q => {
|
|
296
|
+
queryParams.push(`${q.name}=${q.value}`);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
querystring = queryParams.join('&');
|
|
159
300
|
}
|
|
160
301
|
|
|
161
|
-
if (userAgent) {
|
|
162
|
-
headers.append('User-Agent', userAgent);
|
|
302
|
+
if (opts.userAgent) {
|
|
303
|
+
headers.append('User-Agent', opts.userAgent);
|
|
163
304
|
}
|
|
164
305
|
|
|
165
306
|
options.headers = headers;
|
|
166
307
|
|
|
167
|
-
return new Request(`${url}${querystring}`, options);
|
|
308
|
+
return new Request(`${url.split('?')[0]}${querystring ? `?${querystring}` : ''}`, options);
|
|
168
309
|
}
|
|
169
310
|
|
|
170
|
-
function fetchHar(har, userAgent) {
|
|
171
|
-
return fetch(constructRequest(har,
|
|
311
|
+
function fetchHar(har, opts = { userAgent: false, files: false, multipartEncoder: false }) {
|
|
312
|
+
return fetch(constructRequest(har, opts));
|
|
172
313
|
}
|
|
173
314
|
|
|
174
315
|
module.exports = fetchHar;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fetch-har",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.1",
|
|
4
4
|
"description": "Make a fetch request from a HAR definition",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
"prettier": "prettier --list-different --write \"./**.js\"",
|
|
13
13
|
"release": "npx conventional-changelog-cli -i CHANGELOG.md -s",
|
|
14
14
|
"serve": "node __tests__/server.js",
|
|
15
|
-
"test": "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
15
|
+
"test:browser": "karma start --single-run",
|
|
16
|
+
"test:browser:chrome": "karma start --browsers=Chrome --single-run=false",
|
|
17
|
+
"test:browser:debug": "karma start --single-run=false",
|
|
18
|
+
"test": "nyc mocha"
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -27,37 +27,32 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/readmeio/fetch-har#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"parse-data-url": "^4.0.1"
|
|
30
|
+
"parse-data-url": "^4.0.1",
|
|
31
|
+
"readable-stream": "^3.6.0"
|
|
32
|
+
},
|
|
33
|
+
"optionalDependencies": {
|
|
34
|
+
"fromdata-node": "^4.3.2"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"eslint
|
|
38
|
-
"
|
|
37
|
+
"@jsdevtools/host-environment": "^2.1.2",
|
|
38
|
+
"@jsdevtools/karma-config": "^3.1.7",
|
|
39
|
+
"@readme/eslint-config": "^8.1.2",
|
|
40
|
+
"chai": "^4.3.4",
|
|
41
|
+
"eslint": "^8.7.0",
|
|
42
|
+
"eslint-plugin-compat": "^4.0.1",
|
|
43
|
+
"eslint-plugin-mocha": "^10.0.3",
|
|
39
44
|
"form-data": "^4.0.0",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
+
"form-data-encoder": "^1.7.1",
|
|
46
|
+
"formdata-node": "^4.3.2",
|
|
47
|
+
"har-examples": "^3.1.0",
|
|
48
|
+
"isomorphic-fetch": "^3.0.0",
|
|
49
|
+
"mocha": "^9.1.4",
|
|
45
50
|
"node-fetch": "^2.6.0",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"webpack-dev-middleware": "^5.1.0"
|
|
51
|
+
"nyc": "^15.1.0",
|
|
52
|
+
"prettier": "^2.5.1"
|
|
49
53
|
},
|
|
50
54
|
"browserslist": [
|
|
51
55
|
"last 2 versions"
|
|
52
56
|
],
|
|
53
|
-
"prettier": "@readme/eslint-config/prettier"
|
|
54
|
-
"jest": {
|
|
55
|
-
"preset": "jest-puppeteer",
|
|
56
|
-
"globals": {
|
|
57
|
-
"SERVER_URL": "http://localhost:4444"
|
|
58
|
-
},
|
|
59
|
-
"testMatch": [
|
|
60
|
-
"<rootDir>/__tests__/*.test.js"
|
|
61
|
-
]
|
|
62
|
-
}
|
|
57
|
+
"prettier": "@readme/eslint-config/prettier"
|
|
63
58
|
}
|