/** * Audience router for nomadro.com * - Chinese users → Next.js :4003 * - English users / bots / default → WordPress :8088 * * Detection mirrors is-china-user primary signals (language + timezone), * evaluated in a same-origin sniff page (no external CDN). */ import http from "node:http"; import httpProxy from "http-proxy"; const PORT = Number(process.env.PORT || 4403); const NEXT = process.env.NEXT_UPSTREAM || "http://127.0.0.1:4003"; const WP = process.env.WP_UPSTREAM || "http://127.0.0.1:8088"; const COOKIE = "nomadro_route"; const COOKIE_MAX_AGE = 60 * 60 * 24 * 30; const proxy = httpProxy.createProxyServer({ xfwd: true, changeOrigin: false, }); proxy.on("error", (err, _req, res) => { console.error("proxy error", err.message); if (res && !res.headersSent && typeof res.writeHead === "function") { res.writeHead(502, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Bad gateway"); } }); const BOT_RE = /Googlebot|AdsBot-Google|Mediapartners-Google|Google-InspectionTool|bingbot|BingPreview|DuckDuckBot|Baiduspider|YandexBot|Slurp|facebookexternalhit|Twitterbot|LinkedInBot|Applebot|SemrushBot|AhrefsBot|DotBot|PetalBot|Bytespider/i; const WP_PATH_RE = /^\/(wp-admin|wp-login\.php|wp-cron\.php|wp-comments-post\.php|xmlrpc\.php|wp-json|wp-content|wp-includes)(\/|$)/i; function getCookie(req, name) { const raw = req.headers.cookie || ""; for (const p of raw.split(/;\s*/)) { const i = p.indexOf("="); if (i === -1) continue; if (p.slice(0, i) === name) return decodeURIComponent(p.slice(i + 1)); } return null; } function isBot(req) { return BOT_RE.test(req.headers["user-agent"] || ""); } function wantsHtml(req) { if (req.method !== "GET" && req.method !== "HEAD") return false; const accept = req.headers.accept || ""; const url = req.url || "/"; const path = url.split("?")[0]; if (path.startsWith("/_next/") || path.startsWith("/wp-")) return false; if (/\.(js|css|map|png|jpe?g|gif|webp|svg|ico|woff2?|ttf|txt|xml)$/i.test(path)) { return false; } if (!accept || accept === "*/*") return path === "/" || !path.includes("."); return accept.includes("text/html"); } function sniffHtml() { // Inline detection aligned with is-china-user language + timezone signals. return `
Loading…
`; } function resolveTarget(req) { const url = req.url || "/"; const path = url.split("?")[0]; if (WP_PATH_RE.test(path)) return WP; if (isBot(req)) return WP; if (url.includes("noscript=1")) return WP; const route = getCookie(req, COOKIE); if (route === "zh") return NEXT; if (route === "en") return WP; if (wantsHtml(req)) return "SNIFF"; const lang = String(req.headers["accept-language"] || "").toLowerCase(); if (lang.includes("zh")) return NEXT; return WP; } const server = http.createServer((req, res) => { const target = resolveTarget(req); if (target === "SNIFF") { res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store, no-cache, must-revalidate", }); res.end(sniffHtml()); return; } if (!req.headers["x-forwarded-proto"]) { req.headers["x-forwarded-proto"] = "https"; } proxy.web(req, res, { target }); }); server.on("upgrade", (req, socket, head) => { let target = resolveTarget(req); if (target === "SNIFF") target = WP; proxy.ws(req, socket, head, { target }); }); server.listen(PORT, "127.0.0.1", () => { console.log(`nomadro-router 127.0.0.1:${PORT} zh→${NEXT} en→${WP}`); });