import { DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS } from '@/constants' import { normalizeAnyRelayUrl } from '@/lib/url' import { useNostr } from '@/providers/NostrProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' import client from '@/services/client.service' import { useEffect, useMemo, useState } from 'react' const POLL_MS = 1500 function canon(url: string): string { return (normalizeAnyRelayUrl(url) || url).trim().toLowerCase() } function mergeUniquePreserveOrder(...lists: (readonly string[] | undefined)[]): string[] { const seen = new Set() const out: string[] = [] for (const list of lists) { if (!list?.length) continue for (const raw of list) { const n = normalizeAnyRelayUrl(raw) || raw const k = canon(n) if (!k || seen.has(k)) continue seen.add(k) out.push(n) } } return out } export type TRelayConnectionRow = { url: string /** WebSocket in the pool is open. */ connected: boolean } /** * Relays to show in “active relays” UI: favorites + NIP-65 read/write + defaults + fast-read, * then any pool-connected URL not already listed. {@link row.connected} reflects the live WebSocket. */ export function useRelayConnectionRows(): { rows: TRelayConnectionRow[] /** Relays that currently have an open WebSocket connection. */ connectedCount: number } { const { relayList } = useNostr() const { favoriteRelays } = useFavoriteRelays() const [connectedCanon, setConnectedCanon] = useState>(() => new Set(client.getConnectedRelayUrls().map(canon)) ) useEffect(() => { const tick = () => setConnectedCanon(new Set(client.getConnectedRelayUrls().map(canon))) tick() const id = window.setInterval(tick, POLL_MS) return () => clearInterval(id) }, []) return useMemo(() => { const inbox = [...(relayList?.read ?? []), ...(relayList?.write ?? [])] const base = mergeUniquePreserveOrder( favoriteRelays, inbox, DEFAULT_FAVORITE_RELAYS, FAST_READ_RELAY_URLS ) const baseCanon = new Set(base.map(canon)) const rowFor = (url: string, socketConnected: boolean): TRelayConnectionRow => ({ url, connected: socketConnected }) const rows: TRelayConnectionRow[] = base.map((url) => rowFor(url, connectedCanon.has(canon(url))) ) for (const url of client.getConnectedRelayUrls()) { const k = canon(url) if (baseCanon.has(k)) continue rows.push(rowFor(url, true)) } const connectedCount = rows.filter((r) => r.connected).length return { rows, connectedCount } }, [favoriteRelays, relayList?.read, relayList?.write, connectedCanon]) }