| |
| |
| |
| |
| |
| |
| |
| |
| |
| import http from "node:http"; |
| import https from "node:https"; |
| import { isLoopbackHost } from "../gateway/net.js"; |
| import { hasProxyEnvConfigured } from "../infra/net/proxy-env.js"; |
|
|
| |
| const directHttpAgent = new http.Agent(); |
| const directHttpsAgent = new https.Agent(); |
|
|
| |
| |
| |
| |
| |
| export function getDirectAgentForCdp(url: string): http.Agent | https.Agent | undefined { |
| try { |
| const parsed = new URL(url); |
| if (isLoopbackHost(parsed.hostname)) { |
| return parsed.protocol === "https:" || parsed.protocol === "wss:" |
| ? directHttpsAgent |
| : directHttpAgent; |
| } |
| } catch { |
| |
| } |
| return undefined; |
| } |
|
|
| |
| |
| |
| |
| export function hasProxyEnv(): boolean { |
| return hasProxyEnvConfigured(); |
| } |
|
|
| const LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]"; |
|
|
| function noProxyAlreadyCoversLocalhost(): boolean { |
| const current = process.env.NO_PROXY || process.env.no_proxy || ""; |
| return ( |
| current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]") |
| ); |
| } |
|
|
| export async function withNoProxyForLocalhost<T>(fn: () => Promise<T>): Promise<T> { |
| return await withNoProxyForCdpUrl("http://127.0.0.1", fn); |
| } |
|
|
| function isLoopbackCdpUrl(url: string): boolean { |
| try { |
| return isLoopbackHost(new URL(url).hostname); |
| } catch { |
| return false; |
| } |
| } |
|
|
| type NoProxySnapshot = { |
| noProxy: string | undefined; |
| noProxyLower: string | undefined; |
| applied: string; |
| }; |
|
|
| class NoProxyLeaseManager { |
| private leaseCount = 0; |
| private snapshot: NoProxySnapshot | null = null; |
|
|
| acquire(url: string): (() => void) | null { |
| if (!isLoopbackCdpUrl(url) || !hasProxyEnv()) { |
| return null; |
| } |
|
|
| if (this.leaseCount === 0 && !noProxyAlreadyCoversLocalhost()) { |
| const noProxy = process.env.NO_PROXY; |
| const noProxyLower = process.env.no_proxy; |
| const current = noProxy || noProxyLower || ""; |
| const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES; |
| process.env.NO_PROXY = applied; |
| process.env.no_proxy = applied; |
| this.snapshot = { noProxy, noProxyLower, applied }; |
| } |
|
|
| this.leaseCount += 1; |
| let released = false; |
| return () => { |
| if (released) { |
| return; |
| } |
| released = true; |
| this.release(); |
| }; |
| } |
|
|
| private release() { |
| if (this.leaseCount <= 0) { |
| return; |
| } |
| this.leaseCount -= 1; |
| if (this.leaseCount > 0 || !this.snapshot) { |
| return; |
| } |
|
|
| const { noProxy, noProxyLower, applied } = this.snapshot; |
| const currentNoProxy = process.env.NO_PROXY; |
| const currentNoProxyLower = process.env.no_proxy; |
| const untouched = |
| currentNoProxy === applied && |
| (currentNoProxyLower === applied || currentNoProxyLower === undefined); |
| if (untouched) { |
| if (noProxy !== undefined) { |
| process.env.NO_PROXY = noProxy; |
| } else { |
| delete process.env.NO_PROXY; |
| } |
| if (noProxyLower !== undefined) { |
| process.env.no_proxy = noProxyLower; |
| } else { |
| delete process.env.no_proxy; |
| } |
| } |
|
|
| this.snapshot = null; |
| } |
| } |
|
|
| const noProxyLeaseManager = new NoProxyLeaseManager(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function withNoProxyForCdpUrl<T>(url: string, fn: () => Promise<T>): Promise<T> { |
| const release = noProxyLeaseManager.acquire(url); |
| try { |
| return await fn(); |
| } finally { |
| release?.(); |
| } |
| } |
|
|