Browse Source

fix badges and reports

imwald
Silberengel 4 weeks ago
parent
commit
1fe8c5df56
  1. 1
      nip66-cron/index.mjs
  2. 1
      src/constants.ts
  3. 38
      src/hooks/useProfileReportsEvents.tsx
  4. 45
      src/hooks/useProfileWall.tsx
  5. 24
      src/lib/nip58-profile-badges-list.ts
  6. 8
      src/lib/replaceable-list-latest.ts

1
nip66-cron/index.mjs

@ -76,7 +76,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [ @@ -76,7 +76,6 @@ const DEFAULT_RELAYS_TO_MONITOR = [
/** Relays to publish 30166/10166 and to REQ kind 10002 from; broad enough for Imwald + NIP-66 discovery. */
const DEFAULT_PUBLISH_RELAYS = [
'wss://nos.lol',
'wss://orly-relay.imwald.eu',
'wss://relay.damus.io',
'wss://relay.nostr.watch',
'wss://relay.primal.net',

1
src/constants.ts

@ -493,7 +493,6 @@ export const GIF_RELAY_URLS = [ @@ -493,7 +493,6 @@ export const GIF_RELAY_URLS = [
export const SEARCHABLE_RELAY_URLS = [
'wss://search.nos.today',
'wss://nostr.wine',
'wss://orly-relay.imwald.eu',
'wss://relay.noswhere.com',
'wss://nostr-pub.wellorder.net',
]

38
src/hooks/useProfileReportsEvents.tsx

@ -89,10 +89,20 @@ export function useProfileReportsEvents({ @@ -89,10 +89,20 @@ export function useProfileReportsEvents({
const receivedCached = memoryByKey.get(receivedCacheKey)
const madeCached = memoryByKey.get(madeCacheKey)
const [received, setReceived] = useState<Event[]>(receivedCached?.events ?? [])
const [made, setMade] = useState<Event[]>(madeCached?.events ?? [])
const [isLoading, setIsLoading] = useState(!receivedCached || !madeCached)
const reportsCacheHasRows =
(receivedCached?.events.length ?? 0) + (madeCached?.events.length ?? 0) > 0
const reportsCacheFresh =
!!receivedCached &&
!!madeCached &&
Date.now() - receivedCached.lastUpdated < CACHE_DURATION &&
Date.now() - madeCached.lastUpdated < CACHE_DURATION
const hasUsefulReportsCache = reportsCacheHasRows && reportsCacheFresh
const [received, setReceived] = useState<Event[]>(
hasUsefulReportsCache ? receivedCached!.events : []
)
const [made, setMade] = useState<Event[]>(hasUsefulReportsCache ? madeCached!.events : [])
const [isLoading, setIsLoading] = useState(!hasUsefulReportsCache)
const [refreshToken, setRefreshToken] = useState(0)
const includeAuthorLocalRelays = useMemo(() => {
@ -118,6 +128,7 @@ export function useProfileReportsEvents({ @@ -118,6 +128,7 @@ export function useProfileReportsEvents({
blockedRelaysRef.current = blockedRelays
const useGlobalRelayBootstrapRef = useRef(useGlobalRelayBootstrap)
useGlobalRelayBootstrapRef.current = useGlobalRelayBootstrap
const runGenRef = useRef(0)
const resolveFeedUrls = useCallback(
(
@ -174,6 +185,7 @@ export function useProfileReportsEvents({ @@ -174,6 +185,7 @@ export function useProfileReportsEvents({
useEffect(() => {
let cancelled = false
const runGen = ++runGenRef.current
const loadMode = async (
mode: FetchMode,
@ -196,7 +208,11 @@ export function useProfileReportsEvents({ @@ -196,7 +208,11 @@ export function useProfileReportsEvents({
isEventDeletedRef.current,
postFilter(pubkey, mode)
)
memoryByKey.set(cacheKey, { events: processed, lastUpdated: Date.now() })
if (processed.length > 0) {
memoryByKey.set(cacheKey, { events: processed, lastUpdated: Date.now() })
} else {
memoryByKey.delete(cacheKey)
}
setEvents((prev) => (eventsEqualById(prev, processed) ? prev : processed))
}
@ -284,15 +300,21 @@ export function useProfileReportsEvents({ @@ -284,15 +300,21 @@ export function useProfileReportsEvents({
const madeMem = memoryByKey.get(madeCacheKey)
const recvFresh = recvMem && Date.now() - recvMem.lastUpdated < CACHE_DURATION
const madeFresh = madeMem && Date.now() - madeMem.lastUpdated < CACHE_DURATION
const cachedAny =
(recvMem?.events.length ?? 0) + (madeMem?.events.length ?? 0) > 0
if (recvFresh && recvMem) {
setReceived(recvMem.events)
} else if (recvMem?.events.length === 0) {
memoryByKey.delete(receivedCacheKey)
}
if (madeFresh && madeMem) {
setMade(madeMem.events)
} else if (madeMem?.events.length === 0) {
memoryByKey.delete(madeCacheKey)
}
if (recvFresh && madeFresh && refreshToken === 0) {
setIsLoading(false)
if (recvFresh && madeFresh && refreshToken === 0 && cachedAny) {
if (runGen === runGenRef.current) setIsLoading(false)
return
}
@ -303,7 +325,7 @@ export function useProfileReportsEvents({ @@ -303,7 +325,7 @@ export function useProfileReportsEvents({
loadMode('made', madeCacheKey, setMade)
])
} finally {
if (!cancelled) setIsLoading(false)
if (!cancelled && runGen === runGenRef.current) setIsLoading(false)
}
}

45
src/hooks/useProfileWall.tsx

@ -92,10 +92,16 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine @@ -92,10 +92,16 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
const cacheKey = useMemo(() => `${pubkey}-profile-wall-v1`, [pubkey])
const cached = wallCacheByKey.get(cacheKey)
const hasUsefulWallCache =
!!cached &&
cached.badges.length > 0 &&
Date.now() - cached.lastUpdated < CACHE_DURATION
const [badges, setBadges] = useState<ResolvedProfileBadge[]>(cached?.badges ?? [])
const [comments, setComments] = useState<Event[]>(cached?.comments ?? [])
const [isLoading, setIsLoading] = useState(!cached)
const [badges, setBadges] = useState<ResolvedProfileBadge[]>(
hasUsefulWallCache ? cached!.badges : []
)
const [comments, setComments] = useState<Event[]>(hasUsefulWallCache ? cached!.comments : [])
const [isLoading, setIsLoading] = useState(!hasUsefulWallCache)
const [refreshToken, setRefreshToken] = useState(0)
const relayListsKey = useMemo(
@ -108,18 +114,29 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine @@ -108,18 +114,29 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
blockedRelaysRef.current = blockedRelays
const useGlobalRelayBootstrapRef = useRef(useGlobalRelayBootstrap)
useGlobalRelayBootstrapRef.current = useGlobalRelayBootstrap
const runGenRef = useRef(0)
useEffect(() => {
let cancelled = false
const runGen = ++runGenRef.current
const run = async () => {
const mem = wallCacheByKey.get(cacheKey)
if (mem && Date.now() - mem.lastUpdated < CACHE_DURATION && refreshToken === 0) {
// Do not reuse empty cache (transient abort when secondary panel opens used to cache [] for 5m).
if (
mem &&
mem.badges.length > 0 &&
Date.now() - mem.lastUpdated < CACHE_DURATION &&
refreshToken === 0
) {
setBadges(mem.badges)
setComments(mem.comments)
setIsLoading(false)
if (runGen === runGenRef.current) setIsLoading(false)
return
}
if (mem?.badges.length === 0) {
wallCacheByKey.delete(cacheKey)
}
setIsLoading(true)
try {
@ -148,7 +165,7 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine @@ -148,7 +165,7 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
)
// --- 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, { foreground: true })
if (!listEvent || !isNip58ProfileBadgesListEvent(listEvent)) {
const legacy = await fetchLegacyProfileBadgesListEvent(pkNorm, relayUrls)
if (legacy && isNip58ProfileBadgesListEvent(legacy)) listEvent = legacy
@ -208,13 +225,17 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine @@ -208,13 +225,17 @@ export function useProfileWall(pubkey: string, profileEventId: string | undefine
if (cancelled) return
setBadges(resolvedBadges)
setComments(wallComments)
wallCacheByKey.set(cacheKey, {
badges: resolvedBadges,
comments: wallComments,
lastUpdated: Date.now()
})
if (resolvedBadges.length > 0 || wallComments.length > 0) {
wallCacheByKey.set(cacheKey, {
badges: resolvedBadges,
comments: wallComments,
lastUpdated: Date.now()
})
} else {
wallCacheByKey.delete(cacheKey)
}
} finally {
if (!cancelled) setIsLoading(false)
if (!cancelled && runGen === runGenRef.current) setIsLoading(false)
}
}

24
src/lib/nip58-profile-badges-list.ts

@ -13,6 +13,7 @@ import { normalizeHexPubkey } from '@/lib/pubkey' @@ -13,6 +13,7 @@ import { normalizeHexPubkey } from '@/lib/pubkey'
import { fetchLatestReplaceableListEvent } from '@/lib/replaceable-list-latest'
import { normalizeAnyRelayUrl } from '@/lib/url'
import client, { replaceableEventService } from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import type { Event } from 'nostr-tools'
export function profileBadgeEntriesToTags(entries: ProfileBadgeEntry[]): string[][] {
@ -42,19 +43,31 @@ export function profileBadgeListTagsAfterRemovingEntry( @@ -42,19 +43,31 @@ export function profileBadgeListTagsAfterRemovingEntry(
export async function fetchProfileBadgesListEvent(
pubkeyHex: string,
relayUrls: string[]
relayUrls: string[],
options?: { foreground?: boolean }
): Promise<Event | undefined> {
const pk = normalizeHexPubkey(pubkeyHex)
const foreground = options?.foreground === true
let cached: Event | undefined
try {
cached =
const disk = await indexedDb.getReplaceableEvent(pk, ExtendedKind.PROFILE_BADGES_LIST)
if (disk) cached = disk
} catch {
cached = undefined
}
try {
const fromService =
(await replaceableEventService.fetchReplaceableEvent(pk, ExtendedKind.PROFILE_BADGES_LIST)) ??
undefined
if (!cached) cached = fromService
else if (fromService && fromService.created_at >= cached.created_at) cached = fromService
} catch {
cached = undefined
/* best-effort */
}
const fromRelays = relayUrls.length
? await fetchLatestReplaceableListEvent(pk, ExtendedKind.PROFILE_BADGES_LIST, relayUrls)
? await fetchLatestReplaceableListEvent(pk, ExtendedKind.PROFILE_BADGES_LIST, relayUrls, {
foreground
})
: undefined
if (!cached) return fromRelays
if (!fromRelays) return cached
@ -93,7 +106,8 @@ export async function fetchLegacyProfileBadgesListEvent( @@ -93,7 +106,8 @@ export async function fetchLegacyProfileBadgesListEvent(
{
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
}
)

8
src/lib/replaceable-list-latest.ts

@ -41,7 +41,8 @@ function newestReplaceableEvent(candidates: Event[]): Event | undefined { @@ -41,7 +41,8 @@ function newestReplaceableEvent(candidates: Event[]): Event | undefined {
export async function fetchLatestReplaceableListEvent(
pubkeyHex: string,
kind: number,
relayUrls: string[]
relayUrls: string[],
options?: { foreground?: boolean }
): Promise<Event | undefined> {
const pk = normalizeHexPubkey(pubkeyHex)
const allUrls = [...new Set(relayUrls.map((u) => normalizeAnyRelayUrl(u) || u).filter(Boolean))]
@ -50,7 +51,10 @@ export async function fetchLatestReplaceableListEvent( @@ -50,7 +51,10 @@ export async function fetchLatestReplaceableListEvent(
const rows = await client.fetchEvents(
allUrls,
{ authors: [pk], kinds: networkKindsForReplaceableFetch(kind), limit: 80 },
replaceableListFetchQueryOpts(kind)
{
...replaceableListFetchQueryOpts(kind),
...(options?.foreground ? { foreground: true } : {})
}
)
return newestReplaceableEvent(rows.filter((e) => e.kind === kind))

Loading…
Cancel
Save