From 5e22c1baa0d5adbaae6723f92f52b622cd936ba0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 16:52:25 +0000 Subject: [PATCH] Refresh the update check on page load when the cache is stale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The daily cron already keeps system.updateCheck fresh, so most page loads just read the cached setting — no outbound request. As a backstop for when the cron missed its run, getUpdateStatus() now schedules one background GitHub check (via next/server's after(), so it doesn't block the response) whenever the cached result is older than 20 hours. Concurrent requests during that window are deduped via an in-memory in-flight guard. --- src/lib/update-check.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib/update-check.ts b/src/lib/update-check.ts index f41faa6..d85e34a 100644 --- a/src/lib/update-check.ts +++ b/src/lib/update-check.ts @@ -1,7 +1,29 @@ import "server-only" +import { after } from "next/server" import { APP_VERSION, REPO_URL } from "./version" import { getSetting, setSetting, type SettingValue } from "./settings" +// The daily cron (see src/lib/jobs/index.ts) already refreshes this, so a +// page load normally just reads the cached setting below — no outbound +// request. This is only a backstop for when the cron missed its run (e.g. +// the server was down at 06:00): if the cache is older than this, a page +// load triggers one background refresh after the response is sent. +const STALE_AFTER_MS = 20 * 60 * 60 * 1000 + +// Dedupes concurrent triggers within this process (e.g. several tabs/admins +// loading a page at once while the cache is stale). +let refreshInFlight: Promise | null = null + +function triggerBackgroundRefresh() { + if (refreshInFlight) return + refreshInFlight = checkForUpdate() + .then(() => undefined) + .catch((error) => console.error("[update-check]", error)) + .finally(() => { + refreshInFlight = null + }) +} + const [REPO_OWNER, REPO_NAME] = new URL(REPO_URL).pathname.slice(1).split("/") /** Parses "1.2.3" into comparable numeric parts. Non-numeric parts sort as 0. */ @@ -55,6 +77,10 @@ export async function getUpdateStatus(): Promise<{ updateAvailable: boolean }> { const latest = await getSetting("system.updateCheck") + + const stale = !latest || Date.now() - new Date(latest.checkedAt).getTime() > STALE_AFTER_MS + if (stale) after(triggerBackgroundRefresh) + return { current: APP_VERSION, latest,