diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index a763b95c..16168cfd 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -18,14 +18,14 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // MARK: - Published Properties @Published private(set) var peers: [BitchatPeer] = [] - @Published private(set) var connectedPeerIDs: Set = [] + @Published private(set) var connectedPeerIDs: Set = [] @Published private(set) var favorites: [BitchatPeer] = [] @Published private(set) var mutualFavorites: [BitchatPeer] = [] // MARK: - Private Properties - private var peerIndex: [String: BitchatPeer] = [:] - private var fingerprintCache: [String: String] = [:] // peerID -> fingerprint + private var peerIndex: [PeerID: BitchatPeer] = [:] + private var fingerprintCache: [PeerID: String] = [:] private let meshService: Transport private let identityManager: SecureIdentityStateManagerProtocol weak var messageRouter: MessageRouter? @@ -97,7 +97,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // Update fingerprint cache if let publicKey = peerInfo.noisePublicKey { - fingerprintCache[peerID.id] = publicKey.sha256Fingerprint() + fingerprintCache[peerID] = publicKey.sha256Fingerprint() } } @@ -114,12 +114,12 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } if isConnectedByNickname { continue } - let peer = buildPeerFromFavorite(favorite: favorite, peerID: peerID.id) + let peer = buildPeerFromFavorite(favorite: favorite, peerID: peerID) enrichedPeers.append(peer) addedPeerIDs.insert(peerID) // Update fingerprint cache - fingerprintCache[peerID.id] = favoriteKey.sha256Fingerprint() + fingerprintCache[peerID] = favoriteKey.sha256Fingerprint() } // Phase 3: Sort peers @@ -137,10 +137,10 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // Phase 4: Build subsets and indices var favoritesList: [BitchatPeer] = [] var mutualsList: [BitchatPeer] = [] - var newIndex: [String: BitchatPeer] = [:] + var newIndex: [PeerID: BitchatPeer] = [:] for peer in enrichedPeers { - newIndex[peer.peerID.id] = peer + newIndex[peer.peerID] = peer if peer.isFavorite { favoritesList.append(peer) @@ -155,7 +155,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { p.isConnected || p.isReachable || p.isMutualFavorite } self.peers = filtered - self.connectedPeerIDs = Set(connected.map(\.id)) + self.connectedPeerIDs = connected self.favorites = favoritesList self.mutualFavorites = mutualsList self.peerIndex = newIndex @@ -204,10 +204,10 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { private func buildPeerFromFavorite( favorite: FavoritesPersistenceService.FavoriteRelationship, - peerID: String + peerID: PeerID ) -> BitchatPeer { var peer = BitchatPeer( - peerID: PeerID(str: peerID), + peerID: peerID, noisePublicKey: favorite.peerNoisePublicKey, nickname: favorite.peerNickname, lastSeen: favorite.lastUpdated, @@ -224,8 +224,8 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // MARK: - Public Methods /// Get peer by ID - func getPeer(by id: String) -> BitchatPeer? { - return peerIndex[id] + func getPeer(by peerID: PeerID) -> BitchatPeer? { + return peerIndex[peerID] } /// Get peer ID for nickname @@ -238,13 +238,8 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { return nil } - /// Check if peer is online - func isOnline(_ peerID: String) -> Bool { - return connectedPeerIDs.contains(peerID) - } - /// Check if peer is blocked - func isBlocked(_ peerID: String) -> Bool { + func isBlocked(_ peerID: PeerID) -> Bool { // Get fingerprint guard let fingerprint = getFingerprint(for: peerID) else { return false } @@ -257,8 +252,8 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } /// Toggle favorite status - func toggleFavorite(_ peerID: String) { - guard let peer = getPeer(by: peerID) else { + func toggleFavorite(_ peerID: PeerID) { + guard let peer = getPeer(by: peerID) else { SecureLogger.warning("⚠️ Cannot toggle favorite - peer not found: \(peerID)", category: .session) return } @@ -273,7 +268,7 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { if actualNickname.isEmpty { // Try to get from mesh service's current peer list - if let meshPeerNickname = meshService.peerNickname(peerID: PeerID(str: peerID)) { + if let meshPeerNickname = meshService.peerNickname(peerID: peerID) { actualNickname = meshPeerNickname SecureLogger.debug("🔍 Got nickname from mesh service: '\(actualNickname)'", category: .session) } @@ -306,10 +301,10 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // Send favorite notification to the peer via router (mesh or Nostr) if let router = messageRouter { - router.sendFavoriteNotification(to: PeerID(str: peerID), isFavorite: !wasFavorite) + router.sendFavoriteNotification(to: peerID, isFavorite: !wasFavorite) } else { // Fallback to mesh-only if router not yet wired - meshService.sendFavoriteNotification(to: PeerID(str: peerID), isFavorite: !wasFavorite) + meshService.sendFavoriteNotification(to: peerID, isFavorite: !wasFavorite) } // Force update of peers to reflect the change @@ -321,46 +316,14 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { } } - /// Toggle blocked status - func toggleBlocked(_ peerID: String) { - guard let fingerprint = getFingerprint(for: peerID) else { return } - - // Get or create social identity - var identity = identityManager.getSocialIdentity(for: fingerprint) - ?? SocialIdentity( - fingerprint: fingerprint, - localPetname: nil, - claimedNickname: getPeer(by: peerID)?.displayName ?? "Unknown", - trustLevel: .unknown, - isFavorite: false, - isBlocked: false, - notes: nil - ) - - // Toggle blocked status - identity.isBlocked = !identity.isBlocked - - // Can't be both favorite and blocked - if identity.isBlocked { - identity.isFavorite = false - // Also remove from favorites service - if let peer = getPeer(by: peerID) { - favoritesService.removeFavorite(peerNoisePublicKey: peer.noisePublicKey) - } - } - - identityManager.updateSocialIdentity(identity) - } - - /// Get fingerprint for peer ID - func getFingerprint(for peerID: String) -> String? { + func getFingerprint(for peerID: PeerID) -> String? { // Check cache first if let cached = fingerprintCache[peerID] { return cached } // Try to get from mesh service - if let fingerprint = meshService.getFingerprint(for: PeerID(str: peerID)) { + if let fingerprint = meshService.getFingerprint(for: peerID) { fingerprintCache[peerID] = fingerprint return fingerprint } @@ -378,13 +341,13 @@ final class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // MARK: - Compatibility Methods (for easy migration) var allPeers: [BitchatPeer] { peers } - var connectedPeers: [String] { Array(connectedPeerIDs) } - var favoritePeers: Set { - Set(favorites.compactMap { getFingerprint(for: $0.peerID.id) }) + var connectedPeers: [PeerID] { Array(connectedPeerIDs) } + var favoritePeers: Set { + Set(favorites.compactMap { getFingerprint(for: $0.peerID) }) } var blockedUsers: Set { Set(peers.compactMap { peer in - isBlocked(peer.peerID.id) ? getFingerprint(for: peer.peerID.id) : nil + isBlocked(peer.peerID) ? getFingerprint(for: peer.peerID) : nil }) } } diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 97a2b4f2..35fca3b2 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -251,7 +251,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Computed properties for compatibility @MainActor - var connectedPeers: [String] { Array(unifiedPeerService.connectedPeerIDs) } + var connectedPeers: [String] { Array(unifiedPeerService.connectedPeerIDs.map(\.id)) } @Published var allPeers: [BitchatPeer] = [] var privateChats: [String: [BitchatMessage]] { get { privateChatManager.privateChats } @@ -1186,7 +1186,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Check if messages are stored under the stable Noise key hex - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { let noiseKeyHex = peer.noisePublicKey.hexEncodedString() if unreadPrivateMessages.contains(noiseKeyHex) { return true @@ -1233,7 +1233,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if let ephemeralID = ephemeralPeerID { // Found the ephemeral peer, use normal toggle - unifiedPeerService.toggleFavorite(ephemeralID.id) + unifiedPeerService.toggleFavorite(ephemeralID) // Also trigger UI update objectWillChange.send() } else { @@ -1278,7 +1278,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } } else { // This is an ephemeral peer ID (16 hex chars), use normal toggle - unifiedPeerService.toggleFavorite(peerID) + unifiedPeerService.toggleFavorite(PeerID(str: peerID)) // Trigger UI update objectWillChange.send() } @@ -1294,7 +1294,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } } else { // This is an ephemeral peer ID - check with UnifiedPeerService - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { return peer.isFavorite } } @@ -1306,7 +1306,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor func isPeerBlocked(_ peerID: String) -> Bool { - return unifiedPeerService.isBlocked(peerID) + return unifiedPeerService.isBlocked(PeerID(str: peerID)) } // Helper method to find current peer ID for a fingerprint @@ -2206,7 +2206,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { guard !content.isEmpty else { return } // Check if blocked - if unifiedPeerService.isBlocked(peerID) { + if unifiedPeerService.isBlocked(PeerID(str: peerID)) { let nickname = meshService.peerNickname(peerID: PeerID(str: peerID)) ?? "user" addSystemMessage( String( @@ -2458,7 +2458,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let peerNickname = meshService.peerNickname(peerID: PeerID(str: peerID)) ?? "unknown" // Check if the peer is blocked - if unifiedPeerService.isBlocked(peerID) { + if unifiedPeerService.isBlocked(PeerID(str: peerID)) { addSystemMessage( String( format: String(localized: "system.chat.blocked", comment: "System message when starting chat fails because peer is blocked"), @@ -2470,7 +2470,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Check mutual favorites for offline messaging - if let peer = unifiedPeerService.getPeer(by: peerID), + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)), peer.isFavorite && !peer.theyFavoritedUs && !peer.isConnected { addSystemMessage( String( @@ -2484,7 +2484,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Consolidate messages from stable Noise key if needed // This ensures Nostr messages appear when opening a chat with an ephemeral peer ID - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { let noiseKeyHex = peer.noisePublicKey.hexEncodedString() // If we have messages stored under the stable Noise key hex but not under the ephemeral ID, @@ -3045,7 +3045,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { peerNostrPubkey = favoriteStatus.peerNostrPublicKey } // Otherwise get the Noise key from the peer info - else if let peer = unifiedPeerService.getPeer(by: peerID) { + else if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { noiseKeyHex = peer.noisePublicKey.hexEncodedString() let favoriteStatus = FavoritesPersistenceService.shared.getFavoriteStatus(for: peer.noisePublicKey) peerNostrPubkey = favoriteStatus?.peerNostrPublicKey @@ -3068,7 +3068,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Skip if we already sent an ACK for this message if !sentReadReceipts.contains(message.id) { // Use stable Noise key hex if available; else fall back to peerID - let recipPeer = (Data(hexString: peerID) != nil) ? peerID : (unifiedPeerService.getPeer(by: peerID)?.noisePublicKey.hexEncodedString() ?? peerID) + let recipPeer = (Data(hexString: peerID) != nil) ? peerID : (unifiedPeerService.getPeer(by: PeerID(str: peerID))?.noisePublicKey.hexEncodedString() ?? peerID) let receipt = ReadReceipt(originalMessageID: message.id, readerID: meshService.myPeerID.id, readerNickname: nickname) messageRouter.sendReadReceipt(receipt, to: PeerID(str: recipPeer)) sentReadReceipts.insert(message.id) @@ -3088,7 +3088,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Also include messages stored under the stable Noise key (Nostr path) - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { let noiseKeyHex = peer.noisePublicKey.hexEncodedString() if noiseKeyHex != peerID, let nostrMessages = privateChats[noiseKeyHex] { combined.append(contentsOf: nostrMessages) @@ -4247,7 +4247,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor func getFingerprint(for peerID: String) -> String? { - return unifiedPeerService.getFingerprint(for: peerID) + return unifiedPeerService.getFingerprint(for: PeerID(str: peerID)) } // @@ -4337,7 +4337,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Also log any offline favorites and whether we consider them verified let offlineFavorites = unifiedPeerService.favorites.filter { !$0.isConnected } for fav in offlineFavorites { - let fp = unifiedPeerService.getFingerprint(for: fav.peerID.id) + let fp = unifiedPeerService.getFingerprint(for: fav.peerID) let isVer = fp.flatMap { verifiedFingerprints.contains($0) } ?? false let fpShort = fp?.prefix(8) ?? "nil" SecureLogger.info("⭐️ Favorite offline: \(fav.nickname) fp=\(fpShort) verified=\(isVer)", category: .security) @@ -4454,7 +4454,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { switch type { case .privateMessage: guard let pm = PrivateMessagePacket.decode(from: payload) else { return } - let senderName = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? "Unknown" + let senderName = unifiedPeerService.getPeer(by: peerID)?.nickname ?? "Unknown" let pmMentions = parseMentions(from: pm.content) let msg = BitchatMessage( id: pm.messageID, @@ -4474,7 +4474,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { case .delivered: guard let messageID = String(data: payload, encoding: .utf8) else { return } - if let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname { + if let name = unifiedPeerService.getPeer(by: peerID)?.nickname { if let messages = privateChats[peerID.id], let idx = messages.firstIndex(where: { $0.id == messageID }) { privateChats[peerID.id]?[idx].deliveryStatus = .delivered(to: name, at: Date()) objectWillChange.send() @@ -4483,7 +4483,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { case .readReceipt: guard let messageID = String(data: payload, encoding: .utf8) else { return } - if let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname { + if let name = unifiedPeerService.getPeer(by: peerID)?.nickname { if let messages = privateChats[peerID.id], let idx = messages.firstIndex(where: { $0.id == messageID }) { privateChats[peerID.id]?[idx].deliveryStatus = .read(by: name, at: Date()) objectWillChange.send() @@ -4507,7 +4507,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let last = lastMutualToastAt[fp] ?? .distantPast if now.timeIntervalSince(last) > 60 { // 1-minute throttle lastMutualToastAt[fp] = now - let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? resolveNickname(for: peerID.id) + let name = unifiedPeerService.getPeer(by: peerID)?.nickname ?? resolveNickname(for: peerID.id) NotificationService.shared.sendLocalNotification( title: "Mutual verification", body: "You and \(name) verified each other", @@ -4533,7 +4533,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { identityManager.setVerified(fingerprint: fp, verified: true) identityManager.forceSave() verifiedFingerprints.insert(fp) - let name = unifiedPeerService.getPeer(by: peerID.id)?.nickname ?? resolveNickname(for: peerID.id) + let name = unifiedPeerService.getPeer(by: peerID)?.nickname ?? resolveNickname(for: peerID.id) NotificationService.shared.sendLocalNotification( title: "Verified", body: "You verified \(name)", @@ -4639,7 +4639,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { objectWillChange.send() // Cache mapping to full Noise key for session continuity on disconnect - if let peer = unifiedPeerService.getPeer(by: peerID.id) { + if let peer = unifiedPeerService.getPeer(by: peerID) { let noiseKeyHex = peer.noisePublicKey.hexEncodedString() shortIDToNoiseKey[peerID.id] = noiseKeyHex } @@ -5193,7 +5193,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { if selectedPrivateChatPeer == targetPeerID { isViewingThisChat = true } else if let selectedPeer = selectedPrivateChatPeer, - let selectedPeerData = unifiedPeerService.getPeer(by: selectedPeer), + let selectedPeerData = unifiedPeerService.getPeer(by: PeerID(str: selectedPeer)), let key = actualSenderNoiseKey, selectedPeerData.noisePublicKey == key { isViewingThisChat = true @@ -5458,7 +5458,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // If not a hex key, get from peer service (ephemeral ID) - if noiseKey == nil, let peer = unifiedPeerService.getPeer(by: peerID) { + if noiseKey == nil, let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { noiseKey = peer.noisePublicKey } @@ -5651,7 +5651,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { noiseKey = hexKey } else { // It's an ephemeral peer ID, get the Noise key from UnifiedPeerService - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { noiseKey = peer.noisePublicKey } } @@ -5841,7 +5841,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // IMPORTANT: Also consolidate messages from stable Noise key if this is an ephemeral peer // This ensures Nostr messages appear in BLE chats if peerID.count == 16 { // This is an ephemeral peer ID (8 bytes = 16 hex chars) - if let peer = unifiedPeerService.getPeer(by: peerID) { + if let peer = unifiedPeerService.getPeer(by: PeerID(str: peerID)) { let stableKeyHex = peer.noisePublicKey.hexEncodedString() // If we have messages stored under the stable key, merge them