src / tls.ts

// Chrome 131+ Cipher Suite (Approximate order)
// This list mimics the TLS Client Hello of a modern Chrome browser.
const CHROME_CIPHERS = [
	'TLS_AES_128_GCM_SHA256',
	'TLS_AES_256_GCM_SHA384',
	'TLS_CHACHA20_POLY1305_SHA256',
	'ECDHE-ECDSA-AES128-GCM-SHA256',
	'ECDHE-RSA-AES128-GCM-SHA256',
	'ECDHE-ECDSA-AES256-GCM-SHA384',
	'ECDHE-RSA-AES256-GCM-SHA384',
	'ECDHE-ECDSA-CHACHA20-POLY1305',
	'ECDHE-RSA-CHACHA20-POLY1305',
	'ECDHE-RSA-AES128-SHA',
	'ECDHE-RSA-AES256-SHA',
	'AES128-GCM-SHA256',
	'AES256-GCM-SHA384',
	'AES128-SHA',
	'AES256-SHA'
].join(':');

let globalDispatcher: any = undefined;

export async function configureUndiciDispatcher() {
	if (globalDispatcher) return globalDispatcher;

	try {
		// Dynamically import undici to avoid crashes if it's not installed/available
		// In Node 18+, undici is internal but often exposed via global 'undici' or 'Dispatcher' symbols
		// If we are in an environment with the 'undici' package installed:
		const undici = await import('undici').catch(() => null);

		if (undici && undici.Agent && undici.setGlobalDispatcher) {
			const agent = new undici.Agent({
				// Vital: mimic browser connection behavior
				connect: {
					ciphers: CHROME_CIPHERS,
					keepAlive: true,
					timeout: 15000, // 15s timeout
				},
				pipelining: 1, // Browsers often pipeline
				keepAliveTimeout: 10000,
				keepAliveMaxTimeout: 10000,
			});
			
			undici.setGlobalDispatcher(agent);
			globalDispatcher = agent;
			console.log("Enhanced TLS fingerprinting enabled.");
		}
	} catch (e) {
		// Fallback: If we can't configure undici, we just use default fetch.
		// This usually happens if the environment is strict or 'undici' package is missing.
		// In that case, we rely purely on headers.
	}
	return globalDispatcher;
}