Browse Source

add tiered fetching for addressable events

imwald
Silberengel 5 months ago
parent
commit
eadbb1f109
  1. 186
      src/services/client.service.ts

186
src/services/client.service.ts

@ -991,47 +991,6 @@ class ClientService extends EventTarget {
} }
} }
private async fetchEventById(_relayUrls: string[], id: string): Promise<NEvent | undefined> {
// Get user's relay list if available
const userRelayList = this.pubkey ? await this.fetchRelayList(this.pubkey) : { read: [], write: [] }
// Tier 1: User's read relays + fast read relays (deduplicated)
const tier1Relays = Array.from(new Set([
...userRelayList.read,
...FAST_READ_RELAY_URLS
]))
const tier1Event = await this.tryHarderToFetchEvent(tier1Relays, { ids: [id], limit: 1 })
if (tier1Event) {
return tier1Event
}
// Tier 2: User's write relays + fast write relays (deduplicated)
const tier2Relays = Array.from(new Set([
...userRelayList.write,
...FAST_WRITE_RELAY_URLS
]))
const tier2Event = await this.tryHarderToFetchEvent(tier2Relays, { ids: [id], limit: 1 })
if (tier2Event) {
return tier2Event
}
// Tier 3: Search relays + big relays (deduplicated)
const tier3Relays = Array.from(new Set([
...SEARCHABLE_RELAY_URLS,
...BIG_RELAY_URLS
]))
const tier3Event = await this.tryHarderToFetchEvent(tier3Relays, { ids: [id], limit: 1 })
if (tier3Event) {
return tier3Event
}
// Tier 4: Not found - external relays require opt-in (see fetchEventWithExternalRelays)
return undefined
}
/** /**
* Get list of relays that were already tried in tiers 1-3 * Get list of relays that were already tried in tiers 1-3
*/ */
@ -1073,13 +1032,18 @@ class ClientService extends EventTarget {
let author: string | undefined let author: string | undefined
if (!/^[0-9a-f]{64}$/.test(id)) { if (!/^[0-9a-f]{64}$/.test(id)) {
const { type, data } = nip19.decode(id) try {
if (type === 'nevent') { const { type, data } = nip19.decode(id)
if (data.relays) relayHints = data.relays if (type === 'nevent') {
if (data.author) author = data.author if (data.relays) relayHints = data.relays
} else if (type === 'naddr') { if (data.author) author = data.author
if (data.relays) relayHints = data.relays } else if (type === 'naddr') {
author = data.pubkey if (data.relays) relayHints = data.relays
author = data.pubkey
}
} catch (err) {
console.error('Failed to decode bech32 ID:', id, err)
// Continue with empty relay hints and author
} }
} }
@ -1109,45 +1073,40 @@ class ClientService extends EventTarget {
private async _fetchEvent(id: string): Promise<NEvent | undefined> { private async _fetchEvent(id: string): Promise<NEvent | undefined> {
let filter: Filter | undefined let filter: Filter | undefined
let relays: string[] = []
if (/^[0-9a-f]{64}$/.test(id)) { if (/^[0-9a-f]{64}$/.test(id)) {
filter = { ids: [id] } filter = { ids: [id] }
} else { } else {
const { type, data } = nip19.decode(id) try {
switch (type) { const { type, data } = nip19.decode(id)
case 'note': switch (type) {
filter = { ids: [data] } case 'note':
break filter = { ids: [data] }
case 'nevent': break
filter = { ids: [data.id] } case 'nevent':
if (data.relays) relays = data.relays filter = { ids: [data.id] }
break break
case 'naddr': case 'naddr':
filter = { filter = {
authors: [data.pubkey], authors: [data.pubkey],
kinds: [data.kind], kinds: [data.kind],
limit: 1 limit: 1
} }
if (data.identifier) { if (data.identifier) {
filter['#d'] = [data.identifier] filter['#d'] = [data.identifier]
} }
if (data.relays) relays = data.relays }
} catch (err) {
console.error('Failed to decode bech32 ID - likely malformed:', id)
// Malformed naddr/nevent from broken clients - can't fetch it
return undefined
} }
} }
if (!filter) { if (!filter) {
throw new Error('Invalid id') throw new Error('Invalid id')
} }
let event: NEvent | undefined // Use unified tiered fetching for both regular and replaceable events
if (filter.ids) { const event = await this.fetchEventTiered(filter)
event = await this.fetchEventById(relays, filter.ids[0])
} else {
// Privacy: Don't fetch author's relays, use defaults
if (!relays.length) {
relays.push(...BIG_RELAY_URLS)
}
event = await this.tryHarderToFetchEvent(relays, filter)
}
if (event && event.id !== id) { if (event && event.id !== id) {
this.addEventToCache(event) this.addEventToCache(event)
@ -1156,24 +1115,71 @@ class ClientService extends EventTarget {
return event return event
} }
/**
* Unified tiered fetching for both regular and replaceable events
*/
private async fetchEventTiered(filter: Filter): Promise<NEvent | undefined> {
const userRelayList = this.pubkey ? await this.fetchRelayList(this.pubkey) : { read: [], write: [] }
// Tier 1: User's read relays + fast read relays (deduplicated)
const tier1Relays = Array.from(new Set([
...userRelayList.read,
...FAST_READ_RELAY_URLS
]))
const tier1Event = await this.tryHarderToFetchEvent(tier1Relays, filter)
if (tier1Event) { return tier1Event }
// Tier 2: User's write relays + fast write relays (deduplicated)
const tier2Relays = Array.from(new Set([
...userRelayList.write,
...FAST_WRITE_RELAY_URLS
]))
const tier2Event = await this.tryHarderToFetchEvent(tier2Relays, filter)
if (tier2Event) { return tier2Event }
// Tier 3: Search relays + big relays (deduplicated)
const tier3Relays = Array.from(new Set([
...SEARCHABLE_RELAY_URLS,
...BIG_RELAY_URLS
]))
const tier3Event = await this.tryHarderToFetchEvent(tier3Relays, filter)
if (tier3Event) { return tier3Event }
// Tier 4: Not found - external relays require opt-in (see fetchEventWithExternalRelays)
return undefined
}
private async tryHarderToFetchEvent( private async tryHarderToFetchEvent(
relayUrls: string[], relayUrls: string[],
filter: Filter, filter: Filter,
alreadyFetchedFromBigRelays = false alreadyFetchedFromBigRelays = false
) { ) {
// Privacy: Don't fetch author's relays, only use provided relays or defaults try {
if (!relayUrls.length && !alreadyFetchedFromBigRelays) { // Privacy: Don't fetch author's relays, only use provided relays or defaults
relayUrls = BIG_RELAY_URLS if (!relayUrls.length && !alreadyFetchedFromBigRelays) {
} relayUrls = BIG_RELAY_URLS
if (!relayUrls.length) return }
if (!relayUrls.length) return undefined
// Normalize relay URLs (remove trailing slashes for consistency) // Normalize relay URLs (remove trailing slashes for consistency)
const normalizedUrls = relayUrls.map(url => url.endsWith('/') ? url.slice(0, -1) : url) const normalizedUrls = relayUrls.map(url => url.endsWith('/') ? url.slice(0, -1) : url)
console.log(`Trying to fetch from ${normalizedUrls.length} relays:`, normalizedUrls) console.log(`Trying to fetch from ${normalizedUrls.length} relays:`, normalizedUrls)
const events = await this.query(normalizedUrls, filter) const events = await this.query(normalizedUrls, filter)
console.log(`Found ${events.length} events from relays`) console.log(`Found ${events.length} events from relays`)
return events.sort((a, b) => b.created_at - a.created_at)[0]
if (events.length === 0) {
console.log('No events found, continuing to next tier')
return undefined
}
const result = events.sort((a, b) => b.created_at - a.created_at)[0]
console.log('Found event:', result.id)
return result
} catch (error) {
console.error('Error in tryHarderToFetchEvent:', error)
return undefined
}
} }

Loading…
Cancel
Save