import { createContext, useContext, useEffect, useRef, useState } from 'react'; import type { RealtimeChannel } from '@supabase/supabase-js'; import { supabase } from '../lib/supabase'; type OnlinePresenceContext = { onlineUserIds: Set; isOnline: (userId: string) => boolean; }; export const OnlinePresenceContext = createContext({ onlineUserIds: new Set(), isOnline: () => false, }); export function useOnlineUsers(): OnlinePresenceContext { return useContext(OnlinePresenceContext); } let sharedChannel: RealtimeChannel | null = null; let subscriberCount = 0; let onlineUserIds: Set = new Set(); const listeners = new Set<(ids: Set) => void>(); function notify() { const snapshot = new Set(onlineUserIds); listeners.forEach((fn) => fn(snapshot)); } function ensureChannel(currentUserId: string) { if (sharedChannel) return; const ch = supabase.channel('presence:online', { config: { presence: { key: currentUserId } }, }); sharedChannel = ch; ch .on('presence', { event: 'sync' }, () => { const state = ch.presenceState(); const keys = Object.keys(state); onlineUserIds = new Set(keys); notify(); }) .subscribe(async (status: string) => { if (status === 'SUBSCRIBED') { await ch.track({ userId: currentUserId, online_at: new Date().toISOString() }); } }); } function teardownChannel() { if (!sharedChannel) return; sharedChannel.untrack().catch(() => {}); supabase.removeChannel(sharedChannel); sharedChannel = null; onlineUserIds = new Set(); notify(); } export function untrackSelf() { sharedChannel?.untrack().catch(() => {}); } export function retrackSelf(currentUserId: string) { sharedChannel ?.track({ userId: currentUserId, online_at: new Date().toISOString() }) .catch(() => {}); } export function useOnlinePresenceNode(currentUserId: string | null | undefined) { const [ids, setIds] = useState>(new Set(onlineUserIds)); useEffect(() => { if (!currentUserId) return; subscriberCount++; ensureChannel(currentUserId); const listener = (next: Set) => setIds(next); listeners.add(listener); return () => { listeners.delete(listener); subscriberCount--; if (subscriberCount <= 0) { subscriberCount = 0; teardownChannel(); } }; }, [currentUserId]); return ids; }