import { useEffect, useState, useCallback } from 'react'; import { useShadeContext } from './ShadeProvider.js'; export interface ShadeState { identity: { fingerprint: string | null; registrationId: number | null; lastInitialized: number | null; lastRotated: number | null; }; sessions: Array<{ address: string; remoteIdentityKeyHash: string; messageCountSent: number; messageCountReceived: number; lastActivity: number; dhRatchetSteps: number; }>; prekeys: { oneTimeRemaining: number; lastGenerated: number | null; lastConsumed: number | null; signedPreKeyId: number | null; signedPreKeyLastRotated: number | null; }; retiredIdentities: number; server: { registeredIdentities: string[]; totalBundleFetches: number; totalReplenishes: number; totalDeleted: number; totalRateLimited: number; }; } export interface UseShadeStateResult { state: ShadeState | null; loading: boolean; error: Error | null; refresh: () => Promise; } /** * Hook that polls the observer's /api/state endpoint at the configured interval. */ export function useShadeState(): UseShadeStateResult { const ctx = useShadeContext(); const [state, setState] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchState = useCallback(async () => { try { const res = await fetch(`${ctx.observerUrl}/api/state`, { headers: { Authorization: `Bearer ${ctx.token}` }, }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = (await res.json()) as ShadeState; setState(json); setError(null); } catch (err) { setError(err as Error); } finally { setLoading(false); } }, [ctx.observerUrl, ctx.token]); useEffect(() => { fetchState(); const interval = setInterval(fetchState, ctx.pollIntervalMs); return () => clearInterval(interval); }, [fetchState, ctx.pollIntervalMs]); return { state, loading, error, refresh: fetchState }; }