feat: poll /api/version buildTag to detect new deploys and prompt reload

Server stamps a BUILD_TAG (stable per process, changes on restart/deploy)
into GET /api/version alongside the existing version string. Cache-Control
is set to no-store so the response is never cached by the SW or browser.

Client (WEB mode only) baselines the tag on first fetch after boot, then
rechecks every 5 minutes. On mismatch it calls the existing showUpdateBanner()
so the user sees the 'Update ready — Reload now' toast and can reload at will.

This mirrors the build-id polling pattern used across the BukidBountyApp
derivatives to avoid stale UI after deploys.
This commit is contained in:
Jonathan Sykes
2026-07-01 00:53:22 +08:00
parent d9371fdea4
commit 6738644d4b
2 changed files with 48 additions and 1 deletions

View File

@@ -2625,6 +2625,40 @@ function importBackup(e) {
e.target.value = '';
}
// ============================================================================
// Build-tag polling — detect server restarts / new deploys (WEB mode only)
//
// The server stamps every response from GET /api/version with a `buildTag`
// that is stable for the lifetime of the process (changes on restart/deploy).
// We baseline the tag on first fetch; any subsequent change means new code is
// live and we surface the existing update banner so the user can reload.
// ============================================================================
let _knownBuildTag = null;
async function checkBuildTag() {
try {
const res = await fetch('/api/version', { cache: 'no-store' });
if (!res.ok) return;
const data = await res.json();
const tag = data.buildTag;
if (!tag) return;
if (!_knownBuildTag) {
_knownBuildTag = tag; // baseline on first successful fetch
return;
}
if (tag !== _knownBuildTag) {
_knownBuildTag = tag; // update so we don't show the banner twice
showUpdateBanner();
}
} catch { /* network error — try again next interval */ }
}
function pollBuildTag() {
checkBuildTag();
setInterval(checkBuildTag, 5 * 60 * 1000); // recheck every 5 minutes
}
// ============================================================================
// PWA — service worker registration and update banner (WEB mode only)
// ============================================================================
@@ -2720,6 +2754,7 @@ async function boot() {
// Register service worker + ping server with fingerprint (WEB mode only)
registerServiceWorker();
if (WEB) pollBuildTag();
if (WEB) {
fetch('/api/user/sync', {
method: 'POST',