Files
damus/damus/Features/Events/EventLoaderView.swift
alltheseas 9a1ae6f9b5 Consume NIP-19 relay hints for event fetching
Extract and use relay hints from bech32 entities (nevent, nprofile, naddr)
and event tag references (e, q tags) to fetch events from hinted relays
not in the user's relay pool.

Changes:
- Parse relay hints from bech32 TLV data in URLHandler
- Pass relay hints through SearchType and NoteReference enums
- Add ensureConnected() to RelayPool for ephemeral relay connections
- Implement ephemeral relay lease management with race condition protection
- Add repostTarget() helper to extract relay hints from repost e tags
- Add QuoteRef struct to preserve relay hints from q tags (NIP-10/NIP-18)
- Support relay hints in replies with author pubkey in e-tags (NIP-10)
- Implement fallback broadcast when hinted relays don't respond
- Add comprehensive test coverage for relay hint functionality
- Add DEBUG logging for relay hint tracing during development

Implementation details:
- Connect to hinted relays as ephemeral, returning early when first connects
- Use total deadline to prevent timeout accumulation across hint attempts
- Decrement lease count before suspension points to ensure atomicity
- Fall back to broadcast if hints don't resolve or respond

Closes: https://github.com/damus-io/damus/issues/1147
Changelog-Added: Added relay hint support for nevent, nprofile, naddr links and event tag references (reposts, quotes, replies)
Signed-off-by: alltheseas
Signed-off-by: Daniel D'Aquino <daniel@daquino.me>
Co-authored-by: alltheseas <alltheseas@users.noreply.github.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Daniel D'Aquino <daniel@daquino.me
2026-02-02 18:52:41 -08:00

90 lines
2.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// EventLoaderView.swift
// damus
//
// Created by Daniel DAquino on 2023-09-27.
//
import SwiftUI
/// This view handles the loading logic for Nostr events, so that you can easily use views that require `NostrEvent`, even if you only have a `NoteId`.
///
/// Supports NIP-01/NIP-10 relay hints to fetch events from relays not in the user's pool.
struct EventLoaderView<Content: View>: View {
let damus_state: DamusState
let event_id: NoteId
let relayHints: [RelayURL]
@State var event: NostrEvent?
@State var subscription_uuid: String = UUID().description
@State var loadingTask: Task<Void, Never>? = nil
let content: (NostrEvent) -> Content
/// Creates an event loader view.
///
/// - Parameters:
/// - damus_state: The app's shared state.
/// - event_id: The ID of the event to load.
/// - relayHints: Optional relay URLs where the event may be found (per NIP-01/NIP-10).
/// - content: A view builder that receives the loaded event.
init(damus_state: DamusState, event_id: NoteId, relayHints: [RelayURL] = [], @ViewBuilder content: @escaping (NostrEvent) -> Content) {
self.damus_state = damus_state
self.event_id = event_id
self.relayHints = relayHints
self.content = content
let event = damus_state.events.lookup(event_id)
_event = State(initialValue: event)
}
func unsubscribe() {
self.loadingTask?.cancel()
}
func subscribe() {
self.loadingTask?.cancel()
self.loadingTask = Task {
let targetRelays = relayHints.isEmpty ? nil : relayHints
#if DEBUG
if let targetRelays, !targetRelays.isEmpty {
print("[relay-hints] EventLoaderView: Loading event \(event_id.hex().prefix(8))... with \(targetRelays.count) relay hint(s): \(targetRelays.map { $0.absoluteString })")
}
#endif
let lender = try? await damus_state.nostrNetwork.reader.lookup(noteId: self.event_id, to: targetRelays)
lender?.justUseACopy({ event = $0 })
#if DEBUG
if let targetRelays, !targetRelays.isEmpty {
print("[relay-hints] EventLoaderView: Event \(event_id.hex().prefix(8))... loaded: \(event != nil)")
}
#endif
}
}
func load() {
subscribe()
}
var body: some View {
VStack {
if let event {
self.content(event)
} else {
ProgressView().padding()
}
}
.onAppear {
guard event == nil else {
return
}
self.load()
}
}
}
struct EventLoaderView_Previews: PreviewProvider {
static var previews: some View {
EventLoaderView(damus_state: test_damus_state, event_id: test_note.id) { event in
EventView(damus: test_damus_state, event: event)
}
}
}