diff --git a/src/body.js b/src/body.js index f1233034d..6304cfa8b 100644 --- a/src/body.js +++ b/src/body.js @@ -14,6 +14,7 @@ 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'; +import {blobToNodeStream} from './utils/blob-to-stream.js'; const INTERNALS = Symbol('Body internals'); @@ -177,7 +178,7 @@ async function consumeBody(data) { // Body is blob if (isBlob(body)) { - body = body.stream(); + body = blobToNodeStream(body); } // Body is buffer @@ -371,7 +372,7 @@ export const writeToStream = (dest, {body}) => { dest.end(); } else if (isBlob(body)) { // Body is Blob - body.stream().pipe(dest); + blobToNodeStream(body).pipe(dest); } else if (Buffer.isBuffer(body)) { // Body is buffer dest.write(body); diff --git a/src/utils/blob-to-stream.js b/src/utils/blob-to-stream.js new file mode 100644 index 000000000..824956252 --- /dev/null +++ b/src/utils/blob-to-stream.js @@ -0,0 +1,23 @@ +import {Readable} from 'stream'; + +// 64 KiB (same size chrome slice theirs blob into Uint8array's) +const POOL_SIZE = 65536; + +/* c8 ignore start */ +async function * read(blob) { + let position = 0; + while (position !== blob.size) { + const chunk = blob.slice(position, Math.min(blob.size, position + POOL_SIZE)); + // eslint-disable-next-line no-await-in-loop + const buffer = await chunk.arrayBuffer(); + position += buffer.byteLength; + yield new Uint8Array(buffer); + } +} +/* c8 ignore end */ + +export function blobToNodeStream(blob) { + return Readable.from(blob.stream ? blob.stream() : read(blob), { + objectMode: false + }); +} diff --git a/src/utils/is.js b/src/utils/is.js index fa8d15922..f690b3c58 100644 --- a/src/utils/is.js +++ b/src/utils/is.js @@ -38,9 +38,13 @@ export const isBlob = object => { typeof object === 'object' && typeof object.arrayBuffer === 'function' && typeof object.type === 'string' && - typeof object.stream === 'function' && + // typeof object.stream === 'function' && typeof object.constructor === 'function' && - /^(Blob|File)$/.test(object[NAME]) + ( + /* c8 ignore next 2 */ + /^(Blob|File)$/.test(object[NAME]) || + /^(Blob|File)$/.test(object.constructor.name) + ) ); }; diff --git a/test/response.js b/test/response.js index f02b67f4d..cd4753974 100644 --- a/test/response.js +++ b/test/response.js @@ -3,6 +3,7 @@ import * as stream from 'stream'; import {TextEncoder} from 'util'; import chai from 'chai'; import Blob from 'fetch-blob'; +import buffer from 'buffer'; import {Response} from '../src/index.js'; import TestServer from './utils/server.js'; @@ -173,6 +174,15 @@ describe('Response', () => { }); }); + if (buffer.Blob) { + it('should support Buffer.Blob as body', () => { + const res = new Response(new buffer.Blob(['a=1'])); + return res.text().then(result => { + expect(result).to.equal('a=1'); + }); + }); + } + it('should support Uint8Array as body', () => { const encoder = new TextEncoder(); const res = new Response(encoder.encode('a=1'));