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
101 lines
2.5 KiB
Swift
101 lines
2.5 KiB
Swift
//
|
|
// NdbTagIterators.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-07-21.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
|
|
/// The sequence of strings in a single nostr event tag
|
|
///
|
|
/// Example 1:
|
|
/// ```json
|
|
/// ["r", "wss://nostr-relay.example.com", "read"]
|
|
/// ```
|
|
///
|
|
/// Example 2:
|
|
/// ```json
|
|
/// ["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"]
|
|
/// ```
|
|
struct TagSequence: Sequence {
|
|
let note: NdbNote
|
|
let tag: ndb_tag_ptr
|
|
|
|
var count: UInt16 {
|
|
ndb_tag_count(tag.ptr)
|
|
}
|
|
|
|
func strings() -> [String] {
|
|
return self.map { $0.string() }
|
|
}
|
|
|
|
subscript(index: Int) -> NdbTagElem {
|
|
precondition(index < count, "Index out of bounds")
|
|
|
|
return NdbTagElem(note: note, tag: tag, index: Int32(index))
|
|
}
|
|
|
|
func makeIterator() -> TagIterator {
|
|
return TagIterator(note: note, tag: tag)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
|
|
mutating func next() -> NdbTagElem? {
|
|
guard index < ndb_tag_count(tag.ptr) else { return nil }
|
|
let el = NdbTagElem(note: note, tag: tag, index: index)
|
|
|
|
index += 1
|
|
|
|
return el
|
|
}
|
|
|
|
var index: Int32
|
|
let note: NdbNote
|
|
var tag: ndb_tag_ptr
|
|
|
|
var count: UInt16 {
|
|
ndb_tag_count(tag.ptr)
|
|
}
|
|
|
|
init(note: NdbNote, tag: ndb_tag_ptr) {
|
|
self.note = note
|
|
self.tag = tag
|
|
self.index = 0
|
|
}
|
|
}
|