Browse Source

bug-fixes

imwald
Silberengel 4 weeks ago
parent
commit
b4463d9cb0
  1. 30
      src/components/NoteList/index.tsx
  2. 3
      src/constants.ts
  3. 3
      src/lib/index-relay-http.ts
  4. 12
      src/lib/profile-author-warmup-spec.test.ts
  5. 5
      src/lib/profile-author-warmup-spec.ts
  6. 3
      src/lib/profile-relay-search-filters.ts
  7. 20
      src/lib/replaceable-fetch-kinds.test.ts
  8. 8
      src/lib/replaceable-fetch-kinds.ts
  9. 5
      src/lib/replaceable-list-latest.ts
  10. 93
      src/services/client-replaceable-events.service.ts
  11. 9
      src/services/client.service.ts

30
src/components/NoteList/index.tsx

@ -80,7 +80,8 @@ import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { buildFeedFullSearchRelayUrls } from '@/lib/feed-full-search-relays' import { buildFeedFullSearchRelayUrls } from '@/lib/feed-full-search-relays'
import { import {
getProfileAuthorWarmupRelayUrls, getProfileAuthorWarmupRelayUrls,
getProfileAuthorWarmupSpec getProfileAuthorWarmupSpec,
isProfileTimelineSubscriptionKey
} from '@/lib/profile-author-warmup-spec' } from '@/lib/profile-author-warmup-spec'
import type { TProfile } from '@/types' import type { TProfile } from '@/types'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
@ -1982,6 +1983,7 @@ const NoteList = forwardRef(
preserveTimelineOnSubRequestsChange && preserveTimelineOnSubRequestsChange &&
!userPulledRefresh && !userPulledRefresh &&
!feedScopeChanged && !feedScopeChanged &&
eventsRef.current.length > 0 &&
(prevSubKey === subRequestsKey || (prevSubKey === subRequestsKey ||
isRelayUrlStrictSupersetIdentityKey(prevSubKey, subRequestsKey) || isRelayUrlStrictSupersetIdentityKey(prevSubKey, subRequestsKey) ||
(mergeTimelineWhenSubRequestFiltersMatch && (mergeTimelineWhenSubRequestFiltersMatch &&
@ -2058,6 +2060,10 @@ const NoteList = forwardRef(
return undefined return undefined
} }
const isProfileTimelineFeed =
hostPrimaryPageNameRef.current === 'profile' ||
isProfileTimelineSubscriptionKey(timelineSubscriptionKey)
/** /**
* Relay kindless firehose: keep the full batch. Else when the kind picker applies, narrow like * Relay kindless firehose: keep the full batch. Else when the kind picker applies, narrow like
* {@link applyKindPickerInUi}. Remaining spell paths use kinds-only narrowing when client-side kind filter runs. * {@link applyKindPickerInUi}. Remaining spell paths use kinds-only narrowing when client-side kind filter runs.
@ -2074,11 +2080,7 @@ const NoteList = forwardRef(
showKind1111Ref.current showKind1111Ref.current
) )
) )
if ( if (out.length > 0 || !isProfileTimelineFeed || mappedSubRequests.length === 0) {
out.length > 0 ||
hostPrimaryPageNameRef.current !== 'profile' ||
mappedSubRequests.length === 0
) {
return out return out
} }
return filterEvsToMappedTimelineReqKinds(evs, mappedSubRequests) return filterEvsToMappedTimelineReqKinds(evs, mappedSubRequests)
@ -2086,18 +2088,14 @@ const NoteList = forwardRef(
if (!useFilterAsIsRef.current || !clientSideKindFilterRef.current) return evs if (!useFilterAsIsRef.current || !clientSideKindFilterRef.current) return evs
if (!withKindFilterRef.current) return evs if (!withKindFilterRef.current) return evs
const byPicker = evs.filter((e) => effectiveShowKindsRef.current.includes(e.kind)) const byPicker = evs.filter((e) => effectiveShowKindsRef.current.includes(e.kind))
if ( if (byPicker.length > 0 || !isProfileTimelineFeed || mappedSubRequests.length === 0) {
byPicker.length > 0 ||
hostPrimaryPageNameRef.current !== 'profile' ||
mappedSubRequests.length === 0
) {
return byPicker return byPicker
} }
return filterEvsToMappedTimelineReqKinds(evs, mappedSubRequests) return filterEvsToMappedTimelineReqKinds(evs, mappedSubRequests)
} }
const eventMatchesProfileTimelineRequest = (event: Event) => const eventMatchesProfileTimelineRequest = (event: Event) =>
hostPrimaryPageNameRef.current === 'profile' && isProfileTimelineFeed &&
mappedSubRequests.some(({ filter }) => mappedSubRequests.some(({ filter }) =>
eventMatchesSubRequestFilterWithWindow(event, filter as Filter) eventMatchesSubRequestFilterWithWindow(event, filter as Filter)
) )
@ -2377,7 +2375,7 @@ const NoteList = forwardRef(
}> }>
const profileAuthorWarmSpec = getProfileAuthorWarmupSpec(profileMapped) const profileAuthorWarmSpec = getProfileAuthorWarmupSpec(profileMapped)
if ( if (
hostPrimaryPageName === 'profile' && isProfileTimelineFeed &&
profileAuthorWarmSpec && profileAuthorWarmSpec &&
!timelineEffectStale() !timelineEffectStale()
) { ) {
@ -3235,7 +3233,8 @@ const NoteList = forwardRef(
} }
const eventMatchesProfileDeltaRequest = (event: Event) => const eventMatchesProfileDeltaRequest = (event: Event) =>
hostPrimaryPageNameRef.current === 'profile' && (hostPrimaryPageNameRef.current === 'profile' ||
isProfileTimelineSubscriptionKey(timelineSubscriptionKey)) &&
mappedDelta.some(({ filter }) => mappedDelta.some(({ filter }) =>
eventMatchesSubRequestFilterWithWindow(event, filter as Filter) eventMatchesSubRequestFilterWithWindow(event, filter as Filter)
) )
@ -3629,7 +3628,8 @@ const NoteList = forwardRef(
publicReadFallbackAttemptedRef.current = true publicReadFallbackAttemptedRef.current = true
const profileWarm = const profileWarm =
hostPrimaryPageNameRef.current === 'profile' hostPrimaryPageNameRef.current === 'profile' ||
isProfileTimelineSubscriptionKey(timelineSubscriptionKey)
? getProfileAuthorWarmupSpec( ? getProfileAuthorWarmupSpec(
mapped as Array<{ urls: string[]; filter: TSubRequestFilter }> mapped as Array<{ urls: string[]; filter: TSubRequestFilter }>
) )

3
src/constants.ts

@ -613,6 +613,9 @@ export function isAuthorProfileMetadataPublishKind(kind: number): boolean {
* Author-published replaceables refetched on profile-view refresh, profile editor Refresh cache, * Author-published replaceables refetched on profile-view refresh, profile editor Refresh cache,
* settings Refresh cache, and {@link ReplaceableEventService.refreshAuthorPublishedReplaceablesFromRelays}. * settings Refresh cache, and {@link ReplaceableEventService.refreshAuthorPublishedReplaceablesFromRelays}.
*/ */
/** Kinds requested in the same REQ whenever the app fetches author metadata (kind 0). */
export const METADATA_CO_FETCH_KINDS: readonly number[] = [kinds.Metadata, ExtendedKind.PAYMENT_INFO]
export const AUTHOR_PROFILE_VIEW_REPLACEABLE_KINDS: readonly number[] = [ export const AUTHOR_PROFILE_VIEW_REPLACEABLE_KINDS: readonly number[] = [
kinds.Metadata, kinds.Metadata,
kinds.Contacts, kinds.Contacts,

3
src/lib/index-relay-http.ts

@ -227,6 +227,9 @@ export async function queryIndexRelay(
status: res.status status: res.status
}) })
} }
if (res.status >= 500) {
throw new IndexRelayTransportError(new Error(`HTTP ${res.status}`))
}
continue continue
} }
const json = (await res.json()) as { data?: unknown } const json = (await res.json()) as { data?: unknown }

12
src/lib/profile-author-warmup-spec.test.ts

@ -1,6 +1,9 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { ExtendedKind } from '@/constants' import { ExtendedKind } from '@/constants'
import { getProfileAuthorWarmupSpec } from './profile-author-warmup-spec' import {
getProfileAuthorWarmupSpec,
isProfileTimelineSubscriptionKey
} from './profile-author-warmup-spec'
describe('getProfileAuthorWarmupSpec', () => { describe('getProfileAuthorWarmupSpec', () => {
const authorHex = 'a'.repeat(64) const authorHex = 'a'.repeat(64)
@ -23,6 +26,13 @@ describe('getProfileAuthorWarmupSpec', () => {
expect(spec).toEqual({ author: authorHex, kinds: [1] }) expect(spec).toEqual({ author: authorHex, kinds: [1] })
}) })
it('detects profile feed subscription keys', () => {
expect(isProfileTimelineSubscriptionKey('profile-posts-abc-1-200')).toBe(true)
expect(isProfileTimelineSubscriptionKey('profile-media-abc')).toBe(true)
expect(isProfileTimelineSubscriptionKey('home-all-favorites')).toBe(false)
expect(isProfileTimelineSubscriptionKey(null)).toBe(false)
})
it('returns null when no author shards', () => { it('returns null when no author shards', () => {
expect( expect(
getProfileAuthorWarmupSpec([ getProfileAuthorWarmupSpec([

5
src/lib/profile-author-warmup-spec.ts

@ -2,6 +2,11 @@ import type { TSubRequestFilter } from '@/types'
import { normalizeHexPubkey } from '@/lib/pubkey' import { normalizeHexPubkey } from '@/lib/pubkey'
import type { Filter } from 'nostr-tools' import type { Filter } from 'nostr-tools'
/** Profile Posts/Media tabs pass stable keys like `profile-posts-…` / `profile-media-…`. */
export function isProfileTimelineSubscriptionKey(key: string | undefined | null): boolean {
return typeof key === 'string' && key.startsWith('profile-')
}
/** /**
* Profile feeds may include calendar invite shards (`#p`) without `authors`. Local session/IDB * Profile feeds may include calendar invite shards (`#p`) without `authors`. Local session/IDB
* warmup and relay fallback only need the single-author + kinds REQ shards. * warmup and relay fallback only need the single-author + kinds REQ shards.

3
src/lib/profile-relay-search-filters.ts

@ -1,3 +1,4 @@
import { METADATA_CO_FETCH_KINDS } from '@/constants'
import type { Filter } from 'nostr-tools' import type { Filter } from 'nostr-tools'
import { kinds } from 'nostr-tools' import { kinds } from 'nostr-tools'
import { splitNip05Identifier } from '@/lib/nip05' import { splitNip05Identifier } from '@/lib/nip05'
@ -22,7 +23,7 @@ export function buildProfileKind0SearchFilters(opts: {
const limit = Math.max(1, Math.min(opts.limit ?? 50, 500)) const limit = Math.max(1, Math.min(opts.limit ?? 50, 500))
const time = const time =
typeof opts.until === 'number' && opts.until > 0 ? ({ until: opts.until } as Pick<Filter, 'until'>) : {} typeof opts.until === 'number' && opts.until > 0 ? ({ until: opts.until } as Pick<Filter, 'until'>) : {}
const k = [kinds.Metadata] as number[] const k = [...METADATA_CO_FETCH_KINDS] as number[]
const pubkeyHex = decodeProfileSearchQueryToPubkeyHex(searchRaw) const pubkeyHex = decodeProfileSearchQueryToPubkeyHex(searchRaw)
if (pubkeyHex) { if (pubkeyHex) {

20
src/lib/replaceable-fetch-kinds.test.ts

@ -0,0 +1,20 @@
import { ExtendedKind } from '@/constants'
import { networkKindsForReplaceableFetch } from '@/lib/replaceable-fetch-kinds'
import { kinds } from 'nostr-tools'
import { describe, expect, it } from 'vitest'
describe('networkKindsForReplaceableFetch', () => {
it('includes kind 10133 when fetching kind 0', () => {
expect(networkKindsForReplaceableFetch(kinds.Metadata)).toEqual([
kinds.Metadata,
ExtendedKind.PAYMENT_INFO
])
})
it('leaves other replaceable kinds unchanged', () => {
expect(networkKindsForReplaceableFetch(kinds.Contacts)).toEqual([kinds.Contacts])
expect(networkKindsForReplaceableFetch(ExtendedKind.PAYMENT_INFO)).toEqual([
ExtendedKind.PAYMENT_INFO
])
})
})

8
src/lib/replaceable-fetch-kinds.ts

@ -0,0 +1,8 @@
import { METADATA_CO_FETCH_KINDS } from '@/constants'
import { kinds } from 'nostr-tools'
/** Network `kinds` for a replaceable fetch: kind 0 always includes NIP-A3 payment info (10133). */
export function networkKindsForReplaceableFetch(kind: number): number[] {
if (kind === kinds.Metadata) return [...METADATA_CO_FETCH_KINDS]
return [kind]
}

5
src/lib/replaceable-list-latest.ts

@ -1,4 +1,5 @@
import { ExtendedKind, METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS, METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS } from '@/constants' import { ExtendedKind, METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS, METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS } from '@/constants'
import { networkKindsForReplaceableFetch } from '@/lib/replaceable-fetch-kinds'
import { normalizeHexPubkey } from '@/lib/pubkey' import { normalizeHexPubkey } from '@/lib/pubkey'
import { normalizeAnyRelayUrl } from '@/lib/url' import { normalizeAnyRelayUrl } from '@/lib/url'
import client, { eventService } from '@/services/client.service' import client, { eventService } from '@/services/client.service'
@ -48,11 +49,11 @@ export async function fetchLatestReplaceableListEvent(
const rows = await client.fetchEvents( const rows = await client.fetchEvents(
allUrls, allUrls,
{ authors: [pk], kinds: [kind], limit: 80 }, { authors: [pk], kinds: networkKindsForReplaceableFetch(kind), limit: 80 },
replaceableListFetchQueryOpts(kind) replaceableListFetchQueryOpts(kind)
) )
return newestReplaceableEvent(rows) return newestReplaceableEvent(rows.filter((e) => e.kind === kind))
} }
/** /**

93
src/services/client-replaceable-events.service.ts

@ -34,6 +34,7 @@ import { prependAggrNostrLandIfViewerEligible } from '@/lib/nostr-land-relay-eli
import { stripLocalNetworkRelaysForWssReq } from '@/lib/relay-list-sanitize' import { stripLocalNetworkRelaysForWssReq } from '@/lib/relay-list-sanitize'
import { shouldDropEventOnIngest } from '@/lib/event-ingest-filter' import { shouldDropEventOnIngest } from '@/lib/event-ingest-filter'
import { isPromiseTimeoutError, racePromiseWithTimeout } from '@/lib/async-timeout' import { isPromiseTimeoutError, racePromiseWithTimeout } from '@/lib/async-timeout'
import { networkKindsForReplaceableFetch } from '@/lib/replaceable-fetch-kinds'
export class ReplaceableEventService { export class ReplaceableEventService {
/** Limits parallel Step 2/3 profile network work (relay list + wide metadata REQ). */ /** Limits parallel Step 2/3 profile network work (relay list + wide metadata REQ). */
@ -244,7 +245,7 @@ export class ReplaceableEventService {
relayUrls, relayUrls,
{ {
authors: [pubkey], authors: [pubkey],
kinds: [kind] kinds: networkKindsForReplaceableFetch(kind)
}, },
undefined, undefined,
{ {
@ -253,7 +254,12 @@ export class ReplaceableEventService {
globalTimeout: METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS globalTimeout: METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS
} }
) )
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) if (kind === kinds.Metadata) {
this.ingestMetadataCoFetchSidecars(events)
}
const sortedEvents = events
.filter((e) => e.kind === kind)
.sort((a, b) => b.created_at - a.created_at)
event = sortedEvents.length > 0 ? sortedEvents[0] : undefined event = sortedEvents.length > 0 ? sortedEvents[0] : undefined
} else { } else {
// Use DataLoader for batching (IndexedDB checks and network fetches are batched) // Use DataLoader for batching (IndexedDB checks and network fetches are batched)
@ -650,10 +656,13 @@ export class ReplaceableEventService {
kind === kinds.Metadata ? false : !isSlowReplaceableBatch || !chunkMulti kind === kinds.Metadata ? false : !isSlowReplaceableBatch || !chunkMulti
const evts = await this.queryService.query( const evts = await this.queryService.query(
relayUrls, relayUrls,
{ authors: chunkPubkeys, kinds: [kind] }, { authors: chunkPubkeys, kinds: networkKindsForReplaceableFetch(kind) },
undefined, undefined,
{ ...queryOpts, replaceableRace: chunkRace } { ...queryOpts, replaceableRace: chunkRace }
) )
if (kind === kinds.Metadata) {
this.ingestMetadataCoFetchSidecars(evts)
}
merged.push(...evts) merged.push(...evts)
} }
events = merged events = merged
@ -662,11 +671,14 @@ export class ReplaceableEventService {
relayUrls, relayUrls,
{ {
authors: pubkeys, authors: pubkeys,
kinds: [kind] kinds: networkKindsForReplaceableFetch(kind)
}, },
undefined, undefined,
queryOpts queryOpts
) )
if (kind === kinds.Metadata) {
this.ingestMetadataCoFetchSidecars(events)
}
} }
// CRITICAL: Limit the number of events processed to prevent memory issues during rapid scrolling // CRITICAL: Limit the number of events processed to prevent memory issues during rapid scrolling
@ -690,32 +702,12 @@ export class ReplaceableEventService {
const limitedEvents = Array.from(eventsByPubkey.values()).slice(0, 500) const limitedEvents = Array.from(eventsByPubkey.values()).slice(0, 500)
// Use limited events for processing // Use limited events for processing
for (const event of limitedEvents) { for (const event of limitedEvents) {
const key = `${event.pubkey}:${event.kind}` this.applyNetworkReplaceableEventToBatch(event, kind, missingItems, results, eventsMap)
const existing = eventsMap.get(key)
if (!existing || existing.created_at < event.created_at) {
eventsMap.set(key, event)
// Update results array for this event
const itemIndex = missingItems.findIndex(item => item.pubkey === event.pubkey)
if (itemIndex >= 0) {
const paramIndex = missingItems[itemIndex]!.index
results[paramIndex] = event
}
}
} }
} else { } else {
// Normal processing for smaller batches // Normal processing for smaller batches
for (const event of events) { for (const event of events) {
const key = `${event.pubkey}:${event.kind}` this.applyNetworkReplaceableEventToBatch(event, kind, missingItems, results, eventsMap)
const existing = eventsMap.get(key)
if (!existing || existing.created_at < event.created_at) {
eventsMap.set(key, event)
// Update results array for this event
const itemIndex = missingItems.findIndex(item => item.pubkey === event.pubkey)
if (itemIndex >= 0) {
const paramIndex = missingItems[itemIndex]!.index
results[paramIndex] = event
}
}
} }
} }
@ -743,12 +735,8 @@ export class ReplaceableEventService {
// Step 3: Persist hits only. Do not write negative cache rows (`value: null`) — optional kinds // Step 3: Persist hits only. Do not write negative cache rows (`value: null`) — optional kinds
// (e.g. 10432 cache relays, 10001 pins) are missing for most pubkeys and would flood IndexedDB. // (e.g. 10432 cache relays, 10001 pins) are missing for most pubkeys and would flood IndexedDB.
await Promise.allSettled( await Promise.allSettled(
missingParams.map(async ({ pubkey, kind }) => { Array.from(eventsMap.values()).map(async (event) => {
const key = `${pubkey}:${kind}`
const event = eventsMap.get(key)
if (event) {
await indexedDb.putReplaceableEvent(event) await indexedDb.putReplaceableEvent(event)
}
}) })
) )
@ -812,6 +800,33 @@ export class ReplaceableEventService {
}) })
} }
/** Persist kind 10133 rows returned alongside a kind-0 REQ (same filter, separate cache slots). */
private ingestMetadataCoFetchSidecars(events: readonly NEvent[]): void {
for (const event of events) {
if (event.kind !== ExtendedKind.PAYMENT_INFO || shouldDropEventOnIngest(event)) continue
void this.updateReplaceableEventFromBigRelaysCache(event)
}
}
private applyNetworkReplaceableEventToBatch(
event: NEvent,
requestedKind: number,
missingItems: { pubkey: string; index: number }[],
results: (NEvent | null)[],
eventsMap: Map<string, NEvent>
): void {
const kindKey = `${event.pubkey}:${event.kind}`
const existing = eventsMap.get(kindKey)
if (!existing || existing.created_at < event.created_at) {
eventsMap.set(kindKey, event)
}
if (event.kind !== requestedKind) return
const itemIndex = missingItems.findIndex((item) => item.pubkey === event.pubkey)
if (itemIndex < 0) return
const paramIndex = missingItems[itemIndex]!.index
results[paramIndex] = event
}
/** /**
* Private: Update cache for replaceable event from big relays * Private: Update cache for replaceable event from big relays
*/ */
@ -858,7 +873,7 @@ export class ReplaceableEventService {
try { try {
const events = await this.queryService.query( const events = await this.queryService.query(
relays, relays,
{ authors: [pk], kinds: [kinds.Metadata], limit: 1 }, { authors: [pk], kinds: networkKindsForReplaceableFetch(kinds.Metadata), limit: 4 },
undefined, undefined,
{ {
replaceableRace: false, replaceableRace: false,
@ -868,8 +883,10 @@ export class ReplaceableEventService {
relayOpSource: 'ReplaceableEventService.fetchKind0FromProfileRelays' relayOpSource: 'ReplaceableEventService.fetchKind0FromProfileRelays'
} }
) )
if (events.length === 0) return undefined this.ingestMetadataCoFetchSidecars(events)
const sorted = events.sort((a, b) => b.created_at - a.created_at) const metadataRows = events.filter((e) => e.kind === kinds.Metadata)
if (metadataRows.length === 0) return undefined
const sorted = metadataRows.sort((a, b) => b.created_at - a.created_at)
return sorted[0] return sorted[0]
} catch (error) { } catch (error) {
logger.warn('[ReplaceableEventService] fetchKind0FromProfileRelays failed', { logger.warn('[ReplaceableEventService] fetchKind0FromProfileRelays failed', {
@ -1038,7 +1055,7 @@ export class ReplaceableEventService {
relaysForQuery, relaysForQuery,
{ {
authors: [pubkey], authors: [pubkey],
kinds: [kinds.Metadata] kinds: networkKindsForReplaceableFetch(kinds.Metadata)
}, },
undefined, undefined,
{ {
@ -1049,8 +1066,10 @@ export class ReplaceableEventService {
} }
) )
if (events.length > 0) { this.ingestMetadataCoFetchSidecars(events)
const sortedEvents = events.sort((a, b) => b.created_at - a.created_at) const metadataRows = events.filter((e) => e.kind === kinds.Metadata)
if (metadataRows.length > 0) {
const sortedEvents = metadataRows.sort((a, b) => b.created_at - a.created_at)
const found = sortedEvents[0]! const found = sortedEvents[0]!
await this.indexProfile(found) await this.indexProfile(found)
return found return found

9
src/services/client.service.ts

@ -1,5 +1,6 @@
import { import {
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
METADATA_CO_FETCH_KINDS,
ExtendedKind, ExtendedKind,
FAST_WRITE_RELAY_URLS, FAST_WRITE_RELAY_URLS,
DOCUMENT_RELAY_URLS, DOCUMENT_RELAY_URLS,
@ -3797,9 +3798,9 @@ class ClientService extends EventTarget {
limit: limitCap, limit: limitCap,
until: filter.until until: filter.until
}) })
return built.length > 0 ? built : [{ ...filter, kinds: [kinds.Metadata] }] return built.length > 0 ? built : [{ ...filter, kinds: [...METADATA_CO_FETCH_KINDS] }]
})() })()
: { ...filter, kinds: [kinds.Metadata] } : { ...filter, kinds: [...METADATA_CO_FETCH_KINDS] }
/** NIP-50 text on many index relays: per-relay EOSE can be ~38s; global cap was 9s so subs were torn down early. */ /** NIP-50 text on many index relays: per-relay EOSE can be ~38s; global cap was 9s so subs were torn down early. */
const filtersArr = Array.isArray(queryFilter) ? queryFilter : [queryFilter] const filtersArr = Array.isArray(queryFilter) ? queryFilter : [queryFilter]
@ -3819,6 +3820,10 @@ class ClientService extends EventTarget {
const byPk = new Map<string, NEvent>() const byPk = new Map<string, NEvent>()
for (const e of events) { for (const e of events) {
if (e.kind === ExtendedKind.PAYMENT_INFO && !shouldDropEventOnIngest(e)) {
void this.replaceableEventService.updateReplaceableEventCache(e)
continue
}
if (e.kind !== kinds.Metadata) continue if (e.kind !== kinds.Metadata) continue
const prev = byPk.get(e.pubkey) const prev = byPk.get(e.pubkey)
if (!prev || e.created_at > prev.created_at) { if (!prev || e.created_at > prev.created_at) {

Loading…
Cancel
Save