Multi-session subscriptions and RelayPool reopening
This commit implements nostr network subscriptions that survive between sessions, as well as improved handling of RelayPool opening/closing with respect to the app lifecycle. This prevents stale data after users swap out and back into Damus. Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -521,6 +521,7 @@ struct ContentView: View {
|
||||
break
|
||||
case .active:
|
||||
print("txn: 📙 DAMUS ACTIVE")
|
||||
damus_state.nostrNetwork.connect()
|
||||
damus_state.nostrNetwork.ping()
|
||||
@unknown default:
|
||||
break
|
||||
|
||||
@@ -42,6 +42,10 @@ extension NIP65 {
|
||||
self.relays = Self.relayOrderedDictionary(from: relays)
|
||||
}
|
||||
|
||||
init() {
|
||||
self.relays = Self.relayOrderedDictionary(from: [])
|
||||
}
|
||||
|
||||
init(relays: [RelayURL]) {
|
||||
let relayItemList = relays.map({ RelayItem(url: $0, rwConfiguration: .readWrite) })
|
||||
self.relays = Self.relayOrderedDictionary(from: relayItemList)
|
||||
|
||||
@@ -50,6 +50,7 @@ class NostrNetworkManager {
|
||||
/// Connects the app to the Nostr network
|
||||
func connect() {
|
||||
self.userRelayList.connect()
|
||||
self.pool.open = true
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
|
||||
@@ -34,6 +34,54 @@ extension NostrNetworkManager {
|
||||
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
||||
/// - Returns: An async stream of nostr data
|
||||
func subscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil) -> AsyncStream<StreamItem> {
|
||||
return AsyncStream<StreamItem> { continuation in
|
||||
let subscriptionId = UUID()
|
||||
Log.info("Starting subscription %s: %s", for: .subscription_manager, subscriptionId.uuidString, filters.debugDescription)
|
||||
let multiSessionStreamingTask = Task {
|
||||
while !Task.isCancelled {
|
||||
do {
|
||||
guard !self.ndb.is_closed else {
|
||||
Log.info("%s: Ndb closed. Sleeping for 1 second before resuming.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
continue
|
||||
}
|
||||
guard self.pool.open else {
|
||||
Log.info("%s: RelayPool closed. Sleeping for 1 second before resuming.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
continue
|
||||
}
|
||||
Log.info("%s: Streaming.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
for await item in self.sessionSubscribe(filters: filters, to: desiredRelays) {
|
||||
try Task.checkCancellation()
|
||||
continuation.yield(item)
|
||||
}
|
||||
Log.info("%s: Session subscription ended. Sleeping for 1 second before resuming.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000)
|
||||
}
|
||||
catch {
|
||||
Log.error("%s: Error: %s", for: .subscription_manager, subscriptionId.uuidString, error.localizedDescription)
|
||||
}
|
||||
}
|
||||
Log.info("%s: Terminated.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
}
|
||||
continuation.onTermination = { @Sendable _ in
|
||||
Log.info("%s: Cancelled.", for: .subscription_manager, subscriptionId.uuidString)
|
||||
multiSessionStreamingTask.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to data from the user's relays
|
||||
///
|
||||
/// Only survives for a single session. This exits after the app is backgrounded
|
||||
///
|
||||
/// ## Implementation notes
|
||||
///
|
||||
/// - When we migrate to the local relay model, we should modify this function to stream directly from NostrDB
|
||||
///
|
||||
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
||||
/// - Returns: An async stream of nostr data
|
||||
private func sessionSubscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil) -> AsyncStream<StreamItem> {
|
||||
return AsyncStream<StreamItem> { continuation in
|
||||
let ndbStreamTask = Task {
|
||||
do {
|
||||
|
||||
@@ -27,6 +27,7 @@ struct SeenEvent: Hashable {
|
||||
/// Establishes and manages connections and subscriptions to a list of relays.
|
||||
class RelayPool {
|
||||
private(set) var relays: [Relay] = []
|
||||
var open: Bool = false
|
||||
var handlers: [RelayHandler] = []
|
||||
var request_queue: [QueuedRequest] = []
|
||||
var seen: [NoteId: Set<RelayURL>] = [:]
|
||||
@@ -46,6 +47,7 @@ class RelayPool {
|
||||
func close() {
|
||||
disconnect()
|
||||
relays = []
|
||||
open = false
|
||||
handlers = []
|
||||
request_queue = []
|
||||
seen.removeAll()
|
||||
@@ -181,6 +183,7 @@ class RelayPool {
|
||||
}
|
||||
|
||||
func connect(to: [RelayURL]? = nil) {
|
||||
open = true
|
||||
let relays = to.map{ get_relays($0) } ?? self.relays
|
||||
for relay in relays {
|
||||
relay.connection.connect()
|
||||
|
||||
Reference in New Issue
Block a user