Browse Source

more performance fixes

imwald
Silberengel 5 months ago
parent
commit
1ae1f24c41
  1. 38
      src/PageManager.tsx
  2. 6
      src/components/Embedded/EmbeddedNote.tsx
  3. 112
      src/components/NoteList/index.tsx
  4. 24
      src/components/NoteOptions/useMenuActions.tsx
  5. 51
      src/components/NotificationList/index.tsx
  6. 18
      src/components/Profile/ProfileBookmarksAndHashtags.tsx
  7. 22
      src/components/SimpleNoteFeed/index.tsx
  8. 38
      src/lib/logger.ts
  9. 6
      src/pages/primary/NoteListPage/RelaysFeed.tsx
  10. 4
      src/pages/secondary/NotePage/NotFound.tsx
  11. 5
      src/providers/BookmarksProvider.tsx
  12. 10
      src/providers/FeedProvider.tsx
  13. 35
      src/providers/InterestListProvider.tsx
  14. 8
      src/providers/NostrProvider/index.tsx
  15. 16
      src/providers/NotificationProvider.tsx
  16. 1106
      src/services/client.service.ts
  17. 1
      src/services/note-stats.service.ts

38
src/PageManager.tsx

@ -614,15 +614,18 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -614,15 +614,18 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
const pushSecondaryPage = (url: string, index?: number) => {
console.log('pushSecondaryPage called with:', url)
logger.component('PageManager', 'pushSecondaryPage called', { url })
setSecondaryStack((prevStack) => {
console.log('Current secondary stack length:', prevStack.length)
logger.component('PageManager', 'Current secondary stack length', { length: prevStack.length })
// For relay pages, clear the stack and start fresh to avoid confusion
if (url.startsWith('/relays/')) {
console.log('Clearing stack for relay navigation')
logger.component('PageManager', 'Clearing stack for relay navigation')
const { newStack, newItem } = pushNewPageToStack([], url, maxStackSize, 0)
console.log('New stack length:', newStack.length, 'New item:', !!newItem)
logger.component('PageManager', 'New stack created', {
newStackLength: newStack.length,
hasNewItem: !!newItem
})
if (newItem) {
window.history.pushState({ index: newItem.index, url }, '', url)
}
@ -630,7 +633,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -630,7 +633,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}
if (isCurrentPage(prevStack, url)) {
console.log('Page already exists, scrolling to top')
logger.component('PageManager', 'Page already exists, scrolling to top')
const currentItem = prevStack[prevStack.length - 1]
if (currentItem?.ref?.current) {
currentItem.ref.current.scrollToTop('instant')
@ -638,9 +641,12 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -638,9 +641,12 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
return prevStack
}
console.log('Creating new page for URL:', url)
logger.component('PageManager', 'Creating new page for URL', { url })
const { newStack, newItem } = pushNewPageToStack(prevStack, url, maxStackSize, index)
console.log('New stack length:', newStack.length, 'New item:', !!newItem)
logger.component('PageManager', 'New page created', {
newStackLength: newStack.length,
hasNewItem: !!newItem
})
if (newItem) {
window.history.pushState({ index: newItem.index, url }, '', url)
}
@ -715,7 +721,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -715,7 +721,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
{!!secondaryStack.length &&
secondaryStack.map((item, index) => {
const isLast = index === secondaryStack.length - 1
console.log('Rendering secondary stack item:', {
logger.component('PageManager', 'Rendering secondary stack item', {
index,
isLast,
url: item.url,
@ -788,7 +794,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { @@ -788,7 +794,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
<div className="flex-1 overflow-auto">
{secondaryStack.map((item, index) => {
const isLast = index === secondaryStack.length - 1
console.log('Rendering desktop secondary stack item:', {
logger.component('PageManager', 'Rendering desktop secondary stack item', {
index,
isLast,
url: item.url,
@ -861,21 +867,21 @@ function isCurrentPage(stack: TStackItem[], url: string) { @@ -861,21 +867,21 @@ function isCurrentPage(stack: TStackItem[], url: string) {
const currentPage = stack[stack.length - 1]
if (!currentPage) return false
console.log('isCurrentPage check:', { currentUrl: currentPage.url, newUrl: url, match: currentPage.url === url })
logger.component('PageManager', 'isCurrentPage check', { currentUrl: currentPage.url, newUrl: url, match: currentPage.url === url })
return currentPage.url === url
}
function findAndCreateComponent(url: string, index: number) {
const path = url.split('?')[0].split('#')[0]
console.log('findAndCreateComponent called with:', { url, path, routes: routes.length })
logger.component('PageManager', 'findAndCreateComponent called', { url, path, routes: routes.length })
for (const { matcher, element } of routes) {
const match = matcher(path)
console.log('Trying route matcher, match result:', !!match)
logger.component('PageManager', 'Trying route matcher', { matchResult: !!match })
if (!match) continue
if (!element) {
console.log('No element for this route')
logger.component('PageManager', 'No element for this route')
return {}
}
const ref = createRef<TPageRef>()
@ -884,13 +890,13 @@ function findAndCreateComponent(url: string, index: number) { @@ -884,13 +890,13 @@ function findAndCreateComponent(url: string, index: number) {
const params = { ...match.params }
if (params.url && typeof params.url === 'string') {
params.url = decodeURIComponent(params.url)
console.log('Decoded URL parameter:', params.url)
logger.component('PageManager', 'Decoded URL parameter', { url: params.url })
}
console.log('Creating component with params:', params)
logger.component('PageManager', 'Creating component with params', params)
return { component: cloneElement(element, { ...params, index, ref } as any), ref }
}
console.log('No matching route found for:', path)
logger.component('PageManager', 'No matching route found', { path })
return {}
}

6
src/components/Embedded/EmbeddedNote.tsx

@ -25,12 +25,12 @@ export function EmbeddedNote({ noteId, className }: { noteId: string; className? @@ -25,12 +25,12 @@ export function EmbeddedNote({ noteId, className }: { noteId: string; className?
setRetryCount(prev => prev + 1)
client.fetchEventForceRetry(noteId)
.then((retryResult) => {
.then((retryResult: any) => {
if (retryResult) {
setRetryEvent(retryResult)
}
})
.catch((error) => {
.catch((error: any) => {
console.warn(`Retry ${retryCount + 1}/${maxRetries} failed for event:`, noteId, error)
})
.finally(() => {
@ -137,7 +137,7 @@ function EmbeddedNoteNotFound({ @@ -137,7 +137,7 @@ function EmbeddedNoteNotFound({
setIsSearchingExternal(true)
try {
const event = await client.fetchEventWithExternalRelays(noteId)
const event = await client.fetchEventWithExternalRelays(noteId, [])
if (event && onEventFound) {
onEventFound(event)
}

112
src/components/NoteList/index.tsx

@ -83,22 +83,8 @@ const NoteList = forwardRef( @@ -83,22 +83,8 @@ const NoteList = forwardRef(
// Check if this is a profile feed
const isProfileFeed = subRequests.some(req => req.filter.authors && req.filter.authors.length === 1)
console.log('🔍 [NoteList] Checking shouldHideEvent for:', {
id: evt.id,
kind: evt.kind,
pubkey: evt.pubkey.substring(0, 8),
isDeleted: isEventDeleted(evt),
isReply: isReplyNoteEvent(evt),
isTrusted: isUserTrusted(evt.pubkey),
isMuted: mutePubkeySet.has(evt.pubkey),
hideReplies,
hideUntrustedNotes,
filterMutedNotes,
isProfileFeed
})
if (isEventDeleted(evt)) {
console.log('❌ [NoteList] Event deleted:', evt.id)
logger.component('NoteList', 'Event filtered: deleted', { id: evt.id, kind: evt.kind })
return true
}
@ -109,20 +95,24 @@ const NoteList = forwardRef( @@ -109,20 +95,24 @@ const NoteList = forwardRef(
// For profile feeds, show all zaps from the profile owner
// For timeline feeds, filter by threshold
if (!isProfileFeed && zapInfo && zapInfo.amount < zapReplyThreshold) {
console.log('❌ [NoteList] Zap below threshold:', { id: evt.id, amount: zapInfo.amount, threshold: zapReplyThreshold })
logger.component('NoteList', 'Event filtered: zap below threshold', {
id: evt.id,
amount: zapInfo.amount,
threshold: zapReplyThreshold
})
return true
}
} else if (hideReplies && isReplyNoteEvent(evt)) {
console.log('❌ [NoteList] Reply hidden:', evt.id)
logger.component('NoteList', 'Event filtered: reply hidden', { id: evt.id, kind: evt.kind })
return true
}
if (hideUntrustedNotes && !isUserTrusted(evt.pubkey)) {
console.log('❌ [NoteList] Untrusted user:', evt.id)
logger.component('NoteList', 'Event filtered: untrusted user', { id: evt.id, pubkey: evt.pubkey.substring(0, 8) })
return true
}
if (filterMutedNotes && mutePubkeySet.has(evt.pubkey)) {
console.log('❌ [NoteList] Muted user:', evt.id)
logger.component('NoteList', 'Event filtered: muted user', { id: evt.id, pubkey: evt.pubkey.substring(0, 8) })
return true
}
if (
@ -130,11 +120,10 @@ const NoteList = forwardRef( @@ -130,11 +120,10 @@ const NoteList = forwardRef(
hideContentMentioningMutedUsers &&
isMentioningMutedUsers(evt, mutePubkeySet)
) {
console.log('❌ [NoteList] Mentions muted users:', evt.id)
logger.component('NoteList', 'Event filtered: mentions muted users', { id: evt.id, kind: evt.kind })
return true
}
console.log('✅ [NoteList] Event passed all filters:', evt.id)
return false
},
[hideReplies, hideUntrustedNotes, mutePubkeySet, isEventDeleted, zapReplyThreshold, subRequests]
@ -142,34 +131,30 @@ const NoteList = forwardRef( @@ -142,34 +131,30 @@ const NoteList = forwardRef(
const filteredEvents = useMemo(() => {
const idSet = new Set<string>()
console.log('🔍 [NoteList] Filtering events:', {
totalEvents: events.length,
showCount,
eventKinds: events.map(e => e.kind).slice(0, 10)
})
const startTime = performance.now()
const filtered = events.slice(0, showCount).filter((evt) => {
if (shouldHideEvent(evt)) {
console.log('❌ [NoteList] Event hidden:', { id: evt.id, kind: evt.kind, reason: 'shouldHideEvent' })
return false
}
const id = isReplaceableEvent(evt.kind) ? getReplaceableCoordinateFromEvent(evt) : evt.id
if (idSet.has(id)) {
console.log('❌ [NoteList] Event hidden:', { id: evt.id, kind: evt.kind, reason: 'duplicate' })
logger.component('NoteList', 'Event filtered: duplicate', { id: evt.id, kind: evt.kind })
return false
}
idSet.add(id)
return true
})
console.log('✅ [NoteList] Filtered events result:', {
total: events.length,
filtered: filtered.length,
showCount
const endTime = performance.now()
logger.perfComponent('NoteList', 'Event filtering completed', {
totalEvents: events.length,
filteredEvents: filtered.length,
showCount,
duration: `${(endTime - startTime).toFixed(2)}ms`
})
return filtered
}, [events, showCount, shouldHideEvent])
@ -200,7 +185,7 @@ const NoteList = forwardRef( @@ -200,7 +185,7 @@ const NoteList = forwardRef(
scrollToTop()
// Clear relay connection state to force fresh connections
const relayUrls = subRequests.flatMap(req => req.urls)
client.clearRelayConnectionState(relayUrls)
relayUrls.forEach(url => client.clearRelayConnectionState(url))
setTimeout(() => {
setRefreshCount((count) => count + 1)
}, 500)
@ -209,32 +194,32 @@ const NoteList = forwardRef( @@ -209,32 +194,32 @@ const NoteList = forwardRef(
useImperativeHandle(ref, () => ({ scrollToTop, refresh }), [])
useEffect(() => {
console.log('🚀 [NoteList] useEffect triggered:', {
logger.component('NoteList', 'useEffect triggered', {
subRequests: subRequests.length,
showKinds: showKinds.length,
refreshCount
})
logger.debug('NoteList useEffect:', { subRequests, subRequestsLength: subRequests.length })
if (!subRequests.length) {
console.log('❌ [NoteList] No subRequests, returning early')
logger.component('NoteList', 'No subRequests, returning early')
return
}
// Don't initialize if showKinds is empty (still loading from provider)
if (showKinds.length === 0) {
console.log('⏳ [NoteList] showKinds is empty, waiting for provider to initialize')
logger.component('NoteList', 'showKinds is empty, waiting for provider to initialize')
return
}
async function init() {
console.log('🔄 [NoteList] Initializing feed...')
logger.component('NoteList', 'Initializing feed')
setLoading(true)
setEvents([])
setNewEvents([])
setHasMore(true)
if (showKinds.length === 0) {
logger.warn('NoteList: showKinds is empty, no events will be displayed')
logger.component('NoteList', 'showKinds is empty, no events will be displayed')
setLoading(false)
setHasMore(false)
return () => {}
@ -249,24 +234,17 @@ const NoteList = forwardRef( @@ -249,24 +234,17 @@ const NoteList = forwardRef(
}
}))
console.log('[NoteList] Subscribing to timeline with:', finalFilters)
console.log('[NoteList] showKinds:', showKinds)
const { closer, timelineKey } = await client.subscribeTimeline(
finalFilters,
{
onEvents: (events, eosed) => {
console.log('📥 [NoteList] Received events:', {
logger.component('NoteList', 'Received events from relay', {
eventsCount: events.length,
eosed,
loading,
hasMore,
eventKinds: events.map(e => e.kind).slice(0, 10), // First 10 event kinds
showKinds
eventKinds: [...new Set(events.map(e => e.kind))].slice(0, 5)
})
logger.debug('NoteList received events:', { eventsCount: events.length, eosed })
if (events.length > 0) {
console.log('✅ [NoteList] Accumulating events from relay')
setEvents(prevEvents => {
// For profile feeds, accumulate events from all relays
// For timeline feeds, replace events
@ -276,16 +254,19 @@ const NoteList = forwardRef( @@ -276,16 +254,19 @@ const NoteList = forwardRef(
// Accumulate events, removing duplicates
const existingIds = new Set(prevEvents.map(e => e.id))
const newEvents = events.filter(e => !existingIds.has(e.id))
const combined = [...prevEvents, ...newEvents]
console.log('📊 [NoteList] Profile feed - accumulated:', {
previous: prevEvents.length,
new: events.length,
logger.component('NoteList', 'Profile feed - accumulating events', {
previous: prevEvents.length,
new: events.length,
unique: newEvents.length,
total: combined.length
total: prevEvents.length + newEvents.length
})
return combined
return [...prevEvents, ...newEvents]
} else {
// Timeline feed - replace events
logger.component('NoteList', 'Timeline feed - replacing events', {
previous: prevEvents.length,
new: events.length
})
return events
}
})
@ -296,7 +277,10 @@ const NoteList = forwardRef( @@ -296,7 +277,10 @@ const NoteList = forwardRef(
setHasMore(false)
}
if (eosed) {
console.log('🏁 [NoteList] EOSED - setting loading false, hasMore:', events.length > 0)
logger.component('NoteList', 'EOSED - all relays finished', {
eventsCount: events.length,
hasMore: events.length > 0
})
setLoading(false)
setHasMore(events.length > 0)
}
@ -315,7 +299,7 @@ const NoteList = forwardRef( @@ -315,7 +299,7 @@ const NoteList = forwardRef(
}
},
onClose: (url, reason) => {
logger.debug('Relay connection closed:', { url, reason })
logger.component('NoteList', 'Relay connection closed', { url, reason })
if (!showRelayCloseReason) return
// ignore reasons from nostr-tools
if (
@ -344,7 +328,7 @@ const NoteList = forwardRef( @@ -344,7 +328,7 @@ const NoteList = forwardRef(
const fallbackTimeout = setTimeout(() => {
if (loading) {
setLoading(false)
logger.debug('NoteList loading timeout - stopping after 15 seconds')
logger.component('NoteList', 'Loading timeout - stopping after 15 seconds')
}
}, 15000)
@ -419,7 +403,7 @@ const NoteList = forwardRef( @@ -419,7 +403,7 @@ const NoteList = forwardRef(
}, 0)
}
console.log('🎨 [NoteList] Rendering with state:', {
logger.component('NoteList', 'Rendering with state', {
eventsCount: events.length,
filteredEventsCount: filteredEvents.length,
loading,
@ -447,10 +431,10 @@ const NoteList = forwardRef( @@ -447,10 +431,10 @@ const NoteList = forwardRef(
) : (
<div className="flex justify-center w-full mt-2">
<Button size="lg" onClick={() => {
console.log('🔄 [NoteList] Reload button clicked, refreshing feed')
logger.component('NoteList', 'Reload button clicked, refreshing feed')
// Clear relay connection state to force fresh connections
const relayUrls = subRequests.flatMap(req => req.urls)
client.clearRelayConnectionState(relayUrls)
relayUrls.forEach(url => client.clearRelayConnectionState(url))
setRefreshCount((count) => count + 1)
}}>
{t('reload notes')}

24
src/components/NoteOptions/useMenuActions.tsx

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { ExtendedKind } from '@/constants'
import { getNoteBech32Id, isProtectedEvent, getRootEventHexId } from '@/lib/event'
import { toNjump } from '@/lib/link'
import logger from '@/lib/logger'
import { pubkeyToNpub } from '@/lib/pubkey'
import { normalizeUrl, simplifyUrl } from '@/lib/url'
import { useCurrentRelays } from '@/providers/CurrentRelaysProvider'
@ -97,7 +98,7 @@ export function useMenuActions({ @@ -97,7 +98,7 @@ export function useMenuActions({
})
pinListEvent = pinListEvents[0] || null
} catch (error) {
console.warn('[PinStatus] Error fetching pin list from comprehensive relays, falling back to default method:', error)
logger.component('PinStatus', 'Error fetching pin list from comprehensive relays, falling back to default method', { error: (error as Error).message })
pinListEvent = await client.fetchPinListEvent(pubkey)
}
@ -106,7 +107,7 @@ export function useMenuActions({ @@ -106,7 +107,7 @@ export function useMenuActions({
setIsPinned(isEventPinned)
}
} catch (error) {
console.error('Error checking pin status:', error)
logger.component('PinStatus', 'Error checking pin status', { error: (error as Error).message })
}
}
checkIfPinned()
@ -141,11 +142,11 @@ export function useMenuActions({ @@ -141,11 +142,11 @@ export function useMenuActions({
})
pinListEvent = pinListEvents[0] || null
} catch (error) {
console.warn('[PinNote] Error fetching pin list from comprehensive relays, falling back to default method:', error)
logger.component('PinNote', 'Error fetching pin list from comprehensive relays, falling back to default method', { error: (error as Error).message })
pinListEvent = await client.fetchPinListEvent(pubkey)
}
console.log('[PinNote] Current pin list event:', pinListEvent)
logger.component('PinNote', 'Current pin list event', { hasEvent: !!pinListEvent })
// Get existing event IDs, excluding the one we're toggling
const existingEventIds = (pinListEvent?.tags || [])
@ -153,9 +154,9 @@ export function useMenuActions({ @@ -153,9 +154,9 @@ export function useMenuActions({
.map(tag => tag[1])
.filter(id => id !== event.id)
console.log('[PinNote] Existing event IDs (excluding current):', existingEventIds)
console.log('[PinNote] Current event ID:', event.id)
console.log('[PinNote] Is currently pinned:', isPinned)
logger.component('PinNote', 'Existing event IDs (excluding current)', { count: existingEventIds.length })
logger.component('PinNote', 'Current event ID', { eventId: event.id })
logger.component('PinNote', 'Is currently pinned', { isPinned })
let newTags: string[][]
let successMessage: string
@ -164,17 +165,16 @@ export function useMenuActions({ @@ -164,17 +165,16 @@ export function useMenuActions({
// Unpin: just keep the existing tags without this event
newTags = existingEventIds.map(id => ['e', id])
successMessage = t('Note unpinned')
console.log('[PinNote] Unpinning - new tags:', newTags)
logger.component('PinNote', 'Unpinning - new tags', { count: newTags.length })
} else {
// Pin: add this event to the existing list
newTags = [...existingEventIds.map(id => ['e', id]), ['e', event.id]]
successMessage = t('Note pinned')
console.log('[PinNote] Pinning - new tags:', newTags)
logger.component('PinNote', 'Pinning - new tags', { count: newTags.length })
}
// Create and publish the new pin list event
console.log('[PinNote] Publishing new pin list event with', newTags.length, 'tags')
console.log('[PinNote] Publishing to comprehensive relays:', comprehensiveRelays)
logger.component('PinNote', 'Publishing new pin list event', { tagCount: newTags.length, relayCount: comprehensiveRelays.length })
await publish({
kind: 10001,
tags: newTags,
@ -189,7 +189,7 @@ export function useMenuActions({ @@ -189,7 +189,7 @@ export function useMenuActions({
toast.success(successMessage)
closeDrawer()
} catch (error) {
console.error('Error pinning/unpinning note:', error)
logger.component('PinNote', 'Error pinning/unpinning note', { error: (error as Error).message })
toast.error(t('Failed to pin note'))
}
}

51
src/components/NotificationList/index.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import { ExtendedKind, NOTIFICATION_LIST_STYLE, FAST_READ_RELAY_URLS } from '@/constants'
import { compareEvents } from '@/lib/event'
import logger from '@/lib/logger'
import { usePrimaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
import { useNotification } from '@/providers/NotificationProvider'
@ -134,44 +135,36 @@ const NotificationList = forwardRef((_, ref) => { @@ -134,44 +135,36 @@ const NotificationList = forwardRef((_, ref) => {
if (userReadRelays.length > 0) {
// Priority 1: User's read/inbox relays (kind 10002)
primaryRelays = userReadRelays.slice(0, 5)
console.debug('[NotificationList] Using user read relays:', primaryRelays.length, 'relays')
logger.component('NotificationList', 'Using user read relays', {
count: primaryRelays.length,
relays: primaryRelays.slice(0, 3) // Show first 3 for brevity
})
} else if (userFavoriteRelays.length > 0) {
// Priority 2: User's favorite relays (kind 10012)
primaryRelays = userFavoriteRelays.slice(0, 5)
console.debug('[NotificationList] Using user favorite relays:', primaryRelays.length, 'relays')
logger.component('NotificationList', 'Using user favorite relays', {
count: primaryRelays.length,
relays: primaryRelays.slice(0, 3) // Show first 3 for brevity
})
} else {
// Priority 3: Fast read relays (reliable defaults)
primaryRelays = FAST_READ_RELAY_URLS.slice(0, 5)
console.debug('[NotificationList] Using fast read relays fallback:', primaryRelays.length, 'relays')
}
// Create separate subscriptions for different notification types
const subscriptions = []
// Subscription for mentions (events where user is in p-tags)
const mentionKinds = filterKinds.filter(kind => kind !== 11)
if (mentionKinds.length > 0) {
subscriptions.push({
urls: primaryRelays,
filter: {
'#p': [pubkey],
kinds: mentionKinds,
limit: LIMIT
}
})
}
// Separate subscription for discussion notifications (kind 11) - no p-tag requirement
if (filterKinds.includes(11)) {
subscriptions.push({
urls: primaryRelays,
filter: {
kinds: [11],
limit: LIMIT
}
logger.component('NotificationList', 'Using fast read relays fallback', {
count: primaryRelays.length,
relays: primaryRelays.slice(0, 3) // Show first 3 for brevity
})
}
// Create a single optimized subscription for all notification types
const subscriptions = [{
urls: primaryRelays,
filter: {
kinds: filterKinds,
limit: LIMIT,
'#p': [pubkey] // Always filter for mentions to the current user
}
}]
const { closer, timelineKey } = await client.subscribeTimeline(
subscriptions,
{

18
src/components/Profile/ProfileBookmarksAndHashtags.tsx

@ -74,7 +74,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -74,7 +74,7 @@ export default function ProfileBookmarksAndHashtags({
})
bookmarkList = bookmarkListEvents[0] || null
} catch (error) {
console.warn('[ProfileBookmarksAndHashtags] Error fetching bookmark list from comprehensive relays, falling back to default method:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching bookmark list from comprehensive relays, falling back to default method', { error: (error as Error).message })
bookmarkList = await client.fetchBookmarkListEvent(pubkey)
}
@ -110,7 +110,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -110,7 +110,7 @@ export default function ProfileBookmarksAndHashtags({
setBookmarkEvents([])
}
} catch (error) {
console.error('[ProfileBookmarksAndHashtags] Error fetching bookmarks:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching bookmarks', { error: (error as Error).message })
setBookmarkEvents([])
} finally {
setLoadingBookmarks(false)
@ -133,7 +133,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -133,7 +133,7 @@ export default function ProfileBookmarksAndHashtags({
})
interestList = interestListEvents[0] || null
} catch (error) {
console.warn('[ProfileBookmarksAndHashtags] Error fetching interest list from comprehensive relays, falling back to default method:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching interest list from comprehensive relays, falling back to default method', { error: (error as Error).message })
interestList = await client.fetchInterestListEvent(pubkey)
}
@ -143,8 +143,8 @@ export default function ProfileBookmarksAndHashtags({ @@ -143,8 +143,8 @@ export default function ProfileBookmarksAndHashtags({
if (interestList && interestList.tags.length > 0) {
// Extract hashtags from interest list
const hashtags = interestList.tags
.filter(tag => tag[0] === 't' && tag[1])
.map(tag => tag[1])
.filter((tag: string[]) => tag[0] === 't' && tag[1])
.map((tag: string[]) => tag[1])
// console.log('[ProfileBookmarksAndHashtags] Found', hashtags.length, 'interest hashtags:', hashtags)
@ -159,7 +159,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -159,7 +159,7 @@ export default function ProfileBookmarksAndHashtags({
// console.log('[ProfileBookmarksAndHashtags] Fetched', events.length, 'hashtag events')
setHashtagEvents(events)
} catch (error) {
console.warn('[ProfileBookmarksAndHashtags] Error fetching hashtag events:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching hashtag events', { error: (error as Error).message })
setHashtagEvents([])
}
} else {
@ -169,7 +169,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -169,7 +169,7 @@ export default function ProfileBookmarksAndHashtags({
setHashtagEvents([])
}
} catch (error) {
console.error('[ProfileBookmarksAndHashtags] Error fetching hashtags:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching hashtags', { error: (error as Error).message })
setHashtagEvents([])
} finally {
setLoadingHashtags(false)
@ -192,7 +192,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -192,7 +192,7 @@ export default function ProfileBookmarksAndHashtags({
})
pinList = pinListEvents[0] || null
} catch (error) {
console.warn('[ProfileBookmarksAndHashtags] Error fetching pin list from comprehensive relays, falling back to default method:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching pin list from comprehensive relays, falling back to default method', { error: (error as Error).message })
pinList = await client.fetchPinListEvent(pubkey)
}
@ -228,7 +228,7 @@ export default function ProfileBookmarksAndHashtags({ @@ -228,7 +228,7 @@ export default function ProfileBookmarksAndHashtags({
setPinEvents([])
}
} catch (error) {
console.error('[ProfileBookmarksAndHashtags] Error fetching pins:', error)
logger.component('ProfileBookmarksAndHashtags', 'Error fetching pins', { error: (error as Error).message })
setPinEvents([])
} finally {
setLoadingPins(false)

22
src/components/SimpleNoteFeed/index.tsx

@ -63,12 +63,11 @@ const SimpleNoteFeed = forwardRef< @@ -63,12 +63,11 @@ const SimpleNoteFeed = forwardRef<
setIsRefreshing(true)
try {
console.log('🔄 [SimpleNoteFeed] Starting fetch...', { authors, kinds: requestedKinds, limit })
logger.debug('[SimpleNoteFeed] Fetching events...', { authors, kinds: requestedKinds, limit })
logger.component('SimpleNoteFeed', 'Starting fetch', { authors, kinds: requestedKinds, limit })
// Get comprehensive relay list
const allRelays = await buildComprehensiveRelayList()
console.log('📡 [SimpleNoteFeed] Using relays:', allRelays.length)
logger.component('SimpleNoteFeed', 'Using relays', { count: allRelays.length })
// Build filter
const filter: any = {
@ -80,15 +79,13 @@ const SimpleNoteFeed = forwardRef< @@ -80,15 +79,13 @@ const SimpleNoteFeed = forwardRef<
filter.authors = authors
}
console.log('🔍 [SimpleNoteFeed] Using filter:', filter)
logger.debug('[SimpleNoteFeed] Using filter:', filter)
logger.component('SimpleNoteFeed', 'Using filter', filter)
// Fetch events
console.log('⏳ [SimpleNoteFeed] Calling client.fetchEvents...')
logger.component('SimpleNoteFeed', 'Calling client.fetchEvents')
const fetchedEvents = await client.fetchEvents(allRelays, [filter])
console.log('✅ [SimpleNoteFeed] Fetched', fetchedEvents.length, 'events')
logger.debug('[SimpleNoteFeed] Fetched', fetchedEvents.length, 'events')
logger.component('SimpleNoteFeed', 'Fetched events', { count: fetchedEvents.length })
// Deduplicate events by ID (same event might come from different relays)
const seenIds = new Set<string>()
@ -100,8 +97,7 @@ const SimpleNoteFeed = forwardRef< @@ -100,8 +97,7 @@ const SimpleNoteFeed = forwardRef<
return true
})
console.log('🔗 [SimpleNoteFeed] Deduplicated to', uniqueEvents.length, 'unique events')
logger.debug('[SimpleNoteFeed] Deduplicated to', uniqueEvents.length, 'unique events')
logger.component('SimpleNoteFeed', 'Deduplicated events', { count: uniqueEvents.length })
// Filter events (basic filtering)
const filteredEvents = uniqueEvents.filter(event => {
@ -116,13 +112,11 @@ const SimpleNoteFeed = forwardRef< @@ -116,13 +112,11 @@ const SimpleNoteFeed = forwardRef<
return true
})
console.log('🎯 [SimpleNoteFeed] Filtered to', filteredEvents.length, 'events')
logger.debug('[SimpleNoteFeed] Filtered to', filteredEvents.length, 'events')
logger.component('SimpleNoteFeed', 'Filtered events', { count: filteredEvents.length })
setEvents(filteredEvents)
} catch (error) {
console.error('❌ [SimpleNoteFeed] Error fetching events:', error)
logger.error('[SimpleNoteFeed] Error fetching events:', error)
logger.component('SimpleNoteFeed', 'Error fetching events', { error: (error as Error).message })
} finally {
setLoading(false)
setIsRefreshing(false)

38
src/lib/logger.ts

@ -40,9 +40,29 @@ class Logger { @@ -40,9 +40,29 @@ class Logger {
return messageLevelIndex >= currentLevelIndex
}
private getCallerInfo(): string {
const stack = new Error().stack
if (!stack) return 'unknown'
const lines = stack.split('\n')
// Skip the first 3 lines (Error, getCallerInfo, formatMessage)
// Look for the first line that contains a file path
for (let i = 3; i < lines.length; i++) {
const line = lines[i]
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/)
if (match) {
const [, functionName, filePath] = match
const fileName = filePath.split('/').pop()?.replace('.tsx', '').replace('.ts', '') || 'unknown'
return `${fileName}:${functionName}`
}
}
return 'unknown'
}
private formatMessage(level: LogLevel, message: string, ...args: any[]): [string, ...any[]] {
const timestamp = new Date().toISOString().substring(11, 23) // HH:mm:ss.SSS
const prefix = `[${timestamp}] [${level.toUpperCase()}]`
const caller = this.getCallerInfo()
const prefix = `[${timestamp}] [${level.toUpperCase()}] [${caller}]`
return [`${prefix} ${message}`, ...args]
}
@ -101,6 +121,22 @@ class Logger { @@ -101,6 +121,22 @@ class Logger {
isDebugEnabled(): boolean {
return this.config.enableDebug
}
// Context-aware logging for components
component(componentName: string, message: string, ...args: any[]): void {
if (!this.config.enableDebug) return
const timestamp = new Date().toISOString().substring(11, 23)
const caller = this.getCallerInfo()
console.log(`[${timestamp}] [COMPONENT] [${componentName}] [${caller}] ${message}`, ...args)
}
// Performance logging with context
perfComponent(componentName: string, operation: string, ...args: any[]): void {
if (!this.config.enablePerformance) return
const timestamp = new Date().toISOString().substring(11, 23)
const caller = this.getCallerInfo()
console.log(`[${timestamp}] [PERF] [${componentName}] [${caller}] ${operation}`, ...args)
}
}
// Create singleton instance

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

@ -37,7 +37,11 @@ export default function RelaysFeed() { @@ -37,7 +37,11 @@ export default function RelaysFeed() {
if (feedInfo.feedType !== 'relay' && feedInfo.feedType !== 'relays' && feedInfo.feedType !== 'all-favorites') {
return null
}
console.log('[RelaysFeed] Rendering NormalFeed with:', { subRequests, relayUrls, areAlgoRelays })
logger.component('RelaysFeed', 'Rendering NormalFeed', {
subRequests: subRequests.length,
relayUrls: relayUrls.length,
areAlgoRelays
})
return (
<NormalFeed

4
src/pages/secondary/NotePage/NotFound.tsx

@ -25,7 +25,7 @@ export default function NotFound({ @@ -25,7 +25,7 @@ export default function NotFound({
const getExternalRelays = async () => {
// Get all relays that would be tried in tiers 1-3 (already tried)
const alreadyTriedRelays = await client.getAlreadyTriedRelays()
const alreadyTriedRelays: string[] = await client.getAlreadyTriedRelays()
let externalRelays: string[] = []
@ -72,7 +72,7 @@ export default function NotFound({ @@ -72,7 +72,7 @@ export default function NotFound({
setIsSearchingExternal(true)
try {
const event = await client.fetchEventWithExternalRelays(bech32Id)
const event = await client.fetchEventWithExternalRelays(bech32Id, externalRelays)
if (event && onEventFound) {
onEventFound(event)
}

5
src/providers/BookmarksProvider.tsx

@ -2,6 +2,7 @@ import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-even @@ -2,6 +2,7 @@ import { buildATag, buildETag, createBookmarkDraftEvent } from '@/lib/draft-even
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import { normalizeUrl } from '@/lib/url'
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants'
import logger from '@/lib/logger'
import client from '@/services/client.service'
import { Event } from 'nostr-tools'
import { createContext, useCallback, useContext } from 'react'
@ -71,7 +72,7 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) { @@ -71,7 +72,7 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays)
logger.component('BookmarksProvider', 'Publishing to comprehensive relays', { count: comprehensiveRelays.length })
const newBookmarkEvent = await publish(newBookmarkDraftEvent, {
specifiedRelayUrls: comprehensiveRelays
@ -97,7 +98,7 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) { @@ -97,7 +98,7 @@ export function BookmarksProvider({ children }: { children: React.ReactNode }) {
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[BookmarksProvider] Publishing to comprehensive relays:', comprehensiveRelays)
logger.component('BookmarksProvider', 'Publishing to comprehensive relays', { count: comprehensiveRelays.length })
const newBookmarkEvent = await publish(newBookmarkDraftEvent, {
specifiedRelayUrls: comprehensiveRelays

10
src/providers/FeedProvider.tsx

@ -76,10 +76,10 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -76,10 +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)) {
console.log('[FeedProvider] Stored relay is blocked, using first visible relay instead')
logger.component('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)
logger.component('FeedProvider', 'Initial relay setup, calling switchFeed', { relayId: feedInfo.id })
return await switchFeed('relay', { relay: feedInfo.id })
}
@ -139,16 +139,16 @@ export function FeedProvider({ children }: { children: React.ReactNode }) { @@ -139,16 +139,16 @@ export function FeedProvider({ children }: { children: React.ReactNode }) {
}
const newFeedInfo = { feedType, id: normalizedUrl }
console.log('[FeedProvider] Setting relay feed info:', newFeedInfo)
logger.component('FeedProvider', 'Setting relay feed info', newFeedInfo)
setFeedInfo(newFeedInfo)
feedInfoRef.current = newFeedInfo
setRelayUrls([normalizedUrl])
console.log('[FeedProvider] Set relayUrls to:', [normalizedUrl])
logger.component('FeedProvider', 'Set relayUrls', { relayUrls: [normalizedUrl] })
storage.setFeedInfo(newFeedInfo, pubkey)
// Reset note list mode to 'posts' when switching to relay feed to ensure main content is shown
storage.setNoteListMode('posts')
setIsReady(true)
console.log('[FeedProvider] Relay feed setup complete, isReady set to true')
logger.component('FeedProvider', 'Relay feed setup complete, isReady set to true')
return
}
if (feedType === 'relays') {

35
src/providers/InterestListProvider.tsx

@ -2,6 +2,7 @@ import { createInterestListDraftEvent } from '@/lib/draft-event' @@ -2,6 +2,7 @@ import { createInterestListDraftEvent } from '@/lib/draft-event'
import { normalizeTopic } from '@/lib/discussion-topics'
import { normalizeUrl } from '@/lib/url'
import { BIG_RELAY_URLS, FAST_READ_RELAY_URLS, FAST_WRITE_RELAY_URLS } from '@/constants'
import logger from '@/lib/logger'
import client from '@/services/client.service'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -88,7 +89,7 @@ export function InterestListProvider({ children }: { children: React.ReactNode } @@ -88,7 +89,7 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
// Use the same comprehensive relay list as pins for publishing
const comprehensiveRelays = await buildComprehensiveRelayList()
console.log('[InterestListProvider] Publishing to comprehensive relays:', comprehensiveRelays)
logger.component('InterestListProvider', 'Publishing to comprehensive relays', { count: comprehensiveRelays.length })
const publishedEvent = await publish(newInterestListEvent, {
specifiedRelayUrls: comprehensiveRelays
@ -97,46 +98,46 @@ export function InterestListProvider({ children }: { children: React.ReactNode } @@ -97,46 +98,46 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
}
const subscribe = async (topic: string) => {
console.log('[InterestListProvider] subscribe called:', { topic, accountPubkey, changing })
logger.component('InterestListProvider', 'subscribe called', { topic, accountPubkey, changing })
if (!accountPubkey || changing) return
const normalizedTopic = normalizeTopic(topic)
if (subscribedTopics.has(normalizedTopic)) {
console.log('[InterestListProvider] Already subscribed to topic')
logger.component('InterestListProvider', 'Already subscribed to topic')
return
}
setChanging(true)
try {
console.log('[InterestListProvider] Fetching existing interest list event')
logger.component('InterestListProvider', 'Fetching existing interest list event')
const interestListEvent = await client.fetchInterestListEvent(accountPubkey)
console.log('[InterestListProvider] Existing interest list event:', interestListEvent)
logger.component('InterestListProvider', 'Existing interest list event', { hasEvent: !!interestListEvent })
const currentTopics = interestListEvent
? interestListEvent.tags
.filter(tag => tag[0] === 't' && tag[1])
.map(tag => normalizeTopic(tag[1]))
.filter((tag: string[]) => tag[0] === 't' && tag[1])
.map((tag: string[]) => normalizeTopic(tag[1]))
: []
console.log('[InterestListProvider] Current topics:', currentTopics)
logger.component('InterestListProvider', 'Current topics', { topics: currentTopics })
if (currentTopics.includes(normalizedTopic)) {
console.log('[InterestListProvider] Already subscribed to topic (from event)')
logger.component('InterestListProvider', 'Already subscribed to topic (from event)')
return
}
const newTopics = [...currentTopics, normalizedTopic]
console.log('[InterestListProvider] Creating new interest list with topics:', newTopics)
logger.component('InterestListProvider', 'Creating new interest list with topics', { topics: newTopics })
const newInterestListEvent = await publishNewInterestListEvent(newTopics)
console.log('[InterestListProvider] Published new interest list event:', newInterestListEvent)
logger.component('InterestListProvider', 'Published new interest list event', { hasEvent: !!newInterestListEvent })
await updateInterestListEvent(newInterestListEvent)
console.log('[InterestListProvider] Updated interest list event in state')
logger.component('InterestListProvider', 'Updated interest list event in state')
toast.success(t('Subscribed to topic'))
} catch (error) {
console.error('Failed to publish interest list event:', error)
logger.component('InterestListProvider', 'Failed to publish interest list event', { error: (error as Error).message })
// Even if publishing fails, the subscription worked locally, so show success
// The user can still see their hashtag feed working
toast.success(t('Subscribed to topic (local)'))
@ -159,10 +160,10 @@ export function InterestListProvider({ children }: { children: React.ReactNode } @@ -159,10 +160,10 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
if (!interestListEvent) return
const currentTopics = interestListEvent.tags
.filter(tag => tag[0] === 't' && tag[1])
.map(tag => normalizeTopic(tag[1]))
.filter((tag: string[]) => tag[0] === 't' && tag[1])
.map((tag: string[]) => normalizeTopic(tag[1]))
const newTopics = currentTopics.filter(t => t !== normalizedTopic)
const newTopics = currentTopics.filter((t: string) => t !== normalizedTopic)
if (newTopics.length === currentTopics.length) {
// Topic wasn't in the list
@ -174,7 +175,7 @@ export function InterestListProvider({ children }: { children: React.ReactNode } @@ -174,7 +175,7 @@ export function InterestListProvider({ children }: { children: React.ReactNode }
toast.success(t('Unsubscribed from topic'))
} catch (error) {
console.error('Failed to unsubscribe from topic:', error)
logger.component('InterestListProvider', 'Failed to unsubscribe from topic', { error: (error as Error).message })
toast.error(t('Failed to unsubscribe from topic') + ': ' + (error as Error).message)
} finally {
setChanging(false)

8
src/providers/NostrProvider/index.tsx

@ -774,9 +774,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -774,9 +774,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
const relays = await client.determineTargetRelays(event, options)
try {
const publishResult = await client.publishEvent(relays, event, {
disableFallbacks: options.disableFallbacks
})
const publishResult = await client.publishEvent(relays, event)
// Store relay status for display
if (publishResult.relayStatuses.length > 0) {
@ -786,7 +784,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { @@ -786,7 +784,9 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
// If publishing failed completely, throw an error so the form doesn't close
if (!publishResult.success) {
const error = new AggregateError(
publishResult.relayStatuses.map(s => new Error(s.error || 'Failed')),
publishResult.relayStatuses
.filter(s => !s.success)
.map(s => new Error(s.error || 'Failed')),
'Failed to publish to any relay'
)
;(error as any).relayStatuses = publishResult.relayStatuses

16
src/providers/NotificationProvider.tsx

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import { ExtendedKind, FAST_READ_RELAY_URLS } from '@/constants'
import { compareEvents } from '@/lib/event'
import logger from '@/lib/logger'
import { notificationFilter } from '@/lib/notification'
import { usePrimaryPage } from '@/PageManager'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
@ -119,15 +120,24 @@ export function NotificationProvider({ children }: { children: React.ReactNode } @@ -119,15 +120,24 @@ export function NotificationProvider({ children }: { children: React.ReactNode }
if (userReadRelays.length > 0) {
// Priority 1: User's read/inbox relays (kind 10002)
notificationRelays = userReadRelays.slice(0, 5)
console.debug('[NotificationProvider] Using user read relays:', notificationRelays.length, 'relays')
logger.component('NotificationProvider', 'Using user read relays', {
count: notificationRelays.length,
relays: notificationRelays.slice(0, 3) // Show first 3 for brevity
})
} else if (userFavoriteRelays.length > 0) {
// Priority 2: User's favorite relays (kind 10012)
notificationRelays = userFavoriteRelays.slice(0, 5)
console.debug('[NotificationProvider] Using user favorite relays:', notificationRelays.length, 'relays')
logger.component('NotificationProvider', 'Using user favorite relays', {
count: notificationRelays.length,
relays: notificationRelays.slice(0, 3) // Show first 3 for brevity
})
} else {
// Priority 3: Fast read relays (reliable defaults)
notificationRelays = FAST_READ_RELAY_URLS.slice(0, 5)
console.debug('[NotificationProvider] Using fast read relays fallback:', notificationRelays.length, 'relays')
logger.component('NotificationProvider', 'Using fast read relays fallback', {
count: notificationRelays.length,
relays: notificationRelays.slice(0, 3) // Show first 3 for brevity
})
}
// Subscribe to discussion notifications (kind 11)

1106
src/services/client.service.ts

File diff suppressed because it is too large Load Diff

1
src/services/note-stats.service.ts

@ -150,6 +150,7 @@ class NoteStatsService { @@ -150,6 +150,7 @@ class NoteStatsService {
const normalizedRelays = FAST_READ_RELAY_URLS
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
.slice(0, 2) // Limit to 2 relays for better performance and reduced load
return Array.from(new Set(normalizedRelays))
}

Loading…
Cancel
Save