mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 02:25:21 +00:00
deduplicate messages
This commit is contained in:
@@ -21,6 +21,7 @@ import com.bitchat.android.model.ReadReceipt
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.Collections
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.KeyGenerator
|
import javax.crypto.KeyGenerator
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
@@ -97,6 +98,12 @@ class ChatViewModel(application: Application) : AndroidViewModel(application), B
|
|||||||
private val peerIDToPublicKeyFingerprint = mutableMapOf<String, String>()
|
private val peerIDToPublicKeyFingerprint = mutableMapOf<String, String>()
|
||||||
private val blockedUsers = mutableSetOf<String>()
|
private val blockedUsers = mutableSetOf<String>()
|
||||||
|
|
||||||
|
// Message deduplication - FIXED: Prevent duplicate messages from dual connection paths
|
||||||
|
private val processedUIMessages = Collections.synchronizedSet(mutableSetOf<String>())
|
||||||
|
private val recentSystemEvents = Collections.synchronizedMap(mutableMapOf<String, Long>())
|
||||||
|
private val MESSAGE_DEDUP_TIMEOUT = 30000L // 30 seconds
|
||||||
|
private val SYSTEM_EVENT_DEDUP_TIMEOUT = 5000L // 5 seconds
|
||||||
|
|
||||||
// Sidebar state
|
// Sidebar state
|
||||||
private val _showSidebar = MutableLiveData(false)
|
private val _showSidebar = MutableLiveData(false)
|
||||||
val showSidebar: LiveData<Boolean> = _showSidebar
|
val showSidebar: LiveData<Boolean> = _showSidebar
|
||||||
@@ -988,10 +995,61 @@ class ChatViewModel(application: Application) : AndroidViewModel(application), B
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Message Deduplication Utilities
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique key for message deduplication
|
||||||
|
*/
|
||||||
|
private fun generateMessageKey(message: BitchatMessage): String {
|
||||||
|
val senderKey = message.senderPeerID ?: message.sender
|
||||||
|
val contentHash = message.content.hashCode()
|
||||||
|
return "$senderKey-${message.timestamp.time}-$contentHash"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a system event is a duplicate within the timeout window
|
||||||
|
*/
|
||||||
|
private fun isDuplicateSystemEvent(eventType: String, peerID: String): Boolean {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val eventKey = "$eventType-$peerID"
|
||||||
|
val lastEvent = recentSystemEvents[eventKey]
|
||||||
|
|
||||||
|
if (lastEvent != null && (now - lastEvent) < SYSTEM_EVENT_DEDUP_TIMEOUT) {
|
||||||
|
return true // Duplicate event
|
||||||
|
}
|
||||||
|
|
||||||
|
recentSystemEvents[eventKey] = now
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up old entries from deduplication caches
|
||||||
|
*/
|
||||||
|
private fun cleanupDeduplicationCaches() {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
// Clean up processed UI messages (remove entries older than 30 seconds)
|
||||||
|
if (processedUIMessages.size > 1000) {
|
||||||
|
processedUIMessages.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up recent system events (remove entries older than timeout)
|
||||||
|
recentSystemEvents.entries.removeAll { (_, timestamp) ->
|
||||||
|
(now - timestamp) > SYSTEM_EVENT_DEDUP_TIMEOUT * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - BluetoothMeshDelegate Implementation
|
// MARK: - BluetoothMeshDelegate Implementation
|
||||||
|
|
||||||
override fun didReceiveMessage(message: BitchatMessage) {
|
override fun didReceiveMessage(message: BitchatMessage) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
// FIXED: Deduplicate messages from dual connection paths
|
||||||
|
val messageKey = generateMessageKey(message)
|
||||||
|
if (processedUIMessages.contains(messageKey)) {
|
||||||
|
return@launch // Duplicate message, ignore
|
||||||
|
}
|
||||||
|
processedUIMessages.add(messageKey)
|
||||||
|
|
||||||
// Check if sender is blocked
|
// Check if sender is blocked
|
||||||
message.senderPeerID?.let { senderPeerID ->
|
message.senderPeerID?.let { senderPeerID ->
|
||||||
if (isPeerBlocked(senderPeerID)) {
|
if (isPeerBlocked(senderPeerID)) {
|
||||||
@@ -1021,11 +1079,21 @@ class ChatViewModel(application: Application) : AndroidViewModel(application), B
|
|||||||
// Public message
|
// Public message
|
||||||
addMessage(message)
|
addMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Periodic cleanup
|
||||||
|
if (processedUIMessages.size > 500) {
|
||||||
|
cleanupDeduplicationCaches()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didConnectToPeer(peerID: String) {
|
override fun didConnectToPeer(peerID: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
// FIXED: Deduplicate connection events from dual connection paths
|
||||||
|
if (isDuplicateSystemEvent("connect", peerID)) {
|
||||||
|
return@launch // Duplicate connection event, ignore
|
||||||
|
}
|
||||||
|
|
||||||
val systemMessage = BitchatMessage(
|
val systemMessage = BitchatMessage(
|
||||||
sender = "system",
|
sender = "system",
|
||||||
content = "$peerID connected",
|
content = "$peerID connected",
|
||||||
@@ -1038,6 +1106,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application), B
|
|||||||
|
|
||||||
override fun didDisconnectFromPeer(peerID: String) {
|
override fun didDisconnectFromPeer(peerID: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
// FIXED: Deduplicate disconnection events from dual connection paths
|
||||||
|
if (isDuplicateSystemEvent("disconnect", peerID)) {
|
||||||
|
return@launch // Duplicate disconnection event, ignore
|
||||||
|
}
|
||||||
|
|
||||||
val systemMessage = BitchatMessage(
|
val systemMessage = BitchatMessage(
|
||||||
sender = "system",
|
sender = "system",
|
||||||
content = "$peerID disconnected",
|
content = "$peerID disconnected",
|
||||||
|
|||||||
Reference in New Issue
Block a user