feat: track event relays
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { BIG_RELAY_URLS, COMMENT_EVENT_KIND } from '@/constants'
|
import { BIG_RELAY_URLS, COMMENT_EVENT_KIND } from '@/constants'
|
||||||
|
import { isCommentEvent, isProtectedEvent } from '@/lib/event'
|
||||||
import { tagNameEquals } from '@/lib/tag'
|
import { tagNameEquals } from '@/lib/tag'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
@@ -10,7 +11,6 @@ import { Event as NEvent } from 'nostr-tools'
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReplyNote from '../ReplyNote'
|
import ReplyNote from '../ReplyNote'
|
||||||
import { isCommentEvent } from '@/lib/event'
|
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = 100
|
||||||
|
|
||||||
@@ -62,8 +62,13 @@ export default function Nip22ReplyNoteList({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const relayList = await client.fetchRelayList(event.pubkey)
|
const relayList = await client.fetchRelayList(event.pubkey)
|
||||||
|
const relayUrls = relayList.read.concat(BIG_RELAY_URLS)
|
||||||
|
if (isProtectedEvent(event)) {
|
||||||
|
const seenOn = client.getSeenEventRelayUrls(event.id)
|
||||||
|
relayUrls.unshift(...seenOn)
|
||||||
|
}
|
||||||
const { closer, timelineKey } = await client.subscribeTimeline(
|
const { closer, timelineKey } = await client.subscribeTimeline(
|
||||||
relayList.read.concat(BIG_RELAY_URLS).slice(0, 4),
|
relayUrls.slice(0, 4),
|
||||||
{
|
{
|
||||||
'#E': [event.id],
|
'#E': [event.id],
|
||||||
kinds: [COMMENT_EVENT_KIND],
|
kinds: [COMMENT_EVENT_KIND],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Separator } from '@/components/ui/separator'
|
import { Separator } from '@/components/ui/separator'
|
||||||
import { isReplyNoteEvent } from '@/lib/event'
|
import { BIG_RELAY_URLS } from '@/constants'
|
||||||
|
import { isProtectedEvent, isReplyNoteEvent } from '@/lib/event'
|
||||||
import { isReplyETag, isRootETag } from '@/lib/tag'
|
import { isReplyETag, isRootETag } from '@/lib/tag'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useNostr } from '@/providers/NostrProvider'
|
import { useNostr } from '@/providers/NostrProvider'
|
||||||
@@ -10,7 +11,6 @@ import { Event as NEvent, kinds } from 'nostr-tools'
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReplyNote from '../ReplyNote'
|
import ReplyNote from '../ReplyNote'
|
||||||
import { BIG_RELAY_URLS } from '@/constants'
|
|
||||||
|
|
||||||
const LIMIT = 100
|
const LIMIT = 100
|
||||||
|
|
||||||
@@ -56,8 +56,13 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const relayList = await client.fetchRelayList(event.pubkey)
|
const relayList = await client.fetchRelayList(event.pubkey)
|
||||||
|
const relayUrls = relayList.read.concat(BIG_RELAY_URLS)
|
||||||
|
if (isProtectedEvent(event)) {
|
||||||
|
const seenOn = client.getSeenEventRelayUrls(event.id)
|
||||||
|
relayUrls.unshift(...seenOn)
|
||||||
|
}
|
||||||
const { closer, timelineKey } = await client.subscribeTimeline(
|
const { closer, timelineKey } = await client.subscribeTimeline(
|
||||||
relayList.read.concat(BIG_RELAY_URLS).slice(0, 4),
|
relayUrls.slice(0, 4),
|
||||||
{
|
{
|
||||||
'#e': [event.id],
|
'#e': [event.id],
|
||||||
kinds: [kinds.ShortTextNote],
|
kinds: [kinds.ShortTextNote],
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export function isPictureEvent(event: Event) {
|
|||||||
return event.kind === PICTURE_EVENT_KIND
|
return event.kind === PICTURE_EVENT_KIND
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isProtectedEvent(event: Event) {
|
||||||
|
return event.tags.some(([tagName]) => tagName === '-')
|
||||||
|
}
|
||||||
|
|
||||||
export function getParentEventId(event?: Event) {
|
export function getParentEventId(event?: Event) {
|
||||||
if (!event || !isReplyNoteEvent(event)) return undefined
|
if (!event || !isReplyNoteEvent(event)) return undefined
|
||||||
return event.tags.find(isReplyETag)?.[1] ?? event.tags.find(tagNameEquals('e'))?.[1]
|
return event.tags.find(isReplyETag)?.[1] ?? event.tags.find(tagNameEquals('e'))?.[1]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
SimplePool,
|
SimplePool,
|
||||||
VerifiedEvent
|
VerifiedEvent
|
||||||
} from 'nostr-tools'
|
} from 'nostr-tools'
|
||||||
|
import { AbstractRelay } from 'nostr-tools/abstract-relay'
|
||||||
|
|
||||||
type TTimelineRef = [string, number]
|
type TTimelineRef = [string, number]
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ class ClientService extends EventTarget {
|
|||||||
static instance: ClientService
|
static instance: ClientService
|
||||||
|
|
||||||
private defaultRelayUrls: string[] = BIG_RELAY_URLS
|
private defaultRelayUrls: string[] = BIG_RELAY_URLS
|
||||||
private pool = new SimplePool()
|
private pool: SimplePool
|
||||||
|
|
||||||
private timelines: Record<
|
private timelines: Record<
|
||||||
string,
|
string,
|
||||||
@@ -84,6 +85,8 @@ class ClientService extends EventTarget {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
this.pool = new SimplePool()
|
||||||
|
this.pool.trackRelays = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getInstance(): ClientService {
|
public static getInstance(): ClientService {
|
||||||
@@ -117,9 +120,17 @@ class ClientService extends EventTarget {
|
|||||||
const result = await Promise.any(
|
const result = await Promise.any(
|
||||||
relayUrls.map(async (url) => {
|
relayUrls.map(async (url) => {
|
||||||
const relay = await this.pool.ensureRelay(url)
|
const relay = await this.pool.ensureRelay(url)
|
||||||
return relay.publish(event).catch((error) => {
|
return relay
|
||||||
|
.publish(event)
|
||||||
|
.then((reason) => {
|
||||||
|
this.trackEventSeenOn(event.id, relay)
|
||||||
|
return reason
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
if (error instanceof Error && error.message.startsWith('auth-required:') && signer) {
|
if (error instanceof Error && error.message.startsWith('auth-required:') && signer) {
|
||||||
relay.auth((authEvt: EventTemplate) => signer(authEvt)).then(() => relay.publish(event))
|
relay
|
||||||
|
.auth((authEvt: EventTemplate) => signer(authEvt))
|
||||||
|
.then(() => relay.publish(event))
|
||||||
} else {
|
} else {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -203,6 +214,7 @@ class ClientService extends EventTarget {
|
|||||||
},
|
},
|
||||||
onevent: (evt: NEvent) => {
|
onevent: (evt: NEvent) => {
|
||||||
that.eventDataLoader.prime(evt.id, Promise.resolve(evt))
|
that.eventDataLoader.prime(evt.id, Promise.resolve(evt))
|
||||||
|
that.trackEventSeenOn(evt.id, relay)
|
||||||
// not eosed yet, push to events
|
// not eosed yet, push to events
|
||||||
if (eosedCount < startedCount) {
|
if (eosedCount < startedCount) {
|
||||||
return events.push(evt)
|
return events.push(evt)
|
||||||
@@ -537,6 +549,10 @@ class ClientService extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSeenEventRelayUrls(eventId: string) {
|
||||||
|
return Array.from(this.pool.seenOn.get(eventId)?.values() || []).map((relay) => relay.url)
|
||||||
|
}
|
||||||
|
|
||||||
private async fetchEventById(relayUrls: string[], id: string): Promise<NEvent | undefined> {
|
private async fetchEventById(relayUrls: string[], id: string): Promise<NEvent | undefined> {
|
||||||
const event = await this.fetchEventFromDefaultRelaysDataloader.load(id)
|
const event = await this.fetchEventFromDefaultRelaysDataloader.load(id)
|
||||||
if (event) {
|
if (event) {
|
||||||
@@ -740,6 +756,15 @@ class ClientService extends EventTarget {
|
|||||||
|
|
||||||
return followListEvents.sort((a, b) => b.created_at - a.created_at)[0]
|
return followListEvents.sort((a, b) => b.created_at - a.created_at)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private trackEventSeenOn(eventId: string, relay: AbstractRelay) {
|
||||||
|
let set = this.pool.seenOn.get(eventId)
|
||||||
|
if (!set) {
|
||||||
|
set = new Set()
|
||||||
|
this.pool.seenOn.set(eventId, set)
|
||||||
|
}
|
||||||
|
set.add(relay)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = ClientService.getInstance()
|
const instance = ClientService.getInstance()
|
||||||
|
|||||||
Reference in New Issue
Block a user