Browse Source

publication caching

imwald
Silberengel 5 months ago
parent
commit
86589b3306
  1. 117
      src/components/Note/PublicationIndex/PublicationIndex.tsx
  2. 60
      src/services/indexed-db.service.ts

117
src/components/Note/PublicationIndex/PublicationIndex.tsx

@ -8,6 +8,7 @@ import client from '@/services/client.service' @@ -8,6 +8,7 @@ import client from '@/services/client.service'
import logger from '@/lib/logger'
import { Button } from '@/components/ui/button'
import { MoreVertical } from 'lucide-react'
import indexedDb from '@/services/indexed-db.service'
interface PublicationReference {
coordinate: string
@ -208,10 +209,17 @@ export default function PublicationIndex({ @@ -208,10 +209,17 @@ export default function PublicationIndex({
useEffect(() => {
setVisitedIndices(prev => new Set([...prev, currentCoordinate]))
}, [currentCoordinate])
// Cache the current publication index event using its actual event ID
indexedDb.putPublicationEvent(event).catch(err => {
logger.error('[PublicationIndex] Error caching publication event:', err)
})
}, [currentCoordinate, event])
// Fetch referenced events
useEffect(() => {
let isMounted = true
const fetchReferences = async () => {
setIsLoading(true)
const fetchedRefs: PublicationReference[] = []
@ -219,41 +227,78 @@ export default function PublicationIndex({ @@ -219,41 +227,78 @@ export default function PublicationIndex({
// Capture current visitedIndices at the start of the fetch
const currentVisited = visitedIndices
for (const ref of referencesData) {
// Skip if this is a 30040 event we've already visited (prevent circular references)
if (ref.kind === ExtendedKind.PUBLICATION) {
if (currentVisited.has(ref.coordinate)) {
logger.debug('[PublicationIndex] Skipping visited 30040 index:', ref.coordinate)
fetchedRefs.push({ ...ref, event: undefined })
continue
}
// Add a timeout to prevent infinite loading on mobile
const timeout = setTimeout(() => {
if (isMounted) {
logger.warn('[PublicationIndex] Fetch timeout reached, setting loaded state')
setIsLoading(false)
}
}, 30000) // 30 second timeout
try {
// Generate bech32 ID from the 'a' tag
const aTag = ['a', ref.coordinate, ref.relay || '', ref.eventId || '']
const bech32Id = generateBech32IdFromATag(aTag)
try {
for (const ref of referencesData) {
if (!isMounted) break
if (bech32Id) {
const fetchedEvent = await client.fetchEvent(bech32Id)
if (fetchedEvent) {
fetchedRefs.push({ ...ref, event: fetchedEvent })
} else {
logger.warn('[PublicationIndex] Could not fetch event for:', ref.coordinate)
// Skip if this is a 30040 event we've already visited (prevent circular references)
if (ref.kind === ExtendedKind.PUBLICATION) {
if (currentVisited.has(ref.coordinate)) {
logger.debug('[PublicationIndex] Skipping visited 30040 index:', ref.coordinate)
fetchedRefs.push({ ...ref, event: undefined })
continue
}
}
try {
// Generate bech32 ID from the 'a' tag
const aTag = ['a', ref.coordinate, ref.relay || '', ref.eventId || '']
const bech32Id = generateBech32IdFromATag(aTag)
if (bech32Id) {
// First, check if we have this event by its eventId in the ref
let fetchedEvent: Event | undefined = undefined
if (ref.eventId) {
// Try to get by event ID first
fetchedEvent = await indexedDb.getPublicationEvent(ref.eventId)
}
// If not found by event ID, try to fetch from relay
if (!fetchedEvent) {
fetchedEvent = await client.fetchEvent(bech32Id)
// Save to cache using the fetched event's ID as the key
if (fetchedEvent) {
await indexedDb.putPublicationEvent(fetchedEvent)
logger.debug('[PublicationIndex] Cached event with ID:', fetchedEvent.id)
}
} else {
logger.debug('[PublicationIndex] Loaded from cache by event ID:', ref.eventId)
}
if (fetchedEvent && isMounted) {
fetchedRefs.push({ ...ref, event: fetchedEvent })
} else if (isMounted) {
logger.warn('[PublicationIndex] Could not fetch event for:', ref.coordinate)
fetchedRefs.push({ ...ref, event: undefined })
}
} else if (isMounted) {
logger.warn('[PublicationIndex] Could not generate bech32 ID for:', ref.coordinate)
fetchedRefs.push({ ...ref, event: undefined })
}
} catch (error) {
logger.error('[PublicationIndex] Error fetching reference:', error)
if (isMounted) {
fetchedRefs.push({ ...ref, event: undefined })
}
} else {
logger.warn('[PublicationIndex] Could not generate bech32 ID for:', ref.coordinate)
fetchedRefs.push({ ...ref, event: undefined })
}
} catch (error) {
logger.error('[PublicationIndex] Error fetching reference:', error)
fetchedRefs.push({ ...ref, event: undefined })
}
}
setReferences(fetchedRefs)
setIsLoading(false)
if (isMounted) {
setReferences(fetchedRefs)
setIsLoading(false)
}
} finally {
clearTimeout(timeout)
}
}
if (referencesData.length > 0) {
@ -261,6 +306,10 @@ export default function PublicationIndex({ @@ -261,6 +306,10 @@ export default function PublicationIndex({
} else {
setIsLoading(false)
}
return () => {
isMounted = false
}
}, [referencesData, visitedIndices]) // Now include visitedIndices but capture it inside
return (
@ -326,7 +375,17 @@ export default function PublicationIndex({ @@ -326,7 +375,17 @@ export default function PublicationIndex({
{/* Content - render referenced events */}
{isLoading ? (
<div className="text-muted-foreground">Loading publication content...</div>
<div className="text-muted-foreground">
<div>Loading publication content...</div>
<div className="text-xs mt-2">If this takes too long, the content may not be available.</div>
</div>
) : references.length === 0 ? (
<div className="p-6 border rounded-lg bg-muted/30 text-center">
<div className="text-lg font-semibold mb-2">No content loaded</div>
<div className="text-sm text-muted-foreground">
Unable to load publication content. The referenced events may not be available on the current relays.
</div>
</div>
) : (
<div className="space-y-8">
{references.map((ref, index) => {

60
src/services/indexed-db.service.ts

@ -26,7 +26,8 @@ const StoreNames = { @@ -26,7 +26,8 @@ const StoreNames = {
RELAY_SETS: 'relaySets',
FOLLOWING_FAVORITE_RELAYS: 'followingFavoriteRelays',
RELAY_INFOS: 'relayInfos',
RELAY_INFO_EVENTS: 'relayInfoEvents' // deprecated
RELAY_INFO_EVENTS: 'relayInfoEvents', // deprecated
PUBLICATION_EVENTS: 'publicationEvents'
}
class IndexedDbService {
@ -45,7 +46,7 @@ class IndexedDbService { @@ -45,7 +46,7 @@ class IndexedDbService {
init(): Promise<void> {
if (!this.initPromise) {
this.initPromise = new Promise((resolve, reject) => {
const request = window.indexedDB.open('jumble', 11)
const request = window.indexedDB.open('jumble', 12)
request.onerror = (event) => {
reject(event)
@ -109,6 +110,9 @@ class IndexedDbService { @@ -109,6 +110,9 @@ class IndexedDbService {
if (db.objectStoreNames.contains(StoreNames.RELAY_INFO_EVENTS)) {
db.deleteObjectStore(StoreNames.RELAY_INFO_EVENTS)
}
if (!db.objectStoreNames.contains(StoreNames.PUBLICATION_EVENTS)) {
db.createObjectStore(StoreNames.PUBLICATION_EVENTS, { keyPath: 'key' })
}
this.db = db
}
})
@ -477,11 +481,63 @@ class IndexedDbService { @@ -477,11 +481,63 @@ class IndexedDbService {
return StoreNames.USER_EMOJI_LIST_EVENTS
case kinds.Emojisets:
return StoreNames.EMOJI_SET_EVENTS
case ExtendedKind.PUBLICATION:
case ExtendedKind.PUBLICATION_CONTENT:
case ExtendedKind.WIKI_ARTICLE:
case kinds.LongFormArticle:
return StoreNames.PUBLICATION_EVENTS
default:
return undefined
}
}
async putPublicationEvent(event: Event): Promise<Event> {
await this.initPromise
return new Promise((resolve, reject) => {
if (!this.db) {
return reject('database not initialized')
}
const transaction = this.db.transaction(StoreNames.PUBLICATION_EVENTS, 'readwrite')
const store = transaction.objectStore(StoreNames.PUBLICATION_EVENTS)
const key = event.id
// Always update, as these are not replaceable events
const putRequest = store.put(this.formatValue(key, event))
putRequest.onsuccess = () => {
transaction.commit()
resolve(event)
}
putRequest.onerror = (event) => {
transaction.commit()
reject(event)
}
})
}
async getPublicationEvent(eventId: string): Promise<Event | undefined> {
await this.initPromise
return new Promise((resolve, reject) => {
if (!this.db) {
return reject('database not initialized')
}
const transaction = this.db.transaction(StoreNames.PUBLICATION_EVENTS, 'readonly')
const store = transaction.objectStore(StoreNames.PUBLICATION_EVENTS)
const request = store.get(eventId)
request.onsuccess = () => {
transaction.commit()
const cachedValue = (request.result as TValue<Event>)?.value
resolve(cachedValue || undefined)
}
request.onerror = (event) => {
transaction.commit()
reject(event)
}
})
}
private formatValue<T>(key: string, value: T): TValue<T> {
return {
key,

Loading…
Cancel
Save