feat(nostr): Implement Nostr Feed with real-time note fetching

- Add NostrFeed component to display Nostr network notes
- Integrate date-fns for human-readable timestamp formatting
- Enhance NostrClient with fetchNotes and subscribeToNotes methods
- Implement loading, error, and empty state handling
- Add scrollable card-based UI for note display
- Configure dynamic relay selection with fallback to environment variables
This commit is contained in:
padreug 2025-03-09 16:57:22 +01:00
parent 68d6001880
commit 00f4bfa583
5 changed files with 195 additions and 3 deletions

View file

@ -1,9 +1,15 @@
import { SimplePool, getPublicKey, nip19 } from 'nostr-tools'
import { SimplePool, type Filter, type Event } from 'nostr-tools'
export interface NostrClientConfig {
relays: string[]
}
export interface NostrNote extends Event {
// Add any additional note-specific fields we want to track
replyCount?: number
reactionCount?: number
}
export class NostrClient {
private pool: SimplePool
private relays: string[]
@ -41,4 +47,80 @@ export class NostrClient {
this.pool.close(this.relays)
this._isConnected = false
}
async fetchNotes(options: {
limit?: number
since?: number // Unix timestamp in seconds
} = {}): Promise<NostrNote[]> {
const { limit = 20, since = Math.floor((Date.now() - 24 * 60 * 60 * 1000) / 1000) } = options
const filter: Filter = {
kinds: [1], // Regular notes
since,
limit
}
try {
// Get events from all relays
const events = await Promise.all(
this.relays.map(async (relay) => {
try {
return await this.pool.querySync([relay], filter)
} catch (error) {
console.warn(`Failed to fetch from relay ${relay}:`, error)
return []
}
})
)
// Flatten and deduplicate events by ID
const uniqueEvents = Array.from(
new Map(
events.flat().map(event => [event.id, event])
).values()
)
return uniqueEvents
.sort((a: Event, b: Event) => b.created_at - a.created_at) // Sort by newest first
.map((event: Event): NostrNote => ({
...event,
replyCount: 0, // We'll implement this later
reactionCount: 0 // We'll implement this later
}))
} catch (error) {
console.error('Failed to fetch notes:', error)
throw error
}
}
// Subscribe to new notes in real-time
subscribeToNotes(onNote: (note: NostrNote) => void): () => void {
const filters = [{
kinds: [1],
since: Math.floor(Date.now() / 1000)
}]
// Subscribe to each relay individually
const unsubscribes = this.relays.map(relay => {
const sub = this.pool.subscribeMany(
[relay],
filters,
{
onevent: (event: Event) => {
onNote({
...event,
replyCount: 0,
reactionCount: 0
})
}
}
)
return () => sub.close()
})
// Return a function that unsubscribes from all relays
return () => {
unsubscribes.forEach(unsub => unsub())
}
}
}