Surfaces
Proxyline installs at the layer above each network API. It does not own sockets, except via the explicit openProxyConnectTunnel helper.
#node:http and node:https
Both modules have their request entry points patched and their globalAgent replaced.
Patched methods:
http.request,http.gethttps.request,https.get
Per request, Proxyline:
- Copies the call's options object so caller state is not mutated.
- Reads TLS-relevant options off any caller-supplied
agentand lifts them onto the request options (see TLS identity preservation). - Sets
servernameto the destination hostname when not already set and the hostname is not an IP literal. - Builds a fresh Proxyline Node agent for the request and assigns it as
options.agent. - Deletes any
createConnectionoverride so callers cannot punch through to the kernel directly. - Destroys the per-request proxy agent when the request closes.
This means caller agents are not just ignored — the per-request agent is replaced before the original method runs, so even libraries that read req.agent after construction see the proxy agent.
#TLS identity preservation
When the caller supplied an https.Agent with TLS options, the following keys are lifted into the request so the destination TLS handshake still validates correctly:
ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, honorCipherOrder, key, maxVersion, minVersion, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, sessionIdContext.
The destination servername is also inferred from the URL or hostname/host options when the caller did not set one. IP literals are not used as SNI values.
#Absolute-form requests
Some HTTP clients build absolute-form requests themselves (e.g. path: "https://api.example.com/graphql"). Proxyline leaves the path intact and forwards it through the proxy unchanged, so libraries that already implement their own proxy handling continue to function.
#undici and fetch
installGlobalProxy calls undici.setGlobalDispatcher and patches globalThis.fetch to use Proxyline's dispatcher with:
- Proxyline's managed dispatcher in managed mode, backed by
undici.ProxyAgentinstances pointed atproxyUrland trustingproxyTlswhen supplied. - Proxyline's ambient dispatcher in ambient mode, resolving each request against the current install-time
HTTP_PROXY/HTTPS_PROXY/NO_PROXYsnapshot, with the sameproxyTls.
The original dispatcher and fetch globals are captured and restored on stop(). Pass undici options to tune Proxyline-owned dispatcher defaults such as bodyTimeout, headersTimeout, allowH2, and connect.autoSelectFamily.
import { fetch } from "undici";
await fetch("https://api.example.com/health"); // routed through Proxyline
In managed mode, Proxyline's patched globalThis.fetch passes Proxyline's own dispatcher explicitly, so a per-call undici Agent or a later setGlobalDispatcher() call cannot bypass the managed proxy through that global fetch path. In ambient mode, and for callers using imported undici.fetch directly, an explicit undici Agent or Dispatcher still wins. Use proxy.createUndiciDispatcher() to get a dispatcher pre-wired to the same policy.
Proxyline also replaces globalThis.Request, Response, Headers, and FormData with versions from its undici dependency so globalThis.fetch receives compatible objects on Node versions where the built-in fetch no longer shares the package dispatcher. Requests created before Proxyline installs are normalized through the standard public Request fields. Install Proxyline first if you need non-standard Request internals, such as a dispatcher embedded in a pre-install native Request, to be preserved.
#WebSocket
WebSocket clients that accept a Node agent option (e.g. ws) can route through the proxy via:
import WebSocket from "ws";
const socket = new WebSocket("wss://events.example.com/", {
agent: proxy.createWebSocketAgent(),
});
When ambient mode is inactive, createWebSocketAgent() returns a direct-routing agent, so calling code does not need a conditional path.
Clients that do not expose an agent option but still route their handshake through http.request are covered automatically by the global patch.
#HTTP CONNECT tunnel
Some libraries — notably HTTP/2 clients — need ownership of the underlying socket. openProxyConnectTunnel performs an HTTP CONNECT against the proxy and resolves with a connected net.Socket (or tls.TLSSocket for HTTPS proxies).
import { openProxyConnectTunnel } from "@openclaw/proxyline";
const socket = await openProxyConnectTunnel({
proxyUrl: "https://proxy.corp.example:8443",
proxyTls: { caFile: "/etc/proxy-ca.pem" },
targetHost: "api.example.com",
targetPort: 443,
timeoutMs: 2_000,
});
Properties:
- HTTP and HTTPS proxy endpoints supported. Other schemes throw
UNSUPPORTED_PROXY_PROTOCOL. - HTTPS proxies use ALPN
http/1.1. SNI is the proxy hostname unless that is an IP literal. - Userinfo in the
proxyUrlbecomes aProxy-Authorization: Basic ...header. - A bounded
16 KiBheader buffer protects against malicious or runaway proxy responses. timeoutMsis enforced and emits aCONNECT_FAILEDerror on expiry.- Bytes the proxy sends after the response headers are re-injected with
socket.unshift()so the caller sees the full target stream. - Non-2xx status lines, header overrun, premature close, and socket errors are all surfaced as
ProxylineErrorwith codeCONNECT_FAILED.
The helper is standalone — it does not require installGlobalProxy to have been called.
#Caller-built agents
A http.Agent or https.Agent you construct yourself is replaced in managed mode and replaced when active in ambient mode. The replacement happens inside the patched http.request / https.request call: by the time the underlying request starts, options.agent already points at a proxy agent.
This is intentional: a common bypass is new https.Agent({ keepAlive: true }) passed per-request. Without replacement the proxy is silently skipped.
To get a proxy-aware agent without going through the patched globals, use:
proxy.createNodeAgent()— anhttp.Agentthat proxies HTTP and HTTPS requests.createAmbientNodeProxyAgent()— a standalone ambient helper that returns an agent only when proxy env applies.proxy.createUndiciDispatcher()— an undiciDispatchermirroring the current mode.proxy.createWebSocketAgent()— anhttp.Agentsuitable for WebSocket upgrades.
Helper-created agents and dispatchers are caller-owned. Destroy or close them when the caller is done.
When the runtime is inactive (ambient mode with no env proxy set), these helpers return direct agents/dispatchers.