Theseus: the startup update check no longer silently misses new releases
The startup check shared the main thread with snapshot parsing, tab restore and add-on activation, under a 5 s abort timer started before the request. Measured on an empty profile: 3.2 s for the manifest fetch, 1.6 s of it the event loop being busy; a real profile went past 5 s, the abort won, the failure was swallowed and nothing retried before the 6-hourly recheck. The update only appeared after a manual "Check for updates". - the startup check runs 8 s after the toolbar is ready and retries with backoff (30 s, 2 min, 10 min, 30 min) when it fails - 20 s timeout via AbortSignal.timeout - a failed installer download is retried on the next check instead of staying failed until the next release - failures are logged
This commit is contained in:
parent
9b2d6322be
commit
f2ff48b977
1 changed files with 35 additions and 13 deletions
48
main.js
48
main.js
|
|
@ -618,21 +618,27 @@ async function checkForUpdate() {
|
||||||
// newer release on the mirror shows the same one-click "Install & restart"
|
// newer release on the mirror shows the same one-click "Install & restart"
|
||||||
// chip as a real install, and that installer targets the REAL install dir.
|
// chip as a real install, and that installer targets the REAL install dir.
|
||||||
// 2026-09-15 a test run reinstalled the user's Theseus that way.
|
// 2026-09-15 a test run reinstalled the user's Theseus that way.
|
||||||
if (process.env.THESEUS_NO_UPDATE_CHECK) return;
|
// Returns true when the manifest was read (whatever it said), false when
|
||||||
|
// the check itself failed — the startup scheduler retries on false.
|
||||||
|
if (process.env.THESEUS_NO_UPDATE_CHECK) return true;
|
||||||
|
const t0 = Date.now();
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
// 20 s, not 5: the startup check shares the main thread with snapshot
|
||||||
const to = setTimeout(() => controller.abort(), 5000);
|
// parsing, tab restore and add-on activation. Measured 2026-10-03: 3.2 s
|
||||||
const r = await fetch(UPDATE_MANIFEST_URL, { signal: controller.signal, cache: "no-store" });
|
// for this fetch on an empty profile, 1.6 s of it the event loop being
|
||||||
clearTimeout(to);
|
// busy — a real profile went past 5 s, the abort won, and (the failure
|
||||||
if (!r.ok) return;
|
// being silent with no retry) the update only appeared after a manual
|
||||||
|
// "Check for updates".
|
||||||
|
const r = await fetch(UPDATE_MANIFEST_URL, { signal: AbortSignal.timeout(20000), cache: "no-store" });
|
||||||
|
if (!r.ok) { console.warn(`[update] manifest HTTP ${r.status}`); return false; }
|
||||||
const manifest = await r.json();
|
const manifest = await r.json();
|
||||||
const rel = (manifest.releases || []).find((x) => x.id === "theseus-navigator");
|
const rel = (manifest.releases || []).find((x) => x.id === "theseus-navigator");
|
||||||
if (!rel || !rel.version) return;
|
if (!rel || !rel.version) return true;
|
||||||
if (!versionIsNewer(rel.version, app.getVersion())) {
|
if (!versionIsNewer(rel.version, app.getVersion())) {
|
||||||
// Same version or older — nothing to offer. Clear any stale state so the
|
// Same version or older — nothing to offer. Clear any stale state so the
|
||||||
// chip disappears after the user has updated + relaunched.
|
// chip disappears after the user has updated + relaunched.
|
||||||
if (updateAvailable) { updateAvailable = null; updateDownloadState = "idle"; updateDownloadPath = null; emitUpdateAvailable(); }
|
if (updateAvailable) { updateAvailable = null; updateDownloadState = "idle"; updateDownloadPath = null; emitUpdateAvailable(); }
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
const files = rel.files || {};
|
const files = rel.files || {};
|
||||||
const setupFile = Object.keys(files).find((k) => /Setup/i.test(k));
|
const setupFile = Object.keys(files).find((k) => /Setup/i.test(k));
|
||||||
|
|
@ -647,15 +653,31 @@ async function checkForUpdate() {
|
||||||
};
|
};
|
||||||
const versionChanged = !updateAvailable || updateAvailable.version !== nextAvailable.version;
|
const versionChanged = !updateAvailable || updateAvailable.version !== nextAvailable.version;
|
||||||
updateAvailable = nextAvailable;
|
updateAvailable = nextAvailable;
|
||||||
if (versionChanged) {
|
if (versionChanged || updateDownloadState === "failed") {
|
||||||
// New candidate — reset any prior download state and kick off a fresh
|
// New candidate — or the same one whose download failed (truncated,
|
||||||
// silent background fetch so the chip lands as "ready to install".
|
// hash mismatch, network drop), which used to stay failed until the
|
||||||
|
// next release. Reset and kick off a fresh silent background fetch so
|
||||||
|
// the chip lands as "ready to install".
|
||||||
updateDownloadState = "idle";
|
updateDownloadState = "idle";
|
||||||
updateDownloadPath = null;
|
updateDownloadPath = null;
|
||||||
autoDownloadUpdate();
|
autoDownloadUpdate();
|
||||||
}
|
}
|
||||||
emitUpdateAvailable();
|
emitUpdateAvailable();
|
||||||
} catch { /* offline / manifest unreachable — silent */ }
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[update] check failed after ${Date.now() - t0} ms: ${e?.name || ""} ${e?.cause?.code || e?.message || e}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Startup check: wait for the boot rush to pass, then retry with backoff on
|
||||||
|
// failure — a flaky network at launch must not mean "no update until the
|
||||||
|
// 6-hourly recheck".
|
||||||
|
const UPDATE_STARTUP_DELAYS_MS = [8_000, 30_000, 2 * 60_000, 10 * 60_000, 30 * 60_000];
|
||||||
|
function scheduleStartupUpdateCheck(attempt = 0) {
|
||||||
|
if (attempt >= UPDATE_STARTUP_DELAYS_MS.length) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
checkForUpdate().then((ok) => { if (!ok) scheduleStartupUpdateCheck(attempt + 1); }, () => scheduleStartupUpdateCheck(attempt + 1));
|
||||||
|
}, UPDATE_STARTUP_DELAYS_MS[attempt]);
|
||||||
}
|
}
|
||||||
// Silent background pre-download of the update installer. The user never
|
// Silent background pre-download of the update installer. The user never
|
||||||
// has to click Download — clicking the chip goes straight to Install.
|
// has to click Download — clicking the chip goes straight to Install.
|
||||||
|
|
@ -1330,7 +1352,7 @@ function onChromeReady() {
|
||||||
emitUpdateAvailable();
|
emitUpdateAvailable();
|
||||||
setTimeout(loadOverlays, 250);
|
setTimeout(loadOverlays, 250);
|
||||||
refreshSnapshotFromSia().catch(() => {});
|
refreshSnapshotFromSia().catch(() => {});
|
||||||
checkForUpdate().catch(() => {});
|
scheduleStartupUpdateCheck();
|
||||||
refreshRemoteHomeCards().catch(() => {});
|
refreshRemoteHomeCards().catch(() => {});
|
||||||
}
|
}
|
||||||
function applyThrottle() {
|
function applyThrottle() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue