|
|
|
@ -9,10 +9,18 @@ import { generateBech32IdFromATag } from '@/lib/tag' |
|
|
|
import { isReplaceableEvent } from '@/lib/event' |
|
|
|
import { isReplaceableEvent } from '@/lib/event' |
|
|
|
import client from '@/services/client.service' |
|
|
|
import client from '@/services/client.service' |
|
|
|
import { eventService } from '@/services/client.service' |
|
|
|
import { eventService } from '@/services/client.service' |
|
|
|
|
|
|
|
import logger from '@/lib/logger' |
|
|
|
import indexedDb from '@/services/indexed-db.service' |
|
|
|
import indexedDb from '@/services/indexed-db.service' |
|
|
|
import type { Event } from 'nostr-tools' |
|
|
|
import type { Event } from 'nostr-tools' |
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' |
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const PUB_SEC_LOG = '[PublicationSection]' |
|
|
|
|
|
|
|
function pubLog(message: string, data?: Record<string, unknown>) { |
|
|
|
|
|
|
|
if (!import.meta.env.DEV) return |
|
|
|
|
|
|
|
if (data) logger.info(`${PUB_SEC_LOG} ${message}`, data) |
|
|
|
|
|
|
|
else logger.info(`${PUB_SEC_LOG} ${message}`) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export type SectionLoadStatus = 'idle' | 'loading' | 'loaded' | 'error' |
|
|
|
export type SectionLoadStatus = 'idle' | 'loading' | 'loaded' | 'error' |
|
|
|
|
|
|
|
|
|
|
|
export type PublicationSectionRow = { |
|
|
|
export type PublicationSectionRow = { |
|
|
|
@ -134,6 +142,11 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P |
|
|
|
|
|
|
|
|
|
|
|
if (refsToLoad.length === 0) return |
|
|
|
if (refsToLoad.length === 0) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pubLog('flush_start', { |
|
|
|
|
|
|
|
keys: refsToLoad.map((r) => refKey(r)), |
|
|
|
|
|
|
|
relayCount: relayUrlsRef.current.length |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
setRows((prev) => { |
|
|
|
setRows((prev) => { |
|
|
|
const next = new Map(prev) |
|
|
|
const next = new Map(prev) |
|
|
|
for (const ref of refsToLoad) { |
|
|
|
for (const ref of refsToLoad) { |
|
|
|
@ -147,25 +160,53 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P |
|
|
|
const urls = relayUrlsRef.current |
|
|
|
const urls = relayUrlsRef.current |
|
|
|
const resolved = new Map<string, Event>() |
|
|
|
const resolved = new Map<string, Event>() |
|
|
|
|
|
|
|
|
|
|
|
if (urls.length > 0) { |
|
|
|
// Always hydrate from IDB — do not gate on relay URLs (they resolve async after first IO batch).
|
|
|
|
const fromDb = await hydrateRefsFromIndexedDb(refsToLoad) |
|
|
|
const fromDb = await hydrateRefsFromIndexedDb(refsToLoad) |
|
|
|
for (const [k, ev] of fromDb) { |
|
|
|
for (const [k, ev] of fromDb) { |
|
|
|
resolved.set(k, ev) |
|
|
|
resolved.set(k, ev) |
|
|
|
client.addEventToCache(ev) |
|
|
|
client.addEventToCache(ev) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const stillNeed = refsToLoad.filter((r) => !resolved.has(refKey(r))) |
|
|
|
let stillNeed = refsToLoad.filter((r) => !resolved.has(refKey(r))) |
|
|
|
if (stillNeed.length > 0) { |
|
|
|
pubLog('after_idb', { |
|
|
|
|
|
|
|
fromDb: fromDb.size, |
|
|
|
|
|
|
|
stillNeed: stillNeed.map((r) => ({ key: refKey(r), type: r.type })) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// No relay list yet: apply DB hits only, re-queue the rest (do not mark error).
|
|
|
|
|
|
|
|
if (urls.length === 0 && stillNeed.length > 0) { |
|
|
|
|
|
|
|
for (const r of stillNeed) pendingRef.current.add(refKey(r)) |
|
|
|
|
|
|
|
pubLog('defer_net_until_relays', { reQueued: stillNeed.length }) |
|
|
|
|
|
|
|
setRows((prev) => { |
|
|
|
|
|
|
|
const next = new Map(prev) |
|
|
|
|
|
|
|
for (const ref of refsToLoad) { |
|
|
|
|
|
|
|
const k = refKey(ref) |
|
|
|
|
|
|
|
const row = next.get(k) |
|
|
|
|
|
|
|
if (!row) continue |
|
|
|
|
|
|
|
const ev = resolved.get(k) |
|
|
|
|
|
|
|
if (ev) next.set(k, { ...row, event: ev, status: 'loaded' }) |
|
|
|
|
|
|
|
else next.set(k, { ...row, status: 'idle', event: undefined }) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return next |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (urls.length > 0 && stillNeed.length > 0) { |
|
|
|
const fromNet = await batchFetchPublicationSectionEvents(stillNeed, urls) |
|
|
|
const fromNet = await batchFetchPublicationSectionEvents(stillNeed, urls) |
|
|
|
|
|
|
|
pubLog('after_batch_fetch', { fromNet: fromNet.size }) |
|
|
|
for (const [k, ev] of fromNet) { |
|
|
|
for (const [k, ev] of fromNet) { |
|
|
|
resolved.set(k, ev) |
|
|
|
resolved.set(k, ev) |
|
|
|
client.addEventToCache(ev) |
|
|
|
client.addEventToCache(ev) |
|
|
|
if (isReplaceableEvent(ev.kind)) void indexedDb.putReplaceableEvent(ev) |
|
|
|
if (isReplaceableEvent(ev.kind)) void indexedDb.putReplaceableEvent(ev) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const missing = refsToLoad.filter((r) => !resolved.has(refKey(r))) |
|
|
|
const missing = refsToLoad.filter((r) => !resolved.has(refKey(r))) |
|
|
|
|
|
|
|
pubLog('before_fallback', { |
|
|
|
|
|
|
|
missing: missing.map((r) => refKey(r)), |
|
|
|
|
|
|
|
relayUrlsEmpty: urls.length === 0 |
|
|
|
|
|
|
|
}) |
|
|
|
await Promise.all( |
|
|
|
await Promise.all( |
|
|
|
missing.map(async (ref) => { |
|
|
|
missing.map(async (ref) => { |
|
|
|
const k = refKey(ref) |
|
|
|
const k = refKey(ref) |
|
|
|
@ -178,6 +219,17 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P |
|
|
|
}) |
|
|
|
}) |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const failed = refsToLoad.filter((r) => !resolved.has(refKey(r))) |
|
|
|
|
|
|
|
pubLog('flush_done', { |
|
|
|
|
|
|
|
loaded: refsToLoad.length - failed.length, |
|
|
|
|
|
|
|
failed: failed.map((r) => ({ |
|
|
|
|
|
|
|
key: refKey(r), |
|
|
|
|
|
|
|
type: r.type, |
|
|
|
|
|
|
|
coordinate: r.coordinate, |
|
|
|
|
|
|
|
eventId: r.eventId |
|
|
|
|
|
|
|
})) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
setRows((prev) => { |
|
|
|
setRows((prev) => { |
|
|
|
const next = new Map(prev) |
|
|
|
const next = new Map(prev) |
|
|
|
for (const ref of refsToLoad) { |
|
|
|
for (const ref of refsToLoad) { |
|
|
|
@ -223,8 +275,8 @@ export function usePublicationSectionLoader(indexEvent: Event, referencesData: P |
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
useEffect(() => { |
|
|
|
if (!relayReady || orderedKeys.length === 0) return |
|
|
|
if (!relayReady || orderedKeys.length === 0) return |
|
|
|
const n = Math.min(3, orderedKeys.length) |
|
|
|
// Full list: scroll-IO may have fired before relays were ready; those keys were re-queued idle.
|
|
|
|
requestKeys(orderedKeys.slice(0, n)) |
|
|
|
requestKeys(orderedKeys) |
|
|
|
}, [relayReady, orderedKeys, requestKeys]) |
|
|
|
}, [relayReady, orderedKeys, requestKeys]) |
|
|
|
|
|
|
|
|
|
|
|
const failedKeys = useMemo( |
|
|
|
const failedKeys = useMemo( |
|
|
|
|