upd / src /lib /helpers /getFetchClient.ts
cursorpro's picture
Upload 57 files
7a4c980 verified
import { retry, type RetryOptions } from "@std/async";
import type { Config } from "./config.ts";
import { generateRandomIPv6 } from "./ipv6Rotation.ts";
import { getCurrentProxy } from "./proxyManager.ts";
type FetchInputParameter = Parameters<typeof fetch>[0];
type FetchInitParameterWithClient =
| RequestInit
| RequestInit & { client: Deno.HttpClient };
type FetchReturn = ReturnType<typeof fetch>;
export const getFetchClient = (config: Config): {
(
input: FetchInputParameter,
init?: FetchInitParameterWithClient,
): FetchReturn;
} => {
return async (
input: FetchInputParameter,
init?: RequestInit,
) => {
// Use auto-fetched proxy if enabled, otherwise use configured proxy
const proxyAddress = config.networking.auto_proxy
? getCurrentProxy()
: config.networking.proxy;
const ipv6Block = config.networking.ipv6_block;
// If proxy or IPv6 rotation is configured, create a custom HTTP client
if (proxyAddress || ipv6Block) {
const clientOptions: Deno.CreateHttpClientOptions = {};
if (proxyAddress) {
try {
const proxyUrl = new URL(proxyAddress);
// Extract credentials if present
if (proxyUrl.username && proxyUrl.password) {
clientOptions.proxy = {
url: `${proxyUrl.protocol}//${proxyUrl.host}`,
basicAuth: {
username: decodeURIComponent(proxyUrl.username),
password: decodeURIComponent(proxyUrl.password),
},
};
} else {
clientOptions.proxy = {
url: proxyAddress,
};
}
} catch {
clientOptions.proxy = {
url: proxyAddress,
};
}
}
if (ipv6Block) {
clientOptions.localAddress = generateRandomIPv6(ipv6Block);
}
const client = Deno.createHttpClient(clientOptions);
const fetchRes = await fetchShim(config, input, {
client,
headers: init?.headers,
method: init?.method,
body: init?.body,
});
client.close(); // Important: close client to avoid leaking resources
return new Response(fetchRes.body, {
status: fetchRes.status,
headers: fetchRes.headers,
});
}
return fetchShim(config, input, init);
};
};
function fetchShim(
config: Config,
input: FetchInputParameter,
init?: FetchInitParameterWithClient,
): FetchReturn {
const fetchTimeout = config.networking.fetch?.timeout_ms;
const fetchRetry = config.networking.fetch?.retry?.enabled;
const fetchMaxAttempts = config.networking.fetch?.retry?.times;
const fetchInitialDebounce = config.networking.fetch?.retry
?.initial_debounce;
const fetchDebounceMultiplier = config.networking.fetch?.retry
?.debounce_multiplier;
const retryOptions: RetryOptions = {
maxAttempts: fetchMaxAttempts,
minTimeout: fetchInitialDebounce,
multiplier: fetchDebounceMultiplier,
jitter: 0,
};
const callFetch = () =>
fetch(input, {
// only set the AbortSignal if the timeout is supplied in the config
signal: fetchTimeout
? AbortSignal.timeout(Number(fetchTimeout))
: null,
...(init || {}),
});
// if retry enabled, call retry with the fetch shim, otherwise pass the fetch shim back directly
return fetchRetry ? retry(callFetch, retryOptions) : callFetch();
}