From 18ad8152bfe15397cbcb777d4432aab83d320d80 Mon Sep 17 00:00:00 2001 From: Claude Worker Date: Wed, 1 Jul 2026 06:37:12 +0000 Subject: [PATCH] feat: fix repetitive update available prompt Task #58 completed by ClaudeQueue ClaudeQueue --- server/server.js | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/server/server.js b/server/server.js index 243da0b..5d4da4f 100644 --- a/server/server.js +++ b/server/server.js @@ -25,15 +25,46 @@ import { logger } from 'hono/logger'; import { spawnSync } from 'node:child_process'; import { createServer } from 'node:http'; import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; import { initDb, upsertUser, recordVideoAccess, getUserData } from './db.js'; 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); +// ---------------------------------------------------------------------------- +// BUILD_TAG — must be DETERMINISTIC across restarts of identical code. +// +// Previously this was `Date.now().toString(36)`, which changes every time the +// process starts even if nothing was deployed (crash-loop, healthcheck +// restart, container reschedule). The frontend's checkBuildTag() polls +// /api/version and re-shows the "Update available" modal the instant the tag +// drifts — so a restarting-but-unchanged server kept re-announcing an update +// that never actually happened, and clicking "Refresh UI" (which itself +// reloads the page and re-polls) never made the prompt go away for good. +// +// Fix: hash the actual served frontend files. Identical code → identical +// hash → identical tag, no matter how many times the process restarts. A +// real deploy (changed files) still produces a new tag as intended. +// process.env.BUILD_TAG still wins if a CI pipeline already injects a git +// SHA — that's an even better source of truth than a content hash. +// ---------------------------------------------------------------------------- +function computeBuildTag() { + try { + const hash = createHash('sha256'); + for (const file of ['app.js', 'sw.js', 'index.html', 'styles.css']) { + hash.update(readFileSync(`./public/${file}`)); + } + return hash.digest('hex').slice(0, 12); + } catch { + // Frontend files not readable (e.g. unit tests run outside ./public) — + // fall back to a fixed tag rather than Date.now(), so it still never + // drifts spuriously between restarts. + return 'dev-build'; + } +} + +const BUILD_TAG = process.env.BUILD_TAG || computeBuildTag(); const SEARCH_LIMIT = 25; const CHANNEL_LIMIT = 60;