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:
parent
68d6001880
commit
00f4bfa583
5 changed files with 195 additions and 3 deletions
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue