refactor: improve event and profile retrieval methods
This commit is contained in:
@@ -32,7 +32,7 @@ export default function ImageGallery({
|
|||||||
<Image
|
<Image
|
||||||
key={index}
|
key={index}
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-lg cursor-pointer z-0',
|
'rounded-lg cursor-pointer z-0 object-cover',
|
||||||
size === 'small' ? 'h-[15vh]' : 'h-[30vh]'
|
size === 'small' ? 'h-[15vh]' : 'h-[30vh]'
|
||||||
)}
|
)}
|
||||||
src={src}
|
src={src}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import client from '@renderer/services/client.service'
|
import client from '@renderer/services/client.service'
|
||||||
import { Event, Filter, nip19 } from 'nostr-tools'
|
import { Event } from 'nostr-tools'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
export function useFetchEventById(id?: string) {
|
export function useFetchEventById(id?: string) {
|
||||||
@@ -15,45 +15,16 @@ export function useFetchEventById(id?: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter: Filter | undefined
|
try {
|
||||||
if (/^[0-9a-f]{64}$/.test(id)) {
|
const event = await client.fetchEventByBench32Id(id)
|
||||||
filter = { ids: [id] }
|
if (event) {
|
||||||
} else {
|
setEvent(event)
|
||||||
const { type, data } = nip19.decode(id)
|
|
||||||
switch (type) {
|
|
||||||
case 'note':
|
|
||||||
filter = { ids: [data] }
|
|
||||||
break
|
|
||||||
case 'nevent':
|
|
||||||
filter = { ids: [data.id] }
|
|
||||||
break
|
|
||||||
case 'naddr':
|
|
||||||
filter = {
|
|
||||||
authors: [data.pubkey],
|
|
||||||
kinds: [data.kind],
|
|
||||||
limit: 1
|
|
||||||
}
|
|
||||||
if (data.identifier) {
|
|
||||||
filter['#d'] = [data.identifier]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} catch (error) {
|
||||||
if (!filter) {
|
setError(error as Error)
|
||||||
|
} finally {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
setError(new Error('Invalid id'))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let event: Event | undefined
|
|
||||||
if (filter.ids) {
|
|
||||||
event = await client.fetchEventById(filter.ids[0])
|
|
||||||
} else {
|
|
||||||
event = await client.fetchEventByFilter(filter)
|
|
||||||
}
|
|
||||||
if (event) {
|
|
||||||
setEvent(event)
|
|
||||||
}
|
|
||||||
setIsFetching(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchEvent().catch((err) => {
|
fetchEvent().catch((err) => {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { formatPubkey } from '@renderer/lib/pubkey'
|
|
||||||
import client from '@renderer/services/client.service'
|
import client from '@renderer/services/client.service'
|
||||||
import { TProfile } from '@renderer/types'
|
import { TProfile } from '@renderer/types'
|
||||||
import { nip19 } from 'nostr-tools'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
export function useFetchProfile(id?: string) {
|
export function useFetchProfile(id?: string) {
|
||||||
@@ -11,7 +9,6 @@ export function useFetchProfile(id?: string) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProfile = async () => {
|
const fetchProfile = async () => {
|
||||||
let pubkey: string | undefined
|
|
||||||
try {
|
try {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
@@ -19,39 +16,13 @@ export function useFetchProfile(id?: string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/^[0-9a-f]{64}$/.test(id)) {
|
const profile = await client.fetchProfileByBench32Id(id)
|
||||||
pubkey = id
|
|
||||||
} else {
|
|
||||||
const { data, type } = nip19.decode(id)
|
|
||||||
switch (type) {
|
|
||||||
case 'npub':
|
|
||||||
pubkey = data
|
|
||||||
break
|
|
||||||
case 'nprofile':
|
|
||||||
pubkey = data.pubkey
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pubkey) {
|
|
||||||
setIsFetching(false)
|
|
||||||
setError(new Error('Invalid id'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = await client.fetchProfile(pubkey)
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
setProfile(profile)
|
setProfile(profile)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err as Error)
|
setError(err as Error)
|
||||||
} finally {
|
} finally {
|
||||||
if (pubkey) {
|
|
||||||
setProfile((pre) => {
|
|
||||||
if (pre) return pre
|
|
||||||
return { pubkey, username: formatPubkey(pubkey!) } as TProfile
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setIsFetching(false)
|
setIsFetching(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,18 +55,8 @@ export async function extractMentions(content: string, parentEvent?: Event) {
|
|||||||
pubkeySet.add(data.pubkey)
|
pubkeySet.add(data.pubkey)
|
||||||
} else if (type === 'npub') {
|
} else if (type === 'npub') {
|
||||||
pubkeySet.add(data)
|
pubkeySet.add(data)
|
||||||
} else if (type === 'nevent') {
|
} else if (['nevent', 'note', 'naddr'].includes(type)) {
|
||||||
eventIdSet.add(data.id)
|
const event = await client.fetchEventByBench32Id(id)
|
||||||
if (data.author) {
|
|
||||||
pubkeySet.add(data.author)
|
|
||||||
} else {
|
|
||||||
const event = await client.fetchEventById(data.id)
|
|
||||||
if (event) {
|
|
||||||
pubkeySet.add(event.pubkey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (type === 'note') {
|
|
||||||
const event = await client.fetchEventById(data)
|
|
||||||
if (event) {
|
if (event) {
|
||||||
pubkeySet.add(event.pubkey)
|
pubkeySet.add(event.pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Filter,
|
Filter,
|
||||||
kinds,
|
kinds,
|
||||||
Event as NEvent,
|
Event as NEvent,
|
||||||
|
nip19,
|
||||||
SimplePool,
|
SimplePool,
|
||||||
VerifiedEvent
|
VerifiedEvent
|
||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
@@ -30,24 +31,23 @@ class ClientService {
|
|||||||
private relayUrls: string[] = BIG_RELAY_URLS
|
private relayUrls: string[] = BIG_RELAY_URLS
|
||||||
private initPromise!: Promise<void>
|
private initPromise!: Promise<void>
|
||||||
|
|
||||||
private eventByFilterCache = new LRUCache<string, Promise<NEvent | undefined>>({
|
private eventCache = new LRUCache<string, Promise<NEvent | undefined>>({ max: 10000 })
|
||||||
max: 10000,
|
private eventDataLoader = new DataLoader<string, NEvent | undefined>(
|
||||||
fetchMethod: async (filterStr) => {
|
(ids) => Promise.all(ids.map((id) => this._fetchEventByBench32Id(id))),
|
||||||
const events = await this.fetchEvents(BIG_RELAY_URLS, JSON.parse(filterStr))
|
{ cacheMap: this.eventCache }
|
||||||
events.forEach((event) => this.addEventToCache(event))
|
|
||||||
return events.sort((a, b) => b.created_at - a.created_at)[0]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
private eventByIdCache = new LRUCache<string, Promise<NEvent | undefined>>({ max: 10000 })
|
|
||||||
private eventDataloader = new DataLoader<string, NEvent | undefined>(
|
|
||||||
this.eventBatchLoadFn.bind(this),
|
|
||||||
{ cacheMap: this.eventByIdCache }
|
|
||||||
)
|
)
|
||||||
|
private fetchEventFromBigRelaysDataloader = new DataLoader<string, NEvent | undefined>(
|
||||||
|
this.eventBatchLoadFn.bind(this),
|
||||||
|
{ cache: false }
|
||||||
|
)
|
||||||
|
private profileCache = new LRUCache<string, Promise<TProfile | undefined>>({ max: 10000 })
|
||||||
private profileDataloader = new DataLoader<string, TProfile | undefined>(
|
private profileDataloader = new DataLoader<string, TProfile | undefined>(
|
||||||
|
(ids) => Promise.all(ids.map((id) => this._fetchProfileByBench32Id(id))),
|
||||||
|
{ cacheMap: this.profileCache }
|
||||||
|
)
|
||||||
|
private fetchProfileFromBigRelaysDataloader = new DataLoader<string, TProfile>(
|
||||||
this.profileBatchLoadFn.bind(this),
|
this.profileBatchLoadFn.bind(this),
|
||||||
{
|
{ cache: false }
|
||||||
cacheMap: new LRUCache<string, Promise<TProfile | undefined>>({ max: 10000 })
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
private relayListDataLoader = new DataLoader<string, TRelayList>(
|
private relayListDataLoader = new DataLoader<string, TRelayList>(
|
||||||
this.relayListBatchLoadFn.bind(this),
|
this.relayListBatchLoadFn.bind(this),
|
||||||
@@ -127,7 +127,7 @@ class ClientService {
|
|||||||
} else {
|
} else {
|
||||||
events.push(evt)
|
events.push(evt)
|
||||||
}
|
}
|
||||||
that.eventByIdCache.set(evt.id, Promise.resolve(evt))
|
that.eventDataLoader.prime(evt.id, Promise.resolve(evt))
|
||||||
},
|
},
|
||||||
onclose(reason: string) {
|
onclose(reason: string) {
|
||||||
if (reason.startsWith('auth-required:')) {
|
if (reason.startsWith('auth-required:')) {
|
||||||
@@ -171,20 +171,16 @@ class ClientService {
|
|||||||
return await this.pool.querySync(relayUrls.length > 0 ? relayUrls : BIG_RELAY_URLS, filter)
|
return await this.pool.querySync(relayUrls.length > 0 ? relayUrls : BIG_RELAY_URLS, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchEventByFilter(filter: Filter) {
|
async fetchEventByBench32Id(id: string): Promise<NEvent | undefined> {
|
||||||
return this.eventByFilterCache.fetch(JSON.stringify({ ...filter, limit: 1 }))
|
return this.eventDataLoader.load(id)
|
||||||
}
|
|
||||||
|
|
||||||
async fetchEventById(id: string): Promise<NEvent | undefined> {
|
|
||||||
return this.eventDataloader.load(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventToCache(event: NEvent) {
|
addEventToCache(event: NEvent) {
|
||||||
this.eventByIdCache.set(event.id, Promise.resolve(event))
|
this.eventDataLoader.prime(event.id, Promise.resolve(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchProfile(pubkey: string): Promise<TProfile | undefined> {
|
async fetchProfileByBench32Id(id: string): Promise<TProfile | undefined> {
|
||||||
return this.profileDataloader.load(pubkey)
|
return this.profileDataloader.load(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchRelayList(pubkey: string): Promise<TRelayList> {
|
async fetchRelayList(pubkey: string): Promise<TRelayList> {
|
||||||
@@ -199,9 +195,129 @@ class ClientService {
|
|||||||
this.followListCache.set(pubkey, Promise.resolve(event))
|
this.followListCache.set(pubkey, Promise.resolve(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchEventById(relayUrls: string[], id: string): Promise<NEvent | undefined> {
|
||||||
|
const event = await this.fetchEventFromBigRelaysDataloader.load(id)
|
||||||
|
if (event) {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.tryHarderToFetchEvent(relayUrls, { ids: [id], limit: 1 }, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchEventByBench32Id(id: string): Promise<NEvent | undefined> {
|
||||||
|
let filter: Filter | undefined
|
||||||
|
let relays: string[] = []
|
||||||
|
if (/^[0-9a-f]{64}$/.test(id)) {
|
||||||
|
filter = { ids: [id] }
|
||||||
|
} else {
|
||||||
|
const { type, data } = nip19.decode(id)
|
||||||
|
switch (type) {
|
||||||
|
case 'note':
|
||||||
|
filter = { ids: [data] }
|
||||||
|
break
|
||||||
|
case 'nevent':
|
||||||
|
filter = { ids: [data.id] }
|
||||||
|
if (data.relays) relays = data.relays
|
||||||
|
break
|
||||||
|
case 'naddr':
|
||||||
|
filter = {
|
||||||
|
authors: [data.pubkey],
|
||||||
|
kinds: [data.kind],
|
||||||
|
limit: 1
|
||||||
|
}
|
||||||
|
if (data.identifier) {
|
||||||
|
filter['#d'] = [data.identifier]
|
||||||
|
}
|
||||||
|
if (data.relays) relays = data.relays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!filter) {
|
||||||
|
throw new Error('Invalid id')
|
||||||
|
}
|
||||||
|
|
||||||
|
let event: NEvent | undefined
|
||||||
|
if (filter.ids) {
|
||||||
|
event = await this.fetchEventById(relays, filter.ids[0])
|
||||||
|
} else {
|
||||||
|
event = await this.tryHarderToFetchEvent(relays, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event && event.id !== id) {
|
||||||
|
this.eventDataLoader.prime(event.id, Promise.resolve(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchProfileByBench32Id(id: string): Promise<TProfile> {
|
||||||
|
let pubkey: string | undefined
|
||||||
|
let relays: string[] = []
|
||||||
|
if (/^[0-9a-f]{64}$/.test(id)) {
|
||||||
|
pubkey = id
|
||||||
|
} else {
|
||||||
|
const { data, type } = nip19.decode(id)
|
||||||
|
switch (type) {
|
||||||
|
case 'npub':
|
||||||
|
pubkey = data
|
||||||
|
break
|
||||||
|
case 'nprofile':
|
||||||
|
pubkey = data.pubkey
|
||||||
|
if (data.relays) relays = data.relays
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pubkey) {
|
||||||
|
throw new Error('Invalid id')
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileFromBigRelays = this.fetchProfileFromBigRelaysDataloader.load(pubkey)
|
||||||
|
if (profileFromBigRelays) {
|
||||||
|
return profileFromBigRelays
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileEvent = await this.tryHarderToFetchEvent(
|
||||||
|
relays,
|
||||||
|
{
|
||||||
|
authors: [pubkey],
|
||||||
|
kinds: [kinds.Metadata],
|
||||||
|
limit: 1
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
const profile = profileEvent
|
||||||
|
? this.parseProfileFromEvent(profileEvent)
|
||||||
|
: { pubkey, username: formatPubkey(pubkey) }
|
||||||
|
|
||||||
|
if (profile.pubkey !== id) {
|
||||||
|
this.profileCache.set(profile.pubkey, Promise.resolve(profile))
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tryHarderToFetchEvent(
|
||||||
|
relayUrls: string[],
|
||||||
|
filter: Filter,
|
||||||
|
alreadyFetchedFromBigRelays = false
|
||||||
|
) {
|
||||||
|
if (!relayUrls.length && filter.authors?.length) {
|
||||||
|
const relayList = await this.fetchRelayList(filter.authors[0])
|
||||||
|
relayUrls = alreadyFetchedFromBigRelays
|
||||||
|
? relayList.write.filter((url) => !BIG_RELAY_URLS.includes(url)).slice(0, 4)
|
||||||
|
: relayList.write.slice(0, 4)
|
||||||
|
} else if (!relayUrls.length && !alreadyFetchedFromBigRelays) {
|
||||||
|
relayUrls = BIG_RELAY_URLS
|
||||||
|
}
|
||||||
|
if (!relayUrls.length) return
|
||||||
|
|
||||||
|
const events = await this.fetchEvents(relayUrls, filter)
|
||||||
|
return events.sort((a, b) => b.created_at - a.created_at)[0]
|
||||||
|
}
|
||||||
|
|
||||||
private async eventBatchLoadFn(ids: readonly string[]) {
|
private async eventBatchLoadFn(ids: readonly string[]) {
|
||||||
const events = await this.fetchEvents(BIG_RELAY_URLS, {
|
const events = await this.fetchEvents(BIG_RELAY_URLS, {
|
||||||
ids: ids as string[],
|
ids: Array.from(new Set(ids)),
|
||||||
limit: ids.length
|
limit: ids.length
|
||||||
})
|
})
|
||||||
const eventsMap = new Map<string, NEvent>()
|
const eventsMap = new Map<string, NEvent>()
|
||||||
@@ -214,7 +330,7 @@ class ClientService {
|
|||||||
|
|
||||||
private async profileBatchLoadFn(pubkeys: readonly string[]) {
|
private async profileBatchLoadFn(pubkeys: readonly string[]) {
|
||||||
const events = await this.fetchEvents(BIG_RELAY_URLS, {
|
const events = await this.fetchEvents(BIG_RELAY_URLS, {
|
||||||
authors: pubkeys as string[],
|
authors: Array.from(new Set(pubkeys)),
|
||||||
kinds: [kinds.Metadata],
|
kinds: [kinds.Metadata],
|
||||||
limit: pubkeys.length
|
limit: pubkeys.length
|
||||||
})
|
})
|
||||||
@@ -229,7 +345,7 @@ class ClientService {
|
|||||||
|
|
||||||
return pubkeys.map((pubkey) => {
|
return pubkeys.map((pubkey) => {
|
||||||
const event = eventsMap.get(pubkey)
|
const event = eventsMap.get(pubkey)
|
||||||
return event ? this.parseProfileFromEvent(event) : undefined
|
return event ? this.parseProfileFromEvent(event) : { pubkey, username: formatPubkey(pubkey) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user