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

13
src/components/NoteList/index.tsx

@ -995,6 +995,11 @@ const NoteList = forwardRef( @@ -995,6 +995,11 @@ const NoteList = forwardRef(
const primaryPageCtx = usePrimaryPageOptional()
const primaryPageCurrent = primaryPageCtx?.current ?? null
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. */
const clearFeedClientSearchCriteria = useCallback(() => {
@ -1890,7 +1895,7 @@ const NoteList = forwardRef( @@ -1890,7 +1895,7 @@ const NoteList = forwardRef(
timelineEstablishedCloserRef.current?.()
timelineEstablishedCloserRef.current = null
if (primaryPanelFrozen) {
if (pauseTimelineForPrimaryFreeze) {
return () => {}
}
@ -3251,12 +3256,12 @@ const NoteList = forwardRef( @@ -3251,12 +3256,12 @@ const NoteList = forwardRef(
progressiveWarmupQuery,
hostPrimaryPageName,
relayAuthoritativeFeedOnly,
primaryPanelFrozen
pauseTimelineForPrimaryFreeze
])
useEffect(() => {
if (oneShotFetch) return
if (primaryPanelFrozen) {
if (pauseTimelineForPrimaryFreeze) {
followingFeedDeltaCloserRef.current?.()
followingFeedDeltaCloserRef.current = null
return
@ -3519,7 +3524,7 @@ const NoteList = forwardRef( @@ -3519,7 +3524,7 @@ const NoteList = forwardRef(
showKind1OPs,
showKind1Replies,
showKind1111,
primaryPanelFrozen
pauseTimelineForPrimaryFreeze
])
const oneShotDebugPrevLoadingRef = useRef(false)

21
src/hooks/useProfileReportsEvents.tsx

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

187
src/hooks/useProfileWall.tsx

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

8
src/services/client.service.ts

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

Loading…
Cancel
Save