Socket
Socket
Sign inDemoInstall

node-fetch

Package Overview
Dependencies
4
Maintainers
4
Versions
96
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.0.0-beta.6-exportfix to 3.0.0

@types/index.d.ts

240

package.json
{
"name": "node-fetch",
"version": "3.0.0-beta.6-exportfix",
"description": "A light-weight module that brings window.fetch to node.js",
"main": "./dist/index.cjs",
"module": "./src/index.js",
"sideEffects": false,
"type": "module",
"exports": {
"import": "./src/index.js",
"require": "./dist/index.cjs"
},
"files": [
"src",
"dist",
"@types/index.d.ts"
],
"types": "./@types/index.d.ts",
"engines": {
"node": ">=10.16"
},
"scripts": {
"build": "rollup -c",
"test": "node --experimental-modules node_modules/c8/bin/c8 --reporter=html --reporter=lcov --reporter=text --check-coverage node --experimental-modules node_modules/mocha/bin/mocha",
"coverage": "c8 report --reporter=text-lcov | coveralls",
"test-types": "tsd",
"lint": "xo"
},
"repository": {
"type": "git",
"url": "https://github.com/node-fetch/node-fetch.git"
},
"keywords": [
"fetch",
"http",
"promise"
],
"author": "David Frank",
"license": "MIT",
"bugs": {
"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"
},
"devDependencies": {
"abort-controller": "^3.0.0",
"abortcontroller-polyfill": "^1.4.0",
"c8": "^7.1.2",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"chai-iterator": "^3.0.2",
"chai-string": "^1.5.0",
"coveralls": "^3.1.0",
"delay": "^4.3.0",
"form-data": "^3.0.0",
"mocha": "^7.1.2",
"p-timeout": "^3.2.0",
"parted": "^0.1.1",
"promise": "^8.1.0",
"resumer": "0.0.0",
"rollup": "^2.10.8",
"string-to-arraybuffer": "^1.0.2",
"tsc": "^1.20150623.0",
"tsd": "^0.11.0",
"xo": "^0.30.0"
},
"dependencies": {
"data-uri-to-buffer": "^3.0.0",
"fetch-blob": "^1.0.6"
},
"tsd": {
"cwd": "@types",
"compilerOptions": {
"target": "esnext",
"lib": [
"es2018"
],
"allowSyntheticDefaultImports": true
}
},
"xo": {
"envs": [
"node",
"browser"
],
"rules": {
"complexity": 0,
"import/extensions": 0,
"import/no-useless-path-segments": 0,
"unicorn/import-index": 0,
"capitalized-comments": 0
},
"ignores": [
"dist",
"@types"
],
"overrides": [
{
"files": "test/**/*.js",
"envs": [
"node",
"mocha"
],
"rules": {
"max-nested-callbacks": 0,
"no-unused-expressions": 0,
"new-cap": 0,
"guard-for-in": 0,
"unicorn/prevent-abbreviations": 0,
"promise/prefer-await-to-then": 0,
"ava/no-import-test-files": 0
}
},
{
"files": "example.js",
"rules": {
"import/no-extraneous-dependencies": 0
}
}
]
},
"runkitExampleFilename": "example.js"
"name": "node-fetch",
"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": [
"src",
"@types/index.d.ts"
],
"types": "./@types/index.d.ts",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "mocha",
"coverage": "c8 report --reporter=text-lcov | coveralls",
"test-types": "tsd",
"lint": "xo"
},
"repository": {
"type": "git",
"url": "https://github.com/node-fetch/node-fetch.git"
},
"keywords": [
"fetch",
"http",
"promise",
"request",
"curl",
"wget",
"xhr",
"whatwg"
],
"author": "David Frank",
"license": "MIT",
"bugs": {
"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"
},
"devDependencies": {
"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": "^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
}
}
]
},
"runkitExampleFilename": "example.js"
}
<div align="center">
<img src="docs/media/Banner.svg" alt="Node Fetch"/>
<br>
<p>A light-weight module that brings <code>window.fetch</code> to Node.js.</p>
<img src="docs/media/Banner.svg" alt="Node Fetch"/>
<br>
<p>A light-weight module that brings <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> to Node.js.</p>
<a href="https://github.com/node-fetch/node-fetch/actions"><img src="https://github.com/node-fetch/node-fetch/workflows/CI/badge.svg?branch=master" alt="Build status"></a>

@@ -21,2 +21,4 @@ <a href="https://coveralls.io/github/node-fetch/node-fetch"><img src="https://img.shields.io/coveralls/github/node-fetch/node-fetch" alt="Coverage status"></a>

**You might be looking for the [v2 docs](https://github.com/node-fetch/node-fetch/tree/2.x#readme)**
<!-- TOC -->

@@ -53,2 +55,3 @@

- [Custom highWaterMark](#custom-highwatermark)
- [Insecure HTTP Parser](#insecure-http-parser)
- [Class: Request](#class-request)

@@ -60,2 +63,3 @@ - [new Request(input[, options])](#new-requestinput-options)

- [response.redirected](#responseredirected)
- [response.type](#responsetype)
- [Class: Headers](#class-headers)

@@ -91,5 +95,5 @@ - [new Headers([init])](#new-headersinit)

- Make conscious trade-off when following [WHATWG fetch spec][whatwg-fetch] and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known differences.
- Use native promise, but allow substituting it with [insert your favorite promise library].
- Use native promise and async functions.
- Use native Node streams for body, on both request and response.
- Decode content encoding (gzip/deflate) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
- Decode content encoding (gzip/deflate/brotli) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
- Useful extensions such as redirect limit, response size limit, [explicit errors][error-handling.md] for troubleshooting.

@@ -107,6 +111,6 @@

Current stable release (`3.x`)
Current stable release (`3.x`) requires at least Node.js 12.20.0.
```sh
$ npm install node-fetch
npm install node-fetch
```

@@ -117,30 +121,24 @@

```js
// CommonJS
const fetch = require('node-fetch');
// ES Module
import fetch from 'node-fetch';
```
If you are using a Promise library other than native, set it through `fetch.Promise`:
If you want to patch the global object in node:
```js
const fetch = require('node-fetch');
const Bluebird = require('bluebird');
import fetch from 'node-fetch';
fetch.Promise = Bluebird;
if (!globalThis.fetch) {
globalThis.fetch = fetch;
}
```
If you want to patch the global object in node:
`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
const fetch = require('node-fetch');
if (!globalThis.fetch) {
globalThis.fetch = fetch;
}
// mod.cjs
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
```
For versions of Node earlier than 12, use this `globalThis` [polyfill](https://mathiasbynens.be/notes/globalthis).
## Upgrading

@@ -161,10 +159,8 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://github.com/');
const body = await response.text();
const response = await fetch('https://github.com/');
const body = await response.text();
console.log(body);
})();
console.log(body);
```

@@ -175,10 +171,8 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://api.github.com/users/github');
const json = await response.json();
const response = await fetch('https://api.github.com/users/github');
const data = await response.json();
console.log(json);
})();
console.log(data);
```

@@ -189,10 +183,8 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'});
const json = await response.json();
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'});
const data = await response.json();
console.log(json);
})();
console.log(data);
```

@@ -203,16 +195,14 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const body = {a: 1};
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();
const response = await fetch('https://httpbin.org/post', {
method: 'post',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json'}
});
const data = await response.json();
console.log(json);
})();
console.log(data);
```

@@ -227,3 +217,3 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';

@@ -233,8 +223,6 @@ const params = new URLSearchParams();

(async () => {
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: params});
const json = await response.json();
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: params});
const data = await response.json();
console.log(json);
})();
console.log(data);
```

@@ -249,6 +237,6 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
try {
fetch('https://domain.invalid/');
await fetch('https://domain.invalid/');
} catch (error) {

@@ -264,19 +252,30 @@ console.log(error);

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
const checkStatus = res => {
if (res.ok) {
// res.status >= 200 && res.status < 300
return res;
class HTTPResponseError extends Error {
constructor(response, ...args) {
this.response = response;
super(`HTTP Error Response: ${response.status} ${response.statusText}`, ...args);
}
}
const checkStatus = response => {
if (response.ok) {
// response.status >= 200 && response.status < 300
return response;
} else {
throw MyCustomError(res.statusText);
throw new HTTPResponseError(response);
}
}
(async () => {
const response = await fetch('https://httpbin.org/status/400');
const data = checkStatus(response);
const response = await fetch('https://httpbin.org/status/400');
console.log(data); //=> MyCustomError
})();
try {
checkStatus(response);
} catch (error) {
console.error(error);
const errorBody = await error.response.text();
console.error(`Error body: ${errorBody}`);
}
```

@@ -295,15 +294,63 @@

```js
const util = require('util');
const fs = require('fs');
const streamPipeline = util.promisify(require('stream').pipeline);
import {createWriteStream} from 'fs';
import {pipeline} from 'stream';
import {promisify} from 'util'
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png');
if (response.ok) {
return streamPipeline(res.body, fs.createWriteStream('./octocat.png'));
const streamPipeline = promisify(pipeline);
const response = await fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png');
if (!response.ok) throw new Error(`unexpected response ${response.statusText}`);
await streamPipeline(response.body, createWriteStream('./octocat.png'));
```
In Node.js 14 you can also use async iterators to read `body`; however, be careful to catch
errors -- the longer a response runs, the more likely it is to encounter an error.
```js
import fetch from 'node-fetch';
const response = await fetch('https://httpbin.org/stream/3');
try {
for await (const chunk of response.body) {
console.dir(JSON.parse(chunk.toString()));
}
} catch (err) {
console.error(err.stack);
}
```
throw new Error(`unexpected response ${res.statusText}`);
})();
In Node.js 12 you can also use async iterators to read `body`; however, async iterators with streams
did not mature until Node.js 14, so you need to do some extra work to ensure you handle errors
directly from the stream and wait on it response to fully close.
```js
import fetch from 'node-fetch';
const read = async body => {
let error;
body.on('error', err => {
error = err;
});
for await (const chunk of body) {
console.dir(JSON.parse(chunk.toString()));
}
return new Promise((resolve, reject) => {
body.on('close', () => {
error ? reject(error) : resolve();
});
});
};
try {
const response = await fetch('https://httpbin.org/stream/3');
await read(response.body);
} catch (err) {
console.error(err.stack);
}
```

@@ -316,12 +363,10 @@

```js
const fetch = require('node-fetch');
const fileType = require('file-type');
import fetch from 'node-fetch';
import fileType from 'file-type';
(async () => {
const response = await fetch('https://octodex.github.com/images/Fintechtocat.png');
const buffer = await response.buffer();
const type = fileType.fromBuffer(buffer)
console.log(type);
})();
const response = await fetch('https://octodex.github.com/images/Fintechtocat.png');
const buffer = await response.buffer();
const type = await fileType.fromBuffer(buffer)
console.log(type);
```

@@ -332,13 +377,11 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://github.com/');
console.log(res.ok);
console.log(res.status);
console.log(res.statusText);
console.log(res.headers.raw());
console.log(res.headers.get('content-type'));
})();
const response = await fetch('https://github.com/');
console.log(response.ok);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers.raw());
console.log(response.headers.get('content-type'));
```

@@ -351,10 +394,8 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://example.com');
// Returns an array of values, instead of a string of comma-separated values
console.log(res.headers.raw()['set-cookie']);
})();
const response = await fetch('https://example.com');
// Returns an array of values, instead of a string of comma-separated values
console.log(response.headers.raw()['set-cookie']);
```

@@ -365,48 +406,32 @@

```js
const {createReadStream} = require('fs');
const fetch = require('node-fetch');
import {createReadStream} from 'fs';
import fetch from 'node-fetch';
const stream = createReadStream('input.txt');
(async () => {
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: stream});
const json = await response.json();
console.log(json)
})();
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: stream});
const data = await response.json();
console.log(data)
```
### Post with form-data (detect multipart)
node-fetch also supports spec-compliant FormData implementations such as [formdata-polyfill](https://www.npmjs.com/package/formdata-polyfill) and [formdata-node](https://github.com/octet-stream/form-data):
```js
const fetch = require('node-fetch');
const FormData = require('form-data');
import fetch from 'node-fetch';
import {FormData} from 'formdata-polyfill/esm-min.js';
// Alternative package:
import {FormData} from 'formdata-node';
const form = new FormData();
form.append('a', 1);
form.set('greeting', 'Hello, world!');
(async () => {
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: form});
const json = await response.json();
console.log(json)
})();
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: form});
const data = await response.json();
// OR, using custom headers
// NOTE: getHeaders() is non-standard API
console.log(data);
```
const options = {
method: 'POST',
body: form,
headers: form.getHeaders()
};
node-fetch also support form-data but it's now discouraged due to not being spec-compliant and needs workarounds to function - which we hope to remove one day
(async () => {
const response = await fetch('https://httpbin.org/post', options);
const json = await response.json();
console.log(json)
})();
```
### Request cancellation with AbortSignal

@@ -419,4 +444,4 @@

```js
const fetch = require('node-fetch');
const AbortController = require('abort-controller');
import fetch from 'node-fetch';
import AbortController from 'abort-controller';

@@ -428,16 +453,12 @@ const controller = new AbortController();

(async () => {
try {
const response = await fetch('https://example.com', {signal: controller.signal});
const data = await response.json();
useData(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('request was aborted');
}
} finally {
clearTimeout(timeout);
try {
const response = await fetch('https://example.com', {signal: controller.signal});
const data = await response.json();
} catch (error) {
if (error instanceof fetch.AbortError) {
console.log('request was aborted');
}
})();
} finally {
clearTimeout(timeout);
}
```

@@ -467,15 +488,16 @@

{
// These properties are part of the Fetch Standard
method: 'GET',
headers: {}, // Request headers. format is the identical to that accepted by the Headers constructor (see below)
body: null, // Request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
redirect: 'follow', // Set to `manual` to extract redirect headers, `error` to reject redirect
signal: null, // Pass an instance of AbortSignal to optionally abort requests
// These properties are part of the Fetch Standard
method: 'GET',
headers: {}, // Request headers. format is the identical to that accepted by the Headers constructor (see below)
body: null, // Request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream
redirect: 'follow', // Set to `manual` to extract redirect headers, `error` to reject redirect
signal: null, // Pass an instance of AbortSignal to optionally abort requests
// The following properties are node-fetch extensions
follow: 20, // maximum redirect count. 0 to not follow redirect
compress: true, // support gzip/deflate content encoding. false to disable
size: 0, // maximum response body size in bytes. 0 to disable
agent: null, // http(s).Agent instance or function that returns an instance (see below)
highWaterMark: 16384 // the maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource.
// The following properties are node-fetch extensions
follow: 20, // maximum redirect count. 0 to not follow redirect
compress: true, // support gzip/deflate content encoding. false to disable
size: 0, // maximum response body size in bytes. 0 to disable
agent: null, // http(s).Agent instance or function that returns an instance (see below)
highWaterMark: 16384, // the maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource.
insecureHTTPParser: false // Use an insecure HTTP parser that accepts invalid HTTP headers when `true`.
}

@@ -513,4 +535,4 @@ ```

```js
const http = require('http');
const https = require('https');
import http from 'http';
import https from 'https';

@@ -544,13 +566,11 @@ const httpAgent = new http.Agent({

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://example.com');
const r1 = await response.clone();
return Promise.all([res.json(), r1.text()]).then(results => {
console.log(results[0]);
console.log(results[1]);
});
})();
const response = await fetch('https://example.com');
const r1 = await response.clone();
const results = await Promise.all([response.json(), r1.text()]);
console.log(results[0]);
console.log(results[1]);
```

@@ -561,14 +581,18 @@

```js
const fetch = require('node-fetch');
import fetch from 'node-fetch';
(async () => {
const response = await fetch('https://example.com', {
// About 1MB
highWaterMark: 1024 * 1024
});
return res.clone().buffer();
})();
const response = await fetch('https://example.com', {
// About 1MB
highWaterMark: 1024 * 1024
});
const result = await res.clone().buffer();
console.dir(result);
```
#### Insecure HTTP Parser
Passed through to the `insecureHTTPParser` option on http(s).request. See [`http.request`](https://nodejs.org/api/http.html#http_http_request_url_options_callback) for more information.
<a id="class-request"></a>

@@ -621,5 +645,2 @@

- `Response.error()`
- `Response.redirect()`
- `type`
- `trailer`

@@ -650,2 +671,8 @@

#### response.type
<small>_(deviation from spec)_</small>
Convenience property representing the response's type. node-fetch only supports `'default'` and `'error'` and does not make use of [filtered responses](https://fetch.spec.whatwg.org/#concept-filtered-response).
<a id="class-headers"></a>

@@ -667,3 +694,3 @@

// Example adapted from https://fetch.spec.whatwg.org/#example-headers-class
const Headers = require('node-fetch');
import {Headers} from 'node-fetch';

@@ -759,3 +786,3 @@ const meta = {

```sh
$ npm install --save-dev @types/node-fetch
npm install --save-dev @types/node-fetch
```

@@ -771,3 +798,3 @@

| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| [David Frank](https://bitinn.net/) | [Jimmy Wärting](https://jimmy.warting.se/) | [Antoni Kepinski](https://kepinski.me) | [Richie Bendall](https://www.richie-bendall.ml/) | [Gregor Martynus](https://twitter.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) |

@@ -774,0 +801,0 @@ ###### Former

@@ -8,9 +8,12 @@

import Stream, {finished, PassThrough} from 'stream';
import Stream, {PassThrough} from 'stream';
import {types} from 'util';
import Blob from 'fetch-blob';
import FetchError from './errors/fetch-error.js';
import {isBlob, isURLSearchParameters, isAbortError} from './utils/is.js';
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');

@@ -31,2 +34,4 @@

} = {}) {
let boundary = null;
if (body === null) {

@@ -50,2 +55,6 @@ // Body is undefined or null

// 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 {

@@ -59,2 +68,3 @@ // None of the above

body,
boundary,
disturbed: false,

@@ -66,6 +76,6 @@ error: null

if (body instanceof Stream) {
body.on('error', err => {
const error = isAbortError(err) ?
err :
new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err);
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;

@@ -101,7 +111,6 @@ });

const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].body.type) || '';
const buf = await consumeBody(this);
const buf = await this.buffer();
return new Blob([], {
type: ct.toLowerCase(),
buffer: buf
return new Blob([buf], {
type: ct
});

@@ -155,7 +164,7 @@ }

*
* @return Promise
* @return Promise
*/
const consumeBody = data => {
async function consumeBody(data) {
if (data[INTERNALS].disturbed) {
return Body.Promise.reject(new TypeError(`body used already for: ${data.url}`));
throw new TypeError(`body used already for: ${data.url}`);
}

@@ -166,3 +175,3 @@

if (data[INTERNALS].error) {
return Body.Promise.reject(data[INTERNALS].error);
throw data[INTERNALS].error;
}

@@ -174,3 +183,3 @@

if (body === null) {
return Body.Promise.resolve(Buffer.alloc(0));
return Buffer.alloc(0);
}

@@ -180,3 +189,3 @@

if (isBlob(body)) {
body = body.stream();
body = Stream.Readable.from(body.stream());
}

@@ -186,3 +195,3 @@

if (Buffer.isBuffer(body)) {
return Body.Promise.resolve(body);
return body;
}

@@ -192,3 +201,3 @@

if (!(body instanceof Stream)) {
return Body.Promise.resolve(Buffer.alloc(0));
return Buffer.alloc(0);
}

@@ -200,46 +209,34 @@

let accumBytes = 0;
let abort = false;
return new Body.Promise((resolve, reject) => {
body.on('data', chunk => {
if (abort || chunk === null) {
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;
}
if (data.size && accumBytes + chunk.length > data.size) {
abort = true;
reject(new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size'));
return;
}
accumBytes += chunk.length;
accum.push(chunk);
});
}
} catch (error) {
const error_ = error instanceof FetchBaseError ? error : new FetchError(`Invalid response body while trying to fetch ${data.url}: ${error.message}`, 'system', error);
throw error_;
}
finished(body, {writable: false}, err => {
if (err) {
if (isAbortError(err)) {
// 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 ${data.url}: ${err.message}`, 'system', err));
}
} else {
if (abort) {
return;
}
try {
resolve(Buffer.concat(accum, accumBytes));
} catch (error) {
// Handle streams that have accumulated too much data (issue #414)
reject(new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error));
}
if (body.readableEnded === true || body._readableState.ended === true) {
try {
if (accum.every(c => typeof c === 'string')) {
return Buffer.from(accum.join(''));
}
});
});
};
return Buffer.concat(accum, accumBytes);
} catch (error) {
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
}
} else {
throw new FetchError(`Premature close of server response while trying to fetch ${data.url}`);
}
}
/**

@@ -288,3 +285,3 @@ * Clone body given Res/Req instance

*/
export const extractContentType = body => {
export const extractContentType = (body, request) => {
// Body is null or undefined

@@ -320,2 +317,6 @@ if (body === null) {

if (isFormData(body)) {
return `multipart/form-data; boundary=${request[INTERNALS].boundary}`;
}
// Body is stream - can't really do much about this

@@ -339,3 +340,5 @@ if (body instanceof Stream) {

*/
export const getTotalBytes = ({body}) => {
export const getTotalBytes = request => {
const {body} = request;
// Body is null or undefined

@@ -361,2 +364,7 @@ if (body === null) {

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

@@ -379,3 +387,3 @@ return null;

// Body is Blob
body.stream().pipe(dest);
Stream.Readable.from(body.stream()).pipe(dest);
} else if (Buffer.isBuffer(body)) {

@@ -390,4 +398,1 @@ // Body is buffer

};
// Expose Promise
Body.Promise = global.Promise;

@@ -0,27 +1,10 @@

import {FetchBaseError} from './base.js';
/**
* Abort-error.js
*
* AbortError interface for cancelled requests
*/
/**
* Create AbortError instance
*
* @param String message Error message for human
* @param String type Error type for machine
* @param String systemError For Node.js system error
* @return AbortError
*/
export default class AbortError extends Error {
constructor(message) {
super(message);
this.type = 'aborted';
this.message = message;
this.name = 'AbortError';
this[Symbol.toStringTag] = 'AbortError';
// Hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);
export class AbortError extends FetchBaseError {
constructor(message, type = 'aborted') {
super(message, type);
}
}

@@ -0,24 +1,19 @@

import {FetchBaseError} from './base.js';
/**
* Fetch-error.js
*
* FetchError interface for operational errors
*/
* @typedef {{ address?: string, code: string, dest?: string, errno: number, info?: object, message: string, path?: string, port?: number, syscall: string}} SystemError
*/
/**
* Create FetchError instance
*
* @param String message Error message for human
* @param String type Error type for machine
* @param Object systemError For Node.js system error
* @return FetchError
* FetchError interface for operational errors
*/
export default class FetchError extends Error {
export class FetchError extends FetchBaseError {
/**
* @param {string} message - Error message for human
* @param {string} [type] - Error type for machine
* @param {SystemError} [systemError] - For Node.js system error
*/
constructor(message, type, systemError) {
super(message);
this.message = message;
this.type = type;
this.name = 'FetchError';
this[Symbol.toStringTag] = 'FetchError';
super(message, type);
// When err.type is `system`, err.erroredSysCall contains system error and err.code contains system error code

@@ -28,8 +23,5 @@ if (systemError) {

this.code = this.errno = systemError.code;
this.erroredSysCall = systemError;
this.erroredSysCall = systemError.syscall;
}
// Hide custom error implementation details from end-users
Error.captureStackTrace(this, this.constructor);
}
}

@@ -8,22 +8,26 @@ /**

import {types} from 'util';
import http from 'http';
const invalidTokenRegex = /[^`\-\w!#$%&'*+.|~]/;
const invalidHeaderCharRegex = /[^\t\u0020-\u007E\u0080-\u00FF]/;
const validateHeaderName = typeof http.validateHeaderName === 'function' ?
http.validateHeaderName :
name => {
if (!/^[\^`\-\w!#$%&'*+.|~]+$/.test(name)) {
const error = new TypeError(`Header name must be a valid HTTP token [${name}]`);
Object.defineProperty(error, 'code', {value: 'ERR_INVALID_HTTP_TOKEN'});
throw error;
}
};
function validateName(name) {
name = String(name);
if (invalidTokenRegex.test(name) || name === '') {
throw new TypeError(`'${name}' is not a legal HTTP header name`);
}
}
const validateHeaderValue = typeof http.validateHeaderValue === 'function' ?
http.validateHeaderValue :
(name, value) => {
if (/[^\t\u0020-\u007E\u0080-\u00FF]/.test(value)) {
const error = new TypeError(`Invalid character in header content ["${name}"]`);
Object.defineProperty(error, 'code', {value: 'ERR_INVALID_CHAR'});
throw error;
}
};
function validateValue(value) {
value = String(value);
if (invalidHeaderCharRegex.test(value)) {
throw new TypeError(`'${value}' is not a legal HTTP header value`);
}
}
/**
* @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<string>[]} HeadersInit
* @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<Iterable<string>>} HeadersInit
*/

@@ -95,5 +99,5 @@

result.map(([name, value]) => {
validateName(name);
validateValue(value);
return [String(name).toLowerCase(), value];
validateHeaderName(name);
validateHeaderValue(name, String(value));
return [String(name).toLowerCase(), String(value)];
}) :

@@ -112,8 +116,8 @@ undefined;

return (name, value) => {
validateName(name);
validateValue(value);
validateHeaderName(name);
validateHeaderValue(name, String(value));
return URLSearchParams.prototype[p].call(
receiver,
target,
String(name).toLowerCase(),
value
String(value)
);

@@ -126,5 +130,5 @@ };

return name => {
validateName(name);
validateHeaderName(name);
return URLSearchParams.prototype[p].call(
receiver,
target,
String(name).toLowerCase()

@@ -149,3 +153,3 @@ );

get [Symbol.toStringTag]() {
return 'Headers';
return this.constructor.name;
}

@@ -171,5 +175,5 @@

forEach(callback) {
forEach(callback, thisArg = undefined) {
for (const name of this.keys()) {
callback(this.get(name), name);
Reflect.apply(callback, thisArg, [this.get(name), name, this]);
}

@@ -256,5 +260,13 @@ }

}, [])
.filter(([name, value]) => !(invalidTokenRegex.test(name) || invalidHeaderCharRegex.test(value)))
.filter(([name, value]) => {
try {
validateHeaderName(name);
validateHeaderValue(name, String(value));
return true;
} catch {
return false;
}
})
);
}

@@ -13,10 +13,10 @@ /**

import Stream, {PassThrough, pipeline as pump} from 'stream';
import dataURIToBuffer from 'data-uri-to-buffer';
import dataUriToBuffer from 'data-uri-to-buffer';
import Body, {writeToStream, getTotalBytes} from './body.js';
import {writeToStream} from './body.js';
import Response from './response.js';
import Headers, {fromRawHeaders} from './headers.js';
import Request, {getNodeRequestOptions} from './request.js';
import FetchError from './errors/fetch-error.js';
import AbortError from './errors/abort-error.js';
import {FetchError} from './errors/fetch-error.js';
import {AbortError} from './errors/abort-error.js';
import {isRedirect} from './utils/is-redirect.js';

@@ -26,39 +26,28 @@

const supportedSchemas = new Set(['data:', 'http:', 'https:']);
/**
* Fetch function
*
* @param Mixed url Absolute url or Request instance
* @param Object opts Fetch options
* @return Promise
* @param {string | URL | import('./request').default} url - Absolute url or Request instance
* @param {*} [options_] - Fetch options
* @return {Promise<import('./response').default>}
*/
export default function fetch(url, options_) {
// Allow custom promise
if (!fetch.Promise) {
throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
}
// Regex for data uri
const dataUriRegex = /^\s*data:([a-z]+\/[a-z]+(;[a-z-]+=[a-z-]+)?)?(;base64)?,[\w!$&',()*+;=\-.~:@/?%\s]*\s*$/i;
// If valid data uri
if (dataUriRegex.test(url)) {
const data = dataURIToBuffer(url);
const response = new Response(data, {headers: {'Content-Type': data.type}});
return fetch.Promise.resolve(response);
}
// If invalid data uri
if (url.toString().startsWith('data:')) {
const request = new Request(url, options_);
return fetch.Promise.reject(new FetchError(`[${request.method}] ${request.url} invalid URL`, 'system'));
}
Body.Promise = fetch.Promise;
// Wrap http.request into fetch
return new fetch.Promise((resolve, reject) => {
export default async function fetch(url, options_) {
return new Promise((resolve, reject) => {
// Build request object
const request = new Request(url, options_);
const options = getNodeRequestOptions(request);
if (!supportedSchemas.has(options.protocol)) {
throw new TypeError(`node-fetch cannot load ${url}. URL scheme "${options.protocol.replace(/:$/, '')}" is not supported.`);
}
if (options.protocol === 'data:') {
const data = dataUriToBuffer(request.url);
const response = new Response(data, {headers: {'Content-Type': data.typeFull}});
resolve(response);
return;
}
// Wrap http.request into fetch
const send = (options.protocol === 'https:' ? https : http).request;

@@ -106,7 +95,31 @@ const {signal} = request;

request_.on('error', err => {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
request_.on('error', error => {
reject(new FetchError(`request to ${request.url} failed, reason: ${error.message}`, 'system', error));
finalize();
});
fixResponseChunkedTransferBadEnding(request_, error => {
response.body.destroy(error);
});
/* c8 ignore next 18 */
if (process.version < 'v14') {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
request_.on('socket', s => {
let endedWithEventsCount;
s.prependListener('end', () => {
endedWithEventsCount = s._eventsCount;
});
s.prependListener('close', hadError => {
// if end happened before close but the socket didn't emit an error, do it now
if (response && endedWithEventsCount < s._eventsCount && !hadError) {
const error = new Error('Premature close');
error.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', error);
}
});
});
}
request_.on('response', response_ => {

@@ -133,9 +146,3 @@ request_.setTimeout(0);

if (locationURL !== null) {
// Handle corrupted header
try {
headers.set('Location', locationURL);
/* c8 ignore next 3 */
} catch (error) {
reject(error);
}
headers.set('Location', locationURL);
}

@@ -167,7 +174,8 @@

body: request.body,
signal: request.signal
signal: request.signal,
size: request.size
};
// HTTP-redirect fetch step 9
if (response_.statusCode !== 303 && request.body && getTotalBytes(request) === null) {
if (response_.statusCode !== 303 && request.body && options_.body instanceof Stream.Readable) {
reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect'));

@@ -192,3 +200,3 @@ finalize();

default:
// Do nothing
return reject(new TypeError(`Redirect option '${request.redirect}' is not a valid value of RequestRedirect`));
}

@@ -198,11 +206,13 @@ }

// Prepare response
response_.once('end', () => {
if (signal) {
if (signal) {
response_.once('end', () => {
signal.removeEventListener('abort', abortAndFinalize);
}
});
});
}
let body = pump(response_, new PassThrough(), error => {
reject(error);
});
let body = pump(response_, new PassThrough(), reject);
// see https://github.com/nodejs/node/pull/29376
if (process.version < 'v12.10') {
response_.on('aborted', abortAndFinalize);
}

@@ -248,5 +258,3 @@ const responseOptions = {

if (codings === 'gzip' || codings === 'x-gzip') {
body = pump(body, zlib.createGunzip(zlibOptions), error => {
reject(error);
});
body = pump(body, zlib.createGunzip(zlibOptions), reject);
response = new Response(body, responseOptions);

@@ -261,16 +269,6 @@ resolve(response);

// a hack for old IIS and Apache servers
const raw = pump(response_, new PassThrough(), error => {
reject(error);
});
const raw = pump(response_, new PassThrough(), reject);
raw.once('data', chunk => {
// See http://stackoverflow.com/questions/37519828
if ((chunk[0] & 0x0F) === 0x08) {
body = pump(body, zlib.createInflate(), error => {
reject(error);
});
} else {
body = pump(body, zlib.createInflateRaw(), error => {
reject(error);
});
}
body = (chunk[0] & 0x0F) === 0x08 ? pump(body, zlib.createInflate(), reject) : pump(body, zlib.createInflateRaw(), reject);

@@ -285,5 +283,3 @@ response = new Response(body, responseOptions);

if (codings === 'br') {
body = pump(body, zlib.createBrotliDecompress(), error => {
reject(error);
});
body = pump(body, zlib.createBrotliDecompress(), reject);
response = new Response(body, responseOptions);

@@ -303,3 +299,43 @@ resolve(response);

// Expose Promise
fetch.Promise = global.Promise;
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
const LAST_CHUNK = Buffer.from('0\r\n\r\n');
let isChunkedTransfer = false;
let properLastChunkReceived = false;
let previousChunk;
request.on('response', response => {
const {headers} = response;
isChunkedTransfer = headers['transfer-encoding'] === 'chunked' && !headers['content-length'];
});
request.on('socket', socket => {
const onSocketClose = () => {
if (isChunkedTransfer && !properLastChunkReceived) {
const error = new Error('Premature close');
error.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(error);
}
};
socket.prependListener('close', onSocketClose);
request.on('abort', () => {
socket.removeListener('close', onSocketClose);
});
socket.on('data', buf => {
properLastChunkReceived = Buffer.compare(buf.slice(-5), LAST_CHUNK) === 0;
// Sometimes final 0-length chunk and end of message code are in separate packets
if (!properLastChunkReceived && previousChunk) {
properLastChunkReceived = (
Buffer.compare(previousChunk.slice(-3), LAST_CHUNK.slice(0, 3)) === 0 &&
Buffer.compare(buf.slice(-2), LAST_CHUNK.slice(3)) === 0
);
}
previousChunk = buf;
});
});
}

@@ -32,24 +32,6 @@

/**
* Wrapper around `new URL` to handle relative URLs (https://github.com/nodejs/node/issues/12682)
*
* @param {string} urlStr
* @return {void}
*/
const parseURL = urlString => {
/*
Check whether the URL is absolute or not
Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
*/
if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlString)) {
return new URL(urlString);
}
throw new TypeError('Only absolute URLs are supported');
};
/**
* Request class
*
* Ref: https://fetch.spec.whatwg.org/#request-class
*
* @param Mixed input Url or Request instance

@@ -63,16 +45,7 @@ * @param Object init Custom options

// Normalize input and force URL to be encoded as UTF-8 (https://github.com/bitinn/node-fetch/issues/245)
// Normalize input and force URL to be encoded as UTF-8 (https://github.com/node-fetch/node-fetch/issues/245)
if (isRequest(input)) {
parsedURL = parseURL(input.url);
parsedURL = new URL(input.url);
} else {
if (input && input.href) {
// In order to support Node.js' Url objects; though WHATWG's URL objects
// will fall into this branch also (since their `toString()` will return
// `href` property anyway)
parsedURL = parseURL(input.href);
} else {
// Coerce input to a string before attempting to parse
parsedURL = parseURL(`${input}`);
}
parsedURL = new URL(input);
input = {};

@@ -103,3 +76,3 @@ }

if (inputBody !== null && !headers.has('Content-Type')) {
const contentType = extractContentType(inputBody);
const contentType = extractContentType(inputBody, this);
if (contentType) {

@@ -117,4 +90,5 @@ headers.append('Content-Type', contentType);

if (signal !== null && !isAbortSignal(signal)) {
throw new TypeError('Expected signal to be an instanceof AbortSignal');
// eslint-disable-next-line no-eq-null, eqeqeq
if (signal != null && !isAbortSignal(signal)) {
throw new TypeError('Expected signal to be an instanceof AbortSignal or EventTarget');
}

@@ -136,2 +110,3 @@

this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
}

@@ -197,6 +172,2 @@

if (!/^https?:$/.test(parsedURL.protocol)) {
throw new TypeError('Only HTTP(S) protocols are supported');
}
// HTTP-network-or-cache fetch steps 2.4-2.7

@@ -210,3 +181,4 @@ let contentLengthValue = null;

const totalBytes = getTotalBytes(request);
if (typeof totalBytes === 'number') {
// Set Content-Length if totalBytes is a number (that is not NaN)
if (typeof totalBytes === 'number' && !Number.isNaN(totalBytes)) {
contentLengthValue = String(totalBytes);

@@ -257,2 +229,3 @@ }

headers: headers[Symbol.for('nodejs.util.inspect.custom')](),
insecureHTTPParser: request.insecureHTTPParser,
agent

@@ -259,0 +232,0 @@ };

@@ -16,2 +16,4 @@ /**

*
* Ref: https://fetch.spec.whatwg.org/#response-class
*
* @param Stream body Readable stream

@@ -25,3 +27,5 @@ * @param Object opts Response options

const status = options.status || 200;
// eslint-disable-next-line no-eq-null, eqeqeq, no-negated-condition
const status = options.status != null ? options.status : 200;
const headers = new Headers(options.headers);

@@ -37,2 +41,3 @@

this[INTERNALS] = {
type: 'default',
url: options.url,

@@ -47,2 +52,6 @@ status,

get type() {
return this[INTERNALS].type;
}
get url() {

@@ -86,2 +95,3 @@ return this[INTERNALS].url || '';

return new Response(clone(this, this.highWaterMark), {
type: this.type,
url: this.url,

@@ -115,2 +125,8 @@ status: this.status,

static error() {
const response = new Response(null, {status: 0, statusText: ''});
response[INTERNALS].type = 'error';
return response;
}
get [Symbol.toStringTag]() {

@@ -122,2 +138,3 @@ return 'Response';

Object.defineProperties(Response.prototype, {
type: {enumerable: true},
url: {enumerable: true},

@@ -131,2 +148,1 @@ status: {enumerable: true},

});

@@ -31,3 +31,3 @@ /**

/**
* Check if `obj` is a W3C `Blob` object (which `File` inherits from)
* Check if `object` is a W3C `Blob` object (which `File` inherits from)
*

@@ -49,16 +49,25 @@ * @param {*} obj

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

@@ -68,4 +77,10 @@ * @param {*} obj

*/
export const isAbortError = object => {
return object[NAME] === 'AbortError';
export const isAbortSignal = object => {
return (
typeof object === 'object' && (
object[NAME] === 'AbortSignal' ||
object[NAME] === 'EventTarget'
)
);
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc