damus

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

commit 87992f4bb92ddfd1d53906ffc52ce90d1c26f855
parent 13f98659a4fda5d11d15a58b436c58f25b893f6f
Author: William Casarin <jb55@jb55.com>
Date:   Sun,  9 Jul 2023 07:41:45 -0700

Add RelayLog in developer mode

Changelog-Added: Add relay log in developer mode

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 16++++++++++++----
Mdamus/ContentView.swift | 16++++++++--------
Mdamus/Models/DamusState.swift | 4++--
Mdamus/Models/HomeModel.swift | 15++++++++++-----
Mdamus/Nostr/RelayConnection.swift | 8++++++++
Adamus/Nostr/RelayLog.swift | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdamus/Nostr/RelayPool.swift | 13+++++++++++++
Mdamus/Nostr/WebSocket.swift | 2+-
Ddamus/Util/Relays/RelayMetadatas.swift | 20--------------------
Adamus/Util/Relays/RelayModel.swift | 29+++++++++++++++++++++++++++++
Adamus/Util/Relays/RelayModelCache.swift | 27+++++++++++++++++++++++++++
Mdamus/Views/Relays/RecommendedRelayView.swift | 4++--
Mdamus/Views/Relays/RelayDetailView.swift | 19+++++++++++++++++++
Mdamus/Views/Relays/RelayToggle.swift | 2+-
Mdamus/Views/Relays/RelayView.swift | 4++--
15 files changed, 280 insertions(+), 45 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -254,7 +254,6 @@ 4CE6DF0427F7A08200C66700 /* damusUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */; }; 4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */; }; 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794729941DA700F758CC /* RelayFilters.swift */; }; - 4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */; }; 4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794D2996B16A00F758CC /* RelayToggle.swift */; }; 4CE879502996B2BD00F758CC /* RelayStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */; }; 4CE879522996B68900F758CC /* RelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE879512996B68900F758CC /* RelayType.swift */; }; @@ -294,7 +293,10 @@ 501F8C802A0220E1001AFC1D /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */; }; 501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */; }; 5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; }; + 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A62A34915F006AE6DC /* RelayModel.swift */; }; + 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; + 50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A60D132A28BEEE00186190 /* RelayLog.swift */; }; 50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; }; 50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; }; 5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; }; @@ -723,7 +725,6 @@ 4CE6DF0327F7A08200C66700 /* damusUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = damusUITestsLaunchTests.swift; sourceTree = "<group>"; }; 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConnection.swift; sourceTree = "<group>"; }; 4CE8794729941DA700F758CC /* RelayFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilters.swift; sourceTree = "<group>"; }; - 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayMetadatas.swift; sourceTree = "<group>"; }; 4CE8794D2996B16A00F758CC /* RelayToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayToggle.swift; sourceTree = "<group>"; }; 4CE8794F2996B2BD00F758CC /* RelayStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayStatusView.swift; sourceTree = "<group>"; }; 4CE879512996B68900F758CC /* RelayType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayType.swift; sourceTree = "<group>"; }; @@ -764,7 +765,10 @@ 501F8C7F2A0220E1001AFC1D /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; }; 501F8C812A0224EB001AFC1D /* KeychainStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorageTests.swift; sourceTree = "<group>"; }; 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; }; + 504323A62A34915F006AE6DC /* RelayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModel.swift; sourceTree = "<group>"; }; + 504323A82A3495B6006AE6DC /* RelayModelCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModelCache.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; + 50A60D132A28BEEE00186190 /* RelayLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLog.swift; sourceTree = "<group>"; }; 50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; }; 50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; }; 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; }; @@ -1132,6 +1136,7 @@ children = ( 501F8C5329FF5EE2001AFC1D /* CoreData */, 4CE6DF1527F8DEBF00C66700 /* RelayConnection.swift */, + 50A60D132A28BEEE00186190 /* RelayLog.swift */, 4C75EFA527FF87A20006080F /* Nostr.swift */, 4C75EFAE28049D340006080F /* NostrFilter.swift */, 4C75EFB028049D510006080F /* NostrResponse.swift */, @@ -1473,8 +1478,9 @@ isa = PBXGroup; children = ( 4CE8794729941DA700F758CC /* RelayFilters.swift */, - 4CE8794B2995B59E00F758CC /* RelayMetadatas.swift */, + 504323A82A3495B6006AE6DC /* RelayModelCache.swift */, 4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */, + 504323A62A34915F006AE6DC /* RelayModel.swift */, ); path = Relays; sourceTree = "<group>"; @@ -1762,13 +1768,13 @@ 4C216F34286F5ACD00040376 /* DMView.swift in Sources */, 4C3EA64428FF558100C48A62 /* sha256.c in Sources */, 4CCF9AAF2A1FDBDB00E03CFB /* VideoPlayer.swift in Sources */, + 504323A72A34915F006AE6DC /* RelayModel.swift in Sources */, 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */, 4C363AA828297703006E126D /* InsertSort.swift in Sources */, 4C285C86283892E7008A31F1 /* CreateAccountModel.swift in Sources */, 4C64987C286D03E000EAE2B3 /* DirectMessagesView.swift in Sources */, 7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */, 4C190F252A547D2000027FD5 /* LoadScript.swift in Sources */, - 4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */, 4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */, 4CDA128A29E9D10C0006FA5A /* SignalView.swift in Sources */, 4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */, @@ -1782,6 +1788,7 @@ 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, + 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, 3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */, 4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */, 4C363AA228296A7E006E126D /* SearchView.swift in Sources */, @@ -1980,6 +1987,7 @@ 4C363A922825FCF2006E126D /* ProfileUpdate.swift in Sources */, 4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */, 4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */, + 50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */, 4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */, 4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */, 4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */, diff --git a/damus/ContentView.swift b/damus/ContentView.swift @@ -609,25 +609,25 @@ struct ContentView: View { func connect() { let pool = RelayPool() - let metadatas = RelayMetadatas() + let model_cache = RelayModelCache() 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 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(relay_filters: relay_filters, metadatas: metadatas, pool: pool, descriptor: descriptor, new_relay_filters: new_relay_filters) + 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) - // dumb stuff needed for property wrappers - UserSettingsStore.pubkey = pubkey - let settings = UserSettingsStore() - UserSettingsStore.shared = settings - if let nwc_str = settings.nostr_wallet_connect, let nwc = WalletConnectURL(str: nwc_str) { try? pool.add_relay(.nwc(url: nwc.relay)) @@ -646,7 +646,7 @@ struct ContentView: View { lnurls: LNUrls(), settings: settings, relay_filters: relay_filters, - relay_metadata: metadatas, + relay_model_cache: model_cache, drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: pubkey), diff --git a/damus/Models/DamusState.swift b/damus/Models/DamusState.swift @@ -21,7 +21,7 @@ struct DamusState { let lnurls: LNUrls let settings: UserSettingsStore let relay_filters: RelayFilters - let relay_metadata: RelayMetadatas + let relay_model_cache: RelayModelCache let drafts: Drafts let events: EventCache let bookmarks: BookmarksManager @@ -60,5 +60,5 @@ struct DamusState { static var empty: DamusState { let user_search_cache = UserSearchCache() - return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(user_search_cache: user_search_cache), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel(settings: UserSettingsStore()), nav: NavigationCoordinator(), user_search_cache: user_search_cache) } + return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), profiles: Profiles(user_search_cache: user_search_cache), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_model_cache: RelayModelCache(), drafts: Drafts(), events: EventCache(), bookmarks: BookmarksManager(pubkey: ""), postbox: PostBox(pool: RelayPool()), bootstrap_relays: [], replies: ReplyCounter(our_pubkey: ""), muted_threads: MutedThreadsManager(keypair: Keypair(pubkey: "", privkey: nil)), wallet: WalletModel(settings: UserSettingsStore()), nav: NavigationCoordinator(), user_search_cache: user_search_cache) } } diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift @@ -857,7 +857,7 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { if new.contains(d) { if let url = RelayURL(d) { let descriptor = RelayDescriptor(url: url, info: decoded[d] ?? .rw) - add_new_relay(relay_filters: state.relay_filters, metadatas: state.relay_metadata, pool: state.pool, descriptor: descriptor, new_relay_filters: new_relay_filters) + 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) @@ -871,12 +871,12 @@ func load_our_relays(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) { } } -func add_new_relay(relay_filters: RelayFilters, metadatas: RelayMetadatas, pool: RelayPool, descriptor: RelayDescriptor, new_relay_filters: Bool) { +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 - guard metadatas.lookup(relay_id: relay_id) == nil else { + guard model_cache.model(withURL: url) == nil else { return } @@ -885,8 +885,13 @@ func add_new_relay(relay_filters: RelayFilters, metadatas: RelayMetadatas, pool: return } - DispatchQueue.main.async { - metadatas.insert(relay_id: relay_id, metadata: meta) + await MainActor.run { + let model = RelayModel(url, metadata: meta) + model_cache.insert(model: model) + + if logging_enabled { + pool.setLog(model.log, for: relay_id) + } // if this is the first time adding filters, we should filter non-paid relays if new_relay_filters && !meta.is_paid { diff --git a/damus/Nostr/RelayConnection.swift b/damus/Nostr/RelayConnection.swift @@ -49,6 +49,7 @@ final class RelayConnection: ObservableObject { private var handleEvent: (NostrConnectionEvent) -> () private let url: RelayURL + var log: RelayLog? init(url: RelayURL, handleEvent: @escaping (NostrConnectionEvent) -> ()) { self.url = url @@ -59,11 +60,13 @@ final class RelayConnection: ObservableObject { socket.ping { err in if err == nil { self.last_pong = .now + self.log?.add("Successful ping") } else { print("pong failed, reconnecting \(self.url.id)") self.isConnected = false self.isConnecting = false self.reconnect_with_backoff() + self.log?.add("Ping failed") } } } @@ -153,6 +156,10 @@ final class RelayConnection: ObservableObject { DispatchQueue.main.async { self.handleEvent(.ws_event(event)) } + + if let description = event.description { + log?.add(description) + } } func reconnect_with_backoff() { @@ -166,6 +173,7 @@ final class RelayConnection: ObservableObject { } disconnect() connect() + log?.add("Reconnecting...") } func reconnect_in(after: TimeInterval) { diff --git a/damus/Nostr/RelayLog.swift b/damus/Nostr/RelayLog.swift @@ -0,0 +1,146 @@ +// +// RelayLog.swift +// damus +// +// Created by Bryan Montz on 6/1/23. +// + +import Combine +import Foundation +import UIKit + +/// Stores a running list of events and state changes related to a relay, so that users +/// will have information to help developers debug issues. +final class RelayLog: ObservableObject { + private static let line_limit = 250 + private let relay_url: URL? + private lazy var formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .medium + return formatter + }() + + private(set) var lines = [String]() + + private var notification_token: AnyCancellable? + + /// Creates a RelayLog + /// - 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) { + self.relay_url = relay_url + + setUp() + } + + private var log_files_directory: URL { + FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("RelayLogs", isDirectory: true) + } + + private var log_file_url: URL? { + guard let file_name = relay_url?.absoluteString.data(using: .utf8) else { + return nil + } + return log_files_directory.appendingPathComponent(file_name.base64EncodedString()) + } + + /// Sets up the log file and prepares to listen to app state changes + private func setUp() { + guard let log_file_url else { + return + } + + try? FileManager.default.createDirectory(at: log_files_directory, withIntermediateDirectories: false) + + if !FileManager.default.fileExists(atPath: log_file_url.path) { + // create the log file if it doesn't exist yet + FileManager.default.createFile(atPath: log_file_url.path, contents: nil) + } else { + // otherwise load it into memory + readFromDisk() + } + + let willResignPublisher = NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification) + let willTerminatePublisher = NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification) + notification_token = Publishers.Merge(willResignPublisher, willTerminatePublisher) + .sink { [weak self] _ in + self?.writeToDisk() + } + } + + /// The current contents of the log + var contents: String? { + guard !lines.isEmpty else { + return nil + } + return lines.joined(separator: "\n") + } + + /// Adds content to the log + /// - Parameter content: what to add to the log. The date and time are prepended to the content. + func add(_ content: String) { + Task { + await addLine(content) + await publishChanges() + } + } + + @MainActor private func addLine(_ line: String) { + let line = "\(formatter.string(from: .now)) - \(line)" + lines.insert(line, at: 0) + truncateLines() + } + + /// Tells views that our log has been updated + @MainActor private func publishChanges() { + objectWillChange.send() + } + + private func truncateLines() { + lines = Array(lines.prefix(RelayLog.line_limit)) + } + + /// Reads the contents of the log file from disk into memory + private func readFromDisk() { + guard let log_file_url else { + return + } + + do { + let handle = try FileHandle(forReadingFrom: log_file_url) + let data = try handle.readToEnd() + try handle.close() + + guard let data, let content = String(data: data, encoding: .utf8) else { + return + } + + lines = content.components(separatedBy: "\n") + + truncateLines() + } catch { + print("⚠️ Warning: RelayLog failed to read from \(log_file_url)") + } + } + + /// Writes the contents of the lines in memory to disk + private func writeToDisk() { + guard let log_file_url, let relay_url, + !lines.isEmpty, + let content = lines.joined(separator: "\n").data(using: .utf8) else { + return + } + + do { + let handle = try FileHandle(forWritingTo: log_file_url) + + try handle.truncate(atOffset: 0) + try handle.write(contentsOf: content) + try handle.close() + } catch { + print("⚠️ Warning: RelayLog(\(relay_url)) failed to write to file: \(error)") + } + } +} diff --git a/damus/Nostr/RelayPool.swift b/damus/Nostr/RelayPool.swift @@ -43,6 +43,12 @@ class RelayPool { } } + if let self, path.status != self.last_network_status { + for relay in relays { + relay.connection.log?.add("Network state: \(path.status)") + } + } + self?.last_network_status = path.status } network_monitor.start(queue: network_monitor_queue) @@ -110,6 +116,13 @@ class RelayPool { self.relays.append(relay) } + func setLog(_ log: RelayLog, for relay_id: String) { + // add the current network state to the log + log.add("Network state: \(network_monitor.currentPath.status)") + + get_relay(relay_id)?.connection.log = log + } + /// This is used to retry dead connections func connect_to_disconnected() { for relay in relays { diff --git a/damus/Nostr/WebSocket.swift b/damus/Nostr/WebSocket.swift @@ -19,7 +19,7 @@ enum WebSocketEvent { case .connected: return "Connected" case .message(_): - return "Received message" + return nil // adding this to the RelayLog was too noisy case .disconnected(let close_code, let reason): return "Disconnected: Close code: \(close_code), reason: \(reason ?? "unknown")" case .error(let error): diff --git a/damus/Util/Relays/RelayMetadatas.swift b/damus/Util/Relays/RelayMetadatas.swift @@ -1,20 +0,0 @@ -// -// RelayMetadatas.swift -// damus -// -// Created by William Casarin on 2023-02-09. -// - -import Foundation - -class RelayMetadatas { - private var metadata: [String: RelayMetadata] = [:] - - func lookup(relay_id: String) -> RelayMetadata? { - return metadata[relay_id] - } - - func insert(relay_id: String, metadata: RelayMetadata) { - self.metadata[relay_id] = metadata - } -} diff --git a/damus/Util/Relays/RelayModel.swift b/damus/Util/Relays/RelayModel.swift @@ -0,0 +1,29 @@ +// +// RelayModel.swift +// damus +// +// Created by Bryan Montz on 6/10/23. +// + +import Foundation + +final class RelayModel: Hashable { + + let url: RelayURL + let log: RelayLog + let metadata: RelayMetadata + + init(_ url: RelayURL, metadata: RelayMetadata) { + self.url = url + self.log = RelayLog(url.url) + self.metadata = metadata + } + + static func == (lhs: RelayModel, rhs: RelayModel) -> Bool { + lhs.url == rhs.url + } + + func hash(into hasher: inout Hasher) { + hasher.combine(url) + } +} diff --git a/damus/Util/Relays/RelayModelCache.swift b/damus/Util/Relays/RelayModelCache.swift @@ -0,0 +1,27 @@ +// +// RelayModels.swift +// damus +// +// Created by Bryan Montz on 6/10/23. +// + +import Foundation + +final class RelayModelCache { + private var models = [RelayURL: RelayModel]() + + 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 insert(model: RelayModel) { + models[model.url] = model + } +} diff --git a/damus/Views/Relays/RecommendedRelayView.swift b/damus/Views/Relays/RecommendedRelayView.swift @@ -37,11 +37,11 @@ struct RecommendedRelayView: View { } } - RelayType(is_paid: damus.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false) + RelayType(is_paid: damus.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) Text(relay).layoutPriority(1) - if let meta = damus.relay_metadata.lookup(relay_id: relay) { + if let meta = damus.relay_model_cache.model(with_relay_id: relay)?.metadata { NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta)){ EmptyView() } diff --git a/damus/Views/Relays/RelayDetailView.swift b/damus/Views/Relays/RelayDetailView.swift @@ -12,8 +12,18 @@ struct RelayDetailView: View { let relay: String let nip11: RelayMetadata + @ObservedObject var log: RelayLog + @Environment(\.dismiss) var dismiss + init(state: DamusState, relay: String, nip11: RelayMetadata) { + self.state = state + self.relay = relay + self.nip11 = nip11 + + log = state.relay_model_cache.model(with_relay_id: relay)?.log ?? RelayLog() + } + func check_connection() -> Bool { for relay in state.pool.relays { if relay.id == self.relay { @@ -114,6 +124,15 @@ struct RelayDetailView: View { Text(nipsList(nips: nips)) } } + + if state.settings.developer_mode, let log_contents = log.contents { + Section("Log") { + Text(log_contents) + .font(.system(size: 13)) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) + } + } } } .onReceive(handle_notify(.switched_timeline)) { notif in diff --git a/damus/Views/Relays/RelayToggle.swift b/damus/Views/Relays/RelayToggle.swift @@ -29,7 +29,7 @@ struct RelayToggle: View { if let relay_connection { RelayStatusView(connection: relay_connection) } - RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay_id)?.is_paid ?? false) + 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)) .toggleStyle(SwitchToggleStyle(tint: .accentColor)) } diff --git a/damus/Views/Relays/RelayView.swift b/damus/Views/Relays/RelayView.swift @@ -25,9 +25,9 @@ struct RelayView: View { } } - RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false) + RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false) - if let meta = state.relay_metadata.lookup(relay_id: relay) { + if let meta = state.relay_model_cache.model(with_relay_id: relay)?.metadata { Text(relay) .background( NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta), label: {