/** * Heartbeat — sends periodic status updates to TIP routing server. * POST /api/proxy/heartbeat every 30s. */ import * as https from "https"; import * as http from "http"; export interface HeartbeatPayload { token: string; ip?: string; port: number; bytesProxied: number; requestsProxied: number; latencyMs?: number; version: string; } export interface HeartbeatResult { ok: boolean; latencyMs: number; error?: string; } function postJson(url: string, body: unknown): Promise<{ status: number; latencyMs: number }> { return new Promise((resolve, reject) => { const start = Date.now(); const json = JSON.stringify(body); const parsed = new URL(url); const mod = parsed.protocol === "https:" ? https : http; const req = mod.request( { hostname: parsed.hostname, port: parsed.port || (parsed.protocol === "https:" ? 443 : 80), path: parsed.pathname + parsed.search, method: "POST", headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(json), "User-Agent": "TIP-Agent/1.0.0", }, timeout: 10_000, }, (res) => { res.resume(); resolve({ status: res.statusCode ?? 0, latencyMs: Date.now() - start }); } ); req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); }); req.on("error", reject); req.write(json); req.end(); }); } export async function sendHeartbeat( serverUrl: string, payload: HeartbeatPayload ): Promise { const url = `${serverUrl}/api/proxy/heartbeat`; try { const { status, latencyMs } = await postJson(url, payload); return { ok: status >= 200 && status < 300, latencyMs }; } catch (err) { return { ok: false, latencyMs: 0, error: String(err) }; } } export function startHeartbeatLoop( serverUrl: string, getPayload: () => HeartbeatPayload, onResult?: (r: HeartbeatResult) => void, intervalMs = 30_000 ): NodeJS.Timeout { const tick = async () => { const result = await sendHeartbeat(serverUrl, getPayload()); onResult?.(result); }; tick(); return setInterval(tick, intervalMs); }