Browse Source

bug-fixes

imwald
Silberengel 2 weeks ago
parent
commit
74f74f17f1
  1. 50
      src/lib/feed-relay-urls.ts
  2. 2
      src/lib/metadata-policy-curated-relays.ts
  3. 25
      src/pages/primary/SpellsPage/fauxSpellFeeds.test.ts
  4. 52
      src/pages/primary/SpellsPage/fauxSpellFeeds.ts
  5. 8
      src/pages/primary/SpellsPage/useSpellsPageFeed.ts

50
src/lib/feed-relay-urls.ts

@ -65,3 +65,53 @@ export function pinHttpIndexRelaysInRelayCap(
return out.slice(0, maxRelays) return out.slice(0, maxRelays)
} }
/**
* Keep global mention / read aggregators in a capped stack (notifications `#p` REQs).
* Long NIP-65 lists otherwise fill {@link FAUX_SPELL_MAX_RELAYS} before index relays are reached.
*/
export function pinMentionRelaysInRelayCap(
capped: readonly string[],
mentionSources: readonly string[],
maxRelays: number,
minPinned: number
): string[] {
const pinKeys = new Set(
mentionSources
.slice(0, Math.max(0, minPinned))
.map((u) => relayDedupeKey(u))
.filter(Boolean)
)
if (pinKeys.size === 0) return [...capped]
const mentionKeySet = new Set(mentionSources.map((u) => relayDedupeKey(u)).filter(Boolean))
const out = [...capped]
const outKeys = new Set(out.map(relayDedupeKey))
for (const raw of mentionSources) {
const key = relayDedupeKey(raw)
if (!key || outKeys.has(key)) continue
while (out.length >= maxRelays) {
let dropped = false
for (let i = out.length - 1; i >= 0; i--) {
const candidate = out[i]!
const ck = relayDedupeKey(candidate)
if (pinKeys.has(ck) || mentionKeySet.has(ck)) continue
out.splice(i, 1)
outKeys.delete(ck)
dropped = true
break
}
if (!dropped) break
}
if (out.length >= maxRelays) continue
out.push(raw)
outKeys.add(key)
pinKeys.add(key)
if ([...pinKeys].every((k) => outKeys.has(k))) break
}
return out.slice(0, maxRelays)
}

2
src/lib/metadata-policy-curated-relays.ts

@ -1,6 +1,7 @@
import { import {
BOOKSTR_RELAY_URLS, BOOKSTR_RELAY_URLS,
DOCUMENT_RELAY_URLS, DOCUMENT_RELAY_URLS,
FAST_READ_RELAY_URLS,
FOLLOWS_HISTORY_RELAY_URLS, FOLLOWS_HISTORY_RELAY_URLS,
GIF_RELAY_URLS, GIF_RELAY_URLS,
NIP42_POOL_AUTOMATIC_AUTH_RELAY_URLS, NIP42_POOL_AUTOMATIC_AUTH_RELAY_URLS,
@ -46,6 +47,7 @@ function relayKeyForCuratedSet(url: string): string {
/** Relays grantable for the duration of an active read query/subscribe (not general feed widening). */ /** Relays grantable for the duration of an active read query/subscribe (not general feed widening). */
const METADATA_POLICY_ACTIVE_READ_GRANT_RELAY_LISTS: readonly (readonly string[])[] = [ const METADATA_POLICY_ACTIVE_READ_GRANT_RELAY_LISTS: readonly (readonly string[])[] = [
...METADATA_POLICY_OPERATION_SCOPED_RELAY_LISTS, ...METADATA_POLICY_OPERATION_SCOPED_RELAY_LISTS,
FAST_READ_RELAY_URLS,
SEARCHABLE_RELAY_URLS, SEARCHABLE_RELAY_URLS,
READ_ONLY_RELAY_URLS, READ_ONLY_RELAY_URLS,
NIP66_DISCOVERY_RELAY_URLS NIP66_DISCOVERY_RELAY_URLS

25
src/pages/primary/SpellsPage/fauxSpellFeeds.test.ts

@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import {
buildNotificationSpellRelayUrls,
FAUX_SPELL_MAX_RELAYS,
notificationMentionIndexRelayUrls
} from './fauxSpellFeeds'
describe('buildNotificationSpellRelayUrls', () => {
it('pins mention index relays even when personal inbox fills the cap', () => {
const personal = Array.from({ length: 12 }, (_, i) => `wss://personal-inbox-${i}.example/`)
const out = buildNotificationSpellRelayUrls(personal)
expect(out.length).toBeLessThanOrEqual(FAUX_SPELL_MAX_RELAYS)
const mentionKeys = new Set(
notificationMentionIndexRelayUrls().map((u) => u.replace(/\/$/, '').toLowerCase())
)
const pinned = out.filter((u) => mentionKeys.has(u.replace(/\/$/, '').toLowerCase()))
expect(pinned.length).toBeGreaterThanOrEqual(3)
})
it('returns mention index relays when personal stack is empty', () => {
const out = buildNotificationSpellRelayUrls([])
expect(out.length).toBeGreaterThan(0)
expect(out.some((u) => u.includes('theforest.nostr1.com') || u.includes('nostr.land'))).toBe(true)
})
})

52
src/pages/primary/SpellsPage/fauxSpellFeeds.ts

@ -14,6 +14,8 @@ import {
ExtendedKind, ExtendedKind,
FAST_READ_RELAY_URLS, FAST_READ_RELAY_URLS,
PROFILE_MEDIA_TAB_KINDS, PROFILE_MEDIA_TAB_KINDS,
READ_ONLY_RELAY_URLS,
SEARCHABLE_RELAY_URLS
} from '@/constants' } from '@/constants'
import { RENDERABLE_NOTE_KINDS_SORTED } from '@/lib/note-renderable-kinds' import { RENDERABLE_NOTE_KINDS_SORTED } from '@/lib/note-renderable-kinds'
import { buildProfileAugmentedReadRelayUrls } from '@/lib/favorites-feed-relays' import { buildProfileAugmentedReadRelayUrls } from '@/lib/favorites-feed-relays'
@ -29,7 +31,7 @@ import {
parseThreadWatchListRefs parseThreadWatchListRefs
} from '@/lib/notification-thread-watch' } from '@/lib/notification-thread-watch'
import { userIdToPubkey } from '@/lib/pubkey' import { userIdToPubkey } from '@/lib/pubkey'
import { pinHttpIndexRelaysInRelayCap } from '@/lib/feed-relay-urls' import { pinHttpIndexRelaysInRelayCap, pinMentionRelaysInRelayCap } from '@/lib/feed-relay-urls'
import { normalizeAnyRelayUrl, normalizeUrl } from '@/lib/url' import { normalizeAnyRelayUrl, normalizeUrl } from '@/lib/url'
import type { TFeedSubRequest } from '@/types' import type { TFeedSubRequest } from '@/types'
import { type Event, type Filter } from 'nostr-tools' import { type Event, type Filter } from 'nostr-tools'
@ -38,6 +40,54 @@ import { type Event, type Filter } from 'nostr-tools'
export const FAUX_SPELL_MAX_RELAYS = 10 export const FAUX_SPELL_MAX_RELAYS = 10
export const FAUX_SPELL_EVENT_LIMIT = 200 export const FAUX_SPELL_EVENT_LIMIT = 200
/** Minimum global mention/index relays pinned for the notifications spell (`#p` REQ). */
export const NOTIFICATION_MENTION_RELAY_PIN_COUNT = 5
/** Relays that index `#p` mentions and recent social events for the notifications spell. */
export function notificationMentionIndexRelayUrls(): string[] {
return dedupeNormalizeRelayUrlsOrdered([
...FAST_READ_RELAY_URLS,
...SEARCHABLE_RELAY_URLS,
...READ_ONLY_RELAY_URLS
])
}
/**
* Notifications need global mention aggregators, not only the viewer's NIP-65 inbox (which may not store `#p`).
* Pins {@link NOTIFICATION_MENTION_RELAY_PIN_COUNT} index relays under {@link FAUX_SPELL_MAX_RELAYS}.
*/
export function buildNotificationSpellRelayUrls(
personalUrls: readonly string[],
blockedRelays: readonly string[] = []
): string[] {
const blocked = new Set(
blockedRelays
.map((b) => (normalizeAnyRelayUrl(b) || b.trim()).toLowerCase())
.filter(Boolean)
)
const allow = (u: string) => !blocked.has((normalizeAnyRelayUrl(u) || u.trim()).toLowerCase())
const mentionIndex = notificationMentionIndexRelayUrls().filter(allow)
const personal = dedupeNormalizeRelayUrlsOrdered([...personalUrls]).filter(allow)
const capped = feedRelayPolicyUrls(
[
{ source: 'search', urls: mentionIndex },
{ source: 'viewer-read', urls: personal }
],
{
operation: 'read',
maxRelays: FAUX_SPELL_MAX_RELAYS,
applySocialKindBlockedFilter: false,
allowThirdPartyLocalRelays: true
}
)
return pinMentionRelaysInRelayCap(
capped,
mentionIndex,
FAUX_SPELL_MAX_RELAYS,
Math.min(NOTIFICATION_MENTION_RELAY_PIN_COUNT, mentionIndex.length)
)
}
/** Profile Media tab: single REQ `limit` (matches merged cap in NoteList one-shot). */ /** Profile Media tab: single REQ `limit` (matches merged cap in NoteList one-shot). */
export const PROFILE_MEDIA_REQ_LIMIT = 200 export const PROFILE_MEDIA_REQ_LIMIT = 200

8
src/pages/primary/SpellsPage/useSpellsPageFeed.ts

@ -41,6 +41,7 @@ import {
MEDIA_SPELL_KINDS, MEDIA_SPELL_KINDS,
NOTIFICATION_SPELL_KINDS, NOTIFICATION_SPELL_KINDS,
applyFauxSpellCapsToSubRequests, applyFauxSpellCapsToSubRequests,
buildNotificationSpellRelayUrls,
ensureFauxSpellRelayStackTouchesFastRead ensureFauxSpellRelayStackTouchesFastRead
} from './fauxSpellFeeds' } from './fauxSpellFeeds'
import { getRelaysForSpell, spellEventToFilter } from '@/services/spell.service' import { getRelaysForSpell, spellEventToFilter } from '@/services/spell.service'
@ -414,8 +415,11 @@ export function useSpellsPageFeed(a: UseSpellsPageFeedArgs) {
) )
if (selectedFauxSpell === 'notifications') { if (selectedFauxSpell === 'notifications') {
if (!notificationsFeedPubkey || !feedUrls.length) return [] if (!notificationsFeedPubkey) return []
const notificationUrls = appendMoneroNostrRelays(feedUrls) const notificationUrls = appendMoneroNostrRelays(
buildNotificationSpellRelayUrls(feedUrls, blockedRelays)
)
if (!notificationUrls.length) return []
const base = buildNotificationsSpellSubRequests(notificationUrls, notificationsFeedPubkey) const base = buildNotificationsSpellSubRequests(notificationUrls, notificationsFeedPubkey)
const extra = buildNotificationsFollowedThreadSubRequests( const extra = buildNotificationsFollowedThreadSubRequests(
notificationUrls, notificationUrls,

Loading…
Cancel
Save