mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
* 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
97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Shared proxy configuration parser used by ais-relay.cjs and _seed-utils.mjs.
|
|
*
|
|
* Supported formats for PROXY_URL:
|
|
* - http://user:pass@host:port (standard URL)
|
|
* - host:port:user:pass (Decodo/Smartproxy)
|
|
*
|
|
* Returns { host, port, auth: 'user:pass' } or null.
|
|
*/
|
|
function parseProxyConfig(raw) {
|
|
if (!raw) return null;
|
|
|
|
// Standard URL format: http://user:pass@host:port or https://user:pass@host:port
|
|
try {
|
|
const u = new URL(raw);
|
|
if (u.hostname) {
|
|
return {
|
|
host: u.hostname,
|
|
port: parseInt(u.port, 10),
|
|
auth: u.username ? `${decodeURIComponent(u.username)}:${decodeURIComponent(u.password)}` : null,
|
|
tls: u.protocol === 'https:',
|
|
};
|
|
}
|
|
} catch { /* fall through */ }
|
|
|
|
// Froxy/OREF format: user:pass@host:port
|
|
if (raw.includes('@')) {
|
|
const atIdx = raw.lastIndexOf('@');
|
|
const auth = raw.slice(0, atIdx);
|
|
const hostPort = raw.slice(atIdx + 1);
|
|
const colonIdx = hostPort.lastIndexOf(':');
|
|
if (colonIdx !== -1) {
|
|
const host = hostPort.slice(0, colonIdx);
|
|
const port = parseInt(hostPort.slice(colonIdx + 1), 10);
|
|
if (host && port && auth) return { host, port, auth };
|
|
}
|
|
}
|
|
|
|
// Decodo/Smartproxy format: host:port:user:pass
|
|
const parts = raw.split(':');
|
|
if (parts.length >= 4) {
|
|
const host = parts[0];
|
|
const port = parseInt(parts[1], 10);
|
|
const user = parts[2];
|
|
const pass = parts.slice(3).join(':');
|
|
if (host && port && user) return { host, port, auth: `${user}:${pass}` };
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Resolve proxy from PROXY_URL only. Returns { host, port, auth } or null.
|
|
* Use this for sources where OREF (IL-exit) proxy must NOT be used (e.g. USNI).
|
|
*/
|
|
function resolveProxyConfig() {
|
|
return parseProxyConfig(process.env.PROXY_URL || '');
|
|
}
|
|
|
|
/**
|
|
* Resolve proxy from PROXY_URL with fallback to OREF_PROXY_AUTH.
|
|
* Use this for general seeders (fear-greed, disease-outbreaks, etc.).
|
|
*/
|
|
function resolveProxyConfigWithFallback() {
|
|
return parseProxyConfig(process.env.PROXY_URL || process.env.OREF_PROXY_AUTH || '');
|
|
}
|
|
|
|
/**
|
|
* Returns proxy as "user:pass@host:port" string for use with curl -x.
|
|
* Decodo: gate.decodo.com → us.decodo.com (curl endpoint differs from CONNECT endpoint).
|
|
* Returns empty string if no proxy configured.
|
|
*/
|
|
function resolveProxyString() {
|
|
const cfg = resolveProxyConfigWithFallback();
|
|
if (!cfg) return '';
|
|
const host = cfg.host.replace(/^gate\./, 'us.');
|
|
return cfg.auth ? `${cfg.auth}@${host}:${cfg.port}` : `${host}:${cfg.port}`;
|
|
}
|
|
|
|
/**
|
|
* Returns proxy as "user:pass@host:port" string for use with HTTP CONNECT tunneling.
|
|
* Does NOT replace gate.decodo.com → us.decodo.com; CONNECT endpoint is gate.decodo.com.
|
|
* When PROXY_URL uses https:// (TLS proxy), returns "https://user:pass@host:port" so
|
|
* httpsProxyFetchJson uses tls.connect to the proxy instead of plain net.connect.
|
|
* Returns empty string if no proxy configured.
|
|
*/
|
|
function resolveProxyStringConnect() {
|
|
const cfg = resolveProxyConfigWithFallback();
|
|
if (!cfg) return '';
|
|
const base = cfg.auth ? `${cfg.auth}@${cfg.host}:${cfg.port}` : `${cfg.host}:${cfg.port}`;
|
|
return cfg.tls ? `https://${base}` : base;
|
|
}
|
|
|
|
module.exports = { parseProxyConfig, resolveProxyConfig, resolveProxyConfigWithFallback, resolveProxyString, resolveProxyStringConnect };
|