From 7836daa6d34cf77ca86af5c42489ac36ec4dac92 Mon Sep 17 00:00:00 2001 From: Mateusz Matoszko <2045823+mmatoszko@users.noreply.github.com> Date: Sun, 17 Aug 2025 21:29:15 +0200 Subject: [PATCH] Adds O(1) method for peer nickname retrieval (#450) * Adds O(1) method for peerNickname retrieval * Uses peerNickname method where possible --- bitchat/Services/BLEService.swift | 9 ++++++++- bitchat/Services/CommandProcessor.swift | 2 +- bitchat/Services/NostrTransport.swift | 1 + bitchat/Services/PrivateChatManager.swift | 2 +- bitchat/Services/Transport.swift | 1 + bitchat/Services/UnifiedPeerService.swift | 2 +- bitchat/ViewModels/ChatViewModel.swift | 22 +++++++++++----------- bitchat/Views/ContentView.swift | 2 +- bitchat/Views/FingerprintView.swift | 2 +- bitchatTests/Mocks/MockBLEService.swift | 6 +++++- 10 files changed, 31 insertions(+), 18 deletions(-) diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 3bb19c2b..97f0060c 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -336,7 +336,14 @@ final class BLEService: NSObject { }() return collectionsQueue.sync { peers[shortID]?.isConnected ?? false } } - + + func peerNickname(peerID: String) -> String? { + collectionsQueue.sync { + guard let peer = peers[peerID], peer.isConnected else { return nil } + return peer.nickname + } + } + func getPeerNicknames() -> [String: String] { return collectionsQueue.sync { Dictionary(uniqueKeysWithValues: peers.compactMap { (id, info) in diff --git a/bitchat/Services/CommandProcessor.swift b/bitchat/Services/CommandProcessor.swift index 4638981c..0beb91d5 100644 --- a/bitchat/Services/CommandProcessor.swift +++ b/bitchat/Services/CommandProcessor.swift @@ -119,7 +119,7 @@ class CommandProcessor { if chatViewModel?.selectedPrivateChatPeer != nil { // In private chat - if let peerNickname = meshService?.getPeerNicknames()[targetPeerID] { + if let peerNickname = meshService?.peerNickname(peerID: targetPeerID) { let personalMessage = "* \(emoji) \(myNickname) \(action) you\(suffix) *" meshService?.sendPrivateMessage(personalMessage, to: targetPeerID, recipientNickname: peerNickname, diff --git a/bitchat/Services/NostrTransport.swift b/bitchat/Services/NostrTransport.swift index 5b7ba90e..4cc959e3 100644 --- a/bitchat/Services/NostrTransport.swift +++ b/bitchat/Services/NostrTransport.swift @@ -31,6 +31,7 @@ final class NostrTransport: Transport { func emergencyDisconnectAll() { /* no-op */ } func isPeerConnected(_ peerID: String) -> Bool { false } + func peerNickname(peerID: String) -> String? { nil } func getPeerNicknames() -> [String : String] { [:] } func getFingerprint(for peerID: String) -> String? { nil } diff --git a/bitchat/Services/PrivateChatManager.swift b/bitchat/Services/PrivateChatManager.swift index 29fe5308..14a99c2b 100644 --- a/bitchat/Services/PrivateChatManager.swift +++ b/bitchat/Services/PrivateChatManager.swift @@ -53,7 +53,7 @@ class PrivateChatManager: ObservableObject { /// Send a private message func sendMessage(_ content: String, to peerID: String) { guard let meshService = meshService, - let peerNickname = meshService.getPeerNicknames()[peerID] else { + let peerNickname = meshService.peerNickname(peerID: peerID) else { return } diff --git a/bitchat/Services/Transport.swift b/bitchat/Services/Transport.swift index b78587ff..0685cfac 100644 --- a/bitchat/Services/Transport.swift +++ b/bitchat/Services/Transport.swift @@ -29,6 +29,7 @@ protocol Transport: AnyObject { // Connectivity and peers func isPeerConnected(_ peerID: String) -> Bool + func peerNickname(peerID: String) -> String? func getPeerNicknames() -> [String: String] // Protocol utilities diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index 4c099d49..aa04b7ff 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -279,7 +279,7 @@ class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { if actualNickname.isEmpty { // Try to get from mesh service's current peer list - if let meshPeerNickname = meshService.getPeerNicknames()[peerID] { + if let meshPeerNickname = meshService.peerNickname(peerID: peerID) { actualNickname = meshPeerNickname SecureLogger.log("🔍 Got nickname from mesh service: '\(actualNickname)'", category: SecureLogger.session, level: .debug) diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 184bd3b4..9c794112 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -527,8 +527,8 @@ class ChatViewModel: ObservableObject, BitchatDelegate { } // Get the peer's nickname to check for temporary Nostr peer IDs - let peerNickname = meshService.getPeerNicknames()[peerID]?.lowercased() ?? "" - + let peerNickname = meshService.peerNickname(peerID: peerID)?.lowercased() ?? "" + // Check if any temporary Nostr peer IDs have unread messages from this nickname for unreadPeerID in unreadPrivateMessages { if unreadPeerID.hasPrefix("nostr_") { @@ -833,7 +833,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Check if blocked if unifiedPeerService.isBlocked(peerID) { - let nickname = meshService.getPeerNicknames()[peerID] ?? "user" + let nickname = meshService.peerNickname(peerID: peerID) ?? "user" addSystemMessage("cannot send message to \(nickname): user is blocked.") return } @@ -846,7 +846,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { let hasNostrKey = favoriteStatus?.peerNostrPublicKey != nil // Get nickname from various sources - var recipientNickname = meshService.getPeerNicknames()[peerID] + var recipientNickname = meshService.peerNickname(peerID: peerID) if recipientNickname == nil && favoriteStatus != nil { recipientNickname = favoriteStatus?.peerNickname } @@ -938,7 +938,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { return } - let peerNickname = meshService.getPeerNicknames()[peerID] ?? "unknown" + let peerNickname = meshService.peerNickname(peerID: peerID) ?? "unknown" // Check if the peer is blocked if unifiedPeerService.isBlocked(peerID) { @@ -1280,7 +1280,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Find peer nickname let peerNickname: String - if let nickname = meshService.getPeerNicknames()[peerID] { + if let nickname = meshService.peerNickname(peerID: peerID) { peerNickname = nickname } else if let favorite = FavoritesPersistenceService.shared.getFavoriteStatus(for: peerPublicKey) { peerNickname = favorite.peerNickname @@ -1333,7 +1333,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { if let peerID = selectedPrivateChatPeer { // In private chat - send to the other person - if let peerNickname = meshService.getPeerNicknames()[peerID] { + if let peerNickname = meshService.peerNickname(peerID: peerID) { // Only send screenshot notification if we have an established session // This prevents triggering handshake requests for screenshot notifications let sessionState = meshService.getNoiseSessionState(for: peerID) @@ -1355,7 +1355,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { isRelay: false, originalSender: nil, isPrivate: true, - recipientNickname: meshService.getPeerNicknames()[peerID], + recipientNickname: meshService.peerNickname(peerID: peerID), senderPeerID: meshService.myPeerID ) var chats = privateChats @@ -1413,7 +1413,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { var actualPeerID = peerID // Check if this peer ID exists in current nicknames - if meshService.getPeerNicknames()[peerID] == nil { + if meshService.peerNickname(peerID: peerID) == nil { // Peer not found with this ID, try to find by fingerprint or nickname if let oldNoiseKey = Data(hexString: peerID), let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: oldNoiseKey) { @@ -1435,7 +1435,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { if originalTransport == "nostr" { // Skip read receipts for Nostr messages - unnecessary complexity // The radical simplification plan says to accept occasional loss - } else if meshService.getPeerNicknames()[actualPeerID] != nil { + } else if meshService.peerNickname(peerID: actualPeerID) != nil { // Use mesh for connected peers (default behavior) messageRouter.sendReadReceipt(receipt, to: actualPeerID) } else { @@ -3620,7 +3620,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { if let noiseKey = Data(hexString: recipientID), let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey), favoriteStatus.peerNostrPublicKey != nil, - self.meshService.getPeerNicknames()[recipientID] == nil { + self.meshService.peerNickname(peerID: recipientID) == nil { originalTransport = "nostr" } diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 5505dbdf..9154dea2 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -970,7 +970,7 @@ struct ContentView: View { // Resolve peer object for header context (may be offline favorite) let peer = viewModel.getPeer(byID: headerPeerID) let privatePeerNick = peer?.displayName ?? - viewModel.meshService.getPeerNicknames()[headerPeerID] ?? + viewModel.meshService.peerNickname(peerID: headerPeerID) ?? FavoritesPersistenceService.shared.getFavoriteStatus(for: Data(hexString: headerPeerID) ?? Data())?.peerNickname ?? // getFavoriteStatusByNostrKey not implemented // FavoritesPersistenceService.shared.getFavoriteStatusByNostrKey(privatePeerID)?.peerNickname ?? diff --git a/bitchat/Views/FingerprintView.swift b/bitchat/Views/FingerprintView.swift index 093649c6..93cd5659 100644 --- a/bitchat/Views/FingerprintView.swift +++ b/bitchat/Views/FingerprintView.swift @@ -41,7 +41,7 @@ struct FingerprintView: View { VStack(alignment: .leading, spacing: 16) { // Peer info - let peerNickname = viewModel.meshService.getPeerNicknames()[peerID] ?? "Unknown" + let peerNickname = viewModel.meshService.peerNickname(peerID: peerID) ?? "Unknown" let encryptionStatus = viewModel.getEncryptionStatus(for: peerID) HStack { diff --git a/bitchatTests/Mocks/MockBLEService.swift b/bitchatTests/Mocks/MockBLEService.swift index b28859f4..2e3a409b 100644 --- a/bitchatTests/Mocks/MockBLEService.swift +++ b/bitchatTests/Mocks/MockBLEService.swift @@ -63,7 +63,11 @@ class MockBLEService: NSObject { func isPeerConnected(_ peerID: String) -> Bool { return connectedPeers.contains(peerID) } - + + func peerNickname(peerID: String) -> String? { + "MockPeer_\(peerID)" + } + func getPeerNicknames() -> [String: String] { var nicknames: [String: String] = [:] for peer in connectedPeers {