fetch-har 6.1.0 → 8.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 CHANGED
@@ -1,3 +1,53 @@
1
+ ## <small>8.0.1 (2022-05-03)</small>
2
+
3
+ * fix: making har-format types a regular dep ([93dcb7d](https://github.com/readmeio/fetch-har/commit/93dcb7d))
4
+ * chore: don't remove a `@types/` directory that we aren't using ([c915fae](https://github.com/readmeio/fetch-har/commit/c915fae))
5
+ * chore(deps-dev): bump @types/node from 17.0.29 to 17.0.30 (#269) ([e6f62f3](https://github.com/readmeio/fetch-har/commit/e6f62f3)), closes [#269](https://github.com/readmeio/fetch-har/issues/269)
6
+ * chore(deps-dev): bump ts-loader from 7.0.5 to 9.3.0 (#268) ([34877c5](https://github.com/readmeio/fetch-har/commit/34877c5)), closes [#268](https://github.com/readmeio/fetch-har/issues/268)
7
+ * chore(deps-dev): bump typescript from 4.6.3 to 4.6.4 (#267) ([826dcac](https://github.com/readmeio/fetch-har/commit/826dcac)), closes [#267](https://github.com/readmeio/fetch-har/issues/267)
8
+ * chore(deps): bump actions/cache from 3.0.1 to 3.0.2 (#270) ([55e45a6](https://github.com/readmeio/fetch-har/commit/55e45a6)), closes [#270](https://github.com/readmeio/fetch-har/issues/270)
9
+ * chore(deps): bump github/codeql-action from 1 to 2 (#271) ([e29ddce](https://github.com/readmeio/fetch-har/commit/e29ddce)), closes [#271](https://github.com/readmeio/fetch-har/issues/271)
10
+
11
+
12
+
13
+ ## 8.0.0 (2022-04-28)
14
+
15
+ > **BREAKING CHANGE**
16
+ >
17
+ > This library is now written in TypeScript and as a result the main export dist is now contained within a `default` object. If you load it into your codebase with `import` you don't need to do anything, but if you're using `require()` you'll need to do `require('fetch-har').default`.
18
+
19
+ * feat: adding test for common mocking libraries (#266) ([1cdb0bf](https://github.com/readmeio/fetch-har/commit/1cdb0bf)), closes [#266](https://github.com/readmeio/fetch-har/issues/266)
20
+ * feat: rewriting the library in typescript, adding support for node 18 (#265) ([209cb20](https://github.com/readmeio/fetch-har/commit/209cb20)), closes [#265](https://github.com/readmeio/fetch-har/issues/265)
21
+
22
+
23
+
24
+ ## 7.0.0 (2022-04-26)
25
+
26
+ > **BREAKING CHANGE**
27
+ >
28
+ > Node 12 is no longer supported with this release.
29
+
30
+ * chore: removing some docs from the repo now that they're in our .github/ repo ([86035f0](https://github.com/readmeio/fetch-har/commit/86035f0))
31
+ * chore(deps-dev): bump chai from 4.3.4 to 4.3.6 (#247) ([e32e3b1](https://github.com/readmeio/fetch-har/commit/e32e3b1)), closes [#247](https://github.com/readmeio/fetch-har/issues/247)
32
+ * chore(deps-dev): bump eslint from 8.11.0 to 8.12.0 (#258) ([86a2706](https://github.com/readmeio/fetch-har/commit/86a2706)), closes [#258](https://github.com/readmeio/fetch-har/issues/258)
33
+ * chore(deps-dev): bump eslint from 8.7.0 to 8.8.0 (#246) ([1dc64b6](https://github.com/readmeio/fetch-har/commit/1dc64b6)), closes [#246](https://github.com/readmeio/fetch-har/issues/246)
34
+ * chore(deps-dev): bump eslint-plugin-compat from 4.0.1 to 4.0.2 (#245) ([71626b1](https://github.com/readmeio/fetch-har/commit/71626b1)), closes [#245](https://github.com/readmeio/fetch-har/issues/245)
35
+ * chore(deps-dev): bump form-data-encoder from 1.7.1 to 1.7.2 (#262) ([c56238e](https://github.com/readmeio/fetch-har/commit/c56238e)), closes [#262](https://github.com/readmeio/fetch-har/issues/262)
36
+ * chore(deps-dev): bump mocha from 9.1.4 to 9.2.0 (#248) ([80b677e](https://github.com/readmeio/fetch-har/commit/80b677e)), closes [#248](https://github.com/readmeio/fetch-har/issues/248)
37
+ * chore(deps-dev): bump prettier from 2.6.0 to 2.6.1 (#261) ([74022ed](https://github.com/readmeio/fetch-har/commit/74022ed)), closes [#261](https://github.com/readmeio/fetch-har/issues/261)
38
+ * chore(deps-dev): bumping out of date dev deps ([9c6c07f](https://github.com/readmeio/fetch-har/commit/9c6c07f))
39
+ * chore(deps-dev): bumping out of date dev deps (#256) ([f85da13](https://github.com/readmeio/fetch-har/commit/f85da13)), closes [#256](https://github.com/readmeio/fetch-har/issues/256)
40
+ * chore(deps-dev): removing eslint-plugin-mocha as a hard devdep (#244) ([e21fc15](https://github.com/readmeio/fetch-har/commit/e21fc15)), closes [#244](https://github.com/readmeio/fetch-har/issues/244)
41
+ * chore(deps-dev): upgrading our eslint config to pull in the new mocha config (#243) ([79c4348](https://github.com/readmeio/fetch-har/commit/79c4348)), closes [#243](https://github.com/readmeio/fetch-har/issues/243)
42
+ * chore(deps): bump actions/cache from 2 to 3.0.1 (#259) ([68f506b](https://github.com/readmeio/fetch-har/commit/68f506b)), closes [#259](https://github.com/readmeio/fetch-har/issues/259)
43
+ * chore(deps): bump actions/checkout from 2 to 3 (#260) ([08a76a0](https://github.com/readmeio/fetch-har/commit/08a76a0)), closes [#260](https://github.com/readmeio/fetch-har/issues/260)
44
+ * chore(deps): bump follow-redirects from 1.14.7 to 1.14.8 (#250) ([239b437](https://github.com/readmeio/fetch-har/commit/239b437)), closes [#250](https://github.com/readmeio/fetch-har/issues/250)
45
+ * chore(deps): bump minimist from 1.2.5 to 1.2.6 (#257) ([b29ec68](https://github.com/readmeio/fetch-har/commit/b29ec68)), closes [#257](https://github.com/readmeio/fetch-har/issues/257)
46
+ * feat: adding support for node 18 (#263) ([c83dd79](https://github.com/readmeio/fetch-har/commit/c83dd79)), closes [#263](https://github.com/readmeio/fetch-har/issues/263)
47
+ * docs: fixing a typo ([c247ceb](https://github.com/readmeio/fetch-har/commit/c247ceb))
48
+
49
+
50
+
1
51
  ## 6.1.0 (2022-01-27)
2
52
 
3
53
  * feat: automatically polyfilling FormData with formdata-node if present (#241) ([da94dfc](https://github.com/readmeio/fetch-har/commit/da94dfc)), closes [#241](https://github.com/readmeio/fetch-har/issues/241)
package/README.md CHANGED
@@ -1,10 +1,19 @@
1
1
  # fetch-har
2
- [![CI](https://github.com/readmeio/fetch-har/workflows/CI/badge.svg)](https://github.com/readmeio/fetch-har)
3
-
4
2
  Make a [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) request from a HAR definition.
5
3
 
4
+ [![CI](https://github.com/readmeio/fetch-har/workflows/CI/badge.svg)](https://github.com/readmeio/fetch-har/)
5
+ [![](https://img.shields.io/npm/v/fetch-har)](https://npm.im/fetch-har)
6
+ [![License](https://img.shields.io/npm/l/fetch-har.svg)](LICENSE)
7
+
6
8
  [![](https://d3vv6lp55qjaqc.cloudfront.net/items/1M3C3j0I0s0j3T362344/Untitled-2.png)](https://readme.io)
7
9
 
10
+ ## Features
11
+
12
+ - Supports Node 14+ (including the native `fetch` implementation in Node 18!).
13
+ - Natively works in all browsers that support [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) without having to use any polyfils.
14
+ - [Tested](https://github.com/readmeio/fetch-har/actions) across Chrome, Safari, Firefox on Mac, Windows, and Linux.
15
+ - Requests can be mocked with [nock](https://npm.im/nock) or [fetch-mock](https://npm.im/fetch-mock).
16
+
8
17
  ## Installation
9
18
 
10
19
  ```
@@ -15,10 +24,11 @@ npm install --save fetch-har
15
24
  ```js
16
25
  require('isomorphic-fetch');
17
26
 
18
- // If executing from an environment that dodoesn't normally provide `fetch()`
19
- // we'll automatically polyfill in the `Blob`, `File`, and `FormData` APIs
20
- // with the optional `formdata-node` package (provided you've installed it).
21
- const fetchHar = require('.');
27
+ // If executing from an environment that doesn't normally provide `fetch()` we'll automatically
28
+ // polyfill in the `Blob`, `File`, and `FormData` APIs with the optional `formdata-node` package
29
+ // (provided you've installed it).
30
+ const fetchHAR = require('fetch-har').default;
31
+ // import fetchHAR from 'fetch-har'); // Or if you're in an ESM codebase.
22
32
 
23
33
  const har = {
24
34
  log: {
@@ -51,7 +61,7 @@ const har = {
51
61
  },
52
62
  };
53
63
 
54
- fetchHar(har)
64
+ fetchHAR(har)
55
65
  .then(res => res.json())
56
66
  .then(console.log);
57
67
  ```
@@ -68,14 +78,14 @@ Though we recommend either [formdata-node](https://npm.im/formdata-node) or [for
68
78
  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.
69
79
 
70
80
  ```js
71
- await fetchHar(har, { userAgent: 'my-client/1.0' });
81
+ await fetchHAR(har, { userAgent: 'my-client/1.0' });
72
82
  ```
73
83
 
74
84
  ##### files
75
85
  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.
76
86
 
77
87
  ```js
78
- await fetchHar(har, { files: {
88
+ await fetchHAR(har, { files: {
79
89
  'owlbert.png': await fs.readFile('./owlbert.png'),
80
90
  'file.txt': document.querySelector('#some-file-input').files[0],
81
91
  } });
@@ -93,7 +103,22 @@ We recommend [form-data-encoder](https://npm.im/form-data-encoder).
93
103
  ```js
94
104
  const { FormDataEncoder } = require('form-data-encoder');
95
105
 
96
- await fetchHar(har, { multipartEncoder: FormDataEncoder });
106
+ await fetchHAR(har, { multipartEncoder: FormDataEncoder });
97
107
  ```
98
108
 
99
109
  You do **not**, and shouldn't, need to use this option in browser environments.
110
+
111
+ ##### init
112
+ This optional argument lets you supply any option that's available to supply to the [Request constructor](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request).
113
+
114
+ ```js
115
+ await fetchHAR(har, {
116
+ init: {
117
+ headers: new Headers({
118
+ 'x-custom-header': 'buster',
119
+ }),
120
+ },
121
+ })
122
+ ```
123
+
124
+ > ❗ Note that if you supply `body` or `credentials` to this option they may be overridden by what your HAR requires.
@@ -0,0 +1,10 @@
1
+ /// <reference types="node" />
2
+ import type { Har } from 'har-format';
3
+ declare type FetchHAROptions = {
4
+ userAgent?: string;
5
+ files?: Record<string, Blob | Buffer>;
6
+ multipartEncoder?: any;
7
+ init?: RequestInit;
8
+ };
9
+ export default function fetchHAR(har: Har, opts?: FetchHAROptions): Promise<Response>;
10
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ exports.__esModule = true;
17
+ var readable_stream_1 = require("readable-stream");
18
+ var parse_data_url_1 = __importDefault(require("parse-data-url"));
19
+ if (!globalThis.Blob) {
20
+ try {
21
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
22
+ globalThis.Blob = require('formdata-node').Blob;
23
+ }
24
+ catch (e) {
25
+ throw new Error('Since you do not have the Blob API available in this environment you must install the optional `formdata-node` dependency.');
26
+ }
27
+ }
28
+ if (!globalThis.File) {
29
+ try {
30
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
31
+ globalThis.File = require('formdata-node').File;
32
+ }
33
+ catch (e) {
34
+ throw new Error('Since you do not have the File API available in this environment you must install the optional `formdata-node` dependency.');
35
+ }
36
+ }
37
+ if (!globalThis.FormData) {
38
+ try {
39
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
40
+ globalThis.FormData = require('formdata-node').FormData;
41
+ }
42
+ catch (e) {
43
+ throw new Error('Since you do not have the FormData API available in this environment you must install the optional `formdata-node` dependency.');
44
+ }
45
+ }
46
+ function isBrowser() {
47
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
48
+ }
49
+ function isBuffer(value) {
50
+ return typeof Buffer !== 'undefined' && Buffer.isBuffer(value);
51
+ }
52
+ function isFile(value) {
53
+ if (value instanceof File) {
54
+ // The `Blob` polyfill on Node comes back as being an instanceof `File`. Because passing a Blob into
55
+ // a File will end up with a corrupted file we want to prevent this.
56
+ //
57
+ // This object identity crisis does not happen in the browser.
58
+ return value.constructor.name === 'File';
59
+ }
60
+ return false;
61
+ }
62
+ /**
63
+ * @license MIT
64
+ * @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFunction.ts}
65
+ */
66
+ function isFunction(value) {
67
+ return typeof value === 'function';
68
+ }
69
+ /**
70
+ * We're loading this library in here instead of loading it from `form-data-encoder` because that uses lookbehind
71
+ * regex in its main encoder that Safari doesn't support so it throws a fatal page exception.
72
+ *
73
+ * @license MIT
74
+ * @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFormData.ts}
75
+ */
76
+ function isFormData(value) {
77
+ return (value &&
78
+ isFunction(value.constructor) &&
79
+ value[Symbol.toStringTag] === 'FormData' &&
80
+ isFunction(value.append) &&
81
+ isFunction(value.getAll) &&
82
+ isFunction(value.entries) &&
83
+ isFunction(value[Symbol.iterator]));
84
+ }
85
+ function fetchHAR(har, opts) {
86
+ if (opts === void 0) { opts = {}; }
87
+ if (!har)
88
+ throw new Error('Missing HAR definition');
89
+ if (!har.log || !har.log.entries || !har.log.entries.length)
90
+ throw new Error('Missing log.entries array');
91
+ var request = har.log.entries[0].request;
92
+ var url = request.url;
93
+ var querystring = '';
94
+ var options = __assign(__assign({}, (opts.init ? opts.init : {})), { method: request.method });
95
+ if (!options.headers) {
96
+ options.headers = new Headers();
97
+ }
98
+ else if (typeof options.headers === 'object' && !(options.headers instanceof Headers) && options.headers !== null) {
99
+ options.headers = new Headers(options.headers);
100
+ }
101
+ var headers = options.headers;
102
+ if ('headers' in request && request.headers.length) {
103
+ // eslint-disable-next-line consistent-return
104
+ request.headers.forEach(function (header) {
105
+ try {
106
+ return headers.append(header.name, header.value);
107
+ }
108
+ catch (err) {
109
+ // `Headers.append()` will throw errors if the header name is not a legal HTTP header name, like
110
+ // `X-API-KEY (Header)`. If that happens instead of tossing the error back out, we should silently just ignore
111
+ // it.
112
+ }
113
+ });
114
+ }
115
+ if ('cookies' in request && request.cookies.length) {
116
+ // As the browser fetch API can't set custom cookies for requests, they instead need to be defined on the document
117
+ // and passed into the request via `credentials: include`. Since this is a browser-specific quirk, that should only
118
+ // happen in browsers!
119
+ if (isBrowser()) {
120
+ request.cookies.forEach(function (cookie) {
121
+ document.cookie = "".concat(encodeURIComponent(cookie.name), "=").concat(encodeURIComponent(cookie.value));
122
+ });
123
+ options.credentials = 'include';
124
+ }
125
+ else {
126
+ headers.append('cookie', request.cookies
127
+ .map(function (cookie) { return "".concat(encodeURIComponent(cookie.name), "=").concat(encodeURIComponent(cookie.value)); })
128
+ .join('; '));
129
+ }
130
+ }
131
+ if ('postData' in request) {
132
+ if ('params' in request.postData) {
133
+ if (!('mimeType' in request.postData)) {
134
+ // @ts-expect-error HAR spec requires that `mimeType` is always present but it might not be.
135
+ request.postData.mimeType = 'application/octet-stream';
136
+ }
137
+ switch (request.postData.mimeType) {
138
+ case 'application/x-www-form-urlencoded':
139
+ // Since the content we're handling here is to be encoded as `application/x-www-form-urlencoded`, this should
140
+ // override any other Content-Type headers that are present in the HAR. This is how Postman handles this case
141
+ // when building code snippets!
142
+ //
143
+ // https://github.com/github/fetch/issues/263#issuecomment-209530977
144
+ headers.set('Content-Type', request.postData.mimeType);
145
+ var encodedParams_1 = new URLSearchParams();
146
+ request.postData.params.forEach(function (param) { return encodedParams_1.set(param.name, param.value); });
147
+ options.body = encodedParams_1.toString();
148
+ break;
149
+ case 'multipart/alternative':
150
+ case 'multipart/form-data':
151
+ case 'multipart/mixed':
152
+ case 'multipart/related':
153
+ // If there's a Content-Type header set remove it. We're doing this because when we pass the form data object
154
+ // into `fetch` that'll set a proper `Content-Type` header for this request that also includes the boundary
155
+ // used on the content.
156
+ //
157
+ // If we don't do this, then consumers won't be able to parse out the payload because they won't know what
158
+ // the boundary to split on it.
159
+ if (headers.has('Content-Type')) {
160
+ headers["delete"]('Content-Type');
161
+ }
162
+ var form_1 = new FormData();
163
+ if (!isFormData(form_1)) {
164
+ // The `form-data` NPM module returns one of two things: a native `FormData` API or its own polyfill.
165
+ // Unfortunately this polyfill does not support the full API of the native FormData object so when you load
166
+ // `form-data` within a browser environment you'll have two major differences in API:
167
+ //
168
+ // * The `.append()` API in `form-data` requires that the third argument is an object containing various,
169
+ // undocumented, options. In the browser, `.append()`'s third argument should only be present when the
170
+ // second is a `Blob` or `USVString`, and when it is present, it should be a filename string.
171
+ // * `form-data` does not expose an `.entries()` API, so the only way to retrieve data out of it for
172
+ // construction of boundary-separated payload content is to use its `.pipe()` API. Since the browser
173
+ // doesn't have this API, you'll be unable to retrieve data out of it.
174
+ //
175
+ // Now since the native `FormData` API is iterable, and has the `.entries()` iterator, we can easily detect
176
+ // if we have a native copy of the FormData API. It's for all of these reasons that we're opting to hard
177
+ // crash here because supporting this non-compliant API is more trouble than its worth.
178
+ //
179
+ // https://github.com/form-data/form-data/issues/124
180
+ 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");
181
+ }
182
+ request.postData.params.forEach(function (param) {
183
+ if ('fileName' in param) {
184
+ if (opts.files && param.fileName in opts.files) {
185
+ var fileContents = opts.files[param.fileName];
186
+ // If the file we've got available to us is a Buffer then we need to convert it so that the FormData
187
+ // API can use it.
188
+ if (isBuffer(fileContents)) {
189
+ form_1.set(param.name, new File([fileContents], param.fileName, {
190
+ type: param.contentType || null
191
+ }), param.fileName);
192
+ return;
193
+ }
194
+ else if (isFile(fileContents)) {
195
+ form_1.set(param.name, fileContents, param.fileName);
196
+ return;
197
+ }
198
+ 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.');
199
+ }
200
+ else if ('value' in param) {
201
+ var paramBlob = void 0;
202
+ var parsed = (0, parse_data_url_1["default"])(param.value);
203
+ if (parsed) {
204
+ // If we were able to parse out this data URL we don't need to transform its data into a buffer for
205
+ // `Blob` because that supports data URLs already.
206
+ paramBlob = new Blob([param.value], { type: parsed.contentType || param.contentType || null });
207
+ }
208
+ else {
209
+ paramBlob = new Blob([param.value], { type: param.contentType || null });
210
+ }
211
+ form_1.append(param.name, paramBlob, param.fileName);
212
+ return;
213
+ }
214
+ throw new Error("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.");
215
+ }
216
+ form_1.append(param.name, param.value);
217
+ });
218
+ // If a the `fetch` polyfill that's being used here doesn't have spec-compliant handling for the `FormData`
219
+ // API (like `node-fetch@2`), then you should pass in a handler (like the `form-data-encoder` library) to
220
+ // transform its contents into something that can be used with the `Request` object.
221
+ //
222
+ // https://www.npmjs.com/package/formdata-node
223
+ if (opts.multipartEncoder) {
224
+ // eslint-disable-next-line new-cap
225
+ var encoder_1 = new opts.multipartEncoder(form_1);
226
+ Object.keys(encoder_1.headers).forEach(function (header) {
227
+ headers.set(header, encoder_1.headers[header]);
228
+ });
229
+ // @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
230
+ options.body = readable_stream_1.Readable.from(encoder_1);
231
+ }
232
+ else {
233
+ options.body = form_1;
234
+ }
235
+ break;
236
+ default:
237
+ var formBody_1 = {};
238
+ request.postData.params.map(function (param) {
239
+ try {
240
+ formBody_1[param.name] = JSON.parse(param.value);
241
+ }
242
+ catch (e) {
243
+ formBody_1[param.name] = param.value;
244
+ }
245
+ return true;
246
+ });
247
+ options.body = JSON.stringify(formBody_1);
248
+ }
249
+ }
250
+ else if (request.postData.text.length) {
251
+ // If we've got `files` map content present, and this post data content contains a valid data URL then we can
252
+ // substitute the payload with that file instead of the using data URL.
253
+ if (opts.files) {
254
+ var parsed = (0, parse_data_url_1["default"])(request.postData.text);
255
+ if (parsed) {
256
+ if ((parsed === null || parsed === void 0 ? void 0 : parsed.name) && parsed.name in opts.files) {
257
+ var fileContents = opts.files[parsed.name];
258
+ if (isBuffer(fileContents)) {
259
+ options.body = fileContents;
260
+ }
261
+ else if (isFile(fileContents)) {
262
+ // `Readable.from` isn't available in browsers but the browser `Request` object can handle `File` objects
263
+ // just fine without us having to mold it into shape.
264
+ if (isBrowser()) {
265
+ options.body = fileContents;
266
+ }
267
+ else {
268
+ // @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
269
+ options.body = readable_stream_1.Readable.from(fileContents.stream());
270
+ // Supplying a polyfilled `File` stream into `Request.body` doesn't automatically add `Content-Length`.
271
+ if (!headers.has('content-length')) {
272
+ headers.set('content-length', String(fileContents.size));
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+ }
279
+ if (typeof options.body === 'undefined') {
280
+ options.body = request.postData.text;
281
+ }
282
+ }
283
+ }
284
+ // We automaticaly assume that the HAR that we have already has query parameters encoded within it so we do **not**
285
+ // use the `URLSearchParams` API here for composing the query string.
286
+ if ('queryString' in request && request.queryString.length) {
287
+ var urlObj = new URL(url);
288
+ var queryParams_1 = Array.from(urlObj.searchParams).map(function (_a) {
289
+ var k = _a[0], v = _a[1];
290
+ return "".concat(k, "=").concat(v);
291
+ });
292
+ request.queryString.forEach(function (q) {
293
+ queryParams_1.push("".concat(q.name, "=").concat(q.value));
294
+ });
295
+ querystring = queryParams_1.join('&');
296
+ }
297
+ if (opts.userAgent) {
298
+ headers.append('User-Agent', opts.userAgent);
299
+ }
300
+ options.headers = headers;
301
+ return fetch("".concat(url.split('?')[0]).concat(querystring ? "?".concat(querystring) : ''), options);
302
+ }
303
+ exports["default"] = fetchHAR;
package/example.js CHANGED
@@ -1,10 +1,9 @@
1
- /* eslint-disable import/no-extraneous-dependencies, no-console */
2
1
  require('isomorphic-fetch');
3
2
 
4
- // If executing from an environment that dodoesn't normally provide `fetch()`
3
+ // If executing from an environment that doesn't normally provide `fetch()`
5
4
  // we'll automatically polyfill in the `Blob`, `File`, and `FormData` APIs
6
5
  // with the optional `formdata-node` package (provided you've installed it).
7
- const fetchHar = require('.');
6
+ const fetchHAR = require('.').default;
8
7
 
9
8
  const har = {
10
9
  log: {
@@ -37,6 +36,6 @@ const har = {
37
36
  },
38
37
  };
39
38
 
40
- fetchHar(har)
39
+ fetchHAR(har)
41
40
  .then(res => res.json())
42
41
  .then(console.log);
package/package.json CHANGED
@@ -1,21 +1,24 @@
1
1
  {
2
2
  "name": "fetch-har",
3
- "version": "6.1.0",
3
+ "version": "8.0.1",
4
4
  "description": "Make a fetch request from a HAR definition",
5
- "main": "index.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
6
7
  "engines": {
7
- "node": "^12 || ^14 || ^16"
8
+ "node": ">=14"
8
9
  },
9
10
  "scripts": {
10
- "lint": "eslint .",
11
+ "build": "tsc",
12
+ "lint": "eslint . --ext .js,.ts",
13
+ "prebuild": "rm -rf dist/",
14
+ "prepack": "npm run build",
11
15
  "pretest": "npm run lint",
12
- "prettier": "prettier --list-different --write \"./**.js\"",
16
+ "prettier": "prettier --list-different --write \"./**/**.{js,ts}\"",
13
17
  "release": "npx conventional-changelog-cli -i CHANGELOG.md -s",
14
- "serve": "node __tests__/server.js",
15
18
  "test:browser": "karma start --single-run",
16
19
  "test:browser:chrome": "karma start --browsers=Chrome --single-run=false",
17
20
  "test:browser:debug": "karma start --single-run=false",
18
- "test": "nyc mocha"
21
+ "test": "nyc mocha \"test/**/*.test.ts\""
19
22
  },
20
23
  "repository": {
21
24
  "type": "git",
@@ -27,6 +30,7 @@
27
30
  },
28
31
  "homepage": "https://github.com/readmeio/fetch-har#readme",
29
32
  "dependencies": {
33
+ "@types/har-format": "^1.2.8",
30
34
  "parse-data-url": "^4.0.1",
31
35
  "readable-stream": "^3.6.0"
32
36
  },
@@ -36,20 +40,29 @@
36
40
  "devDependencies": {
37
41
  "@jsdevtools/host-environment": "^2.1.2",
38
42
  "@jsdevtools/karma-config": "^3.1.7",
39
- "@readme/eslint-config": "^8.1.2",
43
+ "@readme/eslint-config": "^8.7.3",
44
+ "@types/chai": "^4.3.1",
45
+ "@types/mocha": "^9.1.1",
46
+ "@types/node": "^17.0.29",
47
+ "@types/parse-data-url": "^3.0.0",
48
+ "@types/readable-stream": "^2.3.13",
40
49
  "chai": "^4.3.4",
41
- "eslint": "^8.7.0",
42
- "eslint-plugin-compat": "^4.0.1",
43
- "eslint-plugin-mocha": "^10.0.3",
50
+ "eslint": "^8.14.0",
51
+ "fetch-mock": "^9.11.0",
44
52
  "form-data": "^4.0.0",
45
53
  "form-data-encoder": "^1.7.1",
46
54
  "formdata-node": "^4.3.2",
47
- "har-examples": "^3.1.0",
55
+ "har-examples": "^3.1.1",
48
56
  "isomorphic-fetch": "^3.0.0",
49
- "mocha": "^9.1.4",
57
+ "mocha": "^9.2.2",
58
+ "nock": "^13.2.4",
50
59
  "node-fetch": "^2.6.0",
51
60
  "nyc": "^15.1.0",
52
- "prettier": "^2.5.1"
61
+ "prettier": "^2.6.2",
62
+ "ts-loader": "^9.3.0",
63
+ "ts-node": "^10.7.0",
64
+ "typescript": "^4.6.3",
65
+ "undici": "^5.0.0"
53
66
  },
54
67
  "browserslist": [
55
68
  "last 2 versions"
@@ -1,9 +1,10 @@
1
- const { Readable } = require('readable-stream');
2
- const parseDataUrl = require('parse-data-url');
1
+ import type { Har } from 'har-format';
2
+ import { Readable } from 'readable-stream';
3
+ import parseDataUrl from 'parse-data-url';
3
4
 
4
5
  if (!globalThis.Blob) {
5
6
  try {
6
- // eslint-disable-next-line import/no-extraneous-dependencies
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
7
8
  globalThis.Blob = require('formdata-node').Blob;
8
9
  } catch (e) {
9
10
  throw new Error(
@@ -14,7 +15,7 @@ if (!globalThis.Blob) {
14
15
 
15
16
  if (!globalThis.File) {
16
17
  try {
17
- // eslint-disable-next-line import/no-extraneous-dependencies
18
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
18
19
  globalThis.File = require('formdata-node').File;
19
20
  } catch (e) {
20
21
  throw new Error(
@@ -25,7 +26,7 @@ if (!globalThis.File) {
25
26
 
26
27
  if (!globalThis.FormData) {
27
28
  try {
28
- // eslint-disable-next-line import/no-extraneous-dependencies
29
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
29
30
  globalThis.FormData = require('formdata-node').FormData;
30
31
  } catch (e) {
31
32
  throw new Error(
@@ -38,11 +39,11 @@ function isBrowser() {
38
39
  return typeof window !== 'undefined' && typeof document !== 'undefined';
39
40
  }
40
41
 
41
- function isBuffer(value) {
42
+ function isBuffer(value: any) {
42
43
  return typeof Buffer !== 'undefined' && Buffer.isBuffer(value);
43
44
  }
44
45
 
45
- function isFile(value) {
46
+ function isFile(value: any) {
46
47
  if (value instanceof File) {
47
48
  // The `Blob` polyfill on Node comes back as being an instanceof `File`. Because passing a Blob into
48
49
  // a File will end up with a corrupted file we want to prevent this.
@@ -58,7 +59,7 @@ function isFile(value) {
58
59
  * @license MIT
59
60
  * @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFunction.ts}
60
61
  */
61
- function isFunction(value) {
62
+ function isFunction(value: any) {
62
63
  return typeof value === 'function';
63
64
  }
64
65
 
@@ -69,19 +70,32 @@ function isFunction(value) {
69
70
  * @license MIT
70
71
  * @see {@link https://github.com/octet-stream/form-data-encoder/blob/master/lib/util/isFormData.ts}
71
72
  */
72
- function isFormData(value) {
73
+ function isFormData(value: any) {
73
74
  return (
74
75
  value &&
75
76
  isFunction(value.constructor) &&
76
- value[Symbol.toStringTag] === 'FormData' && // eslint-disable-line compat/compat
77
+ value[Symbol.toStringTag] === 'FormData' &&
77
78
  isFunction(value.append) &&
78
79
  isFunction(value.getAll) &&
79
80
  isFunction(value.entries) &&
80
- isFunction(value[Symbol.iterator]) // eslint-disable-line compat/compat
81
+ isFunction(value[Symbol.iterator])
81
82
  );
82
83
  }
83
84
 
84
- function constructRequest(har, opts = { userAgent: false, files: false, multipartEncoder: false }) {
85
+ type FetchHAROptions = {
86
+ userAgent?: string;
87
+ files?: Record<string, Blob | Buffer>;
88
+ multipartEncoder?: any; // form-data-encoder
89
+ init?: RequestInit;
90
+ };
91
+
92
+ type DataURL = parseDataUrl.DataUrl & {
93
+ // `parse-data-url` doesn't explicitly support `name` in data URLs but if it's there it'll be
94
+ // returned back to us.
95
+ name?: string;
96
+ };
97
+
98
+ export default function fetchHAR(har: Har, opts: FetchHAROptions = {}) {
85
99
  if (!har) throw new Error('Missing HAR definition');
86
100
  if (!har.log || !har.log.entries || !har.log.entries.length) throw new Error('Missing log.entries array');
87
101
 
@@ -89,11 +103,20 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
89
103
  const { url } = request;
90
104
  let querystring = '';
91
105
 
92
- const headers = new Headers();
93
- const options = {
106
+ const options: RequestInit = {
107
+ // If we have custom options for the `Request` API we need to add them in here now before we
108
+ // fill it in with everything we need from the HAR.
109
+ ...(opts.init ? opts.init : {}),
94
110
  method: request.method,
95
111
  };
96
112
 
113
+ if (!options.headers) {
114
+ options.headers = new Headers();
115
+ } else if (typeof options.headers === 'object' && !(options.headers instanceof Headers) && options.headers !== null) {
116
+ options.headers = new Headers(options.headers);
117
+ }
118
+
119
+ const headers = options.headers as Headers;
97
120
  if ('headers' in request && request.headers.length) {
98
121
  // eslint-disable-next-line consistent-return
99
122
  request.headers.forEach(header => {
@@ -130,6 +153,7 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
130
153
  if ('postData' in request) {
131
154
  if ('params' in request.postData) {
132
155
  if (!('mimeType' in request.postData)) {
156
+ // @ts-expect-error HAR spec requires that `mimeType` is always present but it might not be.
133
157
  request.postData.mimeType = 'application/octet-stream';
134
158
  }
135
159
 
@@ -203,7 +227,7 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
203
227
 
204
228
  return;
205
229
  } else if (isFile(fileContents)) {
206
- form.set(param.name, fileContents, param.fileName);
230
+ form.set(param.name, fileContents as Blob, param.fileName);
207
231
  return;
208
232
  }
209
233
 
@@ -245,6 +269,7 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
245
269
  headers.set(header, encoder.headers[header]);
246
270
  });
247
271
 
272
+ // @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
248
273
  options.body = Readable.from(encoder);
249
274
  } else {
250
275
  options.body = form;
@@ -252,7 +277,7 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
252
277
  break;
253
278
 
254
279
  default:
255
- const formBody = {};
280
+ const formBody: Record<string, unknown> = {};
256
281
  request.postData.params.map(param => {
257
282
  try {
258
283
  formBody[param.name] = JSON.parse(param.value);
@@ -269,22 +294,25 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
269
294
  // If we've got `files` map content present, and this post data content contains a valid data URL then we can
270
295
  // substitute the payload with that file instead of the using data URL.
271
296
  if (opts.files) {
272
- const parsed = parseDataUrl(request.postData.text);
273
- if (parsed && 'name' in parsed && parsed.name in opts.files) {
274
- const fileContents = opts.files[parsed.name];
275
- if (isBuffer(fileContents)) {
276
- options.body = fileContents;
277
- } else if (isFile(fileContents)) {
278
- // `Readable.from` isn't available in browsers but the browser `Request` object can handle `File` objects
279
- // just fine without us having to mold it into shape.
280
- if (isBrowser()) {
297
+ const parsed = parseDataUrl(request.postData.text) as DataURL;
298
+ if (parsed) {
299
+ if (parsed?.name && parsed.name in opts.files) {
300
+ const fileContents = opts.files[parsed.name];
301
+ if (isBuffer(fileContents)) {
281
302
  options.body = fileContents;
282
- } else {
283
- options.body = Readable.from(fileContents.stream());
284
-
285
- // Supplying a polyfilled `File` stream into `Request.body` doesn't automatically add `Content-Length`.
286
- if (!headers.has('content-length')) {
287
- headers.set('content-length', fileContents.size);
303
+ } else if (isFile(fileContents)) {
304
+ // `Readable.from` isn't available in browsers but the browser `Request` object can handle `File` objects
305
+ // just fine without us having to mold it into shape.
306
+ if (isBrowser()) {
307
+ options.body = fileContents;
308
+ } else {
309
+ // @ts-expect-error "Property 'from' does not exist on type 'typeof Readable'." but it does!
310
+ options.body = Readable.from((fileContents as File).stream());
311
+
312
+ // Supplying a polyfilled `File` stream into `Request.body` doesn't automatically add `Content-Length`.
313
+ if (!headers.has('content-length')) {
314
+ headers.set('content-length', String((fileContents as File).size));
315
+ }
288
316
  }
289
317
  }
290
318
  }
@@ -316,12 +344,5 @@ function constructRequest(har, opts = { userAgent: false, files: false, multipar
316
344
 
317
345
  options.headers = headers;
318
346
 
319
- return new Request(`${url.split('?')[0]}${querystring ? `?${querystring}` : ''}`, options);
320
- }
321
-
322
- function fetchHar(har, opts = { userAgent: false, files: false, multipartEncoder: false }) {
323
- return fetch(constructRequest(har, opts));
347
+ return fetch(`${url.split('?')[0]}${querystring ? `?${querystring}` : ''}`, options);
324
348
  }
325
-
326
- module.exports = fetchHar;
327
- module.exports.constructRequest = constructRequest;
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "baseUrl": "./src",
5
+ "declaration": true,
6
+ "esModuleInterop": true,
7
+ "lib": ["dom", "es2020"],
8
+ "noImplicitAny": true,
9
+ "outDir": "dist/"
10
+ },
11
+ "include": ["./src/**/*"]
12
+ }
@@ -1,128 +0,0 @@
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/SECURITY.md DELETED
@@ -1,12 +0,0 @@
1
- # Security Policy
2
-
3
- ## Reporting a Vulnerability
4
-
5
- If there are any vulnerabilities in `fetch-har`, don't hesitate to _report them_.
6
-
7
- Please email security@readme.io and describe what you've found.
8
-
9
- - If you have a fix, explain or attach it.
10
- - In the near time, expect a reply with the required steps. Also, there may be a demand for a pull request which include the fixes.
11
-
12
- > You should not disclose the vulnerability publicly if you haven't received an answer in some weeks. If the vulnerability is rejected, you may post it publicly within some hour of rejection, unless the rejection is withdrawn within that time period. After the vulnerability has been fixed, you may disclose the vulnerability details publicly over some days.