From 4eb8c7d653d8c083e5e4cb9aaefd5855fc40c64b Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 30 Oct 2024 14:59:44 +0100 Subject: [PATCH] fix: use subscription for user search --- src/pages/search.tsx | 90 ++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 609e5cc..f94be8f 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -4,11 +4,11 @@ import { NDKKind, NDKSubscriptionCacheUsage, NDKUserProfile, + NostrEvent, profileFromEvent } from '@nostr-dev-kit/ndk' import { ErrorBoundary } from 'components/ErrorBoundary' import { GameCard } from 'components/GameCard' -import { LoadingSpinner } from 'components/LoadingSpinner' import { ModCard } from 'components/ModCard' import { ModFilter } from 'components/ModsFilter' import { Pagination } from 'components/Pagination' @@ -35,10 +35,7 @@ import { DEFAULT_FILTER_OPTIONS, extractModData, isModDataComplete, - log, - LogType, - scrollIntoView, - timeout + scrollIntoView } from 'utils' enum SearchKindEnum { @@ -364,48 +361,70 @@ const UsersResult = ({ moderationFilter, muteLists }: UsersResultProps) => { - const { fetchEvents } = useNDKContext() - const [isFetching, setIsFetching] = useState(false) + const { ndk } = useNDKContext() const [profiles, setProfiles] = useState([]) - const userState = useAppSelector((state) => state.user) useEffect(() => { if (searchTerm === '') { setProfiles([]) } else { - const fetchProfiles = async () => { - setIsFetching(true) - - const filter: NDKFilter = { + const sub = ndk.subscribe( + { kinds: [NDKKind.Metadata], search: searchTerm + }, + { + closeOnEose: true, + cacheUsage: NDKSubscriptionCacheUsage.PARALLEL + }, + undefined, + false + ) + + // Stop the sub after 10 seconds if we are still searching the same term as before + window.setTimeout(() => { + if (sub.filter.search === searchTerm) { + sub.stop() } + }, 10000) - const profiles = await Promise.race([ - fetchEvents(filter), - timeout(10 * 1000) - ]) - .then((events) => { - const results = events.map((event) => { - const ndkEvent = new NDKEvent(undefined, event) - const profile = profileFromEvent(ndkEvent) - return profile - }) - return results - }) - .catch((err) => { - log(true, LogType.Error, 'An error occurred in fetching users', err) - return [] - }) + const onEvent = (event: NostrEvent | NDKEvent) => { + if (!(event instanceof NDKEvent)) event = new NDKEvent(undefined, event) + const dedupKey = event.deduplicationKey() + const existingEvent = events.get(dedupKey) + if (existingEvent) { + event = dedup(existingEvent, event) + } + event.ndk = this + events.set(dedupKey, event) + // We can't rely on the 'eose' to arrive + // Instead we repeat and sort results on each event + const ndkEvents = Array.from(events.values()) + const profiles: NDKUserProfile[] = [] + ndkEvents.forEach((event) => { + try { + const profile = profileFromEvent(event) + profiles.push(profile) + } catch (error) { + // If we are unable to parse silently skip over the errors + } + }) setProfiles(profiles) - setIsFetching(false) } - fetchProfiles() + // Clear previous results + const events = new Map() + + // Bind handler and start the sub + sub.on('event', onEvent) + sub.start() + return () => { + sub.stop() + } } - }, [fetchEvents, searchTerm]) + }, [ndk, searchTerm]) const filteredProfiles = useMemo(() => { let filtered = [...profiles] @@ -430,7 +449,6 @@ const UsersResult = ({ }, [userState.user?.npub, moderationFilter, profiles, muteLists]) return ( <> - {isFetching && }
{filteredProfiles.map((profile) => { @@ -505,3 +523,11 @@ const GamesResult = ({ searchTerm }: GamesResultProps) => { ) } +function dedup(event1: NDKEvent, event2: NDKEvent) { + // return the newest of the two + if (event1.created_at! > event2.created_at!) { + return event1 + } + + return event2 +}