// Single public entrypoint for HF Spaces: local dashboard + reverse proxy to OpenClaw. const http = require("http"); const fs = require("fs"); const net = require("net"); const { randomUUID } = require("node:crypto"); const PORT = 7861; const GATEWAY_PORT = 7860; const GATEWAY_HOST = "127.0.0.1"; const startTime = Date.now(); const LLM_MODEL = process.env.LLM_MODEL || "Not Set"; const GATEWAY_TOKEN = process.env.GATEWAY_TOKEN || ""; const TELEGRAM_ENABLED = !!process.env.TELEGRAM_BOT_TOKEN; const GATEWAY_STATUS_CACHE_MS = 5000; let gatewayStatusCache = { expiresAt: 0, value: { whatsapp: { configured: true, connected: false }, telegram: { configured: TELEGRAM_ENABLED, connected: false }, }, }; function parseRequestUrl(url) { try { return new URL(url, "http://localhost"); } catch { return new URL("http://localhost/"); } } function isDashboardRoute(pathname) { return pathname === "/dashboard" || pathname === "/dashboard/"; } function isLocalRoute(pathname) { return pathname === "/health" || pathname === "/status" || isDashboardRoute(pathname); } function appendForwarded(existingValue, nextValue) { const cleanNext = nextValue || ""; if (!existingValue) return cleanNext; if (Array.isArray(existingValue)) return `${existingValue.join(", ")}, ${cleanNext}`; return `${existingValue}, ${cleanNext}`; } function buildProxyHeaders(headers, remoteAddress) { return { ...headers, host: headers.host || `${GATEWAY_HOST}:${GATEWAY_PORT}`, "x-forwarded-for": appendForwarded( headers["x-forwarded-for"], remoteAddress, ), "x-forwarded-host": headers["x-forwarded-host"] || headers.host || "", "x-forwarded-proto": headers["x-forwarded-proto"] || "https", }; } function readSyncStatus() { try { if (fs.existsSync("/tmp/sync-status.json")) { return JSON.parse(fs.readFileSync("/tmp/sync-status.json", "utf8")); } } catch {} return { status: "unknown", message: "No sync data yet" }; } function normalizeChannelStatus(channel, configured) { return { configured: configured || !!channel, connected: !!(channel && channel.connected), }; } function extractErrorMessage(msg) { if (!msg || typeof msg !== "object") return "Unknown error"; if (typeof msg.error === "string") return msg.error; if (msg.error && typeof msg.error.message === "string") return msg.error.message; if (typeof msg.message === "string") return msg.message; return "Unknown error"; } function createGatewayConnection() { return new Promise((resolve, reject) => { const { WebSocket } = require("/home/node/.openclaw/openclaw-app/node_modules/ws"); const ws = new WebSocket(`ws://${GATEWAY_HOST}:${GATEWAY_PORT}`); let resolved = false; ws.on("message", (data) => { const msg = JSON.parse(data.toString()); if (msg.type === "event" && msg.event === "connect.challenge") { ws.send(JSON.stringify({ type: "req", id: randomUUID(), method: "connect", params: { minProtocol: 3, maxProtocol: 3, client: { id: "health-server", version: "1.0.0", platform: "linux", mode: "backend", }, caps: [], auth: { token: GATEWAY_TOKEN }, role: "operator", scopes: ["operator.read"], }, })); return; } if (!resolved && msg.type === "res" && msg.ok === false) { resolved = true; ws.close(); reject(new Error(extractErrorMessage(msg))); return; } if (!resolved && msg.type === "res" && msg.ok) { resolved = true; resolve(ws); } }); ws.on("error", (error) => { if (!resolved) reject(error); }); setTimeout(() => { if (!resolved) { ws.close(); reject(new Error("Timeout")); } }, 10000); }); } function callGatewayRpc(ws, method, params) { return new Promise((resolve, reject) => { const id = randomUUID(); const handler = (data) => { const msg = JSON.parse(data.toString()); if (msg.id === id) { ws.removeListener("message", handler); resolve(msg); } }; ws.on("message", handler); ws.send(JSON.stringify({ type: "req", id, method, params })); setTimeout(() => { ws.removeListener("message", handler); reject(new Error("RPC Timeout")); }, 15000); }); } async function getGatewayChannelStatus() { if (Date.now() < gatewayStatusCache.expiresAt) { return gatewayStatusCache.value; } let ws; try { ws = await createGatewayConnection(); const statusRes = await callGatewayRpc(ws, "channels.status", {}); const channels = (statusRes.payload || statusRes.result)?.channels || {}; const value = { whatsapp: normalizeChannelStatus(channels.whatsapp, true), telegram: normalizeChannelStatus(channels.telegram, TELEGRAM_ENABLED), }; gatewayStatusCache = { expiresAt: Date.now() + GATEWAY_STATUS_CACHE_MS, value, }; return value; } catch { return gatewayStatusCache.value; } finally { if (ws) ws.close(); } } function renderDashboard() { return `
Space Dashboard