Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement form-data encoding #603

Merged
merged 177 commits into from Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
177 commits
Select commit Hold shift + click to select a range
8b4f4aa
Add minimal formdata-node support.
octet-stream Apr 8, 2019
45fde57
Fix for string literal in formdata-node checks.
octet-stream Apr 8, 2019
92311a7
Add an example of formdata-node usage.
octet-stream Apr 9, 2019
b4d11ff
Fix imprort of the stream.Readable.
octet-stream Apr 9, 2019
f947a9f
feat: Migrate TypeScript types (#669)
Richienb Sep 7, 2019
43b2aa4
style: Introduce linting via XO
Richienb Sep 7, 2019
8607f96
fix: Fix tests
Richienb Sep 7, 2019
155ef26
chore!: Drop support for nodejs 4 and 6
Richienb Sep 7, 2019
939b3bc
chore: Fix Travis CI yml
Richienb Sep 7, 2019
dfc7d24
Use old Babel (needs migration)
xxczaki Sep 7, 2019
684f781
chore: lint everything
xxczaki Sep 7, 2019
6c894f1
Merge branch '3.x' of https://github.com/bitinn/node-fetch into 3.x
xxczaki Sep 7, 2019
d68d0ef
chore: Migrate to microbundle
Richienb Sep 7, 2019
48a356c
Default response.statusText should be blank (#578)
xxczaki Sep 7, 2019
07f7cca
Merge branch '3.x' of https://github.com/bitinn/node-fetch into 3.x
Richienb Sep 7, 2019
b96f3bc
Merge branch '3.x' of https://github.com/bitinn/node-fetch into 3.x
Richienb Sep 7, 2019
886277f
fix: Use correct AbortionError message
Richienb Sep 8, 2019
446d944
chore: Use modern @babel/register
Richienb Sep 8, 2019
ea3a748
chore: Remove redundant packages
Richienb Sep 8, 2019
1cecbc4
chore: Readd form-data
Richienb Sep 8, 2019
f3b00aa
fix: Fix tests and force utf8-encoded urls
Richienb Sep 8, 2019
9a44083
lint index.js
xxczaki Sep 8, 2019
06212bf
Update devDependencies & ignore `test` directory in linter options
xxczaki Sep 8, 2019
cf8ac41
Remove unnecessary eslint-ignore comment
xxczaki Sep 8, 2019
520ad77
Update the `lint` script to run linter on every file
xxczaki Sep 8, 2019
899c3d2
Remove unused const & unnecessary import
xxczaki Sep 8, 2019
e8beddb
TypeScript: Fix Body.blob() wrong type (DefinitelyTyped/DefinitelyTyp…
xxczaki Sep 8, 2019
866e3bc
chore: Lint as part of the build process
Richienb Sep 10, 2019
51c6f1e
fix: Convert Content-Encoding to lowercase (#672)
Richienb Sep 11, 2019
44887cd
fix: Better object checks (#673)
Richienb Sep 14, 2019
b293d24
Fix stream piping (#670)
xxczaki Sep 17, 2019
672896a
chore: Remove useless check
Richienb Sep 21, 2019
356210e
style: Fix lint
Richienb Sep 21, 2019
7330d39
style: Fix lint
Richienb Sep 21, 2019
5c407c5
refactor: Modernise code
Richienb Sep 21, 2019
a6fcfd9
chore: Ensure all files are properly included
Richienb Sep 21, 2019
6a1f5c6
chore: Update deps and utf8 should be in dependencies
Richienb Sep 21, 2019
697b75c
test: Drop Node v4 from tests
Richienb Sep 21, 2019
0adb99e
test: Modernise code
Richienb Sep 23, 2019
9134a00
chore: Move errors to seperate directory
Richienb Sep 23, 2019
e663ba7
refactor: Add fetch-blob (#678)
Richienb Sep 23, 2019
e8c62ba
feat: Migrate data uri integration
Richienb Sep 24, 2019
48d45ec
Allow setting custom highWaterMark via node-fetch options (#386) (#671)
xxczaki Sep 24, 2019
10cebbc
Add TypeScript types for the new highWaterMark option
xxczaki Sep 24, 2019
af4e44f
feat: Include system error in FetchError if one occurs (#654)
Sep 27, 2019
8dde724
style: Add editorconfig
Richienb Sep 27, 2019
bf65e08
chore!: Drop NodeJS v8
Richienb Sep 27, 2019
7e30950
Merge branch '3.x' of https://github.com/bitinn/node-fetch into 3.x
Richienb Sep 27, 2019
9afeea5
chore: Remove legacy code for node < 8
Richienb Sep 27, 2019
eb9f43c
chore: Use proper checks for ArrayBuffer and AbortError
Richienb Sep 28, 2019
6bddf43
chore: Use explicitly set error name in checks
Richienb Sep 28, 2019
352de0b
Propagate size and timeout to cloned response (#664)
nazar-pc Sep 28, 2019
afc83c6
Update Response types
xxczaki Sep 28, 2019
8064fe5
Update devDependencies
xxczaki Sep 28, 2019
46be1bd
feat: Fallback to blob type (Closes: #607)
Richienb Oct 1, 2019
5e3525e
style: Update formatting
Richienb Oct 1, 2019
58c43c0
style: Fix linting issues
Richienb Oct 7, 2019
7e5a483
docs: Add info on patching the global object
Richienb Oct 12, 2019
67b0346
docs: Added non-globalThis polyfill
Richienb Oct 14, 2019
e3cd4d2
Replace deprecated `url.resolve` with the new WHATWG URL
xxczaki Oct 29, 2019
24a7580
Update devDependencies
xxczaki Oct 30, 2019
e4e5316
Format code in examples to use `xo` style
xxczaki Oct 30, 2019
6d22a65
Verify examples with RunKit and edit them if necessary
xxczaki Oct 30, 2019
55f4238
Add information about TypeScript support
xxczaki Oct 30, 2019
9ae768c
Document the new `highWaterMark` option
xxczaki Oct 30, 2019
ab8b840
Add Discord badge & information about Open Collective
xxczaki Oct 30, 2019
3ea46b6
Style change
xxczaki Oct 30, 2019
ae71303
Edit acknowledgement & add "Team" section
xxczaki Oct 30, 2019
b3a9f4d
fix table
xxczaki Oct 30, 2019
a025dde
Format example code to use xo style
xxczaki Oct 31, 2019
c4b4f90
chore: v3 release changelog
xxczaki Nov 1, 2019
bb3f6c4
Add the recommended way to fix `highWaterMark` issues
xxczaki Nov 1, 2019
de11379
docs: Add simple Runkit example
Richienb Nov 2, 2019
0770f83
fix: Properly set the name of the errors.
Richienb Nov 2, 2019
4006f23
docs: Add AbortError to documented types
Richienb Nov 2, 2019
d9c5c27
docs: AbortError proper typing parameters
Richienb Nov 2, 2019
40fedfb
docs: Add example code for Runkit
Richienb Nov 2, 2019
aee95a7
Replace microbundle with @pika/pack (#689)
xxczaki Nov 4, 2019
0be8257
fix incorrect statement in changelog
xxczaki Nov 4, 2019
0ca9547
chore: v3.x upgrade guide
xxczaki Nov 4, 2019
6d2a67d
Change the Open Collective button
xxczaki Nov 4, 2019
53f0932
docs: Encode support button as Markdown instead of HTML
Richienb Nov 5, 2019
8386746
chore: Ignore proper directory in xo
Richienb Nov 5, 2019
e659707
Add an "Upgrading" section to readme
xxczaki Nov 6, 2019
c521b79
Split the upgrade guide into 2 files & add the missing changes about …
xxczaki Nov 6, 2019
ab0e939
style: Lint test and example files
Richienb Nov 10, 2019
f7b37c1
Move *.md files to the `docs` folder (except README.md)
xxczaki Nov 13, 2019
084ecf3
Update references to files
xxczaki Nov 13, 2019
5315421
Split LIMITS.md into 2 files (as of v2.x and v3.x)
xxczaki Nov 13, 2019
b4a6b6e
Merge branch 'update-readme' into 3.x
Richienb Nov 14, 2019
81655c7
Merge branch '3.x' into octet-stream/form-data-support
Richienb Nov 14, 2019
86b32d0
chore: Remove logging statement
Richienb Nov 17, 2019
a0a1200
style: Fix lint
Richienb Nov 18, 2019
5334d91
docs: Correct typings for systemError in FetchError (Fixes #697)
Richienb Nov 22, 2019
af70087
refactor: Replace `encoding` with `fetch-charset-detection`. (#694)
Richienb Nov 23, 2019
fdd0525
docs: Fix typo
Richienb Nov 24, 2019
3f3d76d
fix: Throw SyntaxError instead of FetchError in case of invalid… (#700)
Richienb Nov 29, 2019
d765957
Remove deprecated url.parse from test
xxczaki Dec 2, 2019
fbbfc2b
Remove deprecated url.parse from server
xxczaki Dec 2, 2019
ac3e66b
docs: Users will never use NodeJS v8 because minimum supported is v10
Richienb Dec 13, 2019
df077ba
style: Fix indention
Richienb Dec 13, 2019
61f2446
style: Sentence case for comments
Richienb Dec 13, 2019
b4a4c21
Merge branch '3.x' into octet-stream/form-data-support
Richienb Dec 13, 2019
2727413
style: Use arrow functions and fix indent
Richienb Dec 13, 2019
a88350a
Fix merge conflicts in src/body.js
octet-stream Dec 13, 2019
58c01db
Merge branch 'octet-stream/form-data-support' of github.com:octet-str…
octet-stream Dec 13, 2019
1019f59
Update formdata-node to 2.x version.
octet-stream Dec 13, 2019
a2e8f64
fix: Proper data uri to buffer conversion (#703)
Richienb Dec 15, 2019
36aec77
chore: Add funding info
Richienb Dec 18, 2019
c701d3c
fix: Flawed property existence test (#706)
ekkis Dec 18, 2019
4fe4460
fix: Properly handle stream pipeline double-fire
Richienb Dec 28, 2019
1d1b0c3
docs: Fix spelling
Richienb Dec 28, 2019
b03596e
Merge branch '3.x' of https://github.com/bitinn/node-fetch into octet…
octet-stream Dec 28, 2019
b53a08a
Add isFormData utility to check if given object is a spec-compliant F…
octet-stream Dec 28, 2019
184c0ef
chore: Add `funding` field to package.json (#708)
xxczaki Dec 30, 2019
73da445
Fix: Do not set ContentLength to NaN (#709)
yaacovCR Dec 31, 2019
63370cc
Add FormDataStream class to implement form-data encoding.
octet-stream Jan 1, 2020
265167c
Fix lint errors.
octet-stream Jan 1, 2020
7cd812c
Add form-data-node file.
octet-stream Jan 1, 2020
c81fde9
Fix for size option at formdata-node body test.
octet-stream Jan 1, 2020
8401c51
Fix description in formdata-node example.
octet-stream Jan 2, 2020
f1757bc
docs: Add logo
Richienb Jan 4, 2020
2e8c4aa
chore: Update repository name from bitinn/node-fetch to node-fetch/no…
Richienb Jan 5, 2020
6b70e5b
chore: Fix unit tests
Richienb Jan 5, 2020
2f29e45
chore(deps): Bump @pika/plugin-copy-assets from 0.7.1 to 0.8.1 (#713)
dependabot-preview[bot] Jan 5, 2020
0361a7c
chore(deps): Bump @pika/plugin-build-types from 0.7.1 to 0.8.1 (#710)
dependabot-preview[bot] Jan 5, 2020
0a3fc4e
Bump nyc from 14.1.1 to 15.0.0 (#714)
dependabot-preview[bot] Jan 5, 2020
d65959f
chore: Update travis ci url
Richienb Jan 5, 2020
699bd7f
Merge branch '3.x' of https://github.com/node-fetch/node-fetch into 3.x
Richienb Jan 5, 2020
e4dbe2d
chore(deps): Bump mocha from 6.2.2 to 7.0.0 (#711)
dependabot-preview[bot] Jan 5, 2020
221c9bf
Resolve conflicts from 3.x branch.
octet-stream Jan 5, 2020
2de32e5
feat: Allow excluding a user agent in a fetch request by setting… (#715)
Richienb Jan 7, 2020
abacff5
Bump @pika/plugin-build-node from 0.7.1 to 0.8.1 (#717)
dependabot-preview[bot] Jan 7, 2020
658e5e0
Bump @pika/plugin-standard-pkg from 0.7.1 to 0.8.1 (#716)
dependabot-preview[bot] Jan 7, 2020
d9a0951
Bump form-data from 2.5.1 to 3.0.0 (#712)
dependabot-preview[bot] Jan 7, 2020
649ccd8
Resolve conflicts in package.json
octet-stream Jan 8, 2020
abbbb00
Fix merge conflits and match the master.
octet-stream May 23, 2020
0c60d24
Delete externals.d.ts
Richienb May 23, 2020
c220faf
Delete .travis.yml
Richienb May 23, 2020
e8c2f7d
Delete chai-timeout.js
Richienb May 23, 2020
ff877de
Fix path.join usage in form-data tests
octet-stream May 23, 2020
12228bb
Fix lint problem.
octet-stream May 23, 2020
b1fb555
Add missing semicolon
octet-stream May 23, 2020
dbdb326
Fix lint problems.
octet-stream May 23, 2020
c373e15
Use sync version of fs.stat for tests
octet-stream May 25, 2020
56909af
Rewrite boundary helper.
octet-stream May 25, 2020
bbe17c0
Replace FormDataStream with formDataIterator.
octet-stream May 25, 2020
49f6f7d
Remove unnecessary FormDataStream usage.
octet-stream May 25, 2020
400e75f
Remove nanoid from dependencies.
octet-stream May 25, 2020
313d529
Remove unnecessary comments.
octet-stream May 26, 2020
99c0b5e
Code style fix for form-data-iterator. Fix for extractContentType call.
octet-stream May 26, 2020
4806b94
Resolve merge conflicts.
octet-stream May 26, 2020
b7497cb
Fix lint errors.
octet-stream May 26, 2020
a2ac394
Bump minimal required Node version to 10.17 (was 10.16)
octet-stream May 26, 2020
efe73ac
Merge branch 'master' of github.com:node-fetch/node-fetch into octet-…
octet-stream Jun 4, 2020
4ef91d2
Move form-data utilities into one file and rewrite boundary helper.
octet-stream Jun 4, 2020
f2745db
Move boundary helper to form-data.js file
octet-stream Jun 4, 2020
f4b902a
Remove boundary.js
octet-stream Jun 4, 2020
25cba2d
Add getFormDataLength call in getTotalBytes function.
octet-stream Jun 4, 2020
06fa125
Fix for ESLint problem
octet-stream Jun 5, 2020
ca764f5
Fix for getFormDataLength.
octet-stream Jun 5, 2020
886f396
Add a few tests for form-data helpers.
octet-stream Jun 5, 2020
62ba683
Add formDataIterator test for empty form-data.
octet-stream Jun 5, 2020
c15974a
Add a test for default content-type in form-data
octet-stream Jun 5, 2020
60b41d3
Add tests form formDataIterator.
octet-stream Jun 7, 2020
87658a3
Replace constructor name checking with Symbol.toStringTag in isFormDa…
octet-stream Jun 7, 2020
e25b20b
Code style fixes
octet-stream Jun 7, 2020
d298318
Fix a typo
octet-stream Jun 7, 2020
e294896
Fix a method call in form-data tests.
octet-stream Jun 7, 2020
99543b6
Merge branch 'master' into octet-stream/form-data-support
tinovyatkin Jun 10, 2020
c7af6b2
Resolve merge conflicts.
octet-stream Jun 10, 2020
37e378c
Merge branch 'master' of github.com:node-fetch/node-fetch into octet-…
octet-stream Jun 10, 2020
d2476cd
replace parted with Busboy
tinovyatkin Jun 10, 2020
c1d45d3
don't ignore tests on Windows
tinovyatkin Jun 10, 2020
56c19ea
use fieldName
tinovyatkin Jun 10, 2020
d4f0fef
don't ignore any tests on Windows
tinovyatkin Jun 10, 2020
26549fa
Merge remote-tracking branch 'upstream/master' into pr/octet-stream/603
tinovyatkin Jun 10, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -381,6 +381,20 @@ const options = {
})();
```

node-fetch also supports spec-compliant FormData implementations such as [formdata-node](https://github.com/octet-stream/form-data):

```js
const fetch = require('node-fetch');
const FormData = require('formdata-node');

const form = new FormData();
form.set('greeting', 'Hello, world!');

fetch('https://httpbin.org/post', {method: 'POST', body: form})
.then(res => res.json())
.then(json => console.log(json));
```

### Request cancellation with AbortSignal

You may cancel requests with `AbortController`. A suggested implementation is [`abort-controller`](https://www.npmjs.com/package/abort-controller).
Expand Down
2 changes: 1 addition & 1 deletion docs/CHANGELOG.md
Expand Up @@ -33,7 +33,7 @@ Changelog

## v3.0.0-beta.5

> NOTE: Since the previous beta version included serious issues, such as [#749](https://github.com/node-fetch/node-fetch/issues/749), they will now be deprecated.
> NOTE: Since the previous beta version included serious issues, such as [#749](https://github.com/node-fetch/node-fetch/issues/749), they will now be deprecated.

- Enhance: use built-in AbortSignal for typings.
- Enhance: compile CJS modules as a seperate set of files.
Expand Down
12 changes: 11 additions & 1 deletion package.json
Expand Up @@ -49,6 +49,7 @@
"devDependencies": {
"abort-controller": "^3.0.0",
"abortcontroller-polyfill": "^1.4.0",
"busboy": "^0.3.1",
"c8": "^7.1.2",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
Expand All @@ -57,6 +58,7 @@
"coveralls": "^3.1.0",
"delay": "^4.3.0",
"form-data": "^3.0.0",
"formdata-node": "^2.0.0",
"mocha": "^7.2.0",
"p-timeout": "^3.2.0",
"parted": "^0.1.1",
Expand Down Expand Up @@ -94,7 +96,15 @@
"import/extensions": 0,
"import/no-useless-path-segments": 0,
"unicorn/import-index": 0,
"capitalized-comments": 0
"capitalized-comments": 0,
"node/no-unsupported-features/node-builtins": [
"error",
{
"ignores": [
"stream.Readable.from"
]
}
]
},
"ignores": [
"dist",
Expand Down
28 changes: 24 additions & 4 deletions src/body.js
Expand Up @@ -9,9 +9,11 @@ import Stream, {PassThrough} from 'stream';
import {types} from 'util';

import Blob from 'fetch-blob';

import {FetchError} from './errors/fetch-error.js';
import {FetchBaseError} from './errors/base.js';
import {isBlob, isURLSearchParameters} from './utils/is.js';
import {formDataIterator, getBoundary, getFormDataLength} from './utils/form-data.js';
import {isBlob, isURLSearchParameters, isFormData} from './utils/is.js';

const INTERNALS = Symbol('Body internals');

Expand All @@ -28,6 +30,8 @@ export default class Body {
constructor(body, {
size = 0
} = {}) {
let boundary = null;

if (body === null) {
// Body is undefined or null
body = null;
Expand All @@ -46,6 +50,10 @@ export default class Body {
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
} else if (body instanceof Stream) {
// Body is stream
} else if (isFormData(body)) {
// Body is an instance of formdata-node
boundary = `NodeFetchFormDataBoundary${getBoundary()}`;
body = Stream.Readable.from(formDataIterator(body, boundary));
} else {
// None of the above
// coerce to string then buffer
Expand All @@ -54,6 +62,7 @@ export default class Body {

this[INTERNALS] = {
body,
boundary,
disturbed: false,
error: null
};
Expand Down Expand Up @@ -146,7 +155,7 @@ Object.defineProperties(Body.prototype, {
*
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
*
* @return Promise
* @return Promise
*/
async function consumeBody(data) {
if (data[INTERNALS].disturbed) {
Expand Down Expand Up @@ -264,7 +273,7 @@ export const clone = (instance, highWaterMark) => {
* @param {any} body Any options.body input
* @returns {string | null}
*/
export const extractContentType = body => {
export const extractContentType = (body, request) => {
// Body is null or undefined
if (body === null) {
return null;
Expand Down Expand Up @@ -295,6 +304,10 @@ export const extractContentType = body => {
return `multipart/form-data;boundary=${body.getBoundary()}`;
}

if (isFormData(body)) {
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
}

// Body is stream - can't really do much about this
if (body instanceof Stream) {
return null;
Expand All @@ -313,7 +326,9 @@ export const extractContentType = body => {
* @param {any} obj.body Body object from the Body instance.
* @returns {number | null}
*/
export const getTotalBytes = ({body}) => {
export const getTotalBytes = request => {
const {body} = request;

// Body is null or undefined
if (body === null) {
return 0;
Expand All @@ -334,6 +349,11 @@ export const getTotalBytes = ({body}) => {
return body.hasKnownLength && body.hasKnownLength() ? body.getLengthSync() : null;
}

// Body is a spec-compliant form-data
if (isFormData(body)) {
return getFormDataLength(request[INTERNALS].boundary);
}

// Body is stream
return null;
};
Expand Down
5 changes: 3 additions & 2 deletions src/request.js
Expand Up @@ -69,7 +69,7 @@ export default class Request extends Body {
const headers = new Headers(init.headers || input.headers || {});

if (inputBody !== null && !headers.has('Content-Type')) {
const contentType = extractContentType(inputBody);
const contentType = extractContentType(inputBody, this);
if (contentType) {
headers.append('Content-Type', contentType);
}
Expand Down Expand Up @@ -169,7 +169,8 @@ export const getNodeRequestOptions = request => {

if (request.body !== null) {
const totalBytes = getTotalBytes(request);
if (typeof totalBytes === 'number') {
// Set Content-Length if totalBytes is a number (that is not NaN)
if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) {
contentLengthValue = String(totalBytes);
}
}
Expand Down
82 changes: 82 additions & 0 deletions src/utils/form-data.js
@@ -0,0 +1,82 @@
import {randomBytes} from 'crypto';

import {isBlob} from './is.js';

const carriage = '\r\n';
const dashes = '-'.repeat(2);
const carriageLength = Buffer.byteLength(carriage);

/**
* @param {string} boundary
*/
const getFooter = boundary => `${dashes}${boundary}${dashes}${carriage.repeat(2)}`;

/**
* @param {string} boundary
* @param {string} name
* @param {*} field
*
* @return {string}
*/
function getHeader(boundary, name, field) {
let header = '';

header += `${dashes}${boundary}${carriage}`;
header += `Content-Disposition: form-data; name="${name}"`;

if (isBlob(field)) {
header += `; filename="${field.name}"${carriage}`;
header += `Content-Type: ${field.type || 'application/octet-stream'}`;
}

return `${header}${carriage.repeat(2)}`;
}

/**
* @return {string}
*/
export const getBoundary = () => randomBytes(8).toString('hex');

/**
* @param {FormData} form
* @param {string} boundary
*/
export async function * formDataIterator(form, boundary) {
for (const [name, value] of form) {
yield getHeader(boundary, name, value);

if (isBlob(value)) {
yield * value.stream();
} else {
yield value;
}

yield carriage;
}

yield getFooter(boundary);
}

/**
* @param {FormData} form
* @param {string} boundary
*/
export function getFormDataLength(form, boundary) {
let length = 0;

for (const [name, value] of form) {
length += Buffer.byteLength(getHeader(boundary, name, value));

if (isBlob(value)) {
length += value.size;
} else {
length += Buffer.byteLength(String(value));
}

length += carriageLength;
}

length += Buffer.byteLength(getFooter(boundary));

return length;
}
24 changes: 23 additions & 1 deletion src/utils/is.js
Expand Up @@ -28,7 +28,7 @@ export const isURLSearchParameters = object => {
};

/**
* Check if `obj` is a W3C `Blob` object (which `File` inherits from)
* Check if `object` is a W3C `Blob` object (which `File` inherits from)
*
* @param {*} obj
* @return {boolean}
Expand All @@ -44,6 +44,28 @@ export const isBlob = object => {
);
};

/**
* Check if `obj` is a spec-compliant `FormData` object
*
* @param {*} object
* @return {boolean}
*/
export function isFormData(object) {
return (
typeof object === 'object' &&
typeof object.append === 'function' &&
typeof object.set === 'function' &&
typeof object.get === 'function' &&
typeof object.getAll === 'function' &&
typeof object.delete === 'function' &&
typeof object.keys === 'function' &&
typeof object.values === 'function' &&
typeof object.entries === 'function' &&
typeof object.constructor === 'function' &&
object[NAME] === 'FormData'
);
}

/**
* Check if `obj` is an instance of AbortSignal.
*
Expand Down