refactor: subscribe
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user