fix: prevent Bitcoin Core switch from hanging the Hub UI

This commit is contained in:
2026-08-11 18:47:40 -05:00
parent cb2b49174e
commit 8f89a4350a
9 changed files with 289 additions and 11 deletions
@@ -5,6 +5,10 @@
const POLL_INTERVAL_SERVICES = 5000;
const POLL_INTERVAL_UPDATES = 1800000;
const UPDATE_POLL_INTERVAL = 2000;
// Max consecutive failed rebuild/update status polls before the page gives up
// waiting and reloads to re-sync (2s interval → ~2 minutes of failures).
// A brief Hub restart during activation only causes a handful of failures.
const STATUS_POLL_MAX_FAILURES = 60;
const REBOOT_CHECK_INTERVAL = 5000;
const REBOOT_FETCH_TIMEOUT = 12000;
const REBOOT_REQUEST_TIMEOUT = 4000;
@@ -8,6 +8,7 @@ function openRebuildModal() {
_rebuildLogOffset = 0;
_rebuildServerDown = false;
_rebuildFinished = false;
_rebuildPollFailures = 0;
if ($rebuildLog) { $rebuildLog.textContent = ""; $rebuildLog.style.display = "none"; }
var action = _rebuildIsEnabling ? "Enabling" : "Disabling";
var label = _rebuildFeatureName || "feature";
@@ -45,6 +46,7 @@ async function pollRebuildStatus() {
if (_rebuildFinished) return;
try {
var data = await apiFetch("/api/rebuild/status?offset=" + _rebuildLogOffset);
_rebuildPollFailures = 0;
if (_rebuildServerDown) { _rebuildServerDown = false; }
if (data.log) appendRebuildLog(data.log);
_rebuildLogOffset = data.offset;
@@ -57,6 +59,18 @@ async function pollRebuildStatus() {
onRebuildDone(data.result === "success");
}
} catch (err) {
_rebuildPollFailures += 1;
// The Hub restarts itself during activation, which briefly drops this poll.
// If polling stays broken long past a normal restart, the page's session
// almost certainly no longer matches the server (e.g. the Hub restarted
// and sessions were not recovered). Reload to re-authenticate and show
// the real feature state instead of spinning forever.
if (_rebuildPollFailures >= STATUS_POLL_MAX_FAILURES) {
_rebuildFinished = true;
stopRebuildPoll();
window.location.reload();
return;
}
if (!_rebuildServerDown) { _rebuildServerDown = true; if ($rebuildStatus) $rebuildStatus.textContent = "Applying changes…"; }
}
}
@@ -9,6 +9,7 @@ let _updatePollTimer = null;
let _updateLogOffset = 0;
let _serverWasDown = false;
let _updateFinished = false;
let _updatePollFailures = 0; // consecutive failed update-status polls
let _supportTimerInt = null;
let _supportEnabledAt = null;
let _supportStatus = null; // last fetched /api/support/status payload
@@ -25,6 +26,7 @@ let _rebuildLogOffset = 0;
let _rebuildPollTimer = null;
let _rebuildFinished = false;
let _rebuildServerDown = false;
let _rebuildPollFailures = 0; // consecutive failed rebuild-status polls
let _pendingToggle = null; // {feature, extra} waiting for domain/confirm
let _rebuildFeatureName = "";
let _rebuildIsEnabling = true;
@@ -33,6 +33,7 @@ function _doOpenUpdateModal() {
_updateLogOffset = 0;
_serverWasDown = false;
_updateFinished = false;
_updatePollFailures = 0;
if ($modalLog) $modalLog.textContent = "";
if ($modalStatus) $modalStatus.textContent = "Starting update…";
if ($modalSpinner) $modalSpinner.classList.add("spinning");
@@ -94,6 +95,7 @@ async function pollUpdateStatus() {
if (_updateFinished) return;
try {
var data = await apiFetch("/api/updates/status?offset=" + _updateLogOffset);
_updatePollFailures = 0;
if (_serverWasDown) {
_serverWasDown = false;
if (!data.running) {
@@ -143,6 +145,16 @@ async function pollUpdateStatus() {
onUpdateDone(false);
}
} catch (err) {
_updatePollFailures += 1;
// Same guard as the rebuild modal: if polling stays broken long past a
// normal Hub restart, reload to re-authenticate and show the real state
// instead of spinning forever.
if (_updatePollFailures >= STATUS_POLL_MAX_FAILURES) {
_updateFinished = true;
stopUpdatePoll();
window.location.reload();
return;
}
if (!_serverWasDown) { _serverWasDown = true; appendLog("\n[Server restarting — waiting for it to come back…]\n"); if ($modalStatus) $modalStatus.textContent = "Server restarting…"; }
}
}