fix: implement CONNECT tunnel for proxy routing

Full HTTPS CONNECT tunnel implementation that:
- Routes only Polymarket domains through proxy
- Uses HTTP CONNECT method for HTTPS tunneling
- Leaves all other traffic (RPC, etc.) untouched

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-03-01 22:27:18 +07:00
co-authored by Claude Opus 4.6
parent b770f831db
commit 048d4433c2
+230 -51
View File
@@ -1,82 +1,261 @@
/**
* proxy-patch.cjs
* CommonJS module to patch https.request BEFORE any axios imports.
* This ensures @polymarket/clob-client uses the proxy.
*
* Must be imported as very first thing in the app.
* In ES modules: import './proxy-patch.cjs'
* Strategy: Patch https.request at the lowest level with domain filtering.
* This affects ALL HTTPS requests, but we only route Polymarket domains through proxy.
*
* RPC Polygon calls are NOT affected because:
* - They go to polygon-rpc.com (not in whitelist)
* - They use ethers.js JsonRpcProvider which uses fetch or its own transport
*/
const PROXY_URL = process.env.PROXY_URL || '';
if (PROXY_URL) {
// Load https-proxy-agent which patches https.request properly
const { HttpsProxyAgent } = require('https-proxy-agent');
const https = require('https');
const http = require('http');
const { URL } = require('url');
const net = require('net');
const tls = require('tls');
// Create agent that routes everything through proxy
const agent = new HttpsProxyAgent(PROXY_URL);
const proxyUrl = new URL(PROXY_URL);
const proxyHost = proxyUrl.hostname;
const proxyPort = parseInt(proxyUrl.port) || (proxyUrl.protocol === 'https:' ? 443 : 80);
const proxyAuth = (proxyUrl.username || proxyUrl.password)
? Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')
: null;
// Store original request
const originalRequest = https.request;
// Polymarket domains
const POLY_DOMAINS = [
// Polymarket domains ONLY
const POLY_HOSTS = new Set([
'polymarket.com',
'www.polymarket.com',
'clob.polymarket.com',
'gamma-api.polymarket.com',
'data-api.polymarket.com',
];
]);
function shouldProxy(url) {
try {
const hostname = new URL(url).hostname;
return POLY_DOMAINS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
} catch {
return false;
function shouldProxy(hostname) {
// Check exact match or subdomain
if (POLY_HOSTS.has(hostname)) return true;
for (const host of POLY_HOSTS) {
if (hostname.endsWith('.' + host)) return true;
}
return false;
}
// Override https.request globally
// Store original request
const originalHttpsRequest = https.request;
const originalHttpRequest = http.request;
// Create proxy-aware https.request
https.request = function(options, callback) {
// Handle both string URL and options object
let url;
let hostname;
if (typeof options === 'string') {
url = options;
} else if (options.hostname || options.host) {
const protocol = options.protocol || 'https:';
const hostname = options.hostname || options.host;
const port = options.port ? `:${options.port}` : '';
const path = options.path || '/';
url = `${protocol}//${hostname}${port}${path}`;
}
// If it's a Polymarket domain, force agent
if (url && shouldProxy(url)) {
if (typeof options === 'string') {
// Convert to options object with agent
options = {
agent: agent,
};
} else {
options.agent = agent;
try {
hostname = new URL(options).hostname;
} catch {
return originalHttpsRequest.apply(https, arguments);
}
} else if (options.hostname || options.host) {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host);
}
return originalRequest.call(https, options, callback);
// If not Polymarket, use direct connection
if (!hostname || !shouldProxy(hostname)) {
return originalHttpsRequest.apply(https, arguments);
}
// For Polymarket: create CONNECT tunnel through proxy
const targetHost = hostname;
const targetPort = 443;
// Build CONNECT request headers
const connectHeaders = {
'Host': `${targetHost}:${targetPort}`,
};
if (proxyAuth) {
connectHeaders['Proxy-Authorization'] = `Basic ${proxyAuth}`;
}
// Create socket connection through proxy
const connectOptions = {
host: proxyHost,
port: proxyPort,
method: 'CONNECT',
path: `${targetHost}:${targetPort}`,
headers: connectHeaders,
};
// Make CONNECT request to proxy
const connectReq = originalHttpRequest(connectOptions);
// Return a promise-like request that waits for tunnel
const deferredReq = {
on: function(event, handler) {
if (event === 'response' || event === 'error' || event === 'timeout') {
this._deferredHandlers = this._deferredHandlers || {};
this._deferredHandlers[event] = handler;
}
return this;
},
once: function(event, handler) {
return this.on(event, handler);
},
write: function(chunk) {
if (this._realRequest) {
this._realRequest.write(chunk);
} else {
this._buffer = this._buffer || [];
this._buffer.push(chunk);
}
return true;
},
end: function(chunk) {
if (chunk) this.write(chunk);
if (this._realRequest) {
this._realRequest.end();
} else {
this._ended = true;
}
return this;
},
setTimeout: function(ms) {
this._timeout = ms;
if (this._realRequest) {
this._realRequest.setTimeout(ms);
}
return this;
},
setHeader: function(name, value) {
this._headers = this._headers || {};
this._headers[name] = value;
return this;
},
abort: function() {
if (this._realRequest) {
this._realRequest.abort();
}
if (this._connectReq) {
this._connectReq.abort();
}
}
};
connectReq.on('connect', (res, socket) => {
if (res.statusCode !== 200) {
const err = new Error(`Proxy CONNECT failed: ${res.statusCode}`);
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
return;
}
// Wrap socket in TLS
const tlsOptions = {
socket: socket,
servername: targetHost,
rejectUnauthorized: options.rejectUnauthorized !== false,
};
const tlsSocket = tls.connect(tlsOptions);
// Now make actual HTTPS request through tunnel
const requestOptions = typeof options === 'string'
? { ...new URL(options), createConnection: () => tlsSocket }
: { ...options, createConnection: () => tlsSocket };
const realReq = originalHttpsRequest(requestOptions, (res) => {
if (callback) callback(res);
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.response) {
deferredReq._deferredHandlers.response(res);
}
});
// Apply deferred operations
if (deferredReq._headers) {
for (const [name, value] of Object.entries(deferredReq._headers)) {
realReq.setHeader(name, value);
}
}
if (deferredReq._timeout) {
realReq.setTimeout(deferredReq._timeout);
}
if (deferredReq._buffer) {
for (const chunk of deferredReq._buffer) {
realReq.write(chunk);
}
}
if (deferredReq._ended) {
realReq.end();
}
// Forward events
realReq.on('error', (err) => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
});
realReq.on('timeout', () => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.timeout) {
deferredReq._deferredHandlers.timeout();
}
});
deferredReq._realRequest = realReq;
deferredReq._connectReq = connectReq;
});
connectReq.on('error', (err) => {
if (deferredReq._deferredHandlers && deferredReq._deferredHandlers.error) {
deferredReq._deferredHandlers.error(err);
}
});
connectReq.end();
return deferredReq;
};
// Also patch https.get
https.get = function(options, callback) {
const req = https.request(options, callback);
req.end();
return req;
// Also patch http.request for completeness (though CLOB uses HTTPS)
http.request = function(options, callback) {
let hostname;
if (typeof options === 'string') {
try {
hostname = new URL(options).hostname;
} catch {
return originalHttpRequest.apply(http, arguments);
}
} else if (options.hostname || options.host) {
hostname = options.hostname || (typeof options.host === 'string' ? options.host.split(':')[0] : options.host);
}
// Only proxy Polymarket domains
if (!hostname || !shouldProxy(hostname)) {
return originalHttpRequest.apply(http, arguments);
}
// For HTTP, route through proxy directly
const targetUrl = typeof options === 'string' ? new URL(options) : null;
const path = targetUrl ? targetUrl.pathname + targetUrl.search : (options.path || '/');
const proxyOptions = {
host: proxyHost,
port: proxyPort,
method: options.method || 'GET',
path: path,
headers: { ...(options.headers || {}) },
};
if (proxyAuth) {
proxyOptions.headers['Proxy-Authorization'] = `Basic ${proxyAuth}`;
}
return originalHttpRequest.call(http, proxyOptions, callback);
};
// Set global agent as fallback
https.globalAgent = agent;
console.log(`[proxy-patch] HTTPS agent patched for Polymarket routing via proxy`);
console.log(`[proxy-patch] HTTPS/HTTP CONNECT tunnel active for Polymarket only`);
console.log(`[proxy-patch] RPC Polygon and other domains use direct connection`);
} else {
console.log('[proxy-patch] No PROXY_URL set, skipping patch');
}