Browse Source

more efficiency fixes

imwald
Silberengel 1 month ago
parent
commit
08c5c71763
  1. 4
      src/components/Sidebar/SidebarCalendarWeekWidget.tsx
  2. 65
      src/providers/FeedProvider.tsx
  3. 10
      src/services/client.service.ts
  4. 52
      src/services/indexed-db.service.ts

4
src/components/Sidebar/SidebarCalendarWeekWidget.tsx

@ -20,7 +20,6 @@ import client from '@/services/client.service'
import { registerSessionInteractivePrewarmListener } from '@/services/session-interactive-prewarm-bridge' import { registerSessionInteractivePrewarmListener } from '@/services/session-interactive-prewarm-bridge'
import indexedDb from '@/services/indexed-db.service' import indexedDb from '@/services/indexed-db.service'
import { CALENDAR_EVENT_KINDS, ExtendedKind } from '@/constants' import { CALENDAR_EVENT_KINDS, ExtendedKind } from '@/constants'
import { appendCuratedReadOnlyRelays } from '@/pages/primary/SpellsPage/fauxSpellFeeds'
import { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react' import { CalendarDays, ChevronLeft, ChevronRight } from 'lucide-react'
import { type Event } from 'nostr-tools' import { type Event } from 'nostr-tools'
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react' import { useCallback, useEffect, useMemo, useReducer, useState } from 'react'
@ -69,7 +68,8 @@ export default function SidebarCalendarWeekWidget() {
applySocialKindBlockedFilter: false applySocialKindBlockedFilter: false
} }
) )
return appendCuratedReadOnlyRelays(base, blockedRelays).slice(0, SIDEBAR_CALENDAR_MAX_RELAYS) /** Sidebar only: avoid prepending {@link READ_ONLY_RELAY_URLS} so idle shell does not open aggregator sockets. */
return base.slice(0, SIDEBAR_CALENDAR_MAX_RELAYS)
}, [favoriteRelays, blockedRelays, relayList]) }, [favoriteRelays, blockedRelays, relayList])
const relayKey = useMemo(() => [...relayUrls].sort().join('|'), [relayUrls]) const relayKey = useMemo(() => [...relayUrls].sort().join('|'), [relayUrls])

65
src/providers/FeedProvider.tsx

@ -67,7 +67,10 @@ export function FeedProvider({ children }: { children: ReactNode }) {
[favoriteRelays, relaySets] [favoriteRelays, relaySets]
) )
/** Home Notes/Gallery stay focused: favorites/defaults plus the mixed trending relay. */ /**
* Mixed trending slice (nostrarchives / Wisp-style feed) so the home timeline isnt only the users
* graph keeps a finger on what the wider network is surfacing, alongside favorites / NIP-65.
*/
const primaryExtraRelayUrls = useMemo(() => [buildWispTrendingNotesRelayUrl()], []) const primaryExtraRelayUrls = useMemo(() => [buildWispTrendingNotesRelayUrl()], [])
/** Home Replies widen to relays that can surface inbox/reply context. */ /** Home Replies widen to relays that can surface inbox/reply context. */
@ -182,6 +185,8 @@ export function FeedProvider({ children }: { children: ReactNode }) {
) )
const lastRelayInitDebugKey = useRef('') const lastRelayInitDebugKey = useRef('')
const lastHadFavoriteRelaysRef = useRef<boolean | null>(null) const lastHadFavoriteRelaysRef = useRef<boolean | null>(null)
const relayUrlDebounceTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
useEffect(() => { useEffect(() => {
const initKey = [ const initKey = [
isInitialized ? '1' : '0', isInitialized ? '1' : '0',
@ -194,29 +199,47 @@ export function FeedProvider({ children }: { children: ReactNode }) {
replyExtraRelayLayers.httpRelayUrls.length, replyExtraRelayLayers.httpRelayUrls.length,
blockedRelays.length blockedRelays.length
].join('\x1e') ].join('\x1e')
if (initKey !== lastRelayInitDebugKey.current) {
lastRelayInitDebugKey.current = initKey
logger.debug('FeedProvider relay init:', {
isInitialized,
favoriteRelays: favoriteRelays.length,
relaySets: relaySets.length,
relaySetRelays: favoriteFeedRelayUrls.length - favoriteRelays.length,
inboxRelays: replyExtraRelayLayers.inboxRelayUrls.length,
outboxRelays: replyExtraRelayLayers.outboxRelayUrls.length,
cacheRelays: replyExtraRelayLayers.cacheRelayUrls.length,
httpRelays: replyExtraRelayLayers.httpRelayUrls.length,
blockedRelays: blockedRelays.length
})
}
const hasFavoriteRelays = favoriteFeedRelayUrls.length > 0 const flush = () => {
const prevHad = lastHadFavoriteRelaysRef.current if (initKey !== lastRelayInitDebugKey.current) {
lastHadFavoriteRelaysRef.current = hasFavoriteRelays lastRelayInitDebugKey.current = initKey
if (!hasFavoriteRelays && prevHad !== false) { logger.debug('FeedProvider relay init:', {
logger.debug('FeedProvider: no favorite or relay-set relays, using defaults') isInitialized,
favoriteRelays: favoriteRelays.length,
relaySets: relaySets.length,
relaySetRelays: favoriteFeedRelayUrls.length - favoriteRelays.length,
inboxRelays: replyExtraRelayLayers.inboxRelayUrls.length,
outboxRelays: replyExtraRelayLayers.outboxRelayUrls.length,
cacheRelays: replyExtraRelayLayers.cacheRelayUrls.length,
httpRelays: replyExtraRelayLayers.httpRelayUrls.length,
blockedRelays: blockedRelays.length
})
}
const hasFavoriteRelays = favoriteFeedRelayUrls.length > 0
const prevHad = lastHadFavoriteRelaysRef.current
lastHadFavoriteRelaysRef.current = hasFavoriteRelays
if (!hasFavoriteRelays && prevHad !== false) {
logger.debug('FeedProvider: no favorite or relay-set relays, using defaults')
}
updateFeedRelayUrls()
} }
updateFeedRelayUrls() if (relayUrlDebounceTimerRef.current) {
clearTimeout(relayUrlDebounceTimerRef.current)
}
relayUrlDebounceTimerRef.current = setTimeout(() => {
relayUrlDebounceTimerRef.current = null
flush()
}, 80)
return () => {
if (relayUrlDebounceTimerRef.current) {
clearTimeout(relayUrlDebounceTimerRef.current)
relayUrlDebounceTimerRef.current = null
}
}
}, [isInitialized, favoriteRelaysIdentity, blockedRelaysIdentity, replyExtraRelaysIdentity, updateFeedRelayUrls]) }, [isInitialized, favoriteRelaysIdentity, blockedRelaysIdentity, replyExtraRelaysIdentity, updateFeedRelayUrls])
return ( return (

10
src/services/client.service.ts

@ -483,7 +483,15 @@ class ClientService extends EventTarget {
if (!this.sessionPrewarmBaseCompleted) { if (!this.sessionPrewarmBaseCompleted) {
this.sessionPrewarmBaseCompleted = true this.sessionPrewarmBaseCompleted = true
fastTasks.push(this.prewarmProfileSearchIndexFromIdb(), this.fetchNip66RelayDiscovery()) fastTasks.push(this.prewarmProfileSearchIndexFromIdb())
/** NIP-66 discovery hits extra relays; defer so first feed/session work is not competing for sockets. */
if (typeof window !== 'undefined') {
window.setTimeout(() => {
void this.fetchNip66RelayDiscovery()
}, 12_000)
} else {
void this.fetchNip66RelayDiscovery()
}
} }
if (fastTasks.length === 0 && !options.pubkey) { if (fastTasks.length === 0 && !options.pubkey) {

52
src/services/indexed-db.service.ts

@ -811,35 +811,53 @@ class IndexedDbService {
}) })
} }
async iterateProfileEvents(callback: (event: Event) => Promise<void>): Promise<void> { /**
* Loads all cached kind-0 rows in one synchronous cursor pass (no `await` inside `onsuccess`, which
* would risk inactive transactions), then invokes `callback` in chunks with `requestAnimationFrame`
* yields so FlexSearch indexing does not monopolize the main thread.
*/
async iterateProfileEvents(callback: (event: Event) => void | Promise<void>): Promise<void> {
await this.initPromise await this.initPromise
if (!this.db) { if (!this.db) {
return return
} }
return new Promise<void>((resolve, reject) => { const events = await new Promise<Event[]>((resolve, reject) => {
const transaction = this.db!.transaction(StoreNames.PROFILE_EVENTS, 'readwrite') const out: Event[] = []
const transaction = this.db!.transaction(StoreNames.PROFILE_EVENTS, 'readonly')
const store = transaction.objectStore(StoreNames.PROFILE_EVENTS) const store = transaction.objectStore(StoreNames.PROFILE_EVENTS)
const request = store.openCursor() const request = store.openCursor()
request.onsuccess = (event) => { request.onsuccess = (event) => {
const cursor = (event.target as IDBRequest).result const cursor = (event.target as IDBRequest).result as IDBCursorWithValue | null
if (cursor) { if (!cursor) {
const value = (cursor.value as TValue<Event>).value resolve(out)
if (value) { return
callback(value)
}
cursor.continue()
} else {
transaction.commit()
resolve()
} }
const value = (cursor.value as TValue<Event>).value
if (value) out.push(value)
cursor.continue()
} }
request.onerror = () => {
request.onerror = (event) => { reject(request.error ?? new Error('iterateProfileEvents: cursor failed'))
transaction.commit()
reject(event)
} }
}) })
const yieldToMain = () =>
new Promise<void>((resolve) => {
if (typeof requestAnimationFrame === 'function') {
requestAnimationFrame(() => resolve())
} else {
setTimeout(resolve, 0)
}
})
const chunkYieldEvery = 150
for (let i = 0; i < events.length; i++) {
await Promise.resolve(callback(events[i]!))
if (i > 0 && (i + 1) % chunkYieldEvery === 0) {
await yieldToMain()
}
}
} }
async putFollowingFavoriteRelays(pubkey: string, relays: [string, string[]][]): Promise<void> { async putFollowingFavoriteRelays(pubkey: string, relays: [string, string[]][]): Promise<void> {

Loading…
Cancel
Save