This commit tries to replace all usage of `String` to represent relay URLs and use `RelayURL` which automatically converts strings to a canonical relay URL format that is more reliable and avoids issues related to trailing slashes. Test 1: Main issue fix ----------------------- PASS Device: iPhone 15 Simulator iOS: 17.4 Damus: This commit Steps: 1. Delete all connected relays 2. Add `wss://relay.damus.io/` (with the trailing slash) to the relay list 3. Try to post. Post should succeed. PASS 4. Try removing this newly added relay. Relay should be removed successfully. PASS Test 2: Persistent relay list after upgrade -------------------------------------------- PASS Device: iPhone 15 Simulator iOS: 17.4 Damus: 1.8 (1) `247f313b` + This commit Steps: 1. Downgrade to old version 2. Add some relays to the list, some without a trailing slash, some with 3. Upgrade to this commit 4. All relays added in step 2 should still be there, and ones with a trailing slash should have been corrected to remove the trailing slash Test 3: Miscellaneous regression tests -------------------------------------- Device: iPhone 15 Simulator iOS: 17.4 Damus: This commit Coverage: 1. Posting works 2. Search works 3. Relay connection status works 4. Adding relays work 5. Removing relays work 6. Adding relay with trailing slashes works (it fixes itself to remove the trailing slash) 7. Adding relays with different paths works (e.g. wss://yabu.me/v1 and wss://yabu.me/v2) 8. Adding duplicate relay (but with trailing slash) gets rejected as expected 9. Relay details page works. All items on that view loads correctly 10. Relay logs work 11. Getting follower counts and seeing follow lists on profiles still work 12. Relay list changes persist after app restart 13. Notifications view still work 14. Copying the user's pubkey and profile link works 15. Share note + copy link button still works 16. Connecting NWC wallet works 17. One-tap zaps work 18. Onboarding works 19. Unit tests all passing Closes: https://github.com/damus-io/damus/issues/2072 Changelog-Fixed: Fix bug that would cause connection issues with relays defined with a trailing slash URL, and an inability to delete them. Signed-off-by: Daniel D’Aquino <daniel@daquino.me> Signed-off-by: William Casarin <jb55@jb55.com>
150 lines
4.9 KiB
Swift
150 lines
4.9 KiB
Swift
//
|
||
// Contacts+.swift
|
||
// damus
|
||
//
|
||
// Extra functionality and utilities for `Contacts.swift`
|
||
//
|
||
// Created by Daniel D’Aquino on 2023-11-24.
|
||
//
|
||
|
||
import Foundation
|
||
|
||
func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
||
guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
|
||
return nil
|
||
}
|
||
|
||
box.send(ev)
|
||
|
||
return ev
|
||
}
|
||
|
||
func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
||
guard let cs = our_contacts else {
|
||
return nil
|
||
}
|
||
|
||
guard let ev = unfollow_reference_event(our_contacts: cs, keypair: keypair, unfollow: unfollow) else {
|
||
return nil
|
||
}
|
||
|
||
postbox.send(ev)
|
||
|
||
return ev
|
||
}
|
||
|
||
func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
||
let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in
|
||
if let tag = FollowRef.from_tag(tag: tag), tag == unfollow {
|
||
return
|
||
}
|
||
|
||
ts.append(tag.strings())
|
||
}
|
||
|
||
let kind = NostrKind.contacts.rawValue
|
||
|
||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: Array(tags))
|
||
}
|
||
|
||
func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
||
guard let cs = our_contacts else {
|
||
// don't create contacts for now so we don't nuke our contact list due to connectivity issues
|
||
// we should only create contacts during profile creation
|
||
//return create_contacts(relays: relays, our_pubkey: our_pubkey, follow: follow)
|
||
return nil
|
||
}
|
||
|
||
guard let ev = follow_with_existing_contacts(keypair: keypair, our_contacts: cs, follow: follow) else {
|
||
return nil
|
||
}
|
||
|
||
return ev
|
||
}
|
||
|
||
|
||
func decode_json_relays(_ content: String) -> [RelayURL: RelayInfo]? {
|
||
return decode_json(content)
|
||
}
|
||
|
||
func remove_relay(ev: NostrEvent, current_relays: [RelayDescriptor], keypair: FullKeypair, relay: RelayURL) -> NostrEvent?{
|
||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||
|
||
relays.removeValue(forKey: relay)
|
||
|
||
guard let content = encode_json(relays) else {
|
||
return nil
|
||
}
|
||
|
||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
|
||
}
|
||
|
||
func add_relay(ev: NostrEvent, keypair: FullKeypair, current_relays: [RelayDescriptor], relay: RelayURL, info: RelayInfo) -> NostrEvent? {
|
||
var relays = ensure_relay_info(relays: current_relays, content: ev.content)
|
||
|
||
// If kind:3 content is empty, or if the relay doesn't exist in the list,
|
||
// we want to create a kind:3 event with the new relay
|
||
guard ev.content.isEmpty || relays.index(forKey: relay) == nil else {
|
||
return nil
|
||
}
|
||
|
||
relays[relay] = info
|
||
|
||
guard let content = encode_json(relays) else {
|
||
return nil
|
||
}
|
||
|
||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 3, tags: ev.tags.strings())
|
||
}
|
||
|
||
func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [RelayURL: RelayInfo] {
|
||
return decode_json_relays(content) ?? make_contact_relays(relays)
|
||
}
|
||
|
||
func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
|
||
return contacts.references.contains { ref in
|
||
switch (ref, follow) {
|
||
case let (.hashtag(ht), .hashtag(follow_ht)):
|
||
return ht.hashtag == follow_ht
|
||
case let (.pubkey(pk), .pubkey(follow_pk)):
|
||
return pk == follow_pk
|
||
case (.hashtag, .pubkey), (.pubkey, .hashtag),
|
||
(.event, _), (.quote, _), (.param, _), (.naddr, _):
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: FollowRef) -> NostrEvent? {
|
||
// don't update if we're already following
|
||
if is_already_following(contacts: our_contacts, follow: follow) {
|
||
return nil
|
||
}
|
||
|
||
let kind = NostrKind.contacts.rawValue
|
||
|
||
var tags = our_contacts.tags.strings()
|
||
tags.append(follow.tag)
|
||
|
||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
|
||
}
|
||
|
||
func make_contact_relays(_ relays: [RelayDescriptor]) -> [RelayURL: RelayInfo] {
|
||
return relays.reduce(into: [:]) { acc, relay in
|
||
acc[relay.url] = relay.info
|
||
}
|
||
}
|
||
|
||
func make_relay_metadata(relays: [RelayDescriptor], keypair: FullKeypair) -> NostrEvent? {
|
||
let tags = relays.compactMap { r -> [String]? in
|
||
var tag = ["r", r.url.absoluteString]
|
||
if (r.info.read ?? true) != (r.info.write ?? true) {
|
||
tag += r.info.read == true ? ["read"] : ["write"]
|
||
}
|
||
if ((r.info.read ?? true) || (r.info.write ?? true)) && r.variant == .regular {
|
||
return tag;
|
||
}
|
||
return nil
|
||
}
|
||
return NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 10_002, tags: tags)
|
||
}
|