Browse Source

bug-fixes

imwald
Silberengel 1 month ago
parent
commit
b34adbb553
  1. 1
      nip66-cron/index.mjs
  2. 18
      src/components/Explore/ExploreFavoriteRelays.tsx
  3. 23
      src/components/Explore/ExploreRelayReviews.tsx
  4. 35
      src/components/NoteList/index.tsx
  5. 5
      src/components/NoteStats/index.tsx
  6. 1
      src/constants.ts
  7. 27
      src/lib/relay-list-builder.ts
  8. 13
      src/pages/primary/SpellsPage/fauxSpellFeeds.ts
  9. 6
      src/services/client-replaceable-events.service.ts

1
nip66-cron/index.mjs

@ -60,7 +60,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [ @@ -60,7 +60,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [
'wss://relay.noswhere.com',
'wss://relay.wikifreedia.xyz',
'wss://nostr.einundzwanzig.space',
'wss://relay.lumina.rocks',
'wss://nostrelites.org',
'wss://relay.nsec.app',
'wss://bucket.coracle.social',

18
src/components/Explore/ExploreFavoriteRelays.tsx

@ -2,12 +2,12 @@ import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '@/components/RelaySimp @@ -2,12 +2,12 @@ import RelaySimpleInfo, { RelaySimpleInfoSkeleton } from '@/components/RelaySimp
import { Button } from '@/components/ui/button'
import { DEFAULT_FAVORITE_RELAYS } from '@/constants'
import { useFetchRelayInfo } from '@/hooks'
import { toRelay } from '@/lib/link'
import { toRelay, toRelaySettings } from '@/lib/link'
import { normalizeUrl, simplifyUrl } from '@/lib/url'
import { usePrimaryPage, useSmartRelayNavigation } from '@/PageManager'
import { usePrimaryPage, useSecondaryPage, useSmartRelayNavigation } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import { cn } from '@/lib/utils'
import { Newspaper } from 'lucide-react'
import { Newspaper, Settings } from 'lucide-react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -58,6 +58,7 @@ function FavoriteRelayCard({ url }: { url: string }) { @@ -58,6 +58,7 @@ function FavoriteRelayCard({ url }: { url: string }) {
export default function ExploreFavoriteRelays() {
const { t } = useTranslation()
const { navigate } = usePrimaryPage()
const { push } = useSecondaryPage()
const { favoriteRelays, blockedRelays } = useFavoriteRelays()
const blockedSet = useMemo(
@ -100,6 +101,17 @@ export default function ExploreFavoriteRelays() { @@ -100,6 +101,17 @@ export default function ExploreFavoriteRelays() {
<Newspaper className="size-4 shrink-0" strokeWidth={2.5} />
<span>{t('Favorites Feed')}</span>
</Button>
<Button
type="button"
variant="outline"
size="icon"
className="h-8 w-8 shrink-0"
aria-label={t('Relays and Storage Settings')}
title={t('Relays and Storage Settings')}
onClick={() => push(toRelaySettings('favorite-relays'))}
>
<Settings className="size-4 shrink-0" strokeWidth={2.5} />
</Button>
</div>
{usingDefaults ? (
<span className="text-xs text-muted-foreground">{t('Using app default relays')}</span>

23
src/components/Explore/ExploreRelayReviews.tsx

@ -1,13 +1,30 @@ @@ -1,13 +1,30 @@
import NoteList from '@/components/NoteList'
import { ExtendedKind, FAST_READ_RELAY_URLS } from '@/constants'
import { ExtendedKind, PROFILE_FETCH_RELAY_URLS } from '@/constants'
import {
getRelayUrlFromRelayReviewEvent,
getStarsFromRelayReviewEvent
} from '@/lib/event-metadata'
import { buildExploreProfileAndUserRelayList } from '@/lib/relay-list-builder'
import { useNostr } from '@/providers/NostrProvider'
import { Event } from 'nostr-tools'
import { useCallback } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
export default function ExploreRelayReviews() {
const { pubkey } = useNostr()
const [relayUrls, setRelayUrls] = useState<string[]>(() => [...PROFILE_FETCH_RELAY_URLS])
useEffect(() => {
let cancelled = false
buildExploreProfileAndUserRelayList(pubkey ?? null).then((urls) => {
if (!cancelled) setRelayUrls(urls)
})
return () => {
cancelled = true
}
}, [pubkey])
const subRequests = useMemo(() => [{ urls: relayUrls, filter: {} }], [relayUrls])
const extraShouldHideEvent = useCallback((evt: Event) => {
if (evt.kind !== ExtendedKind.RELAY_REVIEW) return false
if (!getRelayUrlFromRelayReviewEvent(evt)) return true
@ -18,7 +35,7 @@ export default function ExploreRelayReviews() { @@ -18,7 +35,7 @@ export default function ExploreRelayReviews() {
<div className="min-w-0 pt-1">
<NoteList
showKinds={[ExtendedKind.RELAY_REVIEW]}
subRequests={[{ urls: [...FAST_READ_RELAY_URLS], filter: {} }]}
subRequests={subRequests}
showKind1OPs={false}
showKind1Replies={false}
showKind1111={false}

35
src/components/NoteList/index.tsx

@ -237,6 +237,9 @@ const NoteList = forwardRef( @@ -237,6 +237,9 @@ const NoteList = forwardRef(
return () => {}
}
/** False after cleanup so stale timeline callbacks cannot overwrite state after switching feeds (e.g. Spells discussions → notifications). */
let effectActive = true
async function init() {
setLoading(true)
setEvents([])
@ -281,7 +284,10 @@ const NoteList = forwardRef( @@ -281,7 +284,10 @@ const NoteList = forwardRef(
let closer: (() => void) | undefined
let timelineKey: string | undefined
let timelineSubscribePromise:
| Promise<{ closer: () => void; timelineKey: string }>
| undefined
try {
// Add timeout wrapper to prevent subscribeTimeline from hanging indefinitely
const timeoutPromise = new Promise<never>((_, reject) => {
@ -290,11 +296,11 @@ const NoteList = forwardRef( @@ -290,11 +296,11 @@ const NoteList = forwardRef(
}, 5000) // 5 second timeout
})
const result = await Promise.race([
client.subscribeTimeline(
timelineSubscribePromise = client.subscribeTimeline(
mappedSubRequests,
{
onEvents: (events: Event[], eosed: boolean) => {
if (!effectActive) return
if (events.length > 0) {
setEvents(events)
// Do not wait for full EOSE across many relays — otherwise loading/skeleton stays up for 10–30s+
@ -314,6 +320,7 @@ const NoteList = forwardRef( @@ -314,6 +320,7 @@ const NoteList = forwardRef(
pubkeysToFetch.forEach((p) => prefetchedPubkeysRef.current.add(p))
// Batch fetch in background (non-blocking) with delay to not block initial render
setTimeout(() => {
if (!effectActive) return
client.fetchProfilesForPubkeys(pubkeysToFetch).catch(() => {
// On error, remove from prefetched set so we can retry later
pubkeysToFetch.forEach((p) => prefetchedPubkeysRef.current.delete(p))
@ -337,6 +344,7 @@ const NoteList = forwardRef( @@ -337,6 +344,7 @@ const NoteList = forwardRef(
eventIdsToFetch.forEach((id) => prefetchedEventIdsRef.current.add(id))
// Batch fetch embedded events in background (non-blocking) with delay
setTimeout(() => {
if (!effectActive) return
Promise.all(eventIdsToFetch.map((id) => client.fetchEvent(id))).catch(() => {
// On error, remove from prefetched set so we can retry later
eventIdsToFetch.forEach((id) => prefetchedEventIdsRef.current.delete(id))
@ -369,6 +377,7 @@ const NoteList = forwardRef( @@ -369,6 +377,7 @@ const NoteList = forwardRef(
}
},
onNew: (event: Event) => {
if (!effectActive) return
if (!useFilterAsIs && !showKinds.includes(event.kind)) return
if (event.kind === kinds.ShortTextNote) {
const isReply = isReplyNoteEvent(event)
@ -395,22 +404,34 @@ const NoteList = forwardRef( @@ -395,22 +404,34 @@ const NoteList = forwardRef(
needSort: !areAlgoRelays,
useCache: false // Main feeds should always fetch fresh from relays, not use cache
}
),
timeoutPromise
])
)
const result = await Promise.race([timelineSubscribePromise, timeoutPromise])
if (!effectActive) {
result.closer()
return () => {}
}
closer = result.closer
timelineKey = result.timelineKey
setTimelineKey(timelineKey)
return closer
} catch (_error) {
setLoading(false)
// Return a no-op closer function instead of throwing - allows cleanup to work
// Race timeout or subscribe failure: if the timeline promise later resolves, close or subs leak (relay slots + stale setEvents).
if (timelineSubscribePromise) {
void timelineSubscribePromise
.then((r) => {
r.closer()
})
.catch(() => {})
}
return () => {}
}
}
const promise = init()
return () => {
effectActive = false
promise.then((closer) => closer?.())
}
}, [

5
src/components/NoteStats/index.tsx

@ -74,7 +74,8 @@ export default function NoteStats({ @@ -74,7 +74,8 @@ export default function NoteStats({
{displayTopZapsAndLikes && (
<>
<TopZaps event={event} />
<Likes event={event} />
{/* Kind 11: LikeButton already shows ⬆/⬇; Likes row would duplicate those pills */}
{!isDiscussion && <Likes event={event} />}
</>
)}
<div
@ -100,7 +101,7 @@ export default function NoteStats({ @@ -100,7 +101,7 @@ export default function NoteStats({
{displayTopZapsAndLikes && (
<>
<TopZaps event={event} />
<Likes event={event} />
{!isDiscussion && <Likes event={event} />}
</>
)}
<div className="flex justify-between h-5 [&_svg]:size-4">

1
src/constants.ts

@ -179,7 +179,6 @@ export const SEARCHABLE_RELAY_URLS = [ @@ -179,7 +179,6 @@ export const SEARCHABLE_RELAY_URLS = [
'wss://relay.noswhere.com',
'wss://relay.wikifreedia.xyz',
'wss://nostr.einundzwanzig.space',
'wss://relay.lumina.rocks',
'wss://nostrelites.org',
'wss://relay.nsec.app',
'wss://bucket.coracle.social',

27
src/lib/relay-list-builder.ts

@ -230,6 +230,33 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio @@ -230,6 +230,33 @@ export async function buildComprehensiveRelayList(options: RelayListBuilderOptio
return Array.from(relayUrls)
}
/**
* Explore: Following's Favorites (kind 10012 batch) and Relay reviews tab.
* PROFILE_FETCH_RELAY_URLS plus the viewer's read/write and cache (10432) relays no FAST_READ.
*/
export async function buildExploreProfileAndUserRelayList(
userPubkey: string | null | undefined
): Promise<string[]> {
if (!userPubkey) {
return Array.from(new Set([...PROFILE_FETCH_RELAY_URLS]))
}
try {
const built = await buildComprehensiveRelayList({
userPubkey,
includeUserOwnRelays: true,
includeProfileFetchRelays: true,
includeFastReadRelays: false,
includeFavoriteRelays: false,
includeLocalRelays: true,
includeFastWriteRelays: false,
includeSearchableRelays: false
})
return built.length > 0 ? built : Array.from(new Set([...PROFILE_FETCH_RELAY_URLS]))
} catch {
return Array.from(new Set([...PROFILE_FETCH_RELAY_URLS]))
}
}
/**
* Build relay list for reading replies/comments
* READ from: FAST_READ_RELAY_URLS + user's inboxes + local relays + OP author's outboxes

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

@ -17,6 +17,13 @@ const NOTIFICATION_LIMIT = 500 @@ -17,6 +17,13 @@ const NOTIFICATION_LIMIT = 500
const DISCUSSION_LIMIT = 500
const MAX_BOOKMARK_IDS = 250
/**
* Spells Discussions uses NoteList subscribeTimeline one live REQ per relay.
* The same merged list as DiscussionsPages one-shot query would open 80+ sockets and exhaust
* subscription slots; cap keeps first paint fast. Full coverage remains on /discussions.
*/
const DISCUSSION_FAUX_SPELL_MAX_RELAYS = 32
export const MEDIA_SPELL_KINDS = [
ExtendedKind.PICTURE,
ExtendedKind.VIDEO,
@ -66,7 +73,10 @@ export function buildMentionsSpellFilter(pubkey: string): Filter { @@ -66,7 +73,10 @@ export function buildMentionsSpellFilter(pubkey: string): Filter {
}
}
/** Relay set for discussion threads (kind 11), aligned with DiscussionsPage’s merged list (sync). */
/**
* Relay set for Spells Discussions (kind 11): same merge order as DiscussionsPage, but capped
* for subscription-based loading (see DISCUSSION_FAUX_SPELL_MAX_RELAYS).
*/
export function discussionRelayUrls(
relayList: TRelayList | null | undefined,
favoriteRelays: string[],
@ -83,6 +93,7 @@ export function discussionRelayUrls( @@ -83,6 +93,7 @@ export function discussionRelayUrls(
if (!k || seen.has(k) || blocked.has(k)) continue
seen.add(k)
out.push(k)
if (out.length >= DISCUSSION_FAUX_SPELL_MAX_RELAYS) break
}
return out
}

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

@ -18,7 +18,7 @@ import indexedDb from './indexed-db.service' @@ -18,7 +18,7 @@ import indexedDb from './indexed-db.service'
import type { QueryService } from './client-query.service'
import logger from '@/lib/logger'
import client from './client.service'
import { buildComprehensiveRelayList } from '@/lib/relay-list-builder'
import { buildComprehensiveRelayList, buildExploreProfileAndUserRelayList } from '@/lib/relay-list-builder'
export class ReplaceableEventService {
private queryService: QueryService
@ -436,6 +436,8 @@ export class ReplaceableEventService { @@ -436,6 +436,8 @@ export class ReplaceableEventService {
// For metadata with a logged-in user, merge defaults with {@link buildComprehensiveRelayList}: inboxes (read),
// local/cache relays (10432), favorite relays (10012), plus profile + fast read — same idea as favorites feed
// / inbox-scoped discovery without per-author relay list fetches.
// Following's Favorites (Explore): kind 10012 batch uses PROFILE_FETCH_RELAY_URLS + viewer's own relays only
// (no FAST_READ), so outbox data is queried where the user actually reads + profile-index relays.
let relayUrls: string[]
if (kind === kinds.Metadata) {
const userPk = client.pubkey
@ -457,6 +459,8 @@ export class ReplaceableEventService { @@ -457,6 +459,8 @@ export class ReplaceableEventService {
} else {
relayUrls = Array.from(new Set([...PROFILE_FETCH_RELAY_URLS, ...FAST_READ_RELAY_URLS]))
}
} else if (kind === ExtendedKind.FAVORITE_RELAYS) {
relayUrls = await buildExploreProfileAndUserRelayList(client.pubkey)
} else {
relayUrls = [...FAST_READ_RELAY_URLS]
}

Loading…
Cancel
Save