Browse Source

fix profile threads

imwald
Silberengel 4 weeks ago
parent
commit
fedba2eedc
  1. 2
      src/PageManager.tsx
  2. 13
      src/components/NoteList/index.tsx
  3. 21
      src/hooks/useProfileReportsEvents.tsx
  4. 187
      src/hooks/useProfileWall.tsx
  5. 8
      src/services/client.service.ts

2
src/PageManager.tsx

@ -2165,7 +2165,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
const primaryFrozen = const primaryFrozen =
secondaryStack.length > 0 && (isSmallScreen || panelMode === 'double') secondaryStack.length > 0 && (isSmallScreen || panelMode === 'double')
useEffect(() => { useLayoutEffect(() => {
noteStatsService.setBackgroundStatsPaused(primaryFrozen) noteStatsService.setBackgroundStatsPaused(primaryFrozen)
if (primaryFrozen) { if (primaryFrozen) {
client.interruptBackgroundQueries() client.interruptBackgroundQueries()

13
src/components/NoteList/index.tsx

@ -995,6 +995,11 @@ const NoteList = forwardRef(
const primaryPageCtx = usePrimaryPageOptional() const primaryPageCtx = usePrimaryPageOptional()
const primaryPageCurrent = primaryPageCtx?.current ?? null const primaryPageCurrent = primaryPageCtx?.current ?? null
const primaryPanelFrozen = primaryPageCtx?.frozen ?? false const primaryPanelFrozen = primaryPageCtx?.frozen ?? false
/** Only pause timelines on the active primary page feed — not secondary-panel profiles, search, etc. */
const pauseTimelineForPrimaryFreeze =
primaryPanelFrozen &&
hostPrimaryPageName != null &&
hostPrimaryPageName === primaryPageCurrent
/** Clears text/author/time/full-search; does not change panel open state. */ /** Clears text/author/time/full-search; does not change panel open state. */
const clearFeedClientSearchCriteria = useCallback(() => { const clearFeedClientSearchCriteria = useCallback(() => {
@ -1890,7 +1895,7 @@ const NoteList = forwardRef(
timelineEstablishedCloserRef.current?.() timelineEstablishedCloserRef.current?.()
timelineEstablishedCloserRef.current = null timelineEstablishedCloserRef.current = null
if (primaryPanelFrozen) { if (pauseTimelineForPrimaryFreeze) {
return () => {} return () => {}
} }
@ -3251,12 +3256,12 @@ const NoteList = forwardRef(
progressiveWarmupQuery, progressiveWarmupQuery,
hostPrimaryPageName, hostPrimaryPageName,
relayAuthoritativeFeedOnly, relayAuthoritativeFeedOnly,
primaryPanelFrozen pauseTimelineForPrimaryFreeze
]) ])
useEffect(() => { useEffect(() => {
if (oneShotFetch) return if (oneShotFetch) return
if (primaryPanelFrozen) { if (pauseTimelineForPrimaryFreeze) {
followingFeedDeltaCloserRef.current?.() followingFeedDeltaCloserRef.current?.()
followingFeedDeltaCloserRef.current = null followingFeedDeltaCloserRef.current = null
return return
@ -3519,7 +3524,7 @@ const NoteList = forwardRef(
showKind1OPs, showKind1OPs,
showKind1Replies, showKind1Replies,
showKind1111, showKind1111,
primaryPanelFrozen pauseTimelineForPrimaryFreeze
]) ])
const oneShotDebugPrevLoadingRef = useRef(false) const oneShotDebugPrevLoadingRef = useRef(false)

21
src/hooks/useProfileReportsEvents.tsx

@ -236,7 +236,8 @@ export function useProfileReportsEvents({
const fetched = await client.fetchEvents(provisionalUrls, filter, { const fetched = await client.fetchEvents(provisionalUrls, filter, {
cache: true, cache: true,
eoseTimeout: 4500, eoseTimeout: 4500,
globalTimeout: 14_000 globalTimeout: 14_000,
foreground: true
}) })
if (!cancelled) { if (!cancelled) {
for (const e of fetched) pool.set(e.id, e) for (const e of fetched) pool.set(e.id, e)
@ -266,7 +267,8 @@ export function useProfileReportsEvents({
const fetchedDelta = await client.fetchEvents(deltaUrls, filter, { const fetchedDelta = await client.fetchEvents(deltaUrls, filter, {
cache: true, cache: true,
eoseTimeout: 4500, eoseTimeout: 4500,
globalTimeout: 14_000 globalTimeout: 14_000,
foreground: true
}) })
if (!cancelled) { if (!cancelled) {
for (const e of fetchedDelta) pool.set(e.id, e) for (const e of fetchedDelta) pool.set(e.id, e)
@ -295,13 +297,14 @@ export function useProfileReportsEvents({
} }
setIsLoading(true) setIsLoading(true)
try {
await Promise.all([ await Promise.all([
loadMode('received', receivedCacheKey, setReceived), loadMode('received', receivedCacheKey, setReceived),
loadMode('made', madeCacheKey, setMade) loadMode('made', madeCacheKey, setMade)
]) ])
} finally {
if (!cancelled) setIsLoading(false) if (!cancelled) setIsLoading(false)
}
} }
void run() void run()

187
src/hooks/useProfileWall.tsx

@ -65,7 +65,8 @@ async function fetchBadgeDefinitionOnRelays(
{ {
replaceableRace: true, replaceableRace: true,
eoseTimeout: METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS, eoseTimeout: METADATA_BATCH_QUERY_EOSE_TIMEOUT_MS,
globalTimeout: METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS globalTimeout: METADATA_BATCH_QUERY_GLOBAL_TIMEOUT_MS,
foreground: true
} }
) )
const matches = rows.filter((e) => e.kind === ExtendedKind.BADGE_DEFINITION) const matches = rows.filter((e) => e.kind === ExtendedKind.BADGE_DEFINITION)
@ -110,8 +111,6 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
useEffect(() => { useEffect(() => {
let cancelled = false let cancelled = false
let idleHandle: number | undefined
let idleTimeout: ReturnType<typeof setTimeout> | undefined
const run = async () => { const run = async () => {
const mem = wallCacheByKey.get(cacheKey) const mem = wallCacheByKey.get(cacheKey)
@ -123,112 +122,106 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
} }
setIsLoading(true) setIsLoading(true)
try {
const pkNorm = userIdToPubkey(pubkey) || pubkey
if (!isValidPubkey(pkNorm)) {
return
}
const pkNorm = userIdToPubkey(pubkey) || pubkey const emptyAuthor = {
if (!isValidPubkey(pkNorm)) { read: [] as string[],
if (!cancelled) setIsLoading(false) write: [] as string[],
return httpRead: [] as string[],
} httpWrite: [] as string[]
}
const emptyAuthor = { read: [] as string[], write: [] as string[], httpRead: [] as string[], httpWrite: [] as string[] } const authorRl = await client.peekRelayListFromStorage(pubkey).catch(() => emptyAuthor)
const authorRl = await client.peekRelayListFromStorage(pubkey).catch(() => emptyAuthor) if (cancelled) return
if (cancelled) return
const relayUrls = buildProfilePageReadRelayUrls(
const relayUrls = buildProfilePageReadRelayUrls( favoriteRelaysRef.current,
favoriteRelaysRef.current, blockedRelaysRef.current,
blockedRelaysRef.current, authorRl,
authorRl, false,
false, false,
false, [ExtendedKind.COMMENT, ExtendedKind.PROFILE_BADGES_LIST, ExtendedKind.BADGE_DEFINITION],
[ExtendedKind.COMMENT, ExtendedKind.PROFILE_BADGES_LIST, ExtendedKind.BADGE_DEFINITION], useGlobalRelayBootstrapRef.current
useGlobalRelayBootstrapRef.current )
)
// --- Badges (NIP-58): IndexedDB + profile read relays (favorites / fast-read), not inbox-only ---
// --- Badges (NIP-58): IndexedDB + profile read relays (favorites / fast-read), not inbox-only --- let listEvent = await fetchProfileBadgesListEvent(pkNorm, relayUrls)
let listEvent = await fetchProfileBadgesListEvent(pkNorm, relayUrls) if (!listEvent || !isNip58ProfileBadgesListEvent(listEvent)) {
if (!listEvent || !isNip58ProfileBadgesListEvent(listEvent)) { const legacy = await fetchLegacyProfileBadgesListEvent(pkNorm, relayUrls)
const legacy = await fetchLegacyProfileBadgesListEvent(pkNorm, relayUrls) if (legacy && isNip58ProfileBadgesListEvent(legacy)) listEvent = legacy
if (legacy && isNip58ProfileBadgesListEvent(legacy)) listEvent = legacy }
}
const entries = parseProfileBadgeEntries(listEvent)
const defCoords = [...new Set(entries.map((e) => e.definitionCoordinate))]
const defByCoord = new Map<string, Event | undefined>()
await Promise.all( const entries = parseProfileBadgeEntries(listEvent)
defCoords.map(async (coord) => { const defCoords = [...new Set(entries.map((e) => e.definitionCoordinate))]
defByCoord.set(coord, await fetchBadgeDefinitionOnRelays(coord, relayUrls)) const defByCoord = new Map<string, Event | undefined>()
})
) await Promise.all(
defCoords.map(async (coord) => {
const resolvedBadges = entries.map((entry) => defByCoord.set(coord, await fetchBadgeDefinitionOnRelays(coord, relayUrls))
resolveBadgeDisplayFromDefinition(entry, defByCoord.get(entry.definitionCoordinate)) })
) )
// --- Wall comments (kind 1111 on profile kind 0) --- const resolvedBadges = entries.map((entry) =>
let wallComments: Event[] = [] resolveBadgeDisplayFromDefinition(entry, defByCoord.get(entry.definitionCoordinate))
const profileId = profileEventId?.trim().toLowerCase() )
if (profileId && /^[0-9a-f]{64}$/.test(profileId) && relayUrls.length > 0) {
const profileCoord = getReplaceableCoordinate(kinds.Metadata, pkNorm, '') // --- Wall comments (kind 1111 on profile kind 0) ---
const filters: Filter[] = [ let wallComments: Event[] = []
{ kinds: [ExtendedKind.COMMENT], '#e': [profileId], limit: 200 }, const profileId = profileEventId?.trim().toLowerCase()
{ kinds: [ExtendedKind.COMMENT], '#a': [profileCoord], limit: 200 } if (profileId && /^[0-9a-f]{64}$/.test(profileId) && relayUrls.length > 0) {
] const profileCoord = getReplaceableCoordinate(kinds.Metadata, pkNorm, '')
const pool = new Map<string, Event>() const filters: Filter[] = [
try { { kinds: [ExtendedKind.COMMENT], '#e': [profileId], limit: 200 },
const rows = await Promise.all( { kinds: [ExtendedKind.COMMENT], '#a': [profileCoord], limit: 200 }
filters.map((filter) => ]
client.fetchEvents(relayUrls, filter, { const pool = new Map<string, Event>()
cache: true, try {
eoseTimeout: 4500, const rows = await Promise.all(
globalTimeout: 14_000 filters.map((filter) =>
}) client.fetchEvents(relayUrls, filter, {
cache: true,
eoseTimeout: 4500,
globalTimeout: 14_000,
foreground: true
})
)
) )
) for (const batch of rows) {
for (const batch of rows) { for (const e of batch) pool.set(e.id, e)
for (const e of batch) pool.set(e.id, e) }
} catch {
/* ignore */
} }
} catch {
/* ignore */ wallComments = [...pool.values()]
.filter(
(e) =>
!isEventDeletedRef.current(e) &&
isDirectProfileWallComment(e, profileId, pkNorm)
)
.sort((a, b) => b.created_at - a.created_at)
} }
wallComments = [...pool.values()] if (cancelled) return
.filter( setBadges(resolvedBadges)
(e) => setComments(wallComments)
!isEventDeletedRef.current(e) && wallCacheByKey.set(cacheKey, {
isDirectProfileWallComment(e, profileId, pkNorm) badges: resolvedBadges,
) comments: wallComments,
.sort((a, b) => b.created_at - a.created_at) lastUpdated: Date.now()
})
} finally {
if (!cancelled) setIsLoading(false)
} }
if (cancelled) return
setBadges(resolvedBadges)
setComments(wallComments)
wallCacheByKey.set(cacheKey, {
badges: resolvedBadges,
comments: wallComments,
lastUpdated: Date.now()
})
setIsLoading(false)
} }
const scheduleRun = () => { void run()
if (typeof requestIdleCallback === 'function') {
idleHandle = requestIdleCallback(() => void run(), { timeout: 4_000 })
} else {
idleTimeout = setTimeout(() => void run(), 400)
}
}
scheduleRun()
return () => { return () => {
cancelled = true cancelled = true
if (idleHandle !== undefined && typeof cancelIdleCallback === 'function') {
cancelIdleCallback(idleHandle)
}
if (idleTimeout !== undefined) {
clearTimeout(idleTimeout)
}
} }
}, [pubkey, profileEventId, cacheKey, refreshToken, relayListsKey]) }, [pubkey, profileEventId, cacheKey, refreshToken, relayListsKey])

8
src/services/client.service.ts

@ -3419,7 +3419,8 @@ class ClientService extends EventTarget {
globalTimeout, globalTimeout,
firstRelayResultGraceMs, firstRelayResultGraceMs,
replaceableRace, replaceableRace,
immediateReturn immediateReturn,
foreground
}: { }: {
onevent?: (evt: NEvent) => void onevent?: (evt: NEvent) => void
cache?: boolean cache?: boolean
@ -3428,6 +3429,8 @@ class ClientService extends EventTarget {
firstRelayResultGraceMs?: number | false firstRelayResultGraceMs?: number | false
replaceableRace?: boolean replaceableRace?: boolean
immediateReturn?: boolean immediateReturn?: boolean
/** When true, ignore {@link QueryService.interruptBackgroundQueries} (e.g. secondary-panel profile loads). */
foreground?: boolean
} = {} } = {}
) { ) {
const originalDedupedRelays = Array.from(new Set(urls)) const originalDedupedRelays = Array.from(new Set(urls))
@ -3467,7 +3470,8 @@ class ClientService extends EventTarget {
globalTimeout, globalTimeout,
firstRelayResultGraceMs, firstRelayResultGraceMs,
replaceableRace, replaceableRace,
immediateReturn immediateReturn,
foreground
}) })
if (cache) { if (cache) {
events.forEach((evt) => { events.forEach((evt) => {

Loading…
Cancel
Save