diff --git a/src/components/NoteList/index.tsx b/src/components/NoteList/index.tsx index e3f05c4..3e4e40a 100644 --- a/src/components/NoteList/index.tsx +++ b/src/components/NoteList/index.tsx @@ -244,8 +244,20 @@ const NoteList = forwardRef( needSort: !areAlgoRelays } ) + + // Add a fallback timeout to prevent infinite loading + const fallbackTimeout = setTimeout(() => { + if (loading) { + setLoading(false) + logger.debug('NoteList loading timeout - stopping after 6 seconds') + } + }, 6000) + setTimelineKey(timelineKey) - return closer + return () => { + clearTimeout(fallbackTimeout) + closer?.() + } } const promise = init() diff --git a/src/components/ReplyNoteList/index.tsx b/src/components/ReplyNoteList/index.tsx index b0c60e2..98a5766 100644 --- a/src/components/ReplyNoteList/index.tsx +++ b/src/components/ReplyNoteList/index.tsx @@ -316,7 +316,7 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even const { closer, timelineKey } = await client.subscribeTimeline( filters.map((filter) => ({ - urls: finalRelayUrls.slice(0, 8), // Increased from 5 to 8 for better coverage + urls: finalRelayUrls.slice(0, 6), // Reduced from 8 to 6 for faster response filter })), { @@ -337,8 +337,19 @@ function ReplyNoteList({ index, event, sort = 'oldest' }: { index?: number; even } } ) + + // Add a fallback timeout to prevent infinite loading + const fallbackTimeout = setTimeout(() => { + if (loading) { + setLoading(false) + logger.debug('Reply loading timeout - stopping after 8 seconds') + } + }, 8000) setTimelineKey(timelineKey) - return closer + return () => { + clearTimeout(fallbackTimeout) + closer?.() + } } catch { setLoading(false) } diff --git a/src/lib/error-suppression.ts b/src/lib/error-suppression.ts index 6b68018..b0f5c52 100644 --- a/src/lib/error-suppression.ts +++ b/src/lib/error-suppression.ts @@ -28,6 +28,21 @@ export function suppressExpectedErrors() { return } + // Suppress additional network errors that are expected + if (message.includes('net::ERR_FAILED') && ( + message.includes('404 (Not Found)') || + message.includes('302 (Found)') || + message.includes('ERR_NAME_NOT_RESOLVED') || + message.includes('ERR_CONNECTION_REFUSED') + )) { + return + } + + // Suppress postMessage origin errors + if (message.includes('Failed to execute \'postMessage\' on \'DOMWindow\'')) { + return + } + // Suppress YouTube API warnings if (message.includes('Unrecognized feature: \'web-share\'')) { return diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 27ae028..f019646 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -59,7 +59,7 @@ class ClientService extends EventTarget { ) private trendingNotesCache: NEvent[] | null = null private requestThrottle = new Map() // Track request timestamps per relay - private readonly REQUEST_COOLDOWN = 5000 // 5 second cooldown between requests to prevent "too many REQs" + private readonly REQUEST_COOLDOWN = 2000 // 2 second cooldown between requests to prevent "too many REQs" private failureCount = new Map() // Track consecutive failures per relay private readonly MAX_FAILURES = 2 // Max failures before exponential backoff (reduced from 3) private circuitBreaker = new Map() // Track when relays are temporarily disabled @@ -562,6 +562,15 @@ class ClientService extends EventTarget { let eosedCount = 0 let hasCalledOnEvents = false + // Add a global timeout for the entire subscription process + const globalTimeout = setTimeout(() => { + if (!hasCalledOnEvents && events.length === 0) { + hasCalledOnEvents = true + onEvents([], true) // Call with empty events to stop loading + logger.debug('Global subscription timeout - stopping after 8 seconds') + } + }, 8000) + const subs = await Promise.all( subRequests.map(async ({ urls, filter }) => { // Throttle subscription requests to prevent overload @@ -589,8 +598,10 @@ class ClientService extends EventTarget { // Call immediately on first events, then on threshold/completion if (!hasCalledOnEvents && events.length > 0) { hasCalledOnEvents = true + clearTimeout(globalTimeout) onEvents(events, eosedCount >= requestCount) } else if (eosedCount >= threshold) { + clearTimeout(globalTimeout) onEvents(events, eosedCount >= requestCount) } }, @@ -611,6 +622,7 @@ class ClientService extends EventTarget { return { closer: () => { + clearTimeout(globalTimeout) onEvents = () => {} onNew = () => {} subs.forEach((sub) => { @@ -680,7 +692,7 @@ class ClientService extends EventTarget { async function startSub() { startedCount++ - const relay = await that.pool.ensureRelay(url, { connectionTimeout: 1500 }).catch(() => { + const relay = await that.pool.ensureRelay(url, { connectionTimeout: 3000 }).catch(() => { return undefined }) // cannot connect to relay @@ -759,7 +771,7 @@ class ClientService extends EventTarget { } return }, - eoseTimeout: 8_000 // 8s + eoseTimeout: 5_000 // 5s (reduced from 8s) }) } }) @@ -1846,9 +1858,9 @@ class ClientService extends EventTarget { } }) - // Limit to 4 relays to prevent "too many concurrent REQs" errors - // Reduced from 8 to 4 to reduce relay load - return validRelays.slice(0, 4) + // Limit to 3 relays to prevent "too many concurrent REQs" errors and improve speed + // Reduced from 4 to 3 for faster response + return validRelays.slice(0, 3) } // ================= Utils ================= diff --git a/src/services/web.service.ts b/src/services/web.service.ts index 34c7fc9..482e063 100644 --- a/src/services/web.service.ts +++ b/src/services/web.service.ts @@ -9,9 +9,22 @@ class WebService { return await Promise.all( urls.map(async (url) => { try { + // Skip metadata fetching for known problematic domains to reduce CORS errors + const problematicDomains = [ + 'imdb.com', + 'alby.com', + 'github.com', + 'mycelium.social', + 'void.cat' + ] + + if (problematicDomains.some(domain => url.includes(domain))) { + return {} + } + // Add timeout and better error handling for CORS issues const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout + const timeoutId = setTimeout(() => controller.abort(), 3000) // 3 second timeout (reduced from 5s) const res = await fetch(url, { signal: controller.signal, @@ -60,7 +73,7 @@ class WebService { }) ) }, - { maxBatchSize: 1 } + { maxBatchSize: 1, batchScheduleFn: (callback) => setTimeout(callback, 100) } ) constructor() {