mirror of
https://github.com/dergigi/boris.git
synced 2025-12-17 06:34:24 +01:00
feat: persist highlight metadata and offline events to localStorage
- Add localStorage persistence for highlightMetadataCache Map - Add localStorage persistence for offlineCreatedEvents Set - Load both caches from localStorage on module initialization - Save to localStorage whenever caches are updated - Update metadata cache during sync operations (isSyncing changes) - Ensures airplane icon displays correctly after page reloads - Gracefully handles localStorage errors and corrupted data
This commit is contained in:
@@ -21,38 +21,63 @@ const {
|
|||||||
getHighlightAttributions
|
getHighlightAttributions
|
||||||
} = Helpers
|
} = Helpers
|
||||||
|
|
||||||
|
const METADATA_CACHE_KEY = 'highlightMetadataCache'
|
||||||
|
|
||||||
|
type HighlightMetadata = {
|
||||||
|
publishedRelays?: string[]
|
||||||
|
isLocalOnly?: boolean
|
||||||
|
isSyncing?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load highlight metadata from localStorage
|
||||||
|
*/
|
||||||
|
function loadHighlightMetadataFromStorage(): Map<string, HighlightMetadata> {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(METADATA_CACHE_KEY)
|
||||||
|
if (!raw) return new Map()
|
||||||
|
|
||||||
|
const parsed = JSON.parse(raw) as Record<string, HighlightMetadata>
|
||||||
|
return new Map(Object.entries(parsed))
|
||||||
|
} catch {
|
||||||
|
// Silently fail on parse errors or if storage is unavailable
|
||||||
|
return new Map()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save highlight metadata to localStorage
|
||||||
|
*/
|
||||||
|
function saveHighlightMetadataToStorage(cache: Map<string, HighlightMetadata>): void {
|
||||||
|
try {
|
||||||
|
const record = Object.fromEntries(cache.entries())
|
||||||
|
localStorage.setItem(METADATA_CACHE_KEY, JSON.stringify(record))
|
||||||
|
} catch {
|
||||||
|
// Silently fail if storage is full or unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache for highlight metadata that persists across EventStore serialization
|
* Cache for highlight metadata that persists across EventStore serialization
|
||||||
* Key: event ID, Value: { publishedRelays, isLocalOnly, isSyncing }
|
* Key: event ID, Value: { publishedRelays, isLocalOnly, isSyncing }
|
||||||
*/
|
*/
|
||||||
const highlightMetadataCache = new Map<string, {
|
const highlightMetadataCache = loadHighlightMetadataFromStorage()
|
||||||
publishedRelays?: string[]
|
|
||||||
isLocalOnly?: boolean
|
|
||||||
isSyncing?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store highlight metadata for an event ID
|
* Store highlight metadata for an event ID
|
||||||
*/
|
*/
|
||||||
export function setHighlightMetadata(
|
export function setHighlightMetadata(
|
||||||
eventId: string,
|
eventId: string,
|
||||||
metadata: {
|
metadata: HighlightMetadata
|
||||||
publishedRelays?: string[]
|
|
||||||
isLocalOnly?: boolean
|
|
||||||
isSyncing?: boolean
|
|
||||||
}
|
|
||||||
): void {
|
): void {
|
||||||
highlightMetadataCache.set(eventId, metadata)
|
highlightMetadataCache.set(eventId, metadata)
|
||||||
|
saveHighlightMetadataToStorage(highlightMetadataCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get highlight metadata for an event ID
|
* Get highlight metadata for an event ID
|
||||||
*/
|
*/
|
||||||
export function getHighlightMetadata(eventId: string): {
|
export function getHighlightMetadata(eventId: string): HighlightMetadata | undefined {
|
||||||
publishedRelays?: string[]
|
|
||||||
isLocalOnly?: boolean
|
|
||||||
isSyncing?: boolean
|
|
||||||
} | undefined {
|
|
||||||
return highlightMetadataCache.get(eventId)
|
return highlightMetadataCache.get(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,42 @@ import { NostrEvent } from 'nostr-tools'
|
|||||||
import { IEventStore } from 'applesauce-core'
|
import { IEventStore } from 'applesauce-core'
|
||||||
import { RELAYS } from '../config/relays'
|
import { RELAYS } from '../config/relays'
|
||||||
import { isLocalRelay } from '../utils/helpers'
|
import { isLocalRelay } from '../utils/helpers'
|
||||||
|
import { setHighlightMetadata, getHighlightMetadata } from './highlightEventProcessor'
|
||||||
|
|
||||||
|
const OFFLINE_EVENTS_KEY = 'offlineCreatedEvents'
|
||||||
|
|
||||||
let isSyncing = false
|
let isSyncing = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load offline events from localStorage
|
||||||
|
*/
|
||||||
|
function loadOfflineEventsFromStorage(): Set<string> {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(OFFLINE_EVENTS_KEY)
|
||||||
|
if (!raw) return new Set()
|
||||||
|
|
||||||
|
const parsed = JSON.parse(raw) as string[]
|
||||||
|
return new Set(parsed)
|
||||||
|
} catch {
|
||||||
|
// Silently fail on parse errors or if storage is unavailable
|
||||||
|
return new Set()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save offline events to localStorage
|
||||||
|
*/
|
||||||
|
function saveOfflineEventsToStorage(events: Set<string>): void {
|
||||||
|
try {
|
||||||
|
const array = Array.from(events)
|
||||||
|
localStorage.setItem(OFFLINE_EVENTS_KEY, JSON.stringify(array))
|
||||||
|
} catch {
|
||||||
|
// Silently fail if storage is full or unavailable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Track events created during offline period
|
// Track events created during offline period
|
||||||
const offlineCreatedEvents = new Set<string>()
|
const offlineCreatedEvents = loadOfflineEventsFromStorage()
|
||||||
|
|
||||||
// Track events currently being synced
|
// Track events currently being synced
|
||||||
const syncingEvents = new Set<string>()
|
const syncingEvents = new Set<string>()
|
||||||
@@ -20,6 +51,7 @@ const syncStateListeners: Array<(eventId: string, isSyncing: boolean) => void> =
|
|||||||
*/
|
*/
|
||||||
export function markEventAsOfflineCreated(eventId: string): void {
|
export function markEventAsOfflineCreated(eventId: string): void {
|
||||||
offlineCreatedEvents.add(eventId)
|
offlineCreatedEvents.add(eventId)
|
||||||
|
saveOfflineEventsToStorage(offlineCreatedEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,6 +126,7 @@ export async function syncLocalEventsToRemote(
|
|||||||
if (eventsToSync.length === 0) {
|
if (eventsToSync.length === 0) {
|
||||||
isSyncing = false
|
isSyncing = false
|
||||||
offlineCreatedEvents.clear()
|
offlineCreatedEvents.clear()
|
||||||
|
saveOfflineEventsToStorage(offlineCreatedEvents)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +135,17 @@ export async function syncLocalEventsToRemote(
|
|||||||
new Map(eventsToSync.map(e => [e.id, e])).values()
|
new Map(eventsToSync.map(e => [e.id, e])).values()
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mark all events as syncing
|
// Mark all events as syncing and update metadata
|
||||||
uniqueEvents.forEach(event => {
|
uniqueEvents.forEach(event => {
|
||||||
syncingEvents.add(event.id)
|
syncingEvents.add(event.id)
|
||||||
notifySyncStateChange(event.id, true)
|
notifySyncStateChange(event.id, true)
|
||||||
|
|
||||||
|
// Update metadata cache to reflect syncing state
|
||||||
|
const existingMetadata = getHighlightMetadata(event.id)
|
||||||
|
setHighlightMetadata(event.id, {
|
||||||
|
...existingMetadata,
|
||||||
|
isSyncing: true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Publish to remote relays
|
// Publish to remote relays
|
||||||
@@ -125,13 +165,32 @@ export async function syncLocalEventsToRemote(
|
|||||||
syncingEvents.delete(eventId)
|
syncingEvents.delete(eventId)
|
||||||
offlineCreatedEvents.delete(eventId)
|
offlineCreatedEvents.delete(eventId)
|
||||||
notifySyncStateChange(eventId, false)
|
notifySyncStateChange(eventId, false)
|
||||||
|
|
||||||
|
// Update metadata cache: sync complete, no longer local-only
|
||||||
|
const existingMetadata = getHighlightMetadata(eventId)
|
||||||
|
setHighlightMetadata(eventId, {
|
||||||
|
...existingMetadata,
|
||||||
|
isSyncing: false,
|
||||||
|
isLocalOnly: false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Save updated offline events set to localStorage
|
||||||
|
saveOfflineEventsToStorage(offlineCreatedEvents)
|
||||||
|
|
||||||
// Clear syncing state for failed events
|
// Clear syncing state for failed events
|
||||||
uniqueEvents.forEach(event => {
|
uniqueEvents.forEach(event => {
|
||||||
if (!successfulIds.includes(event.id)) {
|
if (!successfulIds.includes(event.id)) {
|
||||||
syncingEvents.delete(event.id)
|
syncingEvents.delete(event.id)
|
||||||
notifySyncStateChange(event.id, false)
|
notifySyncStateChange(event.id, false)
|
||||||
|
|
||||||
|
// Update metadata cache: sync failed, still local-only
|
||||||
|
const existingMetadata = getHighlightMetadata(event.id)
|
||||||
|
setHighlightMetadata(event.id, {
|
||||||
|
...existingMetadata,
|
||||||
|
isSyncing: false
|
||||||
|
// Keep isLocalOnly as true (sync failed)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user