fix: harden ElectrumX status — cached backend, stable frontend
Backend: cache status in RwLock, refresh every 15s via background task. Eliminates per-request TCP race to ElectrumX that caused volatile errors. Fix error classification so "Failed to read" is transient, not hard error. Frontend: keep last-known-good data across failed polls, persist Tor onion once discovered, adaptive polling (5s active / 30s synced). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
07dff3e4ca
commit
d0b9f168f4
@@ -268,6 +268,11 @@
|
||||
<script>
|
||||
var currentTab = 'local';
|
||||
var torOnion = null;
|
||||
var lastGoodData = null;
|
||||
var consecutiveFailures = 0;
|
||||
var pollTimer = null;
|
||||
var POLL_ACTIVE = 5000; // 5s while syncing/indexing/starting
|
||||
var POLL_SYNCED = 30000; // 30s when synced
|
||||
|
||||
function renderQR(containerId, text) {
|
||||
var container = document.getElementById(containerId);
|
||||
@@ -353,84 +358,113 @@
|
||||
}
|
||||
}
|
||||
|
||||
function schedulePoll(intervalMs) {
|
||||
if (pollTimer) clearTimeout(pollTimer);
|
||||
pollTimer = setTimeout(function() { updateStatus(); }, intervalMs);
|
||||
}
|
||||
|
||||
function applyData(data) {
|
||||
// Persist tor onion once discovered — never flicker back to "not configured"
|
||||
if (data.tor_onion) {
|
||||
applyTorOnion(data.tor_onion);
|
||||
}
|
||||
|
||||
var indexedH = data.indexed_height || 0;
|
||||
var networkH = data.network_height || 0;
|
||||
var pct = data.progress_pct || 0;
|
||||
|
||||
document.getElementById('indexedHeight').textContent = indexedH > 0 ? indexedH.toLocaleString() : (data.status === 'indexing' ? 'Building...' : '-');
|
||||
document.getElementById('networkHeight').textContent = networkH > 0 ? networkH.toLocaleString() : '-';
|
||||
document.getElementById('indexSize').textContent = data.index_size || '-';
|
||||
document.getElementById('progressPct').textContent = pct > 0 ? pct.toFixed(1) + '%' : '-';
|
||||
document.getElementById('currentBlock').textContent = indexedH > 0 ? 'Block ' + indexedH.toLocaleString() : (data.index_size ? 'Index: ' + data.index_size : 'Block 0');
|
||||
document.getElementById('syncPercentage').textContent = pct > 0 ? pct.toFixed(1) + '%' : '0%';
|
||||
document.getElementById('syncProgressBar').style.width = Math.max(pct, 0.5) + '%';
|
||||
|
||||
var statusTextEl = document.getElementById('syncStatusText');
|
||||
var statusDot = document.getElementById('statusDot');
|
||||
var syncIcon = document.getElementById('syncIcon');
|
||||
|
||||
if (data.status === 'starting' || data.status === 'waiting') {
|
||||
statusTextEl.textContent = data.error || 'Starting up...';
|
||||
statusTextEl.style.color = '#fbbf24';
|
||||
statusDot.className = 'status-dot bg-yellow animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Starting';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'indexing') {
|
||||
statusTextEl.textContent = data.error || 'Building index...';
|
||||
statusTextEl.style.color = '#fbbf24';
|
||||
statusDot.className = 'status-dot bg-amber animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Indexing';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'error') {
|
||||
statusTextEl.textContent = data.error || 'Unknown error';
|
||||
statusTextEl.style.color = '#f87171';
|
||||
statusDot.className = 'status-dot bg-red';
|
||||
document.getElementById('statusText').textContent = 'Error';
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'synced') {
|
||||
statusTextEl.textContent = 'Fully synchronized with the network';
|
||||
statusTextEl.style.color = '#4ade80';
|
||||
statusDot.className = 'status-dot bg-green';
|
||||
document.getElementById('statusText').textContent = 'Synced';
|
||||
syncIcon.classList.remove('animate-spin-slow');
|
||||
syncIcon.style.color = '#4ade80';
|
||||
document.getElementById('connSubtitle').textContent = 'Use the following details to connect your wallet or application to ElectrumX.';
|
||||
} else {
|
||||
var remaining = networkH - indexedH;
|
||||
statusTextEl.textContent = 'Syncing... ' + remaining.toLocaleString() + ' blocks remaining';
|
||||
statusTextEl.style.color = '#fb923c';
|
||||
statusDot.className = 'status-dot bg-yellow';
|
||||
document.getElementById('statusText').textContent = 'Syncing';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStatus() {
|
||||
try {
|
||||
var resp = await fetch('/electrs-status', { cache: 'no-store' });
|
||||
if (!resp.ok) {
|
||||
throw new Error('Backend unavailable (HTTP ' + resp.status + ')');
|
||||
throw new Error('backend_unavailable');
|
||||
}
|
||||
var text = await resp.text();
|
||||
if (text.trim().charAt(0) !== '{') {
|
||||
throw new Error('Waiting for Archipelago backend...');
|
||||
throw new Error('backend_starting');
|
||||
}
|
||||
var data = JSON.parse(text);
|
||||
|
||||
// Extract Tor onion from status response
|
||||
if (data.tor_onion && !torOnion) {
|
||||
applyTorOnion(data.tor_onion);
|
||||
}
|
||||
// Success — reset failure counter, save as last known good
|
||||
consecutiveFailures = 0;
|
||||
lastGoodData = data;
|
||||
applyData(data);
|
||||
|
||||
var indexedH = data.indexed_height || 0;
|
||||
var networkH = data.network_height || 0;
|
||||
var pct = data.progress_pct || 0;
|
||||
|
||||
document.getElementById('indexedHeight').textContent = indexedH > 0 ? indexedH.toLocaleString() : (data.status === 'indexing' ? 'Building...' : '-');
|
||||
document.getElementById('networkHeight').textContent = networkH > 0 ? networkH.toLocaleString() : '-';
|
||||
document.getElementById('indexSize').textContent = data.index_size || '-';
|
||||
document.getElementById('progressPct').textContent = pct > 0 ? pct.toFixed(1) + '%' : '-';
|
||||
document.getElementById('currentBlock').textContent = indexedH > 0 ? 'Block ' + indexedH.toLocaleString() : (data.index_size ? 'Index: ' + data.index_size : 'Block 0');
|
||||
document.getElementById('syncPercentage').textContent = pct > 0 ? pct.toFixed(1) + '%' : '0%';
|
||||
document.getElementById('syncProgressBar').style.width = Math.max(pct, 0.5) + '%';
|
||||
|
||||
var statusTextEl = document.getElementById('syncStatusText');
|
||||
var statusDot = document.getElementById('statusDot');
|
||||
var syncIcon = document.getElementById('syncIcon');
|
||||
|
||||
if (data.status === 'indexing') {
|
||||
statusTextEl.textContent = data.error || 'Building index...';
|
||||
statusTextEl.style.color = '#fbbf24';
|
||||
statusDot.className = 'status-dot bg-amber animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Indexing';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'error') {
|
||||
statusTextEl.textContent = data.error || 'Unknown error';
|
||||
statusTextEl.style.color = '#f87171';
|
||||
statusDot.className = 'status-dot bg-red';
|
||||
document.getElementById('statusText').textContent = 'Error';
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
} else if (data.status === 'synced') {
|
||||
statusTextEl.textContent = 'Fully synchronized with the network';
|
||||
statusTextEl.style.color = '#4ade80';
|
||||
statusDot.className = 'status-dot bg-green';
|
||||
document.getElementById('statusText').textContent = 'Synced';
|
||||
syncIcon.classList.remove('animate-spin-slow');
|
||||
syncIcon.style.color = '#4ade80';
|
||||
document.getElementById('connSubtitle').textContent = 'Use the following details to connect your wallet or application to ElectrumX.';
|
||||
} else {
|
||||
var remaining = networkH - indexedH;
|
||||
statusTextEl.textContent = 'Syncing... ' + remaining.toLocaleString() + ' blocks remaining';
|
||||
statusTextEl.style.color = '#fb923c';
|
||||
statusDot.className = 'status-dot bg-yellow';
|
||||
document.getElementById('statusText').textContent = 'Syncing';
|
||||
syncIcon.classList.add('animate-spin-slow');
|
||||
document.getElementById('connSubtitle').textContent = 'Connections will be available once ElectrumX has completed syncing.';
|
||||
}
|
||||
// Adaptive polling — slower when synced
|
||||
schedulePoll(data.status === 'synced' ? POLL_SYNCED : POLL_ACTIVE);
|
||||
} catch (e) {
|
||||
var msg = e.message || 'Unknown error';
|
||||
if (msg.indexOf('HTTP 5') !== -1 || msg.indexOf('Failed to fetch') !== -1 || msg.indexOf('NetworkError') !== -1) {
|
||||
msg = 'Waiting for Archipelago backend...';
|
||||
consecutiveFailures++;
|
||||
|
||||
if (lastGoodData && consecutiveFailures <= 3) {
|
||||
// Keep showing last known good data — don't flicker the UI
|
||||
// Just poll again sooner to recover
|
||||
schedulePoll(POLL_ACTIVE);
|
||||
return;
|
||||
}
|
||||
document.getElementById('syncStatusText').textContent = msg;
|
||||
|
||||
// No prior good data, or too many failures — show connecting state
|
||||
document.getElementById('syncStatusText').textContent = 'Connecting to Archipelago...';
|
||||
document.getElementById('syncStatusText').style.color = '#fbbf24';
|
||||
document.getElementById('statusDot').className = 'status-dot bg-yellow animate-pulse';
|
||||
document.getElementById('statusText').textContent = 'Connecting';
|
||||
|
||||
schedulePoll(POLL_ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
updateConnectionInfo();
|
||||
setInterval(updateStatus, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user