fix: remove dead code, SSE error handling, bounded dedup maps

- Remove unused mustParseDuration from main.go
- Replace http.Error calls after SSE headers with plain returns;
  skip bad packets instead of killing the stream on marshal error
- Change processedPackets/seenPackets from boolean to timestamp values
  and prune entries older than 24h on each packet to prevent unbounded
  memory growth in long-running sessions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel Pupius
2026-03-15 16:27:57 +00:00
parent 9e5fd5bcae
commit 1d61a89505
4 changed files with 29 additions and 24 deletions
+12 -4
View File
@@ -72,8 +72,8 @@ interface AggregatorState {
channels: Record<string, ChannelData>;
messages: Record<string, TextMessage[]>;
selectedNodeId?: number;
// Track already processed packet IDs to prevent duplicates
processedPackets: Record<string, boolean>;
// Track already processed packet IDs to prevent duplicates (value = unix timestamp)
processedPackets: Record<string, number>;
}
const initialState: AggregatorState = {
@@ -108,11 +108,19 @@ const processPacket = (state: AggregatorState, packet: Packet) => {
const nodeIdHex = `!${nodeId.toString(16).toLowerCase()}`;
const packetKey = `${nodeIdHex}_${data.id}`;
// Prune processed packets older than 24 hours
const cutoff = timestamp - 86400;
for (const key of Object.keys(state.processedPackets)) {
if (state.processedPackets[key] < cutoff) {
delete state.processedPackets[key];
}
}
// Check if we've already processed this packet
const isNewPacket = !state.processedPackets[packetKey];
// Always mark this packet as processed
state.processedPackets[packetKey] = true;
// Always mark this packet as processed with its timestamp
state.processedPackets[packetKey] = timestamp;
// Update gateway data
// Handle both cases:
+16 -5
View File
@@ -10,7 +10,7 @@ interface PacketState {
error: string | null;
streamPaused: boolean;
bufferedPackets: Packet[]; // Holds packets received while paused
seenPackets: Record<string, boolean>; // Tracks already seen packet IDs to prevent duplicates
seenPackets: Record<string, number>; // Tracks already seen packet IDs (value = unix timestamp) to prevent duplicates
}
const initialState: PacketState = {
@@ -19,7 +19,7 @@ const initialState: PacketState = {
error: null,
streamPaused: false,
bufferedPackets: [],
seenPackets: {}, // Empty object to track seen packets
seenPackets: {}, // Empty object to track seen packets (key → unix timestamp)
};
const packetSlice = createSlice({
@@ -40,7 +40,8 @@ const packetSlice = createSlice({
const packetKey = `${nodeId}_${packet.data.id}`;
if (!state.seenPackets[packetKey]) {
state.seenPackets[packetKey] = true;
const ts = packet.data.rxTime || Math.floor(Date.now() / 1000);
state.seenPackets[packetKey] = ts;
uniquePackets.push(packet);
}
} else {
@@ -65,6 +66,16 @@ const packetSlice = createSlice({
return;
}
const now = packet.data.rxTime || Math.floor(Date.now() / 1000);
// Prune seen packets older than 24 hours
const cutoff = now - 86400;
for (const key of Object.keys(state.seenPackets)) {
if (state.seenPackets[key] < cutoff) {
delete state.seenPackets[key];
}
}
// Create a Meshtastic node ID format
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
const packetKey = `${nodeId}_${packet.data.id}`;
@@ -75,8 +86,8 @@ const packetSlice = createSlice({
return;
}
// Mark this packet as seen
state.seenPackets[packetKey] = true;
// Mark this packet as seen with its timestamp
state.seenPackets[packetKey] = now;
if (state.streamPaused) {
// When paused, add to buffer instead of main list