diff --git a/src/body.js b/src/body.js index c70b41052..4e7d66d8e 100644 --- a/src/body.js +++ b/src/body.js @@ -37,9 +37,8 @@ export default function Body(body, { } else if (isURLSearchParams(body)) { // body is a URLSearchParams body = Buffer.from(body.toString()); - } else if (body instanceof Blob) { + } else if (isBlob(body)) { // body is blob - body = body[BUFFER]; } else if (Buffer.isBuffer(body)) { // body is Buffer } else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { @@ -191,18 +190,25 @@ function consumeBody() { return Body.Promise.reject(this[INTERNALS].error); } + let body = this.body; + // body is null - if (this.body === null) { + if (body === null) { return Body.Promise.resolve(Buffer.alloc(0)); } + // body is blob + if (isBlob(body)) { + body = body.stream(); + } + // body is buffer - if (Buffer.isBuffer(this.body)) { - return Body.Promise.resolve(this.body); + if (Buffer.isBuffer(body)) { + return Body.Promise.resolve(body); } // istanbul ignore if: should never happen - if (!(this.body instanceof Stream)) { + if (!(body instanceof Stream)) { return Body.Promise.resolve(Buffer.alloc(0)); } @@ -224,7 +230,7 @@ function consumeBody() { } // handle stream errors - this.body.on('error', err => { + body.on('error', err => { if (err.name === 'AbortError') { // if the request was aborted, reject with this Error abort = true; @@ -235,7 +241,7 @@ function consumeBody() { } }); - this.body.on('data', chunk => { + body.on('data', chunk => { if (abort || chunk === null) { return; } @@ -250,7 +256,7 @@ function consumeBody() { accum.push(chunk); }); - this.body.on('end', () => { + body.on('end', () => { if (abort) { return; } @@ -355,6 +361,22 @@ function isURLSearchParams(obj) { typeof obj.sort === 'function'; } +/** + * Check if `obj` is a W3C `Blob` object (which `File` inherits from) + * @param {*} obj + * @return {boolean} + */ +function isBlob(obj) { + return typeof obj === 'object' && + typeof obj.arrayBuffer === 'function' && + typeof obj.type === 'string' && + typeof obj.stream === 'function' && + typeof obj.constructor === 'function' && + typeof obj.constructor.name === 'string' && + /^(Blob|File)$/.test(obj.constructor.name) && + /^(Blob|File)$/.test(obj[Symbol.toStringTag]) +} + /** * Clone body given Res/Req instance * @@ -407,7 +429,7 @@ export function extractContentType(body) { } else if (isURLSearchParams(body)) { // body is a URLSearchParams return 'application/x-www-form-urlencoded;charset=UTF-8'; - } else if (body instanceof Blob) { + } else if (isBlob(body)) { // body is blob return body.type || null; } else if (Buffer.isBuffer(body)) { @@ -448,6 +470,8 @@ export function getTotalBytes(instance) { if (body === null) { // body is null return 0; + } else if (isBlob(body)) { + return body.size; } else if (Buffer.isBuffer(body)) { // body is buffer return body.length; @@ -460,8 +484,7 @@ export function getTotalBytes(instance) { return null; } else { // body is stream - // can't really do much about this - return null; + return instance.size || null; } } @@ -477,6 +500,8 @@ export function writeToStream(dest, instance) { if (body === null) { // body is null dest.end(); + } else if (isBlob(body)) { + body.stream().pipe(dest); } else if (Buffer.isBuffer(body)) { // body is buffer dest.write(body);