| 1 | import { useEffect, useRef } from "react"; |
| 2 | import { useRevalidator } from "react-router"; |
| 3 | |
| 4 | import { setErroring } from "./resilient-fetch.ts"; |
| 5 | |
| 6 | /** The wait before the error page's try `attempt` (from 1): 3 s, 6 s, 12 s, then every 30 s. */ |
| 7 | export function reconnectDelay(attempt: number): number { |
| 8 | return Math.min(3000 * 2 ** (attempt - 1), 30_000); |
| 9 | } |
| 10 | |
| 11 | /** |
| 12 | * While `on` (the page fell to its error page because the network was |
| 13 | * gone), loads the page again: a little later each time, at once when the |
| 14 | * browser says it is back online or the tab comes back into view. Once a |
| 15 | * load arrives, the router drops the error and the page is back. Each try |
| 16 | * is one request (lib/resilient-fetch.ts stands back meanwhile), so a |
| 17 | * network that never answers costs a request every 30 s, not a loop; one |
| 18 | * that answers with a real error shows it, and the trying stops. |
| 19 | */ |
| 20 | export function useReconnect(on: boolean) { |
| 21 | const revalidator = useRevalidator(); |
| 22 | const latest = useRef(revalidator); |
| 23 | latest.current = revalidator; |
| 24 | useEffect(() => { |
| 25 | if (!on) return; |
| 26 | setErroring(true); |
| 27 | let attempt = 0; |
| 28 | let timer: ReturnType<typeof setTimeout> | undefined; |
| 29 | const schedule = () => { |
| 30 | clearTimeout(timer); |
| 31 | timer = setTimeout(retry, reconnectDelay(++attempt)); |
| 32 | }; |
| 33 | const retry = () => { |
| 34 | if (document.visibilityState === "visible" && latest.current.state === "idle") void latest.current.revalidate(); |
| 35 | schedule(); |
| 36 | }; |
| 37 | const now = () => { |
| 38 | if (document.visibilityState !== "visible") return; |
| 39 | attempt = 0; |
| 40 | clearTimeout(timer); |
| 41 | retry(); |
| 42 | }; |
| 43 | schedule(); |
| 44 | window.addEventListener("online", now); |
| 45 | document.addEventListener("visibilitychange", now); |
| 46 | return () => { |
| 47 | setErroring(false); |
| 48 | clearTimeout(timer); |
| 49 | window.removeEventListener("online", now); |
| 50 | document.removeEventListener("visibilitychange", now); |
| 51 | }; |
| 52 | }, [on]); |
| 53 | } |