node-fetch
Advanced tools
Comparing version 3.0.0-beta.5 to 3.0.0-beta.6
The MIT License (MIT) | ||
Copyright (c) 2016 David Frank | ||
Copyright (c) 2016 - 2020 Node Fetch Team | ||
@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
252
package.json
{ | ||
"name": "node-fetch", | ||
"version": "3.0.0-beta.5", | ||
"description": "A light-weight module that brings window.fetch to node.js", | ||
"main": "./dist/index.js", | ||
"module": "./src/index.js", | ||
"sideEffects": false, | ||
"exports": { | ||
"import": "./src/index.js", | ||
"require": "./dist/index.js" | ||
}, | ||
"files": [ | ||
"src", | ||
"dist", | ||
"*.d.ts" | ||
], | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"build": "babel src --out-dir dist", | ||
"test": "nyc --reporter=html --reporter=text mocha --require @babel/register --throw-deprecation", | ||
"coverage": "nyc report --reporter=text-lcov | coveralls", | ||
"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": { | ||
"@babel/cli": "^7.8.4", | ||
"@babel/core": "^7.9.0", | ||
"@babel/preset-env": "^7.9.5", | ||
"@babel/register": "^7.9.0", | ||
"@istanbuljs/nyc-config-babel": "^3.0.0", | ||
"abort-controller": "^3.0.0", | ||
"abortcontroller-polyfill": "^1.4.0", | ||
"babel-plugin-add-module-exports": "^1.0.2", | ||
"babel-plugin-istanbul": "^6.0.0", | ||
"chai": "^4.2.0", | ||
"chai-as-promised": "^7.1.1", | ||
"chai-iterator": "^3.0.2", | ||
"chai-string": "^1.5.0", | ||
"coveralls": "^3.0.13", | ||
"form-data": "^3.0.0", | ||
"mocha": "^7.1.1", | ||
"nyc": "^15.0.1", | ||
"parted": "^0.1.1", | ||
"promise": "^8.1.0", | ||
"resumer": "0.0.0", | ||
"string-to-arraybuffer": "^1.0.2", | ||
"xo": "^0.29.1" | ||
}, | ||
"dependencies": { | ||
"data-uri-to-buffer": "^3.0.0", | ||
"fetch-blob": "^1.0.5" | ||
}, | ||
"xo": { | ||
"envs": [ | ||
"node", | ||
"browser" | ||
], | ||
"rules": { | ||
"complexity": 0, | ||
"promise/prefer-await-to-then": 0, | ||
"no-mixed-operators": 0, | ||
"no-negated-condition": 0, | ||
"unicorn/prevent-abbreviations": 0, | ||
"@typescript-eslint/prefer-readonly-parameter-types": 0 | ||
}, | ||
"ignores": [ | ||
"name": "node-fetch", | ||
"version": "3.0.0-beta.6", | ||
"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", | ||
"index.d.ts" | ||
], | ||
"overrides": [ | ||
{ | ||
"files": "test/**/*.js", | ||
"envs": [ | ||
"node", | ||
"mocha" | ||
], | ||
"rules": { | ||
"max-nested-callbacks": 0, | ||
"no-unused-expressions": 0, | ||
"new-cap": 0, | ||
"guard-for-in": 0 | ||
} | ||
}, | ||
{ | ||
"files": "example.js", | ||
"rules": { | ||
"import/no-extraneous-dependencies": 0 | ||
} | ||
} | ||
] | ||
}, | ||
"babel": { | ||
"presets": [ | ||
[ | ||
"@babel/preset-env", | ||
{ | ||
"targets": { | ||
"node": true | ||
} | ||
} | ||
] | ||
], | ||
"plugins": [ | ||
"add-module-exports", | ||
"istanbul" | ||
] | ||
}, | ||
"nyc": { | ||
"extends": "@istanbuljs/nyc-config-babel" | ||
}, | ||
"runkitExampleFilename": "example.js" | ||
"@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" | ||
} |
334
README.md
@@ -5,8 +5,8 @@ <div align="center"> | ||
<p>A light-weight module that brings <code>window.fetch</code> to Node.js.</p> | ||
<a href="https://travis-ci.com/node-fetch/node-fetch"><img src="https://img.shields.io/travis/com/node-fetch/node-fetch/master?style=flat-square" alt="Build status"></a> | ||
<a href="https://coveralls.io/github/node-fetch/node-fetch"><img src="https://img.shields.io/coveralls/github/node-fetch/node-fetch?style=flat-square" alt="Coverage status"></a> | ||
<a href="https://packagephobia.now.sh/result?p=node-fetch"><img src="https://flat.badgen.net/packagephobia/install/node-fetch" alt="Current version"></a> | ||
<a href="https://www.npmjs.com/package/node-fetch"><img src="https://img.shields.io/npm/v/node-fetch?style=flat-square" alt="Install size"></a> | ||
<a href="https://github.com/sindresorhus/awesome-nodejs"><img src="https://awesome.re/mentioned-badge-flat.svg" alt="Mentioned in Awesome Node.js"></a> | ||
<a href="https://discord.gg/Zxbndcm"><img src="https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord&style=flat-square" alt="Discord"></a> | ||
<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> | ||
<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> | ||
<a href="https://packagephobia.now.sh/result?p=node-fetch"><img src="https://badgen.net/packagephobia/install/node-fetch" alt="Current version"></a> | ||
<a href="https://www.npmjs.com/package/node-fetch"><img src="https://img.shields.io/npm/v/node-fetch" alt="Install size"></a> | ||
<a href="https://github.com/sindresorhus/awesome-nodejs"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Node.js"></a> | ||
<a href="https://discord.gg/Zxbndcm"><img src="https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord" alt="Discord"></a> | ||
<br> | ||
@@ -31,46 +31,46 @@ <br> | ||
- [Common Usage](#common-usage) | ||
- [Plain text or HTML](#plain-text-or-html) | ||
- [JSON](#json) | ||
- [Simple Post](#simple-post) | ||
- [Post with JSON](#post-with-json) | ||
- [Post with form parameters](#post-with-form-parameters) | ||
- [Handling exceptions](#handling-exceptions) | ||
- [Handling client and server errors](#handling-client-and-server-errors) | ||
- [Handling cookies](#handling-cookies) | ||
- [Plain text or HTML](#plain-text-or-html) | ||
- [JSON](#json) | ||
- [Simple Post](#simple-post) | ||
- [Post with JSON](#post-with-json) | ||
- [Post with form parameters](#post-with-form-parameters) | ||
- [Handling exceptions](#handling-exceptions) | ||
- [Handling client and server errors](#handling-client-and-server-errors) | ||
- [Handling cookies](#handling-cookies) | ||
- [Advanced Usage](#advanced-usage) | ||
- [Streams](#streams) | ||
- [Buffer](#buffer) | ||
- [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data) | ||
- [Extract Set-Cookie Header](#extract-set-cookie-header) | ||
- [Post data using a file stream](#post-data-using-a-file-stream) | ||
- [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart) | ||
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal) | ||
- [Streams](#streams) | ||
- [Buffer](#buffer) | ||
- [Accessing Headers and other Meta data](#accessing-headers-and-other-meta-data) | ||
- [Extract Set-Cookie Header](#extract-set-cookie-header) | ||
- [Post data using a file stream](#post-data-using-a-file-stream) | ||
- [Post with form-data (detect multipart)](#post-with-form-data-detect-multipart) | ||
- [Request cancellation with AbortSignal](#request-cancellation-with-abortsignal) | ||
- [API](#api) | ||
- [fetch(url[, options])](#fetchurl-options) | ||
- [Options](#options) | ||
- [Default Headers](#default-headers) | ||
- [Custom Agent](#custom-agent) | ||
- [Custom highWaterMark](#custom-highwatermark) | ||
- [Class: Request](#class-request) | ||
- [new Request(input[, options])](#new-requestinput-options) | ||
- [Class: Response](#class-response) | ||
- [new Response([body[, options]])](#new-responsebody-options) | ||
- [response.ok](#responseok) | ||
- [response.redirected](#responseredirected) | ||
- [Class: Headers](#class-headers) | ||
- [new Headers([init])](#new-headersinit) | ||
- [Interface: Body](#interface-body) | ||
- [body.body](#bodybody) | ||
- [body.bodyUsed](#bodybodyused) | ||
- [body.arrayBuffer()](#bodyarraybuffer) | ||
- [body.blob()](#bodyblob) | ||
- [body.json()](#bodyjson) | ||
- [body.text()](#bodytext) | ||
- [body.buffer()](#bodybuffer) | ||
- [Class: FetchError](#class-fetcherror) | ||
- [Class: AbortError](#class-aborterror) | ||
- [fetch(url[, options])](#fetchurl-options) | ||
- [Options](#options) | ||
- [Default Headers](#default-headers) | ||
- [Custom Agent](#custom-agent) | ||
- [Custom highWaterMark](#custom-highwatermark) | ||
- [Class: Request](#class-request) | ||
- [new Request(input[, options])](#new-requestinput-options) | ||
- [Class: Response](#class-response) | ||
- [new Response([body[, options]])](#new-responsebody-options) | ||
- [response.ok](#responseok) | ||
- [response.redirected](#responseredirected) | ||
- [Class: Headers](#class-headers) | ||
- [new Headers([init])](#new-headersinit) | ||
- [Interface: Body](#interface-body) | ||
- [body.body](#bodybody) | ||
- [body.bodyUsed](#bodybodyused) | ||
- [body.arrayBuffer()](#bodyarraybuffer) | ||
- [body.blob()](#bodyblob) | ||
- [body.json()](#bodyjson) | ||
- [body.text()](#bodytext) | ||
- [body.buffer()](#bodybuffer) | ||
- [Class: FetchError](#class-fetcherror) | ||
- [Class: AbortError](#class-aborterror) | ||
- [TypeScript](#typescript) | ||
- [Acknowledgement](#acknowledgement) | ||
- [Team](#team) | ||
- [Former](#former) | ||
- [Former](#former) | ||
- [License](#license) | ||
@@ -93,3 +93,3 @@ | ||
- Decode content encoding (gzip/deflate) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically. | ||
- Useful extensions such as timeout, redirect limit, response size limit, [explicit errors][error-handling.md] for troubleshooting. | ||
- Useful extensions such as redirect limit, response size limit, [explicit errors][error-handling.md] for troubleshooting. | ||
@@ -141,18 +141,4 @@ ## Difference from client-side fetch | ||
For versions of node earlier than 12.x, use this `globalThis` [polyfill](https://mathiasbynens.be/notes/globalthis): | ||
For versions of Node earlier than 12, use this `globalThis` [polyfill](https://mathiasbynens.be/notes/globalthis). | ||
```js | ||
(function() { | ||
if (typeof globalThis === 'object') return; | ||
Object.defineProperty(Object.prototype, '__magic__', { | ||
get: function() { | ||
return this; | ||
}, | ||
configurable: true | ||
}); | ||
__magic__.globalThis = __magic__; | ||
delete Object.prototype.__magic__; | ||
}()); | ||
``` | ||
## Upgrading | ||
@@ -175,5 +161,8 @@ | ||
fetch('https://github.com/') | ||
.then(res => res.text()) | ||
.then(body => console.log(body)); | ||
(async () => { | ||
const response = await fetch('https://github.com/'); | ||
const body = await response.text(); | ||
console.log(body); | ||
})(); | ||
``` | ||
@@ -186,5 +175,8 @@ | ||
fetch('https://api.github.com/users/github') | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://api.github.com/users/github'); | ||
const json = await response.json(); | ||
console.log(json); | ||
})(); | ||
``` | ||
@@ -197,5 +189,8 @@ | ||
fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'}) | ||
.then(res => res.json()) // expecting a json response | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: 'a=1'}); | ||
const json = await response.json(); | ||
console.log(json); | ||
})(); | ||
``` | ||
@@ -208,11 +203,14 @@ | ||
const body = {a: 1}; | ||
(async () => { | ||
const body = {a: 1}; | ||
fetch('https://httpbin.org/post', { | ||
method: 'post', | ||
body: JSON.stringify(body), | ||
headers: {'Content-Type': 'application/json'} | ||
}) | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
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); | ||
})(); | ||
``` | ||
@@ -232,5 +230,8 @@ | ||
fetch('https://httpbin.org/post', {method: 'POST', body: params}) | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: params}); | ||
const json = await response.json(); | ||
console.log(json); | ||
})(); | ||
``` | ||
@@ -242,3 +243,3 @@ | ||
Adding a catch to the fetch promise chain will catch _all_ exceptions, such as errors originating from node core libraries, like network errors, and operational errors which are instances of FetchError. See the [error handling document][error-handling.md] for more details. | ||
Wrapping the fetch function into a `try/catch` block will catch _all_ exceptions, such as errors originating from node core libraries, like network errors, and operational errors which are instances of FetchError. See the [error handling document][error-handling.md] for more details. | ||
@@ -248,3 +249,7 @@ ```js | ||
fetch('https://domain.invalid/').catch(err => console.error(err)); | ||
try { | ||
fetch('https://domain.invalid/'); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
``` | ||
@@ -259,3 +264,3 @@ | ||
function checkStatus(res) { | ||
const checkStatus = res => { | ||
if (res.ok) { | ||
@@ -269,5 +274,8 @@ // res.status >= 200 && res.status < 300 | ||
fetch('https://httpbin.org/status/400') | ||
.then(checkStatus) | ||
.then(res => console.log('will not get here...')); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/status/400'); | ||
const data = checkStatus(response); | ||
console.log(data); //=> MyCustomError | ||
})(); | ||
``` | ||
@@ -290,10 +298,11 @@ | ||
fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png') | ||
.then(res => { | ||
if (!res.ok) { | ||
throw new Error(`unexpected response ${res.statusText}`); | ||
} | ||
(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')); | ||
} | ||
return streamPipeline(res.body, fs.createWriteStream('./octocat.png')); | ||
}); | ||
throw new Error(`unexpected response ${res.statusText}`); | ||
})(); | ||
``` | ||
@@ -309,8 +318,9 @@ | ||
fetch('https://octodex.github.com/images/Fintechtocat.png') | ||
.then(res => res.buffer()) | ||
.then(buffer => fileType(buffer)) | ||
.then(type => { | ||
console.log(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); | ||
})(); | ||
``` | ||
@@ -323,3 +333,5 @@ | ||
fetch('https://github.com/').then(res => { | ||
(async () => { | ||
const response = await fetch('https://github.com/'); | ||
console.log(res.ok); | ||
@@ -330,3 +342,3 @@ console.log(res.status); | ||
console.log(res.headers.get('content-type')); | ||
}); | ||
})(); | ||
``` | ||
@@ -341,6 +353,8 @@ | ||
fetch('https://example.com').then(res => { | ||
// returns an array of values, instead of a string of comma-separated values | ||
(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']); | ||
}); | ||
})(); | ||
``` | ||
@@ -356,5 +370,8 @@ | ||
fetch('https://httpbin.org/post', {method: 'POST', body: stream}) | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: stream}); | ||
const json = await response.json(); | ||
console.log(json) | ||
})(); | ||
``` | ||
@@ -371,5 +388,8 @@ | ||
fetch('https://httpbin.org/post', {method: 'POST', body: form}) | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/post', {method: 'POST', body: form}); | ||
const json = await response.json(); | ||
console.log(json) | ||
})(); | ||
@@ -385,5 +405,8 @@ // OR, using custom headers | ||
fetch('https://httpbin.org/post', options) | ||
.then(res => res.json()) | ||
.then(json => console.log(json)); | ||
(async () => { | ||
const response = await fetch('https://httpbin.org/post', options); | ||
const json = await response.json(); | ||
console.log(json) | ||
})(); | ||
``` | ||
@@ -406,20 +429,19 @@ | ||
fetch('https://example.com', {signal: controller.signal}) | ||
.then(res => res.json()) | ||
.then( | ||
data => { | ||
useData(data); | ||
}, | ||
err => { | ||
if (err.name === 'AbortError') { | ||
console.log('request was aborted'); | ||
} | ||
(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(() => { | ||
} finally { | ||
clearTimeout(timeout); | ||
}); | ||
} | ||
})(); | ||
``` | ||
See [test cases](https://github.com/node-fetch/node-fetch/blob/master/test/test.js) for more examples. | ||
See [test cases](https://github.com/node-fetch/node-fetch/blob/master/test/) for more examples. | ||
@@ -448,10 +470,9 @@ ## API | ||
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 | ||
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 | ||
timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead. | ||
compress: true, // support gzip/deflate content encoding. false to disable | ||
@@ -468,11 +489,12 @@ size: 0, // maximum response body size in bytes. 0 to disable | ||
| Header | Value | | ||
| ------------------- | -------------------------------------------------------- | | ||
| `Accept-Encoding` | `gzip,deflate` _(when `options.compress === true`)_ | | ||
| `Accept` | `*/*` | | ||
| `Connection` | `close` _(when no `options.agent` is present)_ | | ||
| `Content-Length` | _(automatically calculated, if possible)_ | | ||
| `Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ | | ||
| `User-Agent` | `node-fetch (+https://github.com/node-fetch/node-fetch)` | | ||
| Header | Value | | ||
| ------------------- | ------------------------------------------------------ | | ||
| `Accept-Encoding` | `gzip,deflate,br` _(when `options.compress === true`)_ | | ||
| `Accept` | `*/*` | | ||
| `Connection` | `close` _(when no `options.agent` is present)_ | | ||
| `Content-Length` | _(automatically calculated, if possible)_ | | ||
| `Transfer-Encoding` | `chunked` _(when `req.body` is a stream)_ | | ||
| `User-Agent` | `node-fetch` | | ||
Note: when `body` is a `Stream`, `Content-Length` is not set automatically. | ||
@@ -518,3 +540,3 @@ | ||
Stream on Node.js have a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers). Because of that, when you are writing an isomorphic app and using `res.clone()`, it will hang with large response in Node. | ||
Stream on Node.js have a smaller internal buffer size (16kB, aka `highWaterMark`) from client-side browsers (>1MB, not consistent across browsers). Because of that, when you are writing an isomorphic app and using `res.clone()`, it will hang with large response in Node. | ||
@@ -526,5 +548,6 @@ The recommended way to fix this problem is to resolve cloned response in parallel: | ||
fetch('https://example.com').then(res => { | ||
const r1 = res.clone(); | ||
(async () => { | ||
const response = await fetch('https://example.com'); | ||
const r1 = await response.clone(); | ||
return Promise.all([res.json(), r1.text()]).then(results => { | ||
@@ -534,3 +557,3 @@ console.log(results[0]); | ||
}); | ||
}); | ||
})(); | ||
``` | ||
@@ -543,3 +566,10 @@ | ||
fetch('https://example.com', {highWaterMark: 10}).then(res => res.clone().buffer()); | ||
(async () => { | ||
const response = await fetch('https://example.com', { | ||
// About 1MB | ||
highWaterMark: 1024 * 1024 | ||
}); | ||
return res.clone().buffer(); | ||
})(); | ||
``` | ||
@@ -724,3 +754,3 @@ | ||
Since `3.x` types are bundled with `node-fetch`, so you don't need to install any additional packages. | ||
**Since `3.x` types are bundled with `node-fetch`, so you don't need to install any additional packages.** | ||
@@ -739,5 +769,5 @@ For older versions please use the type definitions from [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped): | ||
[![David Frank](https://github.com/bitinn.png?size=100)](https://github.com/bitinn) | [![Jimmy Wärting](https://github.com/jimmywarting.png?size=100)](https://github.com/jimmywarting) | [![Antoni Kepinski](https://github.com/xxczaki.png?size=100)](https://github.com/xxczaki) | [![Richie Bendall](https://github.com/Richienb.png?size=100)](https://github.com/Richienb) | [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | ||
---|---|---|---|--- | ||
[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://github.com/bitinn.png?size=100)](https://github.com/bitinn) | [![Jimmy Wärting](https://github.com/jimmywarting.png?size=100)](https://github.com/jimmywarting) | [![Antoni Kepinski](https://github.com/xxczaki.png?size=100)](https://github.com/xxczaki) | [![Richie Bendall](https://github.com/Richienb.png?size=100)](https://github.com/Richienb) | [![Gregor Martynus](https://github.com/gr2m.png?size=100)](https://github.com/gr2m) | | ||
| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | | ||
| [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) | | ||
@@ -751,3 +781,3 @@ ###### Former | ||
MIT | ||
[MIT](LICENSE.md) | ||
@@ -754,0 +784,0 @@ [whatwg-fetch]: https://fetch.spec.whatwg.org/ |
235
src/body.js
@@ -8,7 +8,8 @@ | ||
import Stream, {PassThrough} from 'stream'; | ||
import Stream, {finished, PassThrough} from 'stream'; | ||
import {types} from 'util'; | ||
import Blob from 'fetch-blob'; | ||
import FetchError from './errors/fetch-error'; | ||
import {isBlob, isURLSearchParams, isArrayBuffer, isAbortError} from './utils/is'; | ||
import FetchError from './errors/fetch-error.js'; | ||
import {isBlob, isURLSearchParameters, isAbortError} from './utils/is.js'; | ||
@@ -26,56 +27,54 @@ const INTERNALS = Symbol('Body internals'); | ||
*/ | ||
export default function Body(body, { | ||
size = 0, | ||
timeout = 0 | ||
} = {}) { | ||
if (body === null) { | ||
// Body is undefined or null | ||
body = null; | ||
} else if (isURLSearchParams(body)) { | ||
export default class Body { | ||
constructor(body, { | ||
size = 0 | ||
} = {}) { | ||
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 (isArrayBuffer(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 { | ||
// None of the above | ||
// coerce to string then buffer | ||
body = Buffer.from(String(body)); | ||
} | ||
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 { | ||
// 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; | ||
this[INTERNALS] = { | ||
body, | ||
disturbed: false, | ||
error: null | ||
}; | ||
this.size = size; | ||
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); | ||
this[INTERNALS].error = error; | ||
}); | ||
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); | ||
this[INTERNALS].error = error; | ||
}); | ||
} | ||
} | ||
} | ||
Body.prototype = { | ||
get body() { | ||
return this[INTERNALS].body; | ||
}, | ||
} | ||
get bodyUsed() { | ||
return this[INTERNALS].disturbed; | ||
}, | ||
} | ||
@@ -87,5 +86,6 @@ /** | ||
*/ | ||
arrayBuffer() { | ||
return consumeBody.call(this).then(({buffer, byteOffset, byteLength}) => buffer.slice(byteOffset, byteOffset + byteLength)); | ||
}, | ||
async arrayBuffer() { | ||
const {buffer, byteOffset, byteLength} = await consumeBody(this); | ||
return buffer.slice(byteOffset, byteOffset + byteLength); | ||
} | ||
@@ -97,9 +97,11 @@ /** | ||
*/ | ||
blob() { | ||
const ct = this.headers && this.headers.get('content-type') || this[INTERNALS].body && this[INTERNALS].body.type || ''; | ||
return consumeBody.call(this).then(buf => new Blob([], { | ||
async blob() { | ||
const ct = (this.headers && this.headers.get('content-type')) || (this[INTERNALS].body && this[INTERNALS].body.type) || ''; | ||
const buf = await consumeBody(this); | ||
return new Blob([], { | ||
type: ct.toLowerCase(), | ||
buffer: buf | ||
})); | ||
}, | ||
}); | ||
} | ||
@@ -111,5 +113,6 @@ /** | ||
*/ | ||
json() { | ||
return consumeBody.call(this).then(buffer => JSON.parse(buffer.toString())); | ||
}, | ||
async json() { | ||
const buffer = await consumeBody(this); | ||
return JSON.parse(buffer.toString()); | ||
} | ||
@@ -121,5 +124,6 @@ /** | ||
*/ | ||
text() { | ||
return consumeBody.call(this).then(buffer => buffer.toString()); | ||
}, | ||
async text() { | ||
const buffer = await consumeBody(this); | ||
return buffer.toString(); | ||
} | ||
@@ -132,5 +136,5 @@ /** | ||
buffer() { | ||
return consumeBody.call(this); | ||
return consumeBody(this); | ||
} | ||
}; | ||
} | ||
@@ -147,12 +151,2 @@ // In browsers, all properties are enumerable. | ||
Body.mixIn = proto => { | ||
for (const name of Object.getOwnPropertyNames(Body.prototype)) { | ||
// istanbul ignore else: future proof | ||
if (!Object.prototype.hasOwnProperty.call(proto, name)) { | ||
const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); | ||
Object.defineProperty(proto, name, desc); | ||
} | ||
} | ||
}; | ||
/** | ||
@@ -165,14 +159,14 @@ * Consume and convert an entire Body to a Buffer. | ||
*/ | ||
function consumeBody() { | ||
if (this[INTERNALS].disturbed) { | ||
return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); | ||
const consumeBody = data => { | ||
if (data[INTERNALS].disturbed) { | ||
return Body.Promise.reject(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) { | ||
return Body.Promise.reject(data[INTERNALS].error); | ||
} | ||
let {body} = this; | ||
let {body} = data; | ||
@@ -194,3 +188,3 @@ // Body is null | ||
// istanbul ignore if: should never happen | ||
/* c8 ignore next 3 */ | ||
if (!(body instanceof Stream)) { | ||
@@ -207,24 +201,2 @@ return Body.Promise.resolve(Buffer.alloc(0)); | ||
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 (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 ${this.url}: ${err.message}`, 'system', err)); | ||
} | ||
}); | ||
body.on('data', chunk => { | ||
@@ -235,5 +207,5 @@ if (abort || chunk === null) { | ||
if (this.size && accumBytes + chunk.length > this.size) { | ||
if (data.size && accumBytes + chunk.length > data.size) { | ||
abort = true; | ||
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size')); | ||
reject(new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size')); | ||
return; | ||
@@ -246,18 +218,27 @@ } | ||
body.on('end', () => { | ||
if (abort) { | ||
return; | ||
} | ||
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; | ||
} | ||
clearTimeout(resTimeout); | ||
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 ${this.url}: ${error.message}`, 'system', error)); | ||
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)); | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
}; | ||
@@ -271,3 +252,3 @@ /** | ||
*/ | ||
export function clone(instance, highWaterMark) { | ||
export const clone = (instance, highWaterMark) => { | ||
let p1; | ||
@@ -296,3 +277,3 @@ let p2; | ||
return body; | ||
} | ||
}; | ||
@@ -309,3 +290,3 @@ /** | ||
*/ | ||
export function extractContentType(body) { | ||
export const extractContentType = body => { | ||
// Body is null or undefined | ||
@@ -322,3 +303,3 @@ if (body === null) { | ||
// Body is a URLSearchParams | ||
if (isURLSearchParams(body)) { | ||
if (isURLSearchParameters(body)) { | ||
return 'application/x-www-form-urlencoded;charset=UTF-8'; | ||
@@ -333,3 +314,3 @@ } | ||
// Body is a Buffer (Buffer, ArrayBuffer or ArrayBufferView) | ||
if (Buffer.isBuffer(body) || isArrayBuffer(body) || ArrayBuffer.isView(body)) { | ||
if (Buffer.isBuffer(body) || types.isAnyArrayBuffer(body) || ArrayBuffer.isView(body)) { | ||
return null; | ||
@@ -350,3 +331,3 @@ } | ||
return 'text/plain;charset=UTF-8'; | ||
} | ||
}; | ||
@@ -362,3 +343,3 @@ /** | ||
*/ | ||
export function getTotalBytes({body}) { | ||
export const getTotalBytes = ({body}) => { | ||
// Body is null or undefined | ||
@@ -386,3 +367,3 @@ if (body === null) { | ||
return null; | ||
} | ||
}; | ||
@@ -396,3 +377,3 @@ /** | ||
*/ | ||
export function writeToStream(dest, {body}) { | ||
export const writeToStream = (dest, {body}) => { | ||
if (body === null) { | ||
@@ -412,5 +393,5 @@ // Body is null | ||
} | ||
} | ||
}; | ||
// Expose Promise | ||
Body.Promise = global.Promise; |
@@ -1,2 +0,1 @@ | ||
/** | ||
@@ -8,2 +7,4 @@ * Headers.js | ||
import {types} from 'util'; | ||
const invalidTokenRegex = /[^`\-\w!#$%&'*+.|~]/; | ||
@@ -13,5 +14,5 @@ const invalidHeaderCharRegex = /[^\t\u0020-\u007E\u0080-\u00FF]/; | ||
function validateName(name) { | ||
name = `${name}`; | ||
name = String(name); | ||
if (invalidTokenRegex.test(name) || name === '') { | ||
throw new TypeError(`${name} is not a legal HTTP header name`); | ||
throw new TypeError(`'${name}' is not a legal HTTP header name`); | ||
} | ||
@@ -21,5 +22,5 @@ } | ||
function validateValue(value) { | ||
value = `${value}`; | ||
value = String(value); | ||
if (invalidHeaderCharRegex.test(value)) { | ||
throw new TypeError(`${value} is not a legal HTTP header value`); | ||
throw new TypeError(`'${value}' is not a legal HTTP header value`); | ||
} | ||
@@ -29,53 +30,38 @@ } | ||
/** | ||
* Find the key in the map object given a header name. | ||
* @typedef {Headers | Record<string, string> | Iterable<readonly [string, string]> | Iterable<string>[]} HeadersInit | ||
*/ | ||
/** | ||
* This Fetch API interface allows you to perform various actions on HTTP request and response headers. | ||
* These actions include retrieving, setting, adding to, and removing. | ||
* A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs. | ||
* You can add to this using methods like append() (see Examples.) | ||
* In all methods of this interface, header names are matched by case-insensitive byte sequence. | ||
* | ||
* Returns undefined if not found. | ||
* | ||
* @param String name Header name | ||
* @return String|Undefined | ||
*/ | ||
function find(map, name) { | ||
name = name.toLowerCase(); | ||
for (const key in map) { | ||
if (key.toLowerCase() === name) { | ||
return key; | ||
} | ||
} | ||
return undefined; | ||
} | ||
const MAP = Symbol('map'); | ||
export default class Headers { | ||
export default class Headers extends URLSearchParams { | ||
/** | ||
* Headers class | ||
* | ||
* @param Object headers Response headers | ||
* @return Void | ||
* @constructor | ||
* @param {HeadersInit} [init] - Response headers | ||
*/ | ||
constructor(init = undefined) { | ||
this[MAP] = Object.create(null); | ||
constructor(init) { | ||
// Validate and normalize init object in [name, value(s)][] | ||
/** @type {string[][]} */ | ||
let result = []; | ||
if (init instanceof Headers) { | ||
const rawHeaders = init.raw(); | ||
const headerNames = Object.keys(rawHeaders); | ||
for (const headerName of headerNames) { | ||
for (const value of rawHeaders[headerName]) { | ||
this.append(headerName, value); | ||
} | ||
const raw = init.raw(); | ||
for (const [name, values] of Object.entries(raw)) { | ||
result.push(...values.map(value => [name, value])); | ||
} | ||
return; | ||
} | ||
// We don't worry about converting prop to ByteString here as append() | ||
// will handle it. | ||
// eslint-disable-next-line no-eq-null, eqeqeq | ||
if (init == null) { | ||
} else if (init == null) { // eslint-disable-line no-eq-null, eqeqeq | ||
// No op | ||
} else if (typeof init === 'object') { | ||
} else if (typeof init === 'object' && !types.isBoxedPrimitive(init)) { | ||
const method = init[Symbol.iterator]; | ||
// eslint-disable-next-line no-eq-null, eqeqeq | ||
if (method != null) { | ||
if (method == null) { | ||
// Record<ByteString, ByteString> | ||
result.push(...Object.entries(init)); | ||
} else { | ||
if (typeof method !== 'function') { | ||
@@ -87,46 +73,93 @@ throw new TypeError('Header pairs must be iterable'); | ||
// Note: per spec we have to first exhaust the lists then process them | ||
const pairs = []; | ||
for (const pair of init) { | ||
if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { | ||
throw new TypeError('Each header pair must be iterable'); | ||
} | ||
result = [...init] | ||
.map(pair => { | ||
if ( | ||
typeof pair !== 'object' || types.isBoxedPrimitive(pair) | ||
) { | ||
throw new TypeError('Each header pair must be an iterable object'); | ||
} | ||
pairs.push([...pair]); | ||
} | ||
return [...pair]; | ||
}).map(pair => { | ||
if (pair.length !== 2) { | ||
throw new TypeError('Each header pair must be a name/value tuple'); | ||
} | ||
for (const pair of pairs) { | ||
if (pair.length !== 2) { | ||
throw new TypeError('Each header pair must be a name/value tuple'); | ||
} | ||
return [...pair]; | ||
}); | ||
} | ||
} else { | ||
throw new TypeError('Failed to construct \'Headers\': The provided value is not of type \'(sequence<sequence<ByteString>> or record<ByteString, ByteString>)'); | ||
} | ||
this.append(pair[0], pair[1]); | ||
// Validate and lowercase | ||
result = | ||
result.length > 0 ? | ||
result.map(([name, value]) => { | ||
validateName(name); | ||
validateValue(value); | ||
return [String(name).toLowerCase(), value]; | ||
}) : | ||
undefined; | ||
super(result); | ||
// Returning a Proxy that will lowercase key names, validate parameters and sort keys | ||
// eslint-disable-next-line no-constructor-return | ||
return new Proxy(this, { | ||
get(target, p, receiver) { | ||
switch (p) { | ||
case 'append': | ||
case 'set': | ||
return (name, value) => { | ||
validateName(name); | ||
validateValue(value); | ||
return URLSearchParams.prototype[p].call( | ||
receiver, | ||
String(name).toLowerCase(), | ||
value | ||
); | ||
}; | ||
case 'delete': | ||
case 'has': | ||
case 'getAll': | ||
return name => { | ||
validateName(name); | ||
return URLSearchParams.prototype[p].call( | ||
receiver, | ||
String(name).toLowerCase() | ||
); | ||
}; | ||
case 'keys': | ||
return () => { | ||
target.sort(); | ||
return new Set(URLSearchParams.prototype.keys.call(target)).keys(); | ||
}; | ||
default: | ||
return Reflect.get(target, p, receiver); | ||
} | ||
} else { | ||
// Record<ByteString, ByteString> | ||
for (const key of Object.keys(init)) { | ||
const value = init[key]; | ||
this.append(key, value); | ||
} | ||
} | ||
} else { | ||
throw new TypeError('Provided initializer must be an object'); | ||
} | ||
/* c8 ignore next */ | ||
}); | ||
} | ||
/** | ||
* Return combined header value given name | ||
* | ||
* @param String name Header name | ||
* @return Mixed | ||
*/ | ||
get [Symbol.toStringTag]() { | ||
return 'Headers'; | ||
} | ||
toString() { | ||
return Object.prototype.toString.call(this); | ||
} | ||
get(name) { | ||
name = `${name}`; | ||
validateName(name); | ||
const key = find(this[MAP], name); | ||
if (key === undefined) { | ||
const values = this.getAll(name); | ||
if (values.length === 0) { | ||
return null; | ||
} | ||
let value = this[MAP][key].join(', '); | ||
if (name.toLowerCase() === 'content-encoding') { | ||
let value = values.join(', '); | ||
if (/^content-encoding$/i.test(name)) { | ||
value = value.toLowerCase(); | ||
@@ -138,256 +171,89 @@ } | ||
/** | ||
* Iterate over all headers | ||
* | ||
* @param Function callback Executed for each item with parameters (value, name, thisArg) | ||
* @param Boolean thisArg `this` context for callback function | ||
* @return Void | ||
*/ | ||
forEach(callback, thisArg = undefined) { | ||
let pairs = getHeaders(this); | ||
let i = 0; | ||
while (i < pairs.length) { | ||
const [name, value] = pairs[i]; | ||
callback.call(thisArg, value, name, this); | ||
pairs = getHeaders(this); | ||
i++; | ||
forEach(callback) { | ||
for (const name of this.keys()) { | ||
callback(this.get(name), name); | ||
} | ||
} | ||
/** | ||
* Overwrite header values given name | ||
* | ||
* @param String name Header name | ||
* @param String value Header value | ||
* @return Void | ||
*/ | ||
set(name, value) { | ||
name = `${name}`; | ||
value = `${value}`; | ||
validateName(name); | ||
validateValue(value); | ||
const key = find(this[MAP], name); | ||
this[MAP][key !== undefined ? key : name] = [value]; | ||
* values() { | ||
for (const name of this.keys()) { | ||
yield this.get(name); | ||
} | ||
} | ||
/** | ||
* Append a value onto existing header | ||
* | ||
* @param String name Header name | ||
* @param String value Header value | ||
* @return Void | ||
* @type {() => IterableIterator<[string, string]>} | ||
*/ | ||
append(name, value) { | ||
name = `${name}`; | ||
value = `${value}`; | ||
validateName(name); | ||
validateValue(value); | ||
const key = find(this[MAP], name); | ||
if (key !== undefined) { | ||
this[MAP][key].push(value); | ||
} else { | ||
this[MAP][name] = [value]; | ||
* entries() { | ||
for (const name of this.keys()) { | ||
yield [name, this.get(name)]; | ||
} | ||
} | ||
/** | ||
* Check for header name existence | ||
* | ||
* @param String name Header name | ||
* @return Boolean | ||
*/ | ||
has(name) { | ||
name = `${name}`; | ||
validateName(name); | ||
return find(this[MAP], name) !== undefined; | ||
[Symbol.iterator]() { | ||
return this.entries(); | ||
} | ||
/** | ||
* Delete all header values given name | ||
* | ||
* @param String name Header name | ||
* @return Void | ||
* Node-fetch non-spec method | ||
* returning all headers and their values as array | ||
* @returns {Record<string, string[]>} | ||
*/ | ||
delete(name) { | ||
name = `${name}`; | ||
validateName(name); | ||
const key = find(this[MAP], name); | ||
if (key !== undefined) { | ||
delete this[MAP][key]; | ||
} | ||
} | ||
/** | ||
* Return raw headers (non-spec api) | ||
* | ||
* @return Object | ||
*/ | ||
raw() { | ||
return this[MAP]; | ||
return [...this.keys()].reduce((result, key) => { | ||
result[key] = this.getAll(key); | ||
return result; | ||
}, {}); | ||
} | ||
/** | ||
* Get an iterator on keys. | ||
* | ||
* @return Iterator | ||
* For better console.log(headers) and also to convert Headers into Node.js Request compatible format | ||
*/ | ||
keys() { | ||
return createHeadersIterator(this, 'key'); | ||
} | ||
[Symbol.for('nodejs.util.inspect.custom')]() { | ||
return [...this.keys()].reduce((result, key) => { | ||
const values = this.getAll(key); | ||
// Http.request() only supports string as Host header. | ||
// This hack makes specifying custom Host header possible. | ||
if (key === 'host') { | ||
result[key] = values[0]; | ||
} else { | ||
result[key] = values.length > 1 ? values : values[0]; | ||
} | ||
/** | ||
* Get an iterator on values. | ||
* | ||
* @return Iterator | ||
*/ | ||
values() { | ||
return createHeadersIterator(this, 'value'); | ||
return result; | ||
}, {}); | ||
} | ||
/** | ||
* Get an iterator on entries. | ||
* | ||
* This is the default iterator of the Headers object. | ||
* | ||
* @return Iterator | ||
*/ | ||
[Symbol.iterator]() { | ||
return createHeadersIterator(this, 'key+value'); | ||
} | ||
} | ||
Headers.prototype.entries = Headers.prototype[Symbol.iterator]; | ||
Object.defineProperty(Headers.prototype, Symbol.toStringTag, { | ||
value: 'Headers', | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Object.defineProperties(Headers.prototype, { | ||
get: {enumerable: true}, | ||
forEach: {enumerable: true}, | ||
set: {enumerable: true}, | ||
append: {enumerable: true}, | ||
has: {enumerable: true}, | ||
delete: {enumerable: true}, | ||
keys: {enumerable: true}, | ||
values: {enumerable: true}, | ||
entries: {enumerable: true} | ||
}); | ||
function getHeaders(headers, kind = 'key+value') { | ||
const keys = Object.keys(headers[MAP]).sort(); | ||
return keys.map( | ||
kind === 'key' ? | ||
k => k.toLowerCase() : | ||
(kind === 'value' ? | ||
k => headers[MAP][k].join(', ') : | ||
k => [k.toLowerCase(), headers[MAP][k].join(', ')]) | ||
); | ||
} | ||
const INTERNAL = Symbol('internal'); | ||
function createHeadersIterator(target, kind) { | ||
const iterator = Object.create(HeadersIteratorPrototype); | ||
iterator[INTERNAL] = { | ||
target, | ||
kind, | ||
index: 0 | ||
}; | ||
return iterator; | ||
} | ||
const HeadersIteratorPrototype = Object.setPrototypeOf({ | ||
next() { | ||
// istanbul ignore if | ||
if (!this || | ||
Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { | ||
throw new TypeError('Value of `this` is not a HeadersIterator'); | ||
} | ||
const { | ||
target, | ||
kind, | ||
index | ||
} = this[INTERNAL]; | ||
const values = getHeaders(target, kind); | ||
const length_ = values.length; | ||
if (index >= length_) { | ||
return { | ||
value: undefined, | ||
done: true | ||
}; | ||
} | ||
this[INTERNAL].index = index + 1; | ||
return { | ||
value: values[index], | ||
done: false | ||
}; | ||
} | ||
}, Object.getPrototypeOf( | ||
Object.getPrototypeOf([][Symbol.iterator]()) | ||
)); | ||
Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { | ||
value: 'HeadersIterator', | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
/** | ||
* Export the Headers object in a form that Node.js can consume. | ||
* | ||
* @param Headers headers | ||
* @return Object | ||
* Re-shaping object for Web IDL tests | ||
* Only need to do it for overridden methods | ||
*/ | ||
export function exportNodeCompatibleHeaders(headers) { | ||
const object = {__proto__: null, ...headers[MAP]}; | ||
Object.defineProperties( | ||
Headers.prototype, | ||
['get', 'entries', 'forEach', 'values'].reduce((result, property) => { | ||
result[property] = {enumerable: true}; | ||
return result; | ||
}, {}) | ||
); | ||
// Http.request() only supports string as Host header. This hack makes | ||
// specifying custom Host header possible. | ||
const hostHeaderKey = find(headers[MAP], 'Host'); | ||
if (hostHeaderKey !== undefined) { | ||
object[hostHeaderKey] = object[hostHeaderKey][0]; | ||
} | ||
return object; | ||
} | ||
/** | ||
* Create a Headers object from an object of headers, ignoring those that do | ||
* Create a Headers object from an http.IncomingMessage.rawHeaders, ignoring those that do | ||
* not conform to HTTP grammar productions. | ||
* | ||
* @param Object obj Object of headers | ||
* @return Headers | ||
* @param {import('http').IncomingMessage['rawHeaders']} headers | ||
*/ | ||
export function createHeadersLenient(object) { | ||
const headers = new Headers(); | ||
for (const name of Object.keys(object)) { | ||
if (invalidTokenRegex.test(name)) { | ||
continue; | ||
} | ||
if (Array.isArray(object[name])) { | ||
for (const value of object[name]) { | ||
if (invalidHeaderCharRegex.test(value)) { | ||
continue; | ||
export function fromRawHeaders(headers = []) { | ||
return new Headers( | ||
headers | ||
// Split into pairs | ||
.reduce((result, value, index, array) => { | ||
if (index % 2 === 0) { | ||
result.push(array.slice(index, index + 2)); | ||
} | ||
if (headers[MAP][name] === undefined) { | ||
headers[MAP][name] = [value]; | ||
} else { | ||
headers[MAP][name].push(value); | ||
} | ||
} | ||
} else if (!invalidHeaderCharRegex.test(object[name])) { | ||
headers[MAP][name] = [object[name]]; | ||
} | ||
} | ||
return result; | ||
}, []) | ||
.filter(([name, value]) => !(invalidTokenRegex.test(name) || invalidHeaderCharRegex.test(value))) | ||
return headers; | ||
); | ||
} |
@@ -15,9 +15,12 @@ /** | ||
import Body, {writeToStream, getTotalBytes} from './body'; | ||
import Response from './response'; | ||
import Headers, {createHeadersLenient} from './headers'; | ||
import Request, {getNodeRequestOptions} from './request'; | ||
import FetchError from './errors/fetch-error'; | ||
import AbortError from './errors/abort-error'; | ||
import Body, {writeToStream, getTotalBytes} 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 {isRedirect} from './utils/is-redirect.js'; | ||
export {Headers, Request, Response, FetchError, AbortError, isRedirect}; | ||
/** | ||
@@ -30,3 +33,3 @@ * Fetch function | ||
*/ | ||
export default function fetch(url, options_) { | ||
const fetch = (url, options_) => { | ||
// Allow custom promise | ||
@@ -43,4 +46,4 @@ if (!fetch.Promise) { | ||
const data = dataURIToBuffer(url); | ||
const res = new Response(data, {headers: {'Content-Type': data.type}}); | ||
return fetch.Promise.resolve(res); | ||
const response = new Response(data, {headers: {'Content-Type': data.type}}); | ||
return fetch.Promise.resolve(response); | ||
} | ||
@@ -97,3 +100,3 @@ | ||
function finalize() { | ||
const finalize = () => { | ||
request_.abort(); | ||
@@ -103,11 +106,4 @@ if (signal) { | ||
} | ||
} | ||
}; | ||
if (request.timeout) { | ||
request_.setTimeout(request.timeout, () => { | ||
finalize(); | ||
reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); | ||
}); | ||
} | ||
request_.on('error', err => { | ||
@@ -118,7 +114,8 @@ reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); | ||
request_.on('response', res => { | ||
const headers = createHeadersLenient(res.headers); | ||
request_.on('response', response_ => { | ||
request_.setTimeout(0); | ||
const headers = fromRawHeaders(response_.rawHeaders); | ||
// HTTP fetch step 5 | ||
if (fetch.isRedirect(res.statusCode)) { | ||
if (isRedirect(response_.statusCode)) { | ||
// HTTP fetch step 5.2 | ||
@@ -142,4 +139,4 @@ const location = headers.get('Location'); | ||
headers.set('Location', locationURL); | ||
/* c8 ignore next 3 */ | ||
} catch (error) { | ||
// istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request | ||
reject(error); | ||
@@ -173,8 +170,7 @@ } | ||
body: request.body, | ||
signal: request.signal, | ||
timeout: request.timeout | ||
signal: request.signal | ||
}; | ||
// HTTP-redirect fetch step 9 | ||
if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { | ||
if (response_.statusCode !== 303 && request.body && getTotalBytes(request) === null) { | ||
reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); | ||
@@ -186,3 +182,3 @@ finalize(); | ||
// HTTP-redirect fetch step 11 | ||
if (res.statusCode === 303 || ((res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST')) { | ||
if (response_.statusCode === 303 || ((response_.statusCode === 301 || response_.statusCode === 302) && request.method === 'POST')) { | ||
requestOptions.method = 'GET'; | ||
@@ -205,3 +201,3 @@ requestOptions.body = undefined; | ||
// Prepare response | ||
res.once('end', () => { | ||
response_.once('end', () => { | ||
if (signal) { | ||
@@ -212,3 +208,3 @@ signal.removeEventListener('abort', abortAndFinalize); | ||
let body = pump(res, new PassThrough(), error => { | ||
let body = pump(response_, new PassThrough(), error => { | ||
reject(error); | ||
@@ -219,7 +215,6 @@ }); | ||
url: request.url, | ||
status: res.statusCode, | ||
statusText: res.statusMessage, | ||
status: response_.statusCode, | ||
statusText: response_.statusMessage, | ||
headers, | ||
size: request.size, | ||
timeout: request.timeout, | ||
counter: request.counter, | ||
@@ -240,3 +235,3 @@ highWaterMark: request.highWaterMark | ||
// 5. content not modified response (304) | ||
if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { | ||
if (!request.compress || request.method === 'HEAD' || codings === null || response_.statusCode === 204 || response_.statusCode === 304) { | ||
response = new Response(body, responseOptions); | ||
@@ -271,3 +266,3 @@ resolve(response); | ||
// a hack for old IIS and Apache servers | ||
const raw = pump(res, new PassThrough(), error => { | ||
const raw = pump(response_, new PassThrough(), error => { | ||
reject(error); | ||
@@ -294,3 +289,3 @@ }); | ||
// For br | ||
if (codings === 'br' && typeof zlib.createBrotliDecompress === 'function') { | ||
if (codings === 'br') { | ||
body = pump(body, zlib.createBrotliDecompress(), error => { | ||
@@ -311,17 +306,7 @@ reject(error); | ||
}); | ||
} | ||
}; | ||
/** | ||
* Redirect code matching | ||
* | ||
* @param Number code Status code | ||
* @return Boolean | ||
*/ | ||
fetch.isRedirect = code => [301, 302, 303, 307, 308].includes(code); | ||
export default fetch; | ||
// Expose Promise | ||
fetch.Promise = global.Promise; | ||
fetch.Headers = Headers; | ||
fetch.Request = Request; | ||
fetch.Response = Response; | ||
fetch.FetchError = FetchError; |
@@ -11,12 +11,9 @@ | ||
import {format as formatUrl} from 'url'; | ||
import Stream from 'stream'; | ||
import Headers, {exportNodeCompatibleHeaders} from './headers'; | ||
import Body, {clone, extractContentType, getTotalBytes} from './body'; | ||
import {isAbortSignal} from './utils/is'; | ||
import {getSearch} from './utils/get-search'; | ||
import Headers from './headers.js'; | ||
import Body, {clone, extractContentType, getTotalBytes} from './body.js'; | ||
import {isAbortSignal} from './utils/is.js'; | ||
import {getSearch} from './utils/get-search.js'; | ||
const INTERNALS = Symbol('Request internals'); | ||
const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; | ||
/** | ||
@@ -28,3 +25,3 @@ * Check if `obj` is an instance of Request. | ||
*/ | ||
function isRequest(object) { | ||
const isRequest = object => { | ||
return ( | ||
@@ -34,3 +31,3 @@ typeof object === 'object' && | ||
); | ||
} | ||
}; | ||
@@ -43,3 +40,3 @@ /** | ||
*/ | ||
function parseURL(urlString) { | ||
const parseURL = urlString => { | ||
/* | ||
@@ -56,3 +53,3 @@ Check whether the URL is absolute or not | ||
throw new TypeError('Only absolute URLs are supported'); | ||
} | ||
}; | ||
@@ -66,3 +63,3 @@ /** | ||
*/ | ||
export default class Request { | ||
export default class Request extends Body { | ||
constructor(input, init = {}) { | ||
@@ -72,3 +69,5 @@ let parsedURL; | ||
// Normalize input and force URL to be encoded as UTF-8 (https://github.com/bitinn/node-fetch/issues/245) | ||
if (!isRequest(input)) { | ||
if (isRequest(input)) { | ||
parsedURL = parseURL(input.url); | ||
} else { | ||
if (input && input.href) { | ||
@@ -85,4 +84,2 @@ // In order to support Node.js' Url objects; though WHATWG's URL objects | ||
input = {}; | ||
} else { | ||
parsedURL = parseURL(input.url); | ||
} | ||
@@ -94,3 +91,3 @@ | ||
// eslint-disable-next-line no-eq-null, eqeqeq | ||
if ((init.body != null || isRequest(input) && input.body !== null) && | ||
if (((init.body != null || isRequest(input)) && input.body !== null) && | ||
(method === 'GET' || method === 'HEAD')) { | ||
@@ -100,4 +97,3 @@ throw new TypeError('Request with GET/HEAD method cannot have body'); | ||
// eslint-disable-next-line no-eq-null, eqeqeq | ||
const inputBody = init.body != null ? | ||
const inputBody = init.body ? | ||
init.body : | ||
@@ -108,4 +104,3 @@ (isRequest(input) && input.body !== null ? | ||
Body.call(this, inputBody, { | ||
timeout: init.timeout || input.timeout || 0, | ||
super(inputBody, { | ||
size: init.size || input.size || 0 | ||
@@ -143,11 +138,7 @@ }); | ||
// Node-fetch-only options | ||
this.follow = init.follow !== undefined ? | ||
init.follow : (input.follow !== undefined ? | ||
input.follow : 20); | ||
this.compress = init.compress !== undefined ? | ||
init.compress : (input.compress !== undefined ? | ||
input.compress : true); | ||
this.follow = init.follow === undefined ? (input.follow === undefined ? 20 : input.follow) : init.follow; | ||
this.compress = init.compress === undefined ? (input.compress === undefined ? true : input.compress) : init.compress; | ||
this.counter = init.counter || input.counter || 0; | ||
this.agent = init.agent || input.agent; | ||
this.highWaterMark = init.highWaterMark || input.highWaterMark; | ||
this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384; | ||
} | ||
@@ -183,13 +174,8 @@ | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'Request'; | ||
} | ||
} | ||
Body.mixIn(Request.prototype); | ||
Object.defineProperty(Request.prototype, Symbol.toStringTag, { | ||
value: 'Request', | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); | ||
Object.defineProperties(Request.prototype, { | ||
@@ -210,3 +196,3 @@ method: {enumerable: true}, | ||
*/ | ||
export function getNodeRequestOptions(request) { | ||
export const getNodeRequestOptions = request => { | ||
const {parsedURL} = request[INTERNALS]; | ||
@@ -220,7 +206,2 @@ const headers = new Headers(request[INTERNALS].headers); | ||
// Basic fetch | ||
if (!parsedURL.protocol || !parsedURL.hostname) { | ||
throw new TypeError('Only absolute URLs are supported'); | ||
} | ||
if (!/^https?:$/.test(parsedURL.protocol)) { | ||
@@ -230,10 +211,2 @@ throw new TypeError('Only HTTP(S) protocols are supported'); | ||
if ( | ||
request.signal && | ||
request.body instanceof Stream.Readable && | ||
!streamDestructionSupported | ||
) { | ||
throw new Error('Cancellation of streamed requests with AbortSignal is not supported'); | ||
} | ||
// HTTP-network-or-cache fetch steps 2.4-2.7 | ||
@@ -258,3 +231,3 @@ let contentLengthValue = null; | ||
if (!headers.has('User-Agent')) { | ||
headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); | ||
headers.set('User-Agent', 'node-fetch'); | ||
} | ||
@@ -264,3 +237,3 @@ | ||
if (request.compress && !headers.has('Accept-Encoding')) { | ||
headers.set('Accept-Encoding', 'gzip,deflate'); | ||
headers.set('Accept-Encoding', 'gzip,deflate,br'); | ||
} | ||
@@ -294,3 +267,3 @@ | ||
method: request.method, | ||
headers: exportNodeCompatibleHeaders(headers), | ||
headers: headers[Symbol.for('nodejs.util.inspect.custom')](), | ||
agent | ||
@@ -300,2 +273,2 @@ }; | ||
return requestOptions; | ||
} | ||
}; |
@@ -7,4 +7,5 @@ /** | ||
import Headers from './headers'; | ||
import Body, {clone, extractContentType} from './body'; | ||
import Headers from './headers.js'; | ||
import Body, {clone, extractContentType} from './body.js'; | ||
import {isRedirect} from './utils/is-redirect.js'; | ||
@@ -20,5 +21,5 @@ const INTERNALS = Symbol('Response internals'); | ||
*/ | ||
export default class Response { | ||
export default class Response extends Body { | ||
constructor(body = null, options = {}) { | ||
Body.call(this, body, options); | ||
super(body, options); | ||
@@ -89,4 +90,3 @@ const status = options.status || 200; | ||
redirected: this.redirected, | ||
size: this.size, | ||
timeout: this.timeout | ||
size: this.size | ||
}); | ||
@@ -101,3 +101,3 @@ } | ||
static redirect(url, status = 302) { | ||
if (![301, 302, 303, 307, 308].includes(status)) { | ||
if (!isRedirect(status)) { | ||
throw new RangeError('Failed to execute "redirect" on "response": Invalid status code'); | ||
@@ -113,6 +113,8 @@ } | ||
} | ||
get [Symbol.toStringTag]() { | ||
return 'Response'; | ||
} | ||
} | ||
Body.mixIn(Response.prototype); | ||
Object.defineProperties(Response.prototype, { | ||
@@ -128,7 +130,1 @@ url: {enumerable: true}, | ||
Object.defineProperty(Response.prototype, Symbol.toStringTag, { | ||
value: 'Response', | ||
writable: false, | ||
enumerable: false, | ||
configurable: true | ||
}); |
@@ -1,2 +0,2 @@ | ||
export function getSearch(parsedURL) { | ||
export const getSearch = parsedURL => { | ||
if (parsedURL.search) { | ||
@@ -9,2 +9,2 @@ return parsedURL.search; | ||
return parsedURL.href[lastOffset - hash.length] === '?' ? '?' : ''; | ||
} | ||
}; |
@@ -16,3 +16,3 @@ /** | ||
*/ | ||
export function isURLSearchParams(object) { | ||
export const isURLSearchParameters = object => { | ||
return ( | ||
@@ -29,3 +29,3 @@ typeof object === 'object' && | ||
); | ||
} | ||
}; | ||
@@ -38,3 +38,3 @@ /** | ||
*/ | ||
export function isBlob(object) { | ||
export const isBlob = object => { | ||
return ( | ||
@@ -48,3 +48,3 @@ typeof object === 'object' && | ||
); | ||
} | ||
}; | ||
@@ -57,3 +57,3 @@ /** | ||
*/ | ||
export function isAbortSignal(object) { | ||
export const isAbortSignal = object => { | ||
return ( | ||
@@ -63,15 +63,5 @@ typeof object === 'object' && | ||
); | ||
} | ||
}; | ||
/** | ||
* Check if `obj` is an instance of ArrayBuffer. | ||
* | ||
* @param {*} obj | ||
* @return {boolean} | ||
*/ | ||
export function isArrayBuffer(object) { | ||
return object[NAME] === 'ArrayBuffer'; | ||
} | ||
/** | ||
* Check if `obj` is an instance of AbortError. | ||
@@ -82,4 +72,4 @@ * | ||
*/ | ||
export function isAbortError(object) { | ||
export const isAbortError = object => { | ||
return object[NAME] === 'AbortError'; | ||
} | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
20
758
22
Yes
189280
16
2780
Updatedfetch-blob@^1.0.6