From 403b245880f98036c84443a55f4b8523013c0dce Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sun, 19 Apr 2026 04:30:29 +0200 Subject: [PATCH] Sync using the new logic. --- composeApp/build.gradle.kts | 1 + .../aux/compose/network/relays/RelayPool.kt | 94 ++++++++--- .../network/relays/RelaysSocketManager.kt | 43 ++--- .../network/sockets/NostrIncomingMessage.kt | 15 ++ .../network/sockets/NostrSocketClient.kt | 2 + .../network/sockets/NostrSocketClientImpl.kt | 14 +- .../kotlin/ac/aux/compose/nostr/Relays.kt | 2 +- .../repository/NostrNotaryRepository.kt | 11 +- .../ui/view/model/NavigationViewModel.kt | 150 ++---------------- .../compose/managers/DataStoreManager.ios.kt | 33 ++++ .../compose/managers/DataStoreManager.jvm.kt | 15 ++ gradle/libs.versions.toml | 1 + 12 files changed, 193 insertions(+), 188 deletions(-) create mode 100644 composeApp/src/iosMain/kotlin/ac/aux/compose/managers/DataStoreManager.ios.kt create mode 100644 composeApp/src/jvmMain/kotlin/ac/aux/compose/managers/DataStoreManager.jvm.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 4d647d19..19efd697 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -45,6 +45,7 @@ kotlin { commonMain.dependencies { api("fr.acinq.lightning:lightning-kmp-core:1.11.5") + implementation(libs.androidx.datastore) implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.lifecycle.viewmodelCompose) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelayPool.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelayPool.kt index 818d616c..9d899bfd 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelayPool.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelayPool.kt @@ -20,15 +20,28 @@ import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.flow.transform import kotlinx.coroutines.launch import ac.aux.compose.exceptions.NostrPublishException +import ac.aux.compose.network.dto.toRelayDTO import ac.aux.compose.network.sockets.NostrIncomingMessage import ac.aux.compose.network.sockets.NostrSocketClient import ac.aux.compose.network.sockets.NostrSocketClientFactory import ac.aux.compose.network.sockets.SocketConnectionClosedCallback import ac.aux.compose.network.sockets.SocketConnectionOpenedCallback import ac.aux.compose.network.sockets.filterByEventId -import ac.aux.compose.network.sockets.parseIncomingMessage +import ac.aux.compose.network.sockets.filterBySubscriptionId +import ac.aux.compose.network.sockets.verifyOrThrow import ac.aux.compose.repository.CachingImportRepository import co.touchlab.kermit.Logger +import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper +import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.flow.transformWhile +import kotlin.time.Duration.Companion.seconds /** * As seen in Primal @@ -114,10 +127,11 @@ class RelayPool( val toAddRelayUrls = newRelayUrls.filter { it !in existingRelayUrls } val toAddSocketClients = relays.filter { it.url in toAddRelayUrls }.mapAsNostrSocketClient() - + logger.d("toAddSocketClients: ${toAddSocketClients.map { it.socketUrl }}" ) val newSocketClients = socketClients.toMutableList().apply { addAll(toAddSocketClients) } + logger.d("newSocketClients: ${newSocketClients.map { it.socketUrl }}") socketClients = newSocketClients @@ -165,30 +179,60 @@ class RelayPool( } } - private suspend fun handleBroadcastEventThroughCachingProxy(relayUrls: List, nostrEvent: NostrEvent) { - val result = - cachingImportRepository.broadcastEvents( - events = listOf(nostrEvent), - relays = relayUrls, - ).getOrNull() - ?: throw NostrPublishException( - cause = NetworkException(message = "Primal NostrEvent 10_000_149 not found or invalid."), - ) - - result.find { response -> response.eventId == nostrEvent.id }?.responses - ?.mapNotNull { relayResponse -> - val relay = relayResponse.firstOrNull() - val responseMessage = relayResponse.getOrNull(index = 1)?.parseIncomingMessage() - if (relay != null && responseMessage != null) { - relay to responseMessage - } else { - null - } - } - ?.find { (_, relayMessage) -> relayMessage is NostrIncomingMessage.OkMessage && relayMessage.success } - ?: throw NostrPublishException( - cause = NetworkException("Event broadcast failed. Could not find success response from relays."), + @OptIn(FlowPreview::class) + suspend fun query(reqCommand: ReqCmd, relayUrl: String): Pair> { + addRelays( + setOf( + NormalizedRelayUrl(relayUrl).url.toRelayDTO() ) + ) + + logger.d("socketClients: ${socketClients.map { it.socketUrl }}") + val nostrSocketClient = socketClients.find { NormalizedRelayUrl(it.socketUrl).displayUrl() == NormalizedRelayUrl(relayUrl).displayUrl() } + + val filterRequest = OptimizedJsonMapper.toJson(reqCommand) + + if (nostrSocketClient == null) { + throw NetworkException("$relayUrl is not connected") + } + return coroutineScope { + val deferredQueryResult = async { nostrSocketClient.collectQueryResult(reqCommand.subId) } + with(nostrSocketClient) { + sendMESSAGE(filterRequest) + } + deferredQueryResult.await() + } + } + + private fun Flow.transformWhileEventsAreIncoming() = + transformWhile { + emit(it) + it is NostrIncomingMessage.EventMessage || it is NostrIncomingMessage.EventsMessage + } + + @OptIn(FlowPreview::class) + private suspend fun NostrSocketClient.collectQueryResult(subscriptionId: String): Pair> { + val messages = this.incomingMessages + .filterBySubscriptionId(id = subscriptionId) + .transformWhileEventsAreIncoming() + .timeout(15.seconds) + .toList() + + val terminationMessage = messages.lastOrNull() + terminationMessage.verifyOrThrow(subscriptionId) + checkNotNull(terminationMessage) + + val eventMessages = messages.filterIsInstance() + val eventsMessage = messages.filterIsInstance() + + val allNostrEvents = eventMessages.mapNotNull { it.nostrEvent } + + eventsMessage.map { it.nostrEvents }.flatten() + + + return Pair( + terminationMessage, + allNostrEvents, + ) } @OptIn(FlowPreview::class) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelaysSocketManager.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelaysSocketManager.kt index 0bbfd72a..75006279 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelaysSocketManager.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/relays/RelaysSocketManager.kt @@ -16,6 +16,12 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import ac.aux.compose.exceptions.NostrPublishException +import ac.aux.compose.managers.CredentialsManager +import ac.aux.compose.managers.SeedManager +import ac.aux.compose.managers.bech32ToHexOrNull +import ac.aux.compose.managers.toHex +import ac.aux.compose.network.sockets.NostrIncomingMessage +import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd /** @@ -26,17 +32,12 @@ import ac.aux.compose.exceptions.NostrPublishException class RelaysSocketManager constructor( private val nostrSocketClientFactory: NostrSocketClientFactory, private val cachingImportRepository: CachingImportRepository, -// private val activeAccountStore: ActiveAccountStore, -// private val usersDatabase: UsersDatabase, private val relayRepository: RelayRepository ) { val logger = Logger.withTag("RelaysSocketManager") private val scope = CoroutineScope(Dispatchers.IO) private val relayPoolsMutex = Mutex() - private var relaysObserverJob: Job? = null - - /** * Concrete relay pool */ @@ -56,27 +57,26 @@ class RelaysSocketManager constructor( observeActiveUserId() } + private val observeRelayJobs = mutableMapOf() private fun observeActiveUserId() = scope.launch { -// TODO activeAccountStore.activeUserId.collect { userId -> -// when { -// userId.isEmpty() -> { -// relaysObserverJob?.cancel() -// relaysObserverJob = null -// clearRelayPools() -// } + SeedManager.activePublicKey().toHex()?.let { publicKey -> + observeRelayJobs[publicKey]?.cancel() + + observeRelayJobs[publicKey] = observeRelays(publicKey) + } +// credentialsManager.credentials.collect { credentials -> +// credentials.forEach { credential -> +// credential.npub.bech32ToHexOrNull()?.let { publicKey -> +// observeRelayJobs[publicKey]?.cancel() // -// else -> { -// relaysObserverJob?.cancel() -// relaysObserverJob = observeRelays(userId) +// observeRelayJobs[publicKey] = observeRelays(publicKey) // } // } // } } - private suspend fun isCachingProxyEnabled() = false // TODO: activeAccountStore.activeUserAccount().cachingProxyEnabled - private fun observeRelays(publicKey: String): Job = scope.launch { try { @@ -129,7 +129,7 @@ class RelaysSocketManager constructor( // customPool.closePool() } - fun tryConnectingToAllUserRelays() { + fun tryConnectingToAllRelays() { relayPool.relays.forEach { scope.launch { relayPool.tryConnectingToRelay(it.url) @@ -138,4 +138,11 @@ class RelaysSocketManager constructor( } suspend fun tryConnectingToUserRelay(url: String) = relayPool.tryConnectingToRelay(url) + + suspend fun query(reqCommand: ReqCmd, relayUrl: String): Pair> { + return relayPool.query( + reqCommand = reqCommand, + relayUrl = relayUrl + ) + } } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrIncomingMessage.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrIncomingMessage.kt index aebf30dc..57d69f8b 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrIncomingMessage.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrIncomingMessage.kt @@ -1,6 +1,8 @@ package ac.aux.compose.network.sockets import ac.aux.compose.database.model.NostrEvent +import ac.aux.compose.exceptions.NetworkException +import ac.aux.compose.exceptions.NostrNoticeException sealed class NostrIncomingMessage { @@ -38,3 +40,16 @@ sealed class NostrIncomingMessage { val nostrEvents: List = emptyList(), ) : NostrIncomingMessage() } + +fun NostrIncomingMessage?.verifyOrThrow(subscriptionId: String) { + if (this == null) { + throw NetworkException("No messages received.") + } + + if (this is NostrIncomingMessage.NoticeMessage) { + throw NostrNoticeException( + reason = this.message, + subscriptionId = subscriptionId, + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClient.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClient.kt index 0da84a89..8f8eb8f4 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClient.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClient.kt @@ -26,4 +26,6 @@ interface NostrSocketClient { suspend fun sendEVENT(signedEvent: JsonObject) suspend fun sendREQ(subscriptionId: String, data: JsonObject) + + suspend fun sendMESSAGE(text: String, ensureSessionBeforeSend: Boolean = true) } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClientImpl.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClientImpl.kt index 4aead323..7aaa0c7e 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClientImpl.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/network/sockets/NostrSocketClientImpl.kt @@ -73,7 +73,7 @@ internal class NostrSocketClientImpl( onSocketConnectionOpened?.invoke(url) if (incomingCompressionEnabled) { val id = Uuid.generateV4().toHexDashString() - sendMessage( + sendMESSAGE( text = """["REQ","$id",{"cache":["set_primal_protocol",{"compression":"zlib"}]}]""", ensureSessionBeforeSend = false, ) @@ -155,7 +155,7 @@ internal class NostrSocketClientImpl( } } - private suspend fun sendMessage(text: String, ensureSessionBeforeSend: Boolean = true) { + override suspend fun sendMESSAGE(text: String, ensureSessionBeforeSend: Boolean) { if (ensureSessionBeforeSend) { ensureSocketConnectionOrThrow() } @@ -165,21 +165,21 @@ internal class NostrSocketClientImpl( override suspend fun sendREQ(subscriptionId: String, data: JsonObject) { val reqMessage = data.buildNostrREQMessage(subscriptionId) - return sendMessage(text = reqMessage) + return sendMESSAGE(text = reqMessage) } override suspend fun sendCOUNT(data: JsonObject): String { val subscriptionId: String = Uuid.generateV4().toHexDashString() val reqMessage = data.buildNostrCOUNTMessage(subscriptionId) - sendMessage(text = reqMessage) + sendMESSAGE(text = reqMessage) return subscriptionId } - override suspend fun sendCLOSE(subscriptionId: String) = sendMessage(text = subscriptionId.buildNostrCLOSEMessage()) + override suspend fun sendCLOSE(subscriptionId: String) = sendMESSAGE(text = subscriptionId.buildNostrCLOSEMessage()) - override suspend fun sendEVENT(signedEvent: JsonObject) = sendMessage(text = signedEvent.buildNostrEVENTMessage()) + override suspend fun sendEVENT(signedEvent: JsonObject) = sendMESSAGE(text = signedEvent.buildNostrEVENTMessage()) - override suspend fun sendAUTH(signedEvent: JsonObject) = sendMessage(text = signedEvent.buildNostrAUTHMessage()) + override suspend fun sendAUTH(signedEvent: JsonObject) = sendMESSAGE(text = signedEvent.buildNostrAUTHMessage()) private fun logLargeText( text: String, diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/nostr/Relays.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/nostr/Relays.kt index 0b1e5854..967a607a 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/nostr/Relays.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/nostr/Relays.kt @@ -35,7 +35,7 @@ object Relays { val bootstrapInboxRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr, yabu) val eventFinderRelaySet = setOf(wine, damus, primal, mom, nos, bitcoiner, oxtr) - val eventPublishRelaySet = setOf(damus, primal, mom, nos, bitcoiner, oxtr) + val eventPublishRelaySet = setOf(primal, mom, nos, bitcoiner, oxtr) val DefaultNIP65RelaySet = setOf(mom, nos, bitcoiner) diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrNotaryRepository.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrNotaryRepository.kt index 7eb697ce..8a44def6 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrNotaryRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/repository/NostrNotaryRepository.kt @@ -7,11 +7,13 @@ import ac.aux.compose.exceptions.SignatureException import ac.aux.compose.exceptions.SigningKeyNotFoundException import ac.aux.compose.exceptions.SigningRejectedException import ac.aux.compose.managers.CredentialsManager +import ac.aux.compose.managers.SeedManager import ac.aux.compose.managers.hexToNpubHrp import ac.aux.compose.network.UserAgent import ac.aux.compose.network.asClientTag import ac.aux.compose.network.dto.RelayDTO import com.vitorpamplona.quartz.nip19Bech32.toNpub +import com.vitorpamplona.quartz.nip19Bech32.toNsec import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent import fr.acinq.secp256k1.Hex import kotlinx.coroutines.CoroutineScope @@ -31,7 +33,6 @@ import kotlinx.coroutines.sync.withLock */ class NostrNotaryRepository( private val nostrRepository: NostrRepository, - private val credentialsStore: CredentialsManager, ) { private val scope = CoroutineScope(Dispatchers.Main) @@ -75,13 +76,15 @@ class NostrNotaryRepository( private fun findNsecOrThrow(pubkey: String): String = runCatching { - val npub = Hex.decode(pubkey).toNpub() - credentialsStore.findOrThrow(npub = npub).nsec +// val npub = Hex.decode(pubkey).toNpub() +// credentialsStore.findOrThrow(npub = npub).nsec + SeedManager.activeKeyPair().privKey?.toNsec() }.getOrNull() ?: throw SigningKeyNotFoundException() private fun signNostrEvent(publicKey: String, event: UnsignedNostrEvent): NostrEvent? { val isExternalSignerLogin = runCatching { - credentialsStore.isExternalSignerCredential(npub = publicKey.hexToNpubHrp()) +// credentialsStore.isExternalSignerCredential(npub = publicKey.hexToNpubHrp()) + false }.getOrDefault(false) if (isExternalSignerLogin) { diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt index f60a9cec..5953ddba 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/NavigationViewModel.kt @@ -59,11 +59,9 @@ class NavigationViewModel( val relaysSocketManager = RelaysSocketManager( nostrSocketClientFactory = NostrSocketClientFactory, cachingImportRepository = CachingImportRepository.NO_OP_CACHING_IMPORT_REPOSITORY, - relayRepository = relayRepository + relayRepository = relayRepository, ) - - companion object { private const val TAG = "NavigationViewModel" @@ -86,8 +84,6 @@ class NavigationViewModel( private val logger = Logger.withTag(TAG) - private val syncingJobs = mutableMapOf() - val httpClient = HttpClient() { install(WebSockets) { contentConverter = KotlinxWebsocketSerializationConverter(Json { @@ -98,10 +94,6 @@ class NavigationViewModel( } } - val nostrEventBroadcaster = NostrEventBroadcaster( - scope = scope - ) - private val _navigationUIState = MutableStateFlow( initialNavigationUIState ) @@ -156,8 +148,6 @@ class NavigationViewModel( private fun observePendingSyncNostrEventRequests() { logger.i { "observePendingSyncNostrEventRequests" } - val syncEventCache = mutableMapOf() - scope.launch(Dispatchers.IO) { nostrRepository.observePendingSynchronizeNostrEventRequests().collect { synchronizeNostrEventRequestOrNull -> synchronizeNostrEventRequestOrNull?.let { synchronizeNostrEventRequest -> @@ -179,120 +169,32 @@ class NavigationViewModel( } ) + scope.launch { try { - val webSocketSession = httpClient.webSocketSession( - urlString = synchronizeNostrEventRequest.relayURL + + val result = relaysSocketManager.query( + reqCommand, + synchronizeNostrEventRequest.relayURL ) - // TODO: don't make a new sync job if there's an active job? - syncingJobs[synchronizeNostrEventRequest.relayURL] = scope.launch { - val filterRequest = OptimizedJsonMapper.toJson(reqCommand) - logger.d("Syncing: $filterRequest") - webSocketSession.send( - frame = Frame.Text(filterRequest) + logger.d("Result: $result") + + result.second.forEach { nostrEvent -> + nostrRepository.saveNostrEvent( + nostrEvent = nostrEvent, + synchronizeNostrEventRequest, + synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } + synchronizeNostrEventRequest.relayURL ) - - nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest) - - webSocketSession.incoming.consumeAsFlow().collect { frame -> - when(frame) { - is Frame.Text -> { - frame.readText().let { text -> - try { - logger.d("WebSocket Read Text: $text") - val result = Json.decodeFromString(text) - - if (result.firstOrNull()?.text == "EVENT") { - result.getOrNull(1)?.text?.let { subscriptionId -> - if (subscriptionId == synchronizeNostrEventRequest.id) { - - result.getOrNull(2)?.jsonObject?.toString()?.let { jsonText -> - logger.i("Processing: $jsonText") - val event = Event.fromJson( - jsonText - ) - - // TODO: Do profile specific cache (to avoid wiping an updated profile) - syncEventCache.getOrPut(event.id, { - val taggedEvent = event.firstTaggedEvent() - val taggedUser = event.firstTaggedUser() - - val nostrEvent = NostrEvent( - id = event.id, - pubKey = event.pubKey, - kind = event.kind, - content = event.content, - tags = event.tags, - createdAt = Instant.fromEpochSeconds(event.createdAt), - unsignedNostrEventId = synchronizeNostrEventRequest.unsignedNostrEventId, - sig = event.sig, - - taggedNostrEventId = taggedEvent?.eventId, - taggedNostrEventRelayUrl = taggedEvent?.relay?.url, - - taggedPublicKey = taggedUser?.pubKey, - taggedPublicKeyRelayUrl = taggedUser?.relayHint?.url - ) - - nostrRepository.saveNostrEvent( - nostrEvent = nostrEvent, - synchronizeNostrEventRequest, - synchronizationRelayURLs = Relays.eventPublishRelaySet.map { normalizedRelayUrl -> normalizedRelayUrl.url } - ) - - nostrEvent - }) - - } - } - } - } else if(result.firstOrNull()?.text == "EOSE") { - result.getOrNull(1)?.text?.let { subscriptionId -> - if (subscriptionId == synchronizeNostrEventRequest.id) { - // Close the job - logger.d("We need to close the job") - } - } - } else { - logger.d("Unsupported: $text") - } - } catch (e: Throwable) { - logger.e("Error processing receipt: $text", e) - } - } - // Syncing doesn't necessary have to close after result.. - webSocketSession.close() - syncingJobs.remove(synchronizeNostrEventRequest.relayURL)?.cancel() - } - is Frame.Close -> { - logger.d("Close $frame") - } - is Frame.Ping -> { - logger.d("Ping: $frame") - } - is Frame.Pong -> { - logger.d("Pong: $frame") - } - is Frame.Binary -> { - logger.d("Binary Message: $frame") - } - else -> { - logger.d("Unsupported frame: $frame") - } - } - } - - logger.i("Websocket exit") } - syncingJobs[synchronizeNostrEventRequest.relayURL]?.join() - } catch (e: Throwable) { - logger.e("Relay Error (${synchronizeNostrEventRequest.relayURL})", e) - // TODO: Have it failed instead of processed... nostrRepository.synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest) + } catch (e: Throwable) { + logger.e("Failed to sync", e) } } + + } } } @@ -325,24 +227,6 @@ class NavigationViewModel( ) ) ) - -// nostrEventBroadcaster.broadcastEvent( -// localBroadcastNostrEventRequest = localBroadcastNostrEventRequest, -// onBroadcastRequestProcessed = { broadcastNostrEventRequest -> -// scope.launch(Dispatchers.IO) { -// nostrRepository.broadcastProcessed( -// broadcastNostrEventRequest -// ) -// } -// }, -// onBroadcastReceipt = { broadcastNostrEventReceipt -> -// scope.launch(Dispatchers.IO) { -// nostrRepository.saveBroadcastReceipt( -// broadcastNostrEventReceipt -// ) -// } -// } -// ) } } } diff --git a/composeApp/src/iosMain/kotlin/ac/aux/compose/managers/DataStoreManager.ios.kt b/composeApp/src/iosMain/kotlin/ac/aux/compose/managers/DataStoreManager.ios.kt new file mode 100644 index 00000000..ff8d2cc9 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/ac/aux/compose/managers/DataStoreManager.ios.kt @@ -0,0 +1,33 @@ +package ac.aux.compose.managers + +import ac.aux.compose.PlatformContext +import androidx.datastore.core.Storage +import androidx.datastore.core.okio.OkioSerializer +import androidx.datastore.core.okio.OkioStorage +import okio.FileSystem +import okio.Path.Companion.toPath +import platform.Foundation.NSDocumentDirectory +import platform.Foundation.NSFileManager +import platform.Foundation.NSURL +import platform.Foundation.NSUserDomainMask + +actual fun computeDatastoreStorage( + platformContext: PlatformContext, + dataStoreFileName: String +): Storage> { + return OkioStorage( + fileSystem = FileSystem.SYSTEM, + serializer = OkioSerializer>, + producePath = { + val documentDirectory: NSURL? = NSFileManager.defaultManager.URLsForDirectory( + directory = NSDocumentDirectory, + inDomains = NSUserDomainMask, + appropriateForURL = null, + create = false, + error = null + ) + + (requireNotNull(documentDirectory).path + "/$dataStoreFileName").toPath() + } + ) +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/ac/aux/compose/managers/DataStoreManager.jvm.kt b/composeApp/src/jvmMain/kotlin/ac/aux/compose/managers/DataStoreManager.jvm.kt new file mode 100644 index 00000000..c462fe5a --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/ac/aux/compose/managers/DataStoreManager.jvm.kt @@ -0,0 +1,15 @@ +package ac.aux.compose.managers + +import ac.aux.compose.PlatformContext +import androidx.datastore.core.Storage +import io.ktor.client.plugins.cache.storage.FileStorage + +actual fun computeDatastoreStorage( + platformContext: PlatformContext, + dataStoreFileName: String +): Storage> { + FileStorage( + directory = "" + ) + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b81f1790..9ae707fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,6 +40,7 @@ androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "an androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } +androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "pagingCommon" } androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingCommon" }