diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index eb2e66d..0599255 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -177,7 +177,7 @@ const NoteList = forwardRef( return () => {} } - logger.debug('NoteList subscribing to timeline with:', subRequests.map(({ urls, filter }) => ({ + console.log('[NoteList] Subscribing to timeline with:', subRequests.map(({ urls, filter }) => ({ urls, filter: { kinds: showKinds, @@ -269,7 +269,7 @@ const NoteList = forwardRef( return () => { promise.then((closer) => closer()) } - }, [JSON.stringify(subRequests), refreshCount, showKinds]) + }, [subRequests, refreshCount, showKinds]) useEffect(() => { const options = { diff --git a/src/components/NoteStats/RepostButton.tsx b/src/components/NoteStats/RepostButton.tsx index 7ba7dc7..5921645 100644 --- a/src/components/NoteStats/RepostButton.tsx +++ b/src/components/NoteStats/RepostButton.tsx @@ -51,8 +51,8 @@ export default function RepostButton({ event }: { event: Event }) { const hasReposted = noteStats?.repostPubkeySet?.has(pubkey) if (hasReposted) return if (!noteStats?.updatedAt) { - const noteStats = await noteStatsService.fetchNoteStats(event, pubkey) - if (noteStats.repostPubkeySet?.has(pubkey)) { + const fetchedNoteStats = await noteStatsService.fetchNoteStats(event, pubkey) + if (fetchedNoteStats?.repostPubkeySet?.has(pubkey)) { return } } diff --git a/src/components/NoteStats/index.tsx b/src/components/NoteStats/index.tsx index 51a962d..e6ca364 100644 --- a/src/components/NoteStats/index.tsx +++ b/src/components/NoteStats/index.tsx @@ -2,6 +2,7 @@ import { cn } from '@/lib/utils' import { useNostr } from '@/providers/NostrProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider' +import { useFeed } from '@/providers/FeedProvider' import noteStatsService from '@/services/note-stats.service' import { ExtendedKind } from '@/constants' import { getRootEventHexId } from '@/lib/event' diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx index 7248730..cd9d024 100644 --- a/src/components/ReplyNoteList/index.tsx +++ b/src/components/ReplyNoteList/index.tsx @@ -18,6 +18,7 @@ import { useContentPolicy } from '@/providers/ContentPolicyProvider' import { useMuteList } from '@/providers/MuteListProvider' import { useNostr } from '@/providers/NostrProvider' import { useReply } from '@/providers/ReplyProvider' +import { useFeed } from '@/providers/FeedProvider' import { useUserTrust } from '@/providers/UserTrustProvider' import client from '@/services/client.service' import noteStatsService from '@/services/note-stats.service' @@ -36,6 +37,8 @@ const LIMIT = 100 const SHOW_COUNT = 10 function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; event: NEvent; sort?: 'newest' | 'oldest' | 'top' | 'controversial' | 'most-zapped' }) { + console.log('[ReplyNoteList] Component rendered for event:', event.id.substring(0, 8)) + const { t } = useTranslation() const { navigateToNote } = useSmartNoteNavigation() const { currentIndex } = useSecondaryPage() @@ -43,6 +46,7 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even const { mutePubkeySet } = useMuteList() const { hideContentMentioningMutedUsers } = useContentPolicy() const { relayList: userRelayList } = useNostr() + const { relayUrls: currentFeedRelays } = useFeed() const [rootInfo, setRootInfo] = useState(undefined) const { repliesMap, addReplies } = useReply() @@ -102,6 +106,14 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even // This prevents the doom loop that was causing "too many concurrent REQS" const events = parentEventKeys.flatMap((id) => repliesMap.get(id)?.events || []) + logger.debug('[ReplyNoteList] Processing replies:', { + eventId: event.id.substring(0, 8), + parentEventKeys, + eventsFromMap: events.length, + repliesMapSize: repliesMap.size, + repliesMapKeys: Array.from(repliesMap.keys()).map(k => k.substring(0, 8)) + }) + events.forEach((evt) => { if (replyIdSet.has(evt.id)) return if (mutePubkeySet.has(evt.pubkey)) { @@ -164,8 +176,10 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even const [highlightReplyId, setHighlightReplyId] = useState(undefined) const replyRefs = useRef>({}) const bottomRef = useRef(null) + const requestTimeoutRef = useRef(null) useEffect(() => { + console.log('[ReplyNoteList] fetchRootEvent useEffect triggered for event:', event.id.substring(0, 8)) const fetchRootEvent = async () => { let root: TRootInfo @@ -207,6 +221,11 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even root = { type: 'I', id: rootITag[1] } } } + logger.debug('[ReplyNoteList] Root info determined:', { + eventId: event.id.substring(0, 8), + rootInfo: root, + eventKind: event.kind + }) setRootInfo(root) } fetchRootEvent() @@ -234,22 +253,79 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even }, [rootInfo, onNewReply]) useEffect(() => { - if (loading || !rootInfo || currentIndex !== index) return + console.log('[ReplyNoteList] Main useEffect triggered:', { + loading, + hasRootInfo: !!rootInfo, + currentIndex, + index, + shouldInit: !loading && !!rootInfo && currentIndex === index + }) + + if (loading || !rootInfo || currentIndex !== index) { + console.log('[ReplyNoteList] Early return - conditions not met:', { + loading, + hasRootInfo: !!rootInfo, + currentIndex, + index, + rootInfo + }) + return + } + + console.log('[ReplyNoteList] All conditions met, starting reply fetch...') + + // Clear any existing timeout to prevent multiple simultaneous requests + if (requestTimeoutRef.current) { + clearTimeout(requestTimeoutRef.current) + } + + // Debounce the request to prevent rapid successive calls + requestTimeoutRef.current = setTimeout(() => { + console.log('[ReplyNoteList] Debounced request starting...') + + // Check if we're already loading to prevent duplicate requests + if (loading) { + console.log('[ReplyNoteList] Already loading, skipping request') + return + } const init = async () => { setLoading(true) try { - // Privacy: Only use user's own relays + defaults, never connect to other users' relays - const userReadRelays = userRelayList?.read || [] - const userWriteRelays = userRelayList?.write || [] - const finalRelayUrls = Array.from(new Set([ - ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url), // Fast, well-connected relays - ...userReadRelays.map(url => normalizeUrl(url) || url), // User's read relays - ...userWriteRelays.map(url => normalizeUrl(url) || url) // User's write relays - ])) + // Use current feed's relay selection - if user selected a specific relay, use only that + let finalRelayUrls: string[] + console.log('[ReplyNoteList] Current feed relays:', currentFeedRelays) + + if (currentFeedRelays.length > 0) { + // Use the current feed's relay selection (respects user's choice of single relay) + finalRelayUrls = currentFeedRelays.map(url => normalizeUrl(url) || url).filter(Boolean) + console.log('[ReplyNoteList] Using current feed relays:', finalRelayUrls) + } else { + // Fallback: build comprehensive relay list only if no feed relays are set + const userReadRelays = userRelayList?.read || [] + const userWriteRelays = userRelayList?.write || [] + const eventHints = client.getEventHints(event.id) + + const allRelays = [ + ...userReadRelays.map(url => normalizeUrl(url) || url), + ...userWriteRelays.map(url => normalizeUrl(url) || url), + ...eventHints.map(url => normalizeUrl(url) || url), + ...FAST_READ_RELAY_URLS.map(url => normalizeUrl(url) || url), + ] + + finalRelayUrls = Array.from(new Set(allRelays.filter(Boolean))) + console.log('[ReplyNoteList] Using fallback relay list:', finalRelayUrls) + } + + logger.debug('[ReplyNoteList] Fetching replies for event:', { + eventId: event.id.substring(0, 8), + rootInfo, + finalRelayUrls: finalRelayUrls.slice(0, 5), // Log first 5 relays + totalRelays: finalRelayUrls.length + }) const filters: (Omit & { limit: number @@ -298,13 +374,22 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even const { closer, timelineKey } = await client.subscribeTimeline( filters.map((filter) => ({ - urls: finalRelayUrls, // Use all relays, don't slice + urls: finalRelayUrls, // Use current feed's relay selection filter })), { onEvents: (evts, eosed) => { + logger.debug('[ReplyNoteList] Received events:', { + totalEvents: evts.length, + eosed, + eventIds: evts.map(e => e.id.substring(0, 8)) + }) if (evts.length > 0) { const regularReplies = evts.filter((evt) => isReplyNoteEvent(evt)) + logger.debug('[ReplyNoteList] Filtered replies:', { + replyCount: regularReplies.length, + replyIds: regularReplies.map(r => r.id.substring(0, 8)) + }) addReplies(regularReplies) } if (eosed) { @@ -342,6 +427,13 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even return () => { promise.then((closer) => closer?.()) } + }, 500) // 500ms debounce delay + + return () => { + if (requestTimeoutRef.current) { + clearTimeout(requestTimeoutRef.current) + } + } }, [rootInfo, currentIndex, index, onNewReply]) useEffect(() => { @@ -349,7 +441,16 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even if (replies.length === 0 && !loading && timelineKey && until !== undefined) { loadMore() } - }, [replies.length, loading, timelineKey, until]) // Added until to prevent infinite loops + }, [replies.length, loading, timelineKey, until]) + + // Cleanup timeout on unmount + useEffect(() => { + return () => { + if (requestTimeoutRef.current) { + clearTimeout(requestTimeoutRef.current) + } + } + }, []) // Added until to prevent infinite loops useEffect(() => { const options = { diff --git a/src/constants.ts b/src/constants.ts index 8883ab9..4fc534d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -81,10 +81,8 @@ export const FAST_READ_RELAY_URLS = [ export const FAST_WRITE_RELAY_URLS = [ 'wss://relay.damus.io', 'wss://relay.primal.net', - 'wss://freelay.sovbit.host', 'wss://thecitadel.nostr1.com', - 'wss://nos.lol', - 'wss://nostr.mom' + 'wss://bevo.nostr1.com' ] export const SEARCHABLE_RELAY_URLS = [ @@ -93,13 +91,11 @@ export const SEARCHABLE_RELAY_URLS = [ 'wss://nostr.wine', 'wss://orly-relay.imwald.eu', 'wss://aggr.nostr.land', - 'wss://nos.lol', 'wss://thecitadel.nostr1.com', 'wss://relay.primal.net', 'wss://relay.damus.io', 'wss://relay.lumina.rocks', 'wss://relay.snort.social', - 'wss://freelay.sovbit.host' ] export const PROFILE_RELAY_URLS = [ diff --git a/src/pages/primary/DiscussionsPage/ThreadCard.tsx b/src/pages/primary/DiscussionsPage/ThreadCard.tsx index 09cc413..74ec5ed 100644 --- a/src/pages/primary/DiscussionsPage/ThreadCard.tsx +++ b/src/pages/primary/DiscussionsPage/ThreadCard.tsx @@ -15,7 +15,6 @@ interface ThreadCardProps { thread: NostrEvent onThreadClick: () => void className?: string - commentCount?: number lastCommentTime?: number lastVoteTime?: number upVotes?: number @@ -26,7 +25,6 @@ export default function ThreadCard({ thread, onThreadClick, className, - commentCount = 0, lastCommentTime = 0, lastVoteTime = 0, upVotes = 0, diff --git a/src/pages/primary/DiscussionsPage/index.tsx b/src/pages/primary/DiscussionsPage/index.tsx index ba7b8d4..e0d89e0 100644 --- a/src/pages/primary/DiscussionsPage/index.tsx +++ b/src/pages/primary/DiscussionsPage/index.tsx @@ -874,7 +874,6 @@ const DiscussionsPage = forwardRef(() => { { !blockedRelays.includes(relay)) - logger.debug('Switching to all-favorites, favoriteRelays:', visibleRelays) + + // If no visible relays, fall back to default favorite relays + const finalRelays = visibleRelays.length > 0 ? visibleRelays : DEFAULT_FAVORITE_RELAYS + + logger.debug('Switching to all-favorites, favoriteRelays:', visibleRelays, 'finalRelays:', finalRelays) const newFeedInfo = { feedType } setFeedInfo(newFeedInfo) feedInfoRef.current = newFeedInfo - setRelayUrls(visibleRelays) + setRelayUrls(finalRelays) storage.setFeedInfo(newFeedInfo, pubkey) setIsReady(true) return diff --git a/src/providers/NotificationProvider.tsx b/src/providers/NotificationProvider.tsx index 7e7e11a..36e93da 100644 --- a/src/providers/NotificationProvider.tsx +++ b/src/providers/NotificationProvider.tsx @@ -196,12 +196,14 @@ export function NotificationProvider({ children }: { children: React.ReactNode } } // Only reconnect if still mounted and not a manual close + // Increase timeout to prevent rapid reconnection loops if (isMountedRef.current) { setTimeout(() => { if (isMountedRef.current) { + console.log('[NotificationProvider] Reconnecting after close...') subscribe() } - }, 5_000) + }, 15_000) // Increased from 5s to 15s } } } diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 40211e0..64a7096 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -1,4 +1,4 @@ -import { BIG_RELAY_URLS, DEFAULT_FAVORITE_RELAYS, ExtendedKind, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS, PROFILE_FETCH_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' +import { BIG_RELAY_URLS, DEFAULT_FAVORITE_RELAYS, ExtendedKind, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS, PROFILE_FETCH_RELAY_URLS, PROFILE_RELAY_URLS, SEARCHABLE_RELAY_URLS } from '@/constants' import { compareEvents, getReplaceableCoordinate, @@ -79,9 +79,6 @@ class ClientService extends EventTarget { super() this.pool = new SimplePool() this.pool.trackRelays = true - - // Pre-blacklist known problematic relays - this.blacklistRelay('wss://freelay.sovbit.host/') } public static getInstance(): ClientService { @@ -151,10 +148,16 @@ class ClientService extends EventTarget { kinds.Contacts, ExtendedKind.FAVORITE_RELAYS, ExtendedKind.BLOSSOM_SERVER_LIST, - ExtendedKind.RELAY_REVIEW + ExtendedKind.RELAY_REVIEW, + ExtendedKind.BLOCKED_RELAYS, + kinds.Pinlist, + kinds.Mutelist, + kinds.BookmarkList, + kinds.InterestsList, + ExtendedKind.FAVORITE_RELAYS, ].includes(event.kind) ) { - _additionalRelayUrls.push(...BIG_RELAY_URLS) + _additionalRelayUrls.push(...PROFILE_RELAY_URLS, ...FAST_WRITE_RELAY_URLS) } // Use current user's relay list @@ -1950,7 +1953,7 @@ class ClientService extends EventTarget { } // For other feeds: limit to 3 relays to prevent "too many concurrent REQs" errors (reduced from 5) - return validRelays.slice(0, 3) + return validRelays.slice(0, 5) } // ================= Utils ================= diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index 50a050f..97dd963 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -49,7 +49,7 @@ class LocalStorageService { private hideContentMentioningMutedUsers: boolean = false private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS - private showRecommendedRelaysPanel: boolean = true + private showRecommendedRelaysPanel: boolean = false private shownCreateWalletGuideToastPubkeys: Set = new Set() constructor() { @@ -166,7 +166,7 @@ class LocalStorageService { window.localStorage.getItem(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT) === 'true' const storedValue = window.localStorage.getItem(StorageKey.SHOW_RECOMMENDED_RELAYS_PANEL) - this.showRecommendedRelaysPanel = storedValue !== 'false' // Default to true if not explicitly set to false + this.showRecommendedRelaysPanel = storedValue === 'true' // Default to false if not explicitly set to true const showKindsStr = window.localStorage.getItem(StorageKey.SHOW_KINDS) if (!showKindsStr) { diff --git a/vite.config.ts b/vite.config.ts index 4a45490..92e8edf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -39,7 +39,7 @@ export default defineConfig({ VitePWA({ registerType: 'autoUpdate', workbox: { - globPatterns: ['**/*.{js,css,html,png,jpg,svg}'], + globPatterns: ['**/*.{js,css,html,png,jpg,svg,ico,webmanifest}'], globDirectory: 'dist/', maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, cleanupOutdatedCaches: true, @@ -47,6 +47,14 @@ export default defineConfig({ clientsClaim: true, navigateFallback: '/index.html', navigateFallbackDenylist: [/^\/api\//, /^\/_/, /^\/admin/], + // Exclude source files and development files from precaching + globIgnores: [ + '**/src/**', + '**/node_modules/**', + '**/*.map', + '**/sw.js', + '**/workbox-*.js' + ], runtimeCaching: [ { urlPattern: /^https:\/\/image\.nostr\.build\/.*/i,