From f50f4af4ad08567cc3fc72ceb9ca4bb31892d256 Mon Sep 17 00:00:00 2001 From: gauthier-th Date: Tue, 25 Mar 2025 22:29:03 +0100 Subject: [PATCH] fix: add back custom proxy agent --- server/utils/customProxyAgent.ts | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 server/utils/customProxyAgent.ts diff --git a/server/utils/customProxyAgent.ts b/server/utils/customProxyAgent.ts new file mode 100644 index 00000000..5f163c3d --- /dev/null +++ b/server/utils/customProxyAgent.ts @@ -0,0 +1,113 @@ +import type { ProxySettings } from '@server/lib/settings'; +import logger from '@server/logger'; +import type { Dispatcher } from 'undici'; +import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici'; + +export default async function createCustomProxyAgent( + proxySettings: ProxySettings +) { + const defaultAgent = new Agent({ keepAliveTimeout: 5000 }); + + const skipUrl = (url: string | URL) => { + const hostname = + typeof url === 'string' ? new URL(url).hostname : url.hostname; + + if (proxySettings.bypassLocalAddresses && isLocalAddress(hostname)) { + return true; + } + + for (const address of proxySettings.bypassFilter.split(',')) { + const trimmedAddress = address.trim(); + if (!trimmedAddress) { + continue; + } + + if (trimmedAddress.startsWith('*')) { + const domain = trimmedAddress.slice(1); + if (hostname.endsWith(domain)) { + return true; + } + } else if (hostname === trimmedAddress) { + return true; + } + } + + return false; + }; + + const noProxyInterceptor = ( + dispatch: Dispatcher['dispatch'] + ): Dispatcher['dispatch'] => { + return (opts, handler) => { + return opts.origin && skipUrl(opts.origin) + ? defaultAgent.dispatch(opts, handler) + : dispatch(opts, handler); + }; + }; + + const token = + proxySettings.user && proxySettings.password + ? `Basic ${Buffer.from( + `${proxySettings.user}:${proxySettings.password}` + ).toString('base64')}` + : undefined; + + try { + const proxyAgent = new ProxyAgent({ + uri: + (proxySettings.useSsl ? 'https://' : 'http://') + + proxySettings.hostname + + ':' + + proxySettings.port, + token, + keepAliveTimeout: 5000, + }); + + setGlobalDispatcher(proxyAgent.compose(noProxyInterceptor)); + } catch (e) { + logger.error('Failed to connect to the proxy: ' + e.message, { + label: 'Proxy', + }); + setGlobalDispatcher(defaultAgent); + return; + } + + try { + const res = await fetch('https://www.google.com', { method: 'HEAD' }); + if (res.ok) { + logger.debug('HTTP(S) proxy connected successfully', { label: 'Proxy' }); + } else { + logger.error('Proxy responded, but with a non-OK status: ' + res.status, { + label: 'Proxy', + }); + setGlobalDispatcher(defaultAgent); + } + } catch (e) { + logger.error( + 'Failed to connect to the proxy: ' + e.message + ': ' + e.cause, + { label: 'Proxy' } + ); + setGlobalDispatcher(defaultAgent); + } +} + +function isLocalAddress(hostname: string) { + if ( + hostname === 'localhost' || + hostname === '127.0.0.1' || + hostname === '::1' + ) { + return true; + } + + const privateIpRanges = [ + /^10\./, // 10.x.x.x + /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.x.x - 172.31.x.x + /^192\.168\./, // 192.168.x.x + ]; + if (privateIpRanges.some((regex) => regex.test(hostname))) { + return true; + } + + return false; +}