Add deterministic id on MLS related models
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "f33937ebefe679fe03b71b3930472139",
|
||||
"identityHash": "8dd191b21e518a19f39b50a5f5b4a432",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "BroadcastNostrEventReceipt",
|
||||
@@ -282,7 +282,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `senderPublicKey` TEXT NOT NULL, `isUserMessage` INTEGER NOT NULL, `giftWrapPayloadId` INTEGER NOT NULL, `chatRoomId` TEXT NOT NULL, `replyToMessageId` INTEGER, `quotedMessageId` INTEGER, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`giftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `senderPublicKey` TEXT NOT NULL, `isUserMessage` INTEGER NOT NULL, `giftWrapPayloadId` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `replyToMessageId` INTEGER, `quotedMessageId` INTEGER, `content` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, FOREIGN KEY(`chatRoomId`) REFERENCES `ChatRoom`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`giftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -305,7 +305,7 @@
|
||||
{
|
||||
"fieldPath": "giftWrapPayloadId",
|
||||
"columnName": "giftWrapPayloadId",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@@ -608,7 +608,7 @@
|
||||
},
|
||||
{
|
||||
"tableName": "ChatRoom",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
@@ -635,7 +635,7 @@
|
||||
{
|
||||
"fieldPath": "initialGiftWrapPayloadId",
|
||||
"columnName": "initialGiftWrapPayloadId",
|
||||
"affinity": "INTEGER"
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "createdAt",
|
||||
@@ -893,12 +893,12 @@
|
||||
},
|
||||
{
|
||||
"tableName": "GiftWrapPayload",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `quotedEventId` TEXT, `giftWrapSealId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, FOREIGN KEY(`giftWrapSealId`) REFERENCES `GiftWrapSeal`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `kind` INTEGER NOT NULL, `tags` TEXT NOT NULL, `content` TEXT NOT NULL, `quotedEventId` TEXT, `giftWrapSealId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`giftWrapSealId`) REFERENCES `GiftWrapSeal`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@@ -971,7 +971,7 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
@@ -1117,12 +1117,12 @@
|
||||
},
|
||||
{
|
||||
"tableName": "MarmotKeyPackage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `publicKey` TEXT NOT NULL, `tlsEncodedMarmotKeyPackage` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, FOREIGN KEY(`publicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `tlsEncodedMarmotKeyPackage` BLOB NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`publicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@@ -1167,7 +1167,7 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
@@ -1188,12 +1188,12 @@
|
||||
},
|
||||
{
|
||||
"tableName": "MarmotKeyPackageBundle",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `publicKey` TEXT NOT NULL, `tlsEncodedMarmotKeyPackage` TEXT NOT NULL, `ncryptsecInitPrivateKey` TEXT NOT NULL, `ncryptsecEncryptionPrivateKey` TEXT NOT NULL, `ncryptsecSignaturePrivateKey` TEXT NOT NULL, `consumed` INTEGER NOT NULL, `rotated` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER)",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `tlsEncodedMarmotKeyPackage` BLOB NOT NULL, `ncryptsecInitPrivateKey` TEXT NOT NULL, `ncryptsecEncryptionPrivateKey` TEXT NOT NULL, `ncryptsecSignaturePrivateKey` TEXT NOT NULL, `consumed` INTEGER NOT NULL, `rotated` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@@ -1205,7 +1205,7 @@
|
||||
{
|
||||
"fieldPath": "tlsEncodedMarmotKeyPackage",
|
||||
"columnName": "tlsEncodedMarmotKeyPackage",
|
||||
"affinity": "TEXT",
|
||||
"affinity": "BLOB",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
@@ -1268,7 +1268,7 @@
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
@@ -3044,7 +3044,7 @@
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f33937ebefe679fe03b71b3930472139')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8dd191b21e518a19f39b50a5f5b4a432')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Transaction
|
||||
import androidx.room3.Upsert
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
@@ -17,7 +18,7 @@ interface ChatMessageDao {
|
||||
fun getChatMessagesByChatRoomId(chatRoomId: String): List<at.torch.compose.database.model.intermdiate.LocalChatMessage>
|
||||
|
||||
@Query("SELECT * FROM ChatMessage WHERE giftWrapPayloadId = :giftWrapPayloadId ORDER BY createdAt DESC")
|
||||
fun getChatMessagesByGiftWrapPayloadId(giftWrapPayloadId: Long): at.torch.compose.database.model.ChatMessage?
|
||||
fun getChatMessagesByGiftWrapPayloadId(giftWrapPayloadId: HexKey): at.torch.compose.database.model.ChatMessage?
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(chatMessage: at.torch.compose.database.model.ChatMessage): Long
|
||||
|
||||
@@ -14,5 +14,5 @@ interface GiftWrapPayloadDao {
|
||||
fun observeUnsealedGiftWrapPayloads(publicKey: String): Flow<at.torch.compose.database.model.GiftWrapPayload?>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(giftWrapPayload: at.torch.compose.database.model.GiftWrapPayload): Long
|
||||
suspend fun upsert(giftWrapPayload: at.torch.compose.database.model.GiftWrapPayload)
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import at.torch.compose.database.model.MarmotKeyPackageBundle
|
||||
|
||||
@Dao
|
||||
interface MarmotKeyPackageBundleDao {
|
||||
@Query("SELECT * FROM MarmotKeyPackageBundle WHERE id = :keyPackageEventId ORDER BY createdAt DESC")
|
||||
fun getMarmotKeyPackageBundleByKeyPackageEventIdId(keyPackageEventId: String): MarmotKeyPackageBundle?
|
||||
@Query("SELECT * FROM MarmotKeyPackageBundle WHERE id = :id ORDER BY createdAt DESC")
|
||||
fun getMarmotKeyPackageBundleById(id: String): MarmotKeyPackageBundle?
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(marmotKeyPackageBundle: MarmotKeyPackageBundle)
|
||||
|
||||
@@ -18,10 +18,8 @@ import at.torch.compose.database.model.types.SynchronizationFilter
|
||||
import at.torch.compose.exceptions.GiftWrapImpersonationException
|
||||
import at.torch.compose.exceptions.GiftWrapSealDecryptionException
|
||||
import at.torch.compose.exceptions.GiftWrapUnsealException
|
||||
import at.torch.compose.exceptions.MarmotMissingKeyPackageEventIdException
|
||||
import at.torch.compose.exceptions.MarmotMissingNostrGroupDataExtension
|
||||
import at.torch.compose.exceptions.MarmotNoMatchingKeyPackageBundleException
|
||||
import at.torch.compose.exceptions.MarmotWelcomeMismatchedGroupIdException
|
||||
import at.torch.compose.exceptions.MarmotMissingKeyPackageBundleException
|
||||
import at.torch.compose.exceptions.MarmotWelcomeEventMissingKeyPackageEventIdException
|
||||
import at.torch.compose.extensions.toHex
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent
|
||||
@@ -39,6 +37,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlin.io.encoding.Base64
|
||||
@@ -127,7 +126,8 @@ abstract class NostrDao(
|
||||
logger.i("This is a placeholder profile might need to get synced...: ${profile.publicKey}")
|
||||
// Setup the sync here...
|
||||
} else {
|
||||
return
|
||||
// TODO: Check if event got indexed...
|
||||
// return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,257 +388,397 @@ abstract class NostrDao(
|
||||
logger.e("Impersonation", giftWrapImpersonation)
|
||||
throw giftWrapImpersonation // Once this is thrown the database transaction fails and nothing gets saved to the DB...
|
||||
}
|
||||
val result = database.giftWrapPayloadDao().upsert(
|
||||
|
||||
database.giftWrapPayloadDao().upsert(
|
||||
decryptedGiftWrapPayload
|
||||
)
|
||||
|
||||
val giftWrapPayload = if (result != -1L) {
|
||||
decryptedGiftWrapPayload.copy(
|
||||
id = result
|
||||
if (decryptedGiftWrapPayload.kind == ChatMessageEvent.KIND) {
|
||||
logger.d("chatMessageEvent: $decryptedGiftWrapPayload")
|
||||
// Handle normal nip-17 messages...
|
||||
val localChatRoom = database.chatRoomDao().findChatRoomById(
|
||||
decryptedGiftWrapPayload.chatRoomId
|
||||
)
|
||||
} else {
|
||||
decryptedGiftWrapPayload
|
||||
}
|
||||
|
||||
// TODO: Handle MLS message/groups
|
||||
if (giftWrapPayload.kind == WelcomeEvent.KIND) {
|
||||
logger.d("Welcome Event")
|
||||
if (localChatRoom == null) {
|
||||
val userPublicKey = activeKeyPair.pubKey.toHex()
|
||||
database.chatRoomDao().upsert(
|
||||
ChatRoom(
|
||||
id = decryptedGiftWrapPayload.chatRoomId,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = decryptedGiftWrapPayload.parseSubject(),
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
initialGiftWrapPayloadId = decryptedGiftWrapPayload.id,
|
||||
mlsGroupState = null
|
||||
)
|
||||
)
|
||||
|
||||
// Add participants...
|
||||
val participants = decryptedGiftWrapPayload.participantPTags(
|
||||
NormalizedRelayUrl(relayURL) // TODO: Get relayURL for publicKey profile...
|
||||
).map {
|
||||
Participant(
|
||||
participantPublicKey = it.pubKey,
|
||||
chatRoomId = decryptedGiftWrapPayload.chatRoomId,
|
||||
relayHint = it.relayHint?.url
|
||||
)
|
||||
}
|
||||
|
||||
// Sync missing participant profiles...
|
||||
participants.forEach { participant ->
|
||||
val giftWrapParticipantProfile =
|
||||
database.profileDao()
|
||||
.getProfileByPublicKey(participant.participantPublicKey)
|
||||
|
||||
if (giftWrapParticipantProfile == null) {
|
||||
// Save a placeholder
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING...",
|
||||
publicKey = participant.participantPublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
|
||||
val recommendRelayUrl =
|
||||
giftWrapMessage.receiverRelayHit
|
||||
|
||||
if (recommendRelayUrl != null) {
|
||||
if (profilePublicKeysToSync[recommendRelayUrl] == null) {
|
||||
profilePublicKeysToSync[recommendRelayUrl] =
|
||||
mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[recommendRelayUrl]?.add(
|
||||
participant.participantPublicKey
|
||||
)
|
||||
} else {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] =
|
||||
mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(
|
||||
participant.participantPublicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.participantDao().upsert(
|
||||
participants
|
||||
)
|
||||
|
||||
participants.filter { it.participantPublicKey != userPublicKey }
|
||||
.forEach { participant ->
|
||||
val publicKey = participant.participantPublicKey
|
||||
|
||||
val chatMessageRelayListEvent =
|
||||
database.nostrEventDao()
|
||||
.getAuthoredNostrEvents(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND
|
||||
),
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
since = GENESIS_AT,
|
||||
limit = 5
|
||||
).firstOrNull()?.let { nostrEvent ->
|
||||
if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) {
|
||||
logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent")
|
||||
null
|
||||
} else {
|
||||
ChatMessageRelayListEvent(
|
||||
id = nostrEvent.id,
|
||||
tags = nostrEvent.tags,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
content = nostrEvent.content,
|
||||
createdAt = nostrEvent.createdAt.epochSeconds,
|
||||
sig = nostrEvent.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
// TODO: Get relayHint form participant...
|
||||
if (profilePublicKeysToSync.containsKey(
|
||||
relayURL
|
||||
)
|
||||
) {
|
||||
profilePublicKeysToSync[relayURL] =
|
||||
mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(
|
||||
participant.participantPublicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decryptedGiftWrapPayload.parseSubject()?.let { subject ->
|
||||
database.chatRoomDao().upsert(
|
||||
localChatRoom.chatRoom.copy(
|
||||
subject = subject,
|
||||
updatedAt = decryptedGiftWrapPayload.createdAt
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} else if (decryptedGiftWrapPayload.kind == WelcomeEvent.KIND) {
|
||||
|
||||
val welcomeEvent = WelcomeEvent(
|
||||
id = "", // TODO: Need the ID as string in decryptedGiftWrapPayload...
|
||||
id = decryptedGiftWrapPayload.id, // TODO: Need the ID as string in decryptedGiftWrapPayload...
|
||||
pubKey = decryptedGiftWrapPayload.publicKey,
|
||||
content = decryptedGiftWrapPayload.content,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt.epochSeconds,
|
||||
tags = decryptedGiftWrapPayload.tags,
|
||||
sig = ""
|
||||
)
|
||||
logger.d("welcomeEvent: $welcomeEvent")
|
||||
|
||||
welcomeEvent.nostrGroupId()?.let { chatRoomId ->
|
||||
val localChatRoom = database.chatRoomDao().findChatRoomById(
|
||||
chatRoomId
|
||||
// TODO: Find keypackage and decrypt welcome...
|
||||
// TODO: Get keyPackage from
|
||||
val keyPackageEventId = welcomeEvent.keyPackageEventId() ?: throw MarmotWelcomeEventMissingKeyPackageEventIdException(
|
||||
"Couldn't find keyPackageEventId for $welcomeEvent"
|
||||
)
|
||||
val marmotKeyPackageBundle = database.marmotKeyPackageBundleDao().getMarmotKeyPackageBundleById(
|
||||
keyPackageEventId
|
||||
) ?: throw MarmotMissingKeyPackageBundleException(
|
||||
"Couldn't find marmotKeyPackageBundle for $welcomeEvent"
|
||||
)
|
||||
|
||||
val welcomeBytes = Base64.decode(
|
||||
welcomeEvent.welcomeBase64()
|
||||
)
|
||||
|
||||
val initPrivateKey = marmotKeyPackageBundle.ncryptsecInitPrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
val signaturePrivateKey = marmotKeyPackageBundle.ncryptsecSignaturePrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
val encryptionPrivateKey = marmotKeyPackageBundle.ncryptsecEncryptionPrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
|
||||
val tlsReader = TlsReader(
|
||||
data = marmotKeyPackageBundle.tlsEncodedMarmotKeyPackage
|
||||
)
|
||||
|
||||
val group = MlsGroup.processWelcome(
|
||||
welcomeBytes,
|
||||
bundle = KeyPackageBundle(
|
||||
keyPackage = MlsKeyPackage.decodeTls(
|
||||
tlsReader
|
||||
),
|
||||
initPrivateKey = initPrivateKey,
|
||||
signaturePrivateKey = signaturePrivateKey,
|
||||
encryptionPrivateKey = encryptionPrivateKey
|
||||
)
|
||||
)
|
||||
|
||||
val nostrGroupId = group.groupId.toHex()
|
||||
|
||||
val localChatRoom = database.chatRoomDao().findChatRoomById(
|
||||
nostrGroupId
|
||||
)
|
||||
|
||||
if (localChatRoom == null) {
|
||||
|
||||
logger.d("processWelcome: welcomeBytes=${welcomeBytes.size}B looking up KeyPackage by ref=${keyPackageEventId.take(8)}…")
|
||||
|
||||
|
||||
// TODO: Save the chat room We can now s
|
||||
val userPublicKey = activeKeyPair.pubKey.toHex()
|
||||
database.chatRoomDao().upsert(
|
||||
ChatRoom(
|
||||
id = nostrGroupId,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = group.currentMarmotData()?.name,
|
||||
initialGiftWrapPayloadId = decryptedGiftWrapPayload.id,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
mlsGroupState = group.saveState().encodeTls().toHex()
|
||||
)
|
||||
)
|
||||
|
||||
if (localChatRoom == null) {
|
||||
|
||||
val welcomeBytes = Base64.decode(
|
||||
welcomeEvent.welcomeBase64()
|
||||
database.marmotKeyPackageBundleDao().upsert(
|
||||
marmotKeyPackageBundle.copy(
|
||||
consumed = true
|
||||
)
|
||||
val keyPackageEventId = welcomeEvent.keyPackageEventId()
|
||||
if (keyPackageEventId == null) {
|
||||
throw MarmotMissingKeyPackageEventIdException(
|
||||
"WelcomeEvent ${welcomeEvent.id} missing KeyPackage event ID tag"
|
||||
)
|
||||
|
||||
// TODO: Add participants... by processing group.members()
|
||||
val normalizedRelayUrl = NormalizedRelayUrl(relayURL)
|
||||
|
||||
val participants = group.members().mapNotNull { (leafIndex, leafNode) ->
|
||||
val pubkey =
|
||||
when (val cred = leafNode.credential) {
|
||||
is Credential.Basic -> cred.identity.toHexKey()
|
||||
else -> null
|
||||
}
|
||||
if (pubkey != null) {
|
||||
Participant(
|
||||
participantPublicKey = pubkey,
|
||||
chatRoomId = nostrGroupId,
|
||||
relayHint = normalizedRelayUrl.url
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
logger.d("processWelcome: welcomeBytes=${welcomeBytes.size}B looking up KeyPackage by ref=${keyPackageEventId.take(8)}…")
|
||||
// Sync missing participant profiles...
|
||||
participants.forEach { participant ->
|
||||
val giftWrapParticipantProfile = database.profileDao().getProfileByPublicKey(participant.participantPublicKey)
|
||||
|
||||
// TODO: Check if we have already joined group with keyPackage
|
||||
val marmotKeyPackageBundle = database.marmotKeyPackageBundleDao().getMarmotKeyPackageBundleByKeyPackageEventIdId(
|
||||
keyPackageEventId
|
||||
)
|
||||
|
||||
if (marmotKeyPackageBundle == null) {
|
||||
// We were unable to find the keyPackageBundle for this invite...
|
||||
throw MarmotNoMatchingKeyPackageBundleException(
|
||||
"NO matching KeyPackageBundle for eventId=${keyPackageEventId.take(8)}…"
|
||||
)
|
||||
}
|
||||
|
||||
val initPrivateKey = marmotKeyPackageBundle.ncryptsecInitPrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
val signaturePrivateKey = marmotKeyPackageBundle.ncryptsecSignaturePrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
val encryptionPrivateKey = marmotKeyPackageBundle.ncryptsecEncryptionPrivateKey.hexToByteArray() // TODO: Decrypt
|
||||
|
||||
val tlsReader = TlsReader(
|
||||
data = marmotKeyPackageBundle.tlsEncodedMarmotKeyPackage.hexToByteArray()
|
||||
)
|
||||
|
||||
val group = MlsGroup.processWelcome(
|
||||
welcomeBytes,
|
||||
bundle = KeyPackageBundle(
|
||||
keyPackage = MlsKeyPackage.decodeTls(
|
||||
tlsReader
|
||||
),
|
||||
initPrivateKey = initPrivateKey,
|
||||
signaturePrivateKey = signaturePrivateKey,
|
||||
encryptionPrivateKey = encryptionPrivateKey
|
||||
)
|
||||
)
|
||||
|
||||
val derivedId = group.currentMarmotData()?.nostrGroupId
|
||||
|
||||
if (derivedId == null) {
|
||||
throw MarmotMissingNostrGroupDataExtension(
|
||||
"The WelcomeEvent ${welcomeEvent.id} is missing the nostrGroupData extension"
|
||||
)
|
||||
}
|
||||
|
||||
if (derivedId != chatRoomId) {
|
||||
throw MarmotWelcomeMismatchedGroupIdException(
|
||||
"The hint group Id $chatRoomId doesn't match the derived group id $derivedId"
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Save the chat room We can now s
|
||||
val userPublicKey = activeKeyPair.pubKey.toHex()
|
||||
database.chatRoomDao().upsert(
|
||||
ChatRoom(
|
||||
id = chatRoomId,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = group.currentMarmotData()?.name,
|
||||
initialGiftWrapPayloadId = giftWrapPayload.id,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
mlsGroupState = group.saveState().encodeTls().toHex()
|
||||
)
|
||||
)
|
||||
|
||||
database.marmotKeyPackageBundleDao().upsert(
|
||||
marmotKeyPackageBundle.copy(
|
||||
consumed = true
|
||||
)
|
||||
)
|
||||
|
||||
// TODO: Add participants... by processing group.members()
|
||||
val normalizedRelayUrl = NormalizedRelayUrl(relayURL)
|
||||
|
||||
val participants = group.members().mapNotNull { (leafIndex, leafNode) ->
|
||||
val pubkey =
|
||||
when (val cred = leafNode.credential) {
|
||||
is Credential.Basic -> cred.identity.toHexKey()
|
||||
else -> null
|
||||
}
|
||||
if (pubkey != null) {
|
||||
Participant(
|
||||
participantPublicKey = pubkey,
|
||||
chatRoomId = chatRoomId,
|
||||
relayHint = normalizedRelayUrl.url
|
||||
if (giftWrapParticipantProfile == null) {
|
||||
// Save a placeholder
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING...",
|
||||
publicKey = participant.participantPublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
|
||||
val recommendRelayUrl = giftWrapMessage.receiverRelayHit
|
||||
|
||||
if (recommendRelayUrl != null) {
|
||||
if (profilePublicKeysToSync[recommendRelayUrl] == null) {
|
||||
profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[recommendRelayUrl]?.add(participant.participantPublicKey)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Sync missing participant profiles...
|
||||
participants.forEach { participant ->
|
||||
val giftWrapParticipantProfile = database.profileDao().getProfileByPublicKey(participant.participantPublicKey)
|
||||
|
||||
if (giftWrapParticipantProfile == null) {
|
||||
// Save a placeholder
|
||||
database.profileDao().insertPlaceholderProfile(
|
||||
Profile(
|
||||
displayName = "LOADING...",
|
||||
publicKey = participant.participantPublicKey,
|
||||
createdAt = GENESIS_AT,
|
||||
nostrEventId = nostrEvent.id, // Will get overwriting by sync,
|
||||
)
|
||||
)
|
||||
|
||||
val recommendRelayUrl = giftWrapMessage.receiverRelayHit
|
||||
|
||||
if (recommendRelayUrl != null) {
|
||||
if (profilePublicKeysToSync[recommendRelayUrl] == null) {
|
||||
profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[recommendRelayUrl]?.add(participant.participantPublicKey)
|
||||
} else {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(
|
||||
participant.participantPublicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
database.participantDao().upsert(
|
||||
participants
|
||||
)
|
||||
|
||||
participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant ->
|
||||
val publicKey = participant.participantPublicKey
|
||||
|
||||
val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND
|
||||
),
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
since = GENESIS_AT,
|
||||
limit = 5
|
||||
).firstOrNull()?.let { nostrEvent ->
|
||||
if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) {
|
||||
logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent")
|
||||
null
|
||||
} else {
|
||||
ChatMessageRelayListEvent(
|
||||
id = nostrEvent.id,
|
||||
tags = nostrEvent.tags,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
content = nostrEvent.content,
|
||||
createdAt = nostrEvent.createdAt.epochSeconds,
|
||||
sig = nostrEvent.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
// TODO: Get relayHint form participant...
|
||||
if (profilePublicKeysToSync.containsKey(relayURL)) {
|
||||
if (profilePublicKeysToSync[relayURL] == null) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey)
|
||||
profilePublicKeysToSync[relayURL]?.add(
|
||||
participant.participantPublicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Where is the decoded welcome message...
|
||||
// TODO: Sync GroupEvents/Messages...
|
||||
database.chatMessageDao().upsert(
|
||||
ChatMessage(
|
||||
giftWrapPayloadId = giftWrapPayload.id,
|
||||
senderPublicKey = giftWrapPayload.publicKey,
|
||||
isUserMessage = activeKeyPair.pubKey.toHex() == giftWrapPayload.publicKey,
|
||||
chatRoomId = chatRoomId,
|
||||
createdAt = giftWrapPayload.createdAt,
|
||||
content = "Coming soon.", // TODO: Figure out what to do here...
|
||||
)
|
||||
database.participantDao().upsert(
|
||||
participants
|
||||
)
|
||||
|
||||
participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant ->
|
||||
val publicKey = participant.participantPublicKey
|
||||
|
||||
val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND
|
||||
),
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
since = GENESIS_AT,
|
||||
limit = 5
|
||||
).firstOrNull()?.let { nostrEvent ->
|
||||
if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) {
|
||||
logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent")
|
||||
null
|
||||
} else {
|
||||
ChatMessageRelayListEvent(
|
||||
id = nostrEvent.id,
|
||||
tags = nostrEvent.tags,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
content = nostrEvent.content,
|
||||
createdAt = nostrEvent.createdAt.epochSeconds,
|
||||
sig = nostrEvent.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
// TODO: Get relayHint form participant...
|
||||
if (profilePublicKeysToSync.containsKey(relayURL)) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Where is the decoded welcome message...
|
||||
// TODO: Sync GroupEvents/Messages...
|
||||
database.chatMessageDao().upsert(
|
||||
ChatMessage(
|
||||
giftWrapPayloadId = decryptedGiftWrapPayload.id,
|
||||
senderPublicKey = decryptedGiftWrapPayload.publicKey,
|
||||
isUserMessage = activeKeyPair.pubKey.toHex() == decryptedGiftWrapPayload.publicKey,
|
||||
chatRoomId = nostrGroupId,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
content = "Coming soon.", // TODO: Figure out what to do here...
|
||||
)
|
||||
)
|
||||
// TODO: Check encoding...
|
||||
} else {
|
||||
logger.w("Unsupported event: $giftWrapPayload")
|
||||
logger.w("Unsupported event: $decryptedGiftWrapPayload")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import at.torch.compose.database.model.traits.LocalStoreEntity
|
||||
import at.torch.compose.database.model.traits.SoftDeletableEntity
|
||||
import at.torch.compose.database.model.traits.TimestampedEntity
|
||||
import at.torch.compose.database.model.traits.UserViewableEntity
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
@@ -33,7 +34,7 @@ data class ChatMessage(
|
||||
val senderPublicKey: String,
|
||||
val isUserMessage: Boolean,
|
||||
|
||||
val giftWrapPayloadId: Long,
|
||||
val giftWrapPayloadId: HexKey,
|
||||
val chatRoomId: String,
|
||||
|
||||
val replyToMessageId: Long? = null,
|
||||
|
||||
@@ -67,7 +67,7 @@ data class ChatRoom(
|
||||
// TODO: Add a LastChatMessageRelation
|
||||
// val lastChatMessageId: String? = null,
|
||||
// Might want to focus on the other last messages...
|
||||
val initialGiftWrapPayloadId: Long? = null,
|
||||
val initialGiftWrapPayloadId: HexKey? = null,
|
||||
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.room3.ForeignKey
|
||||
import androidx.room3.Ignore
|
||||
import androidx.room3.PrimaryKey
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
@@ -24,8 +25,8 @@ import kotlin.time.Instant
|
||||
],
|
||||
)
|
||||
data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
|
||||
/**
|
||||
* Sender Public Key
|
||||
@@ -34,7 +35,7 @@ data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload...
|
||||
|
||||
val chatRoomId: String,
|
||||
|
||||
/**
|
||||
/**`
|
||||
* Payload Event Kind
|
||||
*/
|
||||
val kind: Kind,
|
||||
|
||||
@@ -94,6 +94,7 @@ data class GiftWrapSeal(
|
||||
logger.d("giftWrapPayload: ${giftWrapPayload.toJson()}")
|
||||
|
||||
val giftWrapPayload = GiftWrapPayload(
|
||||
id = id,
|
||||
giftWrapSealId = id,
|
||||
publicKey = giftWrapPayload.pubKey,
|
||||
kind = giftWrapPayload.kind,
|
||||
|
||||
@@ -22,8 +22,8 @@ import kotlin.time.Instant
|
||||
]
|
||||
)
|
||||
data class MarmotKeyPackage(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
|
||||
val publicKey: HexKey,
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ import kotlin.time.Instant
|
||||
],
|
||||
)
|
||||
data class MarmotKeyPackageBundle(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
@PrimaryKey
|
||||
val id: HexKey,
|
||||
|
||||
val publicKey: HexKey,
|
||||
// TODO: have the keyPackageEventId
|
||||
|
||||
val tlsEncodedMarmotKeyPackage: String,
|
||||
val tlsEncodedMarmotKeyPackage: ByteArray,
|
||||
|
||||
val ncryptsecInitPrivateKey: String,
|
||||
val ncryptsecEncryptionPrivateKey: String,
|
||||
val ncryptsecSignaturePrivateKey: String,
|
||||
@@ -45,4 +45,44 @@ data class MarmotKeyPackageBundle(
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
override val deletedAt: Instant? = null,
|
||||
override val broadcastedAt: Instant? = null,
|
||||
): TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity
|
||||
): TimestampedEntity, LocalStoreEntity, BroadcastableEntity, SoftDeletableEntity {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
|
||||
other as MarmotKeyPackageBundle
|
||||
|
||||
if (consumed != other.consumed) return false
|
||||
if (rotated != other.rotated) return false
|
||||
if (id != other.id) return false
|
||||
if (publicKey != other.publicKey) return false
|
||||
if (!tlsEncodedMarmotKeyPackage.contentEquals(other.tlsEncodedMarmotKeyPackage)) return false
|
||||
if (ncryptsecInitPrivateKey != other.ncryptsecInitPrivateKey) return false
|
||||
if (ncryptsecEncryptionPrivateKey != other.ncryptsecEncryptionPrivateKey) return false
|
||||
if (ncryptsecSignaturePrivateKey != other.ncryptsecSignaturePrivateKey) return false
|
||||
if (createdAt != other.createdAt) return false
|
||||
if (updatedAt != other.updatedAt) return false
|
||||
if (savedAt != other.savedAt) return false
|
||||
if (deletedAt != other.deletedAt) return false
|
||||
if (broadcastedAt != other.broadcastedAt) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = consumed.hashCode()
|
||||
result = 31 * result + rotated.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
result = 31 * result + publicKey.hashCode()
|
||||
result = 31 * result + tlsEncodedMarmotKeyPackage.contentHashCode()
|
||||
result = 31 * result + ncryptsecInitPrivateKey.hashCode()
|
||||
result = 31 * result + ncryptsecEncryptionPrivateKey.hashCode()
|
||||
result = 31 * result + ncryptsecSignaturePrivateKey.hashCode()
|
||||
result = 31 * result + createdAt.hashCode()
|
||||
result = 31 * result + updatedAt.hashCode()
|
||||
result = 31 * result + savedAt.hashCode()
|
||||
result = 31 * result + (deletedAt?.hashCode() ?: 0)
|
||||
result = 31 * result + (broadcastedAt?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,7 @@ data class NostrEvent(
|
||||
}
|
||||
return null
|
||||
} catch (e: Throwable) {
|
||||
logger.e("GiftWrapMessage error: ", e)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package at.torch.compose.database.repository
|
||||
|
||||
import at.torch.compose.database.model.ChatMessage
|
||||
import at.torch.compose.database.model.GiftWrapPayload
|
||||
import co.touchlab.kermit.Logger
|
||||
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.crypto.EventHasher
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
@@ -14,6 +17,8 @@ import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
|
||||
class DatabaseChatRepository(
|
||||
private val database: at.torch.compose.database.TorchDatabase,
|
||||
@@ -104,17 +109,28 @@ class DatabaseChatRepository(
|
||||
)
|
||||
}
|
||||
|
||||
val giftWrapPayloadId = database.giftWrapPayloadDao().upsert(
|
||||
_root_ide_package_.at.torch.compose.database.model.GiftWrapPayload(
|
||||
val createdAt = Clock.System.now().epochSeconds
|
||||
val giftWrapPayloadId = EventHasher.hashId(
|
||||
pubKey = localChatRoom.chatRoom.userPublicKey,
|
||||
createdAt = createdAt,
|
||||
tags = receiverTags.toTypedArray(),
|
||||
content = text,
|
||||
kind = messageType
|
||||
)
|
||||
|
||||
database.giftWrapPayloadDao().upsert(
|
||||
GiftWrapPayload(
|
||||
id = giftWrapPayloadId,
|
||||
kind = messageType,
|
||||
tags = receiverTags.toTypedArray(),
|
||||
createdAt = Instant.fromEpochSeconds(createdAt),
|
||||
content = text,
|
||||
chatRoomId = localChatRoom.chatRoom.id,
|
||||
publicKey = localChatRoom.chatRoom.userPublicKey
|
||||
)
|
||||
)
|
||||
database.chatMessageDao().upsert(
|
||||
_root_ide_package_.at.torch.compose.database.model.ChatMessage(
|
||||
ChatMessage(
|
||||
content = text,
|
||||
chatRoomId = localChatRoom.chatRoom.id,
|
||||
senderPublicKey = localChatRoom.chatRoom.userPublicKey,
|
||||
|
||||
@@ -2,13 +2,31 @@ package at.torch.compose.database.repository
|
||||
|
||||
import at.torch.compose.database.TorchDatabase
|
||||
import at.torch.compose.database.model.MarmotKeyPackageBundle
|
||||
import at.torch.compose.database.model.UnsignedNostrEvent
|
||||
import at.torch.compose.extensions.toHex
|
||||
import at.torch.compose.nostr.Relays
|
||||
import at.torch.compose.repository.MarmotRepository
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRotationManager.Companion.KEY_PACKAGE_LIFETIME_SECONDS
|
||||
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageUtils
|
||||
import com.vitorpamplona.quartz.marmot.mls.codec.TlsWriter
|
||||
import com.vitorpamplona.quartz.marmot.mls.crypto.Ed25519
|
||||
import com.vitorpamplona.quartz.marmot.mls.crypto.MlsCryptoProvider
|
||||
import com.vitorpamplona.quartz.marmot.mls.crypto.X25519
|
||||
import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle
|
||||
import com.vitorpamplona.quartz.marmot.mls.messages.MlsKeyPackage
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.Capabilities
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.Credential
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.Extension
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.LeafNode
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.LeafNodeSource
|
||||
import com.vitorpamplona.quartz.marmot.mls.tree.Lifetime
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlin.io.encoding.Base64
|
||||
|
||||
class DatabaseMarmotRepository(
|
||||
val database: TorchDatabase,
|
||||
@@ -16,24 +34,141 @@ class DatabaseMarmotRepository(
|
||||
): MarmotRepository {
|
||||
override suspend fun saveMarmotKeyPackageBundle(
|
||||
publicKey: HexKey,
|
||||
keyPackageBundle: KeyPackageBundle,
|
||||
nsecPassword: String
|
||||
) {
|
||||
val tlsWriter = TlsWriter()
|
||||
// KeyPackage...
|
||||
val dTag = KeyPackageUtils.generateRandomDTag() // TODO: Persist this...
|
||||
val identity = publicKey.hexToByteArray()
|
||||
val bundle = generateKeyPackage(identity, dTag)
|
||||
|
||||
keyPackageBundle.keyPackage.encodeTls(tlsWriter)
|
||||
val keyPackageBytes = bundle.keyPackage.toTlsBytes()
|
||||
val keyPackageBase64 = Base64.encode(keyPackageBytes)
|
||||
val keyPackageRef = bundle.keyPackage.reference().toHexKey()
|
||||
|
||||
val nip49 = Nip49()
|
||||
val keyPackageTemplate = KeyPackageEvent.build(
|
||||
keyPackageBase64 = keyPackageBase64,
|
||||
dTagSlot = dTag,
|
||||
keyPackageRef = keyPackageRef,
|
||||
relays = Relays.DefaultDMRelayList,
|
||||
)
|
||||
|
||||
database.marmotKeyPackageBundleDao().upsert(
|
||||
MarmotKeyPackageBundle(
|
||||
publicKey = publicKey,
|
||||
tlsEncodedMarmotKeyPackage = tlsWriter.toByteArray().toHex(),
|
||||
ncryptsecInitPrivateKey = keyPackageBundle.initPrivateKey.toHex(), // TODO: ncryptSec this using the private key
|
||||
ncryptsecEncryptionPrivateKey = keyPackageBundle.encryptionPrivateKey.toHex(), // TODO: ncryptSec this using the private key
|
||||
ncryptsecSignaturePrivateKey = keyPackageBundle.signaturePrivateKey.toHex() // TODO: ncryptSec this using the private key
|
||||
database.unsignedNostrEventDao().upsert(
|
||||
UnsignedNostrEvent(
|
||||
pubKey = publicKey,
|
||||
kind = keyPackageTemplate.kind,
|
||||
tags = keyPackageTemplate.tags,
|
||||
content = keyPackageTemplate.content
|
||||
)
|
||||
)
|
||||
|
||||
val id = EventHasher.hashIdBytes(
|
||||
pubKey =publicKey,
|
||||
createdAt = keyPackageTemplate.createdAt,
|
||||
kind = keyPackageTemplate.kind,
|
||||
tags = keyPackageTemplate.tags,
|
||||
content = keyPackageTemplate.content
|
||||
)
|
||||
|
||||
database.marmotKeyPackageBundleDao().upsert(
|
||||
MarmotKeyPackageBundle(
|
||||
id = id.toHexKey(),
|
||||
publicKey = publicKey,
|
||||
tlsEncodedMarmotKeyPackage = bundle.keyPackage.toTlsBytes(),
|
||||
ncryptsecInitPrivateKey = bundle.initPrivateKey.toHex(), // TODO: ncryptSec this using the private key
|
||||
ncryptsecEncryptionPrivateKey = bundle.encryptionPrivateKey.toHex(), // TODO: ncryptSec this using the private key
|
||||
ncryptsecSignaturePrivateKey = bundle.signaturePrivateKey.toHex() // TODO: ncryptSec this using the private key
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun generateKeyPackage(
|
||||
identity: ByteArray,
|
||||
dTagSlot: String = KeyPackageUtils.PRIMARY_SLOT,
|
||||
): KeyPackageBundle {
|
||||
val initKp = X25519.generateKeyPair()
|
||||
val encKp = X25519.generateKeyPair()
|
||||
val sigKp = Ed25519.generateKeyPair()
|
||||
|
||||
val leafNode =
|
||||
buildKeyPackageLeafNode(
|
||||
encryptionKey = encKp.publicKey,
|
||||
signatureKey = sigKp.publicKey,
|
||||
identity = identity,
|
||||
signingKey = sigKp.privateKey,
|
||||
)
|
||||
|
||||
val unsigned =
|
||||
MlsKeyPackage(
|
||||
initKey = initKp.publicKey,
|
||||
leafNode = leafNode,
|
||||
// LastResort (0x000A) marks this as a last-resort KeyPackage per MLS Extensions draft.
|
||||
// MDK always marks KeyPackages as last-resort via .mark_as_last_resort().
|
||||
extensions = listOf(
|
||||
Extension(
|
||||
extensionType = 0x000A,
|
||||
extensionData = ByteArray(0)
|
||||
)
|
||||
),
|
||||
signature = ByteArray(0),
|
||||
)
|
||||
val keyPackage =
|
||||
unsigned.copy(
|
||||
signature =
|
||||
MlsCryptoProvider.signWithLabel(
|
||||
sigKp.privateKey,
|
||||
"KeyPackageTBS",
|
||||
unsigned.encodeTbs(),
|
||||
),
|
||||
)
|
||||
|
||||
val bundle = KeyPackageBundle(
|
||||
keyPackage,
|
||||
initPrivateKey = initKp.privateKey,
|
||||
encryptionPrivateKey = encKp.privateKey,
|
||||
signaturePrivateKey = sigKp.privateKey
|
||||
)
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
|
||||
private fun buildKeyPackageLeafNode(
|
||||
encryptionKey: ByteArray,
|
||||
signatureKey: ByteArray,
|
||||
identity: ByteArray,
|
||||
signingKey: ByteArray,
|
||||
): LeafNode {
|
||||
val now = TimeUtils.now()
|
||||
val unsigned =
|
||||
LeafNode(
|
||||
encryptionKey = encryptionKey,
|
||||
signatureKey = signatureKey,
|
||||
credential = Credential.Basic(identity),
|
||||
capabilities =
|
||||
Capabilities(
|
||||
extensions =
|
||||
listOf(
|
||||
0x000A, // LastResort (required by OpenMLS validation)
|
||||
0xF2EE, // NostrGroupData (required by group's RequiredCapabilities)
|
||||
),
|
||||
proposals =
|
||||
listOf(
|
||||
0x000A, // SelfRemove (required by group's RequiredCapabilities)
|
||||
),
|
||||
),
|
||||
leafNodeSource = LeafNodeSource.KEY_PACKAGE,
|
||||
lifetime = Lifetime(
|
||||
notBefore = now,
|
||||
notAfter = now + KEY_PACKAGE_LIFETIME_SECONDS
|
||||
),
|
||||
extensions = emptyList(),
|
||||
signature = ByteArray(0),
|
||||
)
|
||||
|
||||
val tbs = unsigned.encodeTbs(groupId = null, leafIndex = null)
|
||||
val signature = MlsCryptoProvider.signWithLabel(signingKey, "LeafNodeTBS", tbs)
|
||||
return unsigned.copy(signature = signature)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class DatabaseNostrRepository(
|
||||
publicKey: HexKey,
|
||||
name: String?,
|
||||
biography: String?
|
||||
): KeyPackageBundle {
|
||||
) {
|
||||
val profileEventTemplate = MetadataEvent.createNew(
|
||||
name = name,
|
||||
about = biography,
|
||||
@@ -202,113 +202,6 @@ class DatabaseNostrRepository(
|
||||
content = ""
|
||||
)
|
||||
)
|
||||
|
||||
// KeyPackage...
|
||||
KeyPackageUtils.PRIMARY_SLOT
|
||||
val dTag = KeyPackageUtils.generateRandomDTag() // TODO: Persist this...
|
||||
val identity = publicKey.hexToByteArray()
|
||||
val bundle = generateKeyPackage(identity, dTag)
|
||||
|
||||
val keyPackageBytes = bundle.keyPackage.toTlsBytes()
|
||||
val keyPackageBase64 = Base64.encode(keyPackageBytes)
|
||||
val keyPackageRef = bundle.keyPackage.reference().toHexKey()
|
||||
|
||||
val keyPackageTemplate = KeyPackageEvent.build(
|
||||
keyPackageBase64 = keyPackageBase64,
|
||||
dTagSlot = dTag,
|
||||
keyPackageRef = keyPackageRef,
|
||||
relays = Relays.DefaultDMRelayList,
|
||||
)
|
||||
|
||||
unsignedNostrEvents.add(
|
||||
UnsignedNostrEvent(
|
||||
pubKey = publicKey,
|
||||
kind = keyPackageTemplate.kind,
|
||||
tags = keyPackageTemplate.tags,
|
||||
content = keyPackageTemplate.content
|
||||
)
|
||||
)
|
||||
|
||||
saveUnsignedNostrEvents(unsignedNostrEvents)
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
private fun generateKeyPackage(
|
||||
identity: ByteArray,
|
||||
dTagSlot: String = KeyPackageUtils.PRIMARY_SLOT,
|
||||
): KeyPackageBundle {
|
||||
val initKp = X25519.generateKeyPair()
|
||||
val encKp = X25519.generateKeyPair()
|
||||
val sigKp = Ed25519.generateKeyPair()
|
||||
|
||||
val leafNode =
|
||||
buildKeyPackageLeafNode(
|
||||
encryptionKey = encKp.publicKey,
|
||||
signatureKey = sigKp.publicKey,
|
||||
identity = identity,
|
||||
signingKey = sigKp.privateKey,
|
||||
)
|
||||
|
||||
val unsigned =
|
||||
MlsKeyPackage(
|
||||
initKey = initKp.publicKey,
|
||||
leafNode = leafNode,
|
||||
// LastResort (0x000A) marks this as a last-resort KeyPackage per MLS Extensions draft.
|
||||
// MDK always marks KeyPackages as last-resort via .mark_as_last_resort().
|
||||
extensions = listOf(Extension(extensionType = 0x000A, extensionData = ByteArray(0))),
|
||||
signature = ByteArray(0),
|
||||
)
|
||||
val keyPackage =
|
||||
unsigned.copy(
|
||||
signature =
|
||||
MlsCryptoProvider.signWithLabel(
|
||||
sigKp.privateKey,
|
||||
"KeyPackageTBS",
|
||||
unsigned.encodeTbs(),
|
||||
),
|
||||
)
|
||||
|
||||
val bundle = KeyPackageBundle(keyPackage, initKp.privateKey, encKp.privateKey, sigKp.privateKey)
|
||||
// TODO: Persist this...
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
|
||||
private fun buildKeyPackageLeafNode(
|
||||
encryptionKey: ByteArray,
|
||||
signatureKey: ByteArray,
|
||||
identity: ByteArray,
|
||||
signingKey: ByteArray,
|
||||
): LeafNode {
|
||||
val now = TimeUtils.now()
|
||||
val unsigned =
|
||||
LeafNode(
|
||||
encryptionKey = encryptionKey,
|
||||
signatureKey = signatureKey,
|
||||
credential = Credential.Basic(identity),
|
||||
capabilities =
|
||||
Capabilities(
|
||||
extensions =
|
||||
listOf(
|
||||
0x000A, // LastResort (required by OpenMLS validation)
|
||||
0xF2EE, // NostrGroupData (required by group's RequiredCapabilities)
|
||||
),
|
||||
proposals =
|
||||
listOf(
|
||||
0x000A, // SelfRemove (required by group's RequiredCapabilities)
|
||||
),
|
||||
),
|
||||
leafNodeSource = LeafNodeSource.KEY_PACKAGE,
|
||||
lifetime = Lifetime(notBefore = now, notAfter = now + KEY_PACKAGE_LIFETIME_SECONDS),
|
||||
extensions = emptyList(),
|
||||
signature = ByteArray(0),
|
||||
)
|
||||
|
||||
val tbs = unsigned.encodeTbs(groupId = null, leafIndex = null)
|
||||
val signature = MlsCryptoProvider.signWithLabel(signingKey, "LeafNodeTBS", tbs)
|
||||
return unsigned.copy(signature = signature)
|
||||
}
|
||||
|
||||
override suspend fun signInToProfile(
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package at.torch.compose.exceptions
|
||||
|
||||
class MarmotMissingKeyPackageBundleException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package at.torch.compose.exceptions
|
||||
|
||||
class MarmotNoMatchingKeyPackageBundleException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package at.torch.compose.exceptions
|
||||
|
||||
class MarmotWelcomeEventMissingKeyPackageEventIdException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package at.torch.compose.exceptions
|
||||
|
||||
class MarmotWelcomeMissingGroupIdException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) {
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
package at.torch.compose.repository
|
||||
|
||||
import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
interface MarmotRepository {
|
||||
|
||||
suspend fun saveMarmotKeyPackageBundle(
|
||||
publicKey: HexKey,
|
||||
keyPackageBundle: KeyPackageBundle,
|
||||
nsecPassword: String
|
||||
)
|
||||
|
||||
@@ -15,7 +13,6 @@ interface MarmotRepository {
|
||||
val NO_OP_MARMOT_KEY_PACKAGE_BUNDLE = object: MarmotRepository {
|
||||
override suspend fun saveMarmotKeyPackageBundle(
|
||||
publicKey: HexKey,
|
||||
keyPackageBundle: KeyPackageBundle,
|
||||
nsecPassword: String
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package at.torch.compose.repository
|
||||
|
||||
import at.torch.compose.database.model.intermdiate.LocalAccount
|
||||
import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
@@ -28,7 +27,7 @@ interface NostrRepository {
|
||||
publicKey: HexKey,
|
||||
name: String?,
|
||||
biography: String?
|
||||
): KeyPackageBundle
|
||||
)
|
||||
|
||||
suspend fun signInToProfile(
|
||||
publicKey: HexKey
|
||||
@@ -163,7 +162,7 @@ interface NostrRepository {
|
||||
publicKey: HexKey,
|
||||
name: String?,
|
||||
biography: String?
|
||||
): KeyPackageBundle {
|
||||
) {
|
||||
TODO("")
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ private fun ChatRoomDetailScreenPreview() {
|
||||
id = "",
|
||||
userPublicKey = "",
|
||||
subject = "Message title",
|
||||
initialGiftWrapPayloadId = 0,
|
||||
initialGiftWrapPayloadId = "sdfaer",
|
||||
mlsGroupState = null
|
||||
),
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ import at.torch.compose.ui.composable.navigation.routes.ChatRoomCreationRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.LoadingRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.NostrEventDetailRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.ShareProfileRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.SocialPreconditionRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.SovereignWalletStartupRoute
|
||||
import at.torch.compose.ui.composable.navigation.routes.WriteNewNoteRoute
|
||||
import at.torch.compose.ui.view.state.NavigationUIState
|
||||
@@ -101,6 +102,7 @@ fun TorchNavHost(
|
||||
scope = applicationIOScope
|
||||
)
|
||||
)
|
||||
|
||||
// TODO: Produce a notary UI Element...
|
||||
val synchronizationViewModel: at.torch.compose.ui.view.model.SynchronizationViewModel = viewModel(
|
||||
factory = at.torch.compose.ui.view.model.SynchronizationViewModel.factory(
|
||||
@@ -203,7 +205,7 @@ fun TorchNavHost(
|
||||
}
|
||||
is NavigationUIState.ProfileLoaded -> {
|
||||
navController.navigate(
|
||||
route = at.torch.compose.ui.composable.navigation.routes.SocialPreconditionRoute(
|
||||
route = SocialPreconditionRoute(
|
||||
activeUserPubkey = state.publicKey
|
||||
)
|
||||
) {
|
||||
@@ -339,7 +341,7 @@ fun TorchNavHost(
|
||||
},
|
||||
onNavigateToNostrEvent = { hexKey ->
|
||||
navController.navigate(
|
||||
route = at.torch.compose.ui.composable.navigation.routes.NostrEventDetailRoute(
|
||||
route = NostrEventDetailRoute(
|
||||
activeUserPublicKey = route.activeUserPublicKey,
|
||||
nostrEventId = hexKey
|
||||
)
|
||||
@@ -383,8 +385,8 @@ fun TorchNavHost(
|
||||
|
||||
at.torch.compose.ui.composable.UnannouncedProfileScreen()
|
||||
}
|
||||
composable<at.torch.compose.ui.composable.navigation.routes.SocialPreconditionRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<at.torch.compose.ui.composable.navigation.routes.SocialPreconditionRoute>()
|
||||
composable<SocialPreconditionRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<SocialPreconditionRoute>()
|
||||
at.torch.compose.ui.composable.SocialPreconditionScreen(
|
||||
onNavigateToSkipForNow = {
|
||||
navController.navigate(
|
||||
|
||||
@@ -105,7 +105,8 @@ class CreateProfileViewModel(
|
||||
)
|
||||
|
||||
val pubkey = localKeyManager.nostrPublicKey()
|
||||
val keyPackageBundle = nostrRepository.createNewProfile(
|
||||
|
||||
nostrRepository.createNewProfile(
|
||||
pubkey,
|
||||
name = createProfileFormState.nameField.textFieldState.text.toString(),
|
||||
biography = createProfileFormState.biographyField.textFieldState.text.toString()
|
||||
@@ -113,7 +114,6 @@ class CreateProfileViewModel(
|
||||
|
||||
marmotRepository.saveMarmotKeyPackageBundle(
|
||||
publicKey = pubkey,
|
||||
keyPackageBundle,
|
||||
nsecPassword = localKeyManager.nsecPassword()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class NotaryViewModel(
|
||||
init {
|
||||
scope.launch {
|
||||
activeWalletStateFlow.collectLatest { activeWallet ->
|
||||
logger.d("activeWallet: $activeWallet")
|
||||
logger.d("Notary ActiveWallet: $activeWallet")
|
||||
activeWallet?.business?.walletManager?.keyManager?.value?.nostrPrivateKey()?.let { nostrPrivateKey ->
|
||||
|
||||
val keyPair = KeyPair(
|
||||
|
||||
@@ -74,6 +74,7 @@ class SynchronizationViewModel(
|
||||
init {
|
||||
scope.launch {
|
||||
activeWalletStateFlow.collectLatest { activeWallet ->
|
||||
logger.d("Synchronization ActiveWallet: $activeWallet")
|
||||
activeWallet?.business?.walletManager?.keyManager?.value?.nostrPrivateKey()?.let { nostrPrivateKey ->
|
||||
val keyPair = KeyPair(
|
||||
privKey = nostrPrivateKey.value.toByteArray()
|
||||
|
||||
Reference in New Issue
Block a user