From 79068e55af64f6053c80e9f611ad28696709c463 Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Sat, 4 Apr 2026 12:08:18 +0200 Subject: [PATCH] [fix](trx-frontend-http): buffer decode messages until map-data plugins are ready Eagerly load map-data plugins (AIS, APRS, VDES, HF-APRS) on startup and buffer any decode history or live SSE messages that arrive before plugin handlers register. Each plugin drains its pending buffer on init. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Stan Grams --- .../trx-frontend-http/assets/web/app.js | 66 +++++++++++++++---- .../trx-frontend-http/assets/web/index.html | 9 ++- .../assets/web/plugins/ais.js | 1 + .../assets/web/plugins/aprs.js | 1 + .../assets/web/plugins/hf-aprs.js | 1 + .../assets/web/plugins/vdes.js | 1 + 6 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js index 16955b1..194a688 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/app.js @@ -563,6 +563,41 @@ const decodeHistoryTextDecoder = typeof TextDecoder === "function" ? new TextDec let decodeHistoryReplayActive = false; let decodeMapSyncPending = false; +// --- Pending decode data buffers --- +// Map-data plugins (ais.js, aprs.js, vdes.js, hf-aprs.js) are loaded eagerly +// but dynamically-inserted scripts have no guaranteed execution order. If +// decode history or live SSE messages arrive before the plugin handlers are +// registered, buffer them here and let each plugin drain on init. +const _pendingDecodeHistory = {}; +const _pendingDecodeLive = {}; + +window._trxDrainPendingDecode = function(kind) { + const historyKey = { + ais: "restoreAisHistory", + vdes: "restoreVdesHistory", + aprs: "restoreAprsHistory", + hf_aprs: "restoreHfAprsHistory", + }[kind]; + if (historyKey && _pendingDecodeHistory[kind] && window[historyKey]) { + const msgs = _pendingDecodeHistory[kind]; + delete _pendingDecodeHistory[kind]; + window[historyKey](msgs); + } + const liveKey = { + ais: "onServerAis", + vdes: "onServerVdes", + aprs: "onServerAprs", + hf_aprs: "onServerHfAprs", + }[kind]; + if (liveKey && _pendingDecodeLive[kind] && window[liveKey]) { + const msgs = _pendingDecodeLive[kind]; + delete _pendingDecodeLive[kind]; + for (const msg of msgs) { + try { window[liveKey](msg); } catch (_) {} + } + } +}; + function markDecodeMapSyncPending() { decodeMapSyncPending = true; } @@ -5920,10 +5955,10 @@ function updateDecodeStatus(text) { } } function dispatchDecodeMessage(msg, skipStats) { - if (msg.type === "ais" && window.onServerAis) window.onServerAis(msg); - if (msg.type === "vdes" && window.onServerVdes) window.onServerVdes(msg); - if (msg.type === "aprs" && window.onServerAprs) window.onServerAprs(msg); - if (msg.type === "hf_aprs" && window.onServerHfAprs) window.onServerHfAprs(msg); + if (msg.type === "ais") { if (window.onServerAis) window.onServerAis(msg); else (_pendingDecodeLive.ais = _pendingDecodeLive.ais || []).push(msg); } + if (msg.type === "vdes") { if (window.onServerVdes) window.onServerVdes(msg); else (_pendingDecodeLive.vdes = _pendingDecodeLive.vdes || []).push(msg); } + if (msg.type === "aprs") { if (window.onServerAprs) window.onServerAprs(msg); else (_pendingDecodeLive.aprs = _pendingDecodeLive.aprs || []).push(msg); } + if (msg.type === "hf_aprs") { if (window.onServerHfAprs) window.onServerHfAprs(msg); else (_pendingDecodeLive.hf_aprs = _pendingDecodeLive.hf_aprs || []).push(msg); } if (msg.type === "cw" && window.onServerCw) window.onServerCw(msg); if (msg.type === "ft8" && window.onServerFt8) window.onServerFt8(msg); if (msg.type === "ft4" && window.onServerFt4) window.onServerFt4(msg); @@ -6036,20 +6071,24 @@ function restoreDecodeHistoryGroup(kind, messages) { } window.trx.map?.scheduleStatsRender(); } - if (kind === "ais" && window.restoreAisHistory) { - window.restoreAisHistory(messages); + if (kind === "ais") { + if (window.restoreAisHistory) { window.restoreAisHistory(messages); } + else { _pendingDecodeHistory.ais = (_pendingDecodeHistory.ais || []).concat(messages); } return; } - if (kind === "vdes" && window.restoreVdesHistory) { - window.restoreVdesHistory(messages); + if (kind === "vdes") { + if (window.restoreVdesHistory) { window.restoreVdesHistory(messages); } + else { _pendingDecodeHistory.vdes = (_pendingDecodeHistory.vdes || []).concat(messages); } return; } - if (kind === "aprs" && window.restoreAprsHistory) { - window.restoreAprsHistory(messages); + if (kind === "aprs") { + if (window.restoreAprsHistory) { window.restoreAprsHistory(messages); } + else { _pendingDecodeHistory.aprs = (_pendingDecodeHistory.aprs || []).concat(messages); } return; } - if (kind === "hf_aprs" && window.restoreHfAprsHistory) { - window.restoreHfAprsHistory(messages); + if (kind === "hf_aprs") { + if (window.restoreHfAprsHistory) { window.restoreHfAprsHistory(messages); } + else { _pendingDecodeHistory.hf_aprs = (_pendingDecodeHistory.hf_aprs || []).concat(messages); } return; } if (kind === "cw" && window.restoreCwHistory) { @@ -6083,6 +6122,9 @@ function connectDecode() { terminateDecodeHistoryWorker(); decodeHistoryReplayActive = false; decodeMapSyncPending = false; + // Clear any pending buffers from a previous connection cycle. + for (const k in _pendingDecodeHistory) delete _pendingDecodeHistory[k]; + for (const k in _pendingDecodeLive) delete _pendingDecodeLive[k]; if (window.resetAisHistoryView) window.resetAisHistoryView(); if (window.resetVdesHistoryView) window.resetVdesHistoryView(); if (window.resetAprsHistoryView) window.resetAprsHistoryView(); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html index 4a9749a..6a3fd94 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/index.html @@ -1620,6 +1620,7 @@ (function() { var pluginScripts = { 'digital-modes': ['/ft8.js', '/ft4.js', '/ft2.js', '/wspr.js', '/cw.js', '/background-decode.js', '/sat.js', '/wefax.js'], + 'map-data': ['/map-core.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js'], 'map': ['/map-core.js', '/leaflet-ais-tracksymbol.js', '/ais.js', '/vdes.js', '/aprs.js', '/hf-aprs.js', '/sat.js', '/sat-scheduler.js'], 'statistics': ['/map-core.js'], 'bookmarks': ['/bookmarks.js'], @@ -1639,8 +1640,12 @@ document.body.appendChild(s); }); } - // Load core plugins immediately (needed on main tab) - ['digital-modes', 'bookmarks', 'settings'].forEach(loadPlugins); + // Load core plugins immediately (needed on main tab). + // 'map-data' loads map-core.js + data handlers (ais/aprs/vdes/hf-aprs) eagerly + // so decode history replay can populate map data structures before the map + // tab is opened. The 'loaded' Set prevents double-loading when the map tab + // is later activated. + ['digital-modes', 'map-data', 'bookmarks', 'settings'].forEach(loadPlugins); // Load others on tab switch document.addEventListener('click', function(e) { var tab = e.target.closest('[data-tab]'); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js index 994a494..07a0720 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/ais.js @@ -400,3 +400,4 @@ window.onServerAis = function(msg) { }; updateAisSummary(); +if (window._trxDrainPendingDecode) window._trxDrainPendingDecode("ais"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js index e5bb738..33eb91c 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/aprs.js @@ -491,3 +491,4 @@ window.onServerAprs = function(pkt) { }; renderAprsHistory(); +if (window._trxDrainPendingDecode) window._trxDrainPendingDecode("aprs"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/hf-aprs.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/hf-aprs.js index f2ba1f7..20f82f5 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/hf-aprs.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/hf-aprs.js @@ -437,3 +437,4 @@ window.onServerHfAprs = function(pkt) { }; renderHfAprsHistory(); +if (window._trxDrainPendingDecode) window._trxDrainPendingDecode("hf_aprs"); diff --git a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js index 2b20acd..4486970 100644 --- a/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js +++ b/src/trx-client/trx-frontend/trx-frontend-http/assets/web/plugins/vdes.js @@ -345,3 +345,4 @@ window.pruneVdesHistoryView = function() { }; updateVdesSummary(); +if (window._trxDrainPendingDecode) window._trxDrainPendingDecode("vdes");