diff --git a/bitchat.xcodeproj/project.pbxproj b/bitchat.xcodeproj/project.pbxproj index 41f0019b..d14e89fd 100644 --- a/bitchat.xcodeproj/project.pbxproj +++ b/bitchat.xcodeproj/project.pbxproj @@ -328,7 +328,6 @@ A2E8C336FA1ADBEC03261DFD /* bitchatShareExtension */, C3D98EB3E1B455E321F519F4 /* bitchatTests */, 9F37F9F2C353B58AC809E93B /* Products */, - 048A4BE52E5CCC5C00162C4A /* Recovered References */, ); sourceTree = ""; }; @@ -743,14 +742,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */, - 1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */, + 048A4BE72E5CCCC300162C4A /* TransportConfig.swift in Sources */, + 1234567890ABCDEFFEDCBA13 /* PeerDisplayNameResolver.swift in Sources */, AA77BB12CC22DD33EE44FF56 /* VerificationService.swift in Sources */, AA77BB15CC22DD33EE44FF59 /* VerificationViews.swift in Sources */, A1B2C3D54E5F60718293A4B6 /* XChaCha20Poly1305Compat.swift in Sources */, AD11E46940D742AEAF547EB2 /* AppInfoView.swift in Sources */, 9B51E9B63A3EA59B1A7874BD /* BinaryEncodingUtils.swift in Sources */, - 049BD3B42E51F319001A566B /* NostrTransport.swift in Sources */, 049BD3B52E51F319001A566B /* MessageRouter.swift in Sources */, 4B747085D07A1BCE0F5BA612 /* BinaryProtocol.swift in Sources */, @@ -803,14 +801,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */, - 1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */, + 048A4BE82E5CCCC300162C4A /* TransportConfig.swift in Sources */, + 1234567890ABCDEFFEDCBA14 /* PeerDisplayNameResolver.swift in Sources */, AA77BB11CC22DD33EE44FF55 /* VerificationService.swift in Sources */, AA77BB14CC22DD33EE44FF58 /* VerificationViews.swift in Sources */, A1B2C3D44E5F60718293A4B5 /* XChaCha20Poly1305Compat.swift in Sources */, ABAF130D88561F4A646F0430 /* AppInfoView.swift in Sources */, AFB6AEFCABBE97441CB3102B /* BinaryEncodingUtils.swift in Sources */, - 049BD3B22E51F319001A566B /* NostrTransport.swift in Sources */, 049BD3B32E51F319001A566B /* MessageRouter.swift in Sources */, F455F011B3B648ADA233F998 /* BinaryProtocol.swift in Sources */, @@ -1007,7 +1004,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; @@ -1038,7 +1035,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; SDKROOT = iphoneos; @@ -1093,7 +1090,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; SDKROOT = iphoneos; @@ -1125,7 +1122,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.1; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; REGISTER_APP_GROUPS = YES; @@ -1214,7 +1211,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.3.1; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat; PRODUCT_NAME = bitchat; REGISTER_APP_GROUPS = YES; @@ -1307,7 +1304,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.3.2; + MARKETING_VERSION = 1.3.3; PRODUCT_BUNDLE_IDENTIFIER = chat.bitchat.ShareExtension; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 6389d839..46a1412e 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -128,9 +128,13 @@ final class BLEService: NSObject { // Backpressure-aware write queue per peripheral private var pendingPeripheralWrites: [String: [Data]] = [:] + // Debounce duplicate disconnect notifies + private var recentDisconnectNotifies: [String: Date] = [:] // Store-and-forward for directed messages when we have no links // Keyed by recipient short peerID -> messageID -> (packet, enqueuedAt) private var pendingDirectedRelays: [String: [String: (packet: BitchatPacket, enqueuedAt: Date)]] = [:] + // Debounce for 'reconnected' logs + private var lastReconnectLogAt: [String: Date] = [:] // MARK: - Maintenance Timer @@ -1494,12 +1498,19 @@ final class BLEService: NSObject { ) } - // Log connection status only for direct connectivity changes to avoid flapping on relayed announces + // Log connection status only for direct connectivity changes; debounce to reduce spam if isDirectAnnounce || hasPeripheralConnection || hasCentralSubscription { + let now = Date() if existingPeer == nil { SecureLogger.log("🆕 New peer: \(announcement.nickname)", category: SecureLogger.session, level: .debug) } else if wasDisconnected { - SecureLogger.log("🔄 Peer \(announcement.nickname) reconnected", category: SecureLogger.session, level: .debug) + // Debounce 'reconnected' logs within short window + if let last = lastReconnectLogAt[peerID], now.timeIntervalSince(last) < TransportConfig.bleReconnectLogDebounceSeconds { + // Skip duplicate log + } else { + SecureLogger.log("🔄 Peer \(announcement.nickname) reconnected", category: SecureLogger.session, level: .debug) + lastReconnectLogAt[peerID] = now + } } else if existingPeer?.nickname != announcement.nickname { SecureLogger.log("🔄 Peer \(peerID) changed nickname: \(existingPeer?.nickname ?? "Unknown") -> \(announcement.nickname)", category: SecureLogger.session, level: .debug) } @@ -1897,6 +1908,18 @@ final class BLEService: NSObject { } } } + + // Debounced disconnect notifier to avoid duplicate disconnect callbacks within a short window + private func notifyPeerDisconnectedDebounced(_ peerID: String) { + let now = Date() + let last = recentDisconnectNotifies[peerID] + if last == nil || now.timeIntervalSince(last!) >= TransportConfig.bleDisconnectNotifyDebounceSeconds { + delegate?.didDisconnectFromPeer(peerID) + recentDisconnectNotifies[peerID] = now + } else { + // Suppressed duplicate disconnect notification + } + } // NEW: Publish peer snapshots to subscribers and notify Transport delegates private func publishFullPeerData() { @@ -2422,7 +2445,7 @@ func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeriph let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } if let peerID = peerID { - self.delegate?.didDisconnectFromPeer(peerID) + self.notifyPeerDisconnectedDebounced(peerID) } self.publishFullPeerData() self.delegate?.didUpdatePeerList(currentPeerIDs) @@ -2474,6 +2497,18 @@ extension BLEService { guard candidate.isConnectable else { return } let peripheral = candidate.peripheral let peripheralID = peripheral.identifier.uuidString + // Weak-link cooldown: if we recently timed out and RSSI is very weak, delay retries + if let lastTO = recentConnectTimeouts[peripheralID] { + let elapsed = Date().timeIntervalSince(lastTO) + if elapsed < TransportConfig.bleWeakLinkCooldownSeconds && candidate.rssi <= TransportConfig.bleWeakLinkRSSICutoff { + // Requeue the candidate and try again later + connectionCandidates.append(candidate) + let remaining = TransportConfig.bleWeakLinkCooldownSeconds - elapsed + let delay = min(max(2.0, remaining), 15.0) + bleQueue.asyncAfter(deadline: .now() + delay) { [weak self] in self?.tryConnectFromQueue() } + return + } + } if peripherals[peripheralID]?.isConnected == true || peripherals[peripheralID]?.isConnecting == true { // Already in progress; skip bleQueue.async { [weak self] in self?.tryConnectFromQueue() } @@ -2811,7 +2846,7 @@ extension BLEService: CBPeripheralManagerDelegate { // Get current peer list (after removal) let currentPeerIDs = self.collectionsQueue.sync { Array(self.peers.keys) } - self.delegate?.didDisconnectFromPeer(peerID) + self.notifyPeerDisconnectedDebounced(peerID) self.delegate?.didUpdatePeerList(currentPeerIDs) } } diff --git a/bitchat/Services/TransportConfig.swift b/bitchat/Services/TransportConfig.swift index ee53806a..4b34bb7a 100644 --- a/bitchat/Services/TransportConfig.swift +++ b/bitchat/Services/TransportConfig.swift @@ -143,6 +143,14 @@ enum TransportConfig { // Store-and-forward for directed packets at relays static let bleDirectedSpoolWindowSeconds: TimeInterval = 15.0 + // Log/UI debounce windows + static let bleDisconnectNotifyDebounceSeconds: TimeInterval = 1.5 + static let bleReconnectLogDebounceSeconds: TimeInterval = 2.0 + + // Weak-link cooldown after connection timeouts + static let bleWeakLinkCooldownSeconds: TimeInterval = 30.0 + static let bleWeakLinkRSSICutoff: Int = -90 + // Content hashing / formatting static let contentKeyPrefixLength: Int = 256 static let uiLongMessageLengthThreshold: Int = 2000