HuggingPost / cloudflare-proxy.js
somratpro's picture
fix: set FRONTEND_URL to bare origin so CORS allow-origin matches browser
ff3303a
/**
* Cloudflare Proxy: Transparent Fix for Blocked Domains
*
* Patches https.request/http.request/fetch and undici to redirect traffic
* for blocked hosts through a Cloudflare Worker proxy.
*/
"use strict";
const https = require("https");
const http = require("http");
// Use stderr for logs to avoid breaking child processes that communicate via stdout JSON
const log = (...args) => console.error(...args);
let PROXY_URL = process.env.CLOUDFLARE_PROXY_URL;
if (
PROXY_URL &&
!PROXY_URL.startsWith("http://") &&
!PROXY_URL.startsWith("https://")
) {
PROXY_URL = `https://${PROXY_URL}`;
}
const DEBUG = process.env.CLOUDFLARE_PROXY_DEBUG === "true";
const PROXY_SHARED_SECRET = (process.env.CLOUDFLARE_PROXY_SECRET || "").trim();
const DEFAULT_PROXY_DOMAINS = [
"api.telegram.org", "discord.com", "discordapp.com",
"gateway.discord.gg", "status.discord.com", "web.whatsapp.com",
"graph.facebook.com", "graph.instagram.com",
"api.twitter.com", "api.x.com", "upload.twitter.com",
"api.linkedin.com", "www.linkedin.com",
"open.tiktokapis.com", "oauth.reddit.com",
"youtube.com", "www.youtube.com",
"api.openai.com",
"api.resend.com", "api.sendgrid.com", "api.mailgun.net",
"googleapis.com", "google.com", "googleusercontent.com", "gstatic.com",
];
const PROXY_DOMAINS_RAW = (process.env.CLOUDFLARE_PROXY_DOMAINS || "").trim();
const PROXY_ALL = PROXY_DOMAINS_RAW === "*";
let BLOCKED_DOMAINS;
if (PROXY_ALL) {
BLOCKED_DOMAINS = [];
} else {
const extra = PROXY_DOMAINS_RAW.split(",").map((d) => d.trim()).filter(Boolean);
const seen = new Set(DEFAULT_PROXY_DOMAINS);
BLOCKED_DOMAINS = [...DEFAULT_PROXY_DOMAINS];
for (const d of extra) {
if (!seen.has(d)) { BLOCKED_DOMAINS.push(d); seen.add(d); }
}
}
if (PROXY_URL) {
try {
const proxy = new URL(PROXY_URL);
const originalHttpsRequest = https.request;
const originalHttpRequest = http.request;
const originalFetch =
typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : null;
const shouldProxyHost = (hostname) => {
const normalized = String(hostname || "").trim().toLowerCase();
if (!normalized) return false;
const isInternal =
normalized === "localhost" ||
normalized === "127.0.0.1" ||
normalized === "::1" ||
normalized === "0.0.0.0" ||
normalized === proxy.hostname ||
normalized.endsWith(".hf.space") ||
normalized.endsWith(".huggingface.co") ||
normalized === "huggingface.co";
const should = PROXY_ALL ? !isInternal : BLOCKED_DOMAINS.some(
(domain) =>
normalized === domain || normalized.endsWith(`.${domain}`),
);
return should;
};
const patch = (original, originalModuleName) => {
return function patchedRequest(arg1, arg2, arg3) {
let options = {};
let callback;
if (typeof arg1 === "string" || arg1 instanceof URL) {
const url = typeof arg1 === "string" ? new URL(arg1) : arg1;
options = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
};
if (typeof arg2 === "object" && arg2 !== null) {
options = { ...options, ...arg2 };
callback = arg3;
} else {
callback = arg2;
}
} else {
options = { ...arg1 };
callback = arg2;
}
const hostname =
options.hostname ||
(options.host ? String(options.host).split(":")[0] : "");
const path = options.path || "/";
const headers = options.headers || {};
const shouldProxy = shouldProxyHost(hostname);
const alreadyProxied = options._proxied;
const hasTargetHeader =
headers["x-target-host"] || headers["X-Target-Host"];
if (shouldProxy && !alreadyProxied && !hasTargetHeader) {
if (DEBUG) {
log(
`[cloudflare-proxy] Redirecting ${originalModuleName}://${hostname}${path} -> ${proxy.hostname}`,
);
}
const newOptions = { ...options };
newOptions._proxied = true;
newOptions.protocol = "https:";
newOptions.hostname = proxy.hostname;
newOptions.port = proxy.port || 443;
newOptions.servername = proxy.hostname;
delete newOptions.host;
delete newOptions.agent;
newOptions.headers = {
...(options.headers || {}),
host: proxy.host,
"x-target-host": hostname,
};
if (PROXY_SHARED_SECRET) {
newOptions.headers["x-proxy-key"] = PROXY_SHARED_SECRET;
}
return originalHttpsRequest.call(https, newOptions, callback);
}
return original.call(this, arg1, arg2, arg3);
};
};
https.request = patch(originalHttpsRequest, "https");
http.request = patch(originalHttpRequest, "http");
if (originalFetch) {
globalThis.fetch = async function patchedFetch(input, init) {
const request = input instanceof Request ? input : null;
const urlStr = request ? request.url : String(input);
let url;
try {
url = new URL(urlStr);
} catch (e) {
return originalFetch(input, init);
}
const hostname = url.hostname;
const shouldProxy = shouldProxyHost(hostname);
let mergedHeaders;
if (request) {
mergedHeaders = new Headers(request.headers);
} else {
mergedHeaders = new Headers(init?.headers || {});
}
const alreadyProxied =
mergedHeaders.has("x-target-host") || mergedHeaders.has("X-Target-Host");
if (!shouldProxy || alreadyProxied) {
return originalFetch(input, init);
}
if (DEBUG) {
log(
`[cloudflare-proxy] Redirecting fetch://${hostname}${url.pathname}${url.search} -> ${proxy.hostname}`,
);
}
mergedHeaders.set("x-target-host", hostname);
if (PROXY_SHARED_SECRET) {
mergedHeaders.set("x-proxy-key", PROXY_SHARED_SECRET);
}
const proxiedUrl = new URL(url.pathname + url.search, proxy);
const logProxyError = (promise, debugInfo) => {
promise
.then(r => {
if (DEBUG && !r.ok) {
log(`[cloudflare-proxy] Proxy HTTP ${r.status} for ${hostname}: ${r.statusText}`);
}
})
.catch(err => {
const cause = err?.cause;
const causeStr = cause
? ` | cause: ${cause?.code || cause?.message || String(cause)}`
: "";
log(`[cloudflare-proxy] Proxy FAILED ${hostname}: ${err?.message}${causeStr}`);
if (DEBUG && debugInfo) {
log(`[cloudflare-proxy] Debug: ${debugInfo}`);
}
});
return promise;
};
if (request) {
const fetchOpts = {
method: request.method,
headers: mergedHeaders,
redirect: request.redirect,
};
if (request.body) {
fetchOpts.body = request.body;
fetchOpts.duplex = "half";
}
return logProxyError(
originalFetch(String(proxiedUrl), fetchOpts),
`request-mode method=${request.method} hasBody=${!!request.body}`,
);
}
// Build a fresh init: do NOT spread `init` because it may carry a
// `dispatcher`/`client` pinned to the original target's connection
// pool, which causes undici to throw UND_ERR_INVALID_ARG when we
// change the origin. Forward only well-known fetch options.
const newInit = {
method: init?.method || "GET",
headers: mergedHeaders,
};
if (init?.body != null) {
newInit.body = init.body;
if (init.body instanceof ReadableStream) {
newInit.duplex = init.duplex || "half";
}
}
if (init?.signal) newInit.signal = init.signal;
if (init?.redirect) newInit.redirect = init.redirect;
if (init?.credentials) newInit.credentials = init.credentials;
if (init?.cache) newInit.cache = init.cache;
if (init?.mode) newInit.mode = init.mode;
if (init?.referrer) newInit.referrer = init.referrer;
if (init?.referrerPolicy) newInit.referrerPolicy = init.referrerPolicy;
if (init?.integrity) newInit.integrity = init.integrity;
if (init?.keepalive != null) newInit.keepalive = init.keepalive;
const bodyType = init?.body == null
? "none"
: init.body instanceof ReadableStream
? "ReadableStream"
: (init.body?.constructor?.name || typeof init.body);
return logProxyError(
originalFetch(String(proxiedUrl), newInit),
`init-mode method=${newInit.method} body=${bodyType} initKeys=${Object.keys(init || {}).join(",")}`,
);
};
}
// undici patching
const patchUndiciInstance = (exports) => {
if (!exports) return;
const patchDispatch = (proto, name) => {
if (proto && proto.dispatch && !proto.dispatch._patched) {
const origDispatch = proto.dispatch;
proto.dispatch = function(options, handler) {
let origin = options.origin || this.origin;
if (origin && typeof origin !== 'string') {
try { origin = origin.origin || origin.toString(); } catch (e) { origin = ""; }
}
let hostname = "";
try {
hostname = new URL(String(origin)).hostname;
} catch(e) {
hostname = String(origin || "").split(':')[0];
}
if (hostname && shouldProxyHost(hostname)) {
if (DEBUG) log(`[cloudflare-proxy] Redirecting undici ${name}.dispatch: ${hostname}${options.path || ""} -> ${proxy.hostname}`);
const targetHeader = "x-target-host";
const secretHeader = "x-proxy-key";
if (Array.isArray(options.headers)) {
let foundTarget = false;
for (let i = 0; i < options.headers.length; i += 2) {
if (String(options.headers[i]).toLowerCase() === targetHeader) {
foundTarget = true;
break;
}
}
if (!foundTarget) {
options.headers.push(targetHeader, hostname);
if (PROXY_SHARED_SECRET) options.headers.push(secretHeader, PROXY_SHARED_SECRET);
}
} else {
options.headers = options.headers || {};
if (options.headers instanceof Map || (typeof options.headers.set === 'function')) {
options.headers.set(targetHeader, hostname);
if (PROXY_SHARED_SECRET) options.headers.set(secretHeader, PROXY_SHARED_SECRET);
} else {
options.headers[targetHeader] = hostname;
if (PROXY_SHARED_SECRET) options.headers[secretHeader] = PROXY_SHARED_SECRET;
}
}
options.origin = `https://${proxy.hostname}`;
}
return origDispatch.call(this, options, handler);
};
proto.dispatch._patched = true;
}
};
for (const key in exports) {
if (exports[key] && exports[key].prototype && typeof exports[key].prototype.dispatch === 'function') {
patchDispatch(exports[key].prototype, key);
}
}
if (exports.getGlobalDispatcher) {
try {
const globalDispatcher = exports.getGlobalDispatcher();
if (globalDispatcher && globalDispatcher.dispatch && !globalDispatcher.dispatch._patched) {
patchDispatch(globalDispatcher, "GlobalDispatcherInstance");
}
} catch (e) {}
}
// Also patch Agent and other potentially unexported classes if they have dispatch
if (exports.Agent && exports.Agent.prototype) patchDispatch(exports.Agent.prototype, "Agent");
if (exports.Pool && exports.Pool.prototype) patchDispatch(exports.Pool.prototype, "Pool");
if (exports.Client && exports.Client.prototype) patchDispatch(exports.Client.prototype, "Client");
if (exports.fetch && !exports.fetch._patched) {
const origFetch = exports.fetch;
exports.fetch = async function (input, init) {
// If we are calling undici.fetch, it should use our globalThis.fetch which is patched
return globalThis.fetch(input, init);
};
exports.fetch._patched = true;
}
};
// Try to require undici immediately
try {
const undici = require("undici");
patchUndiciInstance(undici);
} catch (e) {}
// Hook require() to patch any undici instance the moment it loads.
// Match either the bare "undici" id or paths whose final package
// segment IS undici (e.g. "/foo/node_modules/undici/index.js"). The
// earlier substring check `id.includes("/undici/")` would also match
// unrelated packages like "super-undici-x".
const Module = require("module");
const originalRequire = Module.prototype.require;
const UNDICI_PATH_RE = /(?:^|\/)node_modules\/undici(?:\/|$)/;
Module.prototype.require = function (id) {
const exports = originalRequire.apply(this, arguments);
if (id === "undici" || UNDICI_PATH_RE.test(id)) {
try { patchUndiciInstance(exports); } catch (e) {}
}
return exports;
};
// Startup banner: print once across all Node spawns. Use a file marker
// because every Node process (health-server, gateway, sync subprocess)
// is spawned fresh from bash with NODE_OPTIONS=--require, so an env-var
// marker won't propagate. /tmp is per-container so it resets on rebuild.
if (DEBUG) {
try {
require("fs").writeFileSync("/tmp/.cf-proxy-banner-shown", "1", {
flag: "wx",
});
log(
`[cloudflare-proxy] active (${PROXY_ALL ? "wildcard" : "list"}) -> ${proxy.hostname}`,
);
} catch (_) {
// marker exists — banner already shown by another process
}
}
} catch (error) {
log(`[cloudflare-proxy] Failed to initialize: ${error.message}`);
}
}