From d605a0b6dbc42b0ee63c9272e5bdc2b8946cecf6 Mon Sep 17 00:00:00 2001 From: jack Date: Fri, 1 Aug 2025 19:04:36 +0200 Subject: [PATCH] Fix crash from duplicate peer IDs in Dictionary initialization - Added deduplication logic in ChatViewModel to handle duplicate peer IDs gracefully - Enhanced PeerManager to prevent duplicate peers by tracking both nicknames and IDs - Added final safety check to ensure no duplicates in peers array - This fixes crash when Dictionary(uniqueKeysWithValues:) encounters duplicate keys --- bitchat/Models/BitchatPeer.swift | 27 +++++++++++++++++++++++++- bitchat/ViewModels/ChatViewModel.swift | 13 ++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/bitchat/Models/BitchatPeer.swift b/bitchat/Models/BitchatPeer.swift index b3ee9f46..8de8271e 100644 --- a/bitchat/Models/BitchatPeer.swift +++ b/bitchat/Models/BitchatPeer.swift @@ -143,6 +143,7 @@ class PeerManager: ObservableObject { // Build peer list var allPeers: [BitchatPeer] = [] var connectedNicknames: Set = [] + var addedPeerIDs: Set = [] // Add connected mesh peers (only if actually connected or relay connected) for (peerID, nickname) in meshPeers { @@ -180,6 +181,9 @@ class PeerManager: ObservableObject { connectedNicknames.insert(nickname) } + // Track that we've added this peer ID + addedPeerIDs.insert(peerID) + var peer = BitchatPeer( id: peerID, noisePublicKey: noiseKey, @@ -221,6 +225,13 @@ class PeerManager: ObservableObject { continue } + // Skip if we already added a peer with this ID (prevents duplicates) + if addedPeerIDs.contains(favoriteID) { + SecureLogger.log(" - Skipping '\(favorite.peerNickname)' - peer ID already added", + category: SecureLogger.session, level: .debug) + continue + } + // Only add peers that WE favorite (not just ones who favorite us) if !favorite.isFavorite { SecureLogger.log(" - Skipping '\(favorite.peerNickname)' - we don't favorite them (they favorite us: \(favorite.theyFavoritedUs))", @@ -241,6 +252,7 @@ class PeerManager: ObservableObject { // Set favorite status peer.favoriteStatus = favorite peer.nostrPublicKey = favorite.peerNostrPublicKey + addedPeerIDs.insert(favoriteID) // Track that we've added this ID allPeers.append(peer) } @@ -287,7 +299,20 @@ class PeerManager: ObservableObject { } } - self.peers = allPeers + // Final safety check: ensure no duplicate IDs + var finalPeers: [BitchatPeer] = [] + var seenIDs: Set = [] + for peer in allPeers { + if !seenIDs.contains(peer.id) { + seenIDs.insert(peer.id) + finalPeers.append(peer) + } else { + SecureLogger.log("⚠️ Removing duplicate peer ID in final check: \(peer.id) (\(peer.displayName))", + category: SecureLogger.session, level: .warning) + } + } + + self.peers = finalPeers self.favorites = favorites self.mutualFavorites = mutualFavorites diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 5f113df6..4aafcf43 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -220,7 +220,18 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Update peers directly self?.allPeers = peers // Update peer index for O(1) lookups - self?.peerIndex = Dictionary(uniqueKeysWithValues: peers.map { ($0.id, $0) }) + // Deduplicate peers by ID to prevent crash from duplicate keys + var uniquePeers: [String: PeerData] = [:] + for peer in peers { + // Keep the first occurrence of each peer ID + if uniquePeers[peer.id] == nil { + uniquePeers[peer.id] = peer + } else { + SecureLogger.log("⚠️ Duplicate peer ID detected: \(peer.id) (\(peer.displayName))", + category: SecureLogger.session, level: .warning) + } + } + self?.peerIndex = uniquePeers // Schedule UI update if peers changed if peers.count > 0 || self?.allPeers.count ?? 0 > 0 { // UI will update automatically