Make RelayPool private to NostrNetworkManager and migrate usages
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -381,6 +381,8 @@ struct ContentView: View {
|
|||||||
self.confirm_mute = true
|
self.confirm_mute = true
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.attached_wallet)) { nwc in
|
.onReceive(handle_notify(.attached_wallet)) { nwc in
|
||||||
|
try? damus_state.nostrNetwork.userRelayList.load() // Reload relay list to apply changes
|
||||||
|
|
||||||
// update the lightning address on our profile when we attach a
|
// update the lightning address on our profile when we attach a
|
||||||
// wallet with an associated
|
// wallet with an associated
|
||||||
guard let ds = self.damus_state,
|
guard let ds = self.damus_state,
|
||||||
@@ -472,7 +474,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.disconnect_relays)) { () in
|
.onReceive(handle_notify(.disconnect_relays)) { () in
|
||||||
damus_state.nostrNetwork.pool.disconnect()
|
damus_state.nostrNetwork.disconnect()
|
||||||
}
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { obj in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { obj in
|
||||||
print("txn: 📙 DAMUS ACTIVE NOTIFY")
|
print("txn: 📙 DAMUS ACTIVE NOTIFY")
|
||||||
@@ -518,7 +520,7 @@ struct ContentView: View {
|
|||||||
break
|
break
|
||||||
case .active:
|
case .active:
|
||||||
print("txn: 📙 DAMUS ACTIVE")
|
print("txn: 📙 DAMUS ACTIVE")
|
||||||
damus_state.nostrNetwork.pool.ping()
|
damus_state.nostrNetwork.ping()
|
||||||
@unknown default:
|
@unknown default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -717,8 +719,7 @@ struct ContentView: View {
|
|||||||
// Purple API is an experimental feature. If not enabled, do not connect `StoreObserver` with Purple API to avoid leaking receipts
|
// Purple API is an experimental feature. If not enabled, do not connect `StoreObserver` with Purple API to avoid leaking receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
|
||||||
damus_state.nostrNetwork.connect()
|
|
||||||
|
|
||||||
if #available(iOS 17, *) {
|
if #available(iOS 17, *) {
|
||||||
if damus_state.settings.developer_mode && damus_state.settings.reset_tips_on_launch {
|
if damus_state.settings.developer_mode && damus_state.settings.reset_tips_on_launch {
|
||||||
@@ -734,6 +735,11 @@ struct ContentView: View {
|
|||||||
Log.error("Failed to configure tips: %s", for: .tips, error.localizedDescription)
|
Log.error("Failed to configure tips: %s", for: .tips, error.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
damus_state.nostrNetwork.connect()
|
||||||
|
// TODO: Move this to a better spot. Not sure what is the best signal to listen to for sending initial filters
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
|
||||||
|
self.home.send_initial_filters()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func music_changed(_ state: MusicState) {
|
func music_changed(_ state: MusicState) {
|
||||||
@@ -943,169 +949,11 @@ enum FindEventType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum FoundEvent {
|
enum FoundEvent {
|
||||||
|
// TODO: Why not return the profile record itself? Right now the code probably just wants to trigger ndb to ingest the profile record and be available at ndb in parallel, but it would be cleaner if the function that uses this simply does that ndb query on their behalf.
|
||||||
case profile(Pubkey)
|
case profile(Pubkey)
|
||||||
case event(NostrEvent)
|
case event(NostrEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds an event from NostrDB if it exists, or from the network
|
|
||||||
///
|
|
||||||
/// This is the callback version. There is also an asyc/await version of this function.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - state: Damus state
|
|
||||||
/// - query_: The query, including the event being looked for, and the relays to use when looking
|
|
||||||
/// - callback: The function to call with results
|
|
||||||
func find_event(state: DamusState, query query_: FindEvent, callback: @escaping (FoundEvent?) -> ()) {
|
|
||||||
return find_event_with_subid(state: state, query: query_, subid: UUID().description, callback: callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds an event from NostrDB if it exists, or from the network
|
|
||||||
///
|
|
||||||
/// This is a the async/await version of `find_event`. Use this when using callbacks is impossible or cumbersome.
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - state: Damus state
|
|
||||||
/// - query_: The query, including the event being looked for, and the relays to use when looking
|
|
||||||
/// - callback: The function to call with results
|
|
||||||
func find_event(state: DamusState, query query_: FindEvent) async -> FoundEvent? {
|
|
||||||
await withCheckedContinuation { continuation in
|
|
||||||
find_event(state: state, query: query_) { event in
|
|
||||||
var already_resumed = false
|
|
||||||
if !already_resumed { // Ensure we do not resume twice, as it causes a crash
|
|
||||||
continuation.resume(returning: event)
|
|
||||||
already_resumed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func find_event_with_subid(state: DamusState, query query_: FindEvent, subid: String, callback: @escaping (FoundEvent?) -> ()) {
|
|
||||||
|
|
||||||
var filter: NostrFilter? = nil
|
|
||||||
let find_from = query_.find_from
|
|
||||||
let query = query_.type
|
|
||||||
|
|
||||||
switch query {
|
|
||||||
case .profile(let pubkey):
|
|
||||||
if let profile_txn = state.ndb.lookup_profile(pubkey),
|
|
||||||
let record = profile_txn.unsafeUnownedValue,
|
|
||||||
record.profile != nil
|
|
||||||
{
|
|
||||||
callback(.profile(pubkey))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
|
||||||
|
|
||||||
case .event(let evid):
|
|
||||||
if let ev = state.events.lookup(evid) {
|
|
||||||
callback(.event(ev))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filter = NostrFilter(ids: [evid], limit: 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var attempts: Int = 0
|
|
||||||
var has_event = false
|
|
||||||
guard let filter else { return }
|
|
||||||
|
|
||||||
state.nostrNetwork.pool.subscribe_to(sub_id: subid, filters: [filter], to: find_from) { relay_id, res in
|
|
||||||
guard case .nostr_event(let ev) = res else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard ev.subid == subid else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ev {
|
|
||||||
case .ok:
|
|
||||||
break
|
|
||||||
case .event(_, let ev):
|
|
||||||
has_event = true
|
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: subid)
|
|
||||||
|
|
||||||
switch query {
|
|
||||||
case .profile:
|
|
||||||
if ev.known_kind == .metadata {
|
|
||||||
callback(.profile(ev.pubkey))
|
|
||||||
}
|
|
||||||
case .event:
|
|
||||||
callback(.event(ev))
|
|
||||||
}
|
|
||||||
case .eose:
|
|
||||||
if !has_event {
|
|
||||||
attempts += 1
|
|
||||||
if attempts >= state.nostrNetwork.pool.our_descriptors.count {
|
|
||||||
callback(nil) // If we could not find any events in any of the relays we are connected to, send back nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: [relay_id]) // We are only finding an event once, so close subscription on eose
|
|
||||||
case .notice:
|
|
||||||
break
|
|
||||||
case .auth:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Finds a replaceable event based on an `naddr` address.
|
|
||||||
///
|
|
||||||
/// This is the callback version of the function. There is another function that makes use of async/await
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - damus_state: The Damus state
|
|
||||||
/// - naddr: the `naddr` address
|
|
||||||
/// - callback: A function to handle the found event
|
|
||||||
func naddrLookup(damus_state: DamusState, naddr: NAddr, callback: @escaping (NostrEvent?) -> ()) {
|
|
||||||
let nostrKinds: [NostrKind]? = NostrKind(rawValue: naddr.kind).map { [$0] }
|
|
||||||
|
|
||||||
let filter = NostrFilter(kinds: nostrKinds, authors: [naddr.author])
|
|
||||||
|
|
||||||
let subid = UUID().description
|
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.subscribe_to(sub_id: subid, filters: [filter], to: nil) { relay_id, res in
|
|
||||||
guard case .nostr_event(let ev) = res else {
|
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: [relay_id])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if case .event(_, let ev) = ev {
|
|
||||||
for tag in ev.tags {
|
|
||||||
if(tag.count >= 2 && tag[0].string() == "d"){
|
|
||||||
if (tag[1].string() == naddr.identifier){
|
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: [relay_id])
|
|
||||||
callback(ev)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: [relay_id])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finds a replaceable event based on an `naddr` address.
|
|
||||||
///
|
|
||||||
/// This is the async/await version of the function. Another version of this function which makes use of callback functions also exists .
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - damus_state: The Damus state
|
|
||||||
/// - naddr: the `naddr` address
|
|
||||||
/// - callback: A function to handle the found event
|
|
||||||
func naddrLookup(damus_state: DamusState, naddr: NAddr) async -> NostrEvent? {
|
|
||||||
await withCheckedContinuation { continuation in
|
|
||||||
var already_resumed = false
|
|
||||||
naddrLookup(damus_state: damus_state, naddr: naddr) { event in
|
|
||||||
if !already_resumed { // Ensure we do not resume twice, as it causes a crash
|
|
||||||
continuation.resume(returning: event)
|
|
||||||
already_resumed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeline_name(_ timeline: Timeline?) -> String {
|
func timeline_name(_ timeline: Timeline?) -> String {
|
||||||
guard let timeline else {
|
guard let timeline else {
|
||||||
return ""
|
return ""
|
||||||
@@ -1260,4 +1108,3 @@ func logout(_ state: DamusState?)
|
|||||||
state?.close()
|
state?.close()
|
||||||
notify(.logout)
|
notify(.logout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class NostrNetworkManager {
|
|||||||
/// ## Implementation notes
|
/// ## Implementation notes
|
||||||
///
|
///
|
||||||
/// - This will be marked `private` in the future to prevent other code from accessing the relay pool directly. Code outside this layer should use a higher level interface
|
/// - This will be marked `private` in the future to prevent other code from accessing the relay pool directly. Code outside this layer should use a higher level interface
|
||||||
let pool: RelayPool // TODO: Make this private and make higher level interface for classes outside the NostrNetworkManager
|
private let pool: RelayPool // TODO: Make this private and make higher level interface for classes outside the NostrNetworkManager
|
||||||
/// A delegate that allows us to interact with the rest of app without introducing hard or circular dependencies
|
/// A delegate that allows us to interact with the rest of app without introducing hard or circular dependencies
|
||||||
private var delegate: Delegate
|
private var delegate: Delegate
|
||||||
/// Manages the user's relay list, controls RelayPool's connected relays
|
/// Manages the user's relay list, controls RelayPool's connected relays
|
||||||
@@ -51,6 +51,14 @@ class NostrNetworkManager {
|
|||||||
func connect() {
|
func connect() {
|
||||||
self.userRelayList.connect()
|
self.userRelayList.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func disconnect() {
|
||||||
|
self.pool.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ping() {
|
||||||
|
self.pool.ping()
|
||||||
|
}
|
||||||
|
|
||||||
func relaysForEvent(event: NostrEvent) -> [RelayURL] {
|
func relaysForEvent(event: NostrEvent) -> [RelayURL] {
|
||||||
// TODO(tyiu) Ideally this list would be sorted by the event author's outbox relay preferences
|
// TODO(tyiu) Ideally this list would be sorted by the event author's outbox relay preferences
|
||||||
@@ -61,6 +69,174 @@ class NostrNetworkManager {
|
|||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: ORGANIZE THESE
|
||||||
|
|
||||||
|
// MARK: - Communication with the Nostr Network
|
||||||
|
/// ## Implementation notes
|
||||||
|
///
|
||||||
|
/// - This class hides the relay pool on purpose to avoid other code from dealing with complex relay + nostrDB logic.
|
||||||
|
/// - Instead, we provide an easy to use interface so that normal code can just get the info they want.
|
||||||
|
/// - This is also to help us migrate to the relay model.
|
||||||
|
// TODO: Define a better interface. This is a temporary scaffold to replace direct relay pool access. After that is done, we can refactor this interface to be cleaner and reduce non-sense.
|
||||||
|
|
||||||
|
func sendToNostrDB(event: NostrEvent) {
|
||||||
|
self.pool.send_raw_to_local_ndb(.typical(.event(event)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(event: NostrEvent) {
|
||||||
|
self.pool.send(.event(event))
|
||||||
|
}
|
||||||
|
|
||||||
|
func query(filters: [NostrFilter], to: [RelayURL]? = nil) async -> [NostrEvent] {
|
||||||
|
var events: [NostrEvent] = []
|
||||||
|
for await item in self.reader.subscribe(filters: filters, to: to) {
|
||||||
|
switch item {
|
||||||
|
case .event(let borrow):
|
||||||
|
try? borrow { event in
|
||||||
|
events.append(event.toOwned())
|
||||||
|
}
|
||||||
|
case .eose:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds a replaceable event based on an `naddr` address.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - naddr: the `naddr` address
|
||||||
|
func lookup(naddr: NAddr) async -> NostrEvent? {
|
||||||
|
var nostrKinds: [NostrKind]? = NostrKind(rawValue: naddr.kind).map { [$0] }
|
||||||
|
|
||||||
|
let filter = NostrFilter(kinds: nostrKinds, authors: [naddr.author])
|
||||||
|
|
||||||
|
for await item in self.reader.subscribe(filters: [filter]) {
|
||||||
|
switch item {
|
||||||
|
case .event(let borrow):
|
||||||
|
var event: NostrEvent? = nil
|
||||||
|
try? borrow { ev in
|
||||||
|
event = ev.toOwned()
|
||||||
|
}
|
||||||
|
if event?.referenced_params.first?.param.string() == naddr.identifier {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
case .eose:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Improve this. This is mostly intact to keep compatibility with its predecessor, but we can do better
|
||||||
|
func findEvent(query: FindEvent) async -> FoundEvent? {
|
||||||
|
var filter: NostrFilter? = nil
|
||||||
|
let find_from = query.find_from
|
||||||
|
let query = query.type
|
||||||
|
|
||||||
|
switch query {
|
||||||
|
case .profile(let pubkey):
|
||||||
|
if let profile_txn = delegate.ndb.lookup_profile(pubkey),
|
||||||
|
let record = profile_txn.unsafeUnownedValue,
|
||||||
|
record.profile != nil
|
||||||
|
{
|
||||||
|
return .profile(pubkey)
|
||||||
|
}
|
||||||
|
filter = NostrFilter(kinds: [.metadata], limit: 1, authors: [pubkey])
|
||||||
|
case .event(let evid):
|
||||||
|
if let event = delegate.ndb.lookup_note(evid)?.unsafeUnownedValue?.to_owned() {
|
||||||
|
return .event(event)
|
||||||
|
}
|
||||||
|
filter = NostrFilter(ids: [evid], limit: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var attempts: Int = 0
|
||||||
|
var has_event = false
|
||||||
|
guard let filter else { return nil }
|
||||||
|
|
||||||
|
for await item in self.reader.subscribe(filters: [filter], to: find_from) {
|
||||||
|
switch item {
|
||||||
|
case .event(let borrow):
|
||||||
|
var result: FoundEvent? = nil
|
||||||
|
try? borrow { event in
|
||||||
|
switch query {
|
||||||
|
case .profile:
|
||||||
|
if event.known_kind == .metadata {
|
||||||
|
result = .profile(event.pubkey)
|
||||||
|
}
|
||||||
|
case .event:
|
||||||
|
result = .event(event.toOwned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
case .eose:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRelay(_ id: RelayURL) -> RelayPool.Relay? {
|
||||||
|
pool.get_relay(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectedRelays: [RelayPool.Relay] {
|
||||||
|
self.pool.relays
|
||||||
|
}
|
||||||
|
|
||||||
|
var ourRelayDescriptors: [RelayPool.RelayDescriptor] {
|
||||||
|
self.pool.our_descriptors
|
||||||
|
}
|
||||||
|
|
||||||
|
func relayURLsThatSawNote(id: NoteId) -> Set<RelayURL>? {
|
||||||
|
return self.pool.seen[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
func determineToRelays(filters: RelayFilters) -> [RelayURL] {
|
||||||
|
return self.pool.our_descriptors
|
||||||
|
.map { $0.url }
|
||||||
|
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: NWC
|
||||||
|
// TODO: Move this to NWCManager
|
||||||
|
|
||||||
|
@discardableResult
|
||||||
|
func nwcPay(url: WalletConnectURL, post: PostBox, invoice: String, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil, zap_request: NostrEvent? = nil) -> NostrEvent? {
|
||||||
|
WalletConnect.pay(url: url, pool: self.pool, post: post, invoice: invoice, zap_request: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestTransactionList(url: WalletConnectURL, delay: TimeInterval? = 0.0, on_flush: OnFlush? = nil) {
|
||||||
|
WalletConnect.request_transaction_list(url: url, pool: self.pool, post: self.postbox, delay: delay, on_flush: on_flush)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestBalanceInformation(url: WalletConnectURL, delay: TimeInterval? = 0.0, on_flush: OnFlush? = nil) {
|
||||||
|
WalletConnect.request_balance_information(url: url, pool: self.pool, post: self.postbox, delay: delay, on_flush: on_flush)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a donation zap to the Damus team
|
||||||
|
func send_donation_zap(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: self.pool, post: self.postbox, invoice: invoice, zap_request: nil, delay: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: - App lifecycle functions
|
||||||
|
|
||||||
|
func close() {
|
||||||
|
pool.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ extension NostrNetworkManager {
|
|||||||
///
|
///
|
||||||
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
/// - Parameter filters: The nostr filters to specify what kind of data to subscribe to
|
||||||
/// - Returns: An async stream of nostr data
|
/// - Returns: An async stream of nostr data
|
||||||
func subscribe(filters: [NostrFilter]) -> AsyncStream<StreamItem> {
|
func subscribe(filters: [NostrFilter], to desiredRelays: [RelayURL]? = nil) -> AsyncStream<StreamItem> {
|
||||||
return AsyncStream<StreamItem> { continuation in
|
return AsyncStream<StreamItem> { continuation in
|
||||||
let streamTask = Task {
|
let streamTask = Task {
|
||||||
for await item in self.pool.subscribe(filters: filters) {
|
for await item in self.pool.subscribe(filters: filters, to: desiredRelays) {
|
||||||
switch item {
|
switch item {
|
||||||
case .eose: continuation.yield(.eose)
|
case .eose: continuation.yield(.eose)
|
||||||
case .event(let nostrEvent):
|
case .event(let nostrEvent):
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ struct QueuedRequest {
|
|||||||
let skip_ephemeral: Bool
|
let skip_ephemeral: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SeenEvent: Hashable {
|
||||||
|
let relay_id: RelayURL
|
||||||
|
let evid: NoteId
|
||||||
|
}
|
||||||
|
|
||||||
/// Establishes and manages connections and subscriptions to a list of relays.
|
/// Establishes and manages connections and subscriptions to a list of relays.
|
||||||
class RelayPool {
|
class RelayPool {
|
||||||
private(set) var relays: [Relay] = []
|
private(set) var relays: [Relay] = []
|
||||||
@@ -31,6 +36,8 @@ class RelayPool {
|
|||||||
var keypair: Keypair?
|
var keypair: Keypair?
|
||||||
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
||||||
var message_sent_function: (((String, Relay)) -> Void)?
|
var message_sent_function: (((String, Relay)) -> Void)?
|
||||||
|
var delegate: Delegate?
|
||||||
|
private(set) var signal: SignalModel = SignalModel()
|
||||||
|
|
||||||
private let network_monitor = NWPathMonitor()
|
private let network_monitor = NWPathMonitor()
|
||||||
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
||||||
@@ -410,3 +417,10 @@ func add_rw_relay(_ pool: RelayPool, _ url: RelayURL) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension RelayPool {
|
||||||
|
protocol Delegate {
|
||||||
|
func latestRelayListChanged(_ newEvent: NdbNote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible {
|
public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible, Sendable {
|
||||||
private(set) var url: URL
|
private(set) var url: URL
|
||||||
|
|
||||||
public var id: URL {
|
public var id: URL {
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
try await self.push_notification_client.revoke_token()
|
try await self.push_notification_client.revoke_token()
|
||||||
}
|
}
|
||||||
wallet.disconnect()
|
wallet.disconnect()
|
||||||
nostrNetwork.pool.close()
|
nostrNetwork.close()
|
||||||
ndb.close()
|
ndb.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class ActionBarModel: ObservableObject {
|
|||||||
self.our_zap = damus.zaps.our_zaps[evid]?.first
|
self.our_zap = damus.zaps.our_zaps[evid]?.first
|
||||||
self.our_reply = damus.replies.our_reply(evid)
|
self.our_reply = damus.replies.our_reply(evid)
|
||||||
self.our_quote_repost = damus.quote_reposts.our_events[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()
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ struct EventDetailBar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if bar.relays > 0 {
|
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)) {
|
NavigationLink(value: Route.UserRelays(relays: relays)) {
|
||||||
let nounString = pluralizedString(key: "relays_count", count: bar.relays)
|
let nounString = pluralizedString(key: "relays_count", count: bar.relays)
|
||||||
let noun = Text(nounString).foregroundColor(.gray)
|
let noun = Text(nounString).foregroundColor(.gray)
|
||||||
|
|||||||
@@ -56,12 +56,7 @@ class ThreadModel: ObservableObject {
|
|||||||
/// The damus state, needed to access the relay pool and load the thread events
|
/// The damus state, needed to access the relay pool and load the thread events
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
private let profiles_subid = UUID().description
|
private var listener: Task<Void, Never>?
|
||||||
private let base_subid = UUID().description
|
|
||||||
private let meta_subid = UUID().description
|
|
||||||
private var subids: [String] {
|
|
||||||
return [profiles_subid, base_subid, meta_subid]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: Initialization
|
// MARK: Initialization
|
||||||
@@ -86,17 +81,6 @@ class ThreadModel: ObservableObject {
|
|||||||
|
|
||||||
// MARK: Relay pool subscription management
|
// 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.
|
/// Subscribe to events in this thread. Call this when loading the view.
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
var meta_events = NostrFilter()
|
var meta_events = NostrFilter()
|
||||||
@@ -127,10 +111,27 @@ class ThreadModel: ObservableObject {
|
|||||||
|
|
||||||
let base_filters = [event_filter, ref_events]
|
let base_filters = [event_filter, ref_events]
|
||||||
let meta_filters = [meta_events, quote_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)
|
self.listener?.cancel()
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: base_filters, handler: handle_event)
|
self.listener = Task {
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event)
|
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.
|
/// 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
|
/// Marked as private because it is this class' responsibility to load events, not the view's. Simplify the interface
|
||||||
@MainActor
|
@MainActor
|
||||||
private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
private func handle_event(ev: NostrEvent) {
|
||||||
let (sub_id, done) = handle_subid_event(pool: damus_state.nostrNetwork.pool, relay_id: relay_id, ev: ev) { sid, ev in
|
if ev.known_kind == .zap {
|
||||||
guard subids.contains(sid) else {
|
process_zap_event(state: damus_state, ev: ev) { zap in
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
} else if ev.is_textlike {
|
||||||
if ev.known_kind == .zap {
|
// handle thread quote reposts, we just count them instead of
|
||||||
process_zap_event(state: damus_state, ev: ev) { zap in
|
// 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 if ev.is_textlike {
|
} else {
|
||||||
// handle thread quote reposts, we just count them instead of
|
self.add_event(ev, keypair: damus_state.keypair)
|
||||||
// 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
|
// MARK: External control interface
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ struct EventLoaderView<Content: View>: View {
|
|||||||
let event_id: NoteId
|
let event_id: NoteId
|
||||||
@State var event: NostrEvent?
|
@State var event: NostrEvent?
|
||||||
@State var subscription_uuid: String = UUID().description
|
@State var subscription_uuid: String = UUID().description
|
||||||
|
@State var loadingTask: Task<Void, Never>? = nil
|
||||||
let content: (NostrEvent) -> Content
|
let content: (NostrEvent) -> Content
|
||||||
|
|
||||||
init(damus_state: DamusState, event_id: NoteId, @ViewBuilder content: @escaping (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() {
|
func unsubscribe() {
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subscription_uuid)
|
self.loadingTask?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(filters: [NostrFilter]) {
|
func subscribe(filters: [NostrFilter]) {
|
||||||
damus_state.nostrNetwork.pool.register_handler(sub_id: subscription_uuid, handler: handle_event)
|
self.loadingTask?.cancel()
|
||||||
damus_state.nostrNetwork.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid)))
|
self.loadingTask = Task {
|
||||||
}
|
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: filters) {
|
||||||
|
switch item {
|
||||||
func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
case .event(let borrow):
|
||||||
guard case .nostr_event(let nostr_response) = ev else {
|
try? borrow { ev in
|
||||||
return
|
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() {
|
func load() {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ struct MenuItems: View {
|
|||||||
profileModel.subscribeToFindRelays()
|
profileModel.subscribeToFindRelays()
|
||||||
}
|
}
|
||||||
.onDisappear() {
|
.onDisappear() {
|
||||||
profileModel.unsubscribeFindRelays()
|
profileModel.findRelaysListener?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ class EventsModel: ObservableObject {
|
|||||||
let state: DamusState
|
let state: DamusState
|
||||||
let target: NoteId
|
let target: NoteId
|
||||||
let kind: QueryKind
|
let kind: QueryKind
|
||||||
let sub_id = UUID().uuidString
|
|
||||||
let profiles_id = UUID().uuidString
|
let profiles_id = UUID().uuidString
|
||||||
var events: EventHolder
|
var events: EventHolder
|
||||||
@Published var loading: Bool
|
@Published var loading: Bool
|
||||||
|
var loadingTask: Task<Void, Never>?
|
||||||
|
|
||||||
enum QueryKind {
|
enum QueryKind {
|
||||||
case kind(NostrKind)
|
case kind(NostrKind)
|
||||||
@@ -68,13 +68,29 @@ class EventsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func subscribe() {
|
||||||
state.nostrNetwork.pool.subscribe(sub_id: sub_id,
|
loadingTask?.cancel()
|
||||||
filters: [get_filter()],
|
loadingTask = Task {
|
||||||
handler: handle_nostr_event)
|
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() {
|
func unsubscribe() {
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
|
loadingTask?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle_event(relay_id: RelayURL, ev: NostrEvent) {
|
private func handle_event(relay_id: RelayURL, ev: NostrEvent) {
|
||||||
@@ -82,28 +98,4 @@ class EventsModel: ObservableObject {
|
|||||||
objectWillChange.send()
|
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)
|
/// Asynchronously find an event from NostrDB or from the network (if not available on NostrDB)
|
||||||
private func loadEvent(noteId: NoteId) async -> NostrEvent? {
|
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 }
|
guard let res, case .event(let ev) = res else { return nil }
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ class LoadableNostrEventViewModel: ObservableObject {
|
|||||||
return .unknown_or_unsupported_kind
|
return .unknown_or_unsupported_kind
|
||||||
}
|
}
|
||||||
case .naddr(let naddr):
|
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)))
|
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
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let subid = UUID().description
|
var listener: Task<Void, Never>? = nil
|
||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
init(damus_state: DamusState) {
|
||||||
@@ -25,52 +25,40 @@ class FollowPackModel: ObservableObject {
|
|||||||
|
|
||||||
func subscribe(follow_pack_users: [Pubkey]) {
|
func subscribe(follow_pack_users: [Pubkey]) {
|
||||||
loading = true
|
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])
|
var filter = NostrFilter(kinds: [.text, .chat])
|
||||||
filter.until = UInt32(Date.now.timeIntervalSince1970)
|
filter.until = UInt32(Date.now.timeIntervalSince1970)
|
||||||
filter.authors = follow_pack_users
|
filter.authors = follow_pack_users
|
||||||
filter.limit = 500
|
filter.limit = 500
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: subid, filters: [filter], handler: handle_event, to: to_relays)
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter], to: to_relays) {
|
||||||
}
|
switch item {
|
||||||
|
case .event(borrow: let borrow):
|
||||||
func unsubscribe(to: RelayURL? = nil) {
|
var event: NostrEvent? = nil
|
||||||
loading = false
|
try? borrow { ev in
|
||||||
damus_state.nostrNetwork.pool.unsubscribe(sub_id: subid, to: to.map { [$0] })
|
event = ev.toOwned()
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
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
|
@Published var contacts: [Pubkey]? = nil
|
||||||
var has_contact: Set<Pubkey> = Set()
|
var has_contact: Set<Pubkey> = Set()
|
||||||
|
|
||||||
let sub_id: String = UUID().description
|
var listener: Task<Void, Never>? = nil
|
||||||
let profiles_id: String = UUID().description
|
var profilesListener: Task<Void, Never>? = nil
|
||||||
|
|
||||||
var count: Int? {
|
var count: Int? {
|
||||||
guard let contacts = self.contacts else {
|
guard let contacts = self.contacts else {
|
||||||
@@ -36,12 +36,27 @@ class FollowersModel: ObservableObject {
|
|||||||
func subscribe() {
|
func subscribe() {
|
||||||
let filter = get_filter()
|
let filter = get_filter()
|
||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
//print_filters(relay_id: "following", filters: [filters])
|
self.listener?.cancel()
|
||||||
self.damus_state.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
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() {
|
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) {
|
func handle_contact_event(_ ev: NostrEvent) {
|
||||||
@@ -53,7 +68,7 @@ class FollowersModel: ObservableObject {
|
|||||||
has_contact.insert(ev.pubkey)
|
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)
|
let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn)
|
||||||
if authors.isEmpty {
|
if authors.isEmpty {
|
||||||
return
|
return
|
||||||
@@ -61,38 +76,24 @@ class FollowersModel: ObservableObject {
|
|||||||
|
|
||||||
let filter = NostrFilter(kinds: [.metadata],
|
let filter = NostrFilter(kinds: [.metadata],
|
||||||
authors: authors)
|
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 {
|
self.profilesListener?.cancel()
|
||||||
case .event(let sub_id, let ev):
|
self.profilesListener = Task {
|
||||||
guard sub_id == self.sub_id || sub_id == self.profiles_id else {
|
for await item in await damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
||||||
return
|
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)
|
|
||||||
}
|
func handle_event(ev: NostrEvent) {
|
||||||
case .notice(let msg):
|
if ev.known_kind == .contacts {
|
||||||
print("followingmodel notice: \(msg)")
|
handle_contact_event(ev)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class FollowingModel {
|
|||||||
let contacts: [Pubkey]
|
let contacts: [Pubkey]
|
||||||
let hashtags: [Hashtag]
|
let hashtags: [Hashtag]
|
||||||
|
|
||||||
let sub_id: String = UUID().description
|
private var listener: Task<Void, Never>? = nil
|
||||||
|
|
||||||
init(damus_state: DamusState, contacts: [Pubkey], hashtags: [Hashtag]) {
|
init(damus_state: DamusState, contacts: [Pubkey], hashtags: [Hashtag]) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
@@ -41,19 +41,17 @@ class FollowingModel {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
let filters = [filter]
|
let filters = [filter]
|
||||||
//print_filters(relay_id: "following", filters: [filters])
|
self.listener?.cancel()
|
||||||
self.damus_state.nostrNetwork.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
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() {
|
func unsubscribe() {
|
||||||
if !needs_sub {
|
self.listener?.cancel()
|
||||||
return
|
self.listener = nil
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
|
|
||||||
let domain: String
|
let domain: String
|
||||||
var filter: NostrFilter
|
var filter: NostrFilter
|
||||||
let sub_id = UUID().description
|
var loadingTask: Task<Void, Never>?
|
||||||
let profiles_subid = UUID().description
|
|
||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
init(state: DamusState, domain: String) {
|
init(state: DamusState, domain: String) {
|
||||||
@@ -29,6 +28,20 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func subscribe() {
|
@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.limit = self.limit
|
||||||
filter.kinds = [.text, .longform, .highlight]
|
filter.kinds = [.text, .longform, .highlight]
|
||||||
|
|
||||||
@@ -50,16 +63,19 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
filter.authors = Array(authors)
|
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)
|
for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
||||||
loading = true
|
switch item {
|
||||||
state.nostrNetwork.pool.send(.subscribe(.init(filters: [filter], sub_id: sub_id)))
|
case .event(borrow: let borrow):
|
||||||
}
|
try? borrow { event in
|
||||||
|
self.add_event(event.toOwned())
|
||||||
func unsubscribe() {
|
guard let txn = NdbTxn(ndb: state.ndb) else { return }
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
|
load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn)
|
||||||
loading = false
|
}
|
||||||
print("unsubscribing from notes from friends of friends with '\(domain)' NIP-05 domain with sub_id \(sub_id)")
|
case .eose:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_event(_ ev: NostrEvent) {
|
func add_event(_ ev: NostrEvent) {
|
||||||
@@ -75,23 +91,4 @@ class NIP05DomainEventsModel: ObservableObject {
|
|||||||
objectWillChange.send()
|
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
|
// - 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
|
// 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 {
|
var body: some View {
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class Drafts: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Saves the drafts tracked by this class persistently using NostrDB + UserDefaults
|
/// 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] = []
|
var draft_events: [NdbNote] = []
|
||||||
post_artifact_block: if let post_artifacts = self.post {
|
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)
|
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:
|
// 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
|
// - 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)
|
// - 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() })
|
damus_state.settings.draft_event_ids = draft_events.map({ $0.id.hex() })
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ struct PostView: View {
|
|||||||
self.prompt_view = prompt_view
|
self.prompt_view = prompt_view
|
||||||
self.placeholder_messages = placeholder_messages ?? [POST_PLACEHOLDER]
|
self.placeholder_messages = placeholder_messages ?? [POST_PLACEHOLDER]
|
||||||
self.initial_text_suffix = initial_text_suffix
|
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
|
@Environment(\.dismiss) var dismiss
|
||||||
@@ -231,7 +231,7 @@ struct PostView: View {
|
|||||||
damus_state.drafts.post = nil
|
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 {
|
func load_draft() -> Bool {
|
||||||
|
|||||||
@@ -23,17 +23,21 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let MAX_SHARE_RELAYS = 4
|
||||||
|
|
||||||
var events: EventHolder
|
var events: EventHolder
|
||||||
let pubkey: Pubkey
|
let pubkey: Pubkey
|
||||||
let damus: DamusState
|
let damus: DamusState
|
||||||
|
|
||||||
var seen_event: Set<NoteId> = Set()
|
var seen_event: Set<NoteId> = Set()
|
||||||
var sub_id = UUID().description
|
|
||||||
var prof_subid = UUID().description
|
var findRelaysListener: Task<Void, Never>? = nil
|
||||||
var conversations_subid = UUID().description
|
var listener: Task<Void, Never>? = nil
|
||||||
var findRelay_subid = UUID().description
|
var profileListener: Task<Void, Never>? = nil
|
||||||
|
var conversationListener: Task<Void, Never>? = nil
|
||||||
|
|
||||||
var conversation_events: Set<NoteId> = Set()
|
var conversation_events: Set<NoteId> = Set()
|
||||||
|
|
||||||
init(pubkey: Pubkey, damus: DamusState) {
|
init(pubkey: Pubkey, damus: DamusState) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
@@ -46,7 +50,7 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
guard let contacts = self.contacts else {
|
guard let contacts = self.contacts else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return contacts.referenced_pubkeys.contains(pubkey)
|
return contacts.referenced_pubkeys.contains(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,39 +64,53 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool {
|
static func == (lhs: ProfileModel, rhs: ProfileModel) -> Bool {
|
||||||
return lhs.pubkey == rhs.pubkey
|
return lhs.pubkey == rhs.pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(pubkey)
|
hasher.combine(pubkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe() {
|
func subscribe() {
|
||||||
print("unsubscribing from profile \(pubkey) with sub_id \(sub_id)")
|
print("subscribing to profile \(pubkey)")
|
||||||
damus.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
|
listener?.cancel()
|
||||||
damus.nostrNetwork.pool.unsubscribe(sub_id: prof_subid)
|
listener = Task {
|
||||||
if pubkey != damus.pubkey {
|
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
|
||||||
damus.nostrNetwork.pool.unsubscribe(sub_id: conversations_subid)
|
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() {
|
func listenToConversations() async {
|
||||||
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() {
|
|
||||||
// Only subscribe to conversation events if the profile is not us.
|
// Only subscribe to conversation events if the profile is not us.
|
||||||
guard pubkey != damus.pubkey else {
|
guard pubkey != damus.pubkey else {
|
||||||
return
|
return
|
||||||
@@ -102,10 +120,35 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
let conversations_filter_them = NostrFilter(kinds: conversation_kinds, pubkeys: [damus.pubkey], limit: limit, authors: [pubkey])
|
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])
|
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)")
|
print("subscribing to conversation events from and to profile \(pubkey)")
|
||||||
damus.nostrNetwork.pool.subscribe(sub_id: conversations_subid, filters: [conversations_filter_them, conversations_filter_us], handler: handle_event)
|
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) {
|
func handle_profile_contact_event(_ ev: NostrEvent) {
|
||||||
process_contact_event(state: damus, ev: ev)
|
process_contact_event(state: damus, ev: ev)
|
||||||
|
|
||||||
@@ -120,8 +163,13 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
self.following = count_pubkeys(ev.tags)
|
self.following = count_pubkeys(ev.tags)
|
||||||
self.legacy_relay_list = decode_json_relays(ev.content)
|
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 ev.is_textlike || ev.known_kind == .boost {
|
||||||
if self.events.insert(ev) {
|
if self.events.insert(ev) {
|
||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
@@ -134,72 +182,13 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
}
|
}
|
||||||
seen_event.insert(ev.id)
|
seen_event.insert(ev.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the event public key matches the public key(s) we are querying.
|
private func handleNostrEvent(_ ev: NostrEvent) {
|
||||||
// This is done to protect against a relay not properly filtering events by the pubkey
|
// Ensure the event public key matches this profiles public key
|
||||||
// See https://github.com/damus-io/damus/issues/1846 for more information
|
// This is done to protect against a relay not properly filtering events by the pubkey
|
||||||
private func relay_filtered_correctly(_ ev: NostrEvent, subid: String?) -> Bool {
|
// See https://github.com/damus-io/damus/issues/1846 for more information
|
||||||
if subid == self.conversations_subid {
|
guard self.pubkey == ev.pubkey else { return }
|
||||||
switch ev.pubkey {
|
Task { await add_event(ev) }
|
||||||
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 findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
private func findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) {
|
||||||
@@ -211,12 +200,27 @@ class ProfileModel: ObservableObject, Equatable {
|
|||||||
func subscribeToFindRelays() {
|
func subscribeToFindRelays() {
|
||||||
var profile_filter = NostrFilter(kinds: [.contacts])
|
var profile_filter = NostrFilter(kinds: [.contacts])
|
||||||
profile_filter.authors = [pubkey]
|
profile_filter.authors = [pubkey]
|
||||||
|
self.findRelaysListener?.cancel()
|
||||||
damus.nostrNetwork.pool.subscribe(sub_id: findRelay_subid, filters: [profile_filter], handler: findRelaysHandler)
|
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() {
|
func unsubscribeFindRelays() {
|
||||||
damus.nostrNetwork.pool.unsubscribe(sub_id: findRelay_subid)
|
self.findRelaysListener?.cancel()
|
||||||
|
self.findRelaysListener = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCappedRelays() -> [RelayURL] {
|
func getCappedRelays() -> [RelayURL] {
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ fileprivate struct ProfileActionSheetZapButton: View {
|
|||||||
VStack(alignment: .center, spacing: 10) {
|
VStack(alignment: .center, spacing: 10) {
|
||||||
Button(
|
Button(
|
||||||
action: {
|
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
|
zap_state = .zapping
|
||||||
},
|
},
|
||||||
label: {
|
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)
|
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) {
|
init(state: DamusState) {
|
||||||
self.state = state
|
self.state = state
|
||||||
_relays = State(initialValue: state.nostrNetwork.pool.our_descriptors)
|
_relays = State(initialValue: state.nostrNetwork.ourRelayDescriptors)
|
||||||
UITabBar.appearance().isHidden = true
|
UITabBar.appearance().isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ struct RelayConfigView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
.onReceive(handle_notify(.relays_changed)) { _ in
|
||||||
self.relays = state.nostrNetwork.pool.our_descriptors
|
self.relays = state.nostrNetwork.ourRelayDescriptors
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
notify(.display_tabbar(false))
|
notify(.display_tabbar(false))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct RelayDetailView: View {
|
|||||||
|
|
||||||
func RemoveRelayButton(_ keypair: FullKeypair) -> some View {
|
func RemoveRelayButton(_ keypair: FullKeypair) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
self.removeRelay()
|
Task { await self.removeRelay() }
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Disconnect", comment: "Button to disconnect from the relay.")
|
Text("Disconnect", comment: "Button to disconnect from the relay.")
|
||||||
@@ -43,7 +43,7 @@ struct RelayDetailView: View {
|
|||||||
|
|
||||||
func ConnectRelayButton(_ keypair: FullKeypair) -> some View {
|
func ConnectRelayButton(_ keypair: FullKeypair) -> some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
self.connectRelay()
|
Task { await self.connectRelay() }
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Connect", comment: "Button to connect to the relay.")
|
Text("Connect", comment: "Button to connect to the relay.")
|
||||||
@@ -177,16 +177,18 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var relay_object: RelayPool.Relay? {
|
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? {
|
private var relay_connection: RelayConnection? {
|
||||||
relay_object?.connection
|
relay_object?.connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeRelay() {
|
func removeRelay() async {
|
||||||
do {
|
do {
|
||||||
try state.nostrNetwork.userRelayList.remove(relayURL: self.relay)
|
// TODO: Concurrency problems?
|
||||||
|
try await state.nostrNetwork.userRelayList.remove(relayURL: self.relay)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
@@ -194,9 +196,10 @@ struct RelayDetailView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectRelay() {
|
func connectRelay() async {
|
||||||
do {
|
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()
|
dismiss()
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ struct RelayFilterView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var relays: [RelayPool.RelayDescriptor] {
|
var relays: [RelayPool.RelayDescriptor] {
|
||||||
return state.nostrNetwork.pool.our_descriptors
|
return state.nostrNetwork.ourRelayDescriptors
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ struct RelayStatusView: View {
|
|||||||
|
|
||||||
struct RelayStatusView_Previews: PreviewProvider {
|
struct RelayStatusView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
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)
|
RelayStatusView(connection: connection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ struct RelayToggle: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var relay_connection: RelayConnection? {
|
private var relay_connection: RelayConnection? {
|
||||||
state.nostrNetwork.pool.get_relay(relay_id)?.connection
|
state.nostrNetwork.getRelay(relay_id)?.connection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ struct RelayView: View {
|
|||||||
self.recommended = recommended
|
self.recommended = recommended
|
||||||
self.model_cache = state.relay_model_cache
|
self.model_cache = state.relay_model_cache
|
||||||
_showActionButtons = showActionButtons
|
_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._relay_state = State(initialValue: relay_state)
|
||||||
self.disableNavLink = disableNavLink
|
self.disableNavLink = disableNavLink
|
||||||
}
|
}
|
||||||
|
|
||||||
static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool {
|
static func get_relay_state(state: DamusState, relay: RelayURL) -> Bool {
|
||||||
return pool.get_relay(relay) == nil
|
return state.nostrNetwork.getRelay(relay) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -110,7 +110,7 @@ struct RelayView: View {
|
|||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
.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 {
|
.onTapGesture {
|
||||||
if !disableNavLink {
|
if !disableNavLink {
|
||||||
@@ -120,7 +120,7 @@ struct RelayView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var relay_connection: RelayConnection? {
|
private var relay_connection: RelayConnection? {
|
||||||
state.nostrNetwork.pool.get_relay(relay)?.connection
|
state.nostrNetwork.getRelay(relay)?.connection
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_action(keypair: FullKeypair) async {
|
func add_action(keypair: FullKeypair) async {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ struct UserRelaysView: View {
|
|||||||
|
|
||||||
static func make_relay_state(state: DamusState, relays: [RelayURL]) -> [(RelayURL, Bool)] {
|
static func make_relay_state(state: DamusState, relays: [RelayURL]) -> [(RelayURL, Bool)] {
|
||||||
return relays.map({ r in
|
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 }
|
}).sorted { (a, b) in a.0 < b.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,63 +39,41 @@ class SearchHomeModel: ObservableObject {
|
|||||||
self.objectWillChange.send()
|
self.objectWillChange.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribe() {
|
func load() async {
|
||||||
loading = true
|
loading = true
|
||||||
let to_relays = determine_to_relays(pool: damus_state.nostrNetwork.pool, filters: damus_state.relay_filters)
|
let to_relays = damus_state.nostrNetwork.ourRelayDescriptors
|
||||||
|
.map { $0.url }
|
||||||
var follow_list_filter = NostrFilter(kinds: [.follow_list])
|
.filter { !damus_state.relay_filters.is_filtered(timeline: .search, relay_id: $0) }
|
||||||
follow_list_filter.until = UInt32(Date.now.timeIntervalSince1970)
|
|
||||||
|
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays)
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [get_base_filter()], to: to_relays) {
|
||||||
damus_state.nostrNetwork.pool.subscribe(sub_id: follow_pack_subid, filters: [follow_list_filter], handler: handle_event, to: to_relays)
|
switch item {
|
||||||
}
|
case .event(let borrow):
|
||||||
|
var event: NostrEvent? = nil
|
||||||
func unsubscribe(to: RelayURL? = nil) {
|
try? borrow { ev in
|
||||||
loading = false
|
event = ev.toOwned()
|
||||||
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] })
|
guard let event else { return }
|
||||||
}
|
await self.handleEvent(event)
|
||||||
|
case .eose: break
|
||||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
}
|
||||||
guard case .nostr_event(let event) = conn_ev else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
loading = false
|
||||||
|
|
||||||
switch event {
|
guard let txn = NdbTxn(ndb: damus_state.ndb) else { return }
|
||||||
case .event(let sub_id, let ev):
|
load_profiles(context: "universe", load: .from_events(events.all_events), damus_state: damus_state, txn: txn)
|
||||||
guard sub_id == self.base_subid || sub_id == self.profiles_subid || sub_id == self.follow_pack_subid else {
|
}
|
||||||
|
|
||||||
|
@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
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
seen_pubkey.insert(ev.pubkey)
|
||||||
{
|
|
||||||
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
|
|
||||||
|
|
||||||
if sub_id == self.base_subid {
|
if self.events.insert(ev) {
|
||||||
// Make sure we unsubscribe after we've fetched the global events
|
self.objectWillChange.send()
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
|
||||||
case .auth:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,44 +113,35 @@ enum PubkeysToLoad {
|
|||||||
case from_keys([Pubkey])
|
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)
|
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn)
|
||||||
|
|
||||||
guard !authors.isEmpty else {
|
guard !authors.isEmpty else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print("load_profiles[\(context)]: requesting \(authors.count) profiles from \(relay_id)")
|
Task {
|
||||||
|
print("load_profiles[\(context)]: requesting \(authors.count) profiles from relay pool")
|
||||||
let filter = NostrFilter(kinds: [.metadata], authors: authors)
|
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
|
|
||||||
|
|
||||||
let now = UInt64(Date.now.timeIntervalSince1970)
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
||||||
switch conn_ev {
|
let now = UInt64(Date.now.timeIntervalSince1970)
|
||||||
case .ws_connection_event:
|
switch item {
|
||||||
break
|
case .event(let borrow):
|
||||||
case .nostr_event(let ev):
|
var event: NostrEvent? = nil
|
||||||
guard ev.subid == profiles_subid, rid == relay_id else { return }
|
try? borrow { ev in
|
||||||
|
event = ev.toOwned()
|
||||||
switch ev {
|
}
|
||||||
case .event(_, let ev):
|
guard let event else { return }
|
||||||
if ev.known_kind == .metadata {
|
if event.known_kind == .metadata {
|
||||||
damus_state.ndb.write_profile_last_fetched(pubkey: ev.pubkey, fetched_at: now)
|
damus_state.ndb.write_profile_last_fetched(pubkey: event.pubkey, fetched_at: now)
|
||||||
}
|
}
|
||||||
case .eose:
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("load_profiles[\(context)]: done loading \(authors.count) profiles from relay pool")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class SearchModel: ObservableObject {
|
|||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
|
|
||||||
var search: NostrFilter
|
var search: NostrFilter
|
||||||
let sub_id = UUID().description
|
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
|
var listener: Task<Void, Never>? = nil
|
||||||
let limit: UInt32 = 500
|
let limit: UInt32 = 500
|
||||||
|
|
||||||
init(state: DamusState, search: NostrFilter) {
|
init(state: DamusState, search: NostrFilter) {
|
||||||
@@ -39,17 +39,32 @@ class SearchModel: ObservableObject {
|
|||||||
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
|
search.kinds = [.text, .like, .longform, .highlight, .follow_list]
|
||||||
|
|
||||||
//likes_filter.ids = ref_events.referenced_ids!
|
//likes_filter.ids = ref_events.referenced_ids!
|
||||||
|
listener?.cancel()
|
||||||
print("subscribing to search '\(search)' with sub_id \(sub_id)")
|
listener = Task {
|
||||||
state.nostrNetwork.pool.register_handler(sub_id: sub_id, handler: handle_event)
|
self.loading = true
|
||||||
loading = true
|
print("subscribing to search")
|
||||||
state.nostrNetwork.pool.send(.subscribe(.init(filters: [search], sub_id: sub_id)))
|
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() {
|
func unsubscribe() {
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: sub_id)
|
listener?.cancel()
|
||||||
loading = false
|
listener = nil
|
||||||
print("unsubscribing from search '\(search)' with sub_id \(sub_id)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func add_event(_ ev: NostrEvent) {
|
func add_event(_ ev: NostrEvent) {
|
||||||
@@ -65,25 +80,6 @@ class SearchModel: ObservableObject {
|
|||||||
objectWillChange.send()
|
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 {
|
func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
|
||||||
@@ -106,33 +102,3 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
|
|||||||
}
|
}
|
||||||
return true
|
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
|
@StateObject var model: SearchHomeModel
|
||||||
@State var search: String = ""
|
@State var search: String = ""
|
||||||
@FocusState private var isFocused: Bool
|
@FocusState private var isFocused: Bool
|
||||||
|
@State var loadingTask: Task<Void, Never>?
|
||||||
|
|
||||||
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
|
func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
|
||||||
var filters = ContentFilters.defaults(damus_state: damus_state)
|
var filters = ContentFilters.defaults(damus_state: damus_state)
|
||||||
@@ -84,8 +85,8 @@ struct SearchHomeView: View {
|
|||||||
)
|
)
|
||||||
.refreshable {
|
.refreshable {
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||||
model.unsubscribe()
|
loadingTask?.cancel()
|
||||||
model.subscribe()
|
loadingTask = Task { await model.load() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,8 +94,8 @@ struct SearchHomeView: View {
|
|||||||
SearchResultsView(damus_state: damus_state, search: $search)
|
SearchResultsView(damus_state: damus_state, search: $search)
|
||||||
.refreshable {
|
.refreshable {
|
||||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||||
model.unsubscribe()
|
loadingTask?.cancel()
|
||||||
model.subscribe()
|
loadingTask = Task { await model.load() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,11 +130,11 @@ struct SearchHomeView: View {
|
|||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if model.events.events.isEmpty {
|
if model.events.events.isEmpty {
|
||||||
model.subscribe()
|
loadingTask = Task { await model.load() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
model.unsubscribe()
|
loadingTask?.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,7 +77,8 @@ struct SearchingEventView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case .event(let note_id):
|
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 {
|
guard case .event(let ev) = res else {
|
||||||
self.search_state = .not_found
|
self.search_state = .not_found
|
||||||
return
|
return
|
||||||
@@ -85,7 +86,8 @@ struct SearchingEventView: View {
|
|||||||
self.search_state = .found(ev)
|
self.search_state = .found(ev)
|
||||||
}
|
}
|
||||||
case .profile(let pubkey):
|
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 {
|
guard case .profile(let pubkey) = res else {
|
||||||
self.search_state = .not_found
|
self.search_state = .not_found
|
||||||
return
|
return
|
||||||
@@ -93,7 +95,8 @@ struct SearchingEventView: View {
|
|||||||
self.search_state = .found_profile(pubkey)
|
self.search_state = .found_profile(pubkey)
|
||||||
}
|
}
|
||||||
case .naddr(let naddr):
|
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 {
|
guard let res = res else {
|
||||||
self.search_state = .not_found
|
self.search_state = .not_found
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ struct FirstAidSettingsView: View {
|
|||||||
guard let new_contact_list_event = make_first_contact_event(keypair: damus_state.keypair) else {
|
guard let new_contact_list_event = make_first_contact_event(keypair: damus_state.keypair) else {
|
||||||
throw FirstAidError.cannotMakeFirstContactEvent
|
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()
|
damus_state.settings.latest_contact_event_id_hex = new_contact_list_event.id.hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,12 +65,11 @@ class HomeModel: ContactsDelegate {
|
|||||||
let resub_debouncer = Debouncer(interval: 3.0)
|
let resub_debouncer = Debouncer(interval: 3.0)
|
||||||
var should_debounce_dms = true
|
var should_debounce_dms = true
|
||||||
|
|
||||||
let home_subid = UUID().description
|
var homeHandlerTask: Task<Void, Never>?
|
||||||
let contacts_subid = UUID().description
|
var contactsHandlerTask: Task<Void, Never>?
|
||||||
let notifications_subid = UUID().description
|
var notificationsHandlerTask: Task<Void, Never>?
|
||||||
let dms_subid = UUID().description
|
var dmsHandlerTask: Task<Void, Never>?
|
||||||
let init_subid = UUID().description
|
var nwcHandlerTask: Task<Void, Never>?
|
||||||
let profiles_subid = UUID().description
|
|
||||||
|
|
||||||
var loading: Bool = false
|
var loading: Bool = false
|
||||||
|
|
||||||
@@ -94,23 +93,10 @@ class HomeModel: ContactsDelegate {
|
|||||||
preload_events(state: self.damus_state, events: [ev])
|
preload_events(state: self.damus_state, events: [ev])
|
||||||
}
|
}
|
||||||
|
|
||||||
var pool: RelayPool {
|
|
||||||
self.damus_state.nostrNetwork.pool
|
|
||||||
}
|
|
||||||
|
|
||||||
var dms: DirectMessagesModel {
|
var dms: DirectMessagesModel {
|
||||||
return damus_state.dms
|
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() {
|
func setup_debouncer() {
|
||||||
// turn off debouncer after initial load
|
// turn off debouncer after initial load
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||||
@@ -140,6 +126,28 @@ class HomeModel: ContactsDelegate {
|
|||||||
damus_state.drafts.load(from: damus_state)
|
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
|
// MARK: - ContactsDelegate functions
|
||||||
|
|
||||||
func latest_contact_event_changed(new_event: NostrEvent) {
|
func latest_contact_event_changed(new_event: NostrEvent) {
|
||||||
@@ -158,9 +166,6 @@ class HomeModel: ContactsDelegate {
|
|||||||
print("hit resub debouncer")
|
print("hit resub debouncer")
|
||||||
|
|
||||||
resub_debouncer.debounce {
|
resub_debouncer.debounce {
|
||||||
print("resub")
|
|
||||||
self.unsubscribe_to_home_filters()
|
|
||||||
|
|
||||||
switch resubbing {
|
switch resubbing {
|
||||||
case .following:
|
case .following:
|
||||||
break
|
break
|
||||||
@@ -175,25 +180,16 @@ class HomeModel: ContactsDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func process_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) {
|
func process_event(ev: NostrEvent, context: SubscriptionContext) {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let kind = ev.known_kind else {
|
guard let kind = ev.known_kind else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case .chat, .longform, .text, .highlight:
|
case .chat, .longform, .text, .highlight:
|
||||||
handle_text_event(sub_id: sub_id, ev)
|
handle_text_event(ev, context: context)
|
||||||
case .contacts:
|
case .contacts:
|
||||||
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
handle_contact_event(ev: ev)
|
||||||
case .metadata:
|
case .metadata:
|
||||||
// profile metadata processing is handled by nostrdb
|
// profile metadata processing is handled by nostrdb
|
||||||
break
|
break
|
||||||
@@ -202,7 +198,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
case .mute_list:
|
case .mute_list:
|
||||||
handle_mute_list_event(ev)
|
handle_mute_list_event(ev)
|
||||||
case .boost:
|
case .boost:
|
||||||
handle_boost_event(sub_id: sub_id, ev)
|
handle_boost_event(ev, context: context)
|
||||||
case .like:
|
case .like:
|
||||||
handle_like_event(ev)
|
handle_like_event(ev)
|
||||||
case .dm:
|
case .dm:
|
||||||
@@ -216,7 +212,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
case .nwc_request:
|
case .nwc_request:
|
||||||
break
|
break
|
||||||
case .nwc_response:
|
case .nwc_response:
|
||||||
handle_nwc_response(ev, relay: relay_id)
|
handle_nwc_response(ev)
|
||||||
case .http_auth:
|
case .http_auth:
|
||||||
break
|
break
|
||||||
case .status:
|
case .status:
|
||||||
@@ -261,7 +257,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
pdata.status.update_status(st)
|
pdata.status.update_status(st)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_nwc_response(_ ev: NostrEvent, relay: RelayURL) {
|
func handle_nwc_response(_ ev: NostrEvent) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
// TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time
|
||||||
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
guard let nwc_str = damus_state.settings.nostr_wallet_connect,
|
||||||
@@ -269,7 +265,6 @@ class HomeModel: ContactsDelegate {
|
|||||||
return
|
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 {
|
guard ev.referenced_pubkeys.first == nwc.keypair.pubkey else {
|
||||||
return // This message is not for us. Ignore it.
|
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,
|
// since command results are not returned for ephemeral events,
|
||||||
// remove the request from the postbox which is likely failing over and over
|
// 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) {
|
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 {
|
} 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
|
damus_state.wallet.handle_nwc_response(response: resp) // This can handle success or error cases
|
||||||
@@ -303,7 +298,6 @@ class HomeModel: ContactsDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print("nwc success: \(resp.response.result.debugDescription) [\(relay)]")
|
|
||||||
WalletConnect.handle_zap_success(state: self.damus_state, resp: resp)
|
WalletConnect.handle_zap_success(state: self.damus_state, resp: resp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,19 +376,11 @@ class HomeModel: ContactsDelegate {
|
|||||||
self.deleted_events.insert(ev.id)
|
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)
|
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()
|
var boost_ev_id = ev.last_refid()
|
||||||
|
|
||||||
if let inner_ev = ev.get_inner_event(cache: damus_state.events) {
|
if let inner_ev = ev.get_inner_event(cache: damus_state.events) {
|
||||||
@@ -409,7 +395,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
|
|
||||||
if inner_ev.is_textlike {
|
if inner_ev.is_textlike {
|
||||||
DispatchQueue.main.async {
|
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
|
/// Send the initial filters, just our contact list and relay list mostly
|
||||||
func handle_event(relay_id: RelayURL, conn_event: NostrConnectionEvent) {
|
func send_initial_filters() {
|
||||||
switch conn_event {
|
Task {
|
||||||
case .ws_connection_event(let ev):
|
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
||||||
switch ev {
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [filter]) {
|
||||||
case .connected:
|
switch item {
|
||||||
if !done_init {
|
case .event(let borrow):
|
||||||
self.loading = true
|
var event: NostrEvent? = nil
|
||||||
send_initial_filters(relay_id: relay_id)
|
try? borrow { ev in
|
||||||
} else {
|
event = ev.toOwned()
|
||||||
//remove_bootstrap_nodes(damus_state)
|
}
|
||||||
send_home_filters(relay_id: relay_id)
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Task {
|
||||||
|
let relayListFilter = NostrFilter(kinds: [.relay_list], limit: 1, authors: [damus_state.pubkey])
|
||||||
/// Send the initial filters, just our contact list mostly
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: [relayListFilter]) {
|
||||||
func send_initial_filters(relay_id: RelayURL) {
|
switch item {
|
||||||
let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey])
|
case .event(let borrow):
|
||||||
let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid)
|
var event: NostrEvent? = nil
|
||||||
pool.send(.subscribe(subscription), to: [relay_id])
|
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
|
/// 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
|
// TODO: since times should be based on events from a specific relay
|
||||||
// perhaps we could mark this in the relay pool somehow
|
// 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)
|
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 contacts_filters = contacts_filter_chunks + [our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
|
||||||
var dms_filters = [dms_filter, our_dms_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])
|
//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] }
|
|
||||||
|
self.contactsHandlerTask?.cancel()
|
||||||
pool.send(.subscribe(.init(filters: contacts_filters, sub_id: contacts_subid)), to: relay_ids)
|
self.contactsHandlerTask = Task {
|
||||||
pool.send(.subscribe(.init(filters: notifications_filters, sub_id: notifications_subid)), to: relay_ids)
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: contacts_filters) {
|
||||||
pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: relay_ids)
|
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] {
|
func get_last_of_kind(relay_id: RelayURL?) -> [UInt32: NostrEvent] {
|
||||||
return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
return relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func unsubscribe_to_home_filters() {
|
|
||||||
pool.send(.unsubscribe(home_subid))
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_friends() -> [Pubkey] {
|
func get_friends() -> [Pubkey] {
|
||||||
var friends = damus_state.contacts.get_friend_list()
|
var friends = damus_state.contacts.get_friend_list()
|
||||||
friends.insert(damus_state.pubkey)
|
friends.insert(damus_state.pubkey)
|
||||||
return Array(friends)
|
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?
|
// TODO: separate likes?
|
||||||
var home_filter_kinds: [NostrKind] = [
|
var home_filter_kinds: [NostrKind] = [
|
||||||
.text, .longform, .boost, .highlight
|
.text, .longform, .boost, .highlight
|
||||||
@@ -649,11 +652,34 @@ class HomeModel: ContactsDelegate {
|
|||||||
home_filters.append(hashtag_filter)
|
home_filters.append(hashtag_filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
let relay_ids = relay_id.map { [$0] }
|
self.homeHandlerTask?.cancel()
|
||||||
home_filters = update_filters_with_since(last_of_kind: get_last_of_kind(relay_id: relay_id), filters: home_filters)
|
self.homeHandlerTask = Task {
|
||||||
let sub = NostrSubscribe(filters: home_filters, sub_id: home_subid)
|
for await item in damus_state.nostrNetwork.reader.subscribe(filters: home_filters) {
|
||||||
|
switch item {
|
||||||
pool.send(.subscribe(sub), to: relay_ids)
|
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) {
|
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 {
|
guard should_show_event(state: damus_state, ev: ev) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -770,10 +796,13 @@ class HomeModel: ContactsDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sub_id == home_subid {
|
switch context {
|
||||||
|
case .home:
|
||||||
insert_home_event(ev)
|
insert_home_event(ev)
|
||||||
} else if sub_id == notifications_subid {
|
case .notifications:
|
||||||
handle_notification(ev: ev)
|
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
|
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)
|
damus_state.nostrNetwork.requestTransactionList(url: nwc, 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.requestBalanceInformation(url: nwc, delay: delay, on_flush: flusher)
|
||||||
return
|
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
|
/// Handles a received Nostr Wallet Connect error
|
||||||
static func handle_error(zapcache: Zaps, evcache: EventCache, resp: WalletConnect.FullWalletResponse) {
|
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
|
// 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
|
sendState = .processing
|
||||||
|
|
||||||
// Process payment
|
// 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(
|
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"),
|
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."),
|
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."),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ZapsModel: ObservableObject {
|
|||||||
let state: DamusState
|
let state: DamusState
|
||||||
let target: ZapTarget
|
let target: ZapTarget
|
||||||
|
|
||||||
let zaps_subid = UUID().description
|
var zapCommsListener: Task<Void, Never>? = nil
|
||||||
let profiles_subid = UUID().description
|
let profiles_subid = UUID().description
|
||||||
|
|
||||||
init(state: DamusState, target: ZapTarget) {
|
init(state: DamusState, target: ZapTarget) {
|
||||||
@@ -31,46 +31,40 @@ class ZapsModel: ObservableObject {
|
|||||||
case .note(let note_target):
|
case .note(let note_target):
|
||||||
filter.referenced_ids = [note_target.note_id]
|
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() {
|
func unsubscribe() {
|
||||||
state.nostrNetwork.pool.unsubscribe(sub_id: zaps_subid)
|
zapCommsListener?.cancel()
|
||||||
|
zapCommsListener = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) {
|
func handle_event(ev: NostrEvent) {
|
||||||
guard case .nostr_event(let resp) = conn_ev else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard resp.subid == zaps_subid else {
|
self.state.add_zap(zap: .zap(zap))
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,9 @@ struct CustomizeZapView: View {
|
|||||||
} else {
|
} else {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
let amount = model.custom_amount_sats
|
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
|
model.zapping = true
|
||||||
}) {
|
}) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ struct NoteZapButton: View {
|
|||||||
|
|
||||||
func tap() {
|
func tap() {
|
||||||
guard let our_zap else {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,13 +173,13 @@ func initial_pending_zap_state(settings: UserSettingsStore) -> PendingZapState {
|
|||||||
return .external(ExtPendingZapState(state: .fetching_invoice))
|
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 {
|
guard let keypair = damus_state.keypair.to_full() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only take the first 10 because reasons
|
// 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 ?? ""
|
let content = comment ?? ""
|
||||||
|
|
||||||
guard let mzapreq = make_zap_request_event(keypair: keypair, content: content, relays: relays, target: target, zap_type: zap_type) else {
|
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
|
flusher = .once({ pe in
|
||||||
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
// send donation zap when the pending zap is flushed, this allows user to cancel and not send a donation
|
||||||
Task { @MainActor in
|
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)
|
// 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 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 {
|
guard let nwc_req, case .nwc(let pzap_state) = pending_zap_state else {
|
||||||
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
print("nwc: failed to send nwc request for zapreq \(reqid.reqid)")
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ enum Route: Hashable {
|
|||||||
case .FollowersYouKnow(let friendedFollowers, let followers):
|
case .FollowersYouKnow(let friendedFollowers, let followers):
|
||||||
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
|
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
|
||||||
case .Script(let load_model):
|
case .Script(let load_model):
|
||||||
LoadScript(pool: damusState.nostrNetwork.pool, model: load_model)
|
LoadScript(pool: RelayPool(ndb: damusState.ndb, keypair: damusState.keypair), model: load_model)
|
||||||
case .NIP05DomainEvents(let events, let nip05_domain_favicon):
|
case .NIP05DomainEvents(let events, let nip05_domain_favicon):
|
||||||
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
||||||
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
||||||
@@ -237,7 +237,6 @@ enum Route: Hashable {
|
|||||||
case .FollowersYouKnow(let friendedFollowers, let followers):
|
case .FollowersYouKnow(let friendedFollowers, let followers):
|
||||||
hasher.combine("followersYouKnow")
|
hasher.combine("followersYouKnow")
|
||||||
hasher.combine(friendedFollowers)
|
hasher.combine(friendedFollowers)
|
||||||
hasher.combine(followers.sub_id)
|
|
||||||
case .Script(let model):
|
case .Script(let model):
|
||||||
hasher.combine("script")
|
hasher.combine("script")
|
||||||
hasher.combine(model.data.count)
|
hasher.combine(model.data.count)
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ struct ShareExtensionView: View {
|
|||||||
break
|
break
|
||||||
case .active:
|
case .active:
|
||||||
print("txn: 📙 HIGHLIGHTER ACTIVE")
|
print("txn: 📙 HIGHLIGHTER ACTIVE")
|
||||||
state.nostrNetwork.pool.ping()
|
state.nostrNetwork.ping()
|
||||||
@unknown default:
|
@unknown default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ struct ShareExtensionView: View {
|
|||||||
break
|
break
|
||||||
case .active:
|
case .active:
|
||||||
print("txn: 📙 SHARE ACTIVE")
|
print("txn: 📙 SHARE ACTIVE")
|
||||||
state.nostrNetwork.pool.ping()
|
state.nostrNetwork.ping()
|
||||||
@unknown default:
|
@unknown default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user