@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
280 lines (279 loc) • 9.94 kB
JavaScript
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Buffer } from 'node:buffer';
import { PonyfillReadableStream } from './ReadableStream.js';
import { fakePromise, isArrayBufferView } from './utils.js';
function getBlobPartAsBuffer(blobPart) {
if (typeof blobPart === 'string') {
return Buffer.from(blobPart);
}
else if (Buffer.isBuffer(blobPart)) {
return blobPart;
}
else if (isArrayBufferView(blobPart)) {
return Buffer.from(blobPart.buffer, blobPart.byteOffset, blobPart.byteLength);
}
else {
return Buffer.from(blobPart);
}
}
export function hasBufferMethod(obj) {
return obj != null && obj.buffer != null && typeof obj.buffer === 'function';
}
export function hasArrayBufferMethod(obj) {
return obj != null && obj.arrayBuffer != null && typeof obj.arrayBuffer === 'function';
}
export function hasBytesMethod(obj) {
return obj != null && obj.bytes != null && typeof obj.bytes === 'function';
}
export function hasTextMethod(obj) {
return obj != null && obj.text != null && typeof obj.text === 'function';
}
export function hasSizeProperty(obj) {
return obj != null && typeof obj.size === 'number';
}
export function hasStreamMethod(obj) {
return obj != null && obj.stream != null && typeof obj.stream === 'function';
}
export function hasBlobSignature(obj) {
return obj != null && obj[Symbol.toStringTag] === 'Blob';
}
export function isArrayBuffer(obj) {
return obj != null && obj.byteLength != null && obj.slice != null;
}
// Will be removed after v14 reaches EOL
// Needed because v14 doesn't have .stream() implemented
export class PonyfillBlob {
blobParts;
type;
encoding;
_size = null;
constructor(blobParts = [], options) {
this.blobParts = blobParts;
this.type = options?.type || 'application/octet-stream';
this.encoding = options?.encoding || 'utf8';
this._size = options?.size || null;
if (blobParts.length === 1 && hasBlobSignature(blobParts[0])) {
return blobParts[0];
}
}
_buffer = null;
buffer() {
if (this._buffer) {
return fakePromise(this._buffer);
}
if (this.blobParts.length === 1) {
const blobPart = this.blobParts[0];
if (hasBufferMethod(blobPart)) {
return blobPart.buffer().then(buf => {
this._buffer = buf;
return this._buffer;
});
}
if (hasBytesMethod(blobPart)) {
return blobPart.bytes().then(bytes => {
this._buffer = Buffer.from(bytes);
return this._buffer;
});
}
if (hasArrayBufferMethod(blobPart)) {
return blobPart.arrayBuffer().then(arrayBuf => {
this._buffer = Buffer.from(arrayBuf, undefined, blobPart.size);
return this._buffer;
});
}
this._buffer = getBlobPartAsBuffer(blobPart);
return fakePromise(this._buffer);
}
const jobs = [];
const bufferChunks = this.blobParts.map((blobPart, i) => {
if (hasBufferMethod(blobPart)) {
jobs.push(blobPart.buffer().then(buf => {
bufferChunks[i] = buf;
}));
return undefined;
}
else if (hasArrayBufferMethod(blobPart)) {
jobs.push(blobPart.arrayBuffer().then(arrayBuf => {
bufferChunks[i] = Buffer.from(arrayBuf, undefined, blobPart.size);
}));
return undefined;
}
else if (hasBytesMethod(blobPart)) {
jobs.push(blobPart.bytes().then(bytes => {
bufferChunks[i] = Buffer.from(bytes);
}));
return undefined;
}
else {
return getBlobPartAsBuffer(blobPart);
}
});
if (jobs.length > 0) {
return Promise.all(jobs).then(() => Buffer.concat(bufferChunks, this._size || undefined));
}
return fakePromise(Buffer.concat(bufferChunks, this._size || undefined));
}
arrayBuffer() {
if (this._buffer) {
// @ts-ignore - Mismatch between Buffer and ArrayBuffer
return fakePromise(this._buffer);
}
if (this.blobParts.length === 1) {
if (isArrayBuffer(this.blobParts[0])) {
return fakePromise(this.blobParts[0]);
}
if (hasArrayBufferMethod(this.blobParts[0])) {
return this.blobParts[0].arrayBuffer();
}
}
// @ts-ignore - Mismatch between Buffer and ArrayBuffer
return this.buffer();
}
bytes() {
if (this._buffer) {
return fakePromise(this._buffer);
}
if (this.blobParts.length === 1) {
if (Buffer.isBuffer(this.blobParts[0])) {
this._buffer = this.blobParts[0];
return fakePromise(this._buffer);
}
if (this.blobParts[0] instanceof Uint8Array) {
this._buffer = Buffer.from(this.blobParts[0]);
return fakePromise(this._buffer);
}
if (hasBytesMethod(this.blobParts[0])) {
return this.blobParts[0].bytes();
}
if (hasBufferMethod(this.blobParts[0])) {
return this.blobParts[0].buffer();
}
}
return this.buffer();
}
_text = null;
text() {
if (this._text) {
return fakePromise(this._text);
}
if (this.blobParts.length === 1) {
const blobPart = this.blobParts[0];
if (typeof blobPart === 'string') {
this._text = blobPart;
return fakePromise(this._text);
}
if (hasTextMethod(blobPart)) {
return blobPart.text().then(text => {
this._text = text;
return this._text;
});
}
const buf = getBlobPartAsBuffer(blobPart);
this._text = buf.toString(this.encoding);
return fakePromise(this._text);
}
return this.buffer().then(buf => {
this._text = buf.toString(this.encoding);
return this._text;
});
}
_json = null;
json() {
if (this._json) {
return fakePromise(this._json);
}
return this.text().then(text => {
this._json = JSON.parse(text);
return this._json;
});
}
_formData = null;
formData() {
if (this._formData) {
return fakePromise(this._formData);
}
throw new Error('Not implemented');
}
get size() {
if (this._size == null) {
this._size = 0;
for (const blobPart of this.blobParts) {
if (typeof blobPart === 'string') {
this._size += Buffer.byteLength(blobPart);
}
else if (hasSizeProperty(blobPart)) {
this._size += blobPart.size;
}
else if (isArrayBufferView(blobPart)) {
this._size += blobPart.byteLength;
}
}
}
return this._size;
}
stream() {
if (this.blobParts.length === 1) {
const blobPart = this.blobParts[0];
if (hasStreamMethod(blobPart)) {
return blobPart.stream();
}
const buf = getBlobPartAsBuffer(blobPart);
return new PonyfillReadableStream({
start: controller => {
controller.enqueue(buf);
controller.close();
},
});
}
if (this._buffer != null) {
return new PonyfillReadableStream({
start: controller => {
controller.enqueue(this._buffer);
controller.close();
},
});
}
let blobPartIterator;
return new PonyfillReadableStream({
start: controller => {
if (this.blobParts.length === 0) {
controller.close();
return;
}
blobPartIterator = this.blobParts[Symbol.iterator]();
},
pull: controller => {
const { value: blobPart, done } = blobPartIterator.next();
if (done) {
controller.close();
return;
}
if (blobPart) {
if (hasBufferMethod(blobPart)) {
return blobPart.buffer().then(buf => {
controller.enqueue(buf);
});
}
if (hasBytesMethod(blobPart)) {
return blobPart.bytes().then(bytes => {
const buf = Buffer.from(bytes);
controller.enqueue(buf);
});
}
if (hasArrayBufferMethod(blobPart)) {
return blobPart.arrayBuffer().then(arrayBuffer => {
const buf = Buffer.from(arrayBuffer, undefined, blobPart.size);
controller.enqueue(buf);
});
}
const buf = getBlobPartAsBuffer(blobPart);
controller.enqueue(buf);
}
},
});
}
slice() {
throw new Error('Not implemented');
}
}