From c39df29b25c30751a1df0cad20a1695110f518a6 Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 25 Aug 2025 20:17:09 +0200 Subject: [PATCH] chore: centralize location + geohash constants (filters, lookback, relay count) and adopt in LocationChannelManager/ChatViewModel --- bitchat/Services/BLEService.swift | 18 ++++++++------- bitchat/Services/LocationChannelManager.swift | 4 ++-- bitchat/Services/NostrTransport.swift | 10 +------- bitchat/Services/TransportConfig.swift | 20 ++++++++++++++++ bitchat/ViewModels/ChatViewModel.swift | 23 +++++++++++-------- 5 files changed, 47 insertions(+), 28 deletions(-) diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 0585167a..1009de49 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -346,7 +346,9 @@ final class BLEService: NSObject { // Single maintenance timer for all periodic tasks (dispatch-based for determinism) let timer = DispatchSource.makeTimerSource(queue: bleQueue) - timer.schedule(deadline: .now() + 10.0, repeating: 10.0, leeway: .seconds(1)) + timer.schedule(deadline: .now() + TransportConfig.bleMaintenanceInterval, + repeating: TransportConfig.bleMaintenanceInterval, + leeway: .seconds(TransportConfig.bleMaintenanceLeewaySeconds)) timer.setEventHandler { [weak self] in self?.performMaintenance() } @@ -1912,10 +1914,10 @@ final class BLEService: NSObject { if lastIsolatedAt == nil { lastIsolatedAt = Date() } let iso = lastIsolatedAt ?? Date() let elapsed = Date().timeIntervalSince(iso) - if elapsed > 60 { - dynamicRSSIThreshold = -92 + if elapsed > TransportConfig.bleIsolationRelaxThresholdSeconds { + dynamicRSSIThreshold = TransportConfig.bleRSSIIsolatedRelaxed } else { - dynamicRSSIThreshold = -90 + dynamicRSSIThreshold = TransportConfig.bleRSSIIsolatedBase } return } @@ -1925,12 +1927,12 @@ final class BLEService: NSObject { // If we're at budget or queue is large, prefer closer peers let linkCount = peripherals.values.filter { $0.isConnected || $0.isConnecting }.count if linkCount >= maxCentralLinks || connectionCandidates.count > TransportConfig.bleConnectionCandidatesMax { - threshold = -85 + threshold = TransportConfig.bleRSSIConnectedThreshold } // If we have many recent timeouts, raise further - let recentTimeouts = recentConnectTimeouts.filter { Date().timeIntervalSince($0.value) < 60 }.count - if recentTimeouts >= 3 { - threshold = max(threshold, -80) + let recentTimeouts = recentConnectTimeouts.filter { Date().timeIntervalSince($0.value) < TransportConfig.bleRecentTimeoutWindowSeconds }.count + if recentTimeouts >= TransportConfig.bleRecentTimeoutCountThreshold { + threshold = max(threshold, TransportConfig.bleRSSIHighTimeoutThreshold) } dynamicRSSIThreshold = threshold } diff --git a/bitchat/Services/LocationChannelManager.swift b/bitchat/Services/LocationChannelManager.swift index e33d9671..64e992a5 100644 --- a/bitchat/Services/LocationChannelManager.swift +++ b/bitchat/Services/LocationChannelManager.swift @@ -39,7 +39,7 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa super.init() cl.delegate = self cl.desiredAccuracy = kCLLocationAccuracyHundredMeters - cl.distanceFilter = 1000 // meters; we're not tracking continuously + cl.distanceFilter = TransportConfig.locationDistanceFilterMeters // meters; we're not tracking continuously // Load selection if let data = UserDefaults.standard.data(forKey: userDefaultsKey), let channel = try? JSONDecoder().decode(ChannelID.self, from: data) { @@ -93,7 +93,7 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa } /// Begin periodic one-shot location refreshes while a selector UI is visible. - func beginLiveRefresh(interval: TimeInterval = 5.0) { + func beginLiveRefresh(interval: TimeInterval = TransportConfig.locationLiveRefreshInterval) { guard permissionState == .authorized else { return } // Switch to a lightweight periodic one-shot request (polling) while the sheet is open refreshTimer?.invalidate() diff --git a/bitchat/Services/NostrTransport.swift b/bitchat/Services/NostrTransport.swift index 225b3ae0..b19a6924 100644 --- a/bitchat/Services/NostrTransport.swift +++ b/bitchat/Services/NostrTransport.swift @@ -177,15 +177,7 @@ final class NostrTransport: Transport { func sendBroadcastAnnounce() { /* no-op for Nostr */ } func sendDeliveryAck(for messageID: String, to peerID: String) { Task { @MainActor in - var recipientNostrPubkey: String? - if let noiseKey = Data(hexString: peerID), - let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: noiseKey) { - recipientNostrPubkey = fav.peerNostrPublicKey - } - if recipientNostrPubkey == nil, peerID.count == 16 { - recipientNostrPubkey = FavoritesPersistenceService.shared.getFavoriteStatus(forPeerID: peerID)?.peerNostrPublicKey - } - guard let recipientNpub = recipientNostrPubkey else { return } + guard let recipientNpub = resolveRecipientNpub(for: peerID) else { return } guard let senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() else { return } SecureLogger.log("NostrTransport: preparing DELIVERED ack for id=\(messageID.prefix(8))… to \(recipientNpub.prefix(16))…", category: SecureLogger.session, level: .debug) diff --git a/bitchat/Services/TransportConfig.swift b/bitchat/Services/TransportConfig.swift index b471057b..fa22f5c9 100644 --- a/bitchat/Services/TransportConfig.swift +++ b/bitchat/Services/TransportConfig.swift @@ -37,4 +37,24 @@ enum TransportConfig { // UI thresholds static let uiLateInsertThreshold: TimeInterval = 15.0 static let uiProcessedNostrEventsCap: Int = 2000 + + // BLE maintenance & thresholds + static let bleMaintenanceInterval: TimeInterval = 10.0 + static let bleMaintenanceLeewaySeconds: Int = 1 + static let bleIsolationRelaxThresholdSeconds: TimeInterval = 60 + static let bleRecentTimeoutWindowSeconds: TimeInterval = 60 + static let bleRecentTimeoutCountThreshold: Int = 3 + static let bleRSSIIsolatedBase: Int = -90 + static let bleRSSIIsolatedRelaxed: Int = -92 + static let bleRSSIConnectedThreshold: Int = -85 + static let bleRSSIHighTimeoutThreshold: Int = -80 + + // Location + static let locationDistanceFilterMeters: Double = 1000 + static let locationLiveRefreshInterval: TimeInterval = 5.0 + + // Nostr geohash + static let nostrGeohashInitialLookbackSeconds: TimeInterval = 3600 + static let nostrGeohashInitialLimit: Int = 200 + static let nostrGeoRelayCount: Int = 5 } diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 06431d31..6ab387c5 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -707,8 +707,15 @@ class ChatViewModel: ObservableObject, BitchatDelegate { startGeoParticipantsTimer() // Unsubscribe + resubscribe NostrRelayManager.shared.unsubscribe(id: subID) - let filter = NostrFilter.geohashEphemeral(ch.geohash, since: Date().addingTimeInterval(-3600), limit: 200) - let subRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5) + let filter = NostrFilter.geohashEphemeral( + ch.geohash, + since: Date().addingTimeInterval(-TransportConfig.nostrGeohashInitialLookbackSeconds), + limit: TransportConfig.nostrGeohashInitialLimit + ) + let subRelays = GeoRelayDirectory.shared.closestRelays( + toGeohash: ch.geohash, + count: TransportConfig.nostrGeoRelayCount + ) NostrRelayManager.shared.subscribe(filter: filter, id: subID, relayUrls: subRelays) { [weak self] event in guard let self = self else { return } guard event.kind == NostrProtocol.EventKind.ephemeralEvent.rawValue else { return } @@ -1264,7 +1271,10 @@ class ChatViewModel: ObservableObject, BitchatDelegate { nickname: self.nickname, teleported: LocationChannelManager.shared.teleported ) - let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5) + let targetRelays = GeoRelayDirectory.shared.closestRelays( + toGeohash: ch.geohash, + count: TransportConfig.nostrGeoRelayCount + ) if targetRelays.isEmpty { SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning) } else { @@ -2450,12 +2460,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { ) let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5) if targetRelays.isEmpty { - let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5) - if targetRelays.isEmpty { - SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning) - } else { - NostrRelayManager.shared.sendEvent(event, to: targetRelays) - } + SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning) } else { NostrRelayManager.shared.sendEvent(event, to: targetRelays) }