refactor(chat): stop syncing chat messages on screen open
The point of the previous four commits. Both chat screens scheduled a message
sync every time they were opened; both are now covered by subscriptions that are
already open, so the sync on open is work with nothing left to do.
**ChatRoomListViewModel** no longer syncs on initiate(). scheduleSynchronization
itself stays, and is unchanged: it is exactly the reconciliation
LiveSubscriptionManager runs when the app returns from the background, and it is
what an explicit user-initiated refresh should call. That is the one case the
live tier genuinely does not answer, because it is the user saying they believe
something is missing.
**ChatMessageListViewModel** loses its message sync entirely. The MLS branch
(negentropy over 445 h-tagged with this one room) is a strict subset of
live-groups-*. The NIP-17 branch's gift wrap sync is a subset of live-giftwrap.
What survives is discovery rather than sync: if we do not hold a participant's
kind-10050 we cannot address a message to them, and that is worth resolving the
moment a chat is opened rather than whenever a background pass reaches it. The
function now does only that, and only when the relay list is actually missing —
it used to queue a request in both branches of that test.
**The dead "sent-messages" reconciliation is deleted**, in the view model and at
both sites in NostrDao. It asked for kind 1059 with authors=[userPublicKey], and
could never match a single event: a gift wrap is signed with a throwaway KeyPair
(DatabaseChatRepository), so its pubkey is random and never ours. It was also
unnecessary — createNip17ChatRoom puts us in our own participant list, so we
wrap a copy to ourselves and the live gift wrap subscription picks our own sent
messages up on every device.
Removing it inverts the surrounding test in NostrDao from `if (relayList !=
null) { sync } else { discover }` to `if (relayList == null) { discover }`. The
discovery half is untouched.
Net effect on a session: opening the chat list queues nothing, opening a chat
queues at most a kind-10050 lookup for a participant we cannot yet address, and
messages arrive because a subscription is open rather than because a screen
asked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -639,41 +639,15 @@ abstract class NostrDao(
|
||||
}
|
||||
}
|
||||
|
||||
if (chatMessageRelayListEvent != null) {
|
||||
// Sync messages from this relay that were sent by us
|
||||
val synchronizationFilter =
|
||||
SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
authors = arrayOf(
|
||||
userPublicKey
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair(
|
||||
"p",
|
||||
listOf(participant.participantPublicKey)
|
||||
)
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
database.negentropySynchronizeRequestDao()
|
||||
.insert(
|
||||
chatMessageRelayListEvent.relays()
|
||||
.map { normalizedRelayUrl ->
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
),
|
||||
purpose = "sent-messages",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// A "sent-messages" reconciliation used to be queued here,
|
||||
// for gift wraps with authors=[userPublicKey] p-tagged at this
|
||||
// participant. It could never match anything: a gift wrap is
|
||||
// signed with a throwaway key (see DatabaseChatRepository), so
|
||||
// its pubkey is random and never ours. It was also unnecessary
|
||||
// — createNip17ChatRoom puts us in our own participant list, so
|
||||
// we wrap a copy to ourselves and the live gift wrap
|
||||
// subscription picks our own sent messages up on every device.
|
||||
if (chatMessageRelayListEvent == null) {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
@@ -875,35 +849,10 @@ abstract class NostrDao(
|
||||
}
|
||||
}
|
||||
|
||||
if (chatMessageRelayListEvent != null) {
|
||||
// Sync messages from this relay that were sent by us
|
||||
val synchronizationFilter = SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
authors = arrayOf(
|
||||
userPublicKey
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(participant.participantPublicKey))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
database.negentropySynchronizeRequestDao().insert(
|
||||
chatMessageRelayListEvent.relays().map { normalizedRelayUrl ->
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
),
|
||||
purpose = "sent-messages",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// See the identical block above: the "sent-messages"
|
||||
// reconciliation that used to live here could never match, and is
|
||||
// covered by the live gift wrap subscription regardless.
|
||||
if (chatMessageRelayListEvent == null) {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
|
||||
@@ -70,9 +70,7 @@ import press.mantra.compose.repository.NostrRepository
|
||||
import press.mantra.compose.ui.composable.widgets.profile.ProfileColor
|
||||
import press.mantra.compose.ui.view.state.ChatMessageListUIState
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -110,63 +108,50 @@ class ChatMessageListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds out where this room's participants read their messages.
|
||||
*
|
||||
* This used to schedule the room's message sync as well — gift wraps for a NIP-17 room,
|
||||
* group events for an MLS one. Both are now covered by the subscriptions
|
||||
* LiveSubscriptionManager holds open for the whole account, so opening a chat no longer
|
||||
* asks for its messages; they are already arriving.
|
||||
*
|
||||
* What is left is discovery, not message sync: if we do not hold a participant's
|
||||
* kind-10050 we cannot address a message to them, and that is worth resolving the moment
|
||||
* a chat is opened rather than whenever a background pass gets to it.
|
||||
*/
|
||||
fun scheduleSynchronization() {
|
||||
logger.d("scheduleSynchronization")
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Sync Notifications... might want to also run this in the background
|
||||
if (localChatRoom.chatRoom.mlsGroupState == null) {
|
||||
localChatRoom.localParticipants.filter { it.participant.participantPublicKey != localChatRoom.chatRoom.userPublicKey }.forEach { recipients ->
|
||||
|
||||
val chatMessageRelayListEvent = chatRepository.getChatMessageRelayForPublicKey(recipients.participant.participantPublicKey)
|
||||
|
||||
val relayAndSynchronizationFilter = if (chatMessageRelayListEvent != null) {
|
||||
// Sync messages from this relay...
|
||||
Pair(
|
||||
chatMessageRelayListEvent.relays(),
|
||||
SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(recipients.participant.participantPublicKey))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
)
|
||||
// We already know where to reach them; nothing to find out.
|
||||
if (chatMessageRelayListEvent != null) return@forEach
|
||||
|
||||
} else {
|
||||
// Try to find the ChatMessageRelayList for this participant...
|
||||
isReceiverChatMessageRelayListMissing.value = true
|
||||
// TODO: compute the timestamp for when list we sent messages and use that as since...
|
||||
Pair(
|
||||
Relays.DefaultDMRelayList,
|
||||
SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
),
|
||||
authors = arrayOf(
|
||||
recipients.participant.participantPublicKey
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
)
|
||||
}
|
||||
isReceiverChatMessageRelayListMissing.value = true
|
||||
|
||||
logger.i("GiftWrapFilter: ${relayAndSynchronizationFilter.second}")
|
||||
val chatMessageRelayListFilter = SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND,
|
||||
),
|
||||
authors = arrayOf(
|
||||
recipients.participant.participantPublicKey
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
|
||||
val negentropySynchronizeRequests = relayAndSynchronizationFilter.first.map { normalizedRelayUrl ->
|
||||
val negentropySynchronizeRequests = Relays.DefaultDMRelayList.map { normalizedRelayUrl ->
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = relayAndSynchronizationFilter.second
|
||||
synchronizationFilter = chatMessageRelayListFilter
|
||||
),
|
||||
purpose = if (isReceiverChatMessageRelayListMissing.value) {
|
||||
"chat-message-relays"
|
||||
} else {
|
||||
"sent-messages"
|
||||
},
|
||||
synchronizationFilter = relayAndSynchronizationFilter.second,
|
||||
purpose = "chat-message-relays",
|
||||
synchronizationFilter = chatMessageRelayListFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
)
|
||||
@@ -177,39 +162,7 @@ class ChatMessageListViewModel(
|
||||
negentropySynchronizeRequests
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Sync mlsMessages
|
||||
val relayChatRoomMaps = Relays.DefaultDMRelayList.map { dmRelay ->
|
||||
dmRelay.url to listOf(
|
||||
localChatRoom.chatRoom.id
|
||||
)
|
||||
}
|
||||
|
||||
val negentropySyncRequests = relayChatRoomMaps.map { relayChatRoomMap ->
|
||||
val mlsGroupMessageFilter = SynchronizationFilter(
|
||||
kinds = arrayOf(GroupEvent.KIND),
|
||||
tags = mapOf("h" to relayChatRoomMap.second),
|
||||
)
|
||||
logger.d("mlsGroupMessageFilter: $mlsGroupMessageFilter")
|
||||
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = relayChatRoomMap.first,
|
||||
synchronizationFilter = mlsGroupMessageFilter
|
||||
),
|
||||
purpose = "mlsMessages",
|
||||
synchronizationFilter = mlsGroupMessageFilter,
|
||||
relayURL = relayChatRoomMap.first,
|
||||
level = 0
|
||||
)
|
||||
}
|
||||
|
||||
logger.d("negentropySyncRequests: ${negentropySyncRequests.map { it.synchronizationFilter }}")
|
||||
nostrRepository.queueNegentropySynchronizeRequest(
|
||||
negentropySyncRequests
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ class ChatRoomListViewModel(
|
||||
|
||||
fun initiate() {
|
||||
logger.d("init")
|
||||
scheduleSynchronization()
|
||||
observeChatRoomFeed()
|
||||
}
|
||||
|
||||
@@ -68,6 +67,16 @@ class ChatRoomListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconciles this account's chat history against the relays: gift wraps addressed to us,
|
||||
* and group events for every group we are in.
|
||||
*
|
||||
* No longer called on open. LiveSubscriptionManager holds both of those subscriptions
|
||||
* open for as long as the app is in the foreground and runs exactly this reconciliation
|
||||
* when it returns from the background, so opening the list is no longer a reason to ask.
|
||||
* This stays for an explicit user-initiated refresh — the one case the live tier does not
|
||||
* answer, because it is the user saying they believe something is missing.
|
||||
*/
|
||||
fun scheduleSynchronization() {
|
||||
logger.d("scheduleSynchronization")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
Reference in New Issue
Block a user