mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 20:05:19 +00:00
chore: centralize location + geohash constants (filters, lookback, relay count) and adopt in LocationChannelManager/ChatViewModel
This commit is contained in:
@@ -346,7 +346,9 @@ final class BLEService: NSObject {
|
|||||||
|
|
||||||
// Single maintenance timer for all periodic tasks (dispatch-based for determinism)
|
// Single maintenance timer for all periodic tasks (dispatch-based for determinism)
|
||||||
let timer = DispatchSource.makeTimerSource(queue: bleQueue)
|
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
|
timer.setEventHandler { [weak self] in
|
||||||
self?.performMaintenance()
|
self?.performMaintenance()
|
||||||
}
|
}
|
||||||
@@ -1912,10 +1914,10 @@ final class BLEService: NSObject {
|
|||||||
if lastIsolatedAt == nil { lastIsolatedAt = Date() }
|
if lastIsolatedAt == nil { lastIsolatedAt = Date() }
|
||||||
let iso = lastIsolatedAt ?? Date()
|
let iso = lastIsolatedAt ?? Date()
|
||||||
let elapsed = Date().timeIntervalSince(iso)
|
let elapsed = Date().timeIntervalSince(iso)
|
||||||
if elapsed > 60 {
|
if elapsed > TransportConfig.bleIsolationRelaxThresholdSeconds {
|
||||||
dynamicRSSIThreshold = -92
|
dynamicRSSIThreshold = TransportConfig.bleRSSIIsolatedRelaxed
|
||||||
} else {
|
} else {
|
||||||
dynamicRSSIThreshold = -90
|
dynamicRSSIThreshold = TransportConfig.bleRSSIIsolatedBase
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1925,12 +1927,12 @@ final class BLEService: NSObject {
|
|||||||
// If we're at budget or queue is large, prefer closer peers
|
// If we're at budget or queue is large, prefer closer peers
|
||||||
let linkCount = peripherals.values.filter { $0.isConnected || $0.isConnecting }.count
|
let linkCount = peripherals.values.filter { $0.isConnected || $0.isConnecting }.count
|
||||||
if linkCount >= maxCentralLinks || connectionCandidates.count > TransportConfig.bleConnectionCandidatesMax {
|
if linkCount >= maxCentralLinks || connectionCandidates.count > TransportConfig.bleConnectionCandidatesMax {
|
||||||
threshold = -85
|
threshold = TransportConfig.bleRSSIConnectedThreshold
|
||||||
}
|
}
|
||||||
// If we have many recent timeouts, raise further
|
// If we have many recent timeouts, raise further
|
||||||
let recentTimeouts = recentConnectTimeouts.filter { Date().timeIntervalSince($0.value) < 60 }.count
|
let recentTimeouts = recentConnectTimeouts.filter { Date().timeIntervalSince($0.value) < TransportConfig.bleRecentTimeoutWindowSeconds }.count
|
||||||
if recentTimeouts >= 3 {
|
if recentTimeouts >= TransportConfig.bleRecentTimeoutCountThreshold {
|
||||||
threshold = max(threshold, -80)
|
threshold = max(threshold, TransportConfig.bleRSSIHighTimeoutThreshold)
|
||||||
}
|
}
|
||||||
dynamicRSSIThreshold = threshold
|
dynamicRSSIThreshold = threshold
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ final class LocationChannelManager: NSObject, CLLocationManagerDelegate, Observa
|
|||||||
super.init()
|
super.init()
|
||||||
cl.delegate = self
|
cl.delegate = self
|
||||||
cl.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
cl.desiredAccuracy = kCLLocationAccuracyHundredMeters
|
||||||
cl.distanceFilter = 1000 // meters; we're not tracking continuously
|
cl.distanceFilter = TransportConfig.locationDistanceFilterMeters // meters; we're not tracking continuously
|
||||||
// Load selection
|
// Load selection
|
||||||
if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
|
if let data = UserDefaults.standard.data(forKey: userDefaultsKey),
|
||||||
let channel = try? JSONDecoder().decode(ChannelID.self, from: data) {
|
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.
|
/// 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 }
|
guard permissionState == .authorized else { return }
|
||||||
// Switch to a lightweight periodic one-shot request (polling) while the sheet is open
|
// Switch to a lightweight periodic one-shot request (polling) while the sheet is open
|
||||||
refreshTimer?.invalidate()
|
refreshTimer?.invalidate()
|
||||||
|
|||||||
@@ -177,15 +177,7 @@ final class NostrTransport: Transport {
|
|||||||
func sendBroadcastAnnounce() { /* no-op for Nostr */ }
|
func sendBroadcastAnnounce() { /* no-op for Nostr */ }
|
||||||
func sendDeliveryAck(for messageID: String, to peerID: String) {
|
func sendDeliveryAck(for messageID: String, to peerID: String) {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
var recipientNostrPubkey: String?
|
guard let recipientNpub = resolveRecipientNpub(for: peerID) else { return }
|
||||||
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 senderIdentity = try? NostrIdentityBridge.getCurrentNostrIdentity() 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))…",
|
SecureLogger.log("NostrTransport: preparing DELIVERED ack for id=\(messageID.prefix(8))… to \(recipientNpub.prefix(16))…",
|
||||||
category: SecureLogger.session, level: .debug)
|
category: SecureLogger.session, level: .debug)
|
||||||
|
|||||||
@@ -37,4 +37,24 @@ enum TransportConfig {
|
|||||||
// UI thresholds
|
// UI thresholds
|
||||||
static let uiLateInsertThreshold: TimeInterval = 15.0
|
static let uiLateInsertThreshold: TimeInterval = 15.0
|
||||||
static let uiProcessedNostrEventsCap: Int = 2000
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -707,8 +707,15 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
startGeoParticipantsTimer()
|
startGeoParticipantsTimer()
|
||||||
// Unsubscribe + resubscribe
|
// Unsubscribe + resubscribe
|
||||||
NostrRelayManager.shared.unsubscribe(id: subID)
|
NostrRelayManager.shared.unsubscribe(id: subID)
|
||||||
let filter = NostrFilter.geohashEphemeral(ch.geohash, since: Date().addingTimeInterval(-3600), limit: 200)
|
let filter = NostrFilter.geohashEphemeral(
|
||||||
let subRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5)
|
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
|
NostrRelayManager.shared.subscribe(filter: filter, id: subID, relayUrls: subRelays) { [weak self] event in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard event.kind == NostrProtocol.EventKind.ephemeralEvent.rawValue else { return }
|
guard event.kind == NostrProtocol.EventKind.ephemeralEvent.rawValue else { return }
|
||||||
@@ -1264,7 +1271,10 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
nickname: self.nickname,
|
nickname: self.nickname,
|
||||||
teleported: LocationChannelManager.shared.teleported
|
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 {
|
if targetRelays.isEmpty {
|
||||||
SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning)
|
SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning)
|
||||||
} else {
|
} else {
|
||||||
@@ -2450,12 +2460,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
|
|||||||
)
|
)
|
||||||
let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5)
|
let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5)
|
||||||
if targetRelays.isEmpty {
|
if targetRelays.isEmpty {
|
||||||
let targetRelays = GeoRelayDirectory.shared.closestRelays(toGeohash: ch.geohash, count: 5)
|
SecureLogger.log("Geo: no geohash relays available for \(ch.geohash); not sending", category: SecureLogger.session, level: .warning)
|
||||||
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)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
NostrRelayManager.shared.sendEvent(event, to: targetRelays)
|
NostrRelayManager.shared.sendEvent(event, to: targetRelays)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user