Skip to content

Commit

Permalink
contacts: save the users' latest contact event ID
Browse files Browse the repository at this point in the history
... to a persistent setting, and try to load it from NostrDB on app start.

This commit causes the user's contact list event ID to be saved
persistently as a user-specific setting, and to be loaded immediately
after startup from the local NostrDB instance.

This helps improve reliability around contact lists, since we previously
relied on fetching that contact list from other relays.

Eventually we will not need the event ID to be stored at all, as we will
be able to query NostrDB, but for now having the latest event ID
persistently stored will allow us to get around this limitation in the
cleanest possible way (i.e. without having to store the event itself
into another mechanism, and migrating it later to NostrDB)

Other notes:

- It uses a mechanism similar to other user settings, so it is
  pubkey-specific and should handle login/logout cases

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Reviewed-by: William Casarin <jb55@jb55.com>
Link: 20240422230912.65056-2-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
  • Loading branch information
danieldaquino authored and jb55 committed Apr 23, 2024
1 parent 43630cb commit 43a5bbd
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 4 deletions.
14 changes: 12 additions & 2 deletions damus/Models/Contacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@

import Foundation


class Contacts {
private var friends: Set<Pubkey> = Set()
private var friend_of_friends: Set<Pubkey> = Set()
/// Tracks which friends are friends of a given pubkey.
private var pubkey_to_our_friends = [Pubkey : Set<Pubkey>]()

let our_pubkey: Pubkey
var event: NostrEvent?
var delegate: ContactsDelegate? = nil
var event: NostrEvent? {
didSet {
guard let event else { return }
self.delegate?.latest_contact_event_changed(new_event: event)
}
}

init(our_pubkey: Pubkey) {
self.our_pubkey = our_pubkey
Expand Down Expand Up @@ -88,3 +93,8 @@ class Contacts {
return Array((pubkey_to_our_friends[pubkey] ?? Set()))
}
}

/// Delegate protocol for `Contacts`. Use this to listen to significant updates from a `Contacts` instance
protocol ContactsDelegate {
func latest_contact_event_changed(new_event: NostrEvent)
}
34 changes: 32 additions & 2 deletions damus/Models/HomeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,15 @@ enum HomeResubFilter {
}
}

class HomeModel {
class HomeModel: ContactsDelegate {
// Don't trigger a user notification for events older than a certain age
static let event_max_age_for_notification: TimeInterval = EVENT_MAX_AGE_FOR_NOTIFICATION

var damus_state: DamusState
var damus_state: DamusState {
didSet {
self.load_our_stuff_from_damus_state()
}
}

// NDBTODO: let's get rid of this entirely, let nostrdb handle it
var has_event: [String: Set<NoteId>] = [:]
Expand Down Expand Up @@ -108,6 +112,32 @@ class HomeModel {
self.should_debounce_dms = false
}
}

// MARK: - Loading items from DamusState

/// This is called whenever DamusState gets set. This function is used to load or setup anything we need from the new DamusState
func load_our_stuff_from_damus_state() {
self.load_latest_contact_event_from_damus_state()
}

/// This loads the latest contact event we have on file from NostrDB. This should be called as soon as we get the new DamusState
/// Loading the latest contact list event into our `Contacts` instance from storage is important to avoid getting into weird states when the network is unreliable or when relays delete such information
func load_latest_contact_event_from_damus_state() {
guard let latest_contact_event_id_hex = damus_state.settings.latest_contact_event_id_hex else { return }
guard let latest_contact_event_id = NoteId(hex: latest_contact_event_id_hex) else { return }
guard let latest_contact_event: NdbNote = damus_state.ndb.lookup_note( latest_contact_event_id)?.unsafeUnownedValue?.to_owned() else { return }
process_contact_event(state: damus_state, ev: latest_contact_event)
damus_state.contacts.delegate = self
}

// MARK: - ContactsDelegate functions

func latest_contact_event_changed(new_event: NostrEvent) {
// When the latest user contact event has changed, save its ID so we know exactly where to find it next time
damus_state.settings.latest_contact_event_id_hex = new_event.id.hex()
}

// MARK: - Nostr event and subscription handling

func resubscribe(_ resubbing: Resubscribe) {
if self.should_debounce_dms {
Expand Down
6 changes: 6 additions & 0 deletions damus/Models/UserSettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ class UserSettingsStore: ObservableObject {
return internal_winetranslate_api_key != nil
}
}

// MARK: Internal, hidden settings

@Setting(key: "latest_contact_event_id", default_value: nil)
var latest_contact_event_id_hex: String?

}

func pk_setting_key(_ pubkey: Pubkey, key: String) -> String {
Expand Down

0 comments on commit 43a5bbd

Please sign in to comment.