node-fetch
Advanced tools
Comparing version 2.3.0 to 2.4.0
@@ -8,5 +8,13 @@ | ||
## v2.4.0 | ||
- Enhance: added `Brotli` compression support (using node's zlib). | ||
- Enhance: updated `Blob` implementation per spec. | ||
- Fix: set content type automatically for `URLSearchParams`. | ||
- Fix: `Headers` now reject empty header names. | ||
- Fix: test cases, as node 12+ no longer accepts invalid header response. | ||
## v2.3.0 | ||
- New: `AbortSignal` support, with README example. | ||
- Enhance: added `AbortSignal` support, with README example. | ||
- Enhance: handle invalid `Location` header during redirect by rejecting them explicitly with `FetchError`. | ||
@@ -13,0 +21,0 @@ - Fix: update `browser.js` to support react-native environment, where `self` isn't available globally. |
process.emitWarning("The .es.js file is deprecated. Use .mjs instead."); | ||
import Stream from 'stream'; | ||
import Stream, { Readable } from 'stream'; | ||
import http from 'http'; | ||
@@ -10,3 +10,2 @@ import Url from 'url'; | ||
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js | ||
// (MIT licensed) | ||
@@ -24,2 +23,3 @@ const BUFFER = Symbol('buffer'); | ||
const buffers = []; | ||
let size = 0; | ||
@@ -43,2 +43,3 @@ if (blobParts) { | ||
} | ||
size += buffer.length; | ||
buffers.push(buffer); | ||
@@ -61,2 +62,20 @@ } | ||
} | ||
text() { | ||
return Promise.resolve(this[BUFFER].toString()); | ||
} | ||
arrayBuffer() { | ||
const buf = this[BUFFER]; | ||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
return Promise.resolve(ab); | ||
} | ||
stream() { | ||
const readable = new Readable(); | ||
readable._read = function () {}; | ||
readable.push(this[BUFFER]); | ||
readable.push(null); | ||
return readable; | ||
} | ||
toString() { | ||
return '[object Blob]'; | ||
} | ||
slice() { | ||
@@ -170,6 +189,18 @@ const size = this.size; | ||
body = null; | ||
} else if (typeof body === 'string') ; else if (isURLSearchParams(body)) ; else if (body instanceof Blob) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') ; else if (ArrayBuffer.isView(body)) ; else if (body instanceof Stream) ; else { | ||
} else if (isURLSearchParams(body)) { | ||
// body is a URLSearchParams | ||
body = Buffer.from(body.toString()); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
body = body[BUFFER]; | ||
} else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
body = Buffer.from(body); | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); | ||
} else if (body instanceof Stream) ; else { | ||
// none of the above | ||
// coerce to string | ||
body = String(body); | ||
// coerce to string then buffer | ||
body = Buffer.from(String(body)); | ||
} | ||
@@ -280,3 +311,2 @@ this[INTERNALS] = { | ||
} | ||
}; | ||
@@ -329,12 +359,2 @@ | ||
// body is string | ||
if (typeof this.body === 'string') { | ||
return Body.Promise.resolve(Buffer.from(this.body)); | ||
} | ||
// body is blob | ||
if (this.body instanceof Blob) { | ||
return Body.Promise.resolve(this.body[BUFFER]); | ||
} | ||
// body is buffer | ||
@@ -345,12 +365,2 @@ if (Buffer.isBuffer(this.body)) { | ||
// body is ArrayBuffer | ||
if (Object.prototype.toString.call(this.body) === '[object ArrayBuffer]') { | ||
return Body.Promise.resolve(Buffer.from(this.body)); | ||
} | ||
// body is ArrayBufferView | ||
if (ArrayBuffer.isView(this.body)) { | ||
return Body.Promise.resolve(Buffer.from(this.body.buffer, this.body.byteOffset, this.body.byteLength)); | ||
} | ||
// istanbul ignore if: should never happen | ||
@@ -413,3 +423,3 @@ if (!(this.body instanceof Stream)) { | ||
try { | ||
resolve(Buffer.concat(accum)); | ||
resolve(Buffer.concat(accum, accumBytes)); | ||
} catch (err) { | ||
@@ -539,8 +549,5 @@ // handle streams that have accumulated too much data (issue #414) | ||
*/ | ||
function extractContentType(instance) { | ||
const body = instance.body; | ||
function extractContentType(body) { | ||
// istanbul ignore if: Currently, because of a guard in Request, body | ||
// can never be null. Included here for completeness. | ||
if (body === null) { | ||
@@ -570,6 +577,9 @@ // body is null | ||
return `multipart/form-data;boundary=${body.getBoundary()}`; | ||
} else { | ||
} else if (body instanceof Stream) { | ||
// body is stream | ||
// can't really do much about this | ||
return null; | ||
} else { | ||
// Body constructor defaults other things to string | ||
return 'text/plain;charset=UTF-8'; | ||
} | ||
@@ -595,20 +605,5 @@ } | ||
return 0; | ||
} else if (typeof body === 'string') { | ||
// body is string | ||
return Buffer.byteLength(body); | ||
} else if (isURLSearchParams(body)) { | ||
// body is URLSearchParams | ||
return Buffer.byteLength(String(body)); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
return body.size; | ||
} else if (Buffer.isBuffer(body)) { | ||
// body is buffer | ||
return body.length; | ||
} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
return body.byteLength; | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
return body.byteLength; | ||
} else if (body && typeof body.getLengthSync === 'function') { | ||
@@ -642,14 +637,2 @@ // detect form data input from form-data module | ||
dest.end(); | ||
} else if (typeof body === 'string') { | ||
// body is string | ||
dest.write(body); | ||
dest.end(); | ||
} else if (isURLSearchParams(body)) { | ||
// body is URLSearchParams | ||
dest.write(Buffer.from(String(body))); | ||
dest.end(); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
dest.write(body[BUFFER]); | ||
dest.end(); | ||
} else if (Buffer.isBuffer(body)) { | ||
@@ -659,10 +642,2 @@ // body is buffer | ||
dest.end(); | ||
} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
dest.write(Buffer.from(body)); | ||
dest.end(); | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
dest.write(Buffer.from(body.buffer, body.byteOffset, body.byteLength)); | ||
dest.end(); | ||
} else { | ||
@@ -688,3 +663,3 @@ // body is stream | ||
name = `${name}`; | ||
if (invalidTokenRegex.test(name)) { | ||
if (invalidTokenRegex.test(name) || name === '') { | ||
throw new TypeError(`${name} is not a legal HTTP header name`); | ||
@@ -1076,3 +1051,11 @@ } | ||
const status = opts.status || 200; | ||
const headers = new Headers(opts.headers); | ||
if (body != null && !headers.has('Content-Type')) { | ||
const contentType = extractContentType(body); | ||
if (contentType) { | ||
headers.append('Content-Type', contentType); | ||
} | ||
} | ||
this[INTERNALS$1] = { | ||
@@ -1082,3 +1065,3 @@ url: opts.url, | ||
statusText: opts.statusText || STATUS_CODES[status], | ||
headers: new Headers(opts.headers) | ||
headers | ||
}; | ||
@@ -1212,5 +1195,5 @@ } | ||
if (init.body != null) { | ||
const contentType = extractContentType(this); | ||
if (contentType !== null && !headers.has('Content-Type')) { | ||
if (inputBody != null && !headers.has('Content-Type')) { | ||
const contentType = extractContentType(inputBody); | ||
if (contentType) { | ||
headers.append('Content-Type', contentType); | ||
@@ -1516,3 +1499,4 @@ } | ||
body: request.body, | ||
signal: request.signal | ||
signal: request.signal, | ||
timeout: request.timeout | ||
}; | ||
@@ -1609,2 +1593,10 @@ | ||
// for br | ||
if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { | ||
body = body.pipe(zlib.createBrotliDecompress()); | ||
response = new Response(body, response_options); | ||
resolve(response); | ||
return; | ||
} | ||
// otherwise, use response as-is | ||
@@ -1611,0 +1603,0 @@ response = new Response(body, response_options); |
155
lib/index.js
@@ -7,3 +7,4 @@ 'use strict'; | ||
var Stream = _interopDefault(require('stream')); | ||
var Stream = require('stream'); | ||
var Stream__default = _interopDefault(Stream); | ||
var http = _interopDefault(require('http')); | ||
@@ -15,3 +16,2 @@ var Url = _interopDefault(require('url')); | ||
// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js | ||
// (MIT licensed) | ||
@@ -29,2 +29,3 @@ const BUFFER = Symbol('buffer'); | ||
const buffers = []; | ||
let size = 0; | ||
@@ -48,2 +49,3 @@ if (blobParts) { | ||
} | ||
size += buffer.length; | ||
buffers.push(buffer); | ||
@@ -66,2 +68,20 @@ } | ||
} | ||
text() { | ||
return Promise.resolve(this[BUFFER].toString()); | ||
} | ||
arrayBuffer() { | ||
const buf = this[BUFFER]; | ||
const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); | ||
return Promise.resolve(ab); | ||
} | ||
stream() { | ||
const readable = new Stream.Readable(); | ||
readable._read = function () {}; | ||
readable.push(this[BUFFER]); | ||
readable.push(null); | ||
return readable; | ||
} | ||
toString() { | ||
return '[object Blob]'; | ||
} | ||
slice() { | ||
@@ -151,3 +171,3 @@ const size = this.size; | ||
// fix an issue where "PassThrough" isn't a named export for node <10 | ||
const PassThrough = Stream.PassThrough; | ||
const PassThrough = Stream__default.PassThrough; | ||
@@ -176,6 +196,18 @@ /** | ||
body = null; | ||
} else if (typeof body === 'string') ; else if (isURLSearchParams(body)) ; else if (body instanceof Blob) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') ; else if (ArrayBuffer.isView(body)) ; else if (body instanceof Stream) ; else { | ||
} else if (isURLSearchParams(body)) { | ||
// body is a URLSearchParams | ||
body = Buffer.from(body.toString()); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
body = body[BUFFER]; | ||
} else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
body = Buffer.from(body); | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); | ||
} else if (body instanceof Stream__default) ; else { | ||
// none of the above | ||
// coerce to string | ||
body = String(body); | ||
// coerce to string then buffer | ||
body = Buffer.from(String(body)); | ||
} | ||
@@ -190,3 +222,3 @@ this[INTERNALS] = { | ||
if (body instanceof Stream) { | ||
if (body instanceof Stream__default) { | ||
body.on('error', function (err) { | ||
@@ -287,3 +319,2 @@ const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); | ||
} | ||
}; | ||
@@ -336,12 +367,2 @@ | ||
// body is string | ||
if (typeof this.body === 'string') { | ||
return Body.Promise.resolve(Buffer.from(this.body)); | ||
} | ||
// body is blob | ||
if (this.body instanceof Blob) { | ||
return Body.Promise.resolve(this.body[BUFFER]); | ||
} | ||
// body is buffer | ||
@@ -352,14 +373,4 @@ if (Buffer.isBuffer(this.body)) { | ||
// body is ArrayBuffer | ||
if (Object.prototype.toString.call(this.body) === '[object ArrayBuffer]') { | ||
return Body.Promise.resolve(Buffer.from(this.body)); | ||
} | ||
// body is ArrayBufferView | ||
if (ArrayBuffer.isView(this.body)) { | ||
return Body.Promise.resolve(Buffer.from(this.body.buffer, this.body.byteOffset, this.body.byteLength)); | ||
} | ||
// istanbul ignore if: should never happen | ||
if (!(this.body instanceof Stream)) { | ||
if (!(this.body instanceof Stream__default)) { | ||
return Body.Promise.resolve(Buffer.alloc(0)); | ||
@@ -420,3 +431,3 @@ } | ||
try { | ||
resolve(Buffer.concat(accum)); | ||
resolve(Buffer.concat(accum, accumBytes)); | ||
} catch (err) { | ||
@@ -523,3 +534,3 @@ // handle streams that have accumulated too much data (issue #414) | ||
// note: we can't clone the form-data object without having it as a dependency | ||
if (body instanceof Stream && typeof body.getBoundary !== 'function') { | ||
if (body instanceof Stream__default && typeof body.getBoundary !== 'function') { | ||
// tee instance body | ||
@@ -547,8 +558,5 @@ p1 = new PassThrough(); | ||
*/ | ||
function extractContentType(instance) { | ||
const body = instance.body; | ||
function extractContentType(body) { | ||
// istanbul ignore if: Currently, because of a guard in Request, body | ||
// can never be null. Included here for completeness. | ||
if (body === null) { | ||
@@ -578,6 +586,9 @@ // body is null | ||
return `multipart/form-data;boundary=${body.getBoundary()}`; | ||
} else { | ||
} else if (body instanceof Stream__default) { | ||
// body is stream | ||
// can't really do much about this | ||
return null; | ||
} else { | ||
// Body constructor defaults other things to string | ||
return 'text/plain;charset=UTF-8'; | ||
} | ||
@@ -603,20 +614,5 @@ } | ||
return 0; | ||
} else if (typeof body === 'string') { | ||
// body is string | ||
return Buffer.byteLength(body); | ||
} else if (isURLSearchParams(body)) { | ||
// body is URLSearchParams | ||
return Buffer.byteLength(String(body)); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
return body.size; | ||
} else if (Buffer.isBuffer(body)) { | ||
// body is buffer | ||
return body.length; | ||
} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
return body.byteLength; | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
return body.byteLength; | ||
} else if (body && typeof body.getLengthSync === 'function') { | ||
@@ -650,14 +646,2 @@ // detect form data input from form-data module | ||
dest.end(); | ||
} else if (typeof body === 'string') { | ||
// body is string | ||
dest.write(body); | ||
dest.end(); | ||
} else if (isURLSearchParams(body)) { | ||
// body is URLSearchParams | ||
dest.write(Buffer.from(String(body))); | ||
dest.end(); | ||
} else if (body instanceof Blob) { | ||
// body is blob | ||
dest.write(body[BUFFER]); | ||
dest.end(); | ||
} else if (Buffer.isBuffer(body)) { | ||
@@ -667,10 +651,2 @@ // body is buffer | ||
dest.end(); | ||
} else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { | ||
// body is ArrayBuffer | ||
dest.write(Buffer.from(body)); | ||
dest.end(); | ||
} else if (ArrayBuffer.isView(body)) { | ||
// body is ArrayBufferView | ||
dest.write(Buffer.from(body.buffer, body.byteOffset, body.byteLength)); | ||
dest.end(); | ||
} else { | ||
@@ -696,3 +672,3 @@ // body is stream | ||
name = `${name}`; | ||
if (invalidTokenRegex.test(name)) { | ||
if (invalidTokenRegex.test(name) || name === '') { | ||
throw new TypeError(`${name} is not a legal HTTP header name`); | ||
@@ -1084,3 +1060,11 @@ } | ||
const status = opts.status || 200; | ||
const headers = new Headers(opts.headers); | ||
if (body != null && !headers.has('Content-Type')) { | ||
const contentType = extractContentType(body); | ||
if (contentType) { | ||
headers.append('Content-Type', contentType); | ||
} | ||
} | ||
this[INTERNALS$1] = { | ||
@@ -1090,3 +1074,3 @@ url: opts.url, | ||
statusText: opts.statusText || STATUS_CODES[status], | ||
headers: new Headers(opts.headers) | ||
headers | ||
}; | ||
@@ -1158,3 +1142,3 @@ } | ||
const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; | ||
const streamDestructionSupported = 'destroy' in Stream__default.Readable.prototype; | ||
@@ -1221,5 +1205,5 @@ /** | ||
if (init.body != null) { | ||
const contentType = extractContentType(this); | ||
if (contentType !== null && !headers.has('Content-Type')) { | ||
if (inputBody != null && !headers.has('Content-Type')) { | ||
const contentType = extractContentType(inputBody); | ||
if (contentType) { | ||
headers.append('Content-Type', contentType); | ||
@@ -1323,3 +1307,3 @@ } | ||
if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { | ||
if (request.signal && request.body instanceof Stream__default.Readable && !streamDestructionSupported) { | ||
throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); | ||
@@ -1394,3 +1378,3 @@ } | ||
// fix an issue where "PassThrough", "resolve" aren't a named export for node <10 | ||
const PassThrough$1 = Stream.PassThrough; | ||
const PassThrough$1 = Stream__default.PassThrough; | ||
const resolve_url = Url.resolve; | ||
@@ -1428,3 +1412,3 @@ | ||
reject(error); | ||
if (request.body && request.body instanceof Stream.Readable) { | ||
if (request.body && request.body instanceof Stream__default.Readable) { | ||
request.body.destroy(error); | ||
@@ -1528,3 +1512,4 @@ } | ||
body: request.body, | ||
signal: request.signal | ||
signal: request.signal, | ||
timeout: request.timeout | ||
}; | ||
@@ -1621,2 +1606,10 @@ | ||
// for br | ||
if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { | ||
body = body.pipe(zlib.createBrotliDecompress()); | ||
response = new Response(body, response_options); | ||
resolve(response); | ||
return; | ||
} | ||
// otherwise, use response as-is | ||
@@ -1623,0 +1616,0 @@ response = new Response(body, response_options); |
{ | ||
"name": "node-fetch", | ||
"version": "2.3.0", | ||
"version": "2.4.0", | ||
"description": "A light-weight module that brings window.fetch to node.js", | ||
@@ -40,6 +40,6 @@ "main": "lib/index", | ||
"devDependencies": { | ||
"abort-controller": "^1.0.2", | ||
"abortcontroller-polyfill": "^1.1.9", | ||
"babel-core": "^6.26.0", | ||
"babel-plugin-istanbul": "^4.1.5", | ||
"abort-controller": "^1.1.0", | ||
"abortcontroller-polyfill": "^1.3.0", | ||
"babel-core": "^6.26.3", | ||
"babel-plugin-istanbul": "^4.1.6", | ||
"babel-preset-env": "^1.6.1", | ||
@@ -51,5 +51,5 @@ "babel-register": "^6.16.3", | ||
"chai-string": "~1.3.0", | ||
"codecov": "^3.0.0", | ||
"cross-env": "^5.1.3", | ||
"form-data": "^2.3.1", | ||
"codecov": "^3.3.0", | ||
"cross-env": "^5.2.0", | ||
"form-data": "^2.3.3", | ||
"is-builtin-module": "^1.0.0", | ||
@@ -59,7 +59,7 @@ "mocha": "^5.0.0", | ||
"parted": "^0.1.1", | ||
"promise": "^8.0.1", | ||
"promise": "^8.0.3", | ||
"resumer": "0.0.0", | ||
"rollup": "^0.63.4", | ||
"rollup-plugin-babel": "^3.0.3", | ||
"string-to-arraybuffer": "^1.0.0", | ||
"rollup-plugin-babel": "^3.0.7", | ||
"string-to-arraybuffer": "^1.0.2", | ||
"url-search-params": "^1.0.2", | ||
@@ -66,0 +66,0 @@ "whatwg-url": "^5.0.0" |
@@ -11,3 +11,3 @@ node-fetch | ||
(We are looking for [v2 maintainers and collaborators](https://github.com/bitinn/node-fetch/issues/252)) | ||
(We are looking for [v2 maintainers and collaborators](https://github.com/bitinn/node-fetch/issues/567)) | ||
@@ -262,3 +262,3 @@ <!-- TOC --> | ||
```js | ||
import AbortContoller from 'abort-controller'; | ||
import AbortController from 'abort-controller'; | ||
@@ -527,3 +527,3 @@ const controller = new AbortController(); | ||
[codecov-url]: https://codecov.io/gh/bitinn/node-fetch | ||
[install-size-image]: https://packagephobia.now.sh/badge?p=node-fetch | ||
[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch | ||
[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch | ||
@@ -530,0 +530,0 @@ [whatwg-fetch]: https://fetch.spec.whatwg.org/ |
Sorry, the diff of this file is not supported yet
151944
4176