diff --git a/src/components/FollowPackDetailContent.tsx b/src/components/FollowPackDetailContent.tsx index 092a7adb..15e8351f 100644 --- a/src/components/FollowPackDetailContent.tsx +++ b/src/components/FollowPackDetailContent.tsx @@ -15,7 +15,6 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useFollowList, useFollowActions } from '@/hooks/useFollowActions'; import { useNostrPublish } from '@/hooks/useNostrPublish'; import { useNostr } from '@nostrify/react'; -import { useAppContext } from '@/hooks/useAppContext'; import { genUserName } from '@/lib/genUserName'; /** Parse a follow pack / starter pack event into structured data. */ @@ -37,7 +36,6 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) { const { toast } = useToast(); const { nostr } = useNostr(); const { user } = useCurrentUser(); - const { config } = useAppContext(); const { data: followList } = useFollowList(); const { mutateAsync: publishEvent } = useNostrPublish(); @@ -71,21 +69,9 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) { setIsFollowingAll(true); try { - const discoveryRelays = [ - 'wss://relay.damus.io', - 'wss://nos.lol', - 'wss://relay.primal.net', - 'wss://purplepag.es', - ]; - const userRelays = config.relayMetadata.relays - .filter((r) => r.read) - .map((r) => r.url); - const allRelayUrls = [...new Set([...discoveryRelays, ...userRelays])]; - const signal = AbortSignal.timeout(10_000); - const relayGroup = nostr.group(allRelayUrls); - const followEvents = await relayGroup.query( + const followEvents = await nostr.query( [{ kinds: [3], authors: [user.pubkey], limit: 1 }], { signal }, ); @@ -123,7 +109,7 @@ export function FollowPackDetailContent({ event }: { event: NostrEvent }) { } finally { setIsFollowingAll(false); } - }, [user, pubkeys, nostr, config, publishEvent, toast]); + }, [user, pubkeys, nostr, publishEvent, toast]); const handleCopyLink = useCallback(() => { const dTag = event.tags.find(([n]) => n === 'd')?.[1] ?? ''; diff --git a/src/components/NostrProvider.tsx b/src/components/NostrProvider.tsx index 2a28f5b5..0a21d7d8 100644 --- a/src/components/NostrProvider.tsx +++ b/src/components/NostrProvider.tsx @@ -21,9 +21,6 @@ const NostrProvider: React.FC = (props) => { // Use refs so the pool always has the latest data const effectiveRelays = useRef(getEffectiveRelays(config.relayMetadata, config.useAppRelays)); - // Track previous relay URLs to detect changes - const prevRelayUrlsRef = useRef(''); - // Update effective relays ref and invalidate all queries when relays change, // since any cached query may have been fetched from a different set of relays. useEffect(() => { @@ -33,31 +30,7 @@ const NostrProvider: React.FC = (props) => { // Only invalidate if the relay URLs actually changed const prevUrls = prev.relays.map(r => r.url).sort().join(','); const nextUrls = effectiveRelays.current.relays.map(r => r.url).sort().join(','); - if (prevUrls !== nextUrls) { - // Close connections to relays that are no longer in the list - if (pool.current && prevRelayUrlsRef.current) { - const prevSet = new Set(prevUrls.split(',').filter(Boolean)); - const nextSet = new Set(nextUrls.split(',').filter(Boolean)); - - // Find relays that were removed - const removedRelays = [...prevSet].filter(url => !nextSet.has(url)); - - // Close removed relay connections - for (const url of removedRelays) { - try { - const relay = (pool.current as any).relays?.get?.(url); - if (relay) { - relay.close(); - (pool.current as any).relays?.delete?.(url); - } - } catch (e) { - // Ignore errors - } - } - } - - prevRelayUrlsRef.current = nextUrls; queryClient.invalidateQueries(); } }, [config.relayMetadata, config.useAppRelays, queryClient]); diff --git a/src/hooks/useFollowActions.ts b/src/hooks/useFollowActions.ts index e74e0e0a..ae18df11 100644 --- a/src/hooks/useFollowActions.ts +++ b/src/hooks/useFollowActions.ts @@ -2,37 +2,20 @@ import { useCallback, useState } from 'react'; import { useNostr } from '@nostrify/react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useCurrentUser } from './useCurrentUser'; -import { useAppContext } from './useAppContext'; import { useNostrPublish } from './useNostrPublish'; import type { NostrEvent } from '@nostrify/nostrify'; /** - * Discovery relays used to find the user's latest kind 3 event. - * We cast a wide net to avoid acting on stale data. - */ -const DISCOVERY_RELAYS = [ - 'wss://relay.damus.io', - 'wss://nos.lol', - 'wss://relay.primal.net', - 'wss://purplepag.es', - 'wss://relay.snort.social', -]; - -/** - * Fetches the absolute freshest kind 3 follow list from multiple relays. - * This prevents stale-data mutations that could accidentally wipe follows. + * Fetches the absolute freshest kind 3 follow list via the pool. + * The pool already routes to all configured read relays. */ async function fetchFreshFollowEvent( nostr: ReturnType['nostr'], pubkey: string, - userRelayUrls: string[], ): Promise { - const allRelayUrls = [...new Set([...DISCOVERY_RELAYS, ...userRelayUrls])]; - const signal = AbortSignal.timeout(10_000); - const relayGroup = nostr.group(allRelayUrls); - const followEvents = await relayGroup.query( + const followEvents = await nostr.query( [{ kinds: [3], authors: [pubkey], limit: 1 }], { signal }, ); @@ -109,25 +92,19 @@ export interface UseFollowActionsReturn { export function useFollowActions(): UseFollowActionsReturn { const { nostr } = useNostr(); const { user } = useCurrentUser(); - const { config } = useAppContext(); const { mutateAsync: publishEvent } = useNostrPublish(); const queryClient = useQueryClient(); const [isPending, setIsPending] = useState(false); - /** User's read-enabled relay URLs (from NIP-65 config). */ - const userRelayUrls = config.relayMetadata.relays - .filter((r) => r.read) - .map((r) => r.url); - const mutateFollowList = useCallback( async (targetPubkey: string, action: 'follow' | 'unfollow') => { if (!user) throw new Error('Not logged in'); setIsPending(true); try { - // ① Fetch the freshest kind 3 event from a wide relay set - const latestEvent = await fetchFreshFollowEvent(nostr, user.pubkey, userRelayUrls); + // ① Fetch the freshest kind 3 event via pool + const latestEvent = await fetchFreshFollowEvent(nostr, user.pubkey); // ② Separate tags into `p` tags (follow entries) and everything else const existingTags = latestEvent?.tags ?? []; @@ -163,7 +140,7 @@ export function useFollowActions(): UseFollowActionsReturn { setIsPending(false); } }, - [nostr, user, userRelayUrls, publishEvent, queryClient], + [nostr, user, publishEvent, queryClient], ); const follow = useCallback( diff --git a/src/hooks/useSearchProfiles.ts b/src/hooks/useSearchProfiles.ts index 3c216467..47efe7d5 100644 --- a/src/hooks/useSearchProfiles.ts +++ b/src/hooks/useSearchProfiles.ts @@ -18,10 +18,8 @@ export function useSearchProfiles(query: string) { queryFn: async ({ signal }) => { if (!query.trim()) return []; - // Use relay.ditto.pub specifically for NIP-50 profile search - const relay = nostr.relay('wss://relay.ditto.pub'); - - const events = await relay.query( + // NIP-50 profile search (uses pool, reuses existing connections) + const events = await nostr.query( [{ kinds: [0], search: query.trim(), limit: 10 }], { signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]) }, ); diff --git a/src/hooks/useStreamKind.ts b/src/hooks/useStreamKind.ts index 7d5a3267..3cbec9f3 100644 --- a/src/hooks/useStreamKind.ts +++ b/src/hooks/useStreamKind.ts @@ -67,13 +67,12 @@ export function useStreamKind(kind: number | number[]) { setEvents(Array.from(eventMap.values()).sort((a, b) => b.created_at - a.created_at)); } - const relay = nostr.relay('wss://relay.ditto.pub'); const filter = { kinds }; - // 1. Fetch initial batch + // 1. Fetch initial batch (uses pool, reuses existing connections) (async () => { try { - const results = await relay.query( + const results = await nostr.query( [{ ...filter, limit: 40 }], { signal: ac.signal }, ); @@ -86,11 +85,11 @@ export function useStreamKind(kind: number | number[]) { if (alive) setIsLoading(false); })(); - // 2. Stream new events + // 2. Stream new events (uses pool, reuses existing connections) (async () => { try { const now = Math.floor(Date.now() / 1000); - for await (const msg of relay.req( + for await (const msg of nostr.req( [{ ...filter, since: now, limit: 100 }], { signal: ac.signal }, )) { diff --git a/src/hooks/useStreamPosts.ts b/src/hooks/useStreamPosts.ts index 8badbf2e..ef41b7d8 100644 --- a/src/hooks/useStreamPosts.ts +++ b/src/hooks/useStreamPosts.ts @@ -95,8 +95,6 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) { setAllEvents(Array.from(eventMap.values()).sort((a, b) => b.created_at - a.created_at)); } - const relay = nostr.relay('wss://relay.ditto.pub'); - // Build the kinds list: either vines-only or kind 1 + enabled extras const kinds: number[] = isVines ? [34236] @@ -108,10 +106,10 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) { baseFilter.search = query.trim(); } - // 1. Fetch initial batch + // 1. Fetch initial batch (uses pool, reuses existing connections) (async () => { try { - const events = await relay.query( + const events = await nostr.query( [{ ...baseFilter, limit: 40 }], { signal: ac.signal }, ); @@ -124,11 +122,11 @@ export function useStreamPosts(query: string, options: StreamPostsOptions) { if (alive) setIsLoading(false); })(); - // 2. Stream new events + // 2. Stream new events (uses pool, reuses existing connections) (async () => { try { const now = Math.floor(Date.now() / 1000); - for await (const msg of relay.req( + for await (const msg of nostr.req( [{ ...baseFilter, since: now, limit: 100 }], { signal: ac.signal }, )) {