import { createRequire } from "module"; import { getTlsClientTimeoutConfig } from "@/shared/utils/runtimeTimeouts"; const require = createRequire(import.meta.url); type WreqSession = { fetch: (url: string, options?: Record) => Promise; close: () => Promise | void; }; type CreateSessionFn = (options: Record) => Promise; let createSession: CreateSessionFn | null; try { const loaded = require("wreq-js") as { createSession?: CreateSessionFn }; createSession = typeof loaded.createSession === "function" ? loaded.createSession : null; } catch { createSession = null; } /** * Get proxy URL from environment variables. * Priority: HTTPS_PROXY > HTTP_PROXY > ALL_PROXY */ function getProxyFromEnv(): string | undefined { return ( process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || process.env.ALL_PROXY || process.env.all_proxy || undefined ); } interface FetchOptions { method?: string; headers?: HeadersInit; body?: unknown; redirect?: string; signal?: AbortSignal; } function normalizeHeaders(headers: HeadersInit | undefined): Record | undefined { if (!headers) return undefined; if (headers instanceof Headers) { return Object.fromEntries(headers.entries()); } if (Array.isArray(headers)) { return Object.fromEntries(headers.map(([key, value]) => [key, String(value)])); } const normalized: Record = {}; for (const [key, value] of Object.entries(headers)) { if (typeof value === "string") { normalized[key] = value; } } return normalized; } /** * TLS Client — Chrome 124 TLS fingerprint spoofing via wreq-js * Singleton instance used to disguise Node.js TLS handshake as Chrome browser. * * wreq-js natively supports proxy — TLS fingerprinting works through proxy. * Proxy URL is read from environment variables (HTTPS_PROXY, HTTP_PROXY, ALL_PROXY). */ class TlsClient { session: WreqSession | null = null; available: boolean; constructor() { this.available = !!createSession; } async getSession() { if (!this.available) return null; if (this.session) return this.session; const createSessionFn = createSession; if (!createSessionFn) return null; const proxy = getProxyFromEnv(); const sessionOpts: Record = { browser: "chrome_124", os: "macos", }; if (proxy) { sessionOpts.proxy = proxy; console.log(`[TlsClient] Using proxy: ${proxy}`); } this.session = await createSessionFn(sessionOpts); console.log("[TlsClient] Session created (Chrome 124 TLS fingerprint)"); return this.session; } /** * Fetch with Chrome 124 TLS fingerprint. * wreq-js Response is already fetch-compatible (headers, text(), json(), clone(), body). */ async fetch(url: string, options: FetchOptions = {}) { const session = await this.getSession(); if (!session) throw new Error("wreq-js not available"); const { timeoutMs } = getTlsClientTimeoutConfig(process.env, (message) => { console.warn(`[TlsClient] ${message}`); }); const method = (options.method || "GET").toUpperCase(); const wreqOptions: Record = { method, headers: normalizeHeaders(options.headers), body: options.body, redirect: options.redirect === "manual" ? "manual" : "follow", timeout: timeoutMs, }; // Pass signal through if available if (options.signal) { wreqOptions.signal = options.signal; } const response = await session.fetch(url, wreqOptions); return response; } async exit() { if (this.session) { await this.session.close(); this.session = null; } } } const tlsClient = new TlsClient(); export default tlsClient;