diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt index 5014d079..7b1fb686 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt @@ -58,28 +58,6 @@ abstract class NostrDao( signedAt = Clock.System.now() ) ) - // Find or create profile with the pubKey... if not found TODO: submit a sync request... -// val profile = database.profileDao().getProfileByPublicKey(nostrEvent.pubKey) -// -// if (profile == null) { -// val placeHolderProfile = Profile( -// publicKey = nostrEvent.pubKey, -// nostrEventId = nostrEvent.pubKey // This is illegal... because the nostrEventId should be the one with all the profile information -// ) -// val placeHolderProfileNostrEvent = NostrEvent( -// id = placeHolderProfile.publicKey, -// kind = DraftWrapEvent.KIND, -// pubKey = placeHolderProfile.publicKey, -// content = placeHolderProfile.publicKey, -// createdAt = GENESIS_AT, -// sig = placeHolderProfile.publicKey, -// tags = emptyArray(), -// ) -// database.nostrEventDao().upsert(placeHolderProfileNostrEvent) -// database.profileDao().upsert(placeHolderProfile) -// -// // TODO: Sync to find and update profile -// } database.nostrEventDao().upsert(nostrEvent) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/UnsignedNostrEventDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/UnsignedNostrEventDao.kt index b3399c6f..426e9f5c 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/UnsignedNostrEventDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/UnsignedNostrEventDao.kt @@ -3,6 +3,7 @@ package ac.cord.auxiliary.compose.database.dao import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalAccount import androidx.room3.Dao +import androidx.room3.Insert import androidx.room3.Query import androidx.room3.Transaction import androidx.room3.Upsert @@ -22,10 +23,13 @@ interface UnsignedNostrEventDao { @Upsert suspend fun upsert(unsignedNostrEvent: UnsignedNostrEvent): Long + @Insert + suspend fun insert(unsignedNostrEvents: List) + @Transaction @Query("SELECT * FROM UnsignedNostrEvent WHERE kind = 0 AND pubKey = :publicKey") fun observeProfile(publicKey: String): Flow - @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL") // TODO: where nostrEvent.unsignedNostrEventId is 0 + @Query("SELECT * FROM UnsignedNostrEvent WHERE pubKey = :publicKey AND signedAt IS NULL") fun observeUnsignedNostrEvents(publicKey: String): Flow } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt index c08cf0c8..44ef04ef 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt @@ -63,7 +63,6 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.collections.emptyMap import kotlin.collections.plus -import kotlin.math.sin import kotlin.time.Clock import kotlin.time.Instant @@ -116,132 +115,129 @@ class DatabaseNostrRepository( override suspend fun createNewProfile( publicKey: HexKey, name: String?, - biography: String?, - onError: () -> Unit, - onProfileReady: (Profile) -> Unit, - synchronizationRelayURLs: List + biography: String? ) { val profileEventTemplate = MetadataEvent.createNew( name = name, about = biography, ) - val signedAt = if (name == null && biography == null) { - // We are most likely try to sign in to an already existing block... - GENESIS_AT - } else { - null - } - - val unsignedNostrEvent = saveUnsignedNostrEvent( + val unsignedNostrEvents = mutableListOf( UnsignedNostrEvent( pubKey = publicKey, kind = profileEventTemplate.kind, createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt), tags = profileEventTemplate.tags, content = profileEventTemplate.content, - signedAt = signedAt // This will keep us from trying to sign this event... but we should automatically sync the npub profile + signedAt = null ) ) - if (unsignedNostrEvent == null) { - onError.invoke() - } else if (unsignedNostrEvent.kind == 0) { - // Look busy for the onboarding screen - delay(3_500) + val followUsers = listOf(ContactTag(publicKey, null, null)) + val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) + + followUsers.map { it.toTagArray() } + + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = ContactListEvent.KIND, + tags = contactListTagList.toTypedArray(), + content = RelaySet.assemble( + emptyMap() + ) + ) + ) + + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = ChatMessageRelayListEvent.KIND, + tags = ChatMessageRelayListEvent.createTagArray( + Relays.DefaultDMRelayList + ), + content = "" + ) + ) + + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = AdvertisedRelayListEvent.KIND, + tags = Relays.DefaultNIP65List.map{ it.toTagArray() }.toTypedArray(), + content = "" + ) + ) - if (unsignedNostrEvent.signedAt == null) { - // Save ContactListNostrEvent - val followUsers = listOf(ContactTag(publicKey, null, null)) - val contactListTagList = listOf(AltTag.assemble(ContactListEvent.ALT)) + - followUsers.map { it.toTagArray() } - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = ContactListEvent.KIND, - tags = contactListTagList.toTypedArray(), - content = RelaySet.assemble( - emptyMap() - ) - ) - ) + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = SearchRelayListEvent.KIND, + tags = Relays.DefaultSearchRelayList.map{ RelayTag.assemble(it) } + .plus( + SearchRelayListEvent.ALT_TAG + ).toTypedArray(), + privateTags = emptyArray(), + content = "" + ) + ) - // Save AdvertisedRelayListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = AdvertisedRelayListEvent.KIND, - tags = Relays.DefaultNIP65List.map{ it.toTagArray() }.toTypedArray(), - content = "" + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = IndexerRelayListEvent.KIND, + tags = arrayOf(AltTag.assemble(IndexerRelayListEvent.ALT)), + privateTags = Relays.DefaultIndexerRelayList.map { RelayTag.assemble(it) }.toTypedArray() , + content = "" + ) + ) + + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = ChannelListEvent.KIND, + tags = arrayOf( + AltTag.assemble( + ChannelListEvent.ALT ) - ) - // Save ChatMessageRelayListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = ChatMessageRelayListEvent.KIND, - tags = Relays.DefaultDMRelayList.map{ RelayTag.assemble(it) } - .plusElement( - AltTag.assemble("Relay list to receive private messages") - ).toTypedArray(), - content = "" - ) - ) - // Save SearchRelayListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = SearchRelayListEvent.KIND, - tags = Relays.DefaultSearchRelayList.map{ RelayTag.assemble(it) } - .plus( - SearchRelayListEvent.ALT_TAG - ).toTypedArray(), - privateTags = emptyArray(), - content = "" - ) - ) - // Save IndexerRelayListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = IndexerRelayListEvent.KIND, - tags = arrayOf(AltTag.assemble(IndexerRelayListEvent.ALT)), - privateTags = Relays.DefaultIndexerRelayList.map { RelayTag.assemble(it) }.toTypedArray() , - content = "" - ) - ) - // Save ChannelListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = ChannelListEvent.KIND, - tags = arrayOf( - AltTag.assemble( - ChannelListEvent.ALT - ) - ), - privateTags = Relays.DefaultChannels.map { it.toTagArray() }.toTypedArray(), - content = "" - ) - ) - // Save RelayFeedsListEvent - saveUnsignedNostrEvent( - UnsignedNostrEvent( - pubKey = publicKey, - kind = RelayFeedsListEvent.KIND, - tags = arrayOf( - AltTag.assemble(RelayFeedsListEvent.ALT) - ), - privateTags = Relays.DefaultGlobalRelays.map { RelayTag.assemble(it)}.toTypedArray(), - content = "" - ) - ) - } - } + ), + privateTags = Relays.DefaultChannels.map { it.toTagArray() }.toTypedArray(), + content = "" + ) + ) + + unsignedNostrEvents.add( + UnsignedNostrEvent( + pubKey = publicKey, + kind = RelayFeedsListEvent.KIND, + tags = arrayOf( + AltTag.assemble(RelayFeedsListEvent.ALT) + ), + privateTags = Relays.DefaultGlobalRelays.map { RelayTag.assemble(it)}.toTypedArray(), + content = "" + ) + ) + + saveUnsignedNostrEvents(unsignedNostrEvents) } + override suspend fun signInToProfile( + publicKey: HexKey + ) { + val profileEventTemplate = MetadataEvent.createNew() + + saveUnsignedNostrEvent( + UnsignedNostrEvent( + pubKey = publicKey, + kind = profileEventTemplate.kind, + createdAt = Instant.fromEpochSeconds(profileEventTemplate.createdAt), + tags = profileEventTemplate.tags, + content = profileEventTemplate.content, + signedAt = GENESIS_AT // This will keep us from trying to sign this event... but we should automatically sync the npub profile + ) + ) + } override suspend fun createNewTextNote( publicKey: HexKey, mentionedPublicKeys: List, @@ -362,8 +358,12 @@ class DatabaseNostrRepository( } override suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? = try { + logger.d("Save unsigned: ${unsignedNostrEvent.kind}") // TODO: We can actually check the unsignedNostrEvent.pubKey for support... if it's writeable - delay(2_100) // Just to make it look like we are doing serious work... + if (unsignedNostrEvent.kind == MetadataEvent.KIND){ + delay(2_100) // Just to make it look like we are doing serious work... + } + unsignedNostrEvent.copy( id = database.unsignedNostrEventDao().upsert(unsignedNostrEvent) ) @@ -372,6 +372,15 @@ class DatabaseNostrRepository( null } + override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List) { + logger.d("saveUnsignedNostrEvents: $unsignedNostrEvents") + delay(2_100) + + database.unsignedNostrEventDao().insert( + unsignedNostrEvents + ) + } + override suspend fun wipeDatabase() { database.wipeData() } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/nostr/Relays.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/nostr/Relays.kt index 924e326b..8596e56f 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/nostr/Relays.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/nostr/Relays.kt @@ -51,7 +51,7 @@ object Relays { val DefaultGlobalRelays = listOf(wine, news) - val DefaultDMRelayList = listOf(auth, oxchat, nos) + val DefaultDMRelayList = listOf(primal, oxtr, nos) val DefaultSearchRelayList = setOf(wine, where, nostoday, antiprimal, ditto) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt index fdbb71e0..67f55b97 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/NostrRepository.kt @@ -40,12 +40,11 @@ interface NostrRepository { suspend fun createNewProfile( publicKey: HexKey, name: String?, - biography: String?, - onError: () -> Unit, -// onUnsignProfile: (UnsignedNostrEvent) -> Unit, -// onUnbroadcastedProfile: (NostrEvent) -> Unit, - onProfileReady: (Profile) -> Unit, - synchronizationRelayURLs: List + biography: String? + ) + + suspend fun signInToProfile( + publicKey: HexKey ) suspend fun createNewTextNote( @@ -60,6 +59,8 @@ interface NostrRepository { suspend fun saveUnsignedNostrEvent(unsignedNostrEvent: UnsignedNostrEvent): UnsignedNostrEvent? + suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List) + suspend fun wipeDatabase() suspend fun publishNostrEvent( @@ -169,13 +170,14 @@ interface NostrRepository { override suspend fun createNewProfile( publicKey: HexKey, name: String?, - biography: String?, - onError: () -> Unit, - onProfileReady: (Profile) -> Unit, - synchronizationRelayURLs: List + biography: String? ) { } + override suspend fun signInToProfile(publicKey: HexKey) { + TODO("Not yet implemented") + } + override suspend fun createNewTextNote( publicKey: HexKey, mentionedPublicKeys: List, @@ -192,6 +194,10 @@ interface NostrRepository { return null } + override suspend fun saveUnsignedNostrEvents(unsignedNostrEvents: List) { + TODO("Not yet implemented") + } + override suspend fun wipeDatabase() { } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/CreateProfileViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/CreateProfileViewModel.kt index 13c6711a..ebcd9f98 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/CreateProfileViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/CreateProfileViewModel.kt @@ -1,7 +1,6 @@ package ac.cord.auxiliary.compose.ui.view.model import ac.cord.auxiliary.compose.managers.SeedManager -import ac.cord.auxiliary.compose.nostr.Relays import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.ui.view.state.CreateProfileUIState import androidx.compose.runtime.MutableState @@ -78,16 +77,7 @@ class CreateProfileViewModel( nostrRepository.createNewProfile( publicKey, name = createProfileFormState.nameField.textFieldState.text.toString(), - biography = createProfileFormState.biographyField.textFieldState.text.toString(), - onError = { - createProfileUIState.value = CreateProfileUIState.Error - }, - { profile -> - createProfileUIState.value = CreateProfileUIState.ProfileReady( - profile = profile - ) - }, - synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + biography = createProfileFormState.biographyField.textFieldState.text.toString() ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt index 647f5118..f5bd441d 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/NavigationViewModel.kt @@ -104,36 +104,38 @@ class NavigationViewModel( val tempSigner = NostrSignerSync( SeedManager.activeKeyPair() ) - logger.i { "observeUnsignedNostrEvents" } scope.launch(Dispatchers.IO) { + logger.i { "observeUnsignedNostrEvents" } nostrRepository.observeUnsignedNostrEvents( publicKey = SeedManager.activePublicKey().toHexKey() ).distinctUntilChanged().collect { unsignedNostrEventOrNull -> - unsignedNostrEventOrNull?.let { unsignedNostrEvent -> - logger.d("Unsigned") - val event = tempSigner.signNormal( - createdAt = unsignedNostrEvent.createdAt.epochSeconds, - kind = unsignedNostrEvent.kind, - tags = unsignedNostrEvent.tags, - content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content - ) + scope.launch(Dispatchers.IO) { + unsignedNostrEventOrNull?.let { unsignedNostrEvent -> + logger.d("Unsigned: ${unsignedNostrEvent.kind}") + val event = tempSigner.signNormal( + createdAt = unsignedNostrEvent.createdAt.epochSeconds, + kind = unsignedNostrEvent.kind, + tags = unsignedNostrEvent.tags, + content = unsignedNostrEvent.privateTags?.let { PrivateTagsInContent.encryptNip44(it, tempSigner) } ?: unsignedNostrEvent.content + ) + logger.d("Signed: ${event.toJson()}") - nostrRepository.publishNostrEvent( - unsignedNostrEvent, - NostrEvent( - id = event.id, - pubKey = event.pubKey, - kind = event.kind, - tags = event.tags, - content = event.content, - createdAt = Instant.fromEpochSeconds(event.createdAt), - sig = event.sig, - unsignedNostrEventId = unsignedNostrEvent.id - ), - relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } - ) + nostrRepository.publishNostrEvent( + unsignedNostrEvent, + NostrEvent( + id = event.id, + pubKey = event.pubKey, + kind = event.kind, + tags = event.tags, + content = event.content, + createdAt = Instant.fromEpochSeconds(event.createdAt), + sig = event.sig, + unsignedNostrEventId = unsignedNostrEvent.id + ), + relayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + ) + } } - } } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SignInToProfileViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SignInToProfileViewModel.kt index 174fbdb7..f80ce050 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SignInToProfileViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/SignInToProfileViewModel.kt @@ -1,6 +1,5 @@ package ac.cord.auxiliary.compose.ui.view.model -import ac.cord.auxiliary.compose.nostr.Relays import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.ui.view.state.SignInToProfileUIState import ac.cord.auxiliary.compose.ui.view.state.form.SignInToProfileFormState @@ -72,23 +71,13 @@ class SignInToProfileViewModel( viewModelScope.launch(Dispatchers.IO) { when (credentialEntity) { is NPub -> { - nostrRepository.createNewProfile( - publicKey = credentialEntity.hex, - name = null, - biography = null, - onError = onError, - onProfileReady = {}, - synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + nostrRepository.signInToProfile( + publicKey = credentialEntity.hex ) } is NSec -> { - nostrRepository.createNewProfile( - publicKey = credentialEntity.toPubKeyHex(), - name = null, - biography = null, - onError = onError, - onProfileReady = {}, - synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + nostrRepository.signInToProfile( + publicKey = credentialEntity.toPubKeyHex() ) } else -> {