import { type Friendship, listFriendships } from '@chat-app/shared/friends'; import { useCallback, useEffect, useState } from 'react'; import { supabase } from './supabase'; interface FriendshipsState { friendships: Friendship[]; loading: boolean; error: string | null; } // Subscribes to the `friendships` realtime channel and re-pulls the typed // list whenever an INSERT/UPDATE/DELETE touches one of the caller's rows. export function useFriendships(userId: string | undefined): FriendshipsState & { refresh: () => Promise; } { const [state, setState] = useState({ friendships: [], loading: true, error: null, }); const refresh = useCallback(async () => { try { const items = await listFriendships(supabase); setState({ friendships: items, loading: false, error: null }); } catch (err: unknown) { setState((prev) => ({ ...prev, loading: false, error: err instanceof Error ? err.message : 'failed to load friendships', })); } }, []); useEffect(() => { if (!userId) return; void refresh(); const channel = supabase .channel('friendships:' + userId) .on('postgres_changes', { event: '*', schema: 'public', table: 'friendships' }, () => { void refresh(); }) .subscribe(); // Windows WebView2 throttles background sockets — refresh on wake. // Throttled + visibility-only so a normal click into the window does not // re-fetch on every focus. let lastAwakeRefresh = 0; const AWAKE_THROTTLE_MS = 30_000; const onAwake = () => { if (document.visibilityState !== 'visible') return; const now = Date.now(); if (now - lastAwakeRefresh < AWAKE_THROTTLE_MS) return; lastAwakeRefresh = now; void refresh(); try { channel.subscribe(); } catch { /* already live */ } }; document.addEventListener('visibilitychange', onAwake); window.addEventListener('online', onAwake); return () => { document.removeEventListener('visibilitychange', onAwake); window.removeEventListener('online', onAwake); void supabase.removeChannel(channel); }; }, [userId, refresh]); return { ...state, refresh }; }