import { useCallback, useEffect, useRef, useState } from 'react' import { Button } from '@/components/ui/button' import { toast } from 'sonner' import { useNostr } from '@/providers/NostrProvider' import { eventKinds, getKindName, type EventKind } from '@/lib/event-kinds' import { SimplePool, type Event, type Filter } from 'nostr-tools' function getRelayWsUrl(): string { const loc = window.location const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:' return `${proto}//${loc.host}` } function getReplaceableKinds(): EventKind[] { return eventKinds.filter( (ek) => ek.isReplaceable || ek.isAddressable || ek.kind === 0 || ek.kind === 3 ) } export default function RecoveryTab() { const { pubkey, publish } = useNostr() const [selectedKind, setSelectedKind] = useState('') const [customKind, setCustomKind] = useState('') const [events, setEvents] = useState([]) const [isLoading, setIsLoading] = useState(false) const [isReposting, setIsReposting] = useState(null) const poolRef = useRef(null) const replaceableKinds = getReplaceableKinds() useEffect(() => { poolRef.current = new SimplePool() return () => { poolRef.current?.close(poolRef.current ? [getRelayWsUrl()] : []) } }, []) const activeKind = selectedKind !== '' ? selectedKind : customKind ? parseInt(customKind, 10) : null const loadEvents = useCallback(async () => { if (activeKind === null || isNaN(activeKind)) return setIsLoading(true) setEvents([]) try { const pool = poolRef.current if (!pool) { toast.error('Pool not initialized') setIsLoading(false) return } const relayUrl = getRelayWsUrl() const filter: Filter = { kinds: [activeKind], limit: 200, } if (pubkey) { filter.authors = [pubkey] } const collected: Event[] = [] await new Promise((resolve) => { const sub = pool.subscribeMany([relayUrl], filter, { onevent(evt: Event) { collected.push(evt) }, oneose() { sub.close() resolve() }, }) setTimeout(() => { sub.close() resolve() }, 15000) }) collected.sort((a, b) => b.created_at - a.created_at) setEvents(collected) if (collected.length === 0) { toast.info('No events found for this kind') } else { toast.success(`Found ${collected.length} versions`) } } catch (e) { toast.error(`Query failed: ${e instanceof Error ? e.message : String(e)}`) } finally { setIsLoading(false) } }, [activeKind, pubkey]) // Auto-load when kind changes useEffect(() => { if (activeKind !== null && !isNaN(activeKind)) { loadEvents() } }, [activeKind]) // eslint-disable-line react-hooks/exhaustive-deps const isCurrentVersion = (_event: Event, index: number): boolean => { return index === 0 } const repostEvent = async (event: Event) => { if (!pubkey) { toast.error('Login required') return } if (!confirm('Republish this old version? It will become the current version.')) return setIsReposting(event.id) try { const draft = { kind: event.kind, content: event.content, tags: event.tags, created_at: Math.floor(Date.now() / 1000), } await publish(draft) toast.success('Event republished successfully') // Reload to see updated order await loadEvents() } catch (e) { toast.error(`Repost failed: ${e instanceof Error ? e.message : String(e)}`) } finally { setIsReposting(null) } } const copyEvent = (event: Event) => { navigator.clipboard.writeText(JSON.stringify(event)) toast.success('Copied to clipboard') } return (

Event Recovery

Search and recover old versions of replaceable events. A wise pelican once said: every version tells a story, even the ones you regret.

{ setCustomKind(e.target.value) if (e.target.value) setSelectedKind('') }} placeholder="e.g., 10001" min="0" className="w-full rounded-md border bg-background px-3 py-2 text-sm" />
{events.length > 0 && (
{events.length} version{events.length !== 1 ? 's' : ''} found for kind{' '} {activeKind} ({getKindName(activeKind!)})
)}
{events.map((event, idx) => { const isCurrent = isCurrentVersion(event, idx) return (
{isCurrent && ( Current Version )}
{new Date(event.created_at * 1000).toLocaleString()}
{event.id.slice(0, 16)}...
{!isCurrent && ( )}
                {JSON.stringify(event, null, 2)}
              
) })} {events.length === 0 && !isLoading && activeKind !== null && (
No events found for this kind.
)} {isLoading && (
Loading events...
)}
) }