perf(explore): stream contacts + early posts from local; merge remote later

This commit is contained in:
Gigi
2025-10-12 22:43:35 +02:00
parent e3debfa5df
commit 31f7d53829
2 changed files with 78 additions and 49 deletions

View File

@@ -31,7 +31,44 @@ const Explore: React.FC<ExploreProps> = ({ relayPool }) => {
setError(null)
// Fetch the user's contacts (friends)
const contacts = await fetchContacts(relayPool, activeAccount.pubkey)
const contacts = await fetchContacts(
relayPool,
activeAccount.pubkey,
(partial) => {
// When local contacts are available, kick off early posts fetch
if (partial.size > 0) {
const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url)
fetchBlogPostsFromAuthors(
relayPool,
Array.from(partial),
relayUrls,
(post) => {
setBlogPosts((prev) => {
const exists = prev.some(p => p.event.id === post.event.id)
if (exists) return prev
const next = [...prev, post]
return next.sort((a, b) => {
const timeA = a.published || a.event.created_at
const timeB = b.published || b.event.created_at
return timeB - timeA
})
})
}
).then((all) => {
// Ensure union of streamed + final is displayed
setBlogPosts((prev) => {
const byId = new Map(prev.map(p => [p.event.id, p]))
for (const post of all) byId.set(post.event.id, post)
return Array.from(byId.values()).sort((a, b) => {
const timeA = a.published || a.event.created_at
const timeB = b.published || b.event.created_at
return timeB - timeA
})
})
})
}
}
)
if (contacts.size === 0) {
setError('You are not following anyone yet. Follow some people to see their blog posts!')
@@ -39,29 +76,9 @@ const Explore: React.FC<ExploreProps> = ({ relayPool }) => {
return
}
// Get relay URLs from pool
// After full contacts, do a final pass for completeness
const relayUrls = Array.from(relayPool.relays.values()).map(relay => relay.url)
// Fetch blog posts from friends
const posts = await fetchBlogPostsFromAuthors(
relayPool,
Array.from(contacts),
relayUrls,
(post) => {
// Stream posts as we get them
setBlogPosts((prev) => {
const exists = prev.some(p => p.event.id === post.event.id)
if (exists) return prev
const next = [...prev, post]
// Keep sorted by published or created_at
return next.sort((a, b) => {
const timeA = a.published || a.event.created_at
const timeB = b.published || b.event.created_at
return timeB - timeA
})
})
}
)
const posts = await fetchBlogPostsFromAuthors(relayPool, Array.from(contacts), relayUrls)
if (posts.length === 0) {
setError('No blog posts found from your friends yet')

View File

@@ -10,7 +10,8 @@ import { prioritizeLocalRelays } from '../utils/helpers'
*/
export const fetchContacts = async (
relayPool: RelayPool,
pubkey: string
pubkey: string,
onPartial?: (contacts: Set<string>) => void
): Promise<Set<string>> => {
try {
const relayUrls = prioritizeLocalRelays(Array.from(relayPool.relays.values()).map(relay => relay.url))
@@ -31,35 +32,46 @@ export const fetchContacts = async (
events = []
}
}
if (events.length === 0) {
events = await lastValueFrom(
relayPool
.req(relayUrls, { kinds: [3], authors: [pubkey] })
.pipe(completeOnEose(), takeUntil(timer(6000)), toArray())
)
let followed = new Set<string>()
if (events.length > 0) {
// Get the most recent contact list
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at)
const contactList = sortedEvents[0]
// Extract pubkeys from 'p' tags
for (const tag of contactList.tags) {
if (tag[0] === 'p' && tag[1]) {
followed.add(tag[1])
}
}
if (onPartial) onPartial(new Set(followed))
}
// Always fetch remote to merge more contacts
const remoteRelays = relayUrls.filter(url => !url.includes('localhost') && !url.includes('127.0.0.1'))
if (remoteRelays.length > 0) {
try {
const remoteEvents = await lastValueFrom(
relayPool
.req(remoteRelays, { kinds: [3], authors: [pubkey] })
.pipe(completeOnEose(), takeUntil(timer(6000)), toArray())
)
if (remoteEvents.length > 0) {
const sortedEvents = remoteEvents.sort((a, b) => b.created_at - a.created_at)
const contactList = sortedEvents[0]
for (const tag of contactList.tags) {
if (tag[0] === 'p' && tag[1]) {
followed.add(tag[1])
}
}
}
} catch {
// ignore
}
}
console.log('📊 Contact events fetched:', events.length)
if (events.length === 0) {
return new Set()
}
// Get the most recent contact list
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at)
const contactList = sortedEvents[0]
// Extract pubkeys from 'p' tags
const followedPubkeys = new Set<string>()
for (const tag of contactList.tags) {
if (tag[0] === 'p' && tag[1]) {
followedPubkeys.add(tag[1])
}
}
console.log('👥 Followed contacts:', followedPubkeys.size)
return followedPubkeys
console.log('👥 Followed contacts:', followed.size)
return followed
} catch (error) {
console.error('Failed to fetch contacts:', error)
return new Set()