Refactor/robustness (#446)

* Refactor BitChat for improved robustness and performance

Major refactoring to simplify architecture and fix critical issues:

Architecture Improvements:
- Replace complex BluetoothMeshService with simplified SimplifiedBluetoothService
- Consolidate message routing and peer management into unified services
- Remove redundant caching layers and optimize performance

Bug Fixes:
- Fix critical BLE peer mapping corruption in mesh networks
- Fix encrypted message routing failures in multi-peer scenarios
- Fix app freezes and Main Thread Checker warnings
- Fix BLE message delivery in dual-role connections
- Fix favorite toggle UI not updating instantly
- Fix Nostr offline messaging with 24-hour message filtering

Features:
- Add command processor for chat commands
- Add autocomplete service for mentions and commands
- Improve private chat management with dedicated service
- Add unified peer service for consistent state management

Performance:
- Optimize BLE reconnection speed
- Reduce excessive logging throughout codebase
- Improve message deduplication efficiency
- Optimize UI updates and state management

* Improvements refactor robust (#441)

* remove unused code

* remove more

* TLV for announcement

* restore

* restore

* restore?

* messages tlv too (#442)

* Fix Nostr notification and read receipt issues, add TLV encoding, cleanup unused code

## Notification & Read Receipt Fixes
- Fixed toolbar notification icon appearing incorrectly on app restart for already-read messages
- Fixed read receipts being incorrectly deleted on startup when privateChats was empty
- Fixed messages not being marked as read when opening chat for first time
- Fixed senderPeerID not being updated during message consolidation
- Added startup phase logic to block old messages (>30s) while allowing recent ones
- Fixed unread status checking across all storage locations (ephemeral, stable Noise keys, temporary Nostr IDs)

## TLV Encoding Implementation
- Implemented Type-Length-Value encoding for private message payloads
- Added PrivateMessagePacket struct with TLV encode/decode methods
- Enhanced message structure for better extensibility and robustness

## Code Cleanup
- Removed unused PeerStateManager class and related dependencies
- Removed dead protocol types (DeliveryAck, ProtocolAck/Nack, NoiseIdentityAnnouncement)
- Cleaned up BitchatDelegate by removing unused methods
- Removed excessive debug logging throughout ChatViewModel
- Added test-only ProtocolNack helper for integration tests

## Technical Details
- Messages stored under three ID types: ephemeral peer IDs, stable Noise key hexes, temporary Nostr IDs
- Fixed cleanupOldReadReceipts() to skip when privateChats is empty or during startup
- Updated message consolidation to properly update senderPeerID
- Restored NIP-17 timestamp randomization (±15 minutes) for privacy

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
jack
2025-08-17 01:51:54 +02:00
committed by GitHub
co-authored by jack callebtc
parent 47b0829685
commit 845ffc601b
38 changed files with 6423 additions and 12246 deletions
+6 -25
View File
@@ -98,10 +98,10 @@ class PeerManager: ObservableObject {
@Published var favorites: [BitchatPeer] = []
@Published var mutualFavorites: [BitchatPeer] = []
private let meshService: BluetoothMeshService
private let meshService: SimplifiedBluetoothService
private let favoritesService = FavoritesPersistenceService.shared
init(meshService: BluetoothMeshService) {
init(meshService: SimplifiedBluetoothService) {
self.meshService = meshService
updatePeers()
@@ -142,8 +142,6 @@ class PeerManager: ObservableObject {
// Safety check: Never add our own peer ID
if peerID == meshService.myPeerID {
SecureLogger.log("⚠️ Skipping self peer ID \(peerID) in peer list",
category: SecureLogger.session, level: .warning)
continue
}
@@ -195,22 +193,19 @@ class PeerManager: ObservableObject {
// Skip if this peer is already connected (by nickname)
if connectedNicknames.contains(favorite.peerNickname) {
SecureLogger.log(" - Skipping '\(favorite.peerNickname)' (key: \(favoriteKey.hexEncodedString())) - already connected",
category: SecureLogger.session, level: .debug)
// Skipping favorite - already connected
continue
}
// Skip if we already added a peer with this ID (prevents duplicates)
if addedPeerIDs.contains(favoriteID) {
SecureLogger.log(" - Skipping '\(favorite.peerNickname)' - peer ID already added",
category: SecureLogger.session, level: .debug)
// Skipping favorite - peer ID already added
continue
}
// Only add peers that WE favorite (not just ones who favorite us)
if !favorite.isFavorite {
SecureLogger.log(" - Skipping '\(favorite.peerNickname)' - we don't favorite them (they favorite us: \(favorite.theyFavoritedUs))",
category: SecureLogger.session, level: .debug)
// Skipping - we don't favorite them
continue
}
@@ -292,21 +287,7 @@ class PeerManager: ObservableObject {
SecureLogger.log("📊 Peer list update: \(allPeers.count) total (\(connectedCount) connected, \(offlineCount) offline), \(favorites.count) favorites, \(mutualFavorites.count) mutual",
category: SecureLogger.session, level: .info)
// Log each peer's status
for peer in allPeers {
let statusIcon: String
switch peer.connectionState {
case .bluetoothConnected:
statusIcon = "🟢"
case .nostrAvailable:
statusIcon = "🌐"
case .offline:
statusIcon = "🔴"
}
let favoriteIcon = peer.isMutualFavorite ? "💕" : (peer.isFavorite ? "" : (peer.theyFavoritedUs ? "🌙" : ""))
SecureLogger.log(" \(statusIcon) \(peer.displayName) (ID: \(peer.id.prefix(8))...) \(favoriteIcon)",
category: SecureLogger.session, level: .debug)
}
// Detailed peer status logging removed for brevity
} else if previousCount != allPeers.count {
// Only log non-favorite updates if count changed
SecureLogger.log("✅ Updated peer list: \(allPeers.count) total peers",
-91
View File
@@ -1,91 +0,0 @@
import Foundation
import CoreBluetooth
/// Unified model for tracking all peer session data
/// Consolidates multiple redundant data structures into a single source of truth
class PeerSession {
// Core identification
let peerID: String
var nickname: String
// Bluetooth connection
var peripheral: CBPeripheral?
var peripheralID: String?
var characteristic: CBCharacteristic?
// Authentication and encryption
var isAuthenticated: Bool = false
var hasEstablishedNoiseSession: Bool = false
var fingerprint: String?
// Connection state
var isConnected: Bool = false
var lastSeen: Date
// Protocol state
var hasAnnounced: Bool = false
var hasReceivedAnnounce: Bool = false
var isActivePeer: Bool = false
// Message tracking
var lastMessageSent: Date?
var lastMessageReceived: Date?
var pendingMessages: [String] = []
// Connection timing
var lastConnectionTime: Date?
var lastSuccessfulMessageTime: Date?
var lastHeardFromPeer: Date?
// Availability tracking
var isAvailable: Bool = false
// Identity binding
var identityBinding: PeerIdentityBinding?
init(peerID: String, nickname: String = "Unknown") {
self.peerID = peerID
self.nickname = nickname
self.lastSeen = Date()
}
/// Update Bluetooth connection info
func updateBluetoothConnection(peripheral: CBPeripheral?, characteristic: CBCharacteristic?) {
self.peripheral = peripheral
self.peripheralID = peripheral?.identifier.uuidString
self.characteristic = characteristic
self.isConnected = (peripheral?.state == .connected)
if isConnected {
self.lastSeen = Date()
}
}
/// Update authentication state
func updateAuthenticationState(authenticated: Bool, noiseSession: Bool) {
self.isAuthenticated = authenticated
self.hasEstablishedNoiseSession = noiseSession
if authenticated {
self.isActivePeer = true
}
}
/// Check if session is stale
var isStale: Bool {
// Consider stale if not seen for more than 5 minutes and not connected
return !isConnected && Date().timeIntervalSince(lastSeen) > 300
}
/// Get display status for UI
var displayStatus: String {
if isConnected {
if isAuthenticated {
return "🟢" // Connected and authenticated
} else {
return "🟡" // Connected but not authenticated
}
} else {
return "🔴" // Not connected
}
}
}