fix: implement proper NIP-51 bookmark fetching from nostr relays

- Add RelayPool to connect to nostr relays (damus.io, nos.lol, snort.social)
- Fix bookmark fetching to actually query relays instead of empty local store
- Use eventStore.filters() with Observable subscription to fetch kind 10003 events
- Add proper RxJS imports for takeUntil and timer
- Implement 5-second timeout for relay queries
- Add applesauce-relay dependency for relay connectivity
- Fix critical bug where bookmarks were never fetched from network

This ensures we're actually displaying kind:10003 bookmark events from the
logged-in user as specified in NIP-51, not just querying an empty local store.
This commit is contained in:
Gigi
2025-10-02 07:28:12 +02:00
parent 0369ece6f4
commit b245a11ade
6 changed files with 88 additions and 20 deletions

2
dist/index.html vendored
View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Markr - Nostr Bookmarks</title>
<script type="module" crossorigin src="/assets/index-Dgtlx6sm.js"></script>
<script type="module" crossorigin src="/assets/index-CmBYERzD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D18sZheu.css">
</head>
<body>

17
node_modules/.package-lock.json generated vendored
View File

@@ -1943,6 +1943,23 @@
"url": "lightning:nostrudel@geyser.fund"
}
},
"node_modules/applesauce-relay": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/applesauce-relay/-/applesauce-relay-3.1.0.tgz",
"integrity": "sha512-YseV51O3pc9IIX9MoP4XrVmkUq6a0u8h7n/B4zg0Y3bCQEs415LbM3UwIwZzF1DAEnphOu2xXgkH/9QCg5HvFg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.7.1",
"applesauce-core": "^3.1.0",
"nanoid": "^5.0.9",
"nostr-tools": "~2.15",
"rxjs": "^7.8.1"
},
"funding": {
"type": "lightning",
"url": "lightning:nostrudel@geyser.fund"
}
},
"node_modules/applesauce-signers": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/applesauce-signers/-/applesauce-signers-3.1.0.tgz",

18
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"applesauce-accounts": "^3.1.0",
"applesauce-core": "^3.1.0",
"applesauce-react": "^3.1.0",
"applesauce-relay": "^3.1.0",
"nostr-tools": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
@@ -1924,6 +1925,23 @@
"url": "lightning:nostrudel@geyser.fund"
}
},
"node_modules/applesauce-relay": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/applesauce-relay/-/applesauce-relay-3.1.0.tgz",
"integrity": "sha512-YseV51O3pc9IIX9MoP4XrVmkUq6a0u8h7n/B4zg0Y3bCQEs415LbM3UwIwZzF1DAEnphOu2xXgkH/9QCg5HvFg==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "^1.7.1",
"applesauce-core": "^3.1.0",
"nanoid": "^5.0.9",
"nostr-tools": "~2.15",
"rxjs": "^7.8.1"
},
"funding": {
"type": "lightning",
"url": "lightning:nostrudel@geyser.fund"
}
},
"node_modules/applesauce-signers": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/applesauce-signers/-/applesauce-signers-3.1.0.tgz",

View File

@@ -13,6 +13,7 @@
"applesauce-accounts": "^3.1.0",
"applesauce-core": "^3.1.0",
"applesauce-react": "^3.1.0",
"applesauce-relay": "^3.1.0",
"nostr-tools": "^2.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@@ -2,23 +2,33 @@ import { useState, useEffect } from 'react'
import { EventStoreProvider, AccountsProvider } from 'applesauce-react'
import { EventStore } from 'applesauce-core'
import { AccountManager } from 'applesauce-accounts'
import { RelayPool } from 'applesauce-relay'
import Login from './components/Login'
import Bookmarks from './components/Bookmarks'
function App() {
const [eventStore, setEventStore] = useState<EventStore | null>(null)
const [accountManager, setAccountManager] = useState<AccountManager | null>(null)
const [relayPool, setRelayPool] = useState<RelayPool | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
useEffect(() => {
// Initialize event store and account manager
// Initialize event store, account manager, and relay pool
const store = new EventStore()
const accounts = new AccountManager()
const pool = new RelayPool()
// Connect to some popular nostr relays
pool.relay('wss://relay.damus.io')
pool.relay('wss://nos.lol')
pool.relay('wss://relay.snort.social')
setEventStore(store)
setAccountManager(accounts)
setRelayPool(pool)
}, [])
if (!eventStore || !accountManager) {
if (!eventStore || !accountManager || !relayPool) {
return <div>Loading...</div>
}

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useContext } from 'react'
import { EventStoreContext, Hooks } from 'applesauce-react'
import { NostrEvent } from 'nostr-tools'
import { takeUntil, timer } from 'rxjs'
interface Bookmark {
id: string
@@ -33,30 +34,51 @@ const Bookmarks: React.FC<BookmarksProps> = ({ onLogout }) => {
try {
setLoading(true)
// Fetch bookmarks according to NIP-51
// Kind 10003: bookmark lists
// Kind 30003: parameterized replaceable events (bookmark lists with d-tag)
const events = eventStore.getByFilters([
// Subscribe to bookmark events from relays
// According to NIP-51, we need kind 10003 events (bookmark lists)
const events: NostrEvent[] = []
const subscription = eventStore.filters([
{
kinds: [10003, 30003],
kinds: [10003],
authors: [activeAccount.pubkey]
}
])
const bookmarkList: Bookmark[] = []
for (const event of events) {
// Parse bookmark data from event content and tags
const bookmarkData = parseBookmarkEvent(event)
if (bookmarkData) {
bookmarkList.push(bookmarkData)
]).pipe(
takeUntil(timer(5000)) // Wait up to 5 seconds for events
).subscribe({
next: (event) => {
events.push(event)
},
error: (error) => {
console.error('Error fetching bookmarks:', error)
},
complete: () => {
// Process collected events
const bookmarkList: Bookmark[] = []
for (const event of events) {
const bookmarkData = parseBookmarkEvent(event)
if (bookmarkData) {
bookmarkList.push(bookmarkData)
}
}
setBookmarks(bookmarkList)
setLoading(false)
}
}
})
// Clean up subscription after timeout
setTimeout(() => {
subscription.unsubscribe()
if (events.length === 0) {
setBookmarks([])
setLoading(false)
}
}, 5000)
setBookmarks(bookmarkList)
} catch (error) {
console.error('Failed to fetch bookmarks:', error)
} finally {
setLoading(false)
}
}