Send welcome event after relay acknowledgement...

This commit is contained in:
Kgothatso Ngako
2026-07-06 15:39:05 +02:00
parent 463141a766
commit bac2b783ee
5 changed files with 95 additions and 52 deletions

View File

@@ -10,6 +10,9 @@ import fr.acinq.bitcoin.PublicKey
@Dao
interface MarmotCommitResultDao {
@Query("SELECT * FROM MarmotCommitResult WHERE id = :nostrEventId")
suspend fun getMarmotCommitRequestById(nostrEventId: HexKey): MarmotCommitResult?
@Upsert
suspend fun upsert(marmotCommitResult: MarmotCommitResult)
}

View File

@@ -206,6 +206,7 @@ abstract class MarmotOutboundDao(
framedCommitBytes = commitResult.preCommitExporterSecret,
groupInfoBytes = commitResult.groupInfoBytes,
userPublicKey = userPublicKey,
peerKeyPackageEventId = peerKeyPackage.id,
createdAt = Instant.fromEpochSeconds(commitEvent.createdAt)
)
)
@@ -222,55 +223,66 @@ abstract class MarmotOutboundDao(
if (isOneMemberInitialGroupCreation) {
// Send welcome event to participant
commitResult.welcomeBytes?.let { welcomeBytes ->
val welcomeBase64 = Base64.encode(
source = welcomeBytes
)
// GiftWrap...
val welcomeEvent = WelcomeEvent.build(
welcomeBase64 = welcomeBase64,
keyPackageEventId = peerKeyPackage.id,
relays = relays.map { NormalizedRelayUrl(it) },
deliveryWelcome(
nostrGroupId = nostrGroupId,
)
// Compute welcomeEventId
val welcomeEventId = EventHasher.hashId(
pubKey = userPublicKey,
kind = welcomeEvent.kind,
tags = welcomeEvent.tags,
createdAt = welcomeEvent.createdAt,
content = welcomeEvent.content
)
GiftWrapPayload(
id = welcomeEventId,
publicKey = userPublicKey,
chatRoomId = nostrGroupId,
kind = welcomeEvent.kind,
tags = welcomeEvent.tags,
content = welcomeEvent.content,
createdAt = Instant.fromEpochSeconds(welcomeEvent.createdAt)
)
// Save chatMessage for the invite...
database.chatMessageDao().upsert(
ChatMessage(
content = "Invited participant to chat", // use profile.humanReadable...
chatRoomId = nostrGroupId,
senderPublicKey = userPublicKey,
isUserMessage = true, // TODO: This is information message...
giftWrapPayloadId = welcomeEventId,
marmotGroupEventId = null,
marmotInnerEventId = null
)
userPublicKey = userPublicKey,
welcomeBytes = welcomeBytes,
peerKeyPackageEventId = peerKeyPackage.id,
relays = relays,
)
}
}
}
suspend fun deliveryWelcome(
nostrGroupId: HexKey,
userPublicKey: HexKey,
welcomeBytes: ByteArray,
peerKeyPackageEventId: HexKey,
relays: List<String>,
) {
val welcomeBase64 = Base64.encode(
source = welcomeBytes
)
// GiftWrap...
val welcomeEvent = WelcomeEvent.build(
welcomeBase64 = welcomeBase64,
keyPackageEventId = peerKeyPackageEventId,
relays = relays.map { NormalizedRelayUrl(it) },
nostrGroupId = nostrGroupId,
)
// Compute welcomeEventId
val welcomeEventId = EventHasher.hashId(
pubKey = userPublicKey,
kind = welcomeEvent.kind,
tags = welcomeEvent.tags,
createdAt = welcomeEvent.createdAt,
content = welcomeEvent.content
)
GiftWrapPayload(
id = welcomeEventId,
publicKey = userPublicKey,
chatRoomId = nostrGroupId,
kind = welcomeEvent.kind,
tags = welcomeEvent.tags,
content = welcomeEvent.content,
createdAt = Instant.fromEpochSeconds(welcomeEvent.createdAt)
)
// Save chatMessage for the invite...
database.chatMessageDao().upsert(
ChatMessage(
content = "Invited participant to chat", // use profile.humanReadable...
chatRoomId = nostrGroupId,
senderPublicKey = userPublicKey,
isUserMessage = true, // TODO: This is information message...
giftWrapPayloadId = welcomeEventId,
marmotGroupEventId = null,
marmotInnerEventId = null
)
)
}
private fun encryptedCommitEvent(

View File

@@ -41,6 +41,8 @@ data class MarmotCommitResult( // TODO: Rename this to GiftWrapPayload...
val userPublicKey: HexKey,
val peerKeyPackageEventId: HexKey,
val chatRoomId: HexKey,
val isOneMemberInitialGroupCreation: Boolean,

View File

@@ -1,5 +1,6 @@
package at.torch.compose.database.repository
import at.torch.compose.database.model.BroadcastNostrEventRequest
import at.torch.compose.database.model.UnsignedNostrEvent
import at.torch.compose.database.model.intermdiate.LocalAccount
import at.torch.compose.nostr.Relays
@@ -384,7 +385,7 @@ class DatabaseNostrRepository(
}
override suspend fun broadcastProcessed(
broadcastNostrEventRequest: at.torch.compose.database.model.BroadcastNostrEventRequest,
broadcastNostrEventRequest: BroadcastNostrEventRequest,
status: String
) {
database.broadcastNostrEventRequestDao().upsert(
@@ -414,6 +415,25 @@ class DatabaseNostrRepository(
)
)
}
database.marmotCommitResultDao().getMarmotCommitRequestById(
nostrEventId = broadcastNostrEventRequest.nostrEventId
)?.let { marmotCommitResult ->
if (marmotCommitResult.isOneMemberInitialGroupCreation.not()) {
database.chatRoomDao().findChatRoomById(marmotCommitResult.chatRoomId)?.let { localChatRoom ->
// TODO: Update status of participant Invitation.PENDING -> Invitation.SENT
marmotCommitResult.welcomeBytes?.let { welcomeBytes ->
database.marmotOutboundDao().deliveryWelcome(
nostrGroupId = marmotCommitResult.chatRoomId,
userPublicKey = marmotCommitResult.userPublicKey,
welcomeBytes = welcomeBytes,
peerKeyPackageEventId = marmotCommitResult.peerKeyPackageEventId,
relays = Relays.DefaultDMRelayList.map { it.url } // TODO: Get localChatRoom relays...
)
}
}
}
}
}
}
@@ -742,7 +762,7 @@ class DatabaseNostrRepository(
return database.relayDao().observePublicKeyRelays(publicKey)
}
override suspend fun scheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List<at.torch.compose.database.model.BroadcastNostrEventRequest>) {
override suspend fun scheduleBroadcastNostrEventRequests(broadcastNostrEventRequests: List<BroadcastNostrEventRequest>) {
database.broadcastNostrEventRequestDao().insert(
broadcastNostrEventRequests
)

View File

@@ -7,7 +7,13 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import at.torch.compose.database.model.SynchronizeNostrEventRequest
import at.torch.compose.database.model.types.SynchronizationFilter
import at.torch.compose.network.dto.toRelayDTO
import at.torch.compose.network.relays.RelaysSocketManager
import at.torch.compose.network.sockets.NostrIncomingMessage
import at.torch.compose.network.sockets.NostrSocketClientFactory
import at.torch.compose.repository.CachingImportRepository
import at.torch.compose.repository.ChatRepository
import at.torch.compose.repository.NostrRepository
import at.torch.compose.repository.RelayRepository
import co.touchlab.kermit.Logger
import com.vitorpamplona.negentropy.Negentropy
import com.vitorpamplona.negentropy.storage.StorageVector
@@ -35,16 +41,16 @@ import kotlin.time.Duration.Companion.milliseconds
class SynchronizationViewModel(
val activeWalletStateFlow: StateFlow<ActiveWallet?>,
val nostrRepository: at.torch.compose.repository.NostrRepository,
val chatRepository: at.torch.compose.repository.ChatRepository,
val relayRepository: at.torch.compose.repository.RelayRepository,
val nostrRepository: NostrRepository,
val chatRepository: ChatRepository,
val relayRepository: RelayRepository,
val scope: CoroutineScope,
): ViewModel() {
val relaysSocketManager = at.torch.compose.network.relays.RelaysSocketManager(
val relaysSocketManager = RelaysSocketManager(
activeWalletStateFlow = activeWalletStateFlow,
nostrSocketClientFactory = at.torch.compose.network.sockets.NostrSocketClientFactory,
cachingImportRepository = at.torch.compose.repository.CachingImportRepository.NO_OP_CACHING_IMPORT_REPOSITORY,
nostrSocketClientFactory = NostrSocketClientFactory,
cachingImportRepository = CachingImportRepository.NO_OP_CACHING_IMPORT_REPOSITORY,
relayRepository = relayRepository,
)
@@ -55,9 +61,9 @@ class SynchronizationViewModel(
fun factory(
activeWalletStateFlow: StateFlow<ActiveWallet?>,
nostrRepository: at.torch.compose.repository.NostrRepository,
chatRepository: at.torch.compose.repository.ChatRepository,
relayRepository: at.torch.compose.repository.RelayRepository,
nostrRepository: NostrRepository,
chatRepository: ChatRepository,
relayRepository: RelayRepository,
scope: CoroutineScope,
): ViewModelProvider.Factory = viewModelFactory {
initializer {