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 })
}
}