A light-weight module that brings Fetch API to Node.js.
+Promise
+- Returns: `Promise`
Consume the body and return a promise that will resolve to one of these formats.
-#### body.buffer()
+
-*(node-fetch extension)*
+### Class: FetchError
-* Returns: Promise<Buffer>
+_(node-fetch extension)_
-Consume the body and return a promise that will resolve to a Buffer.
+An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info.
-#### body.textConverted()
+
-*(node-fetch extension)*
+### Class: AbortError
-* Returns: Promise<String>
+_(node-fetch extension)_
-Identical to `body.text()`, except instead of always converting to UTF-8, encoding sniffing will be performed and text converted to UTF-8 if possible.
+An Error thrown when the request is aborted in response to an `AbortSignal`'s `abort` event. It has a `name` property of `AbortError`. See [ERROR-HANDLING.MD][] for more info.
-(This API requires an optional dependency of the npm package [encoding](https://www.npmjs.com/package/encoding), which you need to install manually. `webpack` users may see [a warning message](https://github.com/bitinn/node-fetch/issues/412#issuecomment-379007792) due to this optional dependency.)
+## TypeScript
-
-### Class: FetchError
+**Since `3.x` types are bundled with `node-fetch`, so you don't need to install any additional packages.**
-*(node-fetch extension)*
+For older versions please use the type definitions from [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped):
-An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info.
+```sh
+npm install --save-dev @types/node-fetch@2.x
+```
-
-### Class: AbortError
+## Acknowledgement
-*(node-fetch extension)*
+Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
-An Error thrown when the request is aborted in response to an `AbortSignal`'s `abort` event. It has a `name` property of `AbortError`. See [ERROR-HANDLING.MD][] for more info.
+## Team
-## Acknowledgement
+| [](https://github.com/bitinn) | [](https://github.com/jimmywarting) | [](https://github.com/xxczaki) | [](https://github.com/Richienb) | [](https://github.com/gr2m) |
+| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
+| [David Frank](https://bitinn.net/) | [Jimmy Wärting](https://jimmy.warting.se/) | [Antoni Kepinski](https://kepinski.ch) | [Richie Bendall](https://www.richie-bendall.ml/) | [Gregor Martynus](https://twitter.com/gr2m) |
-Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
+###### Former
-`node-fetch` v1 was maintained by [@bitinn](https://github.com/bitinn); v2 was maintained by [@TimothyGu](https://github.com/timothygu), [@bitinn](https://github.com/bitinn) and [@jimmywarting](https://github.com/jimmywarting); v2 readme is written by [@jkantr](https://github.com/jkantr).
+- [Timothy Gu](https://github.com/timothygu)
+- [Jared Kantrowitz](https://github.com/jkantr)
## License
-MIT
-
-[npm-image]: https://flat.badgen.net/npm/v/node-fetch
-[npm-url]: https://www.npmjs.com/package/node-fetch
-[travis-image]: https://flat.badgen.net/travis/bitinn/node-fetch
-[travis-url]: https://travis-ci.org/bitinn/node-fetch
-[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/node-fetch/master
-[codecov-url]: https://codecov.io/gh/bitinn/node-fetch
-[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch
-[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch
-[discord-image]: https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord&style=flat-square
-[discord-url]: https://discord.gg/Zxbndcm
-[opencollective-image]: https://opencollective.com/node-fetch/backers.svg
-[opencollective-url]: https://opencollective.com/node-fetch
+[MIT](LICENSE.md)
+
[whatwg-fetch]: https://fetch.spec.whatwg.org/
[response-init]: https://fetch.spec.whatwg.org/#responseinit
[node-readable]: https://nodejs.org/api/stream.html#stream_readable_streams
[mdn-headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers
-[LIMITS.md]: https://github.com/bitinn/node-fetch/blob/master/LIMITS.md
-[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md
-[UPGRADE-GUIDE.md]: https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md
+[error-handling.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..e60fc6870
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+Please report security issues to `jimmy@warting.se`
\ No newline at end of file
diff --git a/browser.js b/browser.js
deleted file mode 100644
index 83c54c584..000000000
--- a/browser.js
+++ /dev/null
@@ -1,25 +0,0 @@
-"use strict";
-
-// ref: https://github.com/tc39/proposal-global
-var getGlobal = function () {
- // the only reliable means to get the global object is
- // `Function('return this')()`
- // However, this causes CSP violations in Chrome apps.
- if (typeof self !== 'undefined') { return self; }
- if (typeof window !== 'undefined') { return window; }
- if (typeof global !== 'undefined') { return global; }
- throw new Error('unable to locate global object');
-}
-
-var global = getGlobal();
-
-module.exports = exports = global.fetch;
-
-// Needed for TypeScript and Webpack.
-if (global.fetch) {
- exports.default = global.fetch.bind(global);
-}
-
-exports.Headers = global.Headers;
-exports.Request = global.Request;
-exports.Response = global.Response;
\ No newline at end of file
diff --git a/build/babel-plugin.js b/build/babel-plugin.js
deleted file mode 100644
index 8cddae954..000000000
--- a/build/babel-plugin.js
+++ /dev/null
@@ -1,61 +0,0 @@
-// This Babel plugin makes it possible to do CommonJS-style function exports
-
-const walked = Symbol('walked');
-
-module.exports = ({ types: t }) => ({
- visitor: {
- Program: {
- exit(program) {
- if (program[walked]) {
- return;
- }
-
- for (let path of program.get('body')) {
- if (path.isExpressionStatement()) {
- const expr = path.get('expression');
- if (expr.isAssignmentExpression() &&
- expr.get('left').matchesPattern('exports.*')) {
- const prop = expr.get('left').get('property');
- if (prop.isIdentifier({ name: 'default' })) {
- program.unshiftContainer('body', [
- t.expressionStatement(
- t.assignmentExpression('=',
- t.identifier('exports'),
- t.assignmentExpression('=',
- t.memberExpression(
- t.identifier('module'), t.identifier('exports')
- ),
- expr.node.right
- )
- )
- ),
- t.expressionStatement(
- t.callExpression(
- t.memberExpression(
- t.identifier('Object'), t.identifier('defineProperty')),
- [
- t.identifier('exports'),
- t.stringLiteral('__esModule'),
- t.objectExpression([
- t.objectProperty(t.identifier('value'), t.booleanLiteral(true))
- ])
- ]
- )
- ),
- t.expressionStatement(
- t.assignmentExpression('=',
- expr.node.left, t.identifier('exports')
- )
- )
- ]);
- path.remove();
- }
- }
- }
- }
-
- program[walked] = true;
- }
- }
- }
-});
diff --git a/build/rollup-plugin.js b/build/rollup-plugin.js
deleted file mode 100644
index 36ebdc804..000000000
--- a/build/rollup-plugin.js
+++ /dev/null
@@ -1,18 +0,0 @@
-export default function tweakDefault() {
- return {
- transformBundle: function (source) {
- var lines = source.split('\n');
- for (var i = 0; i < lines.length; i++) {
- var line = lines[i];
- var matches = /^(exports(?:\['default']|\.default)) = (.*);$/.exec(line);
- if (matches) {
- lines[i] = 'module.exports = exports = ' + matches[2] + ';\n' +
- 'Object.defineProperty(exports, "__esModule", { value: true });\n' +
- matches[1] + ' = exports;';
- break;
- }
- }
- return lines.join('\n');
- }
- };
-}
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index b4e9d3fcd..000000000
--- a/codecov.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-parsers:
- javascript:
- enable_partials: yes
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
new file mode 100644
index 000000000..a15478e3c
--- /dev/null
+++ b/docs/CHANGELOG.md
@@ -0,0 +1,445 @@
+# Changelog
+All notable changes will be recorded here.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## What's Changed
+* core: update fetch-blob by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1371
+* docs: Fix typo around sending a file by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1381
+* core: (http.request): Cast URL to string before sending it to NodeJS core by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1378
+* core: handle errors from the request body stream by @mdmitry01 in https://github.com/node-fetch/node-fetch/pull/1392
+* core: Better handle wrong redirect header in a response by @tasinet in https://github.com/node-fetch/node-fetch/pull/1387
+* core: Don't use buffer to make a blob by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1402
+* docs: update readme for TS @types/node-fetch by @adamellsworth in https://github.com/node-fetch/node-fetch/pull/1405
+* core: Fix logical operator priority to disallow GET/HEAD with non-empty body by @maxshirshin in https://github.com/node-fetch/node-fetch/pull/1369
+* core: Don't use global buffer by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1422
+* ci: fix main branch by @dnalborczyk in https://github.com/node-fetch/node-fetch/pull/1429
+* core: use more node: protocol imports by @dnalborczyk in https://github.com/node-fetch/node-fetch/pull/1428
+* core: Warn when using data by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1421
+* docs: Create SECURITY.md by @JamieSlome in https://github.com/node-fetch/node-fetch/pull/1445
+* core: don't forward secure headers to 3th party by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1449
+
+## New Contributors
+* @mdmitry01 made their first contribution in https://github.com/node-fetch/node-fetch/pull/1392
+* @tasinet made their first contribution in https://github.com/node-fetch/node-fetch/pull/1387
+* @adamellsworth made their first contribution in https://github.com/node-fetch/node-fetch/pull/1405
+* @maxshirshin made their first contribution in https://github.com/node-fetch/node-fetch/pull/1369
+* @JamieSlome made their first contribution in https://github.com/node-fetch/node-fetch/pull/1445
+
+**Full Changelog**: https://github.com/node-fetch/node-fetch/compare/v3.1.0...v3.1.2
+
+## 3.1.0
+
+## What's Changed
+* fix(Body): Discourage form-data and buffer() by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1212
+* fix: Pass url string to http.request by @serverwentdown in https://github.com/node-fetch/node-fetch/pull/1268
+* Fix octocat image link by @lakuapik in https://github.com/node-fetch/node-fetch/pull/1281
+* fix(Body.body): Normalize `Body.body` into a `node:stream` by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/924
+* docs(Headers): Add default Host request header to README.md file by @robertoaceves in https://github.com/node-fetch/node-fetch/pull/1316
+* Update CHANGELOG.md by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1292
+* Add highWaterMark to cloned properties by @davesidious in https://github.com/node-fetch/node-fetch/pull/1162
+* Update README.md to fix HTTPResponseError by @thedanfernandez in https://github.com/node-fetch/node-fetch/pull/1135
+* docs: switch `url` to `URL` by @dhritzkiv in https://github.com/node-fetch/node-fetch/pull/1318
+* fix(types): declare buffer() deprecated by @dnalborczyk in https://github.com/node-fetch/node-fetch/pull/1345
+* chore: fix lint by @dnalborczyk in https://github.com/node-fetch/node-fetch/pull/1348
+* refactor: use node: prefix for imports by @dnalborczyk in https://github.com/node-fetch/node-fetch/pull/1346
+* Bump data-uri-to-buffer from 3.0.1 to 4.0.0 by @dependabot in https://github.com/node-fetch/node-fetch/pull/1319
+* Bump mocha from 8.4.0 to 9.1.3 by @dependabot in https://github.com/node-fetch/node-fetch/pull/1339
+* Referrer and Referrer Policy by @tekwiz in https://github.com/node-fetch/node-fetch/pull/1057
+* Add typing for Response.redirect(url, status) by @c-w in https://github.com/node-fetch/node-fetch/pull/1169
+* chore: Correct stuff in README.md by @Jiralite in https://github.com/node-fetch/node-fetch/pull/1361
+* docs: Improve clarity of "Loading and configuring" by @serverwentdown in https://github.com/node-fetch/node-fetch/pull/1323
+* feat(Body): Added support for `BodyMixin.formData()` and constructing bodies with FormData by @jimmywarting in https://github.com/node-fetch/node-fetch/pull/1314
+
+## New Contributors
+* @serverwentdown made their first contribution in https://github.com/node-fetch/node-fetch/pull/1268
+* @lakuapik made their first contribution in https://github.com/node-fetch/node-fetch/pull/1281
+* @robertoaceves made their first contribution in https://github.com/node-fetch/node-fetch/pull/1316
+* @davesidious made their first contribution in https://github.com/node-fetch/node-fetch/pull/1162
+* @thedanfernandez made their first contribution in https://github.com/node-fetch/node-fetch/pull/1135
+* @dhritzkiv made their first contribution in https://github.com/node-fetch/node-fetch/pull/1318
+* @dnalborczyk made their first contribution in https://github.com/node-fetch/node-fetch/pull/1345
+* @dependabot made their first contribution in https://github.com/node-fetch/node-fetch/pull/1319
+* @c-w made their first contribution in https://github.com/node-fetch/node-fetch/pull/1169
+
+**Full Changelog**: https://github.com/node-fetch/node-fetch/compare/v3.0.0...v3.1.0
+
+## v3.0.0
+
+- other: Marking v3 as stable
+- docs: Add example for loading ESM from CommonJS (#1236)
+
+## v3.0.0-beta.10
+
+- **Breaking:** minimum supported Node.js version is now 12.20.
+- **Breaking:** node-fetch is now a pure ESM module.
+- Other: update readme to inform users about ESM.
+- Other: update dependencies.
+
+## v3.0.0-beta.9
+
+**This is an important security release. It is strongly recommended to update as soon as possible.**
+
+- Fix: honor the `size` option after following a redirect.
+
+## v3.0.0-beta.8
+
+- Enhance: remove string-to-arraybuffer (#882).
+- Enhance: remove parted dependency (#883).
+- Fix: export package.json (#908).
+- Fix: minimum Node.js version (#874).
+- Other: fix typo.
+
+## v3.0.0-beta.7
+
+- **Breaking:** minimum supported Node.js version is now 10.17.
+- Enhance: update `fetch-blob`.
+- Enhance: add insecureHTTPParser Parameter (#856).
+- Enhance: drop custom Promises and refactor to `async` functions (#845).
+- Enhance: polyfill `http.validateHeaderName` and `http.validateHeaderValue` (#843).
+- Enhance: should check body _source_ on redirect (#866).
+- Enhance: remove code duplication in custom errors (#842).
+- Enhance: implement form-data encoding (#603).
+- Fix: improve TypeScript types (#841).
+- Fix: data URI handling and drop all URL analysis RegExps (#853).
+- Fix: headers import statement (#859).
+- Fix: correct Node versions were not installed on test matrix (#846).
+- Other: test CommonJS build artifact (#838).
+- Other: create Code of Conduct (#849).
+- Other: readme update.
+
+## v3.0.0-beta.6-exportfix
+
+- Fix: `fetch` function export & declaration, which broke the previous release.
+
+## v3.0.0-beta.6
+
+- **Breaking:** minimum supported Node.js version is now 10.16.
+- **Breaking:** removed `timeout` option.
+- **Breaking:** revamp TypeScript declarations.
+- Enhance: improve coverage.
+- Enhance: drop Babel (while keeping ESM) (#805).
+- Enhance: normalize export (#827).
+- Enhance: remove guard for Stream.Readable.destroy (#824).
+- Enhance: remove custom isArrayBuffer (#822).
+- Enhance: use normal class inheritance instead of Body.mixIn (#828).
+- Enhance: follow xo linter rules more strictly (#829).
+- Enhance: revamp Headers module (#834).
+- Fix: export the `AbortError` class.
+- Fix: example using `file-type` (#804).
+- Fix: settle `consumeBody` promise when the response closes prematurely (#768).
+- Fix: disambiguate timeout behavior for response headers and body (#770).
+- Fix: make sure the default `highWaterMark` equals 16384.
+- Fix: default user agent (#818).
+- Other: readme update.
+- Other: update copyright information.
+
+## 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.
+
+- Enhance: use built-in AbortSignal for typings.
+- Enhance: compile CJS modules as a seperate set of files.
+- Enhance: add more complete stream download example.
+- Fix: question mark stripped from url when no params are given.
+- Fix: path to tests file in error handling doc.
+- Fix: import URL and URLSearchParams in typings.
+- Fix: Ensure search parameters are included in URL path (#759).
+
+## v3.0.0-beta.2
+
+- Fix: exporting `main` and `types` at the correct path, oops.
+
+## v3.0.0-beta.1
+
+- **Breaking:** minimum supported Node.js version is now 10.
+- Enhance: added new node-fetch-only option: `highWaterMark`.
+- Enhance: `AbortError` now uses a w3c defined message.
+- Enhance: data URI support.
+- Enhance: drop existing blob implementation code and use fetch-blob as dependency instead.
+- Enhance: modernise the code behind `FetchError` and `AbortError`.
+- Enhance: replace deprecated `url.parse()` and `url.replace()` with the new WHATWG's `new URL()`
+- Enhance: allow excluding a `user-agent` in a fetch request by setting it's header to null.
+- Fix: `Response.statusText` no longer sets a default message derived from the HTTP status code.
+- Fix: missing response stream error events.
+- Fix: do not use constructor.name to check object.
+- Fix: convert `Content-Encoding` to lowercase.
+- Fix: propagate size and timeout to cloned response.
+- Other: bundle TypeScript types.
+- Other: replace Rollup with @pika/pack.
+- Other: introduce linting to the project.
+- Other: simplify Travis CI build matrix.
+- Other: dev dependency update.
+- Other: readme update.
+
+
+# 2.x release
+
+## v2.6.1
+
+**This is an important security release. It is strongly recommended to update as soon as possible.**
+
+- Fix: honor the `size` option after following a redirect.
+
+## v2.6.0
+
+- Enhance: `options.agent`, it now accepts a function that returns custom http(s).Agent instance based on current URL, see readme for more information.
+- Fix: incorrect `Content-Length` was returned for stream body in 2.5.0 release; note that `node-fetch` doesn't calculate content length for stream body.
+- Fix: `Response.url` should return empty string instead of `null` by default.
+
+## v2.5.0
+
+- Enhance: `Response` object now includes `redirected` property.
+- Enhance: `fetch()` now accepts third-party `Blob` implementation as body.
+- Other: disable `package-lock.json` generation as we never commit them.
+- Other: dev dependency update.
+- Other: readme update.
+
+## v2.4.1
+
+- Fix: `Blob` import rule for node < 10, as `Readable` isn't a named export.
+
+## v2.4.0
+
+- Enhance: added `Brotli` compression support (using node's zlib).
+- Enhance: updated `Blob` implementation per spec.
+- Fix: set content type automatically for `URLSearchParams`.
+- Fix: `Headers` now reject empty header names.
+- Fix: test cases, as node 12+ no longer accepts invalid header response.
+
+## v2.3.0
+
+- Enhance: added `AbortSignal` support, with README example.
+- Enhance: handle invalid `Location` header during redirect by rejecting them explicitly with `FetchError`.
+- Fix: update `browser.js` to support react-native environment, where `self` isn't available globally.
+
+## v2.2.1
+
+- Fix: `compress` flag shouldn't overwrite existing `Accept-Encoding` header.
+- Fix: multiple `import` rules, where `PassThrough` etc. doesn't have a named export when using node <10 and `--experimental-modules` flag.
+- Other: Better README.
+
+## v2.2.0
+
+- Enhance: Support all `ArrayBuffer` view types
+- Enhance: Support Web Workers
+- Enhance: Support Node.js' `--experimental-modules` mode; deprecate `.es.js` file
+- Fix: Add `__esModule` property to the exports object
+- Other: Better example in README for writing response to a file
+- Other: More tests for Agent
+
+## v2.1.2
+
+- Fix: allow `Body` methods to work on `ArrayBuffer`-backed `Body` objects
+- Fix: reject promise returned by `Body` methods when the accumulated `Buffer` exceeds the maximum size
+- Fix: support custom `Host` headers with any casing
+- Fix: support importing `fetch()` from TypeScript in `browser.js`
+- Fix: handle the redirect response body properly
+
+## v2.1.1
+
+Fix packaging errors in v2.1.0.
+
+## v2.1.0
+
+- Enhance: allow using ArrayBuffer as the `body` of a `fetch()` or `Request`
+- Fix: store HTTP headers of a `Headers` object internally with the given case, for compatibility with older servers that incorrectly treated header names in a case-sensitive manner
+- Fix: silently ignore invalid HTTP headers
+- Fix: handle HTTP redirect responses without a `Location` header just like non-redirect responses
+- Fix: include bodies when following a redirection when appropriate
+
+## v2.0.0
+
+This is a major release. Check [our upgrade guide](https://github.com/node-fetch/node-fetch/blob/master/docs/v2-UPGRADE-GUIDE.md) for an overview on some key differences between v1 and v2.
+
+### General changes
+
+- Major: Node.js 0.10.x and 0.12.x support is dropped
+- Major: `require('node-fetch/lib/response')` etc. is now unsupported; use `require('node-fetch').Response` or ES6 module imports
+- Enhance: start testing on Node.js v4.x, v6.x, v8.x LTS, as well as v9.x stable
+- Enhance: use Rollup to produce a distributed bundle (less memory overhead and faster startup)
+- Enhance: make `Object.prototype.toString()` on Headers, Requests, and Responses return correct class strings
+- Other: rewrite in ES2015 using Babel
+- Other: use Codecov for code coverage tracking
+- Other: update package.json script for npm 5
+- Other: `encoding` module is now optional (alpha.7)
+- Other: expose browser.js through package.json, avoid bundling mishaps (alpha.9)
+- Other: allow TypeScript to `import` node-fetch by exposing default (alpha.9)
+
+### HTTP requests
+
+- Major: overwrite user's `Content-Length` if we can be sure our information is correct (per spec)
+- Fix: errors in a response are caught before the body is accessed
+- Fix: support WHATWG URL objects, created by `whatwg-url` package or `require('url').URL` in Node.js 7+
+
+### Response and Request classes
+
+- Major: `response.text()` no longer attempts to detect encoding, instead always opting for UTF-8 (per spec); use `response.textConverted()` for the v1 behavior
+- Major: make `response.json()` throw error instead of returning an empty object on 204 no-content response (per spec; reverts behavior changed in v1.6.2)
+- Major: internal methods are no longer exposed
+- Major: throw error when a `GET` or `HEAD` Request is constructed with a non-null body (per spec)
+- Enhance: add `response.arrayBuffer()` (also applies to Requests)
+- Enhance: add experimental `response.blob()` (also applies to Requests)
+- Enhance: `URLSearchParams` is now accepted as a body
+- Enhance: wrap `response.json()` json parsing error as `FetchError`
+- Fix: fix Request and Response with `null` body
+
+### Headers class
+
+- Major: remove `headers.getAll()`; make `get()` return all headers delimited by commas (per spec)
+- Enhance: make Headers iterable
+- Enhance: make Headers constructor accept an array of tuples
+- Enhance: make sure header names and values are valid in HTTP
+- Fix: coerce Headers prototype function parameters to strings, where applicable
+
+### Documentation
+
+- Enhance: more comprehensive API docs
+- Enhance: add a list of default headers in README
+
+
+# 1.x release
+
+## Backport releases (v1.7.0 and beyond)
+
+See [changelog on 1.x branch](https://github.com/node-fetch/node-fetch/blob/1.x/CHANGELOG.md) for details.
+
+## v1.6.3
+
+- Enhance: error handling document to explain `FetchError` design
+- Fix: support `form-data` 2.x releases (requires `form-data` >= 2.1.0)
+
+## v1.6.2
+
+- Enhance: minor document update
+- Fix: response.json() returns empty object on 204 no-content response instead of throwing a syntax error
+
+## v1.6.1
+
+- Fix: if `res.body` is a non-stream non-formdata object, we will call `body.toString` and send it as a string
+- Fix: `counter` value is incorrectly set to `follow` value when wrapping Request instance
+- Fix: documentation update
+
+## v1.6.0
+
+- Enhance: added `res.buffer()` api for convenience, it returns body as a Node.js buffer
+- Enhance: better old server support by handling raw deflate response
+- Enhance: skip encoding detection for non-HTML/XML response
+- Enhance: minor document update
+- Fix: HEAD request doesn't need decompression, as body is empty
+- Fix: `req.body` now accepts a Node.js buffer
+
+## v1.5.3
+
+- Fix: handle 204 and 304 responses when body is empty but content-encoding is gzip/deflate
+- Fix: allow resolving response and cloned response in any order
+- Fix: avoid setting `content-length` when `form-data` body use streams
+- Fix: send DELETE request with content-length when body is present
+- Fix: allow any url when calling new Request, but still reject non-http(s) url in fetch
+
+## v1.5.2
+
+- Fix: allow node.js core to handle keep-alive connection pool when passing a custom agent
+
+## v1.5.1
+
+- Fix: redirect mode `manual` should work even when there is no redirection or broken redirection
+
+## v1.5.0
+
+- Enhance: rejected promise now use custom `Error` (thx to @pekeler)
+- Enhance: `FetchError` contains `err.type` and `err.code`, allows for better error handling (thx to @pekeler)
+- Enhance: basic support for redirect mode `manual` and `error`, allows for location header extraction (thx to @jimmywarting for the initial PR)
+
+## v1.4.1
+
+- Fix: wrapping Request instance with FormData body again should preserve the body as-is
+
+## v1.4.0
+
+- Enhance: Request and Response now have `clone` method (thx to @kirill-konshin for the initial PR)
+- Enhance: Request and Response now have proper string and buffer body support (thx to @kirill-konshin)
+- Enhance: Body constructor has been refactored out (thx to @kirill-konshin)
+- Enhance: Headers now has `forEach` method (thx to @tricoder42)
+- Enhance: back to 100% code coverage
+- Fix: better form-data support (thx to @item4)
+- Fix: better character encoding detection under chunked encoding (thx to @dsuket for the initial PR)
+
+## v1.3.3
+
+- Fix: make sure `Content-Length` header is set when body is string for POST/PUT/PATCH requests
+- Fix: handle body stream error, for cases such as incorrect `Content-Encoding` header
+- Fix: when following certain redirects, use `GET` on subsequent request per Fetch Spec
+- Fix: `Request` and `Response` constructors now parse headers input using `Headers`
+
+## v1.3.2
+
+- Enhance: allow auto detect of form-data input (no `FormData` spec on node.js, this is form-data specific feature)
+
+## v1.3.1
+
+- Enhance: allow custom host header to be set (server-side only feature, as it's a forbidden header on client-side)
+
+## v1.3.0
+
+- Enhance: now `fetch.Request` is exposed as well
+
+## v1.2.1
+
+- Enhance: `Headers` now normalized `Number` value to `String`, prevent common mistakes
+
+## v1.2.0
+
+- Enhance: now fetch.Headers and fetch.Response are exposed, making testing easier
+
+## v1.1.2
+
+- Fix: `Headers` should only support `String` and `Array` properties, and ignore others
+
+## v1.1.1
+
+- Enhance: now req.headers accept both plain object and `Headers` instance
+
+## v1.1.0
+
+- Enhance: timeout now also applies to response body (in case of slow response)
+- Fix: timeout is now cleared properly when fetch is done/has failed
+
+## v1.0.6
+
+- Fix: less greedy content-type charset matching
+
+## v1.0.5
+
+- Fix: when `follow = 0`, fetch should not follow redirect
+- Enhance: update tests for better coverage
+- Enhance: code formatting
+- Enhance: clean up doc
+
+## v1.0.4
+
+- Enhance: test iojs support
+- Enhance: timeout attached to socket event only fire once per redirect
+
+## v1.0.3
+
+- Fix: response size limit should reject large chunk
+- Enhance: added character encoding detection for xml, such as rss/atom feed (encoding in DTD)
+
+## v1.0.2
+
+- Fix: added res.ok per spec change
+
+## v1.0.0
+
+- Enhance: better test coverage and doc
+
+
+# 0.x release
+
+## v0.1
+
+- Major: initial public release
+
+[Unreleased]: https://github.com/node-fetch/node-fetch/compare/v3.0.0-beta.10...HEAD
diff --git a/docs/ERROR-HANDLING.md b/docs/ERROR-HANDLING.md
new file mode 100644
index 000000000..85b1f0de8
--- /dev/null
+++ b/docs/ERROR-HANDLING.md
@@ -0,0 +1,39 @@
+
+Error handling with node-fetch
+==============================
+
+Because `window.fetch` isn't designed to be transparent about the cause of request errors, we have to come up with our own solutions.
+
+The basics:
+
+- A cancelled request is rejected with an [`AbortError`](https://github.com/node-fetch/node-fetch/blob/master/README.md#class-aborterror). You can check if the reason for rejection was that the request was aborted by checking the `Error`'s `name` is `AbortError`.
+
+```js
+const fetch = require('node-fetch');
+
+(async () => {
+ try {
+ await fetch(url, {signal});
+ } catch (error) {
+ if (error.name === 'AbortError') {
+ console.log('request was aborted');
+ }
+ }
+})();
+```
+
+- All [operational errors][joyent-guide] *other than aborted requests* are rejected with a [FetchError](https://github.com/node-fetch/node-fetch/blob/master/README.md#class-fetcherror). You can handle them all through the `try/catch` block or promise `catch` clause.
+
+- All errors come with an `error.message` detailing the cause of errors.
+
+- All errors originating from `node-fetch` are marked with a custom `err.type`.
+
+- All errors originating from Node.js core are marked with `error.type = 'system'`, and in addition contain an `error.code` and an `error.errno` for error handling. These are aliases for error codes thrown by Node.js core.
+
+- [Programmer errors][joyent-guide] are either thrown as soon as possible, or rejected with default `Error` with `error.message` for ease of troubleshooting.
+
+List of error types:
+
+- Because we maintain 100% coverage, see [test/main.js](https://github.com/node-fetch/node-fetch/blob/master/test/main.js) for a full list of custom `FetchError` types, as well as some of the common errors from Node.js
+
+[joyent-guide]: https://www.joyent.com/node-js/production/design/errors#operational-errors-vs-programmer-errors
diff --git a/docs/media/Banner.svg b/docs/media/Banner.svg
new file mode 100644
index 000000000..b9c079783
--- /dev/null
+++ b/docs/media/Banner.svg
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/docs/media/Logo.svg b/docs/media/Logo.svg
new file mode 100644
index 000000000..8d1a2c9e8
--- /dev/null
+++ b/docs/media/Logo.svg
@@ -0,0 +1,21 @@
+
+
\ No newline at end of file
diff --git a/docs/media/NodeFetch.sketch b/docs/media/NodeFetch.sketch
new file mode 100644
index 000000000..ad858e7bf
Binary files /dev/null and b/docs/media/NodeFetch.sketch differ
diff --git a/LIMITS.md b/docs/v2-LIMITS.md
similarity index 90%
rename from LIMITS.md
rename to docs/v2-LIMITS.md
index 9c4b8c0c8..849a15533 100644
--- a/LIMITS.md
+++ b/docs/v2-LIMITS.md
@@ -26,7 +26,7 @@ Known differences
- If you are using `res.clone()` and writing an isomorphic app, note that stream on Node.js have a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers).
-- Because node.js stream doesn't expose a [*disturbed*](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly.
+- Because Node.js stream doesn't expose a [*disturbed*](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly.
[readable-stream]: https://nodejs.org/api/stream.html#stream_readable_streams
-[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md
+[ERROR-HANDLING.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md
diff --git a/UPGRADE-GUIDE.md b/docs/v2-UPGRADE-GUIDE.md
similarity index 95%
rename from UPGRADE-GUIDE.md
rename to docs/v2-UPGRADE-GUIDE.md
index 22aab748b..3660dfb3a 100644
--- a/UPGRADE-GUIDE.md
+++ b/docs/v2-UPGRADE-GUIDE.md
@@ -45,7 +45,7 @@ spec-compliant. These changes are done in conjunction with GitHub's
const headers = new Headers({
'Abc': 'string',
- 'Multi': [ 'header1', 'header2' ]
+ 'Multi': ['header1', 'header2']
});
// before after
@@ -63,14 +63,14 @@ headers.get('Multi') => headers.get('Multi') =>
const headers = new Headers({
'Abc': 'string',
- 'Multi': [ 'header1', 'header2' ]
+ 'Multi': ['header1', 'header2']
});
// before after
headers.getAll('Multi') => headers.getAll('Multi') =>
[ 'header1', 'header2' ]; throws ReferenceError
headers.get('Multi').split(',') =>
- [ 'header1', 'header2' ];
+ ['header1', 'header2'];
//////////////////////////////////////////////////////////////////////////////
@@ -91,7 +91,7 @@ headers.get(undefined) headers.get(undefined)
const headers = new Headers();
headers.set('Héy', 'ok'); // now throws
headers.get('Héy'); // now throws
-new Headers({ 'Héy': 'ok' }); // now throws
+new Headers({'Héy': 'ok'}); // now throws
```
## Node.js v0.x support dropped
diff --git a/docs/v3-LIMITS.md b/docs/v3-LIMITS.md
new file mode 100644
index 000000000..a53202e64
--- /dev/null
+++ b/docs/v3-LIMITS.md
@@ -0,0 +1,30 @@
+Known differences
+=================
+
+*As of 3.x release*
+
+- Topics such as Cross-Origin, Content Security Policy, Mixed Content, Service Workers are ignored, given our server-side context.
+
+- On the upside, there are no forbidden headers.
+
+- `res.url` contains the final url when following redirects.
+
+- For convenience, `res.body` is a Node.js [Readable stream][readable-stream], so decoding can be handled independently.
+
+- Similarly, `req.body` can either be `null`, a buffer or a Readable stream.
+
+- Also, you can handle rejected fetch requests through checking `err.type` and `err.code`. See [ERROR-HANDLING.md][] for more info.
+
+- Only support `res.text()`, `res.json()`, `res.blob()`, `res.arraybuffer()`, `res.buffer()`
+
+- There is currently no built-in caching, as server-side caching varies by use-cases.
+
+- Current implementation lacks server-side cookie store, you will need to extract `Set-Cookie` headers manually.
+
+- If you are using `res.clone()` and writing an isomorphic app, note that stream on Node.js has a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers). Learn [how to get around this][highwatermark-fix].
+
+- Because Node.js stream doesn't expose a [*disturbed*](https://fetch.spec.whatwg.org/#concept-readablestream-disturbed) property like Stream spec, using a consumed stream for `new Response(body)` will not set `bodyUsed` flag correctly.
+
+[readable-stream]: https://nodejs.org/api/stream.html#stream_readable_streams
+[ERROR-HANDLING.md]: https://github.com/node-fetch/node-fetch/blob/master/docs/ERROR-HANDLING.md
+[highwatermark-fix]: https://github.com/node-fetch/node-fetch/blob/master/README.md#custom-highwatermark
diff --git a/docs/v3-UPGRADE-GUIDE.md b/docs/v3-UPGRADE-GUIDE.md
new file mode 100644
index 000000000..4e9eada0f
--- /dev/null
+++ b/docs/v3-UPGRADE-GUIDE.md
@@ -0,0 +1,144 @@
+# Upgrade to node-fetch v3.x
+
+node-fetch v3.x brings about many changes that increase the compliance of
+WHATWG's [Fetch Standard][whatwg-fetch]. However, many of these changes mean
+that apps written for node-fetch v2.x needs to be updated to work with
+node-fetch v3.x and be conformant with the Fetch Standard. This document helps
+you make this transition.
+
+Note that this document is not an exhaustive list of all changes made in v3.x,
+but rather that of the most important breaking changes. See our [changelog] for
+other comparatively minor modifications.
+
+- [Breaking Changes](#breaking)
+- [Enhancements](#enhancements)
+
+---
+
+
+
+# Breaking Changes
+
+## Minimum supported Node.js version is now 12.20
+
+Since Node.js 10 has been deprecated since May 2020, we have decided that node-fetch v3 will drop support for Node.js 4, 6, 8, and 10 (which were previously supported). We strongly encourage you to upgrade if you still haven't done so. Check out the Node.js official [LTS plan] for more information.
+
+## Converted to ES Module
+
+This module was converted to be a ESM only package in version `3.0.0-beta.10`.
+`node-fetch` is an ESM-only module - you are not able to import it with `require`. We recommend you stay on v2 which is built with CommonJS unless you use ESM yourself. We will continue to publish critical bug fixes for it.
+
+Alternatively, you can use the async `import()` function from CommonJS to load `node-fetch` asynchronously:
+
+```js
+// mod.cjs
+const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
+```
+
+## The `timeout` option was removed.
+
+Since this was never part of the fetch specification, it was removed. AbortSignal offers more fine grained control of request timeouts, and is standardized in the Fetch spec. For convenience, you can use [timeout-signal](https://github.com/node-fetch/timeout-signal) as a workaround:
+
+```js
+import timeoutSignal from 'timeout-signal';
+import fetch from 'node-fetch';
+
+const {AbortError} = fetch
+
+fetch('https://www.google.com', { signal: timeoutSignal(5000) })
+ .then(response => {
+ // Handle response
+ })
+ .catch(error => {
+ if (error instanceof AbortError) {
+ // Handle timeout
+ }
+ })
+```
+
+## `Response.statusText` no longer sets a default message derived from the HTTP status code
+
+If the server didn't respond with status text, node-fetch would set a default message derived from the HTTP status code. This behavior was not spec-compliant and now the `statusText` will remain blank instead.
+
+## Dropped the `browser` field in package.json
+
+Prior to v3.x, we included a `browser` field in the package.json file. Since node-fetch is intended to be used on the server, we have removed this field. If you are using node-fetch client-side, consider switching to something like [cross-fetch].
+
+## Dropped the `res.textConverted()` function
+
+If you want charset encoding detection, please use the [fetch-charset-detection] package ([documentation][fetch-charset-detection-docs]).
+
+```js
+import fetch from 'node-fetch';
+import convertBody from 'fetch-charset-detection';
+
+fetch('https://somewebsite.com').then(async res => {
+ const buf = await res.arrayBuffer();
+ const text = convertBody(buf, res.headers);
+});
+```
+
+## JSON parsing errors from `res.json()` are of type `SyntaxError` instead of `FetchError`
+
+When attempting to parse invalid json via `res.json()`, a `SyntaxError` will now be thrown instead of a `FetchError` to align better with the spec.
+
+```js
+import fetch from 'node-fetch';
+
+fetch('https://somewebsitereturninginvalidjson.com').then(res => res.json())
+// Throws 'Uncaught SyntaxError: Unexpected end of JSON input' or similar.
+```
+
+## A stream pipeline is now used to forward errors
+
+If you are listening for errors via `res.body.on('error', () => ...)`, replace it with `res.body.once('error', () => ...)` so that your callback is not [fired twice](https://github.com/node-fetch/node-fetch/issues/668#issuecomment-569386115) in NodeJS >=13.5.
+
+## `req.body` can no longer be a string
+
+We are working towards changing body to become either null or a stream.
+
+## Changed default user agent
+
+The default user agent has been changed from `node-fetch/1.0 (+https://github.com/node-fetch/node-fetch)` to `node-fetch (+https://github.com/node-fetch/node-fetch)`.
+
+## Arbitrary URLs are no longer supported
+
+Since in 3.x we are using the WHATWG's `new URL()`, arbitrary URL parsing will fail due to lack of base.
+
+# Enhancements
+
+## Data URI support
+
+Previously, node-fetch only supported http url scheme. However, the Fetch Standard recently introduced the `data:` URI support. Following the specification, we implemented this feature in v3.x. Read more about `data:` URLs [here][data-url].
+
+## New & exposed Blob implementation
+
+Blob implementation is now [fetch-blob] and hence is exposed, unlikely previously, where Blob type was only internal and not exported.
+
+## Better UTF-8 URL handling
+
+We now use the new Node.js [WHATWG-compliant URL API][whatwg-nodejs-url], so UTF-8 URLs are handled properly.
+
+## Request errors are now piped using `stream.pipeline`
+
+Since the v3.x requires at least Node.js 12.20.0, we can utilise the new API.
+
+## Creating Request/Response objects with relative URLs is no longer supported
+
+We introduced Node.js `new URL()` API in 3.x, because it offers better UTF-8 support and is WHATWG URL compatible. The drawback is, given current limit of the API (nodejs/node#12682), it's not possible to support relative URL parsing without hacks.
+Due to the lack of a browsing context in Node.js, we opted to drop support for relative URLs on Request/Response object, and it will now throw errors if you do so.
+The main `fetch()` function will support absolute URLs and data url.
+
+## Bundled TypeScript types
+
+Since v3.x you no longer need to install `@types/node-fetch` package in order to use `node-fetch` with TypeScript.
+
+[whatwg-fetch]: https://fetch.spec.whatwg.org/
+[data-url]: https://fetch.spec.whatwg.org/#data-url-processor
+[LTS plan]: https://github.com/nodejs/LTS#lts-plan
+[cross-fetch]: https://github.com/lquixada/cross-fetch
+[fetch-charset-detection]: https://github.com/Richienb/fetch-charset-detection
+[fetch-charset-detection-docs]: https://richienb.github.io/fetch-charset-detection/globals.html#convertbody
+[fetch-blob]: https://github.com/node-fetch/fetch-blob#readme
+[whatwg-nodejs-url]: https://nodejs.org/api/url.html#url_the_whatwg_url_api
+[changelog]: CHANGELOG.md
diff --git a/example.js b/example.js
new file mode 100644
index 000000000..f19ff803f
--- /dev/null
+++ b/example.js
@@ -0,0 +1,37 @@
+/*
+ Here are some example ways in which you can use node-fetch. Test each code fragment separately so that you don't get errors related to constant reassigning, etc.
+
+ Top-level `await` support is required.
+*/
+
+import fetch from 'node-fetch';
+
+// Plain text or HTML
+const response = await fetch('https://github.com/');
+const body = await response.text();
+
+console.log(body);
+
+// JSON
+const response = await fetch('https://github.com/');
+const json = await response.json();
+
+console.log(json);
+
+// Simple Post
+const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'});
+const json = await response.json();
+
+console.log(json);
+
+// Post with JSON
+const body = {a: 1};
+
+const response = await fetch('https://httpbin.org/post', {
+ method: 'post',
+ body: JSON.stringify(body),
+ headers: {'Content-Type': 'application/json'}
+});
+const json = await response.json();
+
+console.log(json);
diff --git a/package.json b/package.json
index 8e5c883b2..f2c72ca51 100644
--- a/package.json
+++ b/package.json
@@ -1,66 +1,121 @@
{
"name": "node-fetch",
- "version": "2.6.0",
- "description": "A light-weight module that brings window.fetch to node.js",
- "main": "lib/index",
- "browser": "./browser.js",
- "module": "lib/index.mjs",
+ "version": "3.1.1",
+ "description": "A light-weight module that brings Fetch API to node.js",
+ "main": "./src/index.js",
+ "sideEffects": false,
+ "type": "module",
"files": [
- "lib/index.js",
- "lib/index.mjs",
- "lib/index.es.js",
- "browser.js"
+ "src",
+ "@types/index.d.ts"
],
+ "types": "./@types/index.d.ts",
"engines": {
- "node": "4.x || >=6.0.0"
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
- "build": "cross-env BABEL_ENV=rollup rollup -c",
- "prepare": "npm run build",
- "test": "cross-env BABEL_ENV=test mocha --require babel-register --throw-deprecation test/test.js",
- "report": "cross-env BABEL_ENV=coverage nyc --reporter lcov --reporter text mocha -R spec test/test.js",
- "coverage": "cross-env BABEL_ENV=coverage nyc --reporter json --reporter text mocha -R spec test/test.js && codecov -f coverage/coverage-final.json"
+ "test": "mocha",
+ "coverage": "c8 report --reporter=text-lcov | coveralls",
+ "test-types": "tsd",
+ "lint": "xo"
},
"repository": {
"type": "git",
- "url": "https://github.com/bitinn/node-fetch.git"
+ "url": "https://github.com/node-fetch/node-fetch.git"
},
"keywords": [
"fetch",
"http",
- "promise"
+ "promise",
+ "request",
+ "curl",
+ "wget",
+ "xhr",
+ "whatwg"
],
"author": "David Frank",
"license": "MIT",
"bugs": {
- "url": "https://github.com/bitinn/node-fetch/issues"
+ "url": "https://github.com/node-fetch/node-fetch/issues"
+ },
+ "homepage": "https://github.com/node-fetch/node-fetch",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
},
- "homepage": "https://github.com/bitinn/node-fetch",
"devDependencies": {
- "@ungap/url-search-params": "^0.1.2",
- "abort-controller": "^1.1.0",
- "abortcontroller-polyfill": "^1.3.0",
- "babel-core": "^6.26.3",
- "babel-plugin-istanbul": "^4.1.6",
- "babel-preset-env": "^1.6.1",
- "babel-register": "^6.16.3",
- "chai": "^3.5.0",
+ "abort-controller": "^3.0.0",
+ "abortcontroller-polyfill": "^1.7.1",
+ "busboy": "^0.3.1",
+ "c8": "^7.7.2",
+ "chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
- "chai-iterator": "^1.1.1",
- "chai-string": "~1.3.0",
- "codecov": "^3.3.0",
- "cross-env": "^5.2.0",
- "form-data": "^2.3.3",
- "is-builtin-module": "^1.0.0",
- "mocha": "^5.0.0",
- "nyc": "11.9.0",
- "parted": "^0.1.1",
- "promise": "^8.0.3",
- "resumer": "0.0.0",
- "rollup": "^0.63.4",
- "rollup-plugin-babel": "^3.0.7",
- "string-to-arraybuffer": "^1.0.2",
- "whatwg-url": "^5.0.0"
+ "chai-iterator": "^3.0.2",
+ "chai-string": "^1.5.0",
+ "coveralls": "^3.1.0",
+ "delay": "^5.0.0",
+ "form-data": "^4.0.0",
+ "formdata-node": "^4.2.4",
+ "mocha": "^9.1.3",
+ "p-timeout": "^5.0.0",
+ "stream-consumers": "^1.0.1",
+ "tsd": "^0.14.0",
+ "xo": "^0.39.1"
+ },
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.3",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "tsd": {
+ "cwd": "@types",
+ "compilerOptions": {
+ "esModuleInterop": true
+ }
+ },
+ "xo": {
+ "envs": [
+ "node",
+ "browser"
+ ],
+ "ignores": [
+ "example.js"
+ ],
+ "rules": {
+ "complexity": 0,
+ "import/extensions": 0,
+ "import/no-useless-path-segments": 0,
+ "import/no-anonymous-default-export": 0,
+ "import/no-named-as-default": 0,
+ "unicorn/import-index": 0,
+ "unicorn/no-array-reduce": 0,
+ "unicorn/prefer-node-protocol": 0,
+ "unicorn/numeric-separators-style": 0,
+ "unicorn/explicit-length-check": 0,
+ "capitalized-comments": 0,
+ "node/no-unsupported-features/es-syntax": 0,
+ "@typescript-eslint/member-ordering": 0
+ },
+ "overrides": [
+ {
+ "files": "test/**/*.js",
+ "envs": [
+ "node",
+ "mocha"
+ ],
+ "rules": {
+ "max-nested-callbacks": 0,
+ "no-unused-expressions": 0,
+ "no-warning-comments": 0,
+ "new-cap": 0,
+ "guard-for-in": 0,
+ "unicorn/no-array-for-each": 0,
+ "unicorn/prevent-abbreviations": 0,
+ "promise/prefer-await-to-then": 0,
+ "ava/no-import-test-files": 0
+ }
+ }
+ ]
},
- "dependencies": {}
+ "runkitExampleFilename": "example.js"
}
diff --git a/rollup.config.js b/rollup.config.js
deleted file mode 100644
index a201ee455..000000000
--- a/rollup.config.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import isBuiltin from 'is-builtin-module';
-import babel from 'rollup-plugin-babel';
-import tweakDefault from './build/rollup-plugin';
-
-process.env.BABEL_ENV = 'rollup';
-
-export default {
- input: 'src/index.js',
- output: [
- { file: 'lib/index.js', format: 'cjs', exports: 'named' },
- { file: 'lib/index.es.js', format: 'es', exports: 'named', intro: 'process.emitWarning("The .es.js file is deprecated. Use .mjs instead.");' },
- { file: 'lib/index.mjs', format: 'es', exports: 'named' },
- ],
- plugins: [
- babel({
- runtimeHelpers: true
- }),
- tweakDefault()
- ],
- external: function (id) {
- if (isBuiltin(id)) {
- return true;
- }
- id = id.split('/').slice(0, id[0] === '@' ? 2 : 1).join('/');
- return !!require('./package.json').dependencies[id];
- }
-};
diff --git a/src/abort-error.js b/src/abort-error.js
deleted file mode 100644
index cbb13caba..000000000
--- a/src/abort-error.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
- * abort-error.js
- *
- * AbortError interface for cancelled requests
- */
-
-/**
- * Create AbortError instance
- *
- * @param String message Error message for human
- * @return AbortError
- */
-export default function AbortError(message) {
- Error.call(this, message);
-
- this.type = 'aborted';
- this.message = message;
-
- // hide custom error implementation details from end-users
- Error.captureStackTrace(this, this.constructor);
-}
-
-AbortError.prototype = Object.create(Error.prototype);
-AbortError.prototype.constructor = AbortError;
-AbortError.prototype.name = 'AbortError';
diff --git a/src/blob.js b/src/blob.js
deleted file mode 100644
index e1151a955..000000000
--- a/src/blob.js
+++ /dev/null
@@ -1,119 +0,0 @@
-// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
-// (MIT licensed)
-
-import Stream from 'stream';
-
-// fix for "Readable" isn't a named export issue
-const Readable = Stream.Readable;
-
-export const BUFFER = Symbol('buffer');
-const TYPE = Symbol('type');
-
-export default class Blob {
- constructor() {
- this[TYPE] = '';
-
- const blobParts = arguments[0];
- const options = arguments[1];
-
- const buffers = [];
- let size = 0;
-
- if (blobParts) {
- const a = blobParts;
- const length = Number(a.length);
- for (let i = 0; i < length; i++) {
- const element = a[i];
- let buffer;
- if (element instanceof Buffer) {
- buffer = element;
- } else if (ArrayBuffer.isView(element)) {
- buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
- } else if (element instanceof ArrayBuffer) {
- buffer = Buffer.from(element);
- } else if (element instanceof Blob) {
- buffer = element[BUFFER];
- } else {
- buffer = Buffer.from(typeof element === 'string' ? element : String(element));
- }
- size += buffer.length;
- buffers.push(buffer);
- }
- }
-
- this[BUFFER] = Buffer.concat(buffers);
-
- let type = options && options.type !== undefined && String(options.type).toLowerCase();
- if (type && !/[^\u0020-\u007E]/.test(type)) {
- this[TYPE] = type;
- }
- }
- get size() {
- return this[BUFFER].length;
- }
- get type() {
- return this[TYPE];
- }
- text() {
- return Promise.resolve(this[BUFFER].toString())
- }
- arrayBuffer() {
- const buf = this[BUFFER];
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
- return Promise.resolve(ab);
- }
- stream() {
- const readable = new Readable();
- readable._read = () => {};
- readable.push(this[BUFFER]);
- readable.push(null);
- return readable;
- }
- toString() {
- return '[object Blob]'
- }
- slice() {
- const size = this.size;
-
- const start = arguments[0];
- const end = arguments[1];
- let relativeStart, relativeEnd;
- if (start === undefined) {
- relativeStart = 0;
- } else if (start < 0) {
- relativeStart = Math.max(size + start, 0);
- } else {
- relativeStart = Math.min(start, size);
- }
- if (end === undefined) {
- relativeEnd = size;
- } else if (end < 0) {
- relativeEnd = Math.max(size + end, 0);
- } else {
- relativeEnd = Math.min(end, size);
- }
- const span = Math.max(relativeEnd - relativeStart, 0);
-
- const buffer = this[BUFFER];
- const slicedBuffer = buffer.slice(
- relativeStart,
- relativeStart + span
- );
- const blob = new Blob([], { type: arguments[2] });
- blob[BUFFER] = slicedBuffer;
- return blob;
- }
-}
-
-Object.defineProperties(Blob.prototype, {
- size: { enumerable: true },
- type: { enumerable: true },
- slice: { enumerable: true }
-});
-
-Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
- value: 'Blob',
- writable: false,
- enumerable: false,
- configurable: true
-});
diff --git a/src/body.js b/src/body.js
index a9d2e7973..b0fe16bb2 100644
--- a/src/body.js
+++ b/src/body.js
@@ -1,23 +1,24 @@
/**
- * body.js
+ * Body.js
*
* Body interface provides common methods for Request and Response
*/
-import Stream from 'stream';
+import Stream, {PassThrough} from 'node:stream';
+import {types, deprecate, promisify} from 'node:util';
+import {Buffer} from 'node:buffer';
-import Blob, { BUFFER } from './blob.js';
-import FetchError from './fetch-error.js';
+import Blob from 'fetch-blob';
+import {FormData, formDataToBlob} from 'formdata-polyfill/esm.min.js';
-let convert;
-try { convert = require('encoding').convert; } catch(e) {}
+import {FetchError} from './errors/fetch-error.js';
+import {FetchBaseError} from './errors/base.js';
+import {isBlob, isURLSearchParameters} from './utils/is.js';
+const pipeline = promisify(Stream.pipeline);
const INTERNALS = Symbol('Body internals');
-// fix an issue where "PassThrough" isn't a named export for node <10
-const PassThrough = Stream.PassThrough;
-
/**
* Body mixin
*
@@ -27,110 +28,136 @@ const PassThrough = Stream.PassThrough;
* @param Object opts Response options
* @return Void
*/
-export default function Body(body, {
- size = 0,
- timeout = 0
-} = {}) {
- if (body == null) {
- // body is undefined or null
- body = null;
- } else if (isURLSearchParams(body)) {
- // body is a URLSearchParams
- body = Buffer.from(body.toString());
- } else if (isBlob(body)) {
- // body is blob
- } else if (Buffer.isBuffer(body)) {
- // body is Buffer
- } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') {
- // body is ArrayBuffer
- body = Buffer.from(body);
- } else if (ArrayBuffer.isView(body)) {
- // body is ArrayBufferView
- body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
- } else if (body instanceof Stream) {
- // body is stream
- } else {
- // none of the above
- // coerce to string then buffer
- body = Buffer.from(String(body));
- }
- this[INTERNALS] = {
- body,
- disturbed: false,
- error: null
- };
- this.size = size;
- this.timeout = timeout;
+export default class Body {
+ constructor(body, {
+ size = 0
+ } = {}) {
+ let boundary = null;
+
+ if (body === null) {
+ // Body is undefined or null
+ body = null;
+ } else if (isURLSearchParameters(body)) {
+ // Body is a URLSearchParams
+ body = Buffer.from(body.toString());
+ } else if (isBlob(body)) {
+ // Body is blob
+ } else if (Buffer.isBuffer(body)) {
+ // Body is Buffer
+ } else if (types.isAnyArrayBuffer(body)) {
+ // Body is ArrayBuffer
+ body = Buffer.from(body);
+ } else if (ArrayBuffer.isView(body)) {
+ // Body is ArrayBufferView
+ body = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
+ } else if (body instanceof Stream) {
+ // Body is stream
+ } else if (body instanceof FormData) {
+ // Body is FormData
+ body = formDataToBlob(body);
+ boundary = body.type.split('=')[1];
+ } else {
+ // None of the above
+ // coerce to string then buffer
+ body = Buffer.from(String(body));
+ }
- if (body instanceof Stream) {
- body.on('error', err => {
- const error = err.name === 'AbortError'
- ? err
- : new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);
- this[INTERNALS].error = error;
- });
+ let stream = body;
+
+ if (Buffer.isBuffer(body)) {
+ stream = Stream.Readable.from(body);
+ } else if (isBlob(body)) {
+ stream = Stream.Readable.from(body.stream());
+ }
+
+ this[INTERNALS] = {
+ body,
+ stream,
+ boundary,
+ disturbed: false,
+ error: null
+ };
+ this.size = size;
+
+ if (body instanceof Stream) {
+ body.on('error', error_ => {
+ const error = error_ instanceof FetchBaseError ?
+ error_ :
+ new FetchError(`Invalid response body while trying to fetch ${this.url}: ${error_.message}`, 'system', error_);
+ this[INTERNALS].error = error;
+ });
+ }
}
-}
-Body.prototype = {
get body() {
- return this[INTERNALS].body;
- },
+ return this[INTERNALS].stream;
+ }
get bodyUsed() {
return this[INTERNALS].disturbed;
- },
+ }
/**
* Decode response as ArrayBuffer
*
* @return Promise
*/
- arrayBuffer() {
- return consumeBody.call(this).then(buf => buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength));
- },
+ async arrayBuffer() {
+ const {buffer, byteOffset, byteLength} = await consumeBody(this);
+ return buffer.slice(byteOffset, byteOffset + byteLength);
+ }
+
+ async formData() {
+ const ct = this.headers.get('content-type');
+
+ if (ct.startsWith('application/x-www-form-urlencoded')) {
+ const formData = new FormData();
+ const parameters = new URLSearchParams(await this.text());
+
+ for (const [name, value] of parameters) {
+ formData.append(name, value);
+ }
+
+ return formData;
+ }
+
+ const {toFormData} = await import('./utils/multipart-parser.js');
+ return toFormData(this.body, ct);
+ }
/**
* Return raw response as Blob
*
* @return Promise
*/
- blob() {
- let ct = this.headers && this.headers.get('content-type') || '';
- return consumeBody.call(this).then(buf => Object.assign(
- // Prevent copying
- new Blob([], {
- type: ct.toLowerCase()
- }),
- {
- [BUFFER]: buf
- }
- ));
- },
+ async blob() {
+ const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].body.type) || '';
+ const buf = await this.arrayBuffer();
+
+ return new Blob([buf], {
+ type: ct
+ });
+ }
/**
* Decode response as json
*
* @return Promise
*/
- json() {
- return consumeBody.call(this).then((buffer) => {
- try {
- return JSON.parse(buffer.toString());
- } catch (err) {
- return Body.Promise.reject(new FetchError(`invalid json response body at ${this.url} reason: ${err.message}`, 'invalid-json'));
- }
- })
- },
+ async json() {
+ const buffer = await consumeBody(this);
+ return JSON.parse(buffer.toString());
+ }
/**
* Decode response as text
*
* @return Promise
*/
- text() {
- return consumeBody.call(this).then(buffer => buffer.toString());
- },
+ async text() {
+ const buffer = await consumeBody(this);
+ return buffer.toString();
+ }
/**
* Decode response as buffer (non-spec api)
@@ -138,281 +165,129 @@ Body.prototype = {
* @return Promise
*/
buffer() {
- return consumeBody.call(this);
- },
-
- /**
- * Decode response as text, while automatically detecting the encoding and
- * trying to decode to UTF-8 (non-spec api)
- *
- * @return Promise
- */
- textConverted() {
- return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers));
+ return consumeBody(this);
}
-};
+}
+
+Body.prototype.buffer = deprecate(Body.prototype.buffer, 'Please use \'response.arrayBuffer()\' instead of \'response.buffer()\'', 'node-fetch#buffer');
// In browsers, all properties are enumerable.
Object.defineProperties(Body.prototype, {
- body: { enumerable: true },
- bodyUsed: { enumerable: true },
- arrayBuffer: { enumerable: true },
- blob: { enumerable: true },
- json: { enumerable: true },
- text: { enumerable: true }
+ body: {enumerable: true},
+ bodyUsed: {enumerable: true},
+ arrayBuffer: {enumerable: true},
+ blob: {enumerable: true},
+ json: {enumerable: true},
+ text: {enumerable: true},
+ data: {get: deprecate(() => {},
+ 'data doesn\'t exist, use json(), text(), arrayBuffer(), or body instead',
+ 'https://github.com/node-fetch/node-fetch/issues/1000 (response)')}
});
-Body.mixIn = function (proto) {
- for (const name of Object.getOwnPropertyNames(Body.prototype)) {
- // istanbul ignore else: future proof
- if (!(name in proto)) {
- const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
- Object.defineProperty(proto, name, desc);
- }
- }
-};
-
/**
* Consume and convert an entire Body to a Buffer.
*
* Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body
*
- * @return Promise
+ * @return Promise
*/
-function consumeBody() {
- if (this[INTERNALS].disturbed) {
- return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`));
+async function consumeBody(data) {
+ if (data[INTERNALS].disturbed) {
+ throw new TypeError(`body used already for: ${data.url}`);
}
- this[INTERNALS].disturbed = true;
+ data[INTERNALS].disturbed = true;
- if (this[INTERNALS].error) {
- return Body.Promise.reject(this[INTERNALS].error);
+ if (data[INTERNALS].error) {
+ throw data[INTERNALS].error;
}
- let body = this.body;
+ const {body} = data;
- // body is null
+ // Body is null
if (body === null) {
- return Body.Promise.resolve(Buffer.alloc(0));
- }
-
- // body is blob
- if (isBlob(body)) {
- body = body.stream();
+ return Buffer.alloc(0);
}
- // body is buffer
- if (Buffer.isBuffer(body)) {
- return Body.Promise.resolve(body);
- }
-
- // istanbul ignore if: should never happen
+ /* c8 ignore next 3 */
if (!(body instanceof Stream)) {
- return Body.Promise.resolve(Buffer.alloc(0));
+ return Buffer.alloc(0);
}
- // body is stream
+ // Body is stream
// get ready to actually consume the body
- let accum = [];
+ const accum = [];
let accumBytes = 0;
- let abort = false;
-
- return new Body.Promise((resolve, reject) => {
- let resTimeout;
-
- // allow timeout on slow response body
- if (this.timeout) {
- resTimeout = setTimeout(() => {
- abort = true;
- reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout'));
- }, this.timeout);
- }
-
- // handle stream errors
- body.on('error', err => {
- if (err.name === 'AbortError') {
- // if the request was aborted, reject with this Error
- abort = true;
- reject(err);
- } else {
- // other errors, such as incorrect content-encoding
- reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err));
- }
- });
- body.on('data', chunk => {
- if (abort || chunk === null) {
- return;
- }
-
- if (this.size && accumBytes + chunk.length > this.size) {
- abort = true;
- reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
- return;
+ try {
+ for await (const chunk of body) {
+ if (data.size > 0 && accumBytes + chunk.length > data.size) {
+ const error = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
+ body.destroy(error);
+ throw error;
}
accumBytes += chunk.length;
accum.push(chunk);
- });
-
- body.on('end', () => {
- if (abort) {
- return;
- }
-
- clearTimeout(resTimeout);
-
- try {
- resolve(Buffer.concat(accum, accumBytes));
- } catch (err) {
- // handle streams that have accumulated too much data (issue #414)
- reject(new FetchError(`Could not create Buffer from response body for ${this.url}: ${err.message}`, 'system', err));
- }
- });
- });
-}
-
-/**
- * Detect buffer encoding and convert to target encoding
- * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
- *
- * @param Buffer buffer Incoming buffer
- * @param String encoding Target encoding
- * @return String
- */
-function convertBody(buffer, headers) {
- if (typeof convert !== 'function') {
- throw new Error('The package `encoding` must be installed to use the textConverted() function');
- }
-
- const ct = headers.get('content-type');
- let charset = 'utf-8';
- let res, str;
-
- // header
- if (ct) {
- res = /charset=([^;]*)/i.exec(ct);
- }
-
- // no charset in content type, peek at response body for at most 1024 bytes
- str = buffer.slice(0, 1024).toString();
-
- // html5
- if (!res && str) {
- res = /