mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:05:20 +00:00
fix: Rate limit iOS peer notifications to prevent flood (#972)
* fix: Rate limit iOS peer notifications to prevent flood - Remove aggressive formIntersection that cleared recentlySeenPeers when peers temporarily dropped, causing them to be treated as "new" - Add 5-minute cooldown between notifications (aligns with Android) - Use fixed notification identifier so iOS updates existing notification instead of creating new ones - Only mark peers as seen when notification is sent, so peers arriving during cooldown are included in the next notification Fixes notification spam every 10-30 seconds when peers fluctuate. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: Bump version to 1.5.1 --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
jack
Claude Opus 4.5
parent
6206184862
commit
90a5e8ba9d
Generated
+4
-4
@@ -558,7 +558,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = 1.5.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -618,7 +618,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = 1.5.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -654,7 +654,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
|
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = 1.5.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
@@ -746,7 +746,7 @@
|
|||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
|
MACOSX_DEPLOYMENT_TARGET = "$(MACOSX_DEPLOYMENT_TARGET)";
|
||||||
MARKETING_VERSION = "$(MARKETING_VERSION)";
|
MARKETING_VERSION = 1.5.1;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = bitchat;
|
PRODUCT_NAME = bitchat;
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ final class NotificationService {
|
|||||||
func sendNetworkAvailableNotification(peerCount: Int) {
|
func sendNetworkAvailableNotification(peerCount: Int) {
|
||||||
let title = "👥 bitchatters nearby!"
|
let title = "👥 bitchatters nearby!"
|
||||||
let body = peerCount == 1 ? "1 person around" : "\(peerCount) people around"
|
let body = peerCount == 1 ? "1 person around" : "\(peerCount) people around"
|
||||||
let identifier = "network-available-\(Date().timeIntervalSince1970)"
|
// Fixed identifier so iOS updates the existing notification instead of creating new ones
|
||||||
|
let identifier = "network-available"
|
||||||
|
|
||||||
sendLocalNotification(
|
sendLocalNotification(
|
||||||
title: title,
|
title: title,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ enum TransportConfig {
|
|||||||
|
|
||||||
// Timers
|
// Timers
|
||||||
static let networkResetGraceSeconds: TimeInterval = 600 // 10 minutes
|
static let networkResetGraceSeconds: TimeInterval = 600 // 10 minutes
|
||||||
|
static let networkNotificationCooldownSeconds: TimeInterval = 300 // 5 minutes
|
||||||
static let basePublicFlushInterval: TimeInterval = 0.08 // ~12.5 fps batching
|
static let basePublicFlushInterval: TimeInterval = 0.08 // ~12.5 fps batching
|
||||||
|
|
||||||
// BLE duty/announce/connect
|
// BLE duty/announce/connect
|
||||||
|
|||||||
@@ -3348,18 +3348,25 @@ final class ChatViewModel: ObservableObject, BitchatDelegate, CommandContextProv
|
|||||||
self.scheduleNetworkEmptyTimer()
|
self.scheduleNetworkEmptyTimer()
|
||||||
} else {
|
} else {
|
||||||
self.invalidateNetworkEmptyTimer()
|
self.invalidateNetworkEmptyTimer()
|
||||||
// Trim out peers we no longer observe before comparing for new arrivals
|
// Don't trim recentlySeenPeers here - let timers handle cleanup.
|
||||||
self.recentlySeenPeers.formIntersection(meshPeerSet)
|
// Trimming immediately causes peers to be treated as "new" when they
|
||||||
|
// briefly drop and reconnect, triggering notification floods.
|
||||||
let newPeers = meshPeerSet.subtracting(self.recentlySeenPeers)
|
let newPeers = meshPeerSet.subtracting(self.recentlySeenPeers)
|
||||||
|
|
||||||
if !newPeers.isEmpty {
|
if !newPeers.isEmpty {
|
||||||
self.lastNetworkNotificationTime = Date()
|
// Rate limit: max one notification per 5 minutes
|
||||||
self.recentlySeenPeers.formUnion(newPeers)
|
let cooldown = TransportConfig.networkNotificationCooldownSeconds
|
||||||
NotificationService.shared.sendNetworkAvailableNotification(peerCount: meshPeers.count)
|
if Date().timeIntervalSince(self.lastNetworkNotificationTime) >= cooldown {
|
||||||
SecureLogger.info(
|
// Only mark peers as seen when we actually notify about them
|
||||||
"👥 Sent bitchatters nearby notification for \(meshPeers.count) mesh peers (new: \(newPeers.count))",
|
// This ensures peers arriving during cooldown will be included in the next notification
|
||||||
category: .session
|
self.recentlySeenPeers.formUnion(newPeers)
|
||||||
)
|
self.lastNetworkNotificationTime = Date()
|
||||||
|
NotificationService.shared.sendNetworkAvailableNotification(peerCount: meshPeers.count)
|
||||||
|
SecureLogger.info(
|
||||||
|
"👥 Sent bitchatters nearby notification for \(meshPeers.count) mesh peers (new: \(newPeers.count))",
|
||||||
|
category: .session
|
||||||
|
)
|
||||||
|
}
|
||||||
self.scheduleNetworkResetTimer()
|
self.scheduleNetworkResetTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user