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)*
+_(node-fetch extension)_
-* Returns: Promise<Buffer>
+- Returns: `PromisePromise<String>
-
-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.
-
-(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.)
-
+
### Class: FetchError
-*(node-fetch extension)*
+_(node-fetch extension)_
An operational error in the fetching process. See [ERROR-HANDLING.md][] for more info.
+
### Class: AbortError
-*(node-fetch extension)*
+_(node-fetch extension)_
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.
+## TypeScript
+
+**Since `3.x` types are bundled with `node-fetch`, so you don't need to install any additional packages.**
+
+For older versions please use the type definitions from [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped):
+
+```sh
+npm install --save-dev @types/node-fetch
+```
+
## Acknowledgement
Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
-`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).
+## Team
+
+| [](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) |
+
+###### Former
+
+- [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/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/CHANGELOG.md b/docs/CHANGELOG.md
similarity index 64%
rename from CHANGELOG.md
rename to docs/CHANGELOG.md
index 188fcd399..71ec19ae7 100644
--- a/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,10 +1,126 @@
+# Changelog
+All notable changes will be recorded here.
-Changelog
-=========
+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).
+
+## 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.
@@ -40,7 +156,7 @@ Changelog
## 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 `--exerimental-modules` flag.
+- 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
@@ -74,7 +190,7 @@ Fix packaging errors in v2.1.0.
## v2.0.0
-This is a major release. Check [our upgrade guide](https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md) for an overview on some key differences between v1 and v2.
+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
@@ -99,7 +215,7 @@ This is a major release. Check [our upgrade guide](https://github.com/bitinn/nod
### 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 respose (per spec; reverts behavior changed in v1.6.2)
+- 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)
@@ -124,9 +240,9 @@ This is a major release. Check [our upgrade guide](https://github.com/bitinn/nod
# 1.x release
-## backport releases (v1.7.0 and beyond)
+## Backport releases (v1.7.0 and beyond)
-See [changelog on 1.x branch](https://github.com/bitinn/node-fetch/blob/1.x/CHANGELOG.md) for details.
+See [changelog on 1.x branch](https://github.com/node-fetch/node-fetch/blob/1.x/CHANGELOG.md) for details.
## v1.6.3
@@ -264,3 +380,5 @@ See [changelog on 1.x branch](https://github.com/bitinn/node-fetch/blob/1.x/CHAN
## 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..6252c2125 100644
--- a/package.json
+++ b/package.json
@@ -1,66 +1,118 @@
{
"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.0.0",
+ "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": "^3.5.4",
+ "mocha": "^8.3.2",
+ "p-timeout": "^5.0.0",
+ "tsd": "^0.14.0",
+ "xo": "^0.39.1"
+ },
+ "dependencies": {
+ "data-uri-to-buffer": "^3.0.1",
+ "fetch-blob": "^3.1.2"
+ },
+ "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,
+ "@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..c923a8ce5 100644
--- a/src/body.js
+++ b/src/body.js
@@ -1,23 +1,22 @@
/**
- * body.js
+ * Body.js
*
* Body interface provides common methods for Request and Response
*/
-import Stream from 'stream';
+import Stream, {PassThrough} from 'stream';
+import {types} from 'util';
-import Blob, { BUFFER } from './blob.js';
-import FetchError from './fetch-error.js';
+import Blob from 'fetch-blob';
-let convert;
-try { convert = require('encoding').convert; } catch(e) {}
+import {FetchError} from './errors/fetch-error.js';
+import {FetchBaseError} from './errors/base.js';
+import {formDataIterator, getBoundary, getFormDataLength} from './utils/form-data.js';
+import {isBlob, isURLSearchParameters, isFormData} from './utils/is.js';
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 +26,109 @@ 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 (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
+ 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;
- });
+ this[INTERNALS] = {
+ body,
+ 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;
- },
+ }
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);
+ }
/**
* 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.buffer();
+
+ 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 +136,128 @@ 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);
}
-};
+}
// 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}
});
-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;
+ let {body} = data;
- // body is null
+ // Body is null
if (body === null) {
- return Body.Promise.resolve(Buffer.alloc(0));
+ return Buffer.alloc(0);
}
- // body is blob
+ // Body is blob
if (isBlob(body)) {
- body = body.stream();
+ body = Stream.Readable.from(body.stream());
}
- // body is buffer
+ // Body is buffer
if (Buffer.isBuffer(body)) {
- return Body.Promise.resolve(body);
+ return 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 = /