From 160dca628dbcde5565056277bb33cec79a408495 Mon Sep 17 00:00:00 2001 From: Gigi Date: Wed, 22 Oct 2025 00:54:33 +0200 Subject: [PATCH] fix: simplify eventManager and restore working event fetching - Revert eventManager to simpler role: initialization and service coordination - Restore original working fetching logic in useEventLoader - eventManager now provides: getCachedEvent, getEventLoader, setServices - Fixes broken bookmark hydration and direct event loading - Uses eventManager for cache checking but direct subscription for fetching --- src/hooks/useEventLoader.ts | 36 +++++++++-- src/services/eventManager.ts | 118 ++++++++--------------------------- 2 files changed, 56 insertions(+), 98 deletions(-) diff --git a/src/hooks/useEventLoader.ts b/src/hooks/useEventLoader.ts index 54a6354e..63823743 100644 --- a/src/hooks/useEventLoader.ts +++ b/src/hooks/useEventLoader.ts @@ -55,18 +55,40 @@ export function useEventLoader({ useEffect(() => { if (!eventId) return + // Try to get from event store first (check cache synchronously) + const cachedEvent = eventManager.getCachedEvent(eventId) + if (cachedEvent) { + displayEvent(cachedEvent) + setReaderLoading(false) + setIsCollapsed(false) + setSelectedUrl('') + return + } + + // Event not in cache, set loading state and fetch from relays setReaderLoading(true) setReaderContent(undefined) setSelectedUrl('') // Don't set nostr: URL to avoid showing highlights setIsCollapsed(false) - // Fetch event using the event manager - eventManager.fetchEvent(eventId).then( - (event) => { + // If no relay pool yet, wait for it (will re-run when relayPool changes) + if (!relayPool) { + return + } + + // Fetch from relays using event manager's loader + const eventLoader = eventManager.getEventLoader() + if (!eventLoader) { + setReaderLoading(false) + return + } + + const subscription = eventLoader({ id: eventId }).subscribe({ + next: (event) => { displayEvent(event) setReaderLoading(false) }, - (err) => { + error: (err) => { const errorContent: ReadableContent = { url: '', html: `
Failed to load event: ${err instanceof Error ? err.message : 'Unknown error'}
`, @@ -75,6 +97,8 @@ export function useEventLoader({ setReaderContent(errorContent) setReaderLoading(false) } - ) - }, [eventId, displayEvent, setReaderLoading, setSelectedUrl, setIsCollapsed, setReaderContent]) + }) + + return () => subscription.unsubscribe() + }, [eventId, relayPool, displayEvent, setReaderLoading, setSelectedUrl, setIsCollapsed, setReaderContent]) } diff --git a/src/services/eventManager.ts b/src/services/eventManager.ts index e798ea4a..a15993ca 100644 --- a/src/services/eventManager.ts +++ b/src/services/eventManager.ts @@ -2,26 +2,17 @@ import { RelayPool } from 'applesauce-relay' import { IEventStore } from 'applesauce-core' import { createEventLoader } from 'applesauce-loaders/loaders' import { NostrEvent } from 'nostr-tools' -import { BehaviorSubject, Observable } from 'rxjs' - -type EventCallback = (event: NostrEvent) => void -type ErrorCallback = (error: Error) => void +import { Observable } from 'rxjs' /** - * Centralized event manager for fetching and caching events - * Handles deduplication of requests and provides a single source of truth + * Centralized event manager for event fetching coordination + * Manages initialization and provides utilities for event loading */ class EventManager { private eventStore: IEventStore | null = null private relayPool: RelayPool | null = null private eventLoader: ReturnType | null = null - // Track pending requests to avoid duplicates - private pendingRequests = new Map>() - - // Event stream for real-time updates - private eventSubject = new BehaviorSubject(null) - /** * Initialize the event manager with event store and relay pool */ @@ -29,7 +20,8 @@ class EventManager { this.eventStore = eventStore this.relayPool = relayPool - if (relayPool && this.eventLoader === null) { + // Recreate loader when services change + if (relayPool) { this.eventLoader = createEventLoader(relayPool, { eventStore: eventStore || undefined }) @@ -37,98 +29,40 @@ class EventManager { } /** - * Fetch an event by ID, with automatic deduplication and caching + * Get the event loader for fetching events */ - async fetchEvent(eventId: string): Promise { - // Check cache first - if (this.eventStore) { - const cached = this.eventStore.getEvent(eventId) - if (cached) { - return cached - } - } - - // Return a promise that will be resolved when the event is fetched - return new Promise((resolve, reject) => { - this.fetchEventAsync(eventId, resolve, reject) - }) + getEventLoader(): ReturnType | null { + return this.eventLoader } /** - * Subscribe to event fetching with callbacks + * Get the event store */ - private fetchEventAsync( - eventId: string, - onSuccess: EventCallback, - onError: ErrorCallback - ): void { - // Check if we're already fetching this event - if (this.pendingRequests.has(eventId)) { - // Add to existing request queue - this.pendingRequests.get(eventId)!.push({ onSuccess, onError }) - return - } - - // Start a new fetch request - this.pendingRequests.set(eventId, [{ onSuccess, onError }]) - - // If no relay pool yet, wait for it - if (!this.relayPool || !this.eventLoader) { - // Will retry when services are set - setTimeout(() => { - // Retry if still no pool - if (!this.relayPool) { - this.retryPendingRequest(eventId) - } - }, 1000) - return - } - - const subscription = this.eventLoader({ id: eventId }).subscribe({ - next: (event: NostrEvent) => { - // Call all pending callbacks - const callbacks = this.pendingRequests.get(eventId) || [] - this.pendingRequests.delete(eventId) - - callbacks.forEach(cb => cb.onSuccess(event)) - - // Emit to stream - this.eventSubject.next(event) - - subscription.unsubscribe() - }, - error: (err: unknown) => { - // Call all pending callbacks with error - const callbacks = this.pendingRequests.get(eventId) || [] - this.pendingRequests.delete(eventId) - - const error = err instanceof Error ? err : new Error(String(err)) - callbacks.forEach(cb => cb.onError(error)) - - subscription.unsubscribe() - } - }) + getEventStore(): IEventStore | null { + return this.eventStore } /** - * Retry pending requests after delay (useful when relay pool becomes available) + * Get the relay pool */ - private retryPendingRequest(eventId: string): void { - const callbacks = this.pendingRequests.get(eventId) - if (!callbacks) return - - // Re-trigger the fetch - this.pendingRequests.delete(eventId) - if (callbacks.length > 0) { - this.fetchEventAsync(eventId, callbacks[0].onSuccess, callbacks[0].onError) - } + getRelayPool(): RelayPool | null { + return this.relayPool } /** - * Get the event stream for reactive updates + * Check if event exists in store and return it if available */ - getEventStream(): Observable { - return this.eventSubject.asObservable() + getCachedEvent(eventId: string): NostrEvent | null { + if (!this.eventStore) return null + return this.eventStore.getEvent(eventId) || null + } + + /** + * Fetch event by ID, returning an observable + */ + fetchEvent(eventId: string): Observable | null { + if (!this.eventLoader) return null + return this.eventLoader({ id: eventId }) } }