geo: disable background sampling + notifications\n- Gate sampling to foreground only (beginGeohashSampling, watchers)\n- Suppress geohash activity notifications unless app is active\n- Stop sampling explicitly on background scene phase

This commit is contained in:
jack
2025-09-11 13:35:36 +02:00
parent 36b5fdabc4
commit 4894a0b04e
2 changed files with 51 additions and 32 deletions
+4
View File
@@ -61,6 +61,10 @@ struct BitchatApp: App {
// Optionally nudge Tor to dormant to save power // Optionally nudge Tor to dormant to save power
TorManager.shared.setAppForeground(false) TorManager.shared.setAppForeground(false)
TorManager.shared.goDormantOnBackground() TorManager.shared.goDormantOnBackground()
// Stop geohash sampling while backgrounded
Task { @MainActor in
chatViewModel.endGeohashSampling()
}
// Proactively disconnect Nostr to avoid spurious socket errors while Tor is down // Proactively disconnect Nostr to avoid spurious socket errors while Tor is down
NostrRelayManager.shared.disconnect() NostrRelayManager.shared.disconnect()
break break
+47 -32
View File
@@ -614,30 +614,34 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
self.cancellables.insert(cancellable) self.cancellables.insert(cancellable)
// Resubscribe geohash on relay reconnect // Resubscribe geohash on relay reconnect
if let relayMgr = self.nostrRelayManager { if let relayMgr = self.nostrRelayManager {
relayMgr.$isConnected relayMgr.$isConnected
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] connected in .sink { [weak self] connected in
guard let self = self else { return } guard let self = self else { return }
if connected { if connected {
Task { @MainActor in Task { @MainActor in
// Set up DM handler once on first connect // Set up DM handler once on first connect
if !self.nostrHandlersSetup { if !self.nostrHandlersSetup {
self.setupNostrMessageHandling() self.setupNostrMessageHandling()
self.nostrHandlersSetup = true self.nostrHandlersSetup = true
} }
self.resubscribeCurrentGeohash() self.resubscribeCurrentGeohash()
// Re-init sampling for regional + bookmarked geohashes after reconnect // Re-init sampling for regional + bookmarked geohashes after reconnect
let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash } let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash }
let bookmarks = GeohashBookmarksStore.shared.bookmarks let bookmarks = GeohashBookmarksStore.shared.bookmarks
let union = Array(Set(regional).union(bookmarks)) let union = Array(Set(regional).union(bookmarks))
if TorManager.shared.isForeground() {
self.beginGeohashSampling(for: union) self.beginGeohashSampling(for: union)
} else {
self.endGeohashSampling()
} }
} }
} }
.store(in: &self.cancellables) }
} .store(in: &self.cancellables)
}
} }
// Set up Noise encryption callbacks // Set up Noise encryption callbacks
@@ -658,7 +662,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
self.switchLocationChannel(to: LocationChannelManager.shared.selectedChannel) self.switchLocationChannel(to: LocationChannelManager.shared.selectedChannel)
} }
// Background: keep sampling nearby geohashes + bookmarks for notifications even when sheet is closed // Foreground-only: sample nearby geohashes + bookmarks (disabled in background)
LocationChannelManager.shared.$availableChannels LocationChannelManager.shared.$availableChannels
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] channels in .sink { [weak self] channels in
@@ -667,7 +671,11 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
let bookmarks = GeohashBookmarksStore.shared.bookmarks let bookmarks = GeohashBookmarksStore.shared.bookmarks
let union = Array(Set(regional).union(bookmarks)) let union = Array(Set(regional).union(bookmarks))
Task { @MainActor in Task { @MainActor in
self.beginGeohashSampling(for: union) if TorManager.shared.isForeground() {
self.beginGeohashSampling(for: union)
} else {
self.endGeohashSampling()
}
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
@@ -680,17 +688,21 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash } let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash }
let union = Array(Set(regional).union(bookmarks)) let union = Array(Set(regional).union(bookmarks))
Task { @MainActor in Task { @MainActor in
self.beginGeohashSampling(for: union) if TorManager.shared.isForeground() {
self.beginGeohashSampling(for: union)
} else {
self.endGeohashSampling()
}
} }
} }
.store(in: &cancellables) .store(in: &cancellables)
// Kick off initial sampling if we have regional channels or bookmarks // Kick off initial sampling if we have regional channels or bookmarks (foreground only)
do { do {
let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash } let regional = LocationChannelManager.shared.availableChannels.map { $0.geohash }
let bookmarks = GeohashBookmarksStore.shared.bookmarks let bookmarks = GeohashBookmarksStore.shared.bookmarks
let union = Array(Set(regional).union(bookmarks)) let union = Array(Set(regional).union(bookmarks))
if !union.isEmpty { if !union.isEmpty && TorManager.shared.isForeground() {
Task { @MainActor in self.beginGeohashSampling(for: union) } Task { @MainActor in self.beginGeohashSampling(for: union) }
} }
} }
@@ -1931,6 +1943,11 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
/// Begin sampling multiple geohashes (used by channel sheet) without changing active channel. /// Begin sampling multiple geohashes (used by channel sheet) without changing active channel.
@MainActor @MainActor
func beginGeohashSampling(for geohashes: [String]) { func beginGeohashSampling(for geohashes: [String]) {
// Disable sampling when app is backgrounded (Tor is stopped there)
if !TorManager.shared.isForeground() {
endGeohashSampling()
return
}
// Determine which to add and which to remove // Determine which to add and which to remove
let desired = Set(geohashes) let desired = Set(geohashes)
let current = Set(geoSamplingSubs.values) let current = Set(geoSamplingSubs.values)
@@ -1976,15 +1993,13 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Avoid notifications for old sampled events when launching or (re)subscribing // Avoid notifications for old sampled events when launching or (re)subscribing
let eventTime = Date(timeIntervalSince1970: TimeInterval(event.created_at)) let eventTime = Date(timeIntervalSince1970: TimeInterval(event.created_at))
if Date().timeIntervalSince(eventTime) > 30 { return } if Date().timeIntervalSince(eventTime) > 30 { return }
// Foreground policy: allow if it's a different geohash than the one currently open // Foreground-only notifications: app must be active, and not already viewing this geohash
#if os(iOS) #if os(iOS)
if UIApplication.shared.applicationState == .active { guard UIApplication.shared.applicationState == .active else { return }
if case .location(let ch) = self.activeChannel, ch.geohash == gh { return } if case .location(let ch) = self.activeChannel, ch.geohash == gh { return }
}
#elseif os(macOS) #elseif os(macOS)
if NSApplication.shared.isActive { guard NSApplication.shared.isActive else { return }
if case .location(let ch) = self.activeChannel, ch.geohash == gh { return } if case .location(let ch) = self.activeChannel, ch.geohash == gh { return }
}
#endif #endif
// Cooldown per geohash // Cooldown per geohash
let now = Date() let now = Date()