* refactor: consolidate 5 proxy tunnel implementations into _proxy-utils.cjs
5 near-identical HTTP CONNECT proxy tunnel implementations (3 in
ais-relay.cjs, 1 in _seed-utils.mjs, 1 in seed-military-flights.mjs)
consolidated into two shared functions in _proxy-utils.cjs:
- proxyConnectTunnel(): low-level CONNECT + TLS wrapping, returns socket
- proxyFetch(): high-level fetch with decompression, custom headers,
POST support, timeout
All consumers now call the shared implementation:
- _seed-utils.mjs httpsProxyFetchRaw: 75 lines -> 6 lines
- ais-relay.cjs ytFetchViaProxy: 40 lines -> 5 lines
- ais-relay.cjs _openskyProxyConnect: 35 lines -> 8 lines
- ais-relay.cjs inline Dodo CONNECT: 25 lines -> 10 lines
- seed-military-flights.mjs proxyFetchJson: 70 lines -> 14 lines
Also wires weather alerts proxy fallback (fixes STALE_SEED health crit).
Net: -104 lines. Resolves the TODO at _seed-utils.mjs:311.
* fix(proxy): default tls=true for bare proxy strings
parseProxyConfig returned no tls field for bare-format proxies
(user:pass@host:port and host:port:user:pass). proxyConnectTunnel
checked proxyConfig.tls and used plain TCP when it was undefined,
breaking connections to Decodo which requires TLS. Only http:// URLs
should use plain TCP.
* fix(proxy): timeout covers full response, pass targetPort through
- Move clearTimeout from header arrival to stream end, so a server
that stalls after 200 OK headers still hits the timeout
- Make targetPort configurable in proxyConnectTunnel (was hardcoded
443), pass through from _openskyProxyConnect
* fix(relay): proxy fallback for Yahoo/Crypto, isolate OREF proxy, fix Dockerfile
Yahoo Finance and CoinPaprika fail from Railway datacenter IPs (rate
limiting). Added PROXY_URL fallback to fetchYahooChartDirect (used by
5 seeders) and relay chart proxy endpoint. Added shared
_fetchCoinPaprikaTickers with proxy fallback + 5min cache (3 crypto
seeders share one fetch). Added CoinPaprika fallback to CryptoSectors
(previously had none).
Isolated OREF_PROXY_AUTH exclusively for OREF alerts. OpenSky,
seed-military-flights, and _proxy-utils now fall back to PROXY_URL
instead of the expensive IL-exit proxy.
Added seed-climate-news.mjs + _seed-utils.mjs COPY to Dockerfile.relay
(missing since PR #2532). Added pizzint bootstrap hydration to
cache-keys.ts, bootstrap.js, and src/services/pizzint.ts.
* fix(relay): address review — remove unused reverseMap, guard double proxy
- Remove dead reverseMap identity map in CryptoSectors Paprika fallback
- Add _proxied flag to handleYahooChartRequest._tryProxy to prevent
double proxy call on timeout→destroy→error sequence
* fix(seeder): use TLS for proxy CONNECT tunnel to fix FRED fetch failures
Decodo gate.decodo.com:10001 requires TLS. Previous code used http.request
(plain TCP) which received SOCKS5 rejection bytes instead of HTTP 200.
Two issues fixed:
1. Replace http.request CONNECT with tls.connect + manual CONNECT handshake.
Node.js http.request also auto-sets Host to the proxy hostname; Decodo
rejects this and responds with SOCKS5 bytes (0x05 0xff). Manual CONNECT
over a raw TLS socket avoids both issues.
2. Handle https:// and plain "user:pass@host:port" proxy URL formats — always
uses TLS regardless of PROXY_URL prefix.
_proxy-utils.cjs: resolveProxyStringConnect now preserves https:// prefix
from PROXY_URL so callers can detect TLS proxies explicitly.
All 24 FRED series (BAMLH0A0HYM2, FEDFUNDS, DGS10, etc.) confirmed working
locally via gate.decodo.com:10001.
* fix(seeder): respect http:// proxy scheme + buffer full CONNECT response
Two protocol-correctness fixes:
1. http:// proxies used plain TCP before; always-TLS regressed them.
Now: bare/undeclared format → TLS (Decodo requires it), explicit
http:// → plain net.connect, explicit https:// → TLS.
2. CONNECT response buffered until \r\n\r\n instead of acting on the
first data chunk. Fragmented proxy responses (headers split across
packets) could corrupt the TLS handshake by leaving header bytes
on the wire when tls.connect() was called too early.
Verified locally: BAMLH0A0HYM2 → { date: 2026-03-26, value: 3.21 }
* chore(seeder): remove unused http import, fix stale JSDoc
- Drop `import * as http from 'node:http'` — no longer used after
replacing http.request CONNECT with tls.connect + manual handshake
- Update resolveProxyStringConnect() JSDoc: https.request → tls.connect
* fix(seed-economy): use gate.decodo.com for FRED CONNECT proxy, add fallback logging
resolveProxyString() replaces gate. → us.decodo.com for curl compatibility,
but httpsProxyFetchJson uses HTTP CONNECT tunneling which requires gate.decodo.com.
All FRED series were silently failing with "Parse Error: Expected HTTP/, RTSP/ or ICE/"
because us.decodo.com doesn't respond to CONNECT with valid HTTP.
- Add resolveProxyStringConnect() in _proxy-utils.cjs (no host replacement)
- Export resolveProxyForConnect() from _seed-utils.mjs for CONNECT-based proxy
- seed-economy: _proxyAuth uses resolveProxyForConnect() (FRED), _curlProxyAuth uses resolveProxy() (Yahoo)
- fredFetchJson now logs when direct fails and proxy is tried, labels proxy errors as "proxy: ..."
* fix(seed-economy): roll out resolveProxyForConnect to all FRED seeders
seed-economic-calendar, seed-supply-chain-trade, and seed-bls-series were
still passing the curl-oriented us.decodo.com proxy string to fredFetchJson,
which uses HTTP CONNECT tunneling and requires gate.decodo.com.
Previously each seeder (ais-relay.cjs, _seed-utils.mjs, seed-fear-greed.mjs,
seed-disease-outbreaks.mjs) had its own inline resolveProxy() with slightly
different implementations. This caused USNI seeding to fail because
parseProxyUrl() only handled URL format while PROXY_URL uses Decodo
host:port:user:pass format.
- Add scripts/_proxy-utils.cjs with parseProxyConfig(), resolveProxyConfig(),
resolveProxyString() handling both http://user:pass@host:port and
host:port:user:pass formats
- ais-relay.cjs: require _proxy-utils.cjs, alias parseProxyUrl = parseProxyConfig
- _seed-utils.mjs: import resolveProxyString via createRequire, delegate resolveProxy()
- seed-fear-greed.mjs, seed-disease-outbreaks.mjs: remove inline resolveProxy(),
import from _seed-utils.mjs instead