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
90 lines
2.9 KiB
Swift
90 lines
2.9 KiB
Swift
//
|
||
// EventLoaderView.swift
|
||
// damus
|
||
//
|
||
// Created by Daniel D’Aquino 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)
|
||
}
|
||
}
|
||
}
|