mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-08 06:46:11 +00:00
Honor private message admission during panic
This commit is contained in:
parent
c8153c0d18
commit
bf87a80617
@ -480,21 +480,13 @@ class BluetoothMeshService(private val context: Context) : TransportBridgeServic
|
||||
|
||||
// Callbacks
|
||||
override fun onMessageReceived(message: BitchatMessage) {
|
||||
// Always reflect into process-wide store so UI can hydrate after recreation
|
||||
try {
|
||||
when {
|
||||
message.isPrivate -> {
|
||||
val peer = message.senderPeerID ?: ""
|
||||
if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message)
|
||||
}
|
||||
message.channel != null -> {
|
||||
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message)
|
||||
}
|
||||
else -> {
|
||||
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
// Private-message admission is authoritative. In particular, do not forward a
|
||||
// callback or notify after panic mode rejected the message while wiping state.
|
||||
if (
|
||||
!com.bitchat.android.services.IncomingMessageAdmission
|
||||
.admitToAppState(message)
|
||||
) return
|
||||
|
||||
// And forward to UI delegate if attached
|
||||
delegate?.didReceiveMessage(message)
|
||||
|
||||
|
||||
@ -41,7 +41,11 @@ class MeshCore(
|
||||
private val hooks: Hooks = Hooks()
|
||||
) {
|
||||
data class Hooks(
|
||||
val onMessageReceived: ((BitchatMessage) -> Unit)? = null,
|
||||
/**
|
||||
* Reflects a decoded message into transport-owned state before delegate dispatch.
|
||||
* Return false to suppress all downstream effects for a rejected message.
|
||||
*/
|
||||
val onMessageReceived: ((BitchatMessage) -> Boolean)? = null,
|
||||
val onAnnounceProcessed: ((RoutedPacket, Boolean) -> Unit)? = null,
|
||||
val readReceiptInterceptor: ((String, String) -> Boolean)? = null,
|
||||
val onReadReceiptSent: ((String) -> Unit)? = null,
|
||||
@ -392,7 +396,7 @@ class MeshCore(
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: BitchatMessage) {
|
||||
hooks.onMessageReceived?.invoke(message)
|
||||
if (hooks.onMessageReceived?.invoke(message) == false) return
|
||||
delegate?.didReceiveMessage(message)
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package com.bitchat.android.services
|
||||
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
|
||||
/**
|
||||
* Reflects an incoming transport message into process-wide state before any downstream effects.
|
||||
*
|
||||
* Private-message admission is authoritative: a duplicate or a message rejected while panic mode
|
||||
* is wiping state must not continue to UI delegates, unread tracking, haptics, or notifications.
|
||||
* Public and channel messages retain their existing best-effort behavior if state reflection fails.
|
||||
*/
|
||||
internal object IncomingMessageAdmission {
|
||||
fun admitToAppState(message: BitchatMessage): Boolean = try {
|
||||
when {
|
||||
message.isPrivate -> {
|
||||
val peerID = message.senderPeerID?.takeIf(String::isNotBlank)
|
||||
?: return false
|
||||
AppStateStore.addPrivateMessage(peerID, message)
|
||||
}
|
||||
|
||||
message.channel != null -> {
|
||||
AppStateStore.addChannelMessage(message.channel, message)
|
||||
true
|
||||
}
|
||||
|
||||
else -> {
|
||||
AppStateStore.addPublicMessage(message)
|
||||
true
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Preserve the pre-existing best-effort dispatch for public/channel messages, but never
|
||||
// bypass private-message admission when persistence or canonicalization fails.
|
||||
!message.isPrivate
|
||||
}
|
||||
}
|
||||
@ -188,21 +188,13 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
||||
fragmentingSender = FragmentingPacketSender(serviceScope, meshCore.fragmentManager, TAG)
|
||||
}
|
||||
|
||||
private fun handleMessageReceived(message: BitchatMessage) {
|
||||
try {
|
||||
when {
|
||||
message.isPrivate -> {
|
||||
val peer = message.senderPeerID ?: ""
|
||||
if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message)
|
||||
}
|
||||
message.channel != null -> {
|
||||
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message)
|
||||
}
|
||||
else -> {
|
||||
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
private fun handleMessageReceived(message: BitchatMessage): Boolean {
|
||||
// Match BLE admission semantics: a private message rejected during panic or as a
|
||||
// duplicate must not create a notification after the conversation state was cleared.
|
||||
if (
|
||||
!com.bitchat.android.services.IncomingMessageAdmission
|
||||
.admitToAppState(message)
|
||||
) return false
|
||||
|
||||
if (delegate == null && message.isPrivate) {
|
||||
try {
|
||||
@ -215,6 +207,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package com.bitchat.android.services
|
||||
|
||||
import com.bitchat.android.model.BitchatMessage
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.Date
|
||||
|
||||
class IncomingMessageAdmissionTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
AppStateStore.resumePrivateConversationsAfterPanic()
|
||||
AppStateStore.clear()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
AppStateStore.resumePrivateConversationsAfterPanic()
|
||||
AppStateStore.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `private message rejected during panic cannot continue transport dispatch`() {
|
||||
assertTrue(runBlocking { AppStateStore.panicClearPrivateConversations() })
|
||||
|
||||
assertFalse(
|
||||
IncomingMessageAdmission.admitToAppState(
|
||||
privateMessage(id = "during-panic")
|
||||
)
|
||||
)
|
||||
assertTrue(AppStateStore.privateMessages.value.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `duplicate private transport delivery is rejected before downstream effects`() {
|
||||
val message = privateMessage(id = "same-message-over-two-transports")
|
||||
|
||||
assertTrue(IncomingMessageAdmission.admitToAppState(message))
|
||||
assertFalse(IncomingMessageAdmission.admitToAppState(message))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `public and channel messages preserve best effort admission`() {
|
||||
val public = BitchatMessage(
|
||||
id = "public",
|
||||
sender = "alice",
|
||||
content = "hello",
|
||||
timestamp = Date(1L)
|
||||
)
|
||||
val channel = public.copy(id = "channel", channel = "#mesh")
|
||||
|
||||
assertTrue(IncomingMessageAdmission.admitToAppState(public))
|
||||
assertTrue(IncomingMessageAdmission.admitToAppState(channel))
|
||||
assertTrue(AppStateStore.publicMessages.value.contains(public))
|
||||
}
|
||||
|
||||
private fun privateMessage(id: String) = BitchatMessage(
|
||||
id = id,
|
||||
sender = "alice",
|
||||
content = "secret",
|
||||
timestamp = Date(1L),
|
||||
isPrivate = true,
|
||||
senderPeerID = "peer-a"
|
||||
)
|
||||
}
|
||||
@ -227,18 +227,27 @@ class WearMeshService private constructor(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMessageReceived(message: com.bitchat.android.model.BitchatMessage) {
|
||||
try {
|
||||
when {
|
||||
message.isPrivate -> {
|
||||
val peer = message.senderPeerID ?: return
|
||||
AppStateStore.addPrivateMessage(peer, message)
|
||||
try { onPrivateMessage?.invoke(message) } catch (_: Exception) { }
|
||||
}
|
||||
message.channel != null -> AppStateStore.addChannelMessage(message.channel!!, message)
|
||||
else -> AppStateStore.addPublicMessage(message)
|
||||
private fun handleMessageReceived(
|
||||
message: com.bitchat.android.model.BitchatMessage
|
||||
): Boolean = try {
|
||||
when {
|
||||
message.isPrivate -> {
|
||||
val peer = message.senderPeerID ?: return false
|
||||
if (!AppStateStore.addPrivateMessage(peer, message)) return false
|
||||
try { onPrivateMessage?.invoke(message) } catch (_: Exception) { }
|
||||
true
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
message.channel != null -> {
|
||||
AppStateStore.addChannelMessage(message.channel, message)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
AppStateStore.addPublicMessage(message)
|
||||
true
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
!message.isPrivate
|
||||
}
|
||||
|
||||
fun startServices() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user