somratpro Claude Sonnet 4.6 commited on
Commit
86ec115
Β·
1 Parent(s): 3b50174

add /app/test-twitter probe + fix Worker redirect for POST

Browse files

health-server.js:
- /app/test-twitter: probes api.twitter.com GET (reachability) and
POST oauth/access_token (no auth) to distinguish IP-blocked (500 HTML)
from IP-allowed (400/401 JSON). Shows NODE_OPTIONS + CLOUDFLARE_PROXY_URL.

cloudflare-proxy-setup.py:
- Worker: change redirect:"follow" β†’ redirect:"manual" so POST bodies
are not silently dropped when X returns a 3xx (fetch spec converts
POST→GET on 301/302, losing OAuth-signed body → signature mismatch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (2) hide show
  1. cloudflare-proxy-setup.py +5 -1
  2. health-server.js +64 -0
cloudflare-proxy-setup.py CHANGED
@@ -147,11 +147,15 @@ async function handleRequest(request) {{
147
  headers.delete("x-target-host");
148
  headers.delete("x-proxy-key");
149
 
 
 
 
 
150
  const proxiedRequest = new Request(targetUrl, {{
151
  method: request.method,
152
  headers,
153
  body: request.body,
154
- redirect: "follow",
155
  }});
156
 
157
  try {{
 
147
  headers.delete("x-target-host");
148
  headers.delete("x-proxy-key");
149
 
150
+ // Use redirect:"manual" so POST bodies are NOT silently dropped when X
151
+ // returns a 3xx (fetch spec converts POST→GET on 301/302 redirect, losing
152
+ // the OAuth-signed body and causing a signature mismatch β†’ 500 from X).
153
+ // The Node.js client receives the raw 3xx and handles it with body intact.
154
  const proxiedRequest = new Request(targetUrl, {{
155
  method: request.method,
156
  headers,
157
  body: request.body,
158
+ redirect: "manual",
159
  }});
160
 
161
  try {{
health-server.js CHANGED
@@ -1775,6 +1775,70 @@ const server = http.createServer((req, res) => {
1775
  return;
1776
  }
1777
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1778
  // ── /app/debug-logs β€” Temporary endpoint for debugging ───────────────────
1779
  if (pathname === "/app/debug-logs") {
1780
  try {
 
1775
  return;
1776
  }
1777
 
1778
+ // ── /app/test-twitter β€” raw connectivity probe to api.twitter.com ────────
1779
+ if (pathname === "/app/test-twitter") {
1780
+ const https = require("https");
1781
+ const lines = [];
1782
+ lines.push(`NODE_OPTIONS : ${process.env.NODE_OPTIONS || "(not set)"}`);
1783
+ lines.push(`CLOUDFLARE_PROXY_URL : ${process.env.CLOUDFLARE_PROXY_URL || "(not set)"}`);
1784
+ lines.push("");
1785
+ // Two probes:
1786
+ // A) GET api.twitter.com/ β€” basic reachability + proxy detection
1787
+ // B) POST oauth/access_token (no auth) β€” 4xx+JSON if IP allowed, 500 HTML if IP blocked
1788
+ const makeProbe = (opts, postBody) => new Promise((resolve) => {
1789
+ const t0 = Date.now();
1790
+ const req = https.request({ ...opts, timeout: 10000 }, (r) => {
1791
+ let body = "";
1792
+ r.setEncoding("utf8");
1793
+ r.on("data", (c) => { body += c; if (body.length > 800) r.destroy(); });
1794
+ r.on("close", () => resolve({ status: r.statusCode, headers: r.headers, body: body.trim(), ms: Date.now() - t0 }));
1795
+ r.on("end", () => resolve({ status: r.statusCode, headers: r.headers, body: body.trim(), ms: Date.now() - t0 }));
1796
+ });
1797
+ req.on("error", (e) => resolve({ error: e.message, ms: Date.now() - t0 }));
1798
+ req.on("timeout", () => { req.destroy(); resolve({ error: "TIMEOUT 10s", ms: Date.now() - t0 }); });
1799
+ if (postBody) req.write(postBody);
1800
+ req.end();
1801
+ });
1802
+
1803
+ (async () => {
1804
+ // Probe A
1805
+ lines.push("── Probe A: GET https://api.twitter.com/ ──");
1806
+ const a = await makeProbe({ hostname: "api.twitter.com", path: "/", method: "GET",
1807
+ headers: { "User-Agent": "curl/8.0", "Accept": "*/*" } });
1808
+ if (a.error) {
1809
+ lines.push(`ERROR: ${a.error}`);
1810
+ } else {
1811
+ lines.push(`Status : ${a.status} (${a.ms}ms)`);
1812
+ for (const [k, v] of Object.entries(a.headers)) lines.push(` ${k}: ${v}`);
1813
+ lines.push(`cf-ray in headers? : ${a.headers["cf-ray"] ? "YES β†’ request went through Cloudflare" : "no"}`);
1814
+ lines.push(`Body (first 300) : ${a.body.slice(0, 300)}`);
1815
+ }
1816
+
1817
+ // Probe B
1818
+ lines.push("");
1819
+ lines.push("── Probe B: POST https://api.twitter.com/oauth/access_token (no auth) ──");
1820
+ lines.push("Expected: 400/401 JSON if IP allowed by X, 500 HTML if IP blocked");
1821
+ const postBody = "oauth_verifier=test";
1822
+ const b = await makeProbe({ hostname: "api.twitter.com", path: "/oauth/access_token",
1823
+ method: "POST",
1824
+ headers: { "User-Agent": "curl/8.0", "Content-Type": "application/x-www-form-urlencoded",
1825
+ "Content-Length": Buffer.byteLength(postBody) } }, postBody);
1826
+ if (b.error) {
1827
+ lines.push(`ERROR: ${b.error}`);
1828
+ } else {
1829
+ lines.push(`Status : ${b.status} (${b.ms}ms)`);
1830
+ for (const [k, v] of Object.entries(b.headers)) lines.push(` ${k}: ${v}`);
1831
+ const isHtml = b.body.includes("<html") || b.body.includes("<!DOCTYPE");
1832
+ lines.push(`Body type : ${isHtml ? "HTML ← IP probably blocked" : "text/JSON ← IP allowed"}`);
1833
+ lines.push(`Body (first 500) : ${b.body.slice(0, 500)}`);
1834
+ }
1835
+
1836
+ res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
1837
+ res.end(lines.join("\n"));
1838
+ })();
1839
+ return;
1840
+ }
1841
+
1842
  // ── /app/debug-logs β€” Temporary endpoint for debugging ───────────────────
1843
  if (pathname === "/app/debug-logs") {
1844
  try {