From c22d929493b188ffb9bfe55728f493e788ee385e Mon Sep 17 00:00:00 2001 From: Jonny Spicer Date: Wed, 11 Mar 2026 16:41:11 +0000 Subject: [PATCH] Add TCP push client for forwarding detections to tracker-host Forward detection frames to a remote tracker-host over persistent TCP connections with newline-delimited JSON framing, token authentication, ring buffer for offline periods, heartbeat keepalives, and automatic reconnection. Co-Authored-By: Claude Opus 4.6 --- api/server.js | 41 ++++++++++++++++++++++++++++++++++++++++- config/config.yml | 3 +++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/api/server.js b/api/server.js index 204bead4..48ad0989 100644 --- a/api/server.js +++ b/api/server.js @@ -24,6 +24,9 @@ var stash_timing = require('./stash/timing.js'); // TCP client for forwarding detections to external tracker let trackerSocket = null; let trackerConnected = false; +const sendBuffer = []; +let lastSendTime = 0; +let heartbeatInterval = null; function connectToTracker() { if (!config.network.tracker_forward?.enabled) return; @@ -32,10 +35,15 @@ function connectToTracker() { const port = config.network.tracker_forward.port; trackerSocket = new net.Socket(); + trackerSocket.setNoDelay(true); trackerSocket.connect(port, host, () => { console.log(`Connected to tracker at ${host}:${port}`); + while (sendBuffer.length > 0) { + trackerSocket.write(sendBuffer.shift()); + } trackerConnected = true; + lastSendTime = Date.now(); }); trackerSocket.on('error', (err) => { @@ -51,13 +59,40 @@ function connectToTracker() { } function forwardToTracker(data) { + const framed = data + '\n'; if (trackerConnected && trackerSocket) { - trackerSocket.write(data); + trackerSocket.write(framed); + lastSendTime = Date.now(); + } else { + const bufferMax = config.network.tracker_forward.buffer_max || 1000; + if (sendBuffer.length >= bufferMax) { + sendBuffer.shift(); + } + sendBuffer.push(framed); } } +function startHeartbeat() { + if (!config.network.tracker_forward?.enabled) return; + + const intervalSec = config.network.tracker_forward.heartbeat_interval_sec || 10; + const nodeId = config.network.node_id; + const token = config.network.tracker_forward.token || ''; + + heartbeatInterval = setInterval(() => { + if (!trackerConnected || !trackerSocket) return; + const elapsed = Date.now() - lastSendTime; + if (elapsed >= intervalSec * 1000) { + const msg = JSON.stringify({ node_id: nodeId, token: token, type: 'heartbeat' }) + '\n'; + trackerSocket.write(msg); + lastSendTime = Date.now(); + } + }, intervalSec * 1000); +} + // Initialize tracker connection connectToTracker(); +startHeartbeat(); // constants const PORT = config.network.ports.api; @@ -205,6 +240,10 @@ const server_detection = net.createServer((socket)=>{ return bestMatch; }); } + if (config.network.tracker_forward?.enabled) { + det.node_id = config.network.node_id; + det.token = config.network.tracker_forward.token || ''; + } detection = JSON.stringify(det); // Forward to external tracker if enabled forwardToTracker(detection); diff --git a/config/config.yml b/config/config.yml index a4e53262..c93ea9f4 100644 --- a/config/config.yml +++ b/config/config.yml @@ -69,6 +69,9 @@ network: enabled: false host: 'blah2_tracker' port: 3012 + token: '' + buffer_max: 1000 + heartbeat_interval_sec: 10 truth: adsb: