File size: 3,954 Bytes
7a4c980 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | 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();
}
|