Make RelayPool private to NostrNetworkManager and migrate usages

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2025-09-24 14:06:02 -07:00
parent 719a0c8cb0
commit 578d47356d
45 changed files with 852 additions and 892 deletions
@@ -58,7 +58,7 @@ class ActionBarModel: ObservableObject {
self.our_zap = damus.zaps.our_zaps[evid]?.first
self.our_reply = damus.replies.our_reply(evid)
self.our_quote_repost = damus.quote_reposts.our_events[evid]
self.relays = (damus.nostrNetwork.pool.seen[evid] ?? []).count
self.relays = (damus.nostrNetwork.relayURLsThatSawNote(id: evid) ?? []).count
self.objectWillChange.send()
}
@@ -61,7 +61,7 @@ struct EventDetailBar: View {
}
if bar.relays > 0 {
let relays = Array(state.nostrNetwork.pool.seen[target] ?? [])
let relays = Array(state.nostrNetwork.relayURLsThatSawNote(id: target) ?? [])
NavigationLink(value: Route.UserRelays(relays: relays)) {
let nounString = pluralizedString(key: "relays_count", count: bar.relays)
let noun = Text(nounString).foregroundColor(.gray)
+33 -47
View File
@@ -56,12 +56,7 @@ class ThreadModel: ObservableObject {
/// The damus state, needed to access the relay pool and load the thread events
let damus_state: DamusState
private let profiles_subid = UUID().description
private let base_subid = UUID().description
private let meta_subid = UUID().description
private var subids: [String] {
return [profiles_subid, base_subid, meta_subid]
}
private var listener: Task<Void, Never>?
// MARK: Initialization
@@ -86,17 +81,6 @@ class ThreadModel: ObservableObject {
// MARK: Relay pool subscription management
/// Unsubscribe from events in the relay pool. Call this when unloading the view
func unsubscribe() {
self.damus_state.nostrNetwork.pool.remove_handler(sub_id: base_subid)
self.damus_state.nostrNetwork.pool.remove_handler(sub_id: meta_subid)
self.damus_state.nostrNetwork.pool.remove_handler(sub_id: profiles_subid)
self.damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid)
self.damus_state.nostrNetwork.pool.unsubscribe(sub_id: meta_subid)
self.damus_state.nostrNetwork.pool.unsubscribe(sub_id: profiles_subid)
Log.info("unsubscribing to thread %s with sub_id %s", for: .render, original_event.id.hex(), base_subid)
}
/// Subscribe to events in this thread. Call this when loading the view.
func subscribe() {
var meta_events = NostrFilter()
@@ -127,10 +111,27 @@ class ThreadModel: ObservableObject {
let base_filters = [event_filter, ref_events]
let meta_filters = [meta_events, quote_events]
Log.info("subscribing to thread %s with sub_id %s", for: .render, original_event.id.hex(), base_subid)
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
damus_state.nostrNetwork.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event)
self.listener?.cancel()
self.listener = Task {
Log.info("subscribing to thread %s ", for: .render, original_event.id.hex())
for await item in damus_state.nostrNetwork.reader.subscribe(filters: base_filters + meta_filters) {
switch item {
case .event(let borrow):
try? borrow { event in
handle_event(ev: event.toOwned())
}
case .eose:
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "thread", load: .from_events(Array(event_map.events)), damus_state: damus_state, txn: txn)
}
}
}
}
func unsubscribe() {
self.listener?.cancel()
self.listener = nil
}
/// Adds an event to this thread.
@@ -175,35 +176,20 @@ class ThreadModel: ObservableObject {
///
/// Marked as private because it is this class' responsibility to load events, not the view's. Simplify the interface
@MainActor
private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
let (sub_id, done) = handle_subid_event(pool: damus_state.nostrNetwork.pool, relay_id: relay_id, ev: ev) { sid, ev in
guard subids.contains(sid) else {
return
private func handle_event(ev: NostrEvent) {
if ev.known_kind == .zap {
process_zap_event(state: damus_state, ev: ev) { zap in
}
if ev.known_kind == .zap {
process_zap_event(state: damus_state, ev: ev) { zap in
}
} else if ev.is_textlike {
// handle thread quote reposts, we just count them instead of
// adding them to the thread
if let target = ev.is_quote_repost, target == self.selected_event.id {
//let _ = self.damus_state.quote_reposts.add_event(ev, target: target)
} else {
self.add_event(ev, keypair: damus_state.keypair)
}
} else if ev.is_textlike {
// handle thread quote reposts, we just count them instead of
// adding them to the thread
if let target = ev.is_quote_repost, target == self.selected_event.id {
//let _ = self.damus_state.quote_reposts.add_event(ev, target: target)
} else {
self.add_event(ev, keypair: damus_state.keypair)
}
}
guard done, let sub_id, subids.contains(sub_id) else {
return
}
if sub_id == self.base_subid {
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "thread", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(Array(event_map.events)), damus_state: damus_state, txn: txn)
}
}
// MARK: External control interface
+15 -24
View File
@@ -13,6 +13,7 @@ struct EventLoaderView<Content: View>: View {
let event_id: NoteId
@State var event: NostrEvent?
@State var subscription_uuid: String = UUID().description
@State var loadingTask: Task<Void, Never>? = nil
let content: (NostrEvent) -> Content
init(damus_state: DamusState, event_id: NoteId, @ViewBuilder content: @escaping (NostrEvent) -> Content) {
@@ -24,34 +25,24 @@ struct EventLoaderView<Content: View>: View {
}
func unsubscribe() {
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subscription_uuid)
self.loadingTask?.cancel()
}
func subscribe(filters: [NostrFilter]) {
damus_state.nostrNetwork.pool.register_handler(sub_id: subscription_uuid, handler: handle_event)
damus_state.nostrNetwork.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
}
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
guard case .nostr_event(let nostr_response) = ev else {
return
self.loadingTask?.cancel()
self.loadingTask = Task {
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: filters) {
switch item {
case .event(let borrow):
try? borrow { ev in
event = ev.toOwned()
}
break
case .eose:
break
}
}
}
guard case .event(let id, let nostr_event) = nostr_response else {
return
}
guard id == subscription_uuid else {
return
}
if event != nil {
return
}
event = nostr_event
unsubscribe()
}
func load() {
+1 -1
View File
@@ -152,7 +152,7 @@ struct MenuItems: View {
profileModel.subscribeToFindRelays()
}
.onDisappear() {
profileModel.unsubscribeFindRelays()
profileModel.findRelaysListener?.cancel()
}
}
}
+21 -29
View File
@@ -11,10 +11,10 @@ class EventsModel: ObservableObject {
let state: DamusState
let target: NoteId
let kind: QueryKind
let sub_id = UUID().uuidString
let profiles_id = UUID().uuidString
var events: EventHolder
@Published var loading: Bool
var loadingTask: Task<Void, Never>?
enum QueryKind {
case kind(NostrKind)
@@ -68,13 +68,29 @@ class EventsModel: ObservableObject {
}
func subscribe() {
state.nostrNetwork.pool.subscribe(sub_id: sub_id,
filters: [get_filter()],
handler: handle_nostr_event)
loadingTask?.cancel()
loadingTask = Task {
for await item in state.nostrNetwork.reader.subscribe(filters: [get_filter()]) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
if events.insert(event) { objectWillChange.send() }
case .eose:
break
}
}
self.loading = false
guard let txn = NdbTxn(ndb: self.state.ndb) else { return }
load_profiles(context: "events_model", load: .from_events(events.all_events), damus_state: state, txn: txn)
}
}
func unsubscribe() {
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
loadingTask?.cancel()
}
private func handle_event(relay_id: RelayURL, ev: NostrEvent) {
@@ -82,28 +98,4 @@ class EventsModel: ObservableObject {
objectWillChange.send()
}
}
func handle_nostr_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
guard case .nostr_event(let nev) = ev, nev.subid == self.sub_id
else {
return
}
switch nev {
case .event(_, let ev):
handle_event(relay_id: relay_id, ev: ev)
case .notice:
break
case .ok:
break
case .auth:
break
case .eose:
self.loading = false
guard let txn = NdbTxn(ndb: self.state.ndb) else {
return
}
load_profiles(context: "events_model", profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events.all_events), damus_state: state, txn: txn)
}
}
}
@@ -50,7 +50,7 @@ class LoadableNostrEventViewModel: ObservableObject {
/// Asynchronously find an event from NostrDB or from the network (if not available on NostrDB)
private func loadEvent(noteId: NoteId) async -> NostrEvent? {
let res = await find_event(state: damus_state, query: .event(evid: noteId))
let res = await damus_state.nostrNetwork.findEvent(query: .event(evid: noteId))
guard let res, case .event(let ev) = res else { return nil }
return ev
}
@@ -78,7 +78,7 @@ class LoadableNostrEventViewModel: ObservableObject {
return .unknown_or_unsupported_kind
}
case .naddr(let naddr):
guard let event = await naddrLookup(damus_state: damus_state, naddr: naddr) else { return .not_found }
guard let event = await damus_state.nostrNetwork.lookup(naddr: naddr) else { return .not_found }
return .loaded(route: Route.Thread(thread: ThreadModel(event: event, damus_state: damus_state)))
}
}
@@ -13,7 +13,7 @@ class FollowPackModel: ObservableObject {
@Published var loading: Bool = false
let damus_state: DamusState
let subid = UUID().description
var listener: Task<Void, Never>? = nil
let limit: UInt32 = 500
init(damus_state: DamusState) {
@@ -25,52 +25,40 @@ class FollowPackModel: ObservableObject {
func subscribe(follow_pack_users: [Pubkey]) {
loading = true
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
self.listener = Task {
await self.listenForUpdates(follow_pack_users: follow_pack_users)
}
}
func unsubscribe(to: RelayURL? = nil) {
loading = false
self.listener?.cancel()
}
func listenForUpdates(follow_pack_users: [Pubkey]) async {
let to_relays = damus_state.nostrNetwork.determineToRelays(filters: damus_state.relay_filters)
var filter = NostrFilter(kinds: [.text, .chat])
filter.until = UInt32(Date.now.timeIntervalSince1970)
filter.authors = follow_pack_users
filter.limit = 500
damus_state.nostrNetwork.pool.subscribe(sub_id: subid, filters: [filter], handler: handle_event, to: to_relays)
}
func unsubscribe(to: RelayURL? = nil) {
loading = false
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: to.map { [$0] })
}
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
guard case .nostr_event(let event) = conn_ev else {
return
}
switch event {
case .event(let sub_id, let ev):
guard sub_id == self.subid else {
return
}
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
{
if self.events.insert(ev) {
self.objectWillChange.send()
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter], to: to_relays) {
switch item {
case .event(borrow: let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
if event.is_textlike && should_show_event(state: damus_state, ev: event) && !event.is_reply()
{
if self.events.insert(event) {
self.objectWillChange.send()
}
}
case .eose:
continue
}
case .notice(let msg):
print("follow pack notice: \(msg)")
case .ok:
break
case .eose(let sub_id):
loading = false
if sub_id == self.subid {
unsubscribe(to: relay_id)
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
}
break
case .auth:
break
}
}
}
@@ -14,8 +14,8 @@ class FollowersModel: ObservableObject {
@Published var contacts: [Pubkey]? = nil
var has_contact: Set<Pubkey> = Set()
let sub_id: String = UUID().description
let profiles_id: String = UUID().description
var listener: Task<Void, Never>? = nil
var profilesListener: Task<Void, Never>? = nil
var count: Int? {
guard let contacts = self.contacts else {
@@ -36,12 +36,27 @@ class FollowersModel: ObservableObject {
func subscribe() {
let filter = get_filter()
let filters = [filter]
//print_filters(relay_id: "following", filters: [filters])
self.damus_state.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
self.listener?.cancel()
self.listener = Task {
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: filters) {
switch item {
case .event(let borrow):
try? borrow { event in
self.handle_event(ev: event.toOwned())
}
case .eose:
guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return }
load_profiles(txn: txn)
}
}
}
}
func unsubscribe() {
self.damus_state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
self.listener?.cancel()
self.profilesListener?.cancel()
self.listener = nil
self.profilesListener = nil
}
func handle_contact_event(_ ev: NostrEvent) {
@@ -53,7 +68,7 @@ class FollowersModel: ObservableObject {
has_contact.insert(ev.pubkey)
}
func load_profiles<Y>(relay_id: RelayURL, txn: NdbTxn<Y>) {
func load_profiles<Y>(txn: NdbTxn<Y>) {
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn)
if authors.isEmpty {
return
@@ -61,38 +76,24 @@ class FollowersModel: ObservableObject {
let filter = NostrFilter(kinds: [.metadata],
authors: authors)
damus_state.nostrNetwork.pool.subscribe_to(sub_id: profiles_id, filters: [filter], to: [relay_id], handler: handle_event)
}
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
guard case .nostr_event(let nev) = ev else {
return
}
switch nev {
case .event(let sub_id, let ev):
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
return
self.profilesListener?.cancel()
self.profilesListener = Task {
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
switch item {
case .event(let borrow):
try? borrow { event in
self.handle_event(ev: event.toOwned())
}
case .eose: break
}
}
if ev.known_kind == .contacts {
handle_contact_event(ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose(let sub_id):
if sub_id == self.sub_id {
guard let txn = NdbTxn(ndb: self.damus_state.ndb) else { return }
load_profiles(relay_id: relay_id, txn: txn)
} else if sub_id == self.profiles_id {
damus_state.nostrNetwork.pool.unsubscribe(sub_id: profiles_id, to: [relay_id])
}
case .ok:
break
case .auth:
break
}
}
func handle_event(ev: NostrEvent) {
if ev.known_kind == .contacts {
handle_contact_event(ev)
}
}
}
@@ -14,7 +14,7 @@ class FollowingModel {
let contacts: [Pubkey]
let hashtags: [Hashtag]
let sub_id: String = UUID().description
private var listener: Task<Void, Never>? = nil
init(damus_state: DamusState, contacts: [Pubkey], hashtags: [Hashtag]) {
self.damus_state = damus_state
@@ -41,19 +41,17 @@ class FollowingModel {
return
}
let filters = [filter]
//print_filters(relay_id: "following", filters: [filters])
self.damus_state.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
self.listener?.cancel()
self.listener = Task {
for await item in self.damus_state.nostrNetwork.reader.subscribe(filters: filters) {
// don't need to do anything here really
continue
}
}
}
func unsubscribe() {
if !needs_sub {
return
}
print("unsubscribing from following \(sub_id)")
self.damus_state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
}
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
// don't need to do anything here really
self.listener?.cancel()
self.listener = nil
}
}
@@ -15,8 +15,7 @@ class NIP05DomainEventsModel: ObservableObject {
let domain: String
var filter: NostrFilter
let sub_id = UUID().description
let profiles_subid = UUID().description
var loadingTask: Task<Void, Never>?
let limit: UInt32 = 500
init(state: DamusState, domain: String) {
@@ -29,6 +28,20 @@ class NIP05DomainEventsModel: ObservableObject {
}
@MainActor func subscribe() {
print("subscribing to notes from friends of friends with '\(domain)' NIP-05 domain")
loadingTask = Task {
await streamItems()
}
loading = true
}
func unsubscribe() {
loadingTask?.cancel()
loading = false
print("unsubscribing from notes from friends of friends with '\(domain)' NIP-05 domain")
}
func streamItems() async {
filter.limit = self.limit
filter.kinds = [.text, .longform, .highlight]
@@ -50,16 +63,19 @@ class NIP05DomainEventsModel: ObservableObject {
}
filter.authors = Array(authors)
print("subscribing to notes from friends of friends with '\(domain)' NIP-05 domain with sub_id \(sub_id)")
state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: handle_event)
loading = true
state.nostrNetwork.pool.send(.subscribe(.init(filters: [filter], sub_id: sub_id)))
}
func unsubscribe() {
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
loading = false
print("unsubscribing from notes from friends of friends with '\(domain)' NIP-05 domain with sub_id \(sub_id)")
for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) {
switch item {
case .event(borrow: let borrow):
try? borrow { event in
self.add_event(event.toOwned())
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
}
case .eose:
continue
}
}
}
func add_event(_ ev: NostrEvent) {
@@ -75,23 +91,4 @@ class NIP05DomainEventsModel: ObservableObject {
objectWillChange.send()
}
}
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
let (sub_id, done) = handle_subid_event(pool: state.nostrNetwork.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
if sub_id == self.sub_id && ev.is_textlike && ev.should_show_event {
self.add_event(ev)
}
}
guard done else {
return
}
self.loading = false
if sub_id == self.sub_id {
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn)
}
}
}
@@ -56,7 +56,7 @@ struct OnboardingSuggestionsView: View {
// - We don't have other mechanisms to allow the user to edit this yet
//
// Therefore, it is better to just save it locally, and retrieve this once we build out https://github.com/damus-io/damus/issues/3042
model.damus_state.nostrNetwork.pool.send_raw_to_local_ndb(.typical(.event(event)))
model.damus_state.nostrNetwork.sendToNostrDB(event: event)
}
var body: some View {
@@ -224,7 +224,7 @@ class Drafts: ObservableObject {
}
/// Saves the drafts tracked by this class persistently using NostrDB + UserDefaults
func save(damus_state: DamusState) {
func save(damus_state: DamusState) async {
var draft_events: [NdbNote] = []
post_artifact_block: if let post_artifacts = self.post {
let nip37_draft = try? post_artifacts.to_nip37_draft(action: .posting(.user(damus_state.pubkey)), damus_state: damus_state)
@@ -254,7 +254,7 @@ class Drafts: ObservableObject {
// TODO: Once it is time to implement draft syncing with relays, please consider the following:
// - Privacy: Sending drafts to the network leaks metadata about app activity, and may break user expectations
// - Down-sync conflict resolution: Consider how to solve conflicts for different draft versions holding the same ID (e.g. edited in Damus, then another client, then Damus again)
damus_state.nostrNetwork.pool.send_raw_to_local_ndb(.typical(.event(draft_event)))
damus_state.nostrNetwork.sendToNostrDB(event: draft_event)
}
damus_state.settings.draft_event_ids = draft_events.map({ $0.id.hex() })
+2 -2
View File
@@ -105,7 +105,7 @@ struct PostView: View {
self.prompt_view = prompt_view
self.placeholder_messages = placeholder_messages ?? [POST_PLACEHOLDER]
self.initial_text_suffix = initial_text_suffix
self.autoSaveModel = AutoSaveIndicatorView.AutoSaveViewModel(save: { damus_state.drafts.save(damus_state: damus_state) })
self.autoSaveModel = AutoSaveIndicatorView.AutoSaveViewModel(save: { await damus_state.drafts.save(damus_state: damus_state) })
}
@Environment(\.dismiss) var dismiss
@@ -231,7 +231,7 @@ struct PostView: View {
damus_state.drafts.post = nil
}
damus_state.drafts.save(damus_state: damus_state)
Task{ await damus_state.drafts.save(damus_state: damus_state) }
}
func load_draft() -> Bool {
+110 -106
View File
@@ -23,17 +23,21 @@ class ProfileModel: ObservableObject, Equatable {
return nil
}
private let MAX_SHARE_RELAYS = 4
var events: EventHolder
let pubkey: Pubkey
let damus: DamusState
var seen_event: Set<NoteId> = Set()
var sub_id = UUID().description
var prof_subid = UUID().description
var conversations_subid = UUID().description
var findRelay_subid = UUID().description
var findRelaysListener: Task<Void, Never>? = nil
var listener: Task<Void, Never>? = nil
var profileListener: Task<Void, Never>? = nil
var conversationListener: Task<Void, Never>? = nil
var conversation_events: Set<NoteId> = Set()
init(pubkey: Pubkey, damus: DamusState) {
self.pubkey = pubkey
self.damus = damus
@@ -46,7 +50,7 @@ class ProfileModel: ObservableObject, Equatable {
guard let contacts = self.contacts else {
return false
}
return contacts.referenced_pubkeys.contains(pubkey)
}
@@ -60,39 +64,53 @@ class ProfileModel: ObservableObject, Equatable {
static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool {
return lhs.pubkey == rhs.pubkey
}
func hash(into hasher: inout Hasher) {
hasher.combine(pubkey)
}
func unsubscribe() {
print("unsubscribing from profile \(pubkey) with sub_id \(sub_id)")
damus.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
damus.nostrNetwork.pool.unsubscribe(sub_id: prof_subid)
if pubkey != damus.pubkey {
damus.nostrNetwork.pool.unsubscribe(sub_id: conversations_subid)
func subscribe() {
print("subscribing to profile \(pubkey)")
listener?.cancel()
listener = Task {
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
text_filter.authors = [pubkey]
text_filter.limit = 500
for await item in damus.nostrNetwork.reader.subscribe(filters: [text_filter]) {
switch item {
case .event(let borrow):
try? borrow { event in
handleNostrEvent(event.toOwned())
}
case .eose: break
}
}
guard let txn = NdbTxn(ndb: damus.ndb) else { return }
load_profiles(context: "profile", load: .from_events(events.events), damus_state: damus, txn: txn)
progress += 1
}
profileListener?.cancel()
profileListener = Task {
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
profile_filter.authors = [pubkey]
for await item in damus.nostrNetwork.reader.subscribe(filters: [profile_filter]) {
switch item {
case .event(let borrow):
try? borrow { event in
handleNostrEvent(event.toOwned())
}
case .eose: break
}
}
progress += 1
}
conversationListener?.cancel()
conversationListener = Task {
await listenToConversations()
}
}
func subscribe() {
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])
var relay_list_filter = NostrFilter(kinds: [.relay_list], authors: [pubkey])
profile_filter.authors = [pubkey]
text_filter.authors = [pubkey]
text_filter.limit = 500
print("subscribing to textlike events from profile \(pubkey) with sub_id \(sub_id)")
//print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
damus.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: [text_filter], handler: handle_event)
damus.nostrNetwork.pool.subscribe(sub_id: prof_subid, filters: [profile_filter, relay_list_filter], handler: handle_event)
subscribe_to_conversations()
}
private func subscribe_to_conversations() {
func listenToConversations() async {
// Only subscribe to conversation events if the profile is not us.
guard pubkey != damus.pubkey else {
return
@@ -102,10 +120,35 @@ class ProfileModel: ObservableObject, Equatable {
let limit: UInt32 = 500
let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey])
let conversations_filter_us = NostrFilter(kinds: conversation_kinds, pubkeys: [pubkey], limit: limit, authors: [damus.pubkey])
print("subscribing to conversation events from and to profile \(pubkey) with sub_id \(conversations_subid)")
damus.nostrNetwork.pool.subscribe(sub_id: conversations_subid, filters: [conversations_filter_them, conversations_filter_us], handler: handle_event)
print("subscribing to conversation events from and to profile \(pubkey)")
for await item in self.damus.nostrNetwork.reader.subscribe(filters: [conversations_filter_them, conversations_filter_us]) {
switch item {
case .event(borrow: let borrow):
try? borrow { ev in
if !seen_event.contains(ev.id) {
let event = ev.toOwned()
Task { await self.add_event(event) }
conversation_events.insert(ev.id)
}
else if !conversation_events.contains(ev.id) {
conversation_events.insert(ev.id)
}
}
case .eose:
continue
}
}
}
func unsubscribe() {
listener?.cancel()
listener = nil
profileListener?.cancel()
profileListener = nil
conversationListener?.cancel()
conversationListener = nil
}
func handle_profile_contact_event(_ ev: NostrEvent) {
process_contact_event(state: damus, ev: ev)
@@ -120,8 +163,13 @@ class ProfileModel: ObservableObject, Equatable {
self.following = count_pubkeys(ev.tags)
self.legacy_relay_list = decode_json_relays(ev.content)
}
@MainActor
func add_event(_ ev: NostrEvent) {
guard ev.should_show_event else {
return
}
private func add_event(_ ev: NostrEvent) {
if ev.is_textlike || ev.known_kind == .boost {
if self.events.insert(ev) {
self.objectWillChange.send()
@@ -134,72 +182,13 @@ class ProfileModel: ObservableObject, Equatable {
}
seen_event.insert(ev.id)
}
// Ensure the event public key matches the public key(s) we are querying.
// This is done to protect against a relay not properly filtering events by the pubkey
// See https://github.com/damus-io/damus/issues/1846 for more information
private func relay_filtered_correctly(_ ev: NostrEvent, subid: String?) -> Bool {
if subid == self.conversations_subid {
switch ev.pubkey {
case self.pubkey:
return ev.referenced_pubkeys.contains(damus.pubkey)
case damus.pubkey:
return ev.referenced_pubkeys.contains(self.pubkey)
default:
return false
}
}
return self.pubkey == ev.pubkey
}
private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
switch ev {
case .ws_connection_event:
return
case .nostr_event(let resp):
guard resp.subid == self.sub_id || resp.subid == self.prof_subid || resp.subid == self.conversations_subid else {
return
}
switch resp {
case .ok:
break
case .event(_, let ev):
guard ev.should_show_event else {
break
}
if !seen_event.contains(ev.id) {
guard relay_filtered_correctly(ev, subid: resp.subid) else {
break
}
add_event(ev)
if resp.subid == self.conversations_subid {
conversation_events.insert(ev.id)
}
} else if resp.subid == self.conversations_subid && !conversation_events.contains(ev.id) {
guard relay_filtered_correctly(ev, subid: resp.subid) else {
break
}
conversation_events.insert(ev.id)
}
case .notice:
break
//notify(.notice, notice)
case .eose:
guard let txn = NdbTxn(ndb: damus.ndb) else { return }
if resp.subid == sub_id {
load_profiles(context: "profile", profiles_subid: prof_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus, txn: txn)
}
progress += 1
break
case .auth:
break
}
}
private func handleNostrEvent(_ ev: NostrEvent) {
// Ensure the event public key matches this profiles public key
// This is done to protect against a relay not properly filtering events by the pubkey
// See https://github.com/damus-io/damus/issues/1846 for more information
guard self.pubkey == ev.pubkey else { return }
Task { await add_event(ev) }
}
private func findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) {
@@ -211,12 +200,27 @@ class ProfileModel: ObservableObject, Equatable {
func subscribeToFindRelays() {
var profile_filter = NostrFilter(kinds: [.contacts])
profile_filter.authors = [pubkey]
damus.nostrNetwork.pool.subscribe(sub_id: findRelay_subid, filters: [profile_filter], handler: findRelaysHandler)
self.findRelaysListener?.cancel()
self.findRelaysListener = Task {
for await item in await damus.nostrNetwork.reader.subscribe(filters: [profile_filter]) {
switch item {
case .event(let borrow):
try? borrow { event in
if case .contacts = event.known_kind {
// TODO: Is this correct?
self.legacy_relay_list = decode_json_relays(event.content)
}
}
case .eose:
break
}
}
}
}
func unsubscribeFindRelays() {
damus.nostrNetwork.pool.unsubscribe(sub_id: findRelay_subid)
self.findRelaysListener?.cancel()
self.findRelaysListener = nil
}
func getCappedRelays() -> [RelayURL] {
@@ -283,7 +283,7 @@ fileprivate struct ProfileActionSheetZapButton: View {
VStack(alignment: .center, spacing: 10) {
Button(
action: {
send_zap(damus_state: damus_state, target: .profile(self.profile.pubkey), lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type)
Task { await send_zap(damus_state: damus_state, target: .profile(self.profile.pubkey), lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type) }
zap_state = .zapping
},
label: {
@@ -588,3 +588,4 @@ func check_nip05_validity(pubkey: Pubkey, profiles: Profiles) {
}
}
}
@@ -84,9 +84,3 @@ func load_relay_filters(_ pubkey: Pubkey) -> Set<RelayFilter>? {
s.insert(filter)
}
}
func determine_to_relays(pool: RelayPool, filters: RelayFilters) -> [RelayURL] {
return pool.our_descriptors
.map { $0.url }
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
}
@@ -32,7 +32,7 @@ struct RelayConfigView: View {
init(state: DamusState) {
self.state = state
_relays = State(initialValue: state.nostrNetwork.pool.our_descriptors)
_relays = State(initialValue: state.nostrNetwork.ourRelayDescriptors)
UITabBar.appearance().isHidden = true
}
@@ -98,7 +98,7 @@ struct RelayConfigView: View {
}
}
.onReceive(handle_notify(.relays_changed)) { _ in
self.relays = state.nostrNetwork.pool.our_descriptors
self.relays = state.nostrNetwork.ourRelayDescriptors
}
.onAppear {
notify(.display_tabbar(false))
@@ -30,7 +30,7 @@ struct RelayDetailView: View {
func RemoveRelayButton(_ keypair: FullKeypair) -> some View {
Button(action: {
self.removeRelay()
Task { await self.removeRelay() }
}) {
HStack {
Text("Disconnect", comment: "Button to disconnect from the relay.")
@@ -43,7 +43,7 @@ struct RelayDetailView: View {
func ConnectRelayButton(_ keypair: FullKeypair) -> some View {
Button(action: {
self.connectRelay()
Task { await self.connectRelay() }
}) {
HStack {
Text("Connect", comment: "Button to connect to the relay.")
@@ -177,16 +177,18 @@ struct RelayDetailView: View {
}
private var relay_object: RelayPool.Relay? {
state.nostrNetwork.pool.get_relay(relay)
// TODO: Concurrency problems?
state.nostrNetwork.connectedRelays.first(where: { $0.descriptor.url == relay })
}
private var relay_connection: RelayConnection? {
relay_object?.connection
}
func removeRelay() {
func removeRelay() async {
do {
try state.nostrNetwork.userRelayList.remove(relayURL: self.relay)
// TODO: Concurrency problems?
try await state.nostrNetwork.userRelayList.remove(relayURL: self.relay)
dismiss()
}
catch {
@@ -194,9 +196,10 @@ struct RelayDetailView: View {
}
}
func connectRelay() {
func connectRelay() async {
do {
try state.nostrNetwork.userRelayList.insert(relay: NIP65.RelayList.RelayItem(url: relay, rwConfiguration: .readWrite))
// TODO: Concurrency problems?
try await state.nostrNetwork.userRelayList.insert(relay: NIP65.RelayList.RelayItem(url: relay, rwConfiguration: .readWrite))
dismiss()
}
catch {
@@ -19,7 +19,7 @@ struct RelayFilterView: View {
}
var relays: [RelayPool.RelayDescriptor] {
return state.nostrNetwork.pool.our_descriptors
return state.nostrNetwork.ourRelayDescriptors
}
var body: some View {
@@ -56,7 +56,7 @@ struct RelayStatusView: View {
struct RelayStatusView_Previews: PreviewProvider {
static var previews: some View {
let connection = test_damus_state.nostrNetwork.pool.get_relay(RelayURL("wss://relay.damus.io")!)!.connection
let connection = test_damus_state.nostrNetwork.getRelay(RelayURL("wss://relay.damus.io")!)!.connection
RelayStatusView(connection: connection)
}
}
@@ -36,7 +36,7 @@ struct RelayToggle: View {
}
private var relay_connection: RelayConnection? {
state.nostrNetwork.pool.get_relay(relay_id)?.connection
state.nostrNetwork.getRelay(relay_id)?.connection
}
}
+5 -5
View File
@@ -24,13 +24,13 @@ struct RelayView: View {
self.recommended = recommended
self.model_cache = state.relay_model_cache
_showActionButtons = showActionButtons
let relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: relay)
let relay_state = RelayView.get_relay_state(state: state, relay: relay)
self._relay_state = State(initialValue: relay_state)
self.disableNavLink = disableNavLink
}
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
return pool.get_relay(relay) == nil
static func get_relay_state(state: DamusState, relay: RelayURL) -> Bool {
return state.nostrNetwork.getRelay(relay) == nil
}
var body: some View {
@@ -110,7 +110,7 @@ struct RelayView: View {
.contentShape(Rectangle())
}
.onReceive(handle_notify(.relays_changed)) { _ in
self.relay_state = RelayView.get_relay_state(pool: state.nostrNetwork.pool, relay: self.relay)
self.relay_state = RelayView.get_relay_state(state: state, relay: self.relay)
}
.onTapGesture {
if !disableNavLink {
@@ -120,7 +120,7 @@ struct RelayView: View {
}
private var relay_connection: RelayConnection? {
state.nostrNetwork.pool.get_relay(relay)?.connection
state.nostrNetwork.getRelay(relay)?.connection
}
func add_action(keypair: FullKeypair) async {
@@ -22,7 +22,7 @@ struct UserRelaysView: View {
static func make_relay_state(state: DamusState, relays: [RelayURL]) -> [(RelayURL, Bool)] {
return relays.map({ r in
return (r, state.nostrNetwork.pool.get_relay(r) == nil)
return (r, state.nostrNetwork.getRelay(r) == nil)
}).sorted { (a, b) in a.0 < b.0 }
}
@@ -39,63 +39,41 @@ class SearchHomeModel: ObservableObject {
self.objectWillChange.send()
}
func subscribe() {
func load() async {
loading = true
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
var follow_list_filter = NostrFilter(kinds: [.follow_list])
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
let to_relays = damus_state.nostrNetwork.ourRelayDescriptors
.map { $0.url }
.filter { !damus_state.relay_filters.is_filtered(timeline: .search, relay_id: $0) }
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
damus_state.nostrNetwork.pool.subscribe(sub_id: follow_pack_subid, filters: [follow_list_filter], handler: handle_event, to: to_relays)
}
func unsubscribe(to: RelayURL? = nil) {
loading = false
damus_state.nostrNetwork.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] })
damus_state.nostrNetwork.pool.unsubscribe(sub_id: follow_pack_subid, to: to.map { [$0] })
}
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
guard case .nostr_event(let event) = conn_ev else {
return
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [get_base_filter()], to: to_relays) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.handleEvent(event)
case .eose: break
}
}
loading = false
switch event {
case .event(let sub_id, let ev):
guard sub_id == self.base_subid || sub_id == self.profiles_subid || sub_id == self.follow_pack_subid else {
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "universe", load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
}
@MainActor
func handleEvent(_ ev: NostrEvent) {
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply() {
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
return
}
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
{
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
return
}
seen_pubkey.insert(ev.pubkey)
if self.events.insert(ev) {
self.objectWillChange.send()
}
}
case .notice(let msg):
print("search home notice: \(msg)")
case .ok:
break
case .eose(let sub_id):
loading = false
seen_pubkey.insert(ev.pubkey)
if sub_id == self.base_subid {
// Make sure we unsubscribe after we've fetched the global events
// global events are not realtime
unsubscribe(to: relay_id)
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "universe", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
if self.events.insert(ev) {
self.objectWillChange.send()
}
break
case .auth:
break
}
}
}
@@ -135,44 +113,35 @@ enum PubkeysToLoad {
case from_keys([Pubkey])
}
func load_profiles<Y>(context: String, profiles_subid: String, relay_id: RelayURL, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) {
func load_profiles<Y>(context: String, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn<Y>) {
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
guard !authors.isEmpty else {
return
}
print("load_profiles[\(context)]: requesting \(authors.count) profiles from \(relay_id)")
let filter = NostrFilter(kinds: [.metadata], authors: authors)
damus_state.nostrNetwork.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { rid, conn_ev in
Task {
print("load_profiles[\(context)]: requesting \(authors.count) profiles from relay pool")
let filter = NostrFilter(kinds: [.metadata], authors: authors)
let now = UInt64(Date.now.timeIntervalSince1970)
switch conn_ev {
case .ws_connection_event:
break
case .nostr_event(let ev):
guard ev.subid == profiles_subid, rid == relay_id else { return }
switch ev {
case .event(_, let ev):
if ev.known_kind == .metadata {
damus_state.ndb.write_profile_last_fetched(pubkey: ev.pubkey, fetched_at: now)
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
let now = UInt64(Date.now.timeIntervalSince1970)
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
if event.known_kind == .metadata {
damus_state.ndb.write_profile_last_fetched(pubkey: event.pubkey, fetched_at: now)
}
case .eose:
print("load_profiles[\(context)]: done loading \(authors.count) profiles from \(relay_id)")
damus_state.nostrNetwork.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
case .ok:
break
case .notice:
break
case .auth:
break
}
}
print("load_profiles[\(context)]: done loading \(authors.count) profiles from relay pool")
}
}
+24 -58
View File
@@ -14,8 +14,8 @@ class SearchModel: ObservableObject {
@Published var loading: Bool = false
var search: NostrFilter
let sub_id = UUID().description
let profiles_subid = UUID().description
var listener: Task<Void, Never>? = nil
let limit: UInt32 = 500
init(state: DamusState, search: NostrFilter) {
@@ -39,17 +39,32 @@ class SearchModel: ObservableObject {
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
//likes_filter.ids = ref_events.referenced_ids!
print("subscribing to search '\(search)' with sub_id \(sub_id)")
state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: handle_event)
loading = true
state.nostrNetwork.pool.send(.subscribe(.init(filters: [search], sub_id: sub_id)))
listener?.cancel()
listener = Task {
self.loading = true
print("subscribing to search")
for await item in await state.nostrNetwork.reader.subscribe(filters: [search]) {
switch item {
case .event(let borrow):
try? borrow { ev in
let event = ev.toOwned()
if event.is_textlike && event.should_show_event {
self.add_event(event)
}
}
case .eose:
break
}
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
}
self.loading = false
}
}
func unsubscribe() {
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
loading = false
print("unsubscribing from search '\(search)' with sub_id \(sub_id)")
listener?.cancel()
listener = nil
}
func add_event(_ ev: NostrEvent) {
@@ -65,25 +80,6 @@ class SearchModel: ObservableObject {
objectWillChange.send()
}
}
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
let (sub_id, done) = handle_subid_event(pool: state.nostrNetwork.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
if ev.is_textlike && ev.should_show_event {
self.add_event(ev)
}
}
guard done else {
return
}
self.loading = false
if sub_id == self.sub_id {
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "search", profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(self.events.all_events), damus_state: state, txn: txn)
}
}
}
func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
@@ -106,33 +102,3 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
}
return true
}
func handle_subid_event(pool: RelayPool, relay_id: RelayURL, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) {
switch ev {
case .ws_connection_event:
return (nil, false)
case .nostr_event(let res):
switch res {
case .event(let ev_subid, let ev):
handle(ev_subid, ev)
return (ev_subid, false)
case .ok:
return (nil, false)
case .notice(let note):
if note.contains("Too many subscription filters") {
// TODO: resend filters?
pool.reconnect(to: [relay_id])
}
return (nil, false)
case .eose(let subid):
return (subid, true)
case .auth:
return (nil, false)
}
}
}
@@ -14,6 +14,7 @@ struct SearchHomeView: View {
@StateObject var model: SearchHomeModel
@State var search: String = ""
@FocusState private var isFocused: Bool
@State var loadingTask: Task<Void, Never>?
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
var filters = ContentFilters.defaults(damus_state: damus_state)
@@ -84,8 +85,8 @@ struct SearchHomeView: View {
)
.refreshable {
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
model.subscribe()
loadingTask?.cancel()
loadingTask = Task { await model.load() }
}
}
@@ -93,8 +94,8 @@ struct SearchHomeView: View {
SearchResultsView(damus_state: damus_state, search: $search)
.refreshable {
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
model.subscribe()
loadingTask?.cancel()
loadingTask = Task { await model.load() }
}
}
@@ -129,11 +130,11 @@ struct SearchHomeView: View {
}
.onAppear {
if model.events.events.isEmpty {
model.subscribe()
loadingTask = Task { await model.load() }
}
}
.onDisappear {
model.unsubscribe()
loadingTask?.cancel()
}
}
}
@@ -77,7 +77,8 @@ struct SearchingEventView: View {
}
case .event(let note_id):
find_event(state: state, query: .event(evid: note_id)) { res in
Task {
let res = await state.nostrNetwork.findEvent(query: .event(evid: note_id))
guard case .event(let ev) = res else {
self.search_state = .not_found
return
@@ -85,7 +86,8 @@ struct SearchingEventView: View {
self.search_state = .found(ev)
}
case .profile(let pubkey):
find_event(state: state, query: .profile(pubkey: pubkey)) { res in
Task {
let res = await state.nostrNetwork.findEvent(query: .profile(pubkey: pubkey))
guard case .profile(let pubkey) = res else {
self.search_state = .not_found
return
@@ -93,7 +95,8 @@ struct SearchingEventView: View {
self.search_state = .found_profile(pubkey)
}
case .naddr(let naddr):
naddrLookup(damus_state: state, naddr: naddr) { res in
Task {
let res = await state.nostrNetwork.lookup(naddr: naddr)
guard let res = res else {
self.search_state = .not_found
return
@@ -68,7 +68,7 @@ struct FirstAidSettingsView: View {
guard let new_contact_list_event = make_first_contact_event(keypair: damus_state.keypair) else {
throw FirstAidError.cannotMakeFirstContactEvent
}
damus_state.nostrNetwork.pool.send(.event(new_contact_list_event))
damus_state.nostrNetwork.send(event: new_contact_list_event)
damus_state.settings.latest_contact_event_id_hex = new_contact_list_event.id.hex()
}
+207 -157
View File
@@ -65,12 +65,11 @@ class HomeModel: ContactsDelegate {
let resub_debouncer = Debouncer(interval: 3.0)
var should_debounce_dms = true
let home_subid = UUID().description
let contacts_subid = UUID().description
let notifications_subid = UUID().description
let dms_subid = UUID().description
let init_subid = UUID().description
let profiles_subid = UUID().description
var homeHandlerTask: Task<Void, Never>?
var contactsHandlerTask: Task<Void, Never>?
var notificationsHandlerTask: Task<Void, Never>?
var dmsHandlerTask: Task<Void, Never>?
var nwcHandlerTask: Task<Void, Never>?
var loading: Bool = false
@@ -94,23 +93,10 @@ class HomeModel: ContactsDelegate {
preload_events(state: self.damus_state, events: [ev])
}
var pool: RelayPool {
self.damus_state.nostrNetwork.pool
}
var dms: DirectMessagesModel {
return damus_state.dms
}
func has_sub_id_event(sub_id: String, ev_id: NoteId) -> Bool {
if !has_event.keys.contains(sub_id) {
has_event[sub_id] = Set()
return false
}
return has_event[sub_id]!.contains(ev_id)
}
func setup_debouncer() {
// turn off debouncer after initial load
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
@@ -140,6 +126,28 @@ class HomeModel: ContactsDelegate {
damus_state.drafts.load(from: damus_state)
}
enum RelayListLoadingError: Error {
case noRelayList
case relayListParseError
var humanReadableError: ErrorView.UserPresentableError {
switch self {
case .noRelayList:
return ErrorView.UserPresentableError(
user_visible_description: NSLocalizedString("Your relay list could not be found, so we cannot connect you to your Nostr network.", comment: "Human readable error description for a failure to find the relay list"),
tip: NSLocalizedString("Please check your internet connection and restart the app. If the error persists, please go to Settings > First Aid.", comment: "Human readable tips for what to do for a failure to find the relay list"),
technical_info: "No NIP-65 relay list or legacy kind:3 contact event could be found."
)
case .relayListParseError:
return ErrorView.UserPresentableError(
user_visible_description: NSLocalizedString("Your relay list appears to be broken, so we cannot connect you to your Nostr network.", comment: "Human readable error description for a failure to parse the relay list due to a bad relay list"),
tip: NSLocalizedString("Please contact support for further help.", comment: "Human readable tips for what to do for a failure to find the relay list"),
technical_info: "Relay list could not be parsed."
)
}
}
}
// MARK: - ContactsDelegate functions
func latest_contact_event_changed(new_event: NostrEvent) {
@@ -158,9 +166,6 @@ class HomeModel: ContactsDelegate {
print("hit resub debouncer")
resub_debouncer.debounce {
print("resub")
self.unsubscribe_to_home_filters()
switch resubbing {
case .following:
break
@@ -175,25 +180,16 @@ class HomeModel: ContactsDelegate {
}
@MainActor
func process_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) {
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
return
}
let last_k = get_last_event_of_kind(relay_id: relay_id, kind: ev.kind)
if last_k == nil || ev.created_at > last_k!.created_at {
last_event_of_kind[relay_id]?[ev.kind] = ev
}
func process_event(ev: NostrEvent, context: SubscriptionContext) {
guard let kind = ev.known_kind else {
return
}
switch kind {
case .chat, .longform, .text, .highlight:
handle_text_event(sub_id: sub_id, ev)
handle_text_event(ev, context: context)
case .contacts:
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
handle_contact_event(ev: ev)
case .metadata:
// profile metadata processing is handled by nostrdb
break
@@ -202,7 +198,7 @@ class HomeModel: ContactsDelegate {
case .mute_list:
handle_mute_list_event(ev)
case .boost:
handle_boost_event(sub_id: sub_id, ev)
handle_boost_event(ev, context: context)
case .like:
handle_like_event(ev)
case .dm:
@@ -216,7 +212,7 @@ class HomeModel: ContactsDelegate {
case .nwc_request:
break
case .nwc_response:
handle_nwc_response(ev, relay: relay_id)
handle_nwc_response(ev)
case .http_auth:
break
case .status:
@@ -261,7 +257,7 @@ class HomeModel: ContactsDelegate {
pdata.status.update_status(st)
}
func handle_nwc_response(_ ev: NostrEvent, relay: RelayURL) {
func handle_nwc_response(_ ev: NostrEvent) {
Task { @MainActor in
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
@@ -269,7 +265,6 @@ class HomeModel: ContactsDelegate {
return
}
guard nwc.relay == relay else { return } // Don't process NWC responses coming from relays other than our designated one
guard ev.referenced_pubkeys.first == nwc.keypair.pubkey else {
return // This message is not for us. Ignore it.
}
@@ -289,9 +284,9 @@ class HomeModel: ContactsDelegate {
// since command results are not returned for ephemeral events,
// remove the request from the postbox which is likely failing over and over
if damus_state.nostrNetwork.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) {
Log.debug("HomeModel: got NWC response, removed %s from the postbox [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
Log.debug("HomeModel: got NWC response, removed %s from the postbox", for: .nwc, resp.req_id.hex())
} else {
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove [%s]", for: .nwc, resp.req_id.hex(), relay.absoluteString)
Log.debug("HomeModel: got NWC response, %s not found in the postbox, nothing to remove", for: .nwc, resp.req_id.hex())
}
damus_state.wallet.handle_nwc_response(response: resp) // This can handle success or error cases
@@ -303,7 +298,6 @@ class HomeModel: ContactsDelegate {
return
}
print("nwc success: \(resp.response.result.debugDescription) [\(relay)]")
WalletConnect.handle_zap_success(state: self.damus_state, resp: resp)
}
}
@@ -382,19 +376,11 @@ class HomeModel: ContactsDelegate {
self.deleted_events.insert(ev.id)
}
func handle_contact_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) {
func handle_contact_event(ev: NostrEvent) {
process_contact_event(state: self.damus_state, ev: ev)
if sub_id == init_subid {
pool.send(.unsubscribe(init_subid), to: [relay_id])
if !done_init {
done_init = true
send_home_filters(relay_id: nil)
}
}
}
func handle_boost_event(sub_id: String, _ ev: NostrEvent) {
func handle_boost_event(_ ev: NostrEvent, context: SubscriptionContext) {
var boost_ev_id = ev.last_refid()
if let inner_ev = ev.get_inner_event(cache: damus_state.events) {
@@ -409,7 +395,7 @@ class HomeModel: ContactsDelegate {
if inner_ev.is_textlike {
DispatchQueue.main.async {
self.handle_text_event(sub_id: sub_id, ev)
self.handle_text_event(ev, context: context)
}
}
}
@@ -457,94 +443,50 @@ class HomeModel: ContactsDelegate {
}
}
@MainActor
func handle_event(relay_id: RelayURL, conn_event: NostrConnectionEvent) {
switch conn_event {
case .ws_connection_event(let ev):
switch ev {
case .connected:
if !done_init {
self.loading = true
send_initial_filters(relay_id: relay_id)
} else {
//remove_bootstrap_nodes(damus_state)
send_home_filters(relay_id: relay_id)
/// Send the initial filters, just our contact list and relay list mostly
func send_initial_filters() {
Task {
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await process_event(ev: event, context: .initialContactList)
continue
case .eose:
if !done_init {
done_init = true
send_home_filters()
}
break
}
// connect to nwc relays when connected
if let nwc_str = damus_state.settings.nostr_wallet_connect,
let r = pool.get_relay(relay_id),
r.descriptor.variant == .nwc,
let nwc = WalletConnectURL(str: nwc_str),
nwc.relay == relay_id
{
WalletConnect.subscribe(url: nwc, pool: pool)
}
case .error(let merr):
let desc = String(describing: merr)
if desc.contains("Software caused connection abort") {
pool.reconnect(to: [relay_id])
}
case .disconnected:
pool.reconnect(to: [relay_id])
default:
break
}
update_signal_from_pool(signal: self.signal, pool: damus_state.nostrNetwork.pool)
case .nostr_event(let ev):
switch ev {
case .event(let sub_id, let ev):
// globally handle likes
/*
let always_process = sub_id == notifications_subid || sub_id == contacts_subid || sub_id == home_subid || sub_id == dms_subid || sub_id == init_subid || ev.known_kind == .like || ev.known_kind == .boost || ev.known_kind == .zap || ev.known_kind == .contacts || ev.known_kind == .metadata
if !always_process {
// TODO: other views like threads might have their own sub ids, so ignore those events... or should we?
return
}
*/
self.process_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
case .notice(let msg):
print(msg)
case .eose(let sub_id):
guard let txn = NdbTxn(ndb: damus_state.ndb) else {
return
}
if sub_id == dms_subid {
var dms = dms.dms.flatMap { $0.events }
dms.append(contentsOf: incoming_dms)
load_profiles(context: "dms", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state, txn: txn)
} else if sub_id == notifications_subid {
load_profiles(context: "notifications", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state, txn: txn)
} else if sub_id == home_subid {
load_profiles(context: "home", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.events), damus_state: damus_state, txn: txn)
}
self.loading = false
break
case .ok:
break
case .auth:
break
}
}
}
/// Send the initial filters, just our contact list mostly
func send_initial_filters(relay_id: RelayURL) {
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
pool.send(.subscribe(subscription), to: [relay_id])
Task {
let relayListFilter = NostrFilter(kinds: [.relay_list], limit: 1, authors: [damus_state.pubkey])
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [relayListFilter]) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await process_event(ev: event, context: .initialRelayList)
case .eose: break
}
}
}
}
/// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications
func send_home_filters(relay_id: RelayURL?) {
func send_home_filters() {
// TODO: since times should be based on events from a specific relay
// perhaps we could mark this in the relay pool somehow
@@ -589,38 +531,99 @@ class HomeModel: ContactsDelegate {
let contacts_filter_chunks = contacts_filter.chunked(on: .authors, into: MAX_CONTACTS_ON_FILTER)
var contacts_filters = contacts_filter_chunks + [our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
var dms_filters = [dms_filter, our_dms_filter]
let last_of_kind = get_last_of_kind(relay_id: relay_id)
contacts_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: contacts_filters)
notifications_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: notifications_filters)
dms_filters = update_filters_with_since(last_of_kind: last_of_kind, filters: dms_filters)
//print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters, notifications_filters, dms_filters])
subscribe_to_home_filters(relay_id: relay_id)
subscribe_to_home_filters()
let relay_ids = relay_id.map { [$0] }
pool.send(.subscribe(.init(filters: contacts_filters, sub_id: contacts_subid)), to: relay_ids)
pool.send(.subscribe(.init(filters: notifications_filters, sub_id: notifications_subid)), to: relay_ids)
pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: relay_ids)
self.contactsHandlerTask?.cancel()
self.contactsHandlerTask = Task {
for await item in damus_state.nostrNetwork.reader.subscribe(filters: contacts_filters) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
var event = ev.toOwned()
}
guard let event else { return }
await self.process_event(ev: event, context: .contacts)
case .eose: continue
}
}
}
self.notificationsHandlerTask?.cancel()
self.notificationsHandlerTask = Task {
for await item in damus_state.nostrNetwork.reader.subscribe(filters: notifications_filters) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.process_event(ev: event, context: .notifications)
case .eose:
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "notifications", load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state, txn: txn)
}
}
}
self.dmsHandlerTask?.cancel()
self.dmsHandlerTask = Task {
for await item in damus_state.nostrNetwork.reader.subscribe(filters: dms_filters) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.process_event(ev: event, context: .dms)
case .eose:
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
var dms = dms.dms.flatMap { $0.events }
dms.append(contentsOf: incoming_dms)
load_profiles(context: "dms", load: .from_events(dms), damus_state: damus_state, txn: txn)
}
}
}
self.nwcHandlerTask?.cancel()
self.nwcHandlerTask = Task {
if let nwc_str = damus_state.settings.nostr_wallet_connect,
let nwc = WalletConnectURL(str: nwc_str)
{
var filter = NostrFilter(kinds: [.nwc_response])
filter.authors = [nwc.pubkey]
filter.limit = 0
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter], to: [nwc.relay]) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.process_event(ev: event, context: .nwc)
case .eose: continue
}
}
}
}
}
func get_last_of_kind(relay_id: RelayURL?) -> [UInt32: NostrEvent] {
return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
}
func unsubscribe_to_home_filters() {
pool.send(.unsubscribe(home_subid))
}
func get_friends() -> [Pubkey] {
var friends = damus_state.contacts.get_friend_list()
friends.insert(damus_state.pubkey)
return Array(friends)
}
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) {
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil) {
// TODO: separate likes?
var home_filter_kinds: [NostrKind] = [
.text, .longform, .boost, .highlight
@@ -649,11 +652,34 @@ class HomeModel: ContactsDelegate {
home_filters.append(hashtag_filter)
}
let relay_ids = relay_id.map { [$0] }
home_filters = update_filters_with_since(last_of_kind: get_last_of_kind(relay_id: relay_id), filters: home_filters)
let sub = NostrSubscribe(filters: home_filters, sub_id: home_subid)
pool.send(.subscribe(sub), to: relay_ids)
self.homeHandlerTask?.cancel()
self.homeHandlerTask = Task {
for await item in damus_state.nostrNetwork.reader.subscribe(filters: home_filters) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.process_event(ev: event, context: .home)
case .eose:
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
load_profiles(context: "home", load: .from_events(events.events), damus_state: damus_state, txn: txn)
}
}
}
}
/// Adapter pattern to make migration easier
enum SubscriptionContext {
case initialContactList
case initialRelayList
case home
case notifications
case dms
case contacts
case nwc
}
func handle_mute_list_event(_ ev: NostrEvent) {
@@ -746,7 +772,7 @@ class HomeModel: ContactsDelegate {
}
func handle_text_event(sub_id: String, _ ev: NostrEvent) {
func handle_text_event(_ ev: NostrEvent, context: SubscriptionContext) {
guard should_show_event(state: damus_state, ev: ev) else {
return
}
@@ -770,10 +796,13 @@ class HomeModel: ContactsDelegate {
}
}
if sub_id == home_subid {
switch context {
case .home:
insert_home_event(ev)
} else if sub_id == notifications_subid {
case .notifications:
handle_notification(ev: ev)
case .dms, .contacts, .initialRelayList, .initialContactList, .nwc:
break
}
}
@@ -1191,3 +1220,24 @@ func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale:
}
}
// MARK: - Extension to bridge NIP-65 relay list structs with app-native objects
// TODO: Do we need this??
//extension NIP65.RelayList {
// static func fromLegacyContactList(_ contactList: NdbNote) throws(BridgeError) -> Self {
// guard let relayListInfo = decode_json_relays(contactList.content) else { throw .couldNotDecodeRelayListInfo }
// let relayItems = relayListInfo.map({ url, rwConfiguration in
// return RelayItem(url: url, rwConfiguration: rwConfiguration.toNIP65RWConfiguration() ?? .readWrite)
// })
// return NIP65.RelayList(relays: relayItems)
// }
//
// static func fromLegacyContactList(_ contactList: NdbNote?) throws(BridgeError) -> Self? {
// guard let contactList = contactList else { return nil }
// return try fromLegacyContactList(contactList)
// }
//
// enum BridgeError: Error {
// case couldNotDecodeRelayListInfo
// }
//}
@@ -123,8 +123,8 @@ extension WalletConnect {
let delay = 0.0 // We don't need a delay when fetching a transaction list or balance
WalletConnect.request_transaction_list(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
WalletConnect.request_balance_information(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, delay: delay, on_flush: flusher)
damus_state.nostrNetwork.requestTransactionList(url: nwc, delay: delay, on_flush: flusher)
damus_state.nostrNetwork.requestBalanceInformation(url: nwc, delay: delay, on_flush: flusher)
return
}
@@ -153,22 +153,6 @@ extension WalletConnect {
}
}
/// Send a donation zap to the Damus team
static func send_donation_zap(pool: RelayPool, postbox: PostBox, nwc: WalletConnectURL, percent: Int, base_msats: Int64) async {
let percent_f = Double(percent) / 100.0
let donations_msats = Int64(percent_f * Double(base_msats))
let payreq = LNUrlPayRequest(allowsNostr: true, commentAllowed: nil, nostrPubkey: "", callback: "https://sendsats.lol/@damus")
guard let invoice = await fetch_zap_invoice(payreq, zapreq: nil, msats: donations_msats, zap_type: .non_zap, comment: nil) else {
// we failed... oh well. no donation for us.
print("damus-donation failed to fetch invoice")
return
}
print("damus-donation donating...")
WalletConnect.pay(url: nwc, pool: pool, post: postbox, invoice: invoice, zap_request: nil, delay: nil)
}
/// Handles a received Nostr Wallet Connect error
static func handle_error(zapcache: Zaps, evcache: EventCache, resp: WalletConnect.FullWalletResponse) {
// find a pending zap with the nwc request id associated with this response and remove it
@@ -185,7 +185,7 @@ struct SendPaymentView: View {
sendState = .processing
// Process payment
guard let payRequestEv = WalletConnect.pay(url: nwc, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: invoice.string, zap_request: nil, delay: nil) else {
guard let payRequestEv = damus_state.nostrNetwork.nwcPay(url: nwc, post: damus_state.nostrNetwork.postbox, invoice: invoice.string, zap_request: nil) else {
sendState = .failed(error: .init(
user_visible_description: NSLocalizedString("The payment request could not be made to your wallet provider.", comment: "A human-readable error message"),
tip: NSLocalizedString("Check if your wallet looks configured correctly and try again. If the error persists, please contact support.", comment: "A human-readable tip for an error when a payment request cannot be made to a wallet."),
+28 -34
View File
@@ -11,7 +11,7 @@ class ZapsModel: ObservableObject {
let state: DamusState
let target: ZapTarget
let zaps_subid = UUID().description
var zapCommsListener: Task<Void, Never>? = nil
let profiles_subid = UUID().description
init(state: DamusState, target: ZapTarget) {
@@ -31,46 +31,40 @@ class ZapsModel: ObservableObject {
case .note(let note_target):
filter.referenced_ids = [note_target.note_id]
}
state.nostrNetwork.pool.subscribe(sub_id: zaps_subid, filters: [filter], handler: handle_event)
zapCommsListener?.cancel()
zapCommsListener = Task {
for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) {
switch item {
case .event(let borrow):
var event: NostrEvent? = nil
try? borrow { ev in
event = ev.toOwned()
}
guard let event else { return }
await self.handle_event(ev: event)
case .eose:
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "zaps_model", load: .from_events(events), damus_state: state, txn: txn)
}
}
}
}
func unsubscribe() {
state.nostrNetwork.pool.unsubscribe(sub_id: zaps_subid)
zapCommsListener?.cancel()
zapCommsListener = nil
}
@MainActor
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
guard case .nostr_event(let resp) = conn_ev else {
func handle_event(ev: NostrEvent) {
guard ev.kind == 9735,
let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey),
let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey)
else {
return
}
guard resp.subid == zaps_subid else {
return
}
switch resp {
case .ok:
break
case .notice:
break
case .eose:
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
guard let txn = NdbTxn(ndb: state.ndb) else { return }
load_profiles(context: "zaps_model", profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state, txn: txn)
case .event(_, let ev):
guard ev.kind == 9735,
let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey),
let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey)
else {
return
}
self.state.add_zap(zap: .zap(zap))
case .auth:
break
}
self.state.add_zap(zap: .zap(zap))
}
}
@@ -175,7 +175,9 @@ struct CustomizeZapView: View {
} else {
Button(action: {
let amount = model.custom_amount_sats
send_zap(damus_state: state, target: target, lnurl: lnurl, is_custom: true, comment: model.comment, amount_sats: amount, zap_type: model.zap_type)
Task {
await send_zap(damus_state: state, target: target, lnurl: lnurl, is_custom: true, comment: model.comment, amount_sats: amount, zap_type: model.zap_type)
}
model.zapping = true
}) {
HStack {
@@ -72,7 +72,7 @@ struct NoteZapButton: View {
func tap() {
guard let our_zap else {
send_zap(damus_state: damus_state, target: target, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type)
Task { await send_zap(damus_state: damus_state, target: target, lnurl: lnurl, is_custom: false, comment: nil, amount_sats: nil, zap_type: damus_state.settings.default_zap_type) }
return
}
@@ -173,13 +173,13 @@ func initial_pending_zap_state(settings: UserSettingsStore) -> PendingZapState {
return .external(ExtPendingZapState(state: .fetching_invoice))
}
func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) {
func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) async {
guard let keypair = damus_state.keypair.to_full() else {
return
}
// Only take the first 10 because reasons
let relays = Array(damus_state.nostrNetwork.pool.our_descriptors.prefix(10))
let relays = Array(damus_state.nostrNetwork.ourRelayDescriptors.prefix(10))
let content = comment ?? ""
guard let mzapreq = make_zap_request_event(keypair: keypair, content: content, relays: relays, target: target, zap_type: zap_type) else {
@@ -232,7 +232,7 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
flusher = .once({ pe in
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
Task { @MainActor in
await WalletConnect.send_donation_zap(pool: damus_state.nostrNetwork.pool, postbox: damus_state.nostrNetwork.postbox, nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
await damus_state.nostrNetwork.send_donation_zap(nwc: nwc_state.url, percent: damus_state.settings.donation_percent, base_msats: amount_msat)
}
})
}
@@ -240,7 +240,7 @@ func send_zap(damus_state: DamusState, target: ZapTarget, lnurl: String, is_cust
// we don't have a delay on one-tap nozaps (since this will be from customize zap view)
let delay = damus_state.settings.nozaps ? nil : 5.0
let nwc_req = WalletConnect.pay(url: nwc_state.url, pool: damus_state.nostrNetwork.pool, post: damus_state.nostrNetwork.postbox, invoice: inv, zap_request: zapreq, delay: delay, on_flush: flusher)
let nwc_req = damus_state.nostrNetwork.nwcPay(url: nwc_state.url, post: damus_state.nostrNetwork.postbox, invoice: inv, delay: delay, on_flush: flusher)
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")