mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
fix: preserve private message arrival order
This commit is contained in:
parent
d814ac7f80
commit
dfe4fba212
@ -102,9 +102,10 @@ object ContactDirectory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return merged.mapValues { (_, messages) ->
|
return merged.mapValues { (_, messages) ->
|
||||||
messages
|
// A private message's timestamp comes from the sender and is not a reliable ordering
|
||||||
.distinctBy { it.id }
|
// signal when peers' clocks differ. The lists are populated in receive order, so keep
|
||||||
.sortedWith(compareBy<BitchatMessage> { it.timestamp.time }.thenBy { it.id })
|
// their first occurrence in that same order while canonicalizing aliases.
|
||||||
|
messages.distinctBy { it.id }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,6 @@ object ConversationAliasResolver {
|
|||||||
if (didMerge) {
|
if (didMerge) {
|
||||||
currentChats[targetConversationID] = targetList
|
currentChats[targetConversationID] = targetList
|
||||||
.distinctBy { it.id }
|
.distinctBy { it.id }
|
||||||
.sortedBy { it.timestamp.time }
|
|
||||||
state.setPrivateChats(ContactDirectory.canonicalizePrivateChats(currentChats))
|
state.setPrivateChats(ContactDirectory.canonicalizePrivateChats(currentChats))
|
||||||
|
|
||||||
// Move unread flags
|
// Move unread flags
|
||||||
|
|||||||
@ -128,16 +128,29 @@ class AppStateStoreTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `canonicalized private chat history is chronological after alias merge`() {
|
fun `canonicalized private chat history keeps arrival order when peer clocks differ`() {
|
||||||
val noiseKeyHex = "01".repeat(32)
|
val noiseKeyHex = "01".repeat(32)
|
||||||
val contactID = ContactIdentityResolver.contactConversationIdForNoiseKey(ByteArray(32) { 1 })
|
val contactID = ContactIdentityResolver.contactConversationIdForNoiseKey(ByteArray(32) { 1 })
|
||||||
val later = BitchatMessage(id = "later", sender = "alice", content = "later", timestamp = Date(3))
|
val firstArrival = BitchatMessage(
|
||||||
val earlier = BitchatMessage(id = "earlier", sender = "alice", content = "earlier", timestamp = Date(1))
|
id = "first-arrival",
|
||||||
|
sender = "alice",
|
||||||
|
content = "sent from a clock that is ahead",
|
||||||
|
timestamp = Date(3)
|
||||||
|
)
|
||||||
|
val secondArrival = BitchatMessage(
|
||||||
|
id = "second-arrival",
|
||||||
|
sender = "alice",
|
||||||
|
content = "sent from a clock that is behind",
|
||||||
|
timestamp = Date(1)
|
||||||
|
)
|
||||||
|
|
||||||
AppStateStore.addPrivateMessage(contactID, later)
|
AppStateStore.addPrivateMessage(contactID, firstArrival)
|
||||||
AppStateStore.addPrivateMessage(noiseKeyHex, earlier)
|
AppStateStore.addPrivateMessage(noiseKeyHex, secondArrival)
|
||||||
|
|
||||||
assertEquals(listOf(earlier, later), AppStateStore.privateMessages.value[contactID])
|
assertEquals(
|
||||||
|
listOf(firstArrival, secondArrival),
|
||||||
|
AppStateStore.privateMessages.value[contactID]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
package com.bitchat.android.services
|
||||||
|
|
||||||
|
import com.bitchat.android.model.BitchatMessage
|
||||||
|
import com.bitchat.android.ui.ChatState
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class ConversationAliasResolverTest {
|
||||||
|
private lateinit var scope: CoroutineScope
|
||||||
|
private lateinit var state: ChatState
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
AppStateStore.clear()
|
||||||
|
scope = CoroutineScope(SupervisorJob())
|
||||||
|
state = ChatState(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
scope.cancel()
|
||||||
|
AppStateStore.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `explicit alias merge keeps arrival order when peer clocks differ`() {
|
||||||
|
val firstArrival = BitchatMessage(
|
||||||
|
id = "first-arrival",
|
||||||
|
sender = "alice",
|
||||||
|
content = "sent from a clock that is ahead",
|
||||||
|
timestamp = Date(3)
|
||||||
|
)
|
||||||
|
val secondArrival = BitchatMessage(
|
||||||
|
id = "second-arrival",
|
||||||
|
sender = "alice",
|
||||||
|
content = "sent from a clock that is behind",
|
||||||
|
timestamp = Date(1)
|
||||||
|
)
|
||||||
|
state.setPrivateChats(
|
||||||
|
linkedMapOf(
|
||||||
|
"target-peer" to listOf(firstArrival),
|
||||||
|
"source-alias" to listOf(secondArrival)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AppStateStore.addPrivateMessage("target-peer", firstArrival)
|
||||||
|
AppStateStore.addPrivateMessage("source-alias", secondArrival)
|
||||||
|
|
||||||
|
ConversationAliasResolver.unifyChatsIntoPeer(
|
||||||
|
state = state,
|
||||||
|
targetPeerID = "target-peer",
|
||||||
|
keysToMerge = listOf("source-alias")
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf(firstArrival, secondArrival),
|
||||||
|
state.getPrivateChatsValue()["target-peer"]
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf(firstArrival, secondArrival),
|
||||||
|
AppStateStore.privateMessages.value["target-peer"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user