|
|
|
@ -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]) |
|
|
|
|
|
|
|
|
|
|
|
|