161 lines
5.1 KiB
JavaScript
161 lines
5.1 KiB
JavaScript
/**
|
|
* Audience router for nomadro.live
|
|
* - Chinese users → TikTok promo Next.js :3015
|
|
* - English users / bots / default → WordPress :8089
|
|
*
|
|
* /api/* is handled by Caddy (FastAPI :8015) and never reaches this process.
|
|
*/
|
|
import http from "node:http";
|
|
import httpProxy from "http-proxy";
|
|
|
|
const PORT = Number(process.env.PORT || 4404);
|
|
const NEXT = process.env.NEXT_UPSTREAM || "http://127.0.0.1:3015";
|
|
const WP = process.env.WP_UPSTREAM || "http://127.0.0.1:8089";
|
|
const COOKIE = "nomadrolive_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;
|
|
|
|
const ALWAYS_WP_RE = /^\/(ads\.txt|robots\.txt)(\/|$|\?)/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() {
|
|
return `<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta name="robots" content="noindex" />
|
|
<title>Nomadro Live</title>
|
|
<style>html,body{height:100%;margin:0;background:#0b0f19}body{display:grid;place-items:center;font:14px system-ui,sans-serif;color:#9aa4b2}</style>
|
|
</head>
|
|
<body>
|
|
<p>Loading…</p>
|
|
<script>
|
|
(function () {
|
|
function byLang() {
|
|
var list = [];
|
|
try {
|
|
if (navigator.languages && navigator.languages.length) {
|
|
list = list.concat(Array.prototype.slice.call(navigator.languages));
|
|
}
|
|
} catch (e) {}
|
|
if (navigator.language) list.push(navigator.language);
|
|
for (var i = 0; i < list.length; i++) {
|
|
var l = String(list[i] || "").toLowerCase();
|
|
if (l === "zh" || l.indexOf("zh-") === 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
function byTz() {
|
|
try {
|
|
var tz = Intl.DateTimeFormat().resolvedOptions().timeZone || "";
|
|
if (/Shanghai|Chongqing|Urumqi|Hong_Kong|Macau|Taipei/i.test(tz)) return true;
|
|
} catch (e) {}
|
|
try {
|
|
if (new Date().getTimezoneOffset() === -480) return true;
|
|
} catch (e2) {}
|
|
return false;
|
|
}
|
|
var china = byLang() || byTz();
|
|
var value = china ? "zh" : "en";
|
|
document.cookie = "${COOKIE}=" + value + "; Path=/; Max-Age=${COOKIE_MAX_AGE}; SameSite=Lax";
|
|
location.replace(location.pathname + location.search + location.hash);
|
|
})();
|
|
</script>
|
|
<noscript>
|
|
<meta http-equiv="refresh" content="0; url=/?noscript=1" />
|
|
</noscript>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
function resolveTarget(req) {
|
|
const url = req.url || "/";
|
|
const path = url.split("?")[0];
|
|
|
|
if (ALWAYS_WP_RE.test(path) || path === "/ads.txt" || path === "/robots.txt") return WP;
|
|
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-live-router 127.0.0.1:${PORT} zh→${NEXT} en→${WP}`);
|
|
});
|