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
This commit is contained in:
@@ -32,4 +32,26 @@ extension NdbNote {
|
||||
}
|
||||
return self.parse_inner_event()
|
||||
}
|
||||
|
||||
/// Returns the target event ID and relay hints for a repost (kind 6) event.
|
||||
///
|
||||
/// Per NIP-18, reposts MUST include an `e` tag with the reposted event's ID,
|
||||
/// and the tag MUST include a relay URL as its third entry.
|
||||
///
|
||||
/// - Returns: A tuple of (noteId, relayHints) if this is a repost with a valid e tag, nil otherwise.
|
||||
func repostTarget() -> (noteId: NoteId, relayHints: [RelayURL])? {
|
||||
guard self.known_kind == .boost else { return nil }
|
||||
|
||||
for tag in self.tags {
|
||||
guard tag.count >= 2 else { continue }
|
||||
guard tag[0].matches_char("e") else { continue }
|
||||
guard let noteIdData = tag[1].id() else { continue }
|
||||
|
||||
let noteId = NoteId(noteIdData)
|
||||
let relayHints = tag.relayHints
|
||||
return (noteId, relayHints)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +435,10 @@ extension NdbNote {
|
||||
References<QuoteId>(tags: self.tags)
|
||||
}
|
||||
|
||||
public var referenced_quote_refs: References<QuoteRef> {
|
||||
References<QuoteRef>(tags: self.tags)
|
||||
}
|
||||
|
||||
public var referenced_noterefs: References<NoteRef> {
|
||||
References<NoteRef>(tags: self.tags)
|
||||
}
|
||||
@@ -539,6 +543,14 @@ extension NdbNote {
|
||||
return thread_reply()?.reply.note_id
|
||||
}
|
||||
|
||||
/// Returns the direct reply reference with relay hint if available.
|
||||
///
|
||||
/// Per NIP-10, the reply `e` tag may include a relay URL at position 2 where
|
||||
/// the replied-to event can be found.
|
||||
public func direct_reply_ref() -> NoteRef? {
|
||||
return thread_reply()?.reply
|
||||
}
|
||||
|
||||
// NDBTODO: just use Id
|
||||
public func thread_id() -> NoteId {
|
||||
guard let root = self.thread_reply()?.root else {
|
||||
|
||||
@@ -42,6 +42,36 @@ struct TagSequence: Sequence {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Relay Hint Extraction
|
||||
|
||||
extension TagSequence {
|
||||
/// Extracts a relay URL hint from position 2 of the tag, if present and valid.
|
||||
///
|
||||
/// Per NIP-01 and NIP-10, position 2 in `e`, `p`, `a`, and `q` tags contains an optional
|
||||
/// relay URL where the referenced entity may be found.
|
||||
///
|
||||
/// Example tag: `["e", "<event-id>", "wss://relay.example.com"]`
|
||||
///
|
||||
/// - Returns: A valid `RelayURL` if position 2 contains a non-empty, valid relay URL; `nil` otherwise.
|
||||
var relayHint: RelayURL? {
|
||||
guard count >= 3 else { return nil }
|
||||
let urlString = self[2].string()
|
||||
guard !urlString.isEmpty else { return nil }
|
||||
return RelayURL(urlString)
|
||||
}
|
||||
|
||||
/// Extracts relay hints from the tag as an array.
|
||||
///
|
||||
/// Currently tags only support a single relay hint at position 2, but this method
|
||||
/// returns an array for consistency with `NEvent.relays` and future extensibility.
|
||||
///
|
||||
/// - Returns: An array containing the relay hint if present, or an empty array.
|
||||
var relayHints: [RelayURL] {
|
||||
guard let hint = relayHint else { return [] }
|
||||
return [hint]
|
||||
}
|
||||
}
|
||||
|
||||
struct TagIterator: IteratorProtocol {
|
||||
typealias Element = NdbTagElem
|
||||
|
||||
|
||||
Reference in New Issue
Block a user