mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-10 19:56:56 +00:00
## Summary Fixes #451 — packet detail pane crash on direct routed packets where `pathHops` is `null`. ## Root Cause `JSON.parse(pkt.path_json)` can return literal `null` when the DB stores `"null"` for direct routed packets. The existing code only had a catch block for parse errors, but `null` is valid JSON — so the parse succeeds and `pathHops` ends up `null` instead of `[]`. ## Changes - **`public/packets.js`**: Added `|| []` after `JSON.parse(...)` in both `buildFlatRowHtml` (table rows) and the detail pane (`selectPacket`), ensuring `pathHops` is always an array. - **`test-frontend-helpers.js`**: Added 2 regression tests verifying the null guards exist in both code paths. - **`public/index.html`**: Cache buster bump. ## Testing - All 229 frontend helper tests pass - All 62 packet filter tests pass - All 29 aging tests pass Co-authored-by: you <you@example.com>
This commit is contained in:
+28
-28
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="CoreScope">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/corescope/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1775060497">
|
||||
<link rel="stylesheet" href="home.css?v=1775060497">
|
||||
<link rel="stylesheet" href="live.css?v=1775060497">
|
||||
<link rel="stylesheet" href="style.css?v=1775062162">
|
||||
<link rel="stylesheet" href="home.css?v=1775062162">
|
||||
<link rel="stylesheet" href="live.css?v=1775062162">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -85,30 +85,30 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1775060497"></script>
|
||||
<script src="customize.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775060497"></script>
|
||||
<script src="hop-resolver.js?v=1775060497"></script>
|
||||
<script src="hop-display.js?v=1775060497"></script>
|
||||
<script src="app.js?v=1775060497"></script>
|
||||
<script src="home.js?v=1775060497"></script>
|
||||
<script src="packet-filter.js?v=1775060497"></script>
|
||||
<script src="packets.js?v=1775060497"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775060497"></script>
|
||||
<script src="map.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775060497" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1775062162"></script>
|
||||
<script src="customize.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1775062162"></script>
|
||||
<script src="hop-resolver.js?v=1775062162"></script>
|
||||
<script src="hop-display.js?v=1775062162"></script>
|
||||
<script src="app.js?v=1775062162"></script>
|
||||
<script src="home.js?v=1775062162"></script>
|
||||
<script src="packet-filter.js?v=1775062162"></script>
|
||||
<script src="packets.js?v=1775062162"></script>
|
||||
<script src="geo-filter-overlay.js?v=1775062162"></script>
|
||||
<script src="map.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1775062162" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+2
-2
@@ -1080,7 +1080,7 @@
|
||||
function buildFlatRowHtml(p) {
|
||||
let decoded, pathHops = [];
|
||||
try { decoded = JSON.parse(p.decoded_json || '{}'); } catch {}
|
||||
try { pathHops = JSON.parse(p.path_json || '[]'); } catch {}
|
||||
try { pathHops = JSON.parse(p.path_json || '[]') || []; } catch {}
|
||||
const region = p.observer_id ? (observers.find(o => o.id === p.observer_id)?.iata || '') : '';
|
||||
const typeName = payloadTypeName(p.payload_type);
|
||||
const typeClass = payloadTypeColor(p.payload_type);
|
||||
@@ -1420,7 +1420,7 @@
|
||||
let decoded;
|
||||
try { decoded = JSON.parse(pkt.decoded_json); } catch { decoded = {}; }
|
||||
let pathHops;
|
||||
try { pathHops = JSON.parse(pkt.path_json || '[]'); } catch { pathHops = []; }
|
||||
try { pathHops = JSON.parse(pkt.path_json || '[]') || []; } catch { pathHops = []; }
|
||||
|
||||
// Resolve sender GPS — from packet directly, or from known node in DB
|
||||
let senderLat = decoded.lat != null ? decoded.lat : (decoded.latitude || null);
|
||||
|
||||
@@ -2668,6 +2668,22 @@ console.log('\n=== packets.js: savedTimeWindowMin defaults ===');
|
||||
'buildFlatRowHtml should have null-safe decoded_json fallback');
|
||||
});
|
||||
|
||||
test('pathHops null guard in buildFlatRowHtml (issue #451)', () => {
|
||||
const flatBuilderMatch = packetsSource.match(/function buildFlatRowHtml[\s\S]*?(?=\n function )/);
|
||||
assert.ok(flatBuilderMatch, 'buildFlatRowHtml should exist');
|
||||
// The JSON.parse result must be coalesced with || [] to handle literal null from path_json
|
||||
assert.ok(flatBuilderMatch[0].includes("|| '[]') || []"),
|
||||
'buildFlatRowHtml should coalesce parsed path_json with || [] to guard against null');
|
||||
});
|
||||
|
||||
test('pathHops null guard in detail pane (issue #451)', () => {
|
||||
// The detail pane (selectPacket / showPacketDetail) also parses path_json
|
||||
const detailMatch = packetsSource.match(/let pathHops;\s*try \{[^}]+\} catch/);
|
||||
assert.ok(detailMatch, 'detail pane pathHops parsing should exist');
|
||||
assert.ok(detailMatch[0].includes("|| '[]') || []"),
|
||||
'detail pane should coalesce parsed path_json with || [] to guard against null');
|
||||
});
|
||||
|
||||
test('destroy cleans up virtual scroll state', () => {
|
||||
assert.ok(packetsSource.includes('detachVScrollListener'),
|
||||
'destroy should detach virtual scroll listener');
|
||||
|
||||
Reference in New Issue
Block a user