Improve BLE connection stability and message reliability (#318)

* Remove sequence numbers from protocol

- Remove sequenceNumber field from BitchatPacket struct
- Update BinaryProtocol to not encode/decode sequence numbers (header size reduced from 17 to 13 bytes)
- Replace sequence-based duplicate detection with content-based using packet ID hash
- Update packet ID generation to use SHA256(senderID + timestamp + type + payload prefix)
- Remove all sequence tracking variables and methods
- Simplify duplicate detection to rely on timestamp and content hashing

* Fix connection stability issues

- Increase peer availability check interval from 5 to 15 seconds
- Fix availability logic to not mark connected peers as unavailable
- Add BLE connection keepalive timer (20s) to prevent iOS timeouts
- Fix missing delivery ACKs by passing peripheral context through Noise decryption
- Reduce identity announce frequency from 2 to 10 seconds minimum
- Remove unnecessary identity announces on connection
- Debounce identity cache keychain saves (2 second delay)
- Add message retry notification handler in ChatViewModel
- Fix version negotiation redundancy by checking existing negotiations
- Keep Noise sessions for already-connected peers

* Fix build errors in message retry handler

- Fix reference to 'displayedMessages' - should be 'messages'
- Fix sendMessage call signature to use individual parameters instead of message object
- Both iOS and macOS builds now succeed

* Fix remaining connection stability issues

- Fix duplicate identity announces with content-based deduplication
- Simplify peripheral mapping with cleaner temp ID to peer ID transitions
- Improve graceful leave detection across peer ID rotations
- Track previousPeerID from announcements to maintain state
- Add time-based cleanup for gracefully left peers

* Fix connection stability issues

- Add special duplicate detection for identity announces
- Simplify peripheral mapping with dedicated structure
- Improve graceful leave detection with peer ID rotation handling
- Track graceful leave timestamps for cleanup
- Transfer states properly during peer ID rotation

* Improve BLE connection stability and message reliability

- Increase peer availability timeout from 5s to 15s to prevent flapping
- Add BLE keepalive timer with 30s interval to maintain connections
- Fix missing delivery ACKs by passing peripheral context through decryption
- Reduce identity announce frequency from 2s to 10s minimum interval
- Add keychain save debouncing with 2s delay to prevent excessive writes
- Implement message retry system for failed deliveries to favorites
- Fix version negotiation redundancy by checking existing state
- Add special duplicate detection for identity announcements
- Implement graceful leave detection with peer ID rotation support
- Simplify peripheral mapping to reduce complexity
- Fix switch statement structure issues causing build errors

These changes significantly improve connection stability, eliminate peer availability flapping, and ensure reliable message delivery.

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-07-25 02:40:51 +02:00
committed by GitHub
co-authored by jack
parent 86726d7033
commit 809e222a31
6 changed files with 344 additions and 188 deletions
+32
View File
@@ -103,6 +103,14 @@ class ChatViewModel: ObservableObject {
.sink { [weak self] (messageID, status) in
self?.updateMessageDeliveryStatus(messageID, status: status)
}
// Listen for retry notifications
NotificationCenter.default.addObserver(
self,
selector: #selector(handleRetryMessage),
name: Notification.Name("bitchat.retryMessage"),
object: nil
)
// When app becomes active, send read receipts for visible messages
#if os(macOS)
@@ -549,6 +557,30 @@ class ChatViewModel: ObservableObject {
selectedPrivateChatFingerprint = nil
}
@objc private func handleRetryMessage(_ notification: Notification) {
guard let messageID = notification.userInfo?["messageID"] as? String else { return }
// Find the message to retry
if let message = messages.first(where: { $0.id == messageID }) {
SecureLogger.log("Retrying message \(messageID) to \(message.recipientNickname ?? "unknown")",
category: SecureLogger.session, level: .info)
// Resend the message through mesh service
if message.isPrivate,
let peerID = getPeerIDForNickname(message.recipientNickname ?? "") {
// Update status to sending
updateMessageDeliveryStatus(messageID, status: .sending)
// Resend via mesh service
meshService.sendMessage(message.content,
mentions: message.mentions ?? [],
to: peerID,
messageID: messageID,
timestamp: message.timestamp)
}
}
}
@objc private func appDidBecomeActive() {
// When app becomes active, send read receipts for visible private chat
if let peerID = selectedPrivateChatPeer {