diff --git a/frontend/app.js b/frontend/app.js index 2217bce..093f9af 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -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', diff --git a/server/server.js b/server/server.js index 325c82d..830038d 100644 --- a/server/server.js +++ b/server/server.js @@ -30,6 +30,10 @@ const PORT = parseInt(process.env.PORT || '3000', 10); const APP_VERSION = process.env.APP_VERSION || '1.0.0'; const YTDLP = process.env.YTDLP_PATH || 'yt-dlp'; +// Stable tag for this server process — changes on every deploy/restart. +// The frontend polls /api/version and reloads when the tag drifts. +const BUILD_TAG = process.env.BUILD_TAG || Date.now().toString(36); + const SEARCH_LIMIT = 25; const CHANNEL_LIMIT = 60; @@ -119,7 +123,15 @@ app.use('*', logger()); // ============================================================================ // GET /api/version -app.get('/api/version', (c) => c.json({ version: APP_VERSION })); +// Returns version string + a build tag that changes on every server restart/deploy. +// Clients poll this to detect when a new build is live and prompt a reload. +app.get('/api/version', (c) => + c.json( + { version: APP_VERSION, buildTag: BUILD_TAG }, + 200, + { 'Cache-Control': 'no-store, no-cache, must-revalidate' } + ) +); // GET /api/search?q= app.get('/api/search', async (c) => {