From 03e384bbc4d52785579a30c171fdac0849cc9e94 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Fri, 3 Apr 2026 13:03:20 -0700 Subject: [PATCH] fix: null guard on pathHops prevents crash on ADVERT detail (#538) (#540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #538 — `null is not an object (evaluating 'pathHops.length')` crash on ADVERT packet detail. ## Root Cause `getParsedPath` caches its result as `p._parsedPath`. If another code path (e.g., object spread, API response) sets `_parsedPath = null`, the cache check (`!== undefined`) passes and returns `null` — causing `.length` to crash. Same pattern exists for `getParsedDecoded`. ## Changes ### `public/packet-helpers.js` - `getParsedPath`: cached return now uses `|| []` to guard against null cache - `getParsedDecoded`: cached return now uses `|| {}` to guard against null cache ### `public/packets.js` - `renderDetail()` (line ~1440): defensive `|| []` / `|| {}` on getParsedPath/getParsedDecoded calls - `buildFlatRowHtml()` (line ~1103): same defensive guards ### `test-frontend-helpers.js` - Added test: cached `_parsedPath = null` returns `[]` - Added test: cached `_parsedDecoded = null` returns `{}` ## Testing All 428 frontend helper tests pass. All 62 packet filter tests pass. Co-authored-by: you --- public/packet-helpers.js | 4 ++-- public/packets.js | 8 ++++---- test-frontend-helpers.js | 10 ++++++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/public/packet-helpers.js b/public/packet-helpers.js index 5a97a573..3c20b382 100644 --- a/public/packet-helpers.js +++ b/public/packet-helpers.js @@ -10,7 +10,7 @@ */ window.getParsedPath = function getParsedPath(p) { - if (p._parsedPath !== undefined) return p._parsedPath; + if (p._parsedPath !== undefined) return p._parsedPath || []; var raw = p.path_json; if (typeof raw !== 'string') { p._parsedPath = Array.isArray(raw) ? raw : []; @@ -32,7 +32,7 @@ window.clearParsedCache = function clearParsedCache(p) { }; window.getParsedDecoded = function getParsedDecoded(p) { - if (p._parsedDecoded !== undefined) return p._parsedDecoded; + if (p._parsedDecoded !== undefined) return p._parsedDecoded || {}; var raw = p.decoded_json; if (typeof raw !== 'string') { p._parsedDecoded = (raw && typeof raw === 'object') ? raw : {}; diff --git a/public/packets.js b/public/packets.js index e42bf384..12432e2c 100644 --- a/public/packets.js +++ b/public/packets.js @@ -1100,8 +1100,8 @@ // Build HTML for a single flat (ungrouped) packet row function buildFlatRowHtml(p) { - const decoded = getParsedDecoded(p); - const pathHops = getParsedPath(p); + const decoded = getParsedDecoded(p) || {}; + const pathHops = getParsedPath(p) || []; const region = p.observer_id ? (observerMap.get(p.observer_id)?.iata || '') : ''; const typeName = payloadTypeName(p.payload_type); const typeClass = payloadTypeColor(p.payload_type); @@ -1441,8 +1441,8 @@ const pkt = data.packet; const breakdown = data.breakdown || {}; const ranges = breakdown.ranges || []; - const decoded = getParsedDecoded(pkt); - const pathHops = getParsedPath(pkt); + const decoded = getParsedDecoded(pkt) || {}; + const pathHops = getParsedPath(pkt) || []; // Resolve sender GPS — from packet directly, or from known node in DB let senderLat = decoded.lat != null ? decoded.lat : (decoded.latitude || null); diff --git a/test-frontend-helpers.js b/test-frontend-helpers.js index b0ba546e..b36de08d 100644 --- a/test-frontend-helpers.js +++ b/test-frontend-helpers.js @@ -4183,7 +4183,17 @@ console.log('\n=== app.js: routeTypeName/payloadTypeName edge cases ==='); assertJsonEqual(getParsedPath(p), []); }); + test('getParsedPath: cached null _parsedPath returns empty array (#538)', () => { + const p = { path_json: '["a"]', _parsedPath: null }; + assertJsonEqual(getParsedPath(p), []); + }); + // --- getParsedDecoded --- + test('getParsedDecoded: cached null _parsedDecoded returns empty object (#538)', () => { + const p = { decoded_json: '{"x":1}', _parsedDecoded: null }; + assertJsonEqual(getParsedDecoded(p), {}); + }); + test('getParsedDecoded: valid JSON object', () => { const p = { decoded_json: '{"type":"GRP_TXT","text":"hello"}' }; const result = getParsedDecoded(p);