damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 43a5bbd53a91d727db76c0f31e84433bb0d91f8b
parent 43630cbfa650f731c4843dc4acb5dcd9f792d8f0
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Mon, 22 Apr 2024 23:09:27 +0000

contacts: save the users' latest contact event ID

... 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>

Diffstat:
Mdamus/Models/Contacts.swift | 14++++++++++++--
Mdamus/Models/HomeModel.swift | 34++++++++++++++++++++++++++++++++--
Mdamus/Models/UserSettingsStore.swift | 6++++++
3 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift @@ -7,7 +7,6 @@ import Foundation - class Contacts { private var friends: Set<Pubkey> = Set() private var friend_of_friends: Set<Pubkey> = Set() @@ -15,7 +14,13 @@ class Contacts { 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 @@ -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) +} diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift @@ -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>] = [:] @@ -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 { diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift @@ -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 {