Files
SnowLuma/.pnpm-store/v11/files/aa/a8f22717dc2ac8222fb856466331ef5c1bbbb8ce0892c496f0ba5a542bc24dc74c19988ddf59df20fa4fcddcdf792a8bef5cb0dac33efaab5340e29308bfcf
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

120 lines
5.5 KiB
Plaintext

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeHeaders = normalizeHeaders;
exports.createFetchWithInit = createFetchWithInit;
exports.isWithinOrigin = isWithinOrigin;
exports.followWithinOrigin = followWithinOrigin;
exports.fetchLeavingRedirects = fetchLeavingRedirects;
exports.fetchWithinOrigin = fetchWithinOrigin;
exports.unfollowedRedirect = unfollowedRedirect;
/**
* Normalizes HeadersInit to a plain Record<string, string> for manipulation.
* Handles Headers objects, arrays of tuples, and plain objects.
*/
function normalizeHeaders(headers) {
if (!headers)
return {};
if (headers instanceof Headers) {
return Object.fromEntries(headers.entries());
}
if (Array.isArray(headers)) {
return Object.fromEntries(headers);
}
return { ...headers };
}
/**
* Creates a fetch function that includes base RequestInit options.
* This ensures requests inherit settings like credentials, mode, headers, etc. from the base init.
*
* @param baseFetch - The base fetch function to wrap (defaults to global fetch)
* @param baseInit - The base RequestInit to merge with each request
* @returns A wrapped fetch function that merges base options with call-specific options
*/
function createFetchWithInit(baseFetch = fetch, baseInit) {
if (!baseInit) {
return baseFetch;
}
// Return a wrapped fetch that merges base RequestInit with call-specific init
return async (url, init) => {
const mergedInit = {
...baseInit,
...init,
// Headers need special handling - merge instead of replace
headers: init?.headers ? { ...normalizeHeaders(baseInit.headers), ...normalizeHeaders(init.headers) } : baseInit.headers
};
return baseFetch(url, mergedInit);
};
}
const MAX_REDIRECTS = 5;
const REDIRECT_STATUSES = [301, 302, 303, 307, 308];
/** @internal Whether `to` has the scheme, host and port of `from`, or is its https form with both on default ports. */
function isWithinOrigin(from, to) {
if (from.protocol === to.protocol && from.hostname === to.hostname && from.port === to.port) {
return true;
}
return from.hostname === to.hostname && from.protocol === 'http:' && from.port === '' && to.protocol === 'https:' && to.port === '';
}
function redirectTarget(response, requestUrl) {
const location = REDIRECT_STATUSES.includes(response.status) ? response.headers.get('location') : null;
try {
return location ? new URL(location, requestUrl) : undefined;
}
catch {
return undefined;
}
}
/** @internal Follows the redirect in `response` while it stays within the origin of `url`; undefined when `response` is to be used as is. */
function followWithinOrigin(baseFetch, url, init, response, followed = 0) {
const target = redirectTarget(response, url);
// 301, 302 and 303 turn a request with a body into a GET, so only 307 and 308 are followed for those.
const keepsMethod = response.status === 307 || response.status === 308 || (init?.method ?? 'GET').toUpperCase() === 'GET';
if (!target || followed === MAX_REDIRECTS || !keepsMethod) {
return undefined;
}
const from = new URL(url);
const addsUserinfo = (target.username || target.password) && (target.username !== from.username || target.password !== from.password);
if (addsUserinfo || !isWithinOrigin(from, target)) {
return undefined;
}
return Promise.resolve(response.body?.cancel())
.then(() => baseFetch(target, { ...init, redirect: 'manual' }))
.then(next => followWithinOrigin(baseFetch, target, init, next, followed + 1) ?? next);
}
const followingFetches = new WeakSet();
/** @internal Wraps a fetch for `redirectPolicy: 'follow'`: `fetchWithinOrigin` returns the result as it is, so redirects are left to the fetch. */
function fetchLeavingRedirects(baseFetch) {
const following = (url, init) => (baseFetch ?? fetch)(url, init);
followingFetches.add(following);
return following;
}
/** @internal Wraps a fetch so it follows a redirect only within the request's origin; any other redirect response is returned as is. */
function fetchWithinOrigin(baseFetch = fetch) {
if (followingFetches.has(baseFetch)) {
return baseFetch;
}
return (url, init) => {
if (init?.redirect === 'error' || init?.redirect === 'manual') {
return baseFetch(url, init);
}
// Browsers answer a manual redirect with an opaque response: it is returned and nothing is followed.
return Promise.resolve(baseFetch(url, { ...init, redirect: 'manual' })).then(response => followWithinOrigin(baseFetch, url, init, response) ?? response);
};
}
/** @internal Describes a redirect that `fetchWithinOrigin` returned unfollowed, naming its target without userinfo, query or fragment. */
function unfollowedRedirect(response, requestUrl) {
if (response.type === 'opaqueredirect') {
return 'Redirect not followed: this runtime does not expose the redirect target';
}
const from = response.url || requestUrl;
const target = redirectTarget(response, from);
if (!target) {
return undefined;
}
target.username = target.password = target.search = target.hash = '';
if (target.protocol === 'http:' && new URL(from).protocol === 'https:') {
target.protocol = 'https:';
return `Redirect to plain http not followed; try ${target.href} instead`;
}
return `Redirect to ${target.href} not followed`;
}
//# sourceMappingURL=transport.js.map