Sync MLS group messages
This commit is contained in:
@@ -20,6 +20,7 @@ import at.torch.compose.exceptions.GiftWrapImpersonationException
|
||||
import at.torch.compose.exceptions.GiftWrapSealDecryptionException
|
||||
import at.torch.compose.exceptions.GiftWrapUnsealException
|
||||
import at.torch.compose.exceptions.MarmotMissingKeyPackageBundleException
|
||||
import at.torch.compose.exceptions.MarmotMissingNostrGroupDataExtension
|
||||
import at.torch.compose.exceptions.MarmotWelcomeEventMissingKeyPackageEventIdException
|
||||
import at.torch.compose.extensions.toHex
|
||||
import at.torch.compose.network.serialization.encodeToJsonString
|
||||
@@ -615,7 +616,7 @@ abstract class NostrDao(
|
||||
)
|
||||
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")
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ data class ChatRoom(
|
||||
): at.torch.compose.database.model.traits.TimestampedEntity,
|
||||
at.torch.compose.database.model.traits.LocalStoreEntity,
|
||||
at.torch.compose.database.model.traits.UserViewableEntity,
|
||||
at.torch.compose.database.model.traits.SoftDeletableEntity {
|
||||
|
||||
at.torch.compose.database.model.traits.SoftDeletableEntity
|
||||
{
|
||||
companion object {
|
||||
const val TAG = "ChatRoom"
|
||||
|
||||
|
||||
@@ -185,10 +185,6 @@ fun HomeScreen(
|
||||
factory = at.torch.compose.ui.view.model.ChatRoomListViewModel.factory(
|
||||
initialChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
||||
publicKey = homeScreenUIState.profileWithFollowing.profile.publicKey,
|
||||
synchronizationFilter = homeScreenViewModel.getSynchronizationFilter(
|
||||
homeScreenTypeType,
|
||||
profileWithFollowing = homeScreenUIState.profileWithFollowing
|
||||
),
|
||||
nostrRepository = nostrRepository,
|
||||
chatRepository = chatRepository
|
||||
),
|
||||
|
||||
@@ -164,7 +164,7 @@ fun ShareProfileScreen(
|
||||
|
||||
clipboardManager.setText(
|
||||
buildAnnotatedString {
|
||||
activeUserPublicKey.hexToNpubHrp()
|
||||
append(activeUserPublicKey.hexToNpubHrp())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,15 +25,23 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
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 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.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.log
|
||||
|
||||
class ChatRoomListViewModel(
|
||||
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState,
|
||||
val publicKey: String,
|
||||
val synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter,
|
||||
val nostrRepository: at.torch.compose.repository.NostrRepository,
|
||||
val chatRepository: at.torch.compose.repository.ChatRepository
|
||||
): ViewModel() {
|
||||
@@ -65,20 +73,78 @@ class ChatRoomListViewModel(
|
||||
logger.d("scheduleSynchronization")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// 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(
|
||||
at.torch.compose.nostr.Relays.DefaultDMRelayList.shuffled().map { normalizedRelayUrl ->
|
||||
_root_ide_package_.at.torch.compose.database.model.NegentropySynchronizeRequest(
|
||||
id = at.torch.compose.database.model.NegentropySynchronizeRequest.computeId(
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
synchronizationFilter = chatRequestFilter
|
||||
),
|
||||
purpose = "chat",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
synchronizationFilter = chatRequestFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
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(
|
||||
initialChatRoomListUIState: at.torch.compose.ui.view.state.ChatRoomListUIState = at.torch.compose.ui.view.state.ChatRoomListUIState.Loading,
|
||||
publicKey: String,
|
||||
synchronizationFilter: at.torch.compose.database.model.types.SynchronizationFilter,
|
||||
nostrRepository: at.torch.compose.repository.NostrRepository,
|
||||
chatRepository: at.torch.compose.repository.ChatRepository
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
@@ -186,7 +251,6 @@ class ChatRoomListViewModel(
|
||||
ChatRoomListViewModel(
|
||||
initialChatRoomListUIState = initialChatRoomListUIState,
|
||||
publicKey = publicKey,
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
nostrRepository = nostrRepository,
|
||||
chatRepository = chatRepository
|
||||
)
|
||||
|
||||
@@ -9,7 +9,9 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import at.torch.compose.database.model.types.SynchronizationFilter
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
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 {
|
||||
const val TAG = "HomeViewModel"
|
||||
|
||||
|
||||
@@ -75,18 +75,18 @@ class NavigationViewModel(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (startupRoute != null) {
|
||||
val localAccount = nostrRepository.observeProfile(
|
||||
publicKey = activeUserPublicKey
|
||||
).firstOrNull()
|
||||
|
||||
processLocalAccount(localAccount)
|
||||
|
||||
} else {
|
||||
_navigationUIState.getAndUpdate {
|
||||
NavigationUIState.StartupPhoenix
|
||||
}
|
||||
}
|
||||
// if (startupRoute != null) {
|
||||
// val localAccount = nostrRepository.observeProfile(
|
||||
// publicKey = activeUserPublicKey
|
||||
// ).firstOrNull()
|
||||
//
|
||||
// processLocalAccount(localAccount)
|
||||
//
|
||||
// } else {
|
||||
// _navigationUIState.getAndUpdate {
|
||||
// NavigationUIState.StartupPhoenix
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user