Browse Source

performance improvements

imwald
Silberengel 5 months ago
parent
commit
0a8b17a517
  1. 4
      src/components/NoteList/index.tsx
  2. 4
      src/components/NoteStats/RepostButton.tsx
  3. 1
      src/components/NoteStats/index.tsx
  4. 123
      src/components/ReplyNoteList/index.tsx
  5. 6
      src/constants.ts
  6. 2
      src/pages/primary/DiscussionsPage/ThreadCard.tsx
  7. 2
      src/pages/primary/DiscussionsPage/index.tsx
  8. 2
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  9. 21
      src/providers/FeedProvider.tsx
  10. 4
      src/providers/NotificationProvider.tsx
  11. 17
      src/services/client.service.ts
  12. 4
      src/services/local-storage.service.ts
  13. 10
      vite.config.ts

4
src/components/NoteList/index.tsx

@ -177,7 +177,7 @@ const NoteList = forwardRef( @@ -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( @@ -269,7 +269,7 @@ const NoteList = forwardRef(
return () => {
promise.then((closer) => closer())
}
}, [JSON.stringify(subRequests), refreshCount, showKinds])
}, [subRequests, refreshCount, showKinds])
useEffect(() => {
const options = {

4
src/components/NoteStats/RepostButton.tsx

@ -51,8 +51,8 @@ export default function RepostButton({ event }: { event: Event }) { @@ -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
}
}

1
src/components/NoteStats/index.tsx

@ -2,6 +2,7 @@ import { cn } from '@/lib/utils' @@ -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'

123
src/components/ReplyNoteList/index.tsx

@ -18,6 +18,7 @@ import { useContentPolicy } from '@/providers/ContentPolicyProvider' @@ -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 @@ -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 @@ -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<TRootInfo | undefined>(undefined)
const { repliesMap, addReplies } = useReply()
@ -102,6 +106,14 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even @@ -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 @@ -164,8 +176,10 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even
const [highlightReplyId, setHighlightReplyId] = useState<string | undefined>(undefined)
const replyRefs = useRef<Record<string, HTMLDivElement | null>>({})
const bottomRef = useRef<HTMLDivElement | null>(null)
const requestTimeoutRef = useRef<NodeJS.Timeout | null>(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 @@ -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 @@ -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<Filter, 'since' | 'until'> & {
limit: number
@ -298,13 +374,22 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even @@ -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 @@ -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 @@ -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 = {

6
src/constants.ts

@ -81,10 +81,8 @@ export const FAST_READ_RELAY_URLS = [ @@ -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 = [ @@ -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 = [

2
src/pages/primary/DiscussionsPage/ThreadCard.tsx

@ -15,7 +15,6 @@ interface ThreadCardProps { @@ -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({ @@ -26,7 +25,6 @@ export default function ThreadCard({
thread,
onThreadClick,
className,
commentCount = 0,
lastCommentTime = 0,
lastVoteTime = 0,
upVotes = 0,

2
src/pages/primary/DiscussionsPage/index.tsx

@ -874,7 +874,6 @@ const DiscussionsPage = forwardRef(() => { @@ -874,7 +874,6 @@ const DiscussionsPage = forwardRef(() => {
<ThreadCard
key={entry.event.id}
thread={entry.event}
commentCount={entry.commentCount}
lastCommentTime={entry.lastCommentTime}
lastVoteTime={entry.lastVoteTime}
upVotes={entry.upVotes}
@ -906,7 +905,6 @@ const DiscussionsPage = forwardRef(() => { @@ -906,7 +905,6 @@ const DiscussionsPage = forwardRef(() => {
<ThreadCard
key={entry.event.id}
thread={entry.event}
commentCount={entry.commentCount}
lastCommentTime={entry.lastCommentTime}
lastVoteTime={entry.lastVoteTime}
upVotes={entry.upVotes}

2
src/pages/primary/NoteListPage/RelaysFeed.tsx

@ -36,7 +36,7 @@ export default function RelaysFeed() { @@ -36,7 +36,7 @@ export default function RelaysFeed() {
}
const subRequests = [{ urls: relayUrls, filter: {} }]
logger.debug('RelaysFeed rendering NormalFeed with:', { subRequests, relayUrls, areAlgoRelays })
console.log('[RelaysFeed] Rendering NormalFeed with:', { subRequests, relayUrls, areAlgoRelays })
return (
<NormalFeed

21
src/providers/FeedProvider.tsx

@ -54,6 +54,11 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -54,6 +54,11 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
feedType: 'relay',
id: visibleRelays[0] ?? DEFAULT_FAVORITE_RELAYS[0]
}
// Ensure we always have a valid relay ID
if (!feedInfo.id) {
feedInfo.id = DEFAULT_FAVORITE_RELAYS[0]
}
logger.debug('Initial feedInfo setup:', { visibleRelays, favoriteRelays, blockedRelays, feedInfo })
if (pubkey) {
@ -71,9 +76,10 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -71,9 +76,10 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
if (feedInfo.feedType === 'relay') {
// Check if the stored relay is blocked, if so use first visible relay instead
if (feedInfo.id && blockedRelays.includes(feedInfo.id)) {
logger.debug('Stored relay is blocked, using first visible relay instead')
console.log('[FeedProvider] Stored relay is blocked, using first visible relay instead')
feedInfo.id = visibleRelays[0] ?? DEFAULT_FAVORITE_RELAYS[0]
}
console.log('[FeedProvider] Initial relay setup, calling switchFeed with:', feedInfo.id)
return await switchFeed('relay', { relay: feedInfo.id })
}
@ -133,13 +139,14 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -133,13 +139,14 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
}
const newFeedInfo = { feedType, id: normalizedUrl }
logger.debug('Setting relay feed info:', newFeedInfo)
console.log('[FeedProvider] Setting relay feed info:', newFeedInfo)
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls([normalizedUrl])
console.log('[FeedProvider] Set relayUrls to:', [normalizedUrl])
storage.setFeedInfo(newFeedInfo, pubkey)
setIsReady(true)
logger.debug('Relay feed setup complete, isReady set to true')
console.log('[FeedProvider] Relay feed setup complete, isReady set to true')
return
}
if (feedType === 'relays') {
@ -190,11 +197,15 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -190,11 +197,15 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
if (feedType === 'all-favorites') {
// Filter out blocked relays
const visibleRelays = favoriteRelays.filter(relay => !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

4
src/providers/NotificationProvider.tsx

@ -196,12 +196,14 @@ export function NotificationProvider({ children }: { children: React.ReactNode } @@ -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
}
}
}

17
src/services/client.service.ts

@ -1,4 +1,4 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 =================

4
src/services/local-storage.service.ts

@ -49,7 +49,7 @@ class LocalStorageService { @@ -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<string> = new Set()
constructor() {
@ -166,7 +166,7 @@ class LocalStorageService { @@ -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) {

10
vite.config.ts

@ -39,7 +39,7 @@ export default defineConfig({ @@ -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({ @@ -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,

Loading…
Cancel
Save