Introduce giftwrap work flow

This commit is contained in:
Kgothatso Ngako
2026-06-03 01:14:49 +02:00
parent 9f0da86584
commit 62582e7569
15 changed files with 265 additions and 70 deletions

View File

@@ -55,7 +55,6 @@ import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventResult
import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent
import ac.cord.auxiliary.compose.database.model.Zap
import androidx.room3.AutoMigration
import androidx.room3.Database
import androidx.room3.RoomDatabase
import androidx.room3.TypeConverters
@@ -111,7 +110,7 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun giftWrapSealDao(): GiftWrapSealDao
abstract fun giftWrapUnsignedDao(): GiftWrapPayloadDao
abstract fun giftWrapPayloadDao(): GiftWrapPayloadDao
abstract fun inReplyToRelationDao(): InReplyToRelationDao

View File

@@ -4,13 +4,16 @@ import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
import androidx.room3.Dao
import androidx.room3.Query
import androidx.room3.Upsert
import kotlinx.coroutines.flow.Flow
@Dao
interface GiftWrapPayloadDao {
@Query("SELECT * FROM GiftWrapPayload")
fun getAllGiftWrapUnsigned(): List<GiftWrapPayload>
@Upsert
suspend fun upsert(giftWrapPayload: GiftWrapPayload)
@Query("SELECT * FROM GiftWrapPayload WHERE publicKey = :publicKey AND giftWrapSealId IS NULL")
fun observeUnsealedGiftWrapPayloads(publicKey: String): Flow<GiftWrapPayload?>
@Upsert
suspend fun upsert(giftWrapPayload: GiftWrapPayload): Long
}

View File

@@ -347,11 +347,19 @@ abstract class NostrDao(
giftWrapSeal.decryptGiftWrapPayload(
activeUserPrivateKey
)?.let { giftWrapPayload ->
database.giftWrapUnsignedDao().upsert(
giftWrapPayload
)?.let { decryptedGiftWrapPayload ->
val result = database.giftWrapPayloadDao().upsert(
decryptedGiftWrapPayload
)
val giftWrapPayload = if (result != -1L) {
decryptedGiftWrapPayload.copy(
id = result
)
} else {
decryptedGiftWrapPayload
}
val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey()
logger.d("ChatRoomId: $chatRoomId")
@@ -368,7 +376,7 @@ abstract class NostrDao(
userPublicKey = SeedManager.activeKeyPair().pubKey.toHex(),
subject = null, // TODO: Get subject from tags...
createdAt = giftWrapPayload.createdAt,
initialGiftWrapUnsignedId = giftWrapPayload.id
initialGiftWrapPayloadId = giftWrapPayload.id
)
)

View File

@@ -1,7 +1,11 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.AuxDatabase
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.GiftWrapMessage
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.managers.SeedManager
@@ -9,6 +13,11 @@ import androidx.room3.Dao
import androidx.room3.Transaction
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import kotlin.time.Instant
@Dao
@@ -64,6 +73,66 @@ abstract class NostrNip17Dao(
return null
}
@Transaction
open suspend fun persistAndBroadcastGiftWrap(
sealedRumorEvent: SealedRumorEvent,
giftWrapEvent: GiftWrapEvent,
receiverPublicKey: PTag
) {
val nostrEvent = NostrEvent(
id = giftWrapEvent.id,
content = giftWrapEvent.content,
createdAt = Instant.fromEpochSeconds(giftWrapEvent.createdAt),
sig = giftWrapEvent.sig,
pubKey = giftWrapEvent.pubKey,
tags = giftWrapEvent.tags,
kind = giftWrapEvent.kind
)
database.nostrEventDao().insert(nostrEvent)
// TODO: Get ChatMessageRelayListEvent.KIND for receiverPublicKey.pubkey
// receiverPublicKey.relayHint?.url?.let { url ->
// database.broadcastNostrEventRequestDao().insert(
// listOf(
// BroadcastNostrEventRequest(
// nostrEventId = nostrEvent.id,
// relayURL = url
// )
// )
// )
// }
database.giftWrapMessageDao().upsert(
GiftWrapMessage(
id = giftWrapEvent.id,
nostrEventId = nostrEvent.id,
content = giftWrapEvent.content,
createdAt = Instant.fromEpochSeconds(giftWrapEvent.createdAt),
signature = giftWrapEvent.sig,
publicKey = giftWrapEvent.pubKey,
kind = giftWrapEvent.kind,
receiverPublicKey = receiverPublicKey.pubKey,
receiverRelayHit = receiverPublicKey.relayHint?.url
)
)
val giftWrapSeal = GiftWrapSeal(
id = sealedRumorEvent.id,
publicKey = sealedRumorEvent.pubKey,
content = sealedRumorEvent.content,
kind = sealedRumorEvent.kind,
tags = sealedRumorEvent.tags,
signature = sealedRumorEvent.sig,
giftWrapMessageId = giftWrapEvent.id
)
database.giftWrapSealDao().upsert(giftWrapSeal)
}
companion object {
const val TAG = "NostrNip17Dao"
}

View File

@@ -34,8 +34,8 @@ data class ChatMessage(
val senderPublicKey: String,
val isUserMessage: Boolean,
val giftWrapPayloadId: Long,
val chatRoomId: String,
val giftWrapPayloadId: String? = null,
val replyToMessageId: Long? = null,
val quotedMessageId: Long? = null,

View File

@@ -28,7 +28,7 @@ import kotlin.time.Instant
ForeignKey(
entity = GiftWrapPayload::class,
parentColumns = ["id"],
childColumns = ["initialGiftWrapUnsignedId"],
childColumns = ["initialGiftWrapPayloadId"],
onDelete = ForeignKey.CASCADE,
),
],
@@ -69,7 +69,7 @@ data class ChatRoom(
// TODO: Add a LastChatMessageRelation
// val lastChatMessageId: String? = null,
// Might want to focus on the other last messages...
val initialGiftWrapUnsignedId: String? = null,
val initialGiftWrapPayloadId: Long? = null,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = createdAt,
@@ -77,7 +77,7 @@ data class ChatRoom(
override val viewedAt: Instant? = null,
override val deletedAt: Instant? = null,
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity {
companion object {
const val TAG = "ChatRoom"

View File

@@ -30,8 +30,8 @@ import kotlin.time.Instant
],
)
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
@PrimaryKey
val id: HexKey,
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
/**
* Sender Public Key

View File

@@ -96,7 +96,7 @@ data class GiftWrapSeal(
logger.d("giftWrapPayload: ${giftWrapPayload.toJson()}")
return GiftWrapPayload(
id = giftWrapPayload.id,
giftWrapSealId = id,
publicKey = giftWrapPayload.pubKey,
kind = giftWrapPayload.kind,
tags = giftWrapPayload.tags,

View File

@@ -8,9 +8,15 @@ import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatMessage
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.repository.ChatRepository
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import fr.acinq.lightning.utils.UUID
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -49,15 +55,32 @@ class DatabaseChatRepository(
override suspend fun sendChatMessage(
text: String,
chatRoom: ChatRoom
localChatRoom: LocalChatRoom,
messageType: Kind
) {
logger.i("sendChatMessage($text): $chatRoom")
logger.i("sendChatMessage($text): $localChatRoom")
val receiverTags = localChatRoom.participants.filter { participant ->
participant.participant.participantPublicKey != localChatRoom.chatRoom.userPublicKey
}.map { participant ->
PTag.assemble(participant.participant.participantPublicKey, null) // TODO: Get the relays associated with the participant...
}
val giftWrapPayloadId = database.giftWrapPayloadDao().upsert(
GiftWrapPayload(
kind = messageType,
tags = receiverTags.toTypedArray(), // TODO: Get participants...
content = text,
publicKey = localChatRoom.chatRoom.userPublicKey
)
)
database.chatMessageDao().upsert(
ChatMessage(
content = text,
chatRoomId = chatRoom.id,
senderPublicKey = chatRoom.userPublicKey,
isUserMessage = true
chatRoomId = localChatRoom.chatRoom.id,
senderPublicKey = localChatRoom.chatRoom.userPublicKey,
isUserMessage = true,
giftWrapPayloadId = giftWrapPayloadId
)
)
}
@@ -66,9 +89,67 @@ class DatabaseChatRepository(
publicKey: String,
subject: String
): ChatRoom? {
// TODO: Update chatRoom with subject...
return null
}
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?> {
return database.giftWrapPayloadDao().observeUnsealedGiftWrapPayloads(publicKey)
}
override suspend fun sealGiftWrapPayload(
giftWrapPayload: GiftWrapPayload,
nostrSignerSync: NostrSignerSync
) {
val giftWrapPayloadEvent = nostrSignerSync.signNormal<Event>(
createdAt = giftWrapPayload.createdAt.epochSeconds,
kind = giftWrapPayload.kind,
tags = giftWrapPayload.tags,
content = giftWrapPayload.content
)
giftWrapPayload.tags.taggedUsers().forEachIndexed { index, receiverPublicKey ->
val payload = giftWrapPayloadEvent.toJson()
val sealedRumorEvent = nostrSignerSync.signNormal<SealedRumorEvent>(
createdAt = TimeUtils.randomWithTwoDays(),
kind = SealedRumorEvent.KIND,
tags = emptyArray(),
content = nostrSignerSync.nip44Encrypt(
plaintext = payload,
toPublicKey = receiverPublicKey.pubKey
)
)
val giftWrapEvent = nostrSignerSync.signNormal<GiftWrapEvent>(
createdAt = TimeUtils.randomWithTwoDays(),
kind = GiftWrapEvent.KIND,
tags = arrayOf(
PTag.assemble(receiverPublicKey.pubKey, receiverPublicKey.relayHint)
),
content = nostrSignerSync.nip44Encrypt(
plaintext = sealedRumorEvent.toJson(),
toPublicKey = receiverPublicKey.pubKey
)
)
database.nostrNip17Dao().persistAndBroadcastGiftWrap(
sealedRumorEvent = sealedRumorEvent,
giftWrapEvent = giftWrapEvent,
receiverPublicKey = receiverPublicKey
)
if (index == 0 && giftWrapPayload.giftWrapSealId == null) {
// Only update the giftWrapPayload.giftWrapSealId on the first seal...
database.giftWrapPayloadDao().upsert(
giftWrapPayload.copy(
giftWrapSealId = sealedRumorEvent.id
)
)
}
}
}
companion object {
const val TAG = "DatabaseChatRepository"
}

View File

@@ -1,8 +1,14 @@
package ac.cord.auxiliary.compose.repository
import ac.cord.auxiliary.compose.database.model.ChatMessage
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.GiftWrapPayload
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatMessage
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -17,10 +23,18 @@ interface ChatRepository {
suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String? = null): LocalChatRoom?
suspend fun sendChatMessage(text: String, chatRoom: ChatRoom)
suspend fun sendChatMessage(
text: String,
localChatRoom: LocalChatRoom,
messageType: Kind = ChatMessageEvent.KIND
)
fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom?
suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?>
suspend fun sealGiftWrapPayload(giftWrapPayload: GiftWrapPayload, nostrSignerSync: NostrSignerSync)
companion object {
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
override suspend fun getChatRoomListByPublicKey(publicKey: String): List<LocalChatRoom> {
@@ -46,7 +60,8 @@ interface ChatRepository {
override suspend fun sendChatMessage(
text: String,
chatRoom: ChatRoom
localChatRoom: LocalChatRoom,
messageType: Kind
) {
// TODO("Not yet implemented")
}
@@ -54,6 +69,17 @@ interface ChatRepository {
override fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom? {
return null
}
override suspend fun observeUnsealedGiftWrapPayloads(publicKey: HexKey): Flow<GiftWrapPayload?> {
TODO("Not yet implemented")
}
override suspend fun sealGiftWrapPayload(
giftWrapPayload: GiftWrapPayload,
nostrSignerSync: NostrSignerSync
) {
TODO("Not yet implemented")
}
}
}
}

View File

@@ -1,49 +1,31 @@
package ac.cord.auxiliary.compose.ui.composable
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.Participant
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.repository.ChatRepository
import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileColor
import ac.cord.auxiliary.compose.ui.theme.AuxTheme
import ac.cord.auxiliary.compose.ui.view.model.ChatRoomDetailMessageListViewModel
import ac.cord.auxiliary.compose.ui.view.model.ChatRoomDetailViewModel
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailMessageListUIState
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailUIState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AlternateEmail
import androidx.compose.material.icons.filled.BrowseGallery
import androidx.compose.material.icons.filled.Collections
import androidx.compose.material.icons.filled.Email
import androidx.compose.material.icons.filled.EmojiFlags
import androidx.compose.material.icons.filled.InsertEmoticon
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Pin
import androidx.compose.material.icons.filled.PinDrop
import androidx.compose.material.icons.filled.RecordVoiceOver
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FlexibleBottomAppBar
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -52,25 +34,17 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlin.time.Clock
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
@@ -300,7 +274,7 @@ private fun ChatRoomDetailScreenPreview() {
id = "",
userPublicKey = "",
subject = "Message title",
initialGiftWrapUnsignedId = ""
initialGiftWrapPayloadId = 0
),
)
),

View File

@@ -105,6 +105,7 @@ fun AuxNavHost(
factory = NavigationViewModel.factory(
initialNavigationUIState = NavigationUIState.Loading,
nostrRepository = databaseNostrRepository,
chatRepository = databaseChatRepository,
relayRepository = databaseNostrRepository,
scope = applicationIOScope
)

View File

@@ -260,7 +260,7 @@ class ChatRoomDetailMessageListViewModel(
viewModelScope.launch(Dispatchers.IO) {
chatRepository.sendChatMessage(
text = text,
chatRoom = localChatRoom.chatRoom
localChatRoom = localChatRoom
)
}

View File

@@ -1,11 +1,11 @@
package ac.cord.auxiliary.compose.ui.view.model
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
import ac.cord.auxiliary.compose.database.model.GiftWrapSeal
import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.SynchronizeNostrEventRequest
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
import ac.cord.auxiliary.compose.managers.SeedManager
import ac.cord.auxiliary.compose.network.dto.RelayDTO
import ac.cord.auxiliary.compose.network.dto.toRelayDTO
import ac.cord.auxiliary.compose.network.relays.RelayPool.Companion.PUBLISH_TIMEOUT
import ac.cord.auxiliary.compose.network.relays.RelaysSocketManager
@@ -13,6 +13,7 @@ import ac.cord.auxiliary.compose.network.sockets.NostrIncomingMessage
import ac.cord.auxiliary.compose.network.sockets.NostrSocketClientFactory
import ac.cord.auxiliary.compose.nostr.Relays
import ac.cord.auxiliary.compose.repository.CachingImportRepository
import ac.cord.auxiliary.compose.repository.ChatRepository
import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.repository.RelayRepository
import ac.cord.auxiliary.compose.ui.view.state.NavigationUIState
@@ -29,9 +30,15 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CloseCmd
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip77Negentropy.NegCloseCmd
import com.vitorpamplona.quartz.nip77Negentropy.NegOpenCmd
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
@@ -52,6 +59,7 @@ import kotlin.time.Instant
class NavigationViewModel(
initialNavigationUIState: NavigationUIState,
val nostrRepository: NostrRepository,
val chatRepository: ChatRepository,
val relayRepository: RelayRepository,
val scope: CoroutineScope,
): ViewModel() {
@@ -70,6 +78,7 @@ class NavigationViewModel(
fun factory(
initialNavigationUIState: NavigationUIState,
nostrRepository: NostrRepository,
chatRepository: ChatRepository,
relayRepository: RelayRepository,
scope: CoroutineScope,
): ViewModelProvider.Factory = viewModelFactory {
@@ -77,6 +86,7 @@ class NavigationViewModel(
NavigationViewModel(
initialNavigationUIState = initialNavigationUIState,
nostrRepository = nostrRepository,
chatRepository = chatRepository,
relayRepository = relayRepository,
scope = scope
)
@@ -93,7 +103,7 @@ class NavigationViewModel(
init {
observeUnsignedNostrEvents()
// TODO: observePendingUnsignedChatMessages
observeUnsealedGiftWrapPayloads()
observePendingBroadcastNostrEventRequests()
observeProfile()
observePendingSyncNostrEventRequests()
@@ -141,6 +151,29 @@ class NavigationViewModel(
}
}
private fun observeUnsealedGiftWrapPayloads() {
val tempSigner = NostrSignerSync(
SeedManager.activeKeyPair()
)
scope.launch(Dispatchers.IO) {
logger.i { "observeUnsealedGiftWrapPayloads" }
chatRepository.observeUnsealedGiftWrapPayloads(
publicKey = SeedManager.activePublicKey().toHexKey()
).distinctUntilChanged().collect { giftWrapPayloadOrNull ->
scope.launch(Dispatchers.IO) {
giftWrapPayloadOrNull?.let { giftWrapPayload ->
logger.d("seal and deliver giftWrapPayload: $giftWrapPayload")
chatRepository.sealGiftWrapPayload(
giftWrapPayload,
nostrSignerSync = tempSigner
)
}
}
}
}
}
private fun observePendingSyncNostrEventRequests() {
logger.i { "observePendingSyncNostrEventRequests" }