refactor: route all decoders through plugin runtime

This commit is contained in:
sjg
2026-08-01 13:06:38 +02:00
parent 508cf2ba87
commit 9fef0ebc7b
19 changed files with 255 additions and 319 deletions
@@ -488,12 +488,9 @@ function currentDecodeHistoryRetentionMs() {
}
window.getDecodeHistoryRetentionMs = currentDecodeHistoryRetentionMs;
window.applyDecodeHistoryRetention = function() {
window.trxPluginRuntime.prune("aprs");
window.trxPluginRuntime.prune("hf_aprs");
window.trxPluginRuntime.prune("ais");
window.trxPluginRuntime.prune("vdes");
if (typeof window.pruneFt8HistoryView === "function") window.pruneFt8HistoryView();
if (typeof window.pruneWsprHistoryView === "function") window.pruneWsprHistoryView();
for (const decoder of ["aprs", "hf_aprs", "ais", "vdes", "ft8", "ft4", "ft2", "wspr", "wefax"]) {
window.trxPluginRuntime.prune(decoder);
}
};
function syncTopBarAccess() {
const loggedOut = authEnabled && !authRole;
@@ -5857,18 +5854,7 @@ function updateDecodeStatus(text) {
}
}
function dispatchDecodeMessage(msg, skipStats) {
if (msg.type === "ais" || msg.type === "vdes" || msg.type === "aprs" || msg.type === "hf_aprs") {
window.trxPluginRuntime.dispatch(msg.type, 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);
if (msg.type === "ft2" && window.onServerFt2) window.onServerFt2(msg);
if (msg.type === "wspr" && window.onServerWspr) window.onServerWspr(msg);
if (msg.type === "lrpt_image" && window.onServerLrptImage) window.onServerLrptImage(msg);
if (msg.type === "lrpt_progress" && window.onServerLrptProgress) window.onServerLrptProgress(msg);
if (msg.type === "wefax" && window.onServerWefax) window.onServerWefax(msg);
if (msg.type === "wefax_progress" && window.onServerWefaxProgress) window.onServerWefaxProgress(msg);
if (msg.type) window.trxPluginRuntime.dispatch(msg.type, msg);
if (!skipStats && msg.type && msg.type !== "lrpt_image" && msg.type !== "lrpt_progress" && msg.type !== "wefax" && msg.type !== "wefax_progress") {
window.trx.modules.map?.statsRecordDecode(msg.type, msg.rig_id || msg.remote || null);
window.trx.modules.map?.scheduleStatsRender();
@@ -5884,27 +5870,9 @@ function dispatchDecodeBatch(batch) {
window.trx.modules.map?.scheduleStatsRender();
const type = String(batch[0]?.type || "");
const uniformType = batch.every((msg) => String(msg?.type || "") === type);
if (uniformType) {
if (type === "ais" || type === "vdes" || type === "aprs" || type === "hf_aprs") {
window.trxPluginRuntime.dispatchBatch(type, batch);
return;
}
if (type === "ft8" && window.onServerFt8Batch) {
window.onServerFt8Batch(batch);
return;
}
if (type === "ft4" && window.onServerFt4Batch) {
window.onServerFt4Batch(batch);
return;
}
if (type === "ft2" && window.onServerFt2Batch) {
window.onServerFt2Batch(batch);
return;
}
if (type === "wspr" && window.onServerWsprBatch) {
window.onServerWsprBatch(batch);
return;
}
if (uniformType && type) {
window.trxPluginRuntime.dispatchBatch(type, batch);
return;
}
for (const msg of batch) {
dispatchDecodeMessage(msg, true);
@@ -5954,34 +5922,7 @@ function restoreDecodeHistoryGroup(kind, messages) {
}
window.trx.modules.map?.scheduleStatsRender();
}
if (kind === "ais" || kind === "vdes" || kind === "aprs" || kind === "hf_aprs") {
window.trxPluginRuntime.restore(kind, messages);
return;
}
if (kind === "cw" && window.restoreCwHistory) {
window.restoreCwHistory(messages);
return;
}
if (kind === "ft8" && window.restoreFt8History) {
window.restoreFt8History(messages);
return;
}
if (kind === "ft4" && window.restoreFt4History) {
window.restoreFt4History(messages);
return;
}
if (kind === "ft2" && window.restoreFt2History) {
window.restoreFt2History(messages);
return;
}
if (kind === "wspr" && window.restoreWsprHistory) {
window.restoreWsprHistory(messages);
return;
}
if (kind === "wefax" && window.restoreWefaxHistory) {
window.restoreWefaxHistory(messages);
return;
}
window.trxPluginRuntime.restore(kind, messages);
}
function connectDecode() {
if (decodeSource) {
@@ -5991,14 +5932,7 @@ function connectDecode() {
decodeHistoryReplayActive = false;
decodeMapSyncPending = false;
window.trxPluginRuntime.clearQueued();
window.trxPluginRuntime.reset("ais");
window.trxPluginRuntime.reset("vdes");
window.trxPluginRuntime.reset("aprs");
window.trxPluginRuntime.reset("hf_aprs");
if (window.resetCwHistoryView) window.resetCwHistoryView();
if (window.resetFt8HistoryView) window.resetFt8HistoryView();
if (window.resetFt4HistoryView) window.resetFt4HistoryView();
if (window.resetWsprHistoryView) window.resetWsprHistoryView();
window.trxPluginRuntime.resetAll();
let historySettled = false;
let historyWorkerDone = false;
let historyFallbackStarted = false;
@@ -82,7 +82,7 @@
}
cwWindow.updateCwBar = updateCwBar;
cwWindow.clearCwBar = function() {
cwWindow.resetCwHistoryView?.();
resetCwHistoryView();
};
cwWindow.closeCwBar = function() {
cwBarDismissedAtMs = Date.now();
@@ -302,26 +302,26 @@
void setCwTone(tone);
});
}
cwWindow.resetCwHistoryView = function() {
function resetCwHistoryView() {
if (cwOutputEl) cwOutputEl.innerHTML = "";
cwLastAppendTime = 0;
cwBarHistory = [];
cwBarCurrentLine = null;
updateCwBar();
drawCwTonePicker();
};
}
document.getElementById("settings-clear-cw-history")?.addEventListener("click", () => {
void (async () => {
if (!await cwWindow.trxUi.confirm({ title: "Clear CW history?", message: "All stored CW decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
try {
await cwWindow.postPath?.("/clear_cw_decode");
cwWindow.resetCwHistoryView?.();
resetCwHistoryView();
} catch (error) {
console.error("CW history clear failed", error);
}
})();
});
cwWindow.onServerCw = function(evt) {
function onServerCw(evt) {
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
if (evt.text && cwOutputEl) {
const now = Date.now();
@@ -375,14 +375,20 @@
cwTonePickerRaf = null;
drawCwTonePicker();
});
};
cwWindow.restoreCwHistory = function(events) {
}
function restoreCwHistory(events) {
if (!Array.isArray(events) || events.length === 0) return;
if (cwStatusEl) cwStatusEl.textContent = "Receiving";
for (const evt of events) {
cwWindow.onServerCw?.(evt);
onServerCw(evt);
}
};
}
cwWindow.trxPluginRuntime.registerDecoder({
id: "cw",
onMessage: onServerCw,
restore: restoreCwHistory,
reset: resetCwHistoryView
});
cwWindow.refreshCwTonePicker = function refreshCwTonePicker() {
ensureCwToneCanvasResolution();
drawCwTonePicker();
@@ -167,19 +167,19 @@
}
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
};
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
bridge[`onServer${capitalized}Batch`] = (messages) => {
if (Array.isArray(messages)) receiveBatch(messages);
};
bridge[`restore${capitalized}History`] = receiveBatch;
bridge[`prune${capitalized}HistoryView`] = () => {
prune();
render();
};
bridge[`reset${capitalized}HistoryView`] = reset;
bridge[`onServer${capitalized}`] = (message) => {
receiveBatch([message]);
};
bridge.trxPluginRuntime.registerDecoder({
id,
onMessage: (message) => {
receiveBatch([message]);
},
onBatch: receiveBatch,
restore: receiveBatch,
prune: () => {
prune();
render();
},
reset
});
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
const updatePeriod = () => {
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
@@ -167,19 +167,19 @@
}
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
};
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
bridge[`onServer${capitalized}Batch`] = (messages) => {
if (Array.isArray(messages)) receiveBatch(messages);
};
bridge[`restore${capitalized}History`] = receiveBatch;
bridge[`prune${capitalized}HistoryView`] = () => {
prune();
render();
};
bridge[`reset${capitalized}HistoryView`] = reset;
bridge[`onServer${capitalized}`] = (message) => {
receiveBatch([message]);
};
bridge.trxPluginRuntime.registerDecoder({
id,
onMessage: (message) => {
receiveBatch([message]);
},
onBatch: receiveBatch,
restore: receiveBatch,
prune: () => {
prune();
render();
},
reset
});
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
const updatePeriod = () => {
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
@@ -91,7 +91,7 @@
};
bridge.updateFt8Bar = update;
bridge.clearFt8Bar = () => {
({ ft8: bridge.resetFt8HistoryView, ft4: bridge.resetFt4HistoryView, ft2: bridge.resetFt2HistoryView })[active]?.();
bridge.trxPluginRuntime.reset(active);
};
bridge.closeFt8Bar = () => {
dismissed[active] = Date.now();
@@ -209,19 +209,19 @@
}
return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
};
const capitalized = `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
bridge[`onServer${capitalized}Batch`] = (messages) => {
if (Array.isArray(messages)) receiveBatch(messages);
};
bridge[`restore${capitalized}History`] = receiveBatch;
bridge[`prune${capitalized}HistoryView`] = () => {
prune();
render();
};
bridge[`reset${capitalized}HistoryView`] = reset;
bridge[`onServer${capitalized}`] = (message) => {
receiveBatch([message]);
};
bridge.trxPluginRuntime.registerDecoder({
id,
onMessage: (message) => {
receiveBatch([message]);
},
onBatch: receiveBatch,
restore: receiveBatch,
prune: () => {
prune();
render();
},
reset
});
bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
const updatePeriod = () => {
if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
@@ -207,29 +207,39 @@
});
}
}
satWindow.onServerLrptProgress = function(msg) {
function onServerLrptProgress(msg) {
if (satDom.status && (msg.mcu_count ?? 0) > 0) {
satDom.status.textContent = `Receiving — ${String(msg.mcu_count ?? 0)} MCU rows decoded`;
}
};
satWindow.onServerLrptImage = function(msg) {
}
function onServerLrptImage(msg) {
if (satDom.status) satDom.status.textContent = "Image received (Meteor LRPT)";
addSatImage(msg, "lrpt");
if (msg.geo_bounds && msg.path && satWindow.addSatMapOverlay) {
satWindow.addSatMapOverlay(msg);
}
};
satWindow.resetSatHistoryView = function() {
}
function resetSatHistoryView() {
satImageHistory = [];
if (satDom.historyList) satDom.historyList.innerHTML = "";
renderSatLatestCard();
renderSatHistoryTable();
satWindow.clearSatMapOverlays?.();
};
satWindow.pruneSatHistoryView = function() {
}
function pruneSatHistoryView() {
renderSatHistoryTable();
renderSatLatestCard();
};
}
satWindow.trxPluginRuntime.registerDecoder({
id: "lrpt_image",
onMessage: onServerLrptImage,
reset: resetSatHistoryView,
prune: pruneSatHistoryView
});
satWindow.trxPluginRuntime.registerDecoder({
id: "lrpt_progress",
onMessage: onServerLrptProgress
});
var lrptDecodeToggleBtn = document.getElementById("lrpt-decode-toggle-btn");
lrptDecodeToggleBtn?.addEventListener("click", () => {
void (async () => {
@@ -257,7 +267,7 @@
if (!await satWindow.trxUi.confirm({ title: "Clear satellite history?", message: "All stored satellite decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
try {
await satWindow.postPath?.("/clear_lrpt_decode");
satWindow.resetSatHistoryView?.();
resetSatHistoryView();
} catch (e) {
console.error("Weather satellite history clear failed", e);
}
@@ -205,7 +205,7 @@
scheduleWefaxUi("wefax-history", renderWefaxHistoryTable);
}
}
wefaxWindow.onServerWefaxProgress = function(msg) {
function onServerWefaxProgress(msg) {
if (msg.state && !msg.line_data) {
if (wefaxDom.status) {
wefaxDom.status.textContent = msg.state;
@@ -229,16 +229,16 @@
wefaxDom.status.textContent = `Receiving — line ${String(msg.line_count ?? 0)}`;
wefaxDom.status.style.color = "var(--text-accent)";
}
};
wefaxWindow.onServerWefax = function(msg) {
}
function onServerWefax(msg) {
addWefaxImage(msg);
if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none";
if (wefaxDom.status) {
wefaxDom.status.textContent = `Complete — ${String(msg.line_count ?? 0)} lines`;
wefaxDom.status.style.color = "";
}
};
wefaxWindow.restoreWefaxHistory = function(messages) {
}
function restoreWefaxHistory(messages) {
if (!messages.length) return;
for (const message of messages) {
const tsMs = Number.isFinite(message.ts_ms) ? Number(message.ts_ms) : Date.now();
@@ -255,13 +255,13 @@
if (wefaxActiveView === "history") {
scheduleWefaxUi("wefax-history", renderWefaxHistoryTable);
}
};
wefaxWindow.pruneWefaxHistoryView = function() {
}
function pruneWefaxHistoryView() {
pruneWefaxHistory();
renderWefaxHistoryTable();
renderWefaxLatestCard();
};
wefaxWindow.resetWefaxHistoryView = function() {
}
function resetWefaxHistoryView() {
wefaxImageHistory = [];
if (wefaxDom.historyList) wefaxDom.historyList.innerHTML = "";
if (wefaxDom.liveContainer) wefaxDom.liveContainer.style.display = "none";
@@ -273,7 +273,7 @@
wefaxDom.status.textContent = "Idle";
wefaxDom.status.style.color = "";
}
};
}
if (wefaxDom.filterInput) {
const filterInput = wefaxDom.filterInput;
wefaxDom.filterInput.addEventListener("input", function() {
@@ -313,7 +313,7 @@
void (async () => {
try {
await wefaxWindow.postPath?.("/clear_wefax_decode");
wefaxWindow.resetWefaxHistoryView?.();
resetWefaxHistoryView();
} catch (e) {
console.error("WEFAX clear failed", e);
}
@@ -321,4 +321,15 @@
});
}
renderWefaxLatestCard();
wefaxWindow.trxPluginRuntime.registerDecoder({
id: "wefax",
onMessage: onServerWefax,
restore: restoreWefaxHistory,
prune: pruneWefaxHistoryView,
reset: resetWefaxHistoryView
});
wefaxWindow.trxPluginRuntime.registerDecoder({
id: "wefax_progress",
onMessage: onServerWefaxProgress
});
})();
@@ -97,7 +97,7 @@
}
};
}
wsprWindow.onServerWsprBatch = function(messages) {
function onServerWsprBatch(messages) {
if (!Array.isArray(messages) || messages.length === 0) return;
if (wsprStatus) wsprStatus.textContent = "Receiving";
const normalized = [];
@@ -116,15 +116,11 @@
wsprMessageHistory = normalized.concat(wsprMessageHistory);
pruneWsprMessageHistory();
scheduleWsprHistoryRender();
};
wsprWindow.restoreWsprHistory = function(messages) {
const callback = wsprWindow.onServerWsprBatch;
if (typeof callback === "function") callback(messages);
};
wsprWindow.pruneWsprHistoryView = function() {
}
function pruneWsprHistoryView() {
pruneWsprMessageHistory();
renderWsprHistory();
};
}
function escapeWsprHtml(input) {
return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
@@ -205,12 +201,12 @@
const message = row.dataset.message || "";
row.style.display = message.includes(wsprFilterText) ? "" : "none";
}
wsprWindow.resetWsprHistoryView = function() {
function resetWsprHistoryView() {
if (wsprMessagesEl) wsprMessagesEl.innerHTML = "";
wsprMessageHistory = [];
renderWsprHistory();
if (wsprWindow.clearMapMarkersByType) wsprWindow.clearMapMarkersByType("wspr");
};
}
if (wsprFilterInput) {
wsprFilterInput.addEventListener("input", () => {
wsprFilterText = wsprFilterInput.value.trim().toUpperCase();
@@ -246,13 +242,13 @@
if (!await wsprWindow.trxUi.confirm({ title: "Clear WSPR history?", message: "All stored WSPR decodes will be permanently removed.", confirmLabel: "Clear history" })) return;
try {
await wsprWindow.postPath?.("/clear_wspr_decode");
wsprWindow.resetWsprHistoryView?.();
resetWsprHistoryView();
} catch (error) {
console.error("WSPR history clear failed", error);
}
})();
});
wsprWindow.onServerWspr = function(msg) {
function onServerWspr(msg) {
if (wsprStatus) wsprStatus.textContent = "Receiving";
const next = normalizeServerWsprMessage(msg);
if (next.grids.length > 0 && wsprWindow.mapAddLocator) {
@@ -262,5 +258,13 @@
});
}
addWsprMessage(next.history);
};
}
wsprWindow.trxPluginRuntime.registerDecoder({
id: "wspr",
onMessage: onServerWspr,
onBatch: onServerWsprBatch,
restore: onServerWsprBatch,
prune: pruneWsprHistoryView,
reset: resetWsprHistoryView
});
})();