@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
191 lines (190 loc) • 8.21 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFetchUndici = createFetchUndici;
const node_stream_1 = require("node:stream");
const types_1 = require("node:util/types");
const node_zlib_1 = require("node:zlib");
const fetchNodeHttp_js_1 = require("./fetchNodeHttp.js");
const Request_js_1 = require("./Request.js");
const Response_js_1 = require("./Response.js");
const URL_js_1 = require("./URL.js");
const utils_js_1 = require("./utils.js");
function createFetchUndici(getGlobalDispatcher) {
return function fetchUndici(fetchRequest) {
const dispatcher = fetchRequest.dispatcher || getGlobalDispatcher();
if (!dispatcher) {
if (process.env.DEBUG) {
console.debug('[@whatwg-node/node-fetch] - native undici dispatcher not available, falling back to node:http');
}
return (0, fetchNodeHttp_js_1.fetchNodeHttp)(fetchRequest);
}
const deferred = (0, utils_js_1.createDeferredPromise)();
let abortListener;
let passthrough;
let response;
let body = null;
const bodyInit = fetchRequest['bodyInit'];
if (bodyInit != null) {
if (typeof bodyInit === 'string' ||
Buffer.isBuffer(bodyInit) ||
(0, types_1.isUint8Array)(bodyInit) ||
bodyInit instanceof node_stream_1.Readable) {
body = bodyInit;
}
else if (fetchRequest.body != null) {
if ((0, utils_js_1.isNodeReadable)(fetchRequest.body?.readable)) {
body = fetchRequest.body.readable;
}
else {
body = node_stream_1.Readable.from(fetchRequest.body);
}
}
}
function setPassthrough(contentEncoding) {
switch (contentEncoding) {
case 'x-gzip':
case 'gzip':
passthrough = (0, node_zlib_1.createGunzip)();
break;
case 'x-deflate':
case 'deflate':
passthrough = (0, node_zlib_1.createInflate)();
break;
case 'x-deflate-raw':
case 'deflate-raw':
passthrough = (0, node_zlib_1.createInflateRaw)();
break;
case 'br':
passthrough = (0, node_zlib_1.createBrotliDecompress)();
break;
default:
passthrough = new node_stream_1.PassThrough();
}
return passthrough;
}
function onAbort(e) {
fetchRequest['_signal']?.removeEventListener('abort', abortListener);
passthrough?.destroy(e);
deferred.reject(e);
}
const headersSerializer = fetchRequest.headersSerializer || utils_js_1.getHeadersObj;
const nodeHeaders = headersSerializer(fetchRequest.headers);
if (nodeHeaders['accept-encoding'] == null) {
nodeHeaders['accept-encoding'] = 'gzip, deflate, br';
}
const dispatcherReturn = dispatcher.dispatch({
origin: fetchRequest.parsedUrl.origin,
path: fetchRequest.parsedUrl.pathname,
query: Object.fromEntries(fetchRequest.parsedUrl.searchParams),
method: fetchRequest.method,
headers: nodeHeaders,
body,
}, {
onRequestStart(controller) {
abortListener = function abortListener() {
onAbort(fetchRequest['_signal']?.reason);
controller.abort(fetchRequest['_signal']?.reason);
};
fetchRequest['_signal']?.addEventListener('abort', abortListener, { once: true });
},
onRequestUpgrade(_controller, statusCode, headers, socket) {
response = new Response_js_1.PonyfillResponse(socket, {
status: statusCode,
headers: headers,
url: fetchRequest.url,
});
deferred.resolve(response);
fetchRequest['_signal']?.removeEventListener('abort', abortListener);
},
onResponseStart(controller, statusCode, headers, statusMessage) {
if (headers.location) {
if (fetchRequest.redirect === 'error') {
const redirectError = new Error('Redirects are not allowed');
deferred.reject(redirectError);
controller.resume();
return;
}
if (fetchRequest.redirect === 'follow') {
const redirectedUrl = new URL_js_1.PonyfillURL(headers.location, fetchRequest.parsedUrl || fetchRequest.url);
const redirectResponse$ = fetchUndici(new Request_js_1.PonyfillRequest(redirectedUrl, fetchRequest));
deferred.resolve(redirectResponse$.then(redirectResponse => {
redirectResponse.redirected = true;
return redirectResponse;
}));
controller.resume();
return;
}
}
passthrough = setPassthrough(headers['content-encoding']);
deferred.resolve(new Response_js_1.PonyfillResponse(passthrough, {
status: statusCode,
statusText: statusMessage,
headers: headers,
url: fetchRequest.url,
}));
},
onResponseData(controller, chunk) {
passthrough.write(chunk);
if (controller.reason) {
onAbort(controller.reason);
}
},
onResponseEnd(controller, _trailers) {
if (controller.reason) {
onAbort(controller.reason);
}
else {
passthrough.end();
fetchRequest['_signal']?.removeEventListener('abort', abortListener);
}
},
onResponseError(controller, error) {
onAbort(error || controller.reason);
},
// Old Undici support
onConnect(abort) {
abortListener = function abortListener() {
abort(fetchRequest['_signal']?.reason);
onAbort(fetchRequest['_signal']?.reason);
};
fetchRequest['_signal']?.addEventListener('abort', abortListener, { once: true });
},
onError(error) {
onAbort(error);
},
// TODO: onUpgrade
onHeaders(statusCode, headersBuf, _resume, statusText) {
const headers = headersBuf.map(headerBuf => {
const header = headerBuf
.toString('utf-8')
.split(/:\s(.+)/)
.slice(0, 2);
if (header[0] === 'content-encoding') {
const contentEncoding = header[1];
setPassthrough(contentEncoding);
}
return header;
});
passthrough ||= new node_stream_1.PassThrough();
deferred.resolve(new Response_js_1.PonyfillResponse(passthrough, {
status: statusCode,
statusText,
headers,
url: fetchRequest.url,
}));
return true;
},
onData(chunk) {
return passthrough.write(chunk);
},
onComplete() {
passthrough.end();
fetchRequest['_signal']?.removeEventListener('abort', abortListener);
},
});
if (!dispatcherReturn) {
console.warn('Undici dispatcher returned false');
}
return deferred.promise;
};
}