Add anti-spam protection for network availability notifications

- 10 minute cooldown between notifications
- 5 minute hysteresis before resetting notification flag after network becomes empty
- Prevents spam when peers briefly disconnect/reconnect
- Emergency disconnect immediately resets all notification state
This commit is contained in:
jack
2025-07-22 16:35:28 +02:00
parent 79d9f8ee88
commit 3194da8f85
+55 -10
View File
@@ -82,6 +82,10 @@ class BluetoothMeshService: NSObject {
private var announcedToPeers = Set<String>() // Track which peers we've announced to
private var announcedPeers = Set<String>() // Track peers who have already been announced
private var hasNotifiedNetworkAvailable = false // Track if we've notified about network availability
private var lastNetworkNotificationTime: Date? // Track when we last sent a network notification
private var networkBecameEmptyTime: Date? // Track when the network became empty
private let networkNotificationCooldown: TimeInterval = 600 // 10 minutes between notifications
private let networkEmptyResetDelay: TimeInterval = 300 // 5 minutes before resetting notification flag
private var intentionalDisconnects = Set<String>() // Track peripherals we're disconnecting intentionally
private var peerLastSeenTimestamps = LRUCache<String, Date>(maxSize: 100) // Bounded cache for peer timestamps
private var cleanupTimer: Timer? // Timer to clean up stale peers
@@ -600,7 +604,8 @@ class BluetoothMeshService: NSObject {
activePeers.removeAll()
}
announcedPeers.removeAll()
hasNotifiedNetworkAvailable = false
// For normal disconnect, respect the timing
networkBecameEmptyTime = Date()
// Clear announcement tracking
announcedToPeers.removeAll()
@@ -1233,7 +1238,10 @@ class BluetoothMeshService: NSObject {
peripheralRSSI.removeAll()
announcedToPeers.removeAll()
announcedPeers.removeAll()
// For emergency/panic, reset immediately
hasNotifiedNetworkAvailable = false
networkBecameEmptyTime = nil
lastNetworkNotificationTime = nil
processedMessages.removeAll()
incomingFragments.removeAll()
fragmentMetadata.removeAll()
@@ -1335,10 +1343,27 @@ class BluetoothMeshService: NSObject {
if !peersToRemove.isEmpty {
notifyPeerListUpdate()
// Reset notification flag if network is now empty
// Mark when network became empty, but don't reset flag immediately
let currentNetworkSize = collectionsQueue.sync { activePeers.count }
if currentNetworkSize == 0 && networkBecameEmptyTime == nil {
networkBecameEmptyTime = Date()
}
}
// Check if we should reset the notification flag
if let emptyTime = networkBecameEmptyTime {
let currentNetworkSize = collectionsQueue.sync { activePeers.count }
if currentNetworkSize == 0 {
hasNotifiedNetworkAvailable = false
// Network is still empty, check if enough time has passed
let timeSinceEmpty = Date().timeIntervalSince(emptyTime)
if timeSinceEmpty >= networkEmptyResetDelay {
// Reset the flag after network has been empty for the delay period
hasNotifiedNetworkAvailable = false
// Keep the empty time set so we don't immediately notify again
}
} else {
// Network is no longer empty, clear the empty time
networkBecameEmptyTime = nil
}
}
}
@@ -2088,11 +2113,31 @@ class BluetoothMeshService: NSObject {
}
self.notifyPeerListUpdate(immediate: true)
// Send network available notification if this is the first peer we've seen
// Send network available notification if appropriate
let currentNetworkSize = collectionsQueue.sync { self.activePeers.count }
if currentNetworkSize > 0 && !hasNotifiedNetworkAvailable {
hasNotifiedNetworkAvailable = true
NotificationService.shared.sendNetworkAvailableNotification(peerCount: currentNetworkSize)
if currentNetworkSize > 0 {
// Clear empty time since network is active
networkBecameEmptyTime = nil
if !hasNotifiedNetworkAvailable {
// Check if enough time has passed since last notification
let now = Date()
var shouldSendNotification = true
if let lastNotification = lastNetworkNotificationTime {
let timeSinceLastNotification = now.timeIntervalSince(lastNotification)
if timeSinceLastNotification < networkNotificationCooldown {
// Too soon to send another notification
shouldSendNotification = false
}
}
if shouldSendNotification {
hasNotifiedNetworkAvailable = true
lastNetworkNotificationTime = now
NotificationService.shared.sendNetworkAvailableNotification(peerCount: currentNetworkSize)
}
}
}
DispatchQueue.main.async {
@@ -2945,10 +2990,10 @@ extension BluetoothMeshService: CBCentralManagerDelegate {
self.delegate?.didDisconnectFromPeer(peerID)
}
// Reset notification flag if network is now empty
// Mark when network became empty, but don't reset flag immediately
let currentNetworkSize = collectionsQueue.sync { activePeers.count }
if currentNetworkSize == 0 {
hasNotifiedNetworkAvailable = false
if currentNetworkSize == 0 && networkBecameEmptyTime == nil {
networkBecameEmptyTime = Date()
}
}
self.notifyPeerListUpdate()