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') }
}