refactor: subscribe

This commit is contained in:
codytseng
2025-04-11 22:53:51 +08:00
parent 2a4968568a
commit 74d7f9be29
5 changed files with 157 additions and 188 deletions

View File

@@ -22,7 +22,7 @@ export default function Nip22ReplyNoteList({
className?: string className?: string
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { pubkey } = useNostr() const { pubkey, startLogin } = useNostr()
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined) const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
const [until, setUntil] = useState<number | undefined>(() => dayjs().unix()) const [until, setUntil] = useState<number | undefined>(() => dayjs().unix())
const [replies, setReplies] = useState<NEvent[]>([]) const [replies, setReplies] = useState<NEvent[]>([])
@@ -76,7 +76,9 @@ export default function Nip22ReplyNoteList({
}, },
{ {
onEvents: (evts, eosed) => { onEvents: (evts, eosed) => {
setReplies(evts.reverse()) if (evts.length > 0) {
setReplies(evts.reverse())
}
if (eosed) { if (eosed) {
setLoading(false) setLoading(false)
setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined) setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined)
@@ -85,6 +87,9 @@ export default function Nip22ReplyNoteList({
onNew: (evt) => { onNew: (evt) => {
onNewReply(evt) onNewReply(evt)
} }
},
{
startLogin
} }
) )
setTimelineKey(timelineKey) setTimelineKey(timelineKey)

View File

@@ -14,7 +14,7 @@ import relayInfoService from '@/services/relay-info.service'
import { TNoteListMode } from '@/types' import { TNoteListMode } from '@/types'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Event, Filter, kinds } from 'nostr-tools' import { Event, Filter, kinds } from 'nostr-tools'
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh' import PullToRefresh from 'react-simple-pull-to-refresh'
import NoteCard from '../NoteCard' import NoteCard from '../NoteCard'
@@ -47,7 +47,7 @@ export default function NoteList({
const [newEvents, setNewEvents] = useState<Event[]>([]) const [newEvents, setNewEvents] = useState<Event[]>([])
const [showCount, setShowCount] = useState(SHOW_COUNT) const [showCount, setShowCount] = useState(SHOW_COUNT)
const [hasMore, setHasMore] = useState<boolean>(true) const [hasMore, setHasMore] = useState<boolean>(true)
const [refreshing, setRefreshing] = useState(true) const [loading, setLoading] = useState(true)
const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode()) const [listMode, setListMode] = useState<TNoteListMode>(() => storage.getNoteListMode())
const bottomRef = useRef<HTMLDivElement | null>(null) const bottomRef = useRef<HTMLDivElement | null>(null)
const isPictures = useMemo(() => listMode === 'pictures', [listMode]) const isPictures = useMemo(() => listMode === 'pictures', [listMode])
@@ -63,7 +63,7 @@ export default function NoteList({
if (relayUrls.length === 0 && !noteFilter.authors?.length) return if (relayUrls.length === 0 && !noteFilter.authors?.length) return
async function init() { async function init() {
setRefreshing(true) setLoading(true)
setEvents([]) setEvents([])
setNewEvents([]) setNewEvents([])
setHasMore(true) setHasMore(true)
@@ -74,15 +74,11 @@ export default function NoteList({
areAlgoRelays = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo)) areAlgoRelays = relayInfos.every((relayInfo) => checkAlgoRelay(relayInfo))
} }
let eventCount = 0
const { closer, timelineKey } = await client.subscribeTimeline( const { closer, timelineKey } = await client.subscribeTimeline(
[...relayUrls], [...relayUrls],
{ ...noteFilter, limit: areAlgoRelays ? ALGO_LIMIT : LIMIT }, { ...noteFilter, limit: areAlgoRelays ? ALGO_LIMIT : LIMIT },
{ {
onEvents: (events, eosed) => { onEvents: (events, eosed) => {
if (eventCount > events.length) return
eventCount = events.length
if (events.length > 0) { if (events.length > 0) {
setEvents(events) setEvents(events)
} }
@@ -90,7 +86,7 @@ export default function NoteList({
setHasMore(false) setHasMore(false)
} }
if (eosed) { if (eosed) {
setRefreshing(false) setLoading(false)
setHasMore(events.length > 0) setHasMore(events.length > 0)
} }
}, },
@@ -115,25 +111,6 @@ export default function NoteList({
} }
}, [JSON.stringify(relayUrls), noteFilter, refreshCount]) }, [JSON.stringify(relayUrls), noteFilter, refreshCount])
const loadMore = useCallback(async () => {
if (showCount < events.length) {
setShowCount((prev) => prev + SHOW_COUNT)
return
}
if (!timelineKey || refreshing || !hasMore) return
const newEvents = await client.loadMoreTimeline(
timelineKey,
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
LIMIT
)
if (newEvents.length === 0) {
setHasMore(false)
return
}
setEvents((oldEvents) => [...oldEvents, ...newEvents])
}, [timelineKey, refreshing, hasMore, events, noteFilter, showCount])
useEffect(() => { useEffect(() => {
const options = { const options = {
root: null, root: null,
@@ -141,6 +118,30 @@ export default function NoteList({
threshold: 0.1 threshold: 0.1
} }
const loadMore = async () => {
if (showCount < events.length) {
setShowCount((prev) => prev + SHOW_COUNT)
// preload more
if (events.length - showCount > LIMIT / 2) {
return
}
}
if (!timelineKey || loading || !hasMore) return
setLoading(true)
const newEvents = await client.loadMoreTimeline(
timelineKey,
events.length ? events[events.length - 1].created_at - 1 : dayjs().unix(),
LIMIT
)
setLoading(false)
if (newEvents.length === 0) {
setHasMore(false)
return
}
setEvents((oldEvents) => [...oldEvents, ...newEvents])
}
const observerInstance = new IntersectionObserver((entries) => { const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) { if (entries[0].isIntersecting && hasMore) {
loadMore() loadMore()
@@ -158,7 +159,7 @@ export default function NoteList({
observerInstance.unobserve(currentBottomRef) observerInstance.unobserve(currentBottomRef)
} }
} }
}, [loadMore]) }, [timelineKey, loading, hasMore, events, noteFilter, showCount])
const showNewEvents = () => { const showNewEvents = () => {
topRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' }) topRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
@@ -214,7 +215,7 @@ export default function NoteList({
))} ))}
</div> </div>
)} )}
{hasMore || refreshing ? ( {hasMore || loading ? (
<div ref={bottomRef}> <div ref={bottomRef}>
<LoadingSkeleton isPictures={isPictures} /> <LoadingSkeleton isPictures={isPictures} />
</div> </div>

View File

@@ -10,15 +10,7 @@ import storage from '@/services/local-storage.service'
import { TNotificationType } from '@/types' import { TNotificationType } from '@/types'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Event, kinds } from 'nostr-tools' import { Event, kinds } from 'nostr-tools'
import { import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
useState
} from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import PullToRefresh from 'react-simple-pull-to-refresh' import PullToRefresh from 'react-simple-pull-to-refresh'
import { NotificationItem } from './NotificationItem' import { NotificationItem } from './NotificationItem'
@@ -34,7 +26,7 @@ const NotificationList = forwardRef((_, ref) => {
const [lastReadTime, setLastReadTime] = useState(0) const [lastReadTime, setLastReadTime] = useState(0)
const [refreshCount, setRefreshCount] = useState(0) const [refreshCount, setRefreshCount] = useState(0)
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined) const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
const [refreshing, setRefreshing] = useState(true) const [loading, setLoading] = useState(true)
const [notifications, setNotifications] = useState<Event[]>([]) const [notifications, setNotifications] = useState<Event[]>([])
const [newNotifications, setNewNotifications] = useState<Event[]>([]) const [newNotifications, setNewNotifications] = useState<Event[]>([])
const [oldNotifications, setOldNotifications] = useState<Event[]>([]) const [oldNotifications, setOldNotifications] = useState<Event[]>([])
@@ -57,11 +49,11 @@ const NotificationList = forwardRef((_, ref) => {
ref, ref,
() => ({ () => ({
refresh: () => { refresh: () => {
if (refreshing) return if (loading) return
setRefreshCount((count) => count + 1) setRefreshCount((count) => count + 1)
} }
}), }),
[refreshing] [loading]
) )
useEffect(() => { useEffect(() => {
@@ -71,12 +63,12 @@ const NotificationList = forwardRef((_, ref) => {
} }
const init = async () => { const init = async () => {
setRefreshing(true) setLoading(true)
setNotifications([]) setNotifications([])
setShowCount(SHOW_COUNT) setShowCount(SHOW_COUNT)
setLastReadTime(storage.getLastReadNotificationTime(pubkey)) setLastReadTime(storage.getLastReadNotificationTime(pubkey))
const relayList = await client.fetchRelayList(pubkey) const relayList = await client.fetchRelayList(pubkey)
let eventCount = 0
const { closer, timelineKey } = await client.subscribeTimeline( const { closer, timelineKey } = await client.subscribeTimeline(
relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS, relayList.read.length > 0 ? relayList.read.slice(0, 5) : BIG_RELAY_URLS,
{ {
@@ -86,11 +78,11 @@ const NotificationList = forwardRef((_, ref) => {
}, },
{ {
onEvents: (events, eosed) => { onEvents: (events, eosed) => {
if (eventCount > events.length) return if (events.length > 0) {
eventCount = events.length setNotifications(events.filter((event) => event.pubkey !== pubkey))
setNotifications(events.filter((event) => event.pubkey !== pubkey)) }
if (eosed) { if (eosed) {
setRefreshing(false) setLoading(false)
setUntil(events.length > 0 ? events[events.length - 1].created_at - 1 : undefined) setUntil(events.length > 0 ? events[events.length - 1].created_at - 1 : undefined)
updateNoteStatsByEvents(events) updateNoteStatsByEvents(events)
} }
@@ -132,30 +124,6 @@ const NotificationList = forwardRef((_, ref) => {
} }
}, [notifications, lastReadTime, showCount]) }, [notifications, lastReadTime, showCount])
const loadMore = useCallback(async () => {
if (showCount < notifications.length) {
setShowCount((count) => count + SHOW_COUNT)
return
}
if (!pubkey || !timelineKey || !until || refreshing) return
const newNotifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
if (newNotifications.length === 0) {
setUntil(undefined)
return
}
if (newNotifications.length > 0) {
setNotifications((oldNotifications) => [
...oldNotifications,
...newNotifications.filter((event) => event.pubkey !== pubkey)
])
}
setUntil(newNotifications[newNotifications.length - 1].created_at - 1)
}, [pubkey, timelineKey, until, refreshing, showCount, notifications])
useEffect(() => { useEffect(() => {
const options = { const options = {
root: null, root: null,
@@ -163,6 +131,34 @@ const NotificationList = forwardRef((_, ref) => {
threshold: 1 threshold: 1
} }
const loadMore = async () => {
if (showCount < notifications.length) {
setShowCount((count) => count + SHOW_COUNT)
// preload more
if (notifications.length - showCount > LIMIT / 2) {
return
}
}
if (!pubkey || !timelineKey || !until || loading) return
setLoading(true)
const newNotifications = await client.loadMoreTimeline(timelineKey, until, LIMIT)
setLoading(false)
if (newNotifications.length === 0) {
setUntil(undefined)
return
}
if (newNotifications.length > 0) {
setNotifications((oldNotifications) => [
...oldNotifications,
...newNotifications.filter((event) => event.pubkey !== pubkey)
])
}
setUntil(newNotifications[newNotifications.length - 1].created_at - 1)
}
const observerInstance = new IntersectionObserver((entries) => { const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) { if (entries[0].isIntersecting) {
loadMore() loadMore()
@@ -180,7 +176,7 @@ const NotificationList = forwardRef((_, ref) => {
observerInstance.unobserve(currentBottomRef) observerInstance.unobserve(currentBottomRef)
} }
} }
}, [loadMore]) }, [pubkey, timelineKey, until, loading, showCount, notifications])
return ( return (
<div> <div>
@@ -214,7 +210,7 @@ const NotificationList = forwardRef((_, ref) => {
<NotificationItem key={notification.id} notification={notification} /> <NotificationItem key={notification.id} notification={notification} />
))} ))}
<div className="text-center text-sm text-muted-foreground"> <div className="text-center text-sm text-muted-foreground">
{until || refreshing ? ( {until || loading ? (
<div ref={bottomRef}> <div ref={bottomRef}>
<div className="flex gap-2 items-center h-11 py-2"> <div className="flex gap-2 items-center h-11 py-2">
<Skeleton className="w-7 h-7 rounded-full" /> <Skeleton className="w-7 h-7 rounded-full" />

View File

@@ -105,7 +105,9 @@ export default function ReplyNoteList({
}, },
{ {
onEvents: (evts, eosed) => { onEvents: (evts, eosed) => {
setEvents(evts.filter((evt) => isReplyNoteEvent(evt)).reverse()) if (evts.length > 0) {
setEvents(evts.filter((evt) => isReplyNoteEvent(evt)).reverse())
}
if (eosed) { if (eosed) {
setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined) setUntil(evts.length >= LIMIT ? evts[evts.length - 1].created_at - 1 : undefined)
setLoading(false) setLoading(false)

View File

@@ -40,10 +40,8 @@ class ClientService extends EventTarget {
| string[] | string[]
| undefined | undefined
> = {} > = {}
private eventCache = new LRUCache<string, Promise<NEvent | undefined>>({ max: 10000 }) private eventDataLoader = new DataLoader<string, NEvent | undefined>((ids) =>
private eventDataLoader = new DataLoader<string, NEvent | undefined>( Promise.all(ids.map((id) => this._fetchEvent(id)))
(ids) => Promise.all(ids.map((id) => this._fetchEvent(id))),
{ cacheMap: this.eventCache }
) )
private fetchEventFromBigRelaysDataloader = new DataLoader<string, NEvent | undefined>( private fetchEventFromBigRelaysDataloader = new DataLoader<string, NEvent | undefined>(
this.fetchEventsFromBigRelays.bind(this), this.fetchEventsFromBigRelays.bind(this),
@@ -343,9 +341,10 @@ class ClientService extends EventTarget {
async function startSub() { async function startSub() {
startedCount++ startedCount++
const relay = await that.pool.ensureRelay(url, { connectionTimeout: 2000 }).catch(() => { const relay = await that.pool.ensureRelay(url, { connectionTimeout: 5000 }).catch(() => {
return undefined return undefined
}) })
// cannot connect to relay
if (!relay) { if (!relay) {
if (!eosed) { if (!eosed) {
eosedCount++ eosedCount++
@@ -356,6 +355,7 @@ class ClientService extends EventTarget {
close: () => {} close: () => {}
} }
} }
return relay.subscribe(filters, { return relay.subscribe(filters, {
receivedEvent: (relay, id) => { receivedEvent: (relay, id) => {
that.trackEventSeenOn(id, relay) that.trackEventSeenOn(id, relay)
@@ -372,44 +372,52 @@ class ClientService extends EventTarget {
onevent?.(evt) onevent?.(evt)
}, },
oneose: () => { oneose: () => {
// make sure eosed is not called multiple times
if (eosed) return if (eosed) return
eosedCount++ eosedCount++
eosed = eosedCount >= startedCount eosed = eosedCount >= startedCount
oneose?.(eosed) oneose?.(eosed)
}, },
onclose: (reason: string) => { onclose: (reason: string) => {
if (!reason.startsWith('auth-required')) { // auth-required
closedCount++ if (reason.startsWith('auth-required') && !hasAuthed) {
closeReasons.push(reason) // already logged in
if (closedCount >= startedCount) { if (that.signer) {
onclose?.(closeReasons) relay
.auth(async (authEvt: EventTemplate) => {
const evt = await that.signer!.signEvent(authEvt)
if (!evt) {
throw new Error('sign event failed')
}
return evt as VerifiedEvent
})
.then(() => {
hasAuthed = true
if (!eosed) {
subPromises.push(startSub())
}
})
.catch(() => {
// ignore
})
return
} }
return
}
if (hasAuthed) return
if (that.signer) { // open login dialog
relay if (startLogin) {
.auth(async (authEvt: EventTemplate) => { startLogin()
const evt = await that.signer!.signEvent(authEvt) return
if (!evt) { }
throw new Error('sign event failed')
}
return evt as VerifiedEvent
})
.then(() => {
hasAuthed = true
if (!eosed) {
subPromises.push(startSub())
}
})
.catch(() => {
// ignore
})
} else if (startLogin) {
startLogin()
} }
// close the subscription
closedCount++
closeReasons.push(reason)
if (closedCount >= startedCount) {
onclose?.(closeReasons)
}
return
}, },
eoseTimeout: 10_000 // 10s eoseTimeout: 10_000 // 10s
}) })
@@ -432,56 +440,24 @@ class ClientService extends EventTarget {
} }
private async query(urls: string[], filter: Filter | Filter[], onevent?: (evt: NEvent) => void) { private async query(urls: string[], filter: Filter | Filter[], onevent?: (evt: NEvent) => void) {
const relays = Array.from(new Set(urls)) return await new Promise<NEvent[]>((resolve) => {
const filters = Array.isArray(filter) ? filter : [filter] const events: NEvent[] = []
const _knownIds = new Set<string>() const sub = this.subscribe(urls, filter, {
const events: NEvent[] = [] onevent(evt) {
await Promise.allSettled( onevent?.(evt)
relays.map(async (url) => { events.push(evt)
// eslint-disable-next-line @typescript-eslint/no-this-alias },
const that = this oneose: (eosed) => {
const relay = await this.pool.ensureRelay(url) if (eosed) {
let hasAuthed = false sub.close()
resolve(events)
return new Promise<void>((resolve, reject) => {
const startQuery = () => {
const sub = relay.subscribe(filters, {
receivedEvent(relay, id) {
that.trackEventSeenOn(id, relay)
},
onclose(reason) {
if (!reason.startsWith('auth-required') || hasAuthed) {
resolve()
return
}
if (that.signer) {
relay
.auth((authEvt: EventTemplate) => that.signer!.signEvent(authEvt))
.then(() => {
hasAuthed = true
startQuery()
})
.catch(reject)
}
},
oneose() {
sub.close()
resolve()
},
onevent(evt) {
if (_knownIds.has(evt.id)) return
_knownIds.add(evt.id)
events.push(evt)
onevent?.(evt)
}
})
} }
startQuery() },
}) onclose: () => {
resolve(events)
}
}) })
) })
return events
} }
private async _subscribeTimeline( private async _subscribeTimeline(
@@ -509,20 +485,14 @@ class ClientService extends EventTarget {
let since: number | undefined let since: number | undefined
if (timeline && !Array.isArray(timeline) && timeline.refs.length && needSort) { if (timeline && !Array.isArray(timeline) && timeline.refs.length && needSort) {
cachedEvents = ( cachedEvents = (
await Promise.all( await this.eventDataLoader.loadMany(timeline.refs.slice(0, filter.limit).map(([id]) => id))
timeline.refs.slice(0, filter.limit).map(([id]) => this.eventCache.get(id)) ).filter((evt) => !!evt && !(evt instanceof Error)) as NEvent[]
)
).filter(Boolean) as NEvent[]
if (cachedEvents.length) { if (cachedEvents.length) {
onEvents([...cachedEvents], false) onEvents([...cachedEvents], false)
since = cachedEvents[0].created_at + 1 since = cachedEvents[0].created_at + 1
} }
} }
if (!timeline && needSort) {
this.timelines[key] = { refs: [], filter, urls: relays }
}
// eslint-disable-next-line @typescript-eslint/no-this-alias // eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this const that = this
let events: NEvent[] = [] let events: NEvent[] = []
@@ -544,12 +514,8 @@ class ClientService extends EventTarget {
if (!timeline || Array.isArray(timeline) || !timeline.refs.length) { if (!timeline || Array.isArray(timeline) || !timeline.refs.length) {
return onNew(evt) return onNew(evt)
} }
// the event is newer than the first ref, insert it to the front
if (evt.created_at > timeline.refs[0][1]) {
onNew(evt)
return timeline.refs.unshift([evt.id, evt.created_at])
}
// find the right position to insert
let idx = 0 let idx = 0
for (const ref of timeline.refs) { for (const ref of timeline.refs) {
if (evt.created_at > ref[1] || (evt.created_at === ref[1] && evt.id < ref[0])) { if (evt.created_at > ref[1] || (evt.created_at === ref[1] && evt.id < ref[0])) {
@@ -564,6 +530,11 @@ class ClientService extends EventTarget {
// the event is too old, ignore it // the event is too old, ignore it
if (idx >= timeline.refs.length) return if (idx >= timeline.refs.length) return
// new event
if (idx === 0) {
onNew(evt)
}
// insert the event to the right position // insert the event to the right position
timeline.refs.splice(idx, 0, [evt.id, evt.created_at]) timeline.refs.splice(idx, 0, [evt.id, evt.created_at])
}, },
@@ -575,7 +546,7 @@ class ClientService extends EventTarget {
} }
if (!eosed) { if (!eosed) {
events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit) events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit)
return onEvents([...events.concat(cachedEvents)], false) return onEvents([...events.concat(cachedEvents).slice(0, filter.limit)], false)
} }
events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit) events = events.sort((a, b) => b.created_at - a.created_at).slice(0, filter.limit)
@@ -590,22 +561,16 @@ class ClientService extends EventTarget {
return onEvents([...events], true) return onEvents([...events], true)
} }
const newEvents = events.filter((evt) => { const newRefs = events.map((evt) => [evt.id, evt.created_at] as TTimelineRef)
const firstRef = timeline.refs[0]
return (
evt.created_at > firstRef[1] || (evt.created_at === firstRef[1] && evt.id < firstRef[0])
)
})
const newRefs = newEvents.map((evt) => [evt.id, evt.created_at] as TTimelineRef)
if (newRefs.length >= filter.limit) { if (events.length >= filter.limit) {
// if new refs are more than limit, means old refs are too old, replace them // if new refs are more than limit, means old refs are too old, replace them
timeline.refs = newRefs timeline.refs = newRefs
onEvents([...newEvents], true) onEvents([...events], true)
} else { } else {
// merge new refs with old refs // merge new refs with old refs
timeline.refs = newRefs.concat(timeline.refs) timeline.refs = newRefs.concat(timeline.refs)
onEvents([...newEvents.concat(cachedEvents)], true) onEvents([...events.concat(cachedEvents).slice(0, filter.limit)], true)
} }
} }
}) })
@@ -625,14 +590,14 @@ class ClientService extends EventTarget {
if (!timeline || Array.isArray(timeline)) return [] if (!timeline || Array.isArray(timeline)) return []
const { filter, urls, refs } = timeline const { filter, urls, refs } = timeline
const startIdx = refs.findIndex(([, createdAt]) => createdAt < until) const startIdx = refs.findIndex(([, createdAt]) => createdAt <= until)
const cachedEvents = const cachedEvents =
startIdx >= 0 startIdx >= 0
? (( ? ((
await Promise.all( await this.eventDataLoader.loadMany(
refs.slice(startIdx, startIdx + limit).map(([id]) => this.eventCache.get(id)) refs.slice(startIdx, startIdx + limit).map(([id]) => id)
) )
).filter(Boolean) as NEvent[]) ).filter((evt) => !!evt && !(evt instanceof Error)) as NEvent[])
: [] : []
if (cachedEvents.length >= limit) { if (cachedEvents.length >= limit) {
return cachedEvents return cachedEvents
@@ -640,7 +605,7 @@ class ClientService extends EventTarget {
until = cachedEvents.length ? cachedEvents[cachedEvents.length - 1].created_at - 1 : until until = cachedEvents.length ? cachedEvents[cachedEvents.length - 1].created_at - 1 : until
limit = limit - cachedEvents.length limit = limit - cachedEvents.length
let events = await this.query(urls, { ...filter, until: until, limit: limit }) let events = await this.query(urls, { ...filter, until, limit })
events.forEach((evt) => { events.forEach((evt) => {
this.eventDataLoader.prime(evt.id, Promise.resolve(evt)) this.eventDataLoader.prime(evt.id, Promise.resolve(evt))
}) })
@@ -687,7 +652,7 @@ class ClientService extends EventTarget {
break break
} }
if (eventId) { if (eventId) {
const cache = await this.eventCache.get(eventId) const cache = await this.eventDataLoader.load(eventId)
if (cache) { if (cache) {
return cache return cache
} }