mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 06:34:24 +01:00
refactor: remove timeouts and make profile fetching reactive
- Add optional onEvent callback to fetchProfiles (following queryEvents pattern) - Remove all timeouts - rely entirely on EOSE signals - Update useProfileLabels to use reactive streaming callback - Labels update progressively as profiles arrive from relays - Remove unused timer/takeUntil imports - Backwards compatible: other callers of fetchProfiles still work This follows the controller pattern from fetching-data-with-controllers rule: 'Since we are streaming results, we should NEVER use timeouts for fetching data. We should always rely on EOSE.'
This commit is contained in:
@@ -3,6 +3,7 @@ import { Hooks } from 'applesauce-react'
|
|||||||
import { Helpers, IEventStore } from 'applesauce-core'
|
import { Helpers, IEventStore } from 'applesauce-core'
|
||||||
import { getContentPointers } from 'applesauce-factory/helpers'
|
import { getContentPointers } from 'applesauce-factory/helpers'
|
||||||
import { RelayPool } from 'applesauce-relay'
|
import { RelayPool } from 'applesauce-relay'
|
||||||
|
import { NostrEvent } from 'nostr-tools'
|
||||||
import { fetchProfiles, loadCachedProfiles } from '../services/profileService'
|
import { fetchProfiles, loadCachedProfiles } from '../services/profileService'
|
||||||
import { getNpubFallbackDisplay } from '../utils/nostrUriResolver'
|
import { getNpubFallbackDisplay } from '../utils/nostrUriResolver'
|
||||||
|
|
||||||
@@ -170,81 +171,62 @@ export function useProfileLabels(content: string, relayPool?: RelayPool | null):
|
|||||||
console.log(`[profile-labels] Profiles to fetch: ${pubkeysToFetch.length}`, pubkeysToFetch.map(p => p.slice(0, 16) + '...'))
|
console.log(`[profile-labels] Profiles to fetch: ${pubkeysToFetch.length}`, pubkeysToFetch.map(p => p.slice(0, 16) + '...'))
|
||||||
setProfileLabels(new Map(labels))
|
setProfileLabels(new Map(labels))
|
||||||
|
|
||||||
// Fetch missing profiles asynchronously
|
// Fetch missing profiles asynchronously with reactive updates
|
||||||
if (pubkeysToFetch.length > 0 && relayPool && eventStore) {
|
if (pubkeysToFetch.length > 0 && relayPool && eventStore) {
|
||||||
const pubkeysToFetchSet = new Set(pubkeysToFetch)
|
const pubkeysToFetchSet = new Set(pubkeysToFetch)
|
||||||
console.log(`[profile-labels] Fetching ${pubkeysToFetch.length} profiles from relays`)
|
// Create a map from pubkey to encoded identifier for quick lookup
|
||||||
console.log(`[profile-labels] Calling fetchProfiles with relayPool and ${pubkeysToFetch.length} pubkeys`)
|
const pubkeyToEncoded = new Map<string, string>()
|
||||||
fetchProfiles(relayPool, eventStore as unknown as IEventStore, pubkeysToFetch)
|
|
||||||
.then((fetchedProfiles) => {
|
|
||||||
console.log(`[profile-labels] Fetch completed, received ${fetchedProfiles.length} profiles`)
|
|
||||||
const updatedLabels = new Map(labels)
|
|
||||||
const fetchedProfilesByPubkey = new Map(fetchedProfiles.map(p => [p.pubkey, p]))
|
|
||||||
|
|
||||||
profileData.forEach(({ encoded, pubkey }) => {
|
profileData.forEach(({ encoded, pubkey }) => {
|
||||||
// Only update profiles that were in pubkeysToFetch (i.e., were being fetched)
|
|
||||||
// This allows us to replace fallback labels with resolved names
|
|
||||||
if (pubkeysToFetchSet.has(pubkey)) {
|
if (pubkeysToFetchSet.has(pubkey)) {
|
||||||
console.log(`[profile-labels] Processing fetched profile for ${encoded.slice(0, 20)}...`)
|
pubkeyToEncoded.set(pubkey, encoded)
|
||||||
// First, try to use the profile from the returned array
|
|
||||||
const fetchedProfile = fetchedProfilesByPubkey.get(pubkey)
|
|
||||||
if (fetchedProfile) {
|
|
||||||
console.log(`[profile-labels] Found profile in fetch results for ${encoded.slice(0, 20)}...`)
|
|
||||||
try {
|
|
||||||
const profileData = JSON.parse(fetchedProfile.content || '{}') as { name?: string; display_name?: string; nip05?: string }
|
|
||||||
const displayName = profileData.display_name || profileData.name || profileData.nip05
|
|
||||||
if (displayName) {
|
|
||||||
updatedLabels.set(encoded, `@${displayName}`)
|
|
||||||
console.log(`[profile-labels] Updated label for ${encoded.slice(0, 20)}... to @${displayName}`)
|
|
||||||
} else {
|
|
||||||
// Use fallback npub display if profile has no name
|
|
||||||
const fallback = getNpubFallbackDisplay(pubkey)
|
|
||||||
updatedLabels.set(encoded, fallback)
|
|
||||||
console.log(`[profile-labels] Fetched profile for ${encoded.slice(0, 20)}... has no name, using fallback: ${fallback}`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Use fallback npub display if parsing fails
|
|
||||||
const fallback = getNpubFallbackDisplay(pubkey)
|
|
||||||
updatedLabels.set(encoded, fallback)
|
|
||||||
console.warn(`[profile-labels] Error parsing fetched profile for ${encoded.slice(0, 20)}..., using fallback:`, error)
|
|
||||||
}
|
|
||||||
} else if (eventStore) {
|
|
||||||
console.log(`[profile-labels] Profile not in fetch results, checking eventStore for ${encoded.slice(0, 20)}...`)
|
|
||||||
// Fallback: check eventStore (in case fetchProfiles stored but didn't return)
|
|
||||||
const profileEvent = eventStore.getEvent(pubkey + ':0')
|
|
||||||
if (profileEvent) {
|
|
||||||
console.log(`[profile-labels] Found profile in eventStore after fetch for ${encoded.slice(0, 20)}...`)
|
|
||||||
try {
|
|
||||||
const profileData = JSON.parse(profileEvent.content || '{}') as { name?: string; display_name?: string; nip05?: string }
|
|
||||||
const displayName = profileData.display_name || profileData.name || profileData.nip05
|
|
||||||
if (displayName) {
|
|
||||||
updatedLabels.set(encoded, `@${displayName}`)
|
|
||||||
console.log(`[profile-labels] Updated label from eventStore for ${encoded.slice(0, 20)}... to @${displayName}`)
|
|
||||||
} else {
|
|
||||||
// Use fallback npub display if profile has no name
|
|
||||||
const fallback = getNpubFallbackDisplay(pubkey)
|
|
||||||
updatedLabels.set(encoded, fallback)
|
|
||||||
console.log(`[profile-labels] Profile in eventStore for ${encoded.slice(0, 20)}... has no name, using fallback: ${fallback}`)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Use fallback npub display if parsing fails
|
|
||||||
const fallback = getNpubFallbackDisplay(pubkey)
|
|
||||||
updatedLabels.set(encoded, fallback)
|
|
||||||
console.warn(`[profile-labels] Error parsing eventStore profile for ${encoded.slice(0, 20)}..., using fallback:`, error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`[profile-labels] Profile not in eventStore after fetch for ${encoded.slice(0, 20)}..., keeping fallback`)
|
|
||||||
}
|
|
||||||
// If no profile found in eventStore, keep existing fallback
|
|
||||||
} else {
|
|
||||||
console.log(`[profile-labels] No eventStore available, keeping fallback for ${encoded.slice(0, 20)}...`)
|
|
||||||
}
|
|
||||||
// If no eventStore, keep existing fallback
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(`[profile-labels] Final labels after fetch:`, Array.from(updatedLabels.entries()).map(([enc, label]) => ({ encoded: enc.slice(0, 20) + '...', label })))
|
console.log(`[profile-labels] Fetching ${pubkeysToFetch.length} profiles from relays`)
|
||||||
setProfileLabels(updatedLabels)
|
console.log(`[profile-labels] Calling fetchProfiles with relayPool and ${pubkeysToFetch.length} pubkeys`)
|
||||||
|
|
||||||
|
// Reactive callback: update labels as profiles stream in
|
||||||
|
const handleProfileEvent = (event: NostrEvent) => {
|
||||||
|
const encoded = pubkeyToEncoded.get(event.pubkey)
|
||||||
|
if (!encoded) {
|
||||||
|
console.log(`[profile-labels] Received profile for unknown pubkey ${event.pubkey.slice(0, 16)}..., skipping`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[profile-labels] Received profile event for ${encoded.slice(0, 20)}...`)
|
||||||
|
setProfileLabels(prevLabels => {
|
||||||
|
const updatedLabels = new Map(prevLabels)
|
||||||
|
try {
|
||||||
|
const profileData = JSON.parse(event.content || '{}') as { name?: string; display_name?: string; nip05?: string }
|
||||||
|
const displayName = profileData.display_name || profileData.name || profileData.nip05
|
||||||
|
if (displayName) {
|
||||||
|
updatedLabels.set(encoded, `@${displayName}`)
|
||||||
|
console.log(`[profile-labels] Updated label reactively for ${encoded.slice(0, 20)}... to @${displayName}`)
|
||||||
|
} else {
|
||||||
|
// Use fallback npub display if profile has no name
|
||||||
|
const fallback = getNpubFallbackDisplay(event.pubkey)
|
||||||
|
updatedLabels.set(encoded, fallback)
|
||||||
|
console.log(`[profile-labels] Profile for ${encoded.slice(0, 20)}... has no name, keeping fallback: ${fallback}`)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Use fallback npub display if parsing fails
|
||||||
|
const fallback = getNpubFallbackDisplay(event.pubkey)
|
||||||
|
updatedLabels.set(encoded, fallback)
|
||||||
|
console.warn(`[profile-labels] Error parsing profile for ${encoded.slice(0, 20)}..., using fallback:`, error)
|
||||||
|
}
|
||||||
|
return updatedLabels
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchProfiles(relayPool, eventStore as unknown as IEventStore, pubkeysToFetch, undefined, handleProfileEvent)
|
||||||
|
.then((fetchedProfiles) => {
|
||||||
|
console.log(`[profile-labels] Fetch completed (EOSE), received ${fetchedProfiles.length} profiles total`)
|
||||||
|
// Labels have already been updated reactively via handleProfileEvent
|
||||||
|
// Just log final state for debugging
|
||||||
|
setProfileLabels(prevLabels => {
|
||||||
|
console.log(`[profile-labels] Final labels after EOSE:`, Array.from(prevLabels.entries()).map(([enc, label]) => ({ encoded: enc.slice(0, 20) + '...', label })))
|
||||||
|
return prevLabels // No change needed, already updated reactively
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(`[profile-labels] Error fetching profiles:`, error)
|
console.error(`[profile-labels] Error fetching profiles:`, error)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RelayPool, completeOnEose, onlyEvents } from 'applesauce-relay'
|
import { RelayPool, completeOnEose, onlyEvents } from 'applesauce-relay'
|
||||||
import { lastValueFrom, merge, Observable, takeUntil, timer, toArray, tap } from 'rxjs'
|
import { lastValueFrom, merge, Observable, toArray, tap } from 'rxjs'
|
||||||
import { NostrEvent } from 'nostr-tools'
|
import { NostrEvent } from 'nostr-tools'
|
||||||
import { IEventStore } from 'applesauce-core'
|
import { IEventStore } from 'applesauce-core'
|
||||||
import { prioritizeLocalRelays, partitionRelays } from '../utils/helpers'
|
import { prioritizeLocalRelays, partitionRelays } from '../utils/helpers'
|
||||||
@@ -190,7 +190,8 @@ export const fetchProfiles = async (
|
|||||||
relayPool: RelayPool,
|
relayPool: RelayPool,
|
||||||
eventStore: IEventStore,
|
eventStore: IEventStore,
|
||||||
pubkeys: string[],
|
pubkeys: string[],
|
||||||
settings?: UserSettings
|
settings?: UserSettings,
|
||||||
|
onEvent?: (event: NostrEvent) => void
|
||||||
): Promise<NostrEvent[]> => {
|
): Promise<NostrEvent[]> => {
|
||||||
try {
|
try {
|
||||||
if (pubkeys.length === 0) {
|
if (pubkeys.length === 0) {
|
||||||
@@ -269,9 +270,9 @@ export const fetchProfiles = async (
|
|||||||
.req(localRelays, { kinds: [0], authors: pubkeysToFetch })
|
.req(localRelays, { kinds: [0], authors: pubkeysToFetch })
|
||||||
.pipe(
|
.pipe(
|
||||||
onlyEvents(),
|
onlyEvents(),
|
||||||
|
onEvent ? tap((event: NostrEvent) => onEvent(event)) : tap(() => {}),
|
||||||
tap((event: NostrEvent) => processEvent(event)),
|
tap((event: NostrEvent) => processEvent(event)),
|
||||||
completeOnEose(),
|
completeOnEose()
|
||||||
takeUntil(timer(1200))
|
|
||||||
)
|
)
|
||||||
: new Observable<NostrEvent>((sub) => sub.complete())
|
: new Observable<NostrEvent>((sub) => sub.complete())
|
||||||
|
|
||||||
@@ -280,9 +281,9 @@ export const fetchProfiles = async (
|
|||||||
.req(remoteRelays, { kinds: [0], authors: pubkeysToFetch })
|
.req(remoteRelays, { kinds: [0], authors: pubkeysToFetch })
|
||||||
.pipe(
|
.pipe(
|
||||||
onlyEvents(),
|
onlyEvents(),
|
||||||
|
onEvent ? tap((event: NostrEvent) => onEvent(event)) : tap(() => {}),
|
||||||
tap((event: NostrEvent) => processEvent(event)),
|
tap((event: NostrEvent) => processEvent(event)),
|
||||||
completeOnEose(),
|
completeOnEose()
|
||||||
takeUntil(timer(10000)) // Increased from 6000ms to 10000ms to give slow relays more time
|
|
||||||
)
|
)
|
||||||
: new Observable<NostrEvent>((sub) => sub.complete())
|
: new Observable<NostrEvent>((sub) => sub.complete())
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user