[PATCH damus v1 0/3] DRAFT/RFC: #2057 Contact list issue fix

2 views
Skip to first unread message

Daniel D’Aquino

unread,
Apr 19, 2024, 10:43:29 PM4/19/24
to pat...@damus.io, Daniel D’Aquino
Hi Will,

I did not have time to fully test this yet, but I wanted to give you a
chance to review the approach and design if you'd like to take a look.

I tried to address the issue in a "clean" way by using NostrDB as the
storage mechanism and only store an ID to UserDefaults (so that I can
query Ndb). This should also make the
migration to the local relay model as smooth as possible, as no data
migration logic will be needed.

For recovering from the issue, I added a manual first aid section to
avoid accidentally wiping people's contact lists.


Please let me know if you have any concerns about this approach, or any
other suggestions while I finish testing this.


Thank you!
Daniel


Daniel D’Aquino (3):
Save the users' latest contact event ID to a persistent setting, and
try to load it from NostrDB on app start
Save first contact list to storage during onboarding of a new account
Add First Aid view to settings, and create the contact list reset
First Aid action

damus.xcodeproj/project.pbxproj | 4 +
damus/ContentView.swift | 5 +-
damus/Models/Contacts.swift | 14 +++-
damus/Models/HomeModel.swift | 34 +++++++-
damus/Models/UserSettingsStore.swift | 14 ++++
damus/Nostr/RelayPool.swift | 22 +++--
damus/Util/Router.swift | 5 ++
damus/Views/ConfigView.swift | 4 +
damus/Views/SaveKeysView.swift | 28 ++++++-
.../Views/Settings/FirstAidSettingsView.swift | 84 +++++++++++++++++++
10 files changed, 194 insertions(+), 20 deletions(-)
create mode 100644 damus/Views/Settings/FirstAidSettingsView.swift


base-commit: ada99418f6fcdb1354bc5c1c3f3cc3b4db994ce6
--
2.44.0


Daniel D’Aquino

unread,
Apr 19, 2024, 10:43:36 PM4/19/24
to pat...@damus.io, Daniel D’Aquino
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 <dan...@daquino.me>
---
damus/Models/Contacts.swift | 14 ++++++++++--
damus/Models/HomeModel.swift | 34 ++++++++++++++++++++++++++--
damus/Models/UserSettingsStore.swift | 6 +++++
3 files changed, 50 insertions(+), 4 deletions(-)

diff --git a/damus/Models/Contacts.swift b/damus/Models/Contacts.swift
index e411ef0d..6abda61b 100644
--- 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
index a86b1eeb..3cfb38b2 100644
--- 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 }
+ damus_state.contacts.event = 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
index 503f60c2..351bc108 100644
--- 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 {
--
2.44.0


Daniel D’Aquino

unread,
Apr 19, 2024, 10:43:41 PM4/19/24
to pat...@damus.io, Daniel D’Aquino
This commit adds a mechanism to add the contact list to storage as soon
as it is generated, and thus it reduces the risk of poor network conditions
causing issues.

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
damus/ContentView.swift | 5 +----
damus/Models/UserSettingsStore.swift | 8 ++++++++
damus/Nostr/RelayPool.swift | 22 +++++++++++++---------
damus/Views/SaveKeysView.swift | 28 +++++++++++++++++++++++++---
4 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/damus/ContentView.swift b/damus/ContentView.swift
index 0a130075..e8efd7d2 100644
--- a/damus/ContentView.swift
+++ b/damus/ContentView.swift
@@ -679,10 +679,7 @@ struct ContentView: View {
let relay_filters = RelayFilters(our_pubkey: pubkey)
let bootstrap_relays = load_bootstrap_relays(pubkey: pubkey)

- // dumb stuff needed for property wrappers
- UserSettingsStore.pubkey = pubkey
- let settings = UserSettingsStore()
- UserSettingsStore.shared = settings
+ let settings = UserSettingsStore.globally_load_for(pubkey: pubkey)

let new_relay_filters = load_relay_filters(pubkey) == nil
for relay in bootstrap_relays {
diff --git a/damus/Models/UserSettingsStore.swift b/damus/Models/UserSettingsStore.swift
index 351bc108..8abaa257 100644
--- a/damus/Models/UserSettingsStore.swift
+++ b/damus/Models/UserSettingsStore.swift
@@ -96,6 +96,14 @@ class UserSettingsStore: ObservableObject {
static var shared: UserSettingsStore? = nil
static var bool_options = Set<String>()

+ static func globally_load_for(pubkey: Pubkey) -> UserSettingsStore {
+ // dumb stuff needed for property wrappers
+ UserSettingsStore.pubkey = pubkey
+ let settings = UserSettingsStore()
+ UserSettingsStore.shared = settings
+ return settings
+ }
+
@StringSetting(key: "default_wallet", default_value: .system_default_wallet)
var default_wallet: Wallet

diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift
index 6d9a1210..903b3f76 100644
--- a/damus/Nostr/RelayPool.swift
+++ b/damus/Nostr/RelayPool.swift
@@ -226,19 +226,23 @@ class RelayPool {
print("queueing request for \(relay)")
request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral))
}
+
+ func send_raw_to_local_ndb(_ req: NostrRequestType) {
+ // send to local relay (nostrdb)
+ switch req {
+ case .typical(let r):
+ if case .event = r, let rstr = make_nostr_req(r) {
+ let _ = ndb.process_client_event(rstr)
+ }
+ case .custom(let string):
+ let _ = ndb.process_client_event(string)
+ }
+ }

func send_raw(_ req: NostrRequestType, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) {
let relays = to.map{ get_relays($0) } ?? self.relays

- // send to local relay (nostrdb)
- switch req {
- case .typical(let r):
- if case .event = r, let rstr = make_nostr_req(r) {
- let _ = ndb.process_client_event(rstr)
- }
- case .custom(let string):
- let _ = ndb.process_client_event(string)
- }
+ self.send_raw_to_local_ndb(req)

for relay in relays {
if req.is_read && !(relay.descriptor.info.read ?? true) {
diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift
index 4d7b6399..93796e15 100644
--- a/damus/Views/SaveKeysView.swift
+++ b/damus/Views/SaveKeysView.swift
@@ -21,6 +21,13 @@ struct SaveKeysView: View {
@FocusState var pubkey_focused: Bool
@FocusState var privkey_focused: Bool

+ let first_contact_event: NdbNote?
+
+ init(account: CreateAccountModel) {
+ self.account = account
+ self.first_contact_event = make_first_contact_event(keypair: account.keypair)
+ }
+
var body: some View {
ZStack(alignment: .top) {
VStack(alignment: .center) {
@@ -102,6 +109,13 @@ struct SaveKeysView: View {
}

func complete_account_creation(_ account: CreateAccountModel) {
+ guard let first_contact_event else {
+ error = NSLocalizedString("Could not create your initial contact list event. This is a software bug, please contact Damus support via sup...@damus.io or through our Nostr account for help.", comment: "Error message to the user indicating that the initial contact list failed to be created.")
+ return
+ }
+ // Save contact list to storage right away so that we don't need to depend on the network to complete this important step
+ self.save_to_storage(first_contact_event: first_contact_event, for: account)
+
let bootstrap_relays = load_bootstrap_relays(pubkey: account.pubkey)
for relay in bootstrap_relays {
add_rw_relay(self.pool, relay)
@@ -115,6 +129,15 @@ struct SaveKeysView: View {

self.pool.connect()
}
+
+ func save_to_storage(first_contact_event: NdbNote, for account: CreateAccountModel) {
+ // Send to NostrDB so that we have a local copy in storage
+ self.pool.send_raw_to_local_ndb(.typical(.event(first_contact_event)))
+
+ // Save the ID to user settings so that we can easily find it later.
+ let settings = UserSettingsStore.globally_load_for(pubkey: account.pubkey)
+ settings.latest_contact_event_id_hex = first_contact_event.id.hex()
+ }

func handle_event(relay: RelayURL, ev: NostrConnectionEvent) {
switch ev {
@@ -122,15 +145,14 @@ struct SaveKeysView: View {
switch wsev {
case .connected:
let metadata = create_account_to_metadata(account)
- let contacts_ev = make_first_contact_event(keypair: account.keypair)

if let keypair = account.keypair.to_full(),
let metadata_ev = make_metadata_event(keypair: keypair, metadata: metadata) {
self.pool.send(.event(metadata_ev))
}

- if let contacts_ev {
- self.pool.send(.event(contacts_ev))
+ if let first_contact_event {
+ self.pool.send(.event(first_contact_event))
}

do {
--
2.44.0


Daniel D’Aquino

unread,
Apr 19, 2024, 10:43:51 PM4/19/24
to pat...@damus.io, Daniel D’Aquino
Automatically detecting whether or not to create a blank contact list
when we could not find any is very tricky. It could mean that no contact
list exists, but it could also mean that a temporary network or relay
outage occurred.

Since resetting the contact list when one already exists is a
destructive action, we should make no assumptions. Instead, we should
provide users the tool to fix it based on their own judgement.

For that reason, the first aid view was created. It detects if no
contact list was found, and in those cases, it gives them an option to
reset (with appropriate warning messages).

Signed-off-by: Daniel D’Aquino <dan...@daquino.me>
---
damus.xcodeproj/project.pbxproj | 4 +
damus/Util/Router.swift | 5 ++
damus/Views/ConfigView.swift | 4 +
.../Views/Settings/FirstAidSettingsView.swift | 84 +++++++++++++++++++
4 files changed, 97 insertions(+)
create mode 100644 damus/Views/Settings/FirstAidSettingsView.swift

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
index e5fd8ca3..e027c3be 100644
--- a/damus.xcodeproj/project.pbxproj
+++ b/damus.xcodeproj/project.pbxproj
@@ -639,6 +639,7 @@
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
D7EDED342B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
+ D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */; };
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
E02429952B7E97740088B16C /* CameraController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02429942B7E97740088B16C /* CameraController.swift */; };
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
@@ -1433,6 +1434,7 @@
D7EDED202B117DCA0018B19C /* SequenceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceUtils.swift; sourceTree = "<group>"; };
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
+ D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstAidSettingsView.swift; sourceTree = "<group>"; };
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; };
E02429942B7E97740088B16C /* CameraController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraController.swift; sourceTree = "<group>"; };
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
@@ -1724,6 +1726,7 @@
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
BA4AB0AD2A63B9270070A32A /* AddEmojiView.swift */,
BA4AB0AF2A63B94D0070A32A /* EmojiListItemView.swift */,
+ D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */,
);
path = Settings;
sourceTree = "<group>";
@@ -3106,6 +3109,7 @@
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
+ D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */,
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */,
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift
index 0a341d9b..b737446b 100644
--- a/damus/Util/Router.swift
+++ b/damus/Util/Router.swift
@@ -30,6 +30,7 @@ enum Route: Hashable {
case ReactionsSettings(settings: UserSettingsStore)
case SearchSettings(settings: UserSettingsStore)
case DeveloperSettings(settings: UserSettingsStore)
+ case FirstAidSettings(settings: UserSettingsStore)
case Thread(thread: ThreadModel)
case Reposts(reposts: EventsModel)
case QuoteReposts(quotes: EventsModel)
@@ -89,6 +90,8 @@ enum Route: Hashable {
SearchSettingsView(settings: settings)
case .DeveloperSettings(let settings):
DeveloperSettingsView(settings: settings)
+ case .FirstAidSettings(settings: let settings):
+ FirstAidSettingsView(damus_state: damusState, settings: settings)
case .Thread(let thread):
ThreadView(state: damusState, thread: thread)
case .Reposts(let reposts):
@@ -175,6 +178,8 @@ enum Route: Hashable {
hasher.combine("searchSettings")
case .DeveloperSettings:
hasher.combine("developerSettings")
+ case .FirstAidSettings:
+ hasher.combine("firstAidSettings")
case .Thread(let threadModel):
hasher.combine("thread")
hasher.combine(threadModel.event.id)
diff --git a/damus/Views/ConfigView.swift b/damus/Views/ConfigView.swift
index 6ffaf066..c6e331c3 100644
--- a/damus/Views/ConfigView.swift
+++ b/damus/Views/ConfigView.swift
@@ -67,6 +67,10 @@ struct ConfigView: View {
NavigationLink(value: Route.DeveloperSettings(settings: settings)) {
IconLabel(NSLocalizedString("Developer", comment: "Section header for developer settings"), img_name: "magic-stick2.fill", color: DamusColors.adaptableBlack)
}
+
+ NavigationLink(value: Route.FirstAidSettings(settings: settings)) {
+ IconLabel(NSLocalizedString("First Aid", comment: "Section header for first aid tools and settings"), img_name: "help2", color: .red)
+ }
}

Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
diff --git a/damus/Views/Settings/FirstAidSettingsView.swift b/damus/Views/Settings/FirstAidSettingsView.swift
new file mode 100644
index 00000000..05e5ea23
--- /dev/null
+++ b/damus/Views/Settings/FirstAidSettingsView.swift
@@ -0,0 +1,84 @@
+//
+// FirstAidSettingsView.swift
+// damus
+//
+// Created by Daniel D’Aquino on 2024-04-19.
+//
+
+import SwiftUI
+
+struct FirstAidSettingsView: View {
+ let damus_state: DamusState
+ @ObservedObject var settings: UserSettingsStore
+ @State var reset_contact_list_state: ContactListResetState = .not_started
+
+ enum ContactListResetState: Equatable {
+ case not_started
+ case confirming_with_user
+ case error(String)
+ case in_progress
+ case completed
+ }
+
+
+ var body: some View {
+ Form {
+ if damus_state.contacts.event == nil || true {
+ Section(
+ header: Text(NSLocalizedString("Contact list (Follows + Relay list)", comment: "Section title for Contact list first aid tools")),
+ footer: Text(NSLocalizedString("No contact list was found. You might experience issues using the app. If you suspect you have permanently lost your contact list (or if you never had one), you can fix this by resetting it", comment: "Section footer for Contact list first aid tools"))
+ ) {
+ Button(action: {
+ reset_contact_list_state = .confirming_with_user
+ }, label: {
+ HStack(spacing: 6) {
+ switch reset_contact_list_state {
+ case .not_started, .error:
+ Label(NSLocalizedString("Reset contact list", comment: "Button to reset contact list."), image: "broom")
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .foregroundColor(.red)
+ case .confirming_with_user, .in_progress:
+ ProgressView()
+ Text(NSLocalizedString("In progress…", comment: "Loading message indicating that a contact list reset operation is in progress."))
+ case .completed:
+ Image(systemName: "checkmark.circle.fill")
+ .foregroundColor(.green)
+ Text(NSLocalizedString("Contact list has been reset", comment: "Message indicating that the contact list was successfully reset."))
+ }
+ }
+ })
+ .disabled(reset_contact_list_state == .in_progress || reset_contact_list_state == .completed)
+
+ if case let .error(error_message) = reset_contact_list_state {
+ Text(error_message)
+ .foregroundStyle(.red)
+ }
+ }
+ .alert(NSLocalizedString("WARNING:\n\nThis will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.", comment: "Alert for resetting the user's contact list."),
+ isPresented: Binding(get: { reset_contact_list_state == .confirming_with_user }, set: { _ in return })
+ ) {
+ Button(NSLocalizedString("Cancel", comment: "Cancel resetting the contact list."), role: .cancel) {
+ reset_contact_list_state = .not_started
+ }
+ Button(NSLocalizedString("Continue", comment: "Continue with resetting the contact list.")) {
+ guard let new_contact_list_event = make_first_contact_event(keypair: damus_state.keypair) else {
+ reset_contact_list_state = .error(NSLocalizedString("An unexpected error happened while trying to create the new contact list. Please contact support.", comment: "Error message for a failed contact list reset operation"))
+ return
+ }
+ damus_state.pool.send(.event(new_contact_list_event))
+ reset_contact_list_state = .completed
+ }
+ }
+ }
+
+ if damus_state.contacts.event != nil {
+ Text(NSLocalizedString("We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support", comment: "Message indicating that no First Aid actions are available."))
+ }
+ }
+ .navigationTitle(NSLocalizedString("First Aid", comment: "Navigation title for first aid settings and tools"))
+ }
+}
+
+#Preview {
+ FirstAidSettingsView(damus_state: test_damus_state, settings: test_damus_state.settings)
+}
--
2.44.0


William Casarin

unread,
Apr 20, 2024, 12:02:16 PM4/20/24
to Daniel D’Aquino, pat...@damus.io
On Sat, Apr 20, 2024 at 02:43:22AM GMT, Daniel D’Aquino wrote:
>Hi Will,
>
>I did not have time to fully test this yet, but I wanted to give you a
>chance to review the approach and design if you'd like to take a look.
>
>I tried to address the issue in a "clean" way by using NostrDB as the
>storage mechanism and only store an ID to UserDefaults (so that I can
>query Ndb). This should also make the
>migration to the local relay model as smooth as possible, as no data
>migration logic will be needed.

for some reason I didn't think of this, but of course you can look up by
id! I was think you would do a nostr filter to look this up, but this
works just as well for now until we have full query support. awesome!

>For recovering from the issue, I added a manual first aid section to
>avoid accidentally wiping people's contact lists.
>
>
>Please let me know if you have any concerns about this approach, or any
>other suggestions while I finish testing this.

First aid should be fine, as long as it is very clear it will wipe your
contact list. Will test!
Reply all
Reply to author
Forward
0 new messages