diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj index 4bfec267..9aa17a87 100644 --- a/damus.xcodeproj/project.pbxproj +++ b/damus.xcodeproj/project.pbxproj @@ -458,6 +458,7 @@ D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; }; D723C38E2AB8D83400065664 /* ContentFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723C38D2AB8D83400065664 /* ContentFilters.swift */; }; D724D8272B64B40B00ABE789 /* DamusPurpleAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */; }; + D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */; }; D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; }; D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; }; D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; }; @@ -1375,6 +1376,7 @@ D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = ""; }; D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = ""; }; D724D8262B64B40B00ABE789 /* DamusPurpleAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleAccountView.swift; sourceTree = ""; }; + D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURLTests.swift; sourceTree = ""; }; D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = ""; }; D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = ""; }; D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = ""; }; @@ -2544,6 +2546,7 @@ E0E024102B7C19C20075735D /* TranslationTests.swift */, E06336A92B75832100A88E6B /* ImageMetadataTest.swift */, D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */, + D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */, ); path = damusTests; sourceTree = ""; @@ -3503,6 +3506,7 @@ 3A90B1832A4EA3C600000D94 /* UserSearchCacheTests.swift in Sources */, 4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */, 4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */, + D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */, 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */, D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */, 3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift index 6fe9a1ee..0a130075 100644 --- a/damus/ContentView.swift +++ b/damus/ContentView.swift @@ -683,15 +683,13 @@ struct ContentView: View { UserSettingsStore.pubkey = pubkey let settings = UserSettingsStore() UserSettingsStore.shared = settings - + let new_relay_filters = load_relay_filters(pubkey) == nil for relay in bootstrap_relays { - if let url = RelayURL(relay) { - let descriptor = RelayDescriptor(url: url, info: .rw) - add_new_relay(model_cache: model_cache, relay_filters: relay_filters, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: settings.developer_mode) - } + let descriptor = RelayDescriptor(url: relay, info: .rw) + add_new_relay(model_cache: model_cache, relay_filters: relay_filters, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: settings.developer_mode) } - + pool.register_handler(sub_id: sub_id, handler: home.handle_event) if let nwc_str = settings.nostr_wallet_connect, @@ -888,13 +886,13 @@ func setup_notifications() { struct FindEvent { let type: FindEventType - let find_from: [String]? - - static func profile(pubkey: Pubkey, find_from: [String]? = nil) -> FindEvent { + let find_from: [RelayURL]? + + static func profile(pubkey: Pubkey, find_from: [RelayURL]? = nil) -> FindEvent { return FindEvent(type: .profile(pubkey), find_from: find_from) } - - static func event(evid: NoteId, find_from: [String]? = nil) -> FindEvent { + + static func event(evid: NoteId, find_from: [RelayURL]? = nil) -> FindEvent { return FindEvent(type: .event(evid), find_from: find_from) } } diff --git a/damus/Models/Contacts+.swift b/damus/Models/Contacts+.swift index 03a8339e..3ed16a22 100644 --- a/damus/Models/Contacts+.swift +++ b/damus/Models/Contacts+.swift @@ -63,10 +63,6 @@ func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: } -func decode_json_relays(_ content: String) -> [String: RelayInfo]? { - return decode_json(content) -} - func decode_json_relays(_ content: String) -> [RelayURL: RelayInfo]? { return decode_json(content) } @@ -140,7 +136,7 @@ func make_contact_relays(_ relays: [RelayDescriptor]) -> [RelayURL: RelayInfo] { func make_relay_metadata(relays: [RelayDescriptor], keypair: FullKeypair) -> NostrEvent? { let tags = relays.compactMap { r -> [String]? in - var tag = ["r", r.url.id] + var tag = ["r", r.url.absoluteString] if (r.info.read ?? true) != (r.info.write ?? true) { tag += r.info.read == true ? ["read"] : ["write"] } diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift index 97a5d843..ed4cf268 100644 --- a/damus/Models/DamusState.swift +++ b/damus/Models/DamusState.swift @@ -28,7 +28,7 @@ class DamusState: HeadlessDamusState { let events: EventCache let bookmarks: BookmarksManager let postbox: PostBox - let bootstrap_relays: [String] + let bootstrap_relays: [RelayURL] let replies: ReplyCounter let wallet: WalletModel let nav: NavigationCoordinator @@ -37,7 +37,7 @@ class DamusState: HeadlessDamusState { let ndb: Ndb var purple: DamusPurple - init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [String], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) { + init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) { self.pool = pool self.keypair = keypair self.likes = likes diff --git a/damus/Models/EventsModel.swift b/damus/Models/EventsModel.swift index 5e263c3f..aac2faa9 100644 --- a/damus/Models/EventsModel.swift +++ b/damus/Models/EventsModel.swift @@ -76,14 +76,14 @@ class EventsModel: ObservableObject { func unsubscribe() { state.pool.unsubscribe(sub_id: sub_id) } - - private func handle_event(relay_id: String, ev: NostrEvent) { + + private func handle_event(relay_id: RelayURL, ev: NostrEvent) { if events.insert(ev) { objectWillChange.send() } } - - func handle_nostr_event(relay_id: String, ev: NostrConnectionEvent) { + + func handle_nostr_event(relay_id: RelayURL, ev: NostrConnectionEvent) { guard case .nostr_event(let nev) = ev, nev.subid == self.sub_id else { return diff --git a/damus/Models/FollowersModel.swift b/damus/Models/FollowersModel.swift index b74a08b8..56e83215 100644 --- a/damus/Models/FollowersModel.swift +++ b/damus/Models/FollowersModel.swift @@ -52,8 +52,8 @@ class FollowersModel: ObservableObject { contacts?.append(ev.pubkey) has_contact.insert(ev.pubkey) } - - func load_profiles(relay_id: String, txn: NdbTxn) { + + func load_profiles(relay_id: RelayURL, txn: NdbTxn) { let authors = find_profiles_to_fetch_from_keys(profiles: damus_state.profiles, pks: contacts ?? [], txn: txn) if authors.isEmpty { return @@ -63,8 +63,8 @@ class FollowersModel: ObservableObject { authors: authors) damus_state.pool.subscribe_to(sub_id: profiles_id, filters: [filter], to: [relay_id], handler: handle_event) } - - func handle_event(relay_id: String, ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { guard case .nostr_event(let nev) = ev else { return } diff --git a/damus/Models/FollowingModel.swift b/damus/Models/FollowingModel.swift index 87c67074..9a6e2e7a 100644 --- a/damus/Models/FollowingModel.swift +++ b/damus/Models/FollowingModel.swift @@ -52,8 +52,8 @@ class FollowingModel { print("unsubscribing from following \(sub_id)") self.damus_state.pool.unsubscribe(sub_id: sub_id) } - - func handle_event(relay_id: String, ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { // don't need to do anything here really } } diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift index ac6909b2..a86b1eeb 100644 --- a/damus/Models/HomeModel.swift +++ b/damus/Models/HomeModel.swift @@ -50,7 +50,7 @@ class HomeModel { // NDBTODO: let's get rid of this entirely, let nostrdb handle it var has_event: [String: Set] = [:] var deleted_events: Set = Set() - var last_event_of_kind: [String: [UInt32: NostrEvent]] = [:] + var last_event_of_kind: [RelayURL: [UInt32: NostrEvent]] = [:] var done_init: Bool = false var incoming_dms: [NostrEvent] = [] let dm_debouncer = Debouncer(interval: 0.5) @@ -135,7 +135,7 @@ class HomeModel { } @MainActor - func process_event(sub_id: String, relay_id: String, ev: NostrEvent) { + func process_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) { if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) { return } @@ -211,7 +211,7 @@ class HomeModel { pdata.status.update_status(st) } - func handle_nwc_response(_ ev: NostrEvent, relay: String) { + func handle_nwc_response(_ ev: NostrEvent, relay: RelayURL) { Task { @MainActor in // TODO: Adapt KeychainStorage to StringCodable and instead of parsing to WalletConnectURL every time guard let nwc_str = damus_state.settings.nostr_wallet_connect, @@ -219,10 +219,10 @@ class HomeModel { let resp = await FullWalletResponse(from: ev, nwc: nwc) else { return } - + // since command results are not returned for ephemeral events, // remove the request from the postbox which is likely failing over and over - if damus_state.postbox.remove_relayer(relay_id: nwc.relay.id, event_id: resp.req_id) { + if damus_state.postbox.remove_relayer(relay_id: nwc.relay, event_id: resp.req_id) { print("nwc: got response, removed \(resp.req_id) from the postbox [\(relay)]") } else { print("nwc: \(resp.req_id) not found in the postbox, nothing to remove [\(relay)]") @@ -308,7 +308,7 @@ class HomeModel { self.deleted_events.insert(ev.id) } - func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) { + func handle_contact_event(sub_id: String, relay_id: RelayURL, ev: NostrEvent) { process_contact_event(state: self.damus_state, ev: ev) if sub_id == init_subid { @@ -382,7 +382,7 @@ class HomeModel { } @MainActor - func handle_event(relay_id: String, conn_event: NostrConnectionEvent) { + func handle_event(relay_id: RelayURL, conn_event: NostrConnectionEvent) { switch conn_event { case .ws_event(let ev): switch ev { @@ -400,7 +400,7 @@ class HomeModel { let r = pool.get_relay(relay_id), r.descriptor.variant == .nwc, let nwc = WalletConnectURL(str: nwc_str), - nwc.relay.id == relay_id + nwc.relay == relay_id { subscribe_to_nwc(url: nwc, pool: pool) } @@ -461,14 +461,14 @@ class HomeModel { /// Send the initial filters, just our contact list mostly - func send_initial_filters(relay_id: String) { + func send_initial_filters(relay_id: RelayURL) { let filter = NostrFilter(kinds: [.contacts], limit: 1, authors: [damus_state.pubkey]) let subscription = NostrSubscribe(filters: [filter], sub_id: init_subid) pool.send(.subscribe(subscription), to: [relay_id]) } /// After initial connection or reconnect, send subscription filters for the home timeline, DMs, and notifications - func send_home_filters(relay_id: String?) { + func send_home_filters(relay_id: RelayURL?) { // TODO: since times should be based on events from a specific relay // perhaps we could mark this in the relay pool somehow @@ -529,7 +529,7 @@ class HomeModel { pool.send(.subscribe(.init(filters: dms_filters, sub_id: dms_subid)), to: relay_ids) } - func get_last_of_kind(relay_id: String?) -> [UInt32: NostrEvent] { + func get_last_of_kind(relay_id: RelayURL?) -> [UInt32: NostrEvent] { return relay_id.flatMap { last_event_of_kind[$0] } ?? [:] } @@ -543,7 +543,7 @@ class HomeModel { return Array(friends) } - func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: String? = nil) { + func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) { // TODO: separate likes? var home_filter_kinds: [NostrKind] = [ .text, .longform, .boost @@ -618,8 +618,8 @@ class HomeModel { migrate_old_muted_threads_to_new_mutelist(keypair: damus_state.keypair, damus_state: damus_state) } - - func get_last_event_of_kind(relay_id: String, kind: UInt32) -> NostrEvent? { + + func get_last_event_of_kind(relay_id: RelayURL, kind: UInt32) -> NostrEvent? { guard let m = last_event_of_kind[relay_id] else { last_event_of_kind[relay_id] = [:] return nil @@ -883,23 +883,23 @@ func process_contact_event(state: DamusState, ev: NostrEvent) { } func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { - let bootstrap_dict: [String: RelayInfo] = [:] + let bootstrap_dict: [RelayURL: RelayInfo] = [:] let old_decoded = m_old_ev.flatMap { decode_json_relays($0.content) } ?? state.bootstrap_relays.reduce(into: bootstrap_dict) { (d, r) in d[r] = .rw } - - guard let decoded: [String: RelayInfo] = decode_json_relays(ev.content) else { + + guard let decoded: [RelayURL: RelayInfo] = decode_json_relays(ev.content) else { return } - + var changed = false - - var new = Set() + + var new = Set() for key in decoded.keys { new.insert(key) } - - var old = Set() + + var old = Set() for key in old_decoded.keys { old.insert(key) } @@ -910,10 +910,8 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { for d in diff { changed = true if new.contains(d) { - if let url = RelayURL(d) { - let descriptor = RelayDescriptor(url: url, info: decoded[d] ?? .rw) - add_new_relay(model_cache: state.relay_model_cache, relay_filters: state.relay_filters, pool: state.pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: state.settings.developer_mode) - } + let descriptor = RelayDescriptor(url: d, info: decoded[d] ?? .rw) + add_new_relay(model_cache: state.relay_model_cache, relay_filters: state.relay_filters, pool: state.pool, descriptor: descriptor, new_relay_filters: new_relay_filters, logging_enabled: state.settings.developer_mode) } else { state.pool.remove_relay(d) } @@ -929,8 +927,8 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { func add_new_relay(model_cache: RelayModelCache, relay_filters: RelayFilters, pool: RelayPool, descriptor: RelayDescriptor, new_relay_filters: Bool, logging_enabled: Bool) { try? pool.add_relay(descriptor) let url = descriptor.url - - let relay_id = url.id + + let relay_id = url guard model_cache.model(withURL: url) == nil else { return } @@ -956,10 +954,10 @@ func add_new_relay(model_cache: RelayModelCache, relay_filters: RelayFilters, po } } -func fetch_relay_metadata(relay_id: String) async throws -> RelayMetadata? { - var urlString = relay_id.replacingOccurrences(of: "wss://", with: "https://") +func fetch_relay_metadata(relay_id: RelayURL) async throws -> RelayMetadata? { + var urlString = relay_id.absoluteString.replacingOccurrences(of: "wss://", with: "https://") urlString = urlString.replacingOccurrences(of: "ws://", with: "http://") - + guard let url = URL(string: urlString) else { return nil } diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift index c86ae2dd..4781168d 100644 --- a/damus/Models/ProfileModel.swift +++ b/damus/Models/ProfileModel.swift @@ -10,9 +10,9 @@ import Foundation class ProfileModel: ObservableObject, Equatable { @Published var contacts: NostrEvent? = nil @Published var following: Int = 0 - @Published var relays: [String: RelayInfo]? = nil + @Published var relays: [RelayURL: RelayInfo]? = nil @Published var progress: Int = 0 - + private let MAX_SHARE_RELAYS = 4 var events: EventHolder @@ -108,8 +108,8 @@ class ProfileModel: ObservableObject, Equatable { } seen_event.insert(ev.id) } - - private func handle_event(relay_id: String, ev: NostrConnectionEvent) { + + private func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { switch ev { case .ws_event: return @@ -142,8 +142,8 @@ class ProfileModel: ObservableObject, Equatable { } } } - - private func findRelaysHandler(relay_id: String, ev: NostrConnectionEvent) { + + private func findRelaysHandler(relay_id: RelayURL, ev: NostrConnectionEvent) { if case .nostr_event(let resp) = ev, case .event(_, let event) = resp, case .contacts = event.known_kind { self.relays = decode_json_relays(event.content) } @@ -159,13 +159,9 @@ class ProfileModel: ObservableObject, Equatable { func unsubscribeFindRelays() { damus.pool.unsubscribe(sub_id: findRelay_subid) } - - func getRelayStrings() -> [String] { - return relays?.keys.map {$0} ?? [] - } - + func getCappedRelayStrings() -> [String] { - return relays?.keys.prefix(MAX_SHARE_RELAYS).map { $0 } ?? [] + return relays?.keys.prefix(MAX_SHARE_RELAYS).map { $0.absoluteString } ?? [] } } diff --git a/damus/Models/SearchHomeModel.swift b/damus/Models/SearchHomeModel.swift index 33f4b608..78c72f21 100644 --- a/damus/Models/SearchHomeModel.swift +++ b/damus/Models/SearchHomeModel.swift @@ -45,12 +45,12 @@ class SearchHomeModel: ObservableObject { damus_state.pool.subscribe(sub_id: base_subid, filters: [get_base_filter()], handler: handle_event, to: to_relays) } - func unsubscribe(to: String? = nil) { + func unsubscribe(to: RelayURL? = nil) { loading = false damus_state.pool.unsubscribe(sub_id: base_subid, to: to.map { [$0] }) } - - func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) { guard case .nostr_event(let event) = conn_ev else { return } @@ -129,7 +129,7 @@ enum PubkeysToLoad { case from_keys([Pubkey]) } -func load_profiles(context: String, profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn) { +func load_profiles(context: String, profiles_subid: String, relay_id: RelayURL, load: PubkeysToLoad, damus_state: DamusState, txn: NdbTxn) { let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load, cache: damus_state.events, txn: txn) guard !authors.isEmpty else { diff --git a/damus/Models/SearchModel.swift b/damus/Models/SearchModel.swift index faebe284..0f02dfb5 100644 --- a/damus/Models/SearchModel.swift +++ b/damus/Models/SearchModel.swift @@ -65,8 +65,8 @@ class SearchModel: ObservableObject { objectWillChange.send() } } - - func handle_event(relay_id: String, ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { let (sub_id, done) = handle_subid_event(pool: state.pool, relay_id: relay_id, ev: ev) { sub_id, ev in if ev.is_textlike && ev.should_show_event { self.add_event(ev) @@ -107,7 +107,7 @@ func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool { return true } -func handle_subid_event(pool: RelayPool, relay_id: String, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) { +func handle_subid_event(pool: RelayPool, relay_id: RelayURL, ev: NostrConnectionEvent, handle: (String, NostrEvent) -> ()) -> (String?, Bool) { switch ev { case .ws_event: return (nil, false) diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift index a02a299e..c105d34c 100644 --- a/damus/Models/ThreadModel.swift +++ b/damus/Models/ThreadModel.swift @@ -103,8 +103,8 @@ class ThreadModel: ObservableObject { } @MainActor - func handle_event(relay_id: String, ev: NostrConnectionEvent) { - + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { + let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in guard subids.contains(sid) else { return diff --git a/damus/Models/ZapsModel.swift b/damus/Models/ZapsModel.swift index 49870f2a..ff2b8252 100644 --- a/damus/Models/ZapsModel.swift +++ b/damus/Models/ZapsModel.swift @@ -39,7 +39,7 @@ class ZapsModel: ObservableObject { } @MainActor - func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) { + func handle_event(relay_id: RelayURL, conn_ev: NostrConnectionEvent) { guard case .nostr_event(let resp) = conn_ev else { return } diff --git a/damus/Nostr/NostrAuth.swift b/damus/Nostr/NostrAuth.swift index d08d23d0..c1afff5a 100644 --- a/damus/Nostr/NostrAuth.swift +++ b/damus/Nostr/NostrAuth.swift @@ -8,7 +8,7 @@ import Foundation func make_auth_request(keypair: FullKeypair, challenge_string: String, relay: Relay) -> NostrEvent? { - let tags: [[String]] = [["relay", relay.descriptor.url.id],["challenge", challenge_string]] + let tags: [[String]] = [["relay", relay.descriptor.url.absoluteString],["challenge", challenge_string]] let event = NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 22242, tags: tags) return event } diff --git a/damus/Nostr/NostrEvent+.swift b/damus/Nostr/NostrEvent+.swift index 3e866d5e..4c936fa7 100644 --- a/damus/Nostr/NostrEvent+.swift +++ b/damus/Nostr/NostrEvent+.swift @@ -10,9 +10,9 @@ import Foundation func make_zap_request_event(keypair: FullKeypair, content: String, relays: [RelayDescriptor], target: ZapTarget, zap_type: ZapType) -> MakeZapRequest? { var tags = zap_target_to_tags(target) var relay_tag = ["relays"] - relay_tag.append(contentsOf: relays.map { $0.url.id }) + relay_tag.append(contentsOf: relays.map { $0.url.absoluteString }) tags.append(relay_tag) - + var kp = keypair let now = UInt32(Date().timeIntervalSince1970) @@ -79,8 +79,8 @@ func make_private_zap_request_event(identity: FullKeypair, enc_key: FullKeypair, func make_first_contact_event(keypair: Keypair) -> NostrEvent? { let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey) let rw_relay_info = RelayInfo(read: true, write: true) - var relays: [String: RelayInfo] = [:] - + var relays: [RelayURL: RelayInfo] = [:] + for relay in bootstrap_relays { relays[relay] = rw_relay_info } diff --git a/damus/Nostr/Relay.swift b/damus/Nostr/Relay.swift index 18ddf4c1..f19f777f 100644 --- a/damus/Nostr/Relay.swift +++ b/damus/Nostr/Relay.swift @@ -118,9 +118,9 @@ class Relay: Identifiable { var is_broken: Bool { return (flags & RelayFlags.broken.rawValue) == RelayFlags.broken.rawValue } - - var id: String { - return get_relay_id(descriptor.url) + + var id: RelayURL { + return descriptor.url } } @@ -128,15 +128,3 @@ class Relay: Identifiable { enum RelayError: Error { case RelayAlreadyExists } - -func get_relay_id(_ url: RelayURL) -> String { - let trimTrailingSlashes: (String) -> String = { url in - var trimmedUrl = url - while trimmedUrl.hasSuffix("/") { - trimmedUrl.removeLast() - } - return trimmedUrl - } - - return trimTrailingSlashes(url.url.absoluteString) -} diff --git a/damus/Nostr/RelayConnection.swift b/damus/Nostr/RelayConnection.swift index 24a46a12..c3e5f625 100644 --- a/damus/Nostr/RelayConnection.swift +++ b/damus/Nostr/RelayConnection.swift @@ -21,19 +21,19 @@ final class RelayConnection: ObservableObject { private(set) var last_connection_attempt: TimeInterval = 0 private(set) var last_pong: Date? = nil private(set) var backoff: TimeInterval = 1.0 - private lazy var socket = WebSocket(url.url) + private lazy var socket = WebSocket(relay_url.url) private var subscriptionToken: AnyCancellable? - + private var handleEvent: (NostrConnectionEvent) -> () private var processEvent: (WebSocketEvent) -> () - private let url: RelayURL + private let relay_url: RelayURL var log: RelayLog? init(url: RelayURL, handleEvent: @escaping (NostrConnectionEvent) -> (), processEvent: @escaping (WebSocketEvent) -> ()) { - self.url = url + self.relay_url = url self.handleEvent = handleEvent self.processEvent = processEvent } @@ -48,7 +48,7 @@ final class RelayConnection: ObservableObject { self.last_pong = .now self.log?.add("Successful ping") } else { - print("pong failed, reconnecting \(self.url.id)") + print("pong failed, reconnecting \(self.relay_url.id)") self.isConnected = false self.isConnecting = false self.reconnect_with_backoff() @@ -126,7 +126,7 @@ final class RelayConnection: ObservableObject { self.receive(message: message) case .disconnected(let closeCode, let reason): if closeCode != .normalClosure { - print("⚠️ Warning: RelayConnection (\(self.url)) closed with code \(closeCode), reason: \(String(describing: reason))") + print("⚠️ Warning: RelayConnection (\(self.relay_url)) closed with code \(closeCode), reason: \(String(describing: reason))") } DispatchQueue.main.async { self.isConnected = false @@ -134,7 +134,7 @@ final class RelayConnection: ObservableObject { self.reconnect() } case .error(let error): - print("⚠️ Warning: RelayConnection (\(self.url)) error: \(error)") + print("⚠️ Warning: RelayConnection (\(self.relay_url)) error: \(error)") let nserr = error as NSError if nserr.domain == NSPOSIXErrorDomain && nserr.code == 57 { // ignore socket not connected? diff --git a/damus/Nostr/RelayLog.swift b/damus/Nostr/RelayLog.swift index 6471ddc4..4927fc39 100644 --- a/damus/Nostr/RelayLog.swift +++ b/damus/Nostr/RelayLog.swift @@ -13,7 +13,7 @@ import UIKit /// will have information to help developers debug issues. final class RelayLog: ObservableObject { private static let line_limit = 250 - private let relay_url: URL? + private let relay_url: RelayURL? private lazy var formatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .short @@ -29,9 +29,9 @@ final class RelayLog: ObservableObject { /// - Parameter relay_url: the relay url the log represents. Pass nil for the url to create /// a RelayLog that does nothing. This is required to allow RelayLog to be used as a StateObject, /// because they cannot be Optional. - init(_ relay_url: URL? = nil) { + init(_ relay_url: RelayURL? = nil) { self.relay_url = relay_url - + setUp() } diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift index 385a6c54..6d9a1210 100644 --- a/damus/Nostr/RelayPool.swift +++ b/damus/Nostr/RelayPool.swift @@ -10,17 +10,17 @@ import Network struct RelayHandler { let sub_id: String - let callback: (String, NostrConnectionEvent) -> () + let callback: (RelayURL, NostrConnectionEvent) -> () } struct QueuedRequest { let req: NostrRequestType - let relay: String + let relay: RelayURL let skip_ephemeral: Bool } struct SeenEvent: Hashable { - let relay_id: String + let relay_id: RelayURL let evid: NoteId } @@ -29,7 +29,7 @@ class RelayPool { var handlers: [RelayHandler] = [] var request_queue: [QueuedRequest] = [] var seen: Set = Set() - var counts: [String: UInt64] = [:] + var counts: [RelayURL: UInt64] = [:] var ndb: Ndb var keypair: Keypair? var message_received_function: (((String, RelayDescriptor)) -> Void)? @@ -93,8 +93,8 @@ class RelayPool { relay.connection.ping() } } - - func register_handler(sub_id: String, handler: @escaping (String, NostrConnectionEvent) -> ()) { + + func register_handler(sub_id: String, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) { for handler in handlers { // don't add duplicate handlers if handler.sub_id == sub_id { @@ -104,10 +104,10 @@ class RelayPool { self.handlers.append(RelayHandler(sub_id: sub_id, callback: handler)) print("registering \(sub_id) handler, current: \(self.handlers.count)") } - - func remove_relay(_ relay_id: String) { + + func remove_relay(_ relay_id: RelayURL) { var i: Int = 0 - + self.disconnect(to: [relay_id]) for relay in relays { @@ -120,14 +120,13 @@ class RelayPool { i += 1 } } - + func add_relay(_ desc: RelayDescriptor) throws { - let url = desc.url - let relay_id = get_relay_id(url) + let relay_id = desc.url if get_relay(relay_id) != nil { throw RelayError.RelayAlreadyExists } - let conn = RelayConnection(url: url, handleEvent: { event in + let conn = RelayConnection(url: desc.url, handleEvent: { event in self.handle_event(relay_id: relay_id, event: event) }, processEvent: { wsev in guard case .message(let msg) = wsev, @@ -140,11 +139,11 @@ class RelayPool { let relay = Relay(descriptor: desc, connection: conn) self.relays.append(relay) } - - func setLog(_ log: RelayLog, for relay_id: String) { + + func setLog(_ log: RelayLog, for relay_id: RelayURL) { // add the current network state to the log log.add("Network state: \(network_monitor.currentPath.status)") - + get_relay(relay_id)?.connection.log = log } @@ -154,9 +153,9 @@ class RelayPool { let c = relay.connection let is_connecting = c.isConnecting - + if is_connecting && (Date.now.timeIntervalSince1970 - c.last_connection_attempt) > 5 { - print("stale connection detected (\(relay.descriptor.url.url.absoluteString)). retrying...") + print("stale connection detected (\(relay.descriptor.url.absoluteString)). retrying...") relay.connection.reconnect() } else if relay.is_broken || is_connecting || c.isConnected { continue @@ -166,8 +165,8 @@ class RelayPool { } } - - func reconnect(to: [String]? = nil) { + + func reconnect(to: [RelayURL]? = nil) { let relays = to.map{ get_relays($0) } ?? self.relays for relay in relays { // don't try to reconnect to broken relays @@ -175,38 +174,38 @@ class RelayPool { } } - func connect(to: [String]? = nil) { + func connect(to: [RelayURL]? = nil) { let relays = to.map{ get_relays($0) } ?? self.relays for relay in relays { relay.connection.connect() } } - func disconnect(to: [String]? = nil) { + func disconnect(to: [RelayURL]? = nil) { let relays = to.map{ get_relays($0) } ?? self.relays for relay in relays { relay.connection.disconnect() } } - - func unsubscribe(sub_id: String, to: [String]? = nil) { + + func unsubscribe(sub_id: String, to: [RelayURL]? = nil) { if to == nil { self.remove_handler(sub_id: sub_id) } self.send(.unsubscribe(sub_id), to: to) } - - func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (String, NostrConnectionEvent) -> (), to: [String]? = nil) { + + func subscribe(sub_id: String, filters: [NostrFilter], handler: @escaping (RelayURL, NostrConnectionEvent) -> (), to: [RelayURL]? = nil) { register_handler(sub_id: sub_id, handler: handler) send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to) } - - func subscribe_to(sub_id: String, filters: [NostrFilter], to: [String]?, handler: @escaping (String, NostrConnectionEvent) -> ()) { + + func subscribe_to(sub_id: String, filters: [NostrFilter], to: [RelayURL]?, handler: @escaping (RelayURL, NostrConnectionEvent) -> ()) { register_handler(sub_id: sub_id, handler: handler) send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: to) } - - func count_queued(relay: String) -> Int { + + func count_queued(relay: RelayURL) -> Int { var c = 0 for request in request_queue { if request.relay == relay { @@ -216,8 +215,8 @@ class RelayPool { return c } - - func queue_req(r: NostrRequestType, relay: String, skip_ephemeral: Bool) { + + func queue_req(r: NostrRequestType, relay: RelayURL, skip_ephemeral: Bool) { let count = count_queued(relay: relay) guard count <= 10 else { print("can't queue, too many queued events for \(relay)") @@ -228,9 +227,9 @@ class RelayPool { request_queue.append(QueuedRequest(req: r, relay: relay, skip_ephemeral: skip_ephemeral)) } - func send_raw(_ req: NostrRequestType, to: [String]? = nil, skip_ephemeral: Bool = true) { + 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): @@ -264,21 +263,21 @@ class RelayPool { }) } } - - func send(_ req: NostrRequest, to: [String]? = nil, skip_ephemeral: Bool = true) { + + func send(_ req: NostrRequest, to: [RelayURL]? = nil, skip_ephemeral: Bool = true) { send_raw(.typical(req), to: to, skip_ephemeral: skip_ephemeral) } - - func get_relays(_ ids: [String]) -> [Relay] { + + func get_relays(_ ids: [RelayURL]) -> [Relay] { // don't include ephemeral relays in the default list to query relays.filter { ids.contains($0.id) } } - - func get_relay(_ id: String) -> Relay? { + + func get_relay(_ id: RelayURL) -> Relay? { relays.first(where: { $0.id == id }) } - - func run_queue(_ relay_id: String) { + + func run_queue(_ relay_id: RelayURL) { self.request_queue = request_queue.reduce(into: Array()) { (q, req) in guard req.relay == relay_id else { q.append(req) @@ -289,8 +288,8 @@ class RelayPool { self.send_raw(req.req, to: [relay_id], skip_ephemeral: false) } } - - func record_seen(relay_id: String, event: NostrConnectionEvent) { + + func record_seen(relay_id: RelayURL, event: NostrConnectionEvent) { if case .nostr_event(let ev) = event { if case .event(_, let nev) = ev { let k = SeenEvent(relay_id: relay_id, evid: nev.id) @@ -305,10 +304,10 @@ class RelayPool { } } } - - func handle_event(relay_id: String, event: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, event: NostrConnectionEvent) { record_seen(relay_id: relay_id, event: event) - + // run req queue when we reconnect if case .ws_event(let ws) = event { if case .connected = ws { @@ -349,10 +348,7 @@ class RelayPool { } } -func add_rw_relay(_ pool: RelayPool, _ url: String) { - guard let url = RelayURL(url) else { - return - } +func add_rw_relay(_ pool: RelayPool, _ url: RelayURL) { try? pool.add_relay(RelayDescriptor(url: url, info: .rw)) } diff --git a/damus/Nostr/RelayURL.swift b/damus/Nostr/RelayURL.swift index 216dad48..53f6fbe1 100644 --- a/damus/Nostr/RelayURL.swift +++ b/damus/Nostr/RelayURL.swift @@ -7,18 +7,31 @@ import Foundation -public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable { +public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Identifiable, Comparable, CustomStringConvertible { private(set) var url: URL - - var id: String { + + public var id: URL { + return url + } + + public var description: String { + return self.absoluteString + } + + public var absoluteString: String { return url.absoluteString } - + init?(_ str: String) { - guard let url = URL(string: str) else { + var trimmed_url_str = str + while trimmed_url_str.hasSuffix("/") { + trimmed_url_str.removeLast() + } + + guard let url = URL(string: trimmed_url_str) else { return nil } - + guard let scheme = url.scheme else { return nil } @@ -67,7 +80,12 @@ public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable { public func hash(into hasher: inout Hasher) { hasher.combine(self.url) } - + + // MARK: - Comparable + public static func < (lhs: RelayURL, rhs: RelayURL) -> Bool { + return lhs.url.absoluteString < rhs.url.absoluteString + } + } private struct StringKey: CodingKey { diff --git a/damus/Util/PostBox.swift b/damus/Util/PostBox.swift index e45a05af..6ae7304e 100644 --- a/damus/Util/PostBox.swift +++ b/damus/Util/PostBox.swift @@ -9,12 +9,12 @@ import Foundation class Relayer { - let relay: String + let relay: RelayURL var attempts: Int var retry_after: Double var last_attempt: Int64? - - init(relay: String, attempts: Int, retry_after: Double) { + + init(relay: RelayURL, attempts: Int, retry_after: Double) { self.relay = relay self.attempts = attempts self.retry_after = retry_after @@ -34,8 +34,8 @@ class PostedEvent { let flush_after: Date? var flushed_once: Bool let on_flush: OnFlush? - - init(event: NostrEvent, remaining: [String], skip_ephemeral: Bool, flush_after: Date?, on_flush: OnFlush?) { + + init(event: NostrEvent, remaining: [RelayURL], skip_ephemeral: Bool, flush_after: Date?, on_flush: OnFlush?) { self.event = event self.skip_ephemeral = skip_ephemeral self.flush_after = flush_after @@ -100,8 +100,8 @@ class PostBox { } } } - - func handle_event(relay_id: String, _ ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, _ ev: NostrConnectionEvent) { guard case .nostr_event(let resp) = ev else { return } @@ -112,9 +112,9 @@ class PostBox { remove_relayer(relay_id: relay_id, event_id: cr.event_id) } - + @discardableResult - func remove_relayer(relay_id: String, event_id: NoteId) -> Bool { + func remove_relayer(relay_id: RelayURL, event_id: NoteId) -> Bool { guard let ev = self.events[event_id] else { return false } @@ -158,17 +158,17 @@ class PostBox { pool.send(.event(event.event), to: [relayer.relay], skip_ephemeral: event.skip_ephemeral) } } - - func send(_ event: NostrEvent, to: [String]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil, on_flush: OnFlush? = nil) { + + func send(_ event: NostrEvent, to: [RelayURL]? = nil, skip_ephemeral: Bool = true, delay: TimeInterval? = nil, on_flush: OnFlush? = nil) { // Don't add event if we already have it if events[event.id] != nil { return } - - let remaining = to ?? pool.our_descriptors.map { $0.url.id } + + let remaining = to ?? pool.our_descriptors.map { $0.url } let after = delay.map { d in Date.now.addingTimeInterval(d) } let posted_ev = PostedEvent(event: event, remaining: remaining, skip_ephemeral: skip_ephemeral, flush_after: after, on_flush: on_flush) - + events[event.id] = posted_ev if after == nil { diff --git a/damus/Util/Relays/RelayBootstrap.swift b/damus/Util/Relays/RelayBootstrap.swift index cdddbbfd..a3d0754a 100644 --- a/damus/Util/Relays/RelayBootstrap.swift +++ b/damus/Util/Relays/RelayBootstrap.swift @@ -37,15 +37,15 @@ func bootstrap_relays_setting_key(pubkey: Pubkey) -> String { return pk_setting_key(pubkey, key: "bootstrap_relays") } -func save_bootstrap_relays(pubkey: Pubkey, relays: [String]) { +func save_bootstrap_relays(pubkey: Pubkey, relays: [RelayURL]) { let key = bootstrap_relays_setting_key(pubkey: pubkey) - - UserDefaults.standard.set(relays, forKey: key) + + UserDefaults.standard.set(relays.map({ $0.absoluteString }), forKey: key) } -func load_bootstrap_relays(pubkey: Pubkey) -> [String] { +func load_bootstrap_relays(pubkey: Pubkey) -> [RelayURL] { let key = bootstrap_relays_setting_key(pubkey: pubkey) - + guard let relays = UserDefaults.standard.stringArray(forKey: key) else { print("loading default bootstrap relays") return get_default_bootstrap_relays().map { $0 } @@ -55,18 +55,20 @@ func load_bootstrap_relays(pubkey: Pubkey) -> [String] { print("loading default bootstrap relays") return get_default_bootstrap_relays().map { $0 } } - - let loaded_relays = Array(Set(relays + get_default_bootstrap_relays())) + + let relay_urls = relays.compactMap({ RelayURL($0) }) + + let loaded_relays = Array(Set(relay_urls + get_default_bootstrap_relays())) print("Loading custom bootstrap relays: \(loaded_relays)") return loaded_relays } -func get_default_bootstrap_relays() -> [String] { - var default_bootstrap_relays = BOOTSTRAP_RELAYS - +func get_default_bootstrap_relays() -> [RelayURL] { + var default_bootstrap_relays: [RelayURL] = BOOTSTRAP_RELAYS.compactMap({ RelayURL($0) }) + if let user_region = Locale.current.region, let regional_bootstrap_relays = REGION_SPECIFIC_BOOTSTRAP_RELAYS[user_region] { - default_bootstrap_relays.append(contentsOf: regional_bootstrap_relays) + default_bootstrap_relays.append(contentsOf: regional_bootstrap_relays.compactMap({ RelayURL($0) })) } - + return default_bootstrap_relays } diff --git a/damus/Util/Relays/RelayFilters.swift b/damus/Util/Relays/RelayFilters.swift index dabd4f94..1940b069 100644 --- a/damus/Util/Relays/RelayFilters.swift +++ b/damus/Util/Relays/RelayFilters.swift @@ -9,9 +9,9 @@ import Foundation struct RelayFilter: Hashable { let timeline: Timeline - let relay_id: String - - init(timeline: Timeline, relay_id: String) { + let relay_id: RelayURL + + init(timeline: Timeline, relay_id: RelayURL) { self.timeline = timeline self.relay_id = relay_id } @@ -20,14 +20,14 @@ struct RelayFilter: Hashable { class RelayFilters { private let our_pubkey: Pubkey private var disabled: Set - - func is_filtered(timeline: Timeline, relay_id: String) -> Bool { + + func is_filtered(timeline: Timeline, relay_id: RelayURL) -> Bool { let filter = RelayFilter(timeline: timeline, relay_id: relay_id) let contains = disabled.contains(filter) return contains } - - func remove(timeline: Timeline, relay_id: String) { + + func remove(timeline: Timeline, relay_id: RelayURL) { let filter = RelayFilter(timeline: timeline, relay_id: relay_id) if !disabled.contains(filter) { return @@ -36,8 +36,8 @@ class RelayFilters { disabled.remove(filter) save_relay_filters(our_pubkey, filters: disabled) } - - func insert(timeline: Timeline, relay_id: String) { + + func insert(timeline: Timeline, relay_id: RelayURL) { let filter = RelayFilter(timeline: timeline, relay_id: relay_id) if disabled.contains(filter) { return @@ -77,13 +77,16 @@ func load_relay_filters(_ pubkey: Pubkey) -> Set? { guard let timeline = Timeline.init(rawValue: parts[0]) else { return } - let filter = RelayFilter(timeline: timeline, relay_id: parts[1]) + guard let relay_id = RelayURL(parts[1]) else { + return + } + let filter = RelayFilter(timeline: timeline, relay_id: relay_id) s.insert(filter) } } -func determine_to_relays(pool: RelayPool, filters: RelayFilters) -> [String] { +func determine_to_relays(pool: RelayPool, filters: RelayFilters) -> [RelayURL] { return pool.our_descriptors - .map { $0.url.url.absoluteString } + .map { $0.url } .filter { !filters.is_filtered(timeline: .search, relay_id: $0) } } diff --git a/damus/Util/Relays/RelayModel.swift b/damus/Util/Relays/RelayModel.swift index 47c3fe48..282abc5a 100644 --- a/damus/Util/Relays/RelayModel.swift +++ b/damus/Util/Relays/RelayModel.swift @@ -15,10 +15,10 @@ final class RelayModel: Hashable { init(_ url: RelayURL, metadata: RelayMetadata) { self.url = url - self.log = RelayLog(url.url) + self.log = RelayLog(url) self.metadata = metadata } - + static func == (lhs: RelayModel, rhs: RelayModel) -> Bool { lhs.url == rhs.url } diff --git a/damus/Util/Relays/RelayModelCache.swift b/damus/Util/Relays/RelayModelCache.swift index 1e8ada0e..ef9bebd3 100644 --- a/damus/Util/Relays/RelayModelCache.swift +++ b/damus/Util/Relays/RelayModelCache.swift @@ -13,14 +13,11 @@ final class RelayModelCache: ObservableObject { func model(withURL url: RelayURL) -> RelayModel? { models[url] } - - func model(with_relay_id url_string: String) -> RelayModel? { - guard let url = RelayURL(url_string) else { - return nil - } - return model(withURL: url) + + func model(with_relay_id url_string: RelayURL) -> RelayModel? { + return model(withURL: url_string) } - + func insert(model: RelayModel) { models[model.url] = model objectWillChange.send() diff --git a/damus/Util/Router.swift b/damus/Util/Router.swift index 3bf55eff..0a341d9b 100644 --- a/damus/Util/Router.swift +++ b/damus/Util/Router.swift @@ -11,8 +11,8 @@ enum Route: Hashable { case ProfileByKey(pubkey: Pubkey) case Profile(profile: ProfileModel, followers: FollowersModel) case Followers(followers: FollowersModel) - case Relay(relay: String, showActionButtons: Binding) - case RelayDetail(relay: String, metadata: RelayMetadata?) + case Relay(relay: RelayURL, showActionButtons: Binding) + case RelayDetail(relay: RelayURL, metadata: RelayMetadata?) case Following(following: FollowingModel) case MuteList case RelayConfig @@ -21,7 +21,7 @@ enum Route: Hashable { case Config case EditMetadata case DMChat(dms: DirectMessageModel) - case UserRelays(relays: [String]) + case UserRelays(relays: [RelayURL]) case KeySettings(keypair: Keypair) case AppearanceSettings(settings: UserSettingsStore) case NotificationSettings(settings: UserSettingsStore) diff --git a/damus/Util/WalletConnect+.swift b/damus/Util/WalletConnect+.swift index 7271e1ab..56cd1717 100644 --- a/damus/Util/WalletConnect+.swift +++ b/damus/Util/WalletConnect+.swift @@ -37,8 +37,8 @@ func subscribe_to_nwc(url: WalletConnectURL, pool: RelayPool) { filter.authors = [url.pubkey] filter.limit = 0 let sub = NostrSubscribe(filters: [filter], sub_id: "nwc") - - pool.send(.subscribe(sub), to: [url.relay.id], skip_ephemeral: false) + + pool.send(.subscribe(sub), to: [url.relay], skip_ephemeral: false) } @discardableResult @@ -47,10 +47,10 @@ func nwc_pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: Str guard let ev = make_wallet_connect_request(req: req, to_pk: url.pubkey, keypair: url.keypair) else { return nil } - + try? pool.add_relay(.nwc(url: url.relay)) subscribe_to_nwc(url: url, pool: pool) - post.send(ev, to: [url.relay.id], skip_ephemeral: false, delay: delay, on_flush: on_flush) + post.send(ev, to: [url.relay], skip_ephemeral: false, delay: delay, on_flush: on_flush) return ev } diff --git a/damus/Util/WalletConnect.swift b/damus/Util/WalletConnect.swift index 496b0d90..853f226f 100644 --- a/damus/Util/WalletConnect.swift +++ b/damus/Util/WalletConnect.swift @@ -24,7 +24,7 @@ struct WalletConnectURL: Equatable { urlComponents.scheme = "nostrwalletconnect" urlComponents.host = pubkey.hex() urlComponents.queryItems = [ - URLQueryItem(name: "relay", value: relay.id), + URLQueryItem(name: "relay", value: relay.absoluteString), URLQueryItem(name: "secret", value: keypair.privkey.hex()) ] diff --git a/damus/Views/AddRelayView.swift b/damus/Views/AddRelayView.swift index 42a930a8..e9749b71 100644 --- a/damus/Views/AddRelayView.swift +++ b/damus/Views/AddRelayView.swift @@ -103,7 +103,7 @@ struct AddRelayView: View { return } - state.pool.connect(to: [new_relay]) + state.pool.connect(to: [url]) if let new_ev = add_relay(ev: ev, keypair: keypair, current_relays: state.pool.our_descriptors, relay: url, info: info) { process_contact_event(state: state, ev: ev) diff --git a/damus/Views/Events/EventLoaderView.swift b/damus/Views/Events/EventLoaderView.swift index c41d65e0..57d3f5ed 100644 --- a/damus/Views/Events/EventLoaderView.swift +++ b/damus/Views/Events/EventLoaderView.swift @@ -31,8 +31,8 @@ struct EventLoaderView: View { damus_state.pool.register_handler(sub_id: subscription_uuid, handler: handle_event) damus_state.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_uuid))) } - - func handle_event(relay_id: String, ev: NostrConnectionEvent) { + + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { guard case .nostr_event(let nostr_response) = ev else { return } diff --git a/damus/Views/Onboarding/SuggestedUsersViewModel.swift b/damus/Views/Onboarding/SuggestedUsersViewModel.swift index a711e32f..3d1f2dca 100644 --- a/damus/Views/Onboarding/SuggestedUsersViewModel.swift +++ b/damus/Views/Onboarding/SuggestedUsersViewModel.swift @@ -80,7 +80,7 @@ class SuggestedUsersViewModel: ObservableObject { damus_state.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event) } - func handle_event(relay_id: String, ev: NostrConnectionEvent) { + func handle_event(relay_id: RelayURL, ev: NostrConnectionEvent) { guard case .nostr_event(let nev) = ev else { return } diff --git a/damus/Views/RelayFilterView.swift b/damus/Views/RelayFilterView.swift index 02c22363..8382a11b 100644 --- a/damus/Views/RelayFilterView.swift +++ b/damus/Views/RelayFilterView.swift @@ -27,9 +27,9 @@ struct RelayFilterView: View { .padding() .padding(.top, 20) .padding(.bottom, 0) - + List(Array(relays), id: \.url.id) { relay in - RelayToggle(state: state, timeline: timeline, relay_id: relay.url.url.absoluteString) + RelayToggle(state: state, timeline: timeline, relay_id: relay.url) } } } diff --git a/damus/Views/Relays/RelayConfigView.swift b/damus/Views/Relays/RelayConfigView.swift index 16bb49ab..19c5c2c8 100644 --- a/damus/Views/Relays/RelayConfigView.swift +++ b/damus/Views/Relays/RelayConfigView.swift @@ -40,12 +40,10 @@ struct RelayConfigView: View { let rs: [RelayDescriptor] = [] let recommended_relay_addresses = get_default_bootstrap_relays() return recommended_relay_addresses.reduce(into: rs) { xs, x in - if let url = RelayURL(x) { - xs.append(RelayDescriptor(url: url, info: .rw)) - } + xs.append(RelayDescriptor(url: x, info: .rw)) } } - + var body: some View { NavigationView { ZStack(alignment: .bottom){ @@ -133,10 +131,10 @@ struct RelayConfigView: View { } } .padding(.top, 5) - + ForEach(relayList, id: \.url) { relay in Group { - RelayView(state: state, relay: relay.url.id, showActionButtons: $showActionButtons, recommended: recommended) + RelayView(state: state, relay: relay.url, showActionButtons: $showActionButtons, recommended: recommended) Divider() } } diff --git a/damus/Views/Relays/RelayDetailView.swift b/damus/Views/Relays/RelayDetailView.swift index 08129cab..b844425e 100644 --- a/damus/Views/Relays/RelayDetailView.swift +++ b/damus/Views/Relays/RelayDetailView.swift @@ -9,14 +9,14 @@ import SwiftUI struct RelayDetailView: View { let state: DamusState - let relay: String + let relay: RelayURL let nip11: RelayMetadata? - + @ObservedObject var log: RelayLog - + @Environment(\.dismiss) var dismiss - - init(state: DamusState, relay: String, nip11: RelayMetadata?) { + + init(state: DamusState, relay: RelayURL, nip11: RelayMetadata?) { self.state = state self.relay = relay self.nip11 = nip11 @@ -48,8 +48,7 @@ struct RelayDetailView: View { } let descriptors = state.pool.our_descriptors - guard let relay_url = RelayURL(relay), - let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else { + guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else { return } @@ -78,8 +77,7 @@ struct RelayDetailView: View { guard let ev_before_add = state.contacts.event else { return } - guard let relay_url = RelayURL(relay), - let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else { + guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else { return } process_contact_event(state: state, ev: ev_after_add) @@ -114,7 +112,7 @@ struct RelayDetailView: View { if let relay_connection { Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) { HStack { - Text(relay) + Text(relay.absoluteString) Spacer() RelayStatusView(connection: relay_connection) } @@ -166,7 +164,7 @@ struct RelayDetailView: View { .onReceive(handle_notify(.switched_timeline)) { notif in dismiss() } - .navigationTitle(nip11?.name ?? relay) + .navigationTitle(nip11?.name ?? relay.absoluteString) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(true) .navigationBarItems(leading: BackNav()) @@ -202,6 +200,6 @@ struct RelayDetailView: View { struct RelayDetailView_Previews: PreviewProvider { static var previews: some View { let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "") - RelayDetailView(state: test_damus_state, relay: "relay", nip11: metadata) + RelayDetailView(state: test_damus_state, relay: RelayURL("wss://relay.damus.io")!, nip11: metadata) } } diff --git a/damus/Views/Relays/RelayPicView.swift b/damus/Views/Relays/RelayPicView.swift index c45c39d0..277f5740 100644 --- a/damus/Views/Relays/RelayPicView.swift +++ b/damus/Views/Relays/RelayPicView.swift @@ -66,13 +66,13 @@ struct InnerRelayPicView: View { } struct RelayPicView: View { - let relay: String + let relay: RelayURL let icon: String? let size: CGFloat let highlight: Highlight let disable_animation: Bool - - init(relay: String, icon: String? = nil, size: CGFloat, highlight: Highlight, disable_animation: Bool) { + + init(relay: RelayURL, icon: String? = nil, size: CGFloat, highlight: Highlight, disable_animation: Bool) { self.relay = relay self.icon = icon self.size = size @@ -106,10 +106,10 @@ func extract_tld(_ host: String) -> String { return host } -func get_relay_url(relay: String, icon: String?) -> URL? { - var favicon = relay + "/favicon.ico" - let tld = extract_tld(relay) - if tld != relay { +func get_relay_url(relay: RelayURL, icon: String?) -> URL? { + var favicon = relay.absoluteString + "/favicon.ico" + let tld = extract_tld(relay.absoluteString) + if tld != relay.absoluteString { favicon = "https://" + tld + "/favicon.ico" } let pic = icon ?? favicon @@ -119,10 +119,10 @@ func get_relay_url(relay: String, icon: String?) -> URL? { struct RelayPicView_Previews: PreviewProvider { static var previews: some View { VStack { - RelayPicView(relay: "wss://relay.damus.io", size: 55, highlight: .none, disable_animation: false) - RelayPicView(relay: "wss://nostr.wine", size: 55, highlight: .none, disable_animation: false) - RelayPicView(relay: "wss://nos.lol", size: 55, highlight: .none, disable_animation: false) - RelayPicView(relay: "fail", size: 55, highlight: .none, disable_animation: false) + RelayPicView(relay: RelayURL("wss://relay.damus.io")!, size: 55, highlight: .none, disable_animation: false) + RelayPicView(relay: RelayURL("wss://nostr.wine")!, size: 55, highlight: .none, disable_animation: false) + RelayPicView(relay: RelayURL("wss://nos.lol")!, size: 55, highlight: .none, disable_animation: false) + RelayPicView(relay: RelayURL("wss://fail.com")!, size: 55, highlight: .none, disable_animation: false) } } } diff --git a/damus/Views/Relays/RelayStatusView.swift b/damus/Views/Relays/RelayStatusView.swift index 05151893..3ec9c29c 100644 --- a/damus/Views/Relays/RelayStatusView.swift +++ b/damus/Views/Relays/RelayStatusView.swift @@ -56,7 +56,7 @@ struct RelayStatusView: View { struct RelayStatusView_Previews: PreviewProvider { static var previews: some View { - let connection = test_damus_state.pool.get_relay("wss://relay.damus.io")!.connection + let connection = test_damus_state.pool.get_relay(RelayURL("wss://relay.damus.io")!)!.connection RelayStatusView(connection: connection) } } diff --git a/damus/Views/Relays/RelayToggle.swift b/damus/Views/Relays/RelayToggle.swift index 22113496..a5ab24c9 100644 --- a/damus/Views/Relays/RelayToggle.swift +++ b/damus/Views/Relays/RelayToggle.swift @@ -10,9 +10,9 @@ import SwiftUI struct RelayToggle: View { let state: DamusState let timeline: Timeline - let relay_id: String - - func toggle_binding(relay_id: String) -> Binding { + let relay_id: RelayURL + + func toggle_binding(relay_id: RelayURL) -> Binding { return Binding(get: { !state.relay_filters.is_filtered(timeline: timeline, relay_id: relay_id) }, set: { on in @@ -30,7 +30,7 @@ struct RelayToggle: View { RelayStatusView(connection: relay_connection) } RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay_id)?.metadata.is_paid ?? false) - Toggle(relay_id, isOn: toggle_binding(relay_id: relay_id)) + Toggle(relay_id.absoluteString, isOn: toggle_binding(relay_id: relay_id)) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } } @@ -42,7 +42,7 @@ struct RelayToggle: View { struct RelayToggle_Previews: PreviewProvider { static var previews: some View { - RelayToggle(state: test_damus_state, timeline: .search, relay_id: "wss://jb55.com") + RelayToggle(state: test_damus_state, timeline: .search, relay_id: RelayURL("wss://jb55.com")!) .padding() } } diff --git a/damus/Views/Relays/RelayView.swift b/damus/Views/Relays/RelayView.swift index 27c3b008..d89e8605 100644 --- a/damus/Views/Relays/RelayView.swift +++ b/damus/Views/Relays/RelayView.swift @@ -9,14 +9,14 @@ import SwiftUI struct RelayView: View { let state: DamusState - let relay: String + let relay: RelayURL let recommended: Bool @ObservedObject private var model_cache: RelayModelCache - + @State var relay_state: Bool @Binding var showActionButtons: Bool - - init(state: DamusState, relay: String, showActionButtons: Binding, recommended: Bool) { + + init(state: DamusState, relay: RelayURL, showActionButtons: Binding, recommended: Bool) { self.state = state self.relay = relay self.recommended = recommended @@ -25,11 +25,11 @@ struct RelayView: View { let relay_state = RelayView.get_relay_state(pool: state.pool, relay: relay) self._relay_state = State(initialValue: relay_state) } - - static func get_relay_state(pool: RelayPool, relay: String) -> Bool { + + static func get_relay_state(pool: RelayPool, relay: RelayURL) -> Bool { return pool.get_relay(relay) == nil } - + var body: some View { Group { HStack { @@ -42,22 +42,22 @@ struct RelayView: View { let meta = model_cache.model(with_relay_id: relay)?.metadata RelayPicView(relay: relay, icon: meta?.icon, size: 55, highlight: .none, disable_animation: false) - + VStack(alignment: .leading) { HStack { - Text(meta?.name ?? relay) + Text(meta?.name ?? relay.absoluteString) .font(.headline) .padding(.bottom, 2) .lineLimit(1) RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) } - Text(relay) + Text(relay.absoluteString) .font(.subheadline) .foregroundColor(.gray) .lineLimit(1) .contextMenu { - CopyAction(relay: relay) - + CopyAction(relay: relay.absoluteString) + if let privkey = state.keypair.privkey { RemoveButton(privkey: privkey, showText: true) } @@ -113,8 +113,7 @@ struct RelayView: View { guard let ev_before_add = state.contacts.event else { return } - guard let relay_url = RelayURL(relay), - let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay_url, info: .rw) else { + guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else { return } process_contact_event(state: state, ev: ev_after_add) @@ -129,14 +128,13 @@ struct RelayView: View { guard let ev = state.contacts.event else { return } - + let descriptors = state.pool.our_descriptors guard let keypair = state.keypair.to_full(), - let relay_url = RelayURL(relay), - let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay_url) else { + let new_ev = remove_relay(ev: ev, current_relays: descriptors, keypair: keypair, relay: relay) else { return } - + process_contact_event(state: state, ev: new_ev) state.postbox.send(new_ev) @@ -182,6 +180,6 @@ struct RelayView: View { struct RelayView_Previews: PreviewProvider { static var previews: some View { - RelayView(state: test_damus_state, relay: "wss://relay.damus.io", showActionButtons: .constant(false), recommended: false) + RelayView(state: test_damus_state, relay: RelayURL("wss://relay.damus.io")!, showActionButtons: .constant(false), recommended: false) } } diff --git a/damus/Views/SaveKeysView.swift b/damus/Views/SaveKeysView.swift index 1000d34b..4d7b6399 100644 --- a/damus/Views/SaveKeysView.swift +++ b/damus/Views/SaveKeysView.swift @@ -115,8 +115,8 @@ struct SaveKeysView: View { self.pool.connect() } - - func handle_event(relay: String, ev: NostrConnectionEvent) { + + func handle_event(relay: RelayURL, ev: NostrConnectionEvent) { switch ev { case .ws_event(let wsev): switch wsev { diff --git a/damus/Views/UserRelaysView.swift b/damus/Views/UserRelaysView.swift index 16099d67..8e3d8231 100644 --- a/damus/Views/UserRelaysView.swift +++ b/damus/Views/UserRelaysView.swift @@ -9,18 +9,18 @@ import SwiftUI struct UserRelaysView: View { let state: DamusState - let relays: [String] - - @State var relay_state: [(String, Bool)] - - init(state: DamusState, relays: [String]) { + let relays: [RelayURL] + + @State var relay_state: [(RelayURL, Bool)] + + init(state: DamusState, relays: [RelayURL]) { self.state = state self.relays = relays let relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: relays) self._relay_state = State(initialValue: relay_state) } - - static func make_relay_state(pool: RelayPool, relays: [String]) -> [(String, Bool)] { + + static func make_relay_state(pool: RelayPool, relays: [RelayURL]) -> [(RelayURL, Bool)] { return relays.map({ r in return (r, pool.get_relay(r) == nil) }).sorted { (a, b) in a.0 < b.0 } diff --git a/damus/Views/Wallet/ConnectWalletView.swift b/damus/Views/Wallet/ConnectWalletView.swift index 3a7e01e4..1360c0e4 100644 --- a/damus/Views/Wallet/ConnectWalletView.swift +++ b/damus/Views/Wallet/ConnectWalletView.swift @@ -54,11 +54,11 @@ struct ConnectWalletView: View { Text("Are you sure you want to connect this wallet?", comment: "Prompt to ask user if they want to attach their Nostr Wallet Connect lightning wallet.") .fontWeight(.bold) .multilineTextAlignment(.center) - - Text(nwc.relay.id) + + Text(nwc.relay.absoluteString) .font(.body) .foregroundColor(.gray) - + if let lud16 = nwc.lud16 { Text(lud16) .font(.body) diff --git a/damus/Views/Wallet/WalletView.swift b/damus/Views/Wallet/WalletView.swift index 3a3c9f46..6cb6d8bb 100644 --- a/damus/Views/Wallet/WalletView.swift +++ b/damus/Views/Wallet/WalletView.swift @@ -34,7 +34,7 @@ struct WalletView: View { Divider() - RelayView(state: damus_state, relay: nwc.relay.id, showActionButtons: .constant(false), recommended: false) + RelayView(state: damus_state, relay: nwc.relay, showActionButtons: .constant(false), recommended: false) } .frame(maxWidth: .infinity, minHeight: 125, alignment: .top) .padding(.horizontal, 10) diff --git a/damusTests/RelayURLTests.swift b/damusTests/RelayURLTests.swift new file mode 100644 index 00000000..680131bf --- /dev/null +++ b/damusTests/RelayURLTests.swift @@ -0,0 +1,84 @@ +// +// RelayURLTests.swift +// damusTests +// +// Created by Daniel D’Aquino on 2024-03-20. +// + +import Foundation +import XCTest +@testable import damus + +final class RelayURLTests : XCTestCase { + func testRelayURLTrailingSlash() { + let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")! + let relay_url_2: RelayURL = RelayURL("wss://relay.damus.io/")! + + XCTAssertEqual(relay_url_1.id, relay_url_2.id, "Relays with the same address should have the same ID even if one of them was initialized with a trailing slash") + XCTAssertEqual(relay_url_1, relay_url_2, "Relays with the same address should be equal even if one of them was initialized with a trailing slash") + + var relays: [RelayURL: Int] = [:] + relays[relay_url_1] = 1 + relays[relay_url_2] = 2 + + XCTAssertEqual(relays[relay_url_1], 2, "RelayURL with a trailing slash should evaluate to the same hash in a dictionary as an equivalent one without trailing slashes") + } + + func testRelayURLDifferentProtocols() { + let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")! + let relay_url_2: RelayURL = RelayURL("ws://relay.damus.io")! + + XCTAssertNotEqual(relay_url_1.id, relay_url_2.id, "Relays with different protocols should not have the same ID") + XCTAssertNotEqual(relay_url_1, relay_url_2, "Relays with different protocols should not be equal") + + var relays: [RelayURL: Int] = [:] + relays[relay_url_1] = 1 + relays[relay_url_2] = 2 + + XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_2], "RelayURL with different protocols should not evaluate to the same hash in a dictionary") + } + + func testRelayURLDifferentDomains() { + let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")! + let relay_url_3: RelayURL = RelayURL("wss://example.com")! + + XCTAssertNotEqual(relay_url_1, relay_url_3, "Relays with different domains should not be equal") + + var relays: [RelayURL: Int] = [:] + relays[relay_url_1] = 1 + relays[relay_url_3] = 3 + + XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_3], "RelayURL with different domains should not evaluate to the same hash in a dictionary") + } + + func testRelayURLDifferentPaths() { + let relay_url_1: RelayURL = RelayURL("wss://relay.damus.io")! + let relay_url_2: RelayURL = RelayURL("wss://relay.damus.io/")! + let relay_url_3: RelayURL = RelayURL("wss://relay.damus.io/v1")! + let relay_url_4: RelayURL = RelayURL("wss://relay.damus.io/v2")! + let relay_url_5: RelayURL = RelayURL("wss://relay.damus.io/v2/beta")! + let relay_url_6: RelayURL = RelayURL("wss://relay.damus.io/v2/beta/")! + + XCTAssertEqual(relay_url_1.id, relay_url_2.id, "Relays with the same address should have the same ID even if one of them was initialized with a trailing slash") + XCTAssertEqual(relay_url_1, relay_url_2, "Relays with the same address should be equal even if one of them was initialized with a trailing slash") + + XCTAssertNotEqual(relay_url_1, relay_url_3, "Relays with different paths should not be equal") + XCTAssertNotEqual(relay_url_3, relay_url_4, "Relays with different paths should not be equal") + XCTAssertNotEqual(relay_url_4, relay_url_5, "Relays with different subpaths should not be equal") + XCTAssertEqual(relay_url_5, relay_url_6, "Relays with the same address should be equal if one of them is initialized with a trailing slash") + + var relays: [RelayURL: Int] = [:] + relays[relay_url_1] = 1 + relays[relay_url_2] = 2 + relays[relay_url_3] = 3 + relays[relay_url_4] = 4 + relays[relay_url_5] = 5 + relays[relay_url_6] = 6 + + XCTAssertEqual(relays[relay_url_1], relays[relay_url_2], "RelayURL with the same path should evaluate to the same hash in a dictionary") + XCTAssertNotEqual(relays[relay_url_1], relays[relay_url_3], "RelayURLs with different pathsshould not evaluate to the same hash in a dictionary") + XCTAssertNotEqual(relays[relay_url_3], relays[relay_url_4], "RelayURLs with different paths should not evaluate to the same hash in a dictionary") + XCTAssertNotEqual(relays[relay_url_4], relays[relay_url_5], "RelayURLs with different subpaths should not evaluate to the same hash in a dictionary") + XCTAssertEqual(relays[relay_url_5], relays[relay_url_6], "RelayURL with the same subpath should evaluate to the same hash in a dictionary even if one of them is initialized with a trailing slash") + } +} diff --git a/damusTests/WalletConnectTests.swift b/damusTests/WalletConnectTests.swift index 89d789ac..d1e1b550 100644 --- a/damusTests/WalletConnectTests.swift +++ b/damusTests/WalletConnectTests.swift @@ -55,7 +55,7 @@ final class WalletConnectTests: XCTestCase { XCTAssertEqual(url.pubkey, pk) XCTAssertEqual(url.keypair.privkey, sec) XCTAssertEqual(url.keypair.pubkey, privkey_to_pubkey(privkey: sec)) - XCTAssertEqual(url.relay.id, relay) + XCTAssertEqual(url.relay.url.absoluteString, relay) XCTAssertEqual(url.lud16, "jb55@jb55.com") // Test an NWC url format which is NIP-47 and RFC 3986 compliant @@ -76,9 +76,9 @@ final class WalletConnectTests: XCTestCase { XCTAssertEqual(url_2.pubkey, pk_2) XCTAssertEqual(url_2.keypair.privkey, sec_2) XCTAssertEqual(url_2.keypair.pubkey, privkey_to_pubkey(privkey: sec_2)) - XCTAssertEqual(url_2.relay.id, relay_2) + XCTAssertEqual(url_2.relay.url.absoluteString, relay_2) } - + func testNWCEphemeralRelay() { let sec = "8ba3a6b3b57d0f4211bb1ea4d8d1e351a367e9b4ea694746e0a4a452b2bc4d37" let pk = "89446b900c70d62438dcf66756405eea6225ad94dc61f3856f62f9699111a9a6" @@ -92,12 +92,12 @@ final class WalletConnectTests: XCTestCase { XCTAssertEqual(pool.our_descriptors.count, 0) XCTAssertEqual(pool.all_descriptors.count, 1) XCTAssertEqual(pool.all_descriptors[0].variant, .nwc) - XCTAssertEqual(pool.all_descriptors[0].url.id, "ws://127.0.0.1") + XCTAssertEqual(pool.all_descriptors[0].url.url.absoluteString, "ws://127.0.0.1") XCTAssertEqual(box.events.count, 1) let ev = box.events.first!.value XCTAssertEqual(ev.skip_ephemeral, false) XCTAssertEqual(ev.remaining.count, 1) - XCTAssertEqual(ev.remaining[0].relay, "ws://127.0.0.1") + XCTAssertEqual(ev.remaining[0].relay.url.absoluteString, "ws://127.0.0.1") } func testPerformanceExample() throws { diff --git a/damusTests/damusTests.swift b/damusTests/damusTests.swift index 362f1927..6eed0450 100644 --- a/damusTests/damusTests.swift +++ b/damusTests/damusTests.swift @@ -113,12 +113,12 @@ class damusTests: XCTestCase { func testSaveRelayFilters() { var filters = Set() - - let filter1 = RelayFilter(timeline: .search, relay_id: "wss://abc.com") - let filter2 = RelayFilter(timeline: .home, relay_id: "wss://abc.com") + + let filter1 = RelayFilter(timeline: .search, relay_id: RelayURL("wss://abc.com")!) + let filter2 = RelayFilter(timeline: .home, relay_id: RelayURL("wss://abc.com")!) filters.insert(filter1) filters.insert(filter2) - + save_relay_filters(test_pubkey, filters: filters) let loaded_filters = load_relay_filters(test_pubkey)! diff --git a/nostrscript/NostrScript.swift b/nostrscript/NostrScript.swift index 8dfc4fa6..50aee2af 100644 --- a/nostrscript/NostrScript.swift +++ b/nostrscript/NostrScript.swift @@ -335,18 +335,19 @@ public func nscript_set_bool(interp: UnsafeMutablePointer?, setting @_cdecl("nscript_pool_send_to") public func nscript_pool_send_to(interp: UnsafeMutablePointer?, preq: UnsafePointer, req_len: Int32, to: UnsafePointer, to_len: Int32) -> Int32 { - + guard let script = interp_nostrscript(interp: interp), let req_str = asm_str(cstr: preq, len: req_len), - let to = asm_str(cstr: to, len: to_len) + let to = asm_str(cstr: to, len: to_len), + let to_relay_url = RelayURL(to) else { return 0 } - + DispatchQueue.main.async { - script.pool.send_raw(.custom(req_str), to: [to], skip_ephemeral: false) + script.pool.send_raw(.custom(req_str), to: [to_relay_url], skip_ephemeral: false) } - + return 1; }