mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +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
|
// Callbacks
|
||||||
override fun onMessageReceived(message: BitchatMessage) {
|
override fun onMessageReceived(message: BitchatMessage) {
|
||||||
// Always reflect into process-wide store so UI can hydrate after recreation
|
// Private-message admission is authoritative. In particular, do not forward a
|
||||||
try {
|
// callback or notify after panic mode rejected the message while wiping state.
|
||||||
when {
|
if (
|
||||||
message.isPrivate -> {
|
!com.bitchat.android.services.IncomingMessageAdmission
|
||||||
val peer = message.senderPeerID ?: ""
|
.admitToAppState(message)
|
||||||
if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message)
|
) return
|
||||||
}
|
|
||||||
message.channel != null -> {
|
|
||||||
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
// And forward to UI delegate if attached
|
// And forward to UI delegate if attached
|
||||||
delegate?.didReceiveMessage(message)
|
delegate?.didReceiveMessage(message)
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,11 @@ class MeshCore(
|
|||||||
private val hooks: Hooks = Hooks()
|
private val hooks: Hooks = Hooks()
|
||||||
) {
|
) {
|
||||||
data class 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 onAnnounceProcessed: ((RoutedPacket, Boolean) -> Unit)? = null,
|
||||||
val readReceiptInterceptor: ((String, String) -> Boolean)? = null,
|
val readReceiptInterceptor: ((String, String) -> Boolean)? = null,
|
||||||
val onReadReceiptSent: ((String) -> Unit)? = null,
|
val onReadReceiptSent: ((String) -> Unit)? = null,
|
||||||
@ -392,7 +396,7 @@ class MeshCore(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMessageReceived(message: BitchatMessage) {
|
override fun onMessageReceived(message: BitchatMessage) {
|
||||||
hooks.onMessageReceived?.invoke(message)
|
if (hooks.onMessageReceived?.invoke(message) == false) return
|
||||||
delegate?.didReceiveMessage(message)
|
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)
|
fragmentingSender = FragmentingPacketSender(serviceScope, meshCore.fragmentManager, TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMessageReceived(message: BitchatMessage) {
|
private fun handleMessageReceived(message: BitchatMessage): Boolean {
|
||||||
try {
|
// Match BLE admission semantics: a private message rejected during panic or as a
|
||||||
when {
|
// duplicate must not create a notification after the conversation state was cleared.
|
||||||
message.isPrivate -> {
|
if (
|
||||||
val peer = message.senderPeerID ?: ""
|
!com.bitchat.android.services.IncomingMessageAdmission
|
||||||
if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message)
|
.admitToAppState(message)
|
||||||
}
|
) return false
|
||||||
message.channel != null -> {
|
|
||||||
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
|
|
||||||
if (delegate == null && message.isPrivate) {
|
if (delegate == null && message.isPrivate) {
|
||||||
try {
|
try {
|
||||||
@ -215,6 +207,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
}
|
}
|
||||||
} catch (_: Exception) { }
|
} 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) {
|
private fun handleMessageReceived(
|
||||||
try {
|
message: com.bitchat.android.model.BitchatMessage
|
||||||
when {
|
): Boolean = try {
|
||||||
message.isPrivate -> {
|
when {
|
||||||
val peer = message.senderPeerID ?: return
|
message.isPrivate -> {
|
||||||
AppStateStore.addPrivateMessage(peer, message)
|
val peer = message.senderPeerID ?: return false
|
||||||
try { onPrivateMessage?.invoke(message) } catch (_: Exception) { }
|
if (!AppStateStore.addPrivateMessage(peer, message)) return false
|
||||||
}
|
try { onPrivateMessage?.invoke(message) } catch (_: Exception) { }
|
||||||
message.channel != null -> AppStateStore.addChannelMessage(message.channel!!, message)
|
true
|
||||||
else -> AppStateStore.addPublicMessage(message)
|
|
||||||
}
|
}
|
||||||
} catch (_: Exception) { }
|
message.channel != null -> {
|
||||||
|
AppStateStore.addChannelMessage(message.channel, message)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
AppStateStore.addPublicMessage(message)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
!message.isPrivate
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startServices() {
|
fun startServices() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user