Sync MLS group messages
This commit is contained in:
1 parent
2210a0e28b
commit
0a5a2194c4
7 files changed
+90
-50
No files matched your search
@@ -20,6 +20,7 @@ import at.torch.compose.exceptions.GiftWrapImpersonationException
|
|||||||
import at.torch.compose.exceptions.GiftWrapSealDecryptionException
|
import at.torch.compose.exceptions.GiftWrapSealDecryptionException
|
||||||
import at.torch.compose.exceptions.GiftWrapUnsealException
|
import at.torch.compose.exceptions.GiftWrapUnsealException
|
||||||
import at.torch.compose.exceptions.MarmotMissingKeyPackageBundleException
|
import at.torch.compose.exceptions.MarmotMissingKeyPackageBundleException
|
||||||
|
import at.torch.compose.exceptions.MarmotMissingNostrGroupDataExtension
|
||||||
import at.torch.compose.exceptions.MarmotWelcomeEventMissingKeyPackageEventIdException
|
import at.torch.compose.exceptions.MarmotWelcomeEventMissingKeyPackageEventIdException
|
||||||
import at.torch.compose.extensions.toHex
|
import at.torch.compose.extensions.toHex
|
||||||
import at.torch.compose.network.serialization.encodeToJsonString
|
import at.torch.compose.network.serialization.encodeToJsonString
|
||||||
@@ -615,7 +616,7 @@ abstract class NostrDao(
|
|||||||
)
|
)
|
||||||
logger.d("Group: $group")
|
logger.d("Group: $group")
|
||||||
|
|
||||||
val nostrGroupId = group.groupId.toHex()
|
val nostrGroupId = group.currentMarmotData()?.nostrGroupId ?: throw MarmotMissingNostrGroupDataExtension("Didn't find group data for $group")
|
||||||
|
|
||||||
logger.d("nostrGroupId: $nostrGroupId")
|
logger.d("nostrGroupId: $nostrGroupId")
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ data class ChatRoom(
|
|||||||
): at.torch.compose.database.model.traits.TimestampedEntity,
|
): at.torch.compose.database.model.traits.TimestampedEntity,
|
||||||
at.torch.compose.database.model.traits.LocalStoreEntity,
|
at.torch.compose.database.model.traits.LocalStoreEntity,
|
||||||
at.torch.compose.database.model.traits.UserViewableEntity,
|
at.torch.compose.database.model.traits.UserViewableEntity,
|
||||||
at.torch.compose.database.model.traits.SoftDeletableEntity {
|
at.torch.compose.database.model.traits.SoftDeletableEntity
|
||||||
|
{
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ChatRoom"
|
const val TAG = "ChatRoom"
|
||||||
|
|
||||||
|
|||||||
@@ -185,10 +185,6 @@ fun HomeScreen(
|
|||||||
factory = at.torch.compose.ui.view.model.ChatRoomListViewModel.factory(
|
factory = at.torch.compose.ui.view.model.ChatRoomListViewModel.factory(
|
||||||
initialChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
initialChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
||||||
publicKey = homeScreenUIState.profileWithFollowing.profile.publicKey,
|
publicKey = homeScreenUIState.profileWithFollowing.profile.publicKey,
|
||||||
synchronizationFilter = homeScreenViewModel.getSynchronizationFilter(
|
|
||||||
homeScreenTypeType,
|
|
||||||
profileWithFollowing = homeScreenUIState.profileWithFollowing
|
|
||||||
),
|
|
||||||
nostrRepository = nostrRepository,
|
nostrRepository = nostrRepository,
|
||||||
chatRepository = chatRepository
|
chatRepository = chatRepository
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-1
@@ -164,7 +164,7 @@ fun ShareProfileScreen(
|
|||||||
|
|
||||||
clipboardManager.setText(
|
clipboardManager.setText(
|
||||||
buildAnnotatedString {
|
buildAnnotatedString {
|
||||||
activeUserPublicKey.hexToNpubHrp()
|
append(activeUserPublicKey.hexToNpubHrp())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-7
@@ -25,15 +25,23 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import at.torch.compose.database.model.NegentropySynchronizeRequest
|
||||||
|
import at.torch.compose.database.model.types.SynchronizationFilter
|
||||||
|
import at.torch.compose.nostr.Relays
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
|
||||||
|
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
|
import io.ktor.utils.io.core.toByteArray
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.IO
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.log
|
||||||
|
|
||||||
class ChatRoomListViewModel(
|
class ChatRoomListViewModel(
|
||||||
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState,
|
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState,
|
||||||
val publicKey: String,
|
val publicKey: String,
|
||||||
val synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter,
|
|
||||||
val nostrRepository: at.torch.compose.repository.NostrRepository,
|
val nostrRepository: at.torch.compose.repository.NostrRepository,
|
||||||
val chatRepository: at.torch.compose.repository.ChatRepository
|
val chatRepository: at.torch.compose.repository.ChatRepository
|
||||||
): ViewModel() {
|
): ViewModel() {
|
||||||
@@ -65,20 +73,78 @@ class ChatRoomListViewModel(
|
|||||||
logger.d("scheduleSynchronization")
|
logger.d("scheduleSynchronization")
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
// Sync Notifications... might want to also run this in the background
|
// Sync Notifications... might want to also run this in the background
|
||||||
|
val chatRequestFilter = SynchronizationFilter(
|
||||||
|
kinds = arrayOf(
|
||||||
|
GiftWrapEvent.KIND,
|
||||||
|
),
|
||||||
|
tags = mapOf(
|
||||||
|
Pair("p", listOf(publicKey))
|
||||||
|
),
|
||||||
|
limit = 50
|
||||||
|
)
|
||||||
nostrRepository.queueNegentropySynchronizeRequest(
|
nostrRepository.queueNegentropySynchronizeRequest(
|
||||||
at.torch.compose.nostr.Relays.DefaultDMRelayList.shuffled().map { normalizedRelayUrl ->
|
at.torch.compose.nostr.Relays.DefaultDMRelayList.shuffled().map { normalizedRelayUrl ->
|
||||||
_root_ide_package_.at.torch.compose.database.model.NegentropySynchronizeRequest(
|
NegentropySynchronizeRequest(
|
||||||
id = at.torch.compose.database.model.NegentropySynchronizeRequest.computeId(
|
id = NegentropySynchronizeRequest.computeId(
|
||||||
relayURL = normalizedRelayUrl.url,
|
relayURL = normalizedRelayUrl.url,
|
||||||
synchronizationFilter = synchronizationFilter
|
synchronizationFilter = chatRequestFilter
|
||||||
),
|
),
|
||||||
purpose = "chat",
|
purpose = "chat",
|
||||||
synchronizationFilter = synchronizationFilter,
|
synchronizationFilter = chatRequestFilter,
|
||||||
relayURL = normalizedRelayUrl.url,
|
relayURL = normalizedRelayUrl.url,
|
||||||
level = 0
|
level = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Get/observe groupIds for publicKey
|
||||||
|
val chatRooms = chatRepository.getChatRoomListByPublicKey(publicKey)
|
||||||
|
val relayChatRoomMaps = Relays.DefaultDMRelayList.map { dmRelay ->
|
||||||
|
dmRelay.url to chatRooms.map { localChatRoom ->
|
||||||
|
// TODO: Just return localChatRoom.chatRoom.id once we have nostrGroupId stored as the chatRoom primary id
|
||||||
|
if (localChatRoom.chatRoom.mlsGroupState != null) {
|
||||||
|
|
||||||
|
if (localChatRoom.chatRoom.id.length == 64) {
|
||||||
|
localChatRoom.chatRoom.id
|
||||||
|
} else {
|
||||||
|
val group = MlsGroup.restore(
|
||||||
|
MlsGroupState.decodeTls(
|
||||||
|
localChatRoom.chatRoom.mlsGroupState.hexToByteArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.d("chatRoomId: ${localChatRoom.chatRoom.id} vs nostrGroupId (${group.currentMarmotData()?.nostrGroupId})")
|
||||||
|
group.currentMarmotData()?.nostrGroupId ?: localChatRoom.chatRoom.id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localChatRoom.chatRoom.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Get relay with chatGroups...
|
||||||
|
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 = "messages",
|
||||||
|
synchronizationFilter = mlsGroupMessageFilter,
|
||||||
|
relayURL = relayChatRoomMap.first,
|
||||||
|
level = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.d("negentropySyncRequests: ${negentropySyncRequests.map { it.synchronizationFilter }}")
|
||||||
|
nostrRepository.queueNegentropySynchronizeRequest(
|
||||||
|
negentropySyncRequests
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +244,6 @@ class ChatRoomListViewModel(
|
|||||||
fun factory(
|
fun factory(
|
||||||
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter,
|
|
||||||
nostrRepository: at.torch.compose.repository.NostrRepository,
|
nostrRepository: at.torch.compose.repository.NostrRepository,
|
||||||
chatRepository: at.torch.compose.repository.ChatRepository
|
chatRepository: at.torch.compose.repository.ChatRepository
|
||||||
): ViewModelProvider.Factory = viewModelFactory {
|
): ViewModelProvider.Factory = viewModelFactory {
|
||||||
@@ -186,7 +251,6 @@ class ChatRoomListViewModel(
|
|||||||
ChatRoomListViewModel(
|
ChatRoomListViewModel(
|
||||||
initialChatRoomListUIState = initialChatRoomListUIState,
|
initialChatRoomListUIState = initialChatRoomListUIState,
|
||||||
publicKey = publicKey,
|
publicKey = publicKey,
|
||||||
synchronizationFilter = synchronizationFilter,
|
|
||||||
nostrRepository = nostrRepository,
|
nostrRepository = nostrRepository,
|
||||||
chatRepository = chatRepository
|
chatRepository = chatRepository
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.initializer
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import at.torch.compose.database.model.types.SynchronizationFilter
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -56,29 +58,6 @@ class HomeViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSynchronizationFilter(
|
|
||||||
homeScreenType: HomeScreenType,
|
|
||||||
profileWithFollowing: at.torch.compose.database.model.intermdiate.LocalProfileWithFollowing,
|
|
||||||
): at.torch.compose.database.model.types.SynchronizationFilter {
|
|
||||||
|
|
||||||
Clock.System.now().minus(12.hours)
|
|
||||||
Clock.System.now()
|
|
||||||
|
|
||||||
return when (homeScreenType) {
|
|
||||||
HomeScreenType.Messages -> {
|
|
||||||
_root_ide_package_.at.torch.compose.database.model.types.SynchronizationFilter(
|
|
||||||
kinds = arrayOf(
|
|
||||||
GiftWrapEvent.KIND,
|
|
||||||
),
|
|
||||||
tags = mapOf(
|
|
||||||
Pair("p", listOf(activeUserPublicKey))
|
|
||||||
),
|
|
||||||
limit = 50
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "HomeViewModel"
|
const val TAG = "HomeViewModel"
|
||||||
|
|
||||||
|
|||||||
+12
-12
@@ -75,18 +75,18 @@ class NavigationViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (startupRoute != null) {
|
// if (startupRoute != null) {
|
||||||
val localAccount = nostrRepository.observeProfile(
|
// val localAccount = nostrRepository.observeProfile(
|
||||||
publicKey = activeUserPublicKey
|
// publicKey = activeUserPublicKey
|
||||||
).firstOrNull()
|
// ).firstOrNull()
|
||||||
|
//
|
||||||
processLocalAccount(localAccount)
|
// processLocalAccount(localAccount)
|
||||||
|
//
|
||||||
} else {
|
// } else {
|
||||||
_navigationUIState.getAndUpdate {
|
// _navigationUIState.getAndUpdate {
|
||||||
NavigationUIState.StartupPhoenix
|
// NavigationUIState.StartupPhoenix
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user