File size: 1,446 Bytes
dcedb25 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import { useState } from 'react'
import { apiFetch } from '@/lib/api'
import { fetchLatestIndex, isNewBuild, loadedEntryScript } from '@/lib/new-build'
const POLL_MS = 3000
const GIVE_UP_MS = 5 * 60 * 1000
export type DockerUpdatePhase = 'idle' | 'starting' | 'waiting' | 'failed' | 'timeout'
/**
* Docker "Update now": the server asks its Watchtower sidecar to pull the new
* image and recreate the container (POST /api/update/apply), which takes this
* server down mid-session. The page then polls for index.html and reloads
* once the server is back — on a new build, or after it was seen restarting.
*/
export function useDockerUpdate() {
const [phase, setPhase] = useState<DockerUpdatePhase>('idle')
async function start() {
setPhase('starting')
try {
await apiFetch('/api/update/apply', { method: 'POST' })
} catch {
setPhase('failed')
return
}
setPhase('waiting')
const loaded = loadedEntryScript()
const started = Date.now()
let wentDown = false
while (Date.now() - started < GIVE_UP_MS) {
await new Promise(resolve => setTimeout(resolve, POLL_MS))
try {
const html = await fetchLatestIndex()
if (isNewBuild(loaded, html) || wentDown) {
window.location.reload()
return
}
} catch {
wentDown = true
}
}
setPhase('timeout')
}
return { phase, start, reset: () => setPhase('idle') }
}
|