// The update gate (scripts/app-version.mjs builds the other half). // // The bundle knows its own build number (__APP_BUILD__). Every few minutes, // and whenever the app comes back to the foreground, it reads version.json // from the server, bypassing every cache. A newer build there means a release // went out: the modal lists what changed since the build this tab is running, // and «به‌روزرسانی» drops the service worker and every Cache Storage entry, // then reloads — so the next load is the new release, not a precached copy. // localStorage is left alone: sign-in, cart and settings survive the update. import { computed, onBeforeUnmount, onMounted, ref } from 'vue' declare const __APP_BUILD__: number declare const __APP_VERSION__: string export interface VersionInfo { build: number version: string history: { build: number; date: string; notes: string[] }[] } const CHECK_EVERY_MS = 5 * 60 * 1000 export const runningBuild: number = typeof __APP_BUILD__ === 'number' ? __APP_BUILD__ : 0 export const runningVersion: string = typeof __APP_VERSION__ === 'string' ? __APP_VERSION__ : '' export function useAppUpdate() { const available = ref(null) const updating = ref(false) /** What changed between the running build and the server's, newest first. */ const changes = computed(() => { const info = available.value if (!info) return [] return (info.history ?? []).filter((h) => h.build > runningBuild && h.build <= info.build) }) async function check() { if (import.meta.env.DEV || !runningBuild || available.value) return try { const res = await fetch(`${import.meta.env.BASE_URL}version.json?t=${Date.now()}`, { cache: 'no-store' }) if (!res.ok) return const info = (await res.json()) as VersionInfo if (typeof info.build === 'number' && info.build > runningBuild) available.value = info } catch { // Offline or a deploy in progress: try again on the next tick. } } async function applyUpdate() { updating.value = true try { const regs = await navigator.serviceWorker?.getRegistrations?.() if (regs) await Promise.all(regs.map((r) => r.unregister())) } catch { /* no service worker */ } try { if ('caches' in window) { const keys = await caches.keys() await Promise.all(keys.map((k) => caches.delete(k))) } } catch { /* Cache Storage unavailable */ } window.location.reload() } let timer: ReturnType | undefined const onVisible = () => { if (document.visibilityState === 'visible') check() } onMounted(() => { check() timer = setInterval(check, CHECK_EVERY_MS) document.addEventListener('visibilitychange', onVisible) }) onBeforeUnmount(() => { if (timer) clearInterval(timer) document.removeEventListener('visibilitychange', onVisible) }) return { available, changes, updating, applyUpdate, check } }