import { Trans, useLingui } from "@lingui/react/macro"; import type { DesktopUpdateState } from "@rakazo/contracts"; import { Button } from "@rakazo/ui-web"; import { createContext, type ReactNode, useContext, useEffect, useRef, useState } from "react"; import { desktopBridge } from "../lib/desktop"; function useDesktopUpdates() { const { t } = useLingui(); const bridge = desktopBridge()?.update; const [state, setState] = useState(null); const [busy, setBusy] = useState(false); const [confirmedCheck, setConfirmedCheck] = useState(null); const [error, setError] = useState(null); const pending = useRef(false); const revision = useRef(0); useEffect(() => { if (!bridge) return; let stopped = false; let timer: ReturnType; async function refresh() { const started = revision.current; try { const next = await bridge!.state(); if (!stopped && started === revision.current && !pending.current) { setState((current) => current && Object.entries(next).every( ([key, value]) => current[key as keyof DesktopUpdateState] === value, ) ? current : next, ); } } catch { // A disappearing bridge during shutdown should not interrupt the app. } finally { if (!stopped) timer = setTimeout(refresh, 2_000); } } void refresh(); return () => { stopped = true; clearTimeout(timer); }; }, [bridge]); async function act(action: "check" | "install") { if (!bridge || pending.current) return; pending.current = true; revision.current++; setBusy(true); setError(null); if (action === "check") setConfirmedCheck(null); try { const next = await bridge[action](); setState(next); if ( action === "check" && next.phase === "idle" && !next.message && next.checkedAt !== state?.checkedAt ) { setConfirmedCheck(next.checkedAt); } if (next.phase === "error" || action === "install") { setError(next.message); } } catch { setError(t`Could not complete the update. Try again.`); } finally { revision.current++; pending.current = false; setBusy(false); } } return { state, busy, error, act, confirmedCheck }; } const UpdatesContext = createContext | null>(null); export function DesktopUpdatesProvider({ children }: { children: ReactNode }) { const { t } = useLingui(); const updates = useDesktopUpdates(); const [dismissed, setDismissed] = useState(null); const { state, busy, error, act } = updates; return ( {children} {state && (state.phase === "ready" || (error && state.availableVersion)) && dismissed !== state.availableVersion ? ( ) : null} ); } export function DesktopUpdateSection() { const updates = useContext(UpdatesContext); if (!updates?.state) return null; const { state, busy, error, act, confirmedCheck } = updates; const ready = state.phase === "ready"; const downloading = state.phase === "available" || state.phase === "downloading"; return (

Desktop app

v{state.currentVersion}

{error ?? state.message ?? (downloading ? ( `${state.percent ?? 0}%` ) : state.phase === "idle" && confirmedCheck && state.checkedAt === confirmedCheck ? ( Up to date ) : null)}

); }