Spaces:
Running
Running
feat: add Telegram proxy support, improve fetch/undici header handling, and expand dispatcher patching
Browse files- cloudflare-proxy.js +30 -17
- start.sh +6 -3
cloudflare-proxy.js
CHANGED
|
@@ -149,15 +149,15 @@ if (PROXY_URL) {
|
|
| 149 |
const hostname = url.hostname;
|
| 150 |
const shouldProxy = shouldProxyHost(hostname);
|
| 151 |
|
| 152 |
-
let
|
| 153 |
if (request) {
|
| 154 |
-
|
| 155 |
} else {
|
| 156 |
-
|
| 157 |
}
|
| 158 |
|
| 159 |
const alreadyProxied =
|
| 160 |
-
|
| 161 |
|
| 162 |
if (!shouldProxy || alreadyProxied) {
|
| 163 |
return originalFetch(input, init);
|
|
@@ -169,9 +169,9 @@ if (PROXY_URL) {
|
|
| 169 |
);
|
| 170 |
}
|
| 171 |
|
| 172 |
-
|
| 173 |
if (PROXY_SHARED_SECRET) {
|
| 174 |
-
|
| 175 |
}
|
| 176 |
|
| 177 |
const proxiedUrl = new URL(url.pathname + url.search, proxy);
|
|
@@ -179,18 +179,25 @@ if (PROXY_URL) {
|
|
| 179 |
if (request) {
|
| 180 |
const newInit = {
|
| 181 |
method: request.method,
|
| 182 |
-
headers,
|
| 183 |
body: request.body,
|
| 184 |
redirect: request.redirect,
|
| 185 |
duplex: "half",
|
| 186 |
};
|
|
|
|
|
|
|
| 187 |
return originalFetch(new Request(proxiedUrl, newInit));
|
| 188 |
}
|
| 189 |
|
| 190 |
-
|
| 191 |
...init,
|
| 192 |
-
headers,
|
| 193 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
};
|
| 195 |
}
|
| 196 |
|
|
@@ -217,24 +224,23 @@ if (PROXY_URL) {
|
|
| 217 |
if (hostname && shouldProxyHost(hostname)) {
|
| 218 |
if (DEBUG) log(`[cloudflare-proxy] Redirecting undici ${name}.dispatch: ${hostname}${options.path || ""} -> ${proxy.hostname}`);
|
| 219 |
|
| 220 |
-
let headers = options.headers;
|
| 221 |
const targetHeader = "x-target-host";
|
| 222 |
const secretHeader = "x-proxy-key";
|
| 223 |
|
| 224 |
-
if (Array.isArray(headers)) {
|
| 225 |
let foundTarget = false;
|
| 226 |
-
for (let i = 0; i < headers.length; i += 2) {
|
| 227 |
-
if (String(headers[i]).toLowerCase() === targetHeader) {
|
| 228 |
foundTarget = true;
|
| 229 |
break;
|
| 230 |
}
|
| 231 |
}
|
| 232 |
if (!foundTarget) {
|
| 233 |
-
headers.push(targetHeader, hostname);
|
| 234 |
-
if (PROXY_SHARED_SECRET) headers.push(secretHeader, PROXY_SHARED_SECRET);
|
| 235 |
}
|
| 236 |
} else {
|
| 237 |
-
options.headers = headers || {};
|
| 238 |
if (options.headers instanceof Map || (typeof options.headers.set === 'function')) {
|
| 239 |
options.headers.set(targetHeader, hostname);
|
| 240 |
if (PROXY_SHARED_SECRET) options.headers.set(secretHeader, PROXY_SHARED_SECRET);
|
|
@@ -266,8 +272,15 @@ if (PROXY_URL) {
|
|
| 266 |
} catch (e) {}
|
| 267 |
}
|
| 268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
if (exports.fetch && !exports.fetch._patched) {
|
|
|
|
| 270 |
exports.fetch = async function (input, init) {
|
|
|
|
| 271 |
return globalThis.fetch(input, init);
|
| 272 |
};
|
| 273 |
exports.fetch._patched = true;
|
|
|
|
| 149 |
const hostname = url.hostname;
|
| 150 |
const shouldProxy = shouldProxyHost(hostname);
|
| 151 |
|
| 152 |
+
let mergedHeaders;
|
| 153 |
if (request) {
|
| 154 |
+
mergedHeaders = new Headers(request.headers);
|
| 155 |
} else {
|
| 156 |
+
mergedHeaders = new Headers(init?.headers || {});
|
| 157 |
}
|
| 158 |
|
| 159 |
const alreadyProxied =
|
| 160 |
+
mergedHeaders.has("x-target-host") || mergedHeaders.has("X-Target-Host");
|
| 161 |
|
| 162 |
if (!shouldProxy || alreadyProxied) {
|
| 163 |
return originalFetch(input, init);
|
|
|
|
| 169 |
);
|
| 170 |
}
|
| 171 |
|
| 172 |
+
mergedHeaders.set("x-target-host", hostname);
|
| 173 |
if (PROXY_SHARED_SECRET) {
|
| 174 |
+
mergedHeaders.set("x-proxy-key", PROXY_SHARED_SECRET);
|
| 175 |
}
|
| 176 |
|
| 177 |
const proxiedUrl = new URL(url.pathname + url.search, proxy);
|
|
|
|
| 179 |
if (request) {
|
| 180 |
const newInit = {
|
| 181 |
method: request.method,
|
| 182 |
+
headers: mergedHeaders,
|
| 183 |
body: request.body,
|
| 184 |
redirect: request.redirect,
|
| 185 |
duplex: "half",
|
| 186 |
};
|
| 187 |
+
// If body is null/undefined, don't set duplex as some Node versions throw
|
| 188 |
+
if (!request.body) delete newInit.duplex;
|
| 189 |
return originalFetch(new Request(proxiedUrl, newInit));
|
| 190 |
}
|
| 191 |
|
| 192 |
+
const newInit = {
|
| 193 |
...init,
|
| 194 |
+
headers: mergedHeaders,
|
| 195 |
+
};
|
| 196 |
+
if (newInit.body && !newInit.duplex) {
|
| 197 |
+
newInit.duplex = "half";
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
return originalFetch(proxiedUrl, newInit);
|
| 201 |
};
|
| 202 |
}
|
| 203 |
|
|
|
|
| 224 |
if (hostname && shouldProxyHost(hostname)) {
|
| 225 |
if (DEBUG) log(`[cloudflare-proxy] Redirecting undici ${name}.dispatch: ${hostname}${options.path || ""} -> ${proxy.hostname}`);
|
| 226 |
|
|
|
|
| 227 |
const targetHeader = "x-target-host";
|
| 228 |
const secretHeader = "x-proxy-key";
|
| 229 |
|
| 230 |
+
if (Array.isArray(options.headers)) {
|
| 231 |
let foundTarget = false;
|
| 232 |
+
for (let i = 0; i < options.headers.length; i += 2) {
|
| 233 |
+
if (String(options.headers[i]).toLowerCase() === targetHeader) {
|
| 234 |
foundTarget = true;
|
| 235 |
break;
|
| 236 |
}
|
| 237 |
}
|
| 238 |
if (!foundTarget) {
|
| 239 |
+
options.headers.push(targetHeader, hostname);
|
| 240 |
+
if (PROXY_SHARED_SECRET) options.headers.push(secretHeader, PROXY_SHARED_SECRET);
|
| 241 |
}
|
| 242 |
} else {
|
| 243 |
+
options.headers = options.headers || {};
|
| 244 |
if (options.headers instanceof Map || (typeof options.headers.set === 'function')) {
|
| 245 |
options.headers.set(targetHeader, hostname);
|
| 246 |
if (PROXY_SHARED_SECRET) options.headers.set(secretHeader, PROXY_SHARED_SECRET);
|
|
|
|
| 272 |
} catch (e) {}
|
| 273 |
}
|
| 274 |
|
| 275 |
+
// Also patch Agent and other potentially unexported classes if they have dispatch
|
| 276 |
+
if (exports.Agent && exports.Agent.prototype) patchDispatch(exports.Agent.prototype, "Agent");
|
| 277 |
+
if (exports.Pool && exports.Pool.prototype) patchDispatch(exports.Pool.prototype, "Pool");
|
| 278 |
+
if (exports.Client && exports.Client.prototype) patchDispatch(exports.Client.prototype, "Client");
|
| 279 |
+
|
| 280 |
if (exports.fetch && !exports.fetch._patched) {
|
| 281 |
+
const origFetch = exports.fetch;
|
| 282 |
exports.fetch = async function (input, init) {
|
| 283 |
+
// If we are calling undici.fetch, it should use our globalThis.fetch which is patched
|
| 284 |
return globalThis.fetch(input, init);
|
| 285 |
};
|
| 286 |
exports.fetch._patched = true;
|
start.sh
CHANGED
|
@@ -354,18 +354,21 @@ fi
|
|
| 354 |
# Telegram (supports multiple user IDs, comma-separated)
|
| 355 |
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
|
| 356 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq '.plugins.entries.telegram = {"enabled": true}')
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
| 358 |
export OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
|
| 359 |
export OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first
|
| 360 |
# Force ipv4 for Telegram specifically as HF IPv6 often times out
|
| 361 |
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--dns-result-order=ipv4first"
|
| 362 |
|
| 363 |
-
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq --arg token "$
|
| 364 |
.channels.telegram.enabled = true
|
| 365 |
| .channels.telegram.botToken = $token
|
| 366 |
| .channels.telegram.commands.native = false
|
| 367 |
| .channels.telegram.timeoutSeconds = 60
|
| 368 |
-
| .channels.telegram.apiRoot = "https://api.telegram.org"
|
| 369 |
| .channels.telegram.retry = {
|
| 370 |
"attempts": 5,
|
| 371 |
"minDelayMs": 800,
|
|
|
|
| 354 |
# Telegram (supports multiple user IDs, comma-separated)
|
| 355 |
if [ -n "${TELEGRAM_BOT_TOKEN:-}" ]; then
|
| 356 |
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq '.plugins.entries.telegram = {"enabled": true}')
|
| 357 |
+
# Trim spaces and ensure it is exported for the plugin
|
| 358 |
+
CLEAN_TG_TOKEN=$(echo "$TELEGRAM_BOT_TOKEN" | tr -d '[:space:]')
|
| 359 |
+
export TELEGRAM_BOT_TOKEN="$CLEAN_TG_TOKEN"
|
| 360 |
+
|
| 361 |
export OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1
|
| 362 |
export OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first
|
| 363 |
# Force ipv4 for Telegram specifically as HF IPv6 often times out
|
| 364 |
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--dns-result-order=ipv4first"
|
| 365 |
|
| 366 |
+
CONFIG_JSON=$(echo "$CONFIG_JSON" | jq --arg token "$CLEAN_TG_TOKEN" --arg proxy_url "${CLOUDFLARE_PROXY_URL:-}" '
|
| 367 |
.channels.telegram.enabled = true
|
| 368 |
| .channels.telegram.botToken = $token
|
| 369 |
| .channels.telegram.commands.native = false
|
| 370 |
| .channels.telegram.timeoutSeconds = 60
|
| 371 |
+
| (if $proxy_url != "" then .channels.telegram.apiRoot = $proxy_url else .channels.telegram.apiRoot = "https://api.telegram.org" end)
|
| 372 |
| .channels.telegram.retry = {
|
| 373 |
"attempts": 5,
|
| 374 |
"minDelayMs": 800,
|