File size: 2,901 Bytes
201b13c | 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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | import { useEffect, useState } from "react"
import { fetchLatestReport, getWebSocketUrl } from "../lib/api"
export default function useRealtimeInspection() {
const [latestInspection, setLatestInspection] = useState(null)
const [connectionState, setConnectionState] = useState("connecting")
const [lastEventAt, setLastEventAt] = useState(null)
const [streamError, setStreamError] = useState("")
useEffect(() => {
let socket
let reconnectTimer
let heartbeatTimer
let refreshTimer
let active = true
let retryCount = 0
async function hydrateLatest() {
try {
const latest = await fetchLatestReport()
if (active && latest?.timestamp) {
setLatestInspection((current) => {
if (!current || current.timestamp !== latest.timestamp) {
return latest
}
return current
})
setLastEventAt(latest.timestamp)
}
} catch {
if (active) {
setStreamError("Latest inspection snapshot is unavailable right now.")
}
}
}
function scheduleReconnect() {
if (!active) return
setConnectionState("reconnecting")
const delay = Math.min(8000, 1200 * 2 ** retryCount)
retryCount += 1
reconnectTimer = window.setTimeout(connect, delay)
}
function connect() {
if (!active) return
setConnectionState("connecting")
socket = new WebSocket(getWebSocketUrl("/ws"))
socket.onopen = () => {
retryCount = 0
setConnectionState("connected")
setStreamError("")
heartbeatTimer = window.setInterval(() => {
if (socket?.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type: "ping" }))
}
}, 15000)
}
socket.onmessage = (event) => {
const payload = JSON.parse(event.data)
if (payload.type === "inspection.update" || payload.type === "inspection.snapshot") {
setLatestInspection(payload)
setLastEventAt(payload.timestamp)
return
}
if (payload.type === "pong") {
setConnectionState("connected")
}
}
socket.onerror = () => {
setStreamError("Live stream interrupted. Reconnecting to the inspection server.")
}
socket.onclose = () => {
window.clearInterval(heartbeatTimer)
if (active) {
scheduleReconnect()
}
}
}
hydrateLatest()
connect()
refreshTimer = window.setInterval(hydrateLatest, 6000)
return () => {
active = false
window.clearTimeout(reconnectTimer)
window.clearInterval(heartbeatTimer)
window.clearInterval(refreshTimer)
if (socket) {
socket.close()
}
}
}, [])
return {
latestInspection,
connectionState,
lastEventAt,
streamError,
}
}
|