import type { RealtimeChannel } from '@supabase/supabase-js'; import { useEffect, useState } from 'react'; import { supabase } from './supabase'; // Observes the `call-presence:` channel for every conversation // the user is a member of and returns the first one (if any) that currently // has peers other than the viewer in the call. Enables a "call is live — // rejoin" affordance in the sidebar for conversations the viewer never // joined, mirroring Discord's active-voice indicator. // // Uses polling on each channel's `presenceState()` to side-step Supabase's // topic-based channel dedupe (see useCallPresence.ts). export function useAnyActiveCall( conversationIds: readonly string[], myId: string | null, ): { conversationId: string; userIds: string[] } | null { const [byConv, setByConv] = useState>({}); const key = conversationIds.join(','); useEffect(() => { if (conversationIds.length === 0) { setByConv({}); return; } const tracked: Array<{ id: string; ch: RealtimeChannel; owns: boolean }> = []; for (const id of conversationIds) { const ch = supabase.channel('call-presence:' + id, { config: { presence: { key: 'obs-' + Math.random().toString(36).slice(2, 8), enabled: true, }, }, }); const owns = ch.state === 'closed'; if (owns) void ch.subscribe(); tracked.push({ id, ch, owns }); } const resync = () => { setByConv((prev) => { const next: Record = {}; let changed = false; for (const { id, ch } of tracked) { const presState = ch.presenceState() as Record< string, Array> >; const ids = new Set(); for (const list of Object.values(presState)) { for (const e of list) { const uid = e?.userId; if (typeof uid === 'string') ids.add(uid); } } const arr = Array.from(ids).sort(); next[id] = arr; const prevArr = prev[id] ?? []; if (prevArr.length !== arr.length || !arr.every((v, i) => v === prevArr[i])) { changed = true; } } // Also pick up removed conv ids. if (!changed && Object.keys(prev).length !== Object.keys(next).length) { changed = true; } return changed ? next : prev; }); }; resync(); const pollId = window.setInterval(resync, 1500); return () => { window.clearInterval(pollId); for (const { ch, owns } of tracked) { if (owns) void supabase.removeChannel(ch); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]); for (const id of conversationIds) { const users = byConv[id] ?? []; const others = myId ? users.filter((u) => u !== myId) : users; if (others.length > 0) { return { conversationId: id, userIds: others }; } } return null; }