diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 7ce629bb9..efb4208c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -121,7 +121,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.downloadFirstEvent import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter @@ -220,7 +220,7 @@ class Account( val signer: NostrSigner, geolocationFlow: StateFlow, val cache: LocalCache, - val client: NostrClient, + val client: INostrClient, val scope: CoroutineScope, ) { private var userProfileCache: User? = null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/BaseEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/BaseEoseManager.kt index ea0c7db73..cfc55e4d8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/BaseEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/BaseEoseManager.kt @@ -23,14 +23,14 @@ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers import android.util.Log import com.vitorpamplona.amethyst.isDebug import com.vitorpamplona.ammolite.relays.BundledUpdate -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.SubscriptionController import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import kotlinx.coroutines.Dispatchers abstract class BaseEoseManager( - val client: NostrClient, + val client: INostrClient, val allKeys: () -> Set, ) { protected val logTag: String = this.javaClass.simpleName diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt index 651cc38dd..c07a30266 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/eoseManagers/SingleSubNoEoseCacheEoseManager.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.service.relayClient.eoseManagers -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay @@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay * filters */ abstract class SingleSubNoEoseCacheEoseManager( - client: NostrClient, + client: INostrClient, allKeys: () -> Set, val invalidateAfterEose: Boolean = false, ) : BaseEoseManager(client, allKeys) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt index 71ca89d70..f372693db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentFilterAssembler.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl // This allows multiple screen to be listening to tags, even the same tag @@ -34,7 +34,7 @@ class NWCPaymentQueryState( ) class NWCPaymentFilterAssembler( - client: NostrClient, + client: INostrClient, ) : ComposeSubscriptionManager() { val group = listOf( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt index 98e15761e..369c6b69f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCPaymentWatcherSubAssembler.kt @@ -21,11 +21,11 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubNoEoseCacheEoseManager -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter class NWCPaymentWatcherSubAssembler( - client: NostrClient, + client: INostrClient, allKeys: () -> Set, ) : SingleSubNoEoseCacheEoseManager(client, allKeys) { override fun updateFilter(keys: List): List? { diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt new file mode 100644 index 000000000..badd472f0 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/INostrClient.kt @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip01Core.relay.client + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool +import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +interface INostrClient { + fun relayStatusFlow(): StateFlow + + fun connect() + + fun disconnect() + + fun isActive(): Boolean + + fun openReqSubscription( + subId: String = newSubId(), + filters: Map>, + ) + + fun openCountSubscription( + subId: String = newSubId(), + filters: Map>, + ) + + fun close(subscriptionId: String) + + fun sendIfExists( + event: Event, + connectedRelay: NormalizedRelayUrl, + ) + + fun send( + event: Event, + relayList: Set, + ) + + fun subscribe(listener: IRelayClientListener) + + fun unsubscribe(listener: IRelayClientListener) + + fun getReqFiltersOrNull(subId: String): Map>? + + fun getCountFiltersOrNull(subId: String): Map>? +} + +object EmptyNostrClient : INostrClient { + override fun relayStatusFlow() = MutableStateFlow(RelayPool.RelayPoolStatus()) + + override fun connect() { } + + override fun disconnect() { } + + override fun isActive() = false + + override fun openReqSubscription( + subId: String, + filters: Map>, + ) { } + + override fun openCountSubscription( + subId: String, + filters: Map>, + ) { } + + override fun close(subscriptionId: String) { } + + override fun sendIfExists( + event: Event, + connectedRelay: NormalizedRelayUrl, + ) { } + + override fun send( + event: Event, + relayList: Set, + ) { } + + override fun subscribe(listener: IRelayClientListener) {} + + override fun unsubscribe(listener: IRelayClientListener) {} + + override fun getReqFiltersOrNull(subId: String): Map>? = null + + override fun getCountFiltersOrNull(subId: String): Map>? = null +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt index e464d787f..81a35d07b 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt @@ -29,7 +29,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.PoolSubscriptionRepo import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayPool import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient -import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -73,7 +72,8 @@ import kotlinx.coroutines.flow.stateIn class NostrClient( private val websocketBuilder: WebsocketBuilder, private val scope: CoroutineScope, -) : IRelayClientListener { +) : INostrClient, + IRelayClientListener { private val relayPool: RelayPool = RelayPool(this, ::buildRelay) private val activeRequests: PoolSubscriptionRepository = PoolSubscriptionRepository() private val activeCounts: PoolSubscriptionRepository = PoolSubscriptionRepository() @@ -124,17 +124,17 @@ class NostrClient( } // Reconnects all relays that may have disconnected - fun connect() { + override fun connect() { isActive = true relayPool.connect() } - fun disconnect() { + override fun disconnect() { isActive = false relayPool.disconnect() } - fun isActive() = isActive + override fun isActive() = isActive @Synchronized fun reconnect(onlyIfChanged: Boolean = false) { @@ -204,8 +204,8 @@ class NostrClient( return false } - fun openReqSubscription( - subId: String = newSubId(), + override fun openReqSubscription( + subId: String, filters: Map>, ) { val oldFilters = activeRequests.getSubscriptionFiltersOrNull(subId) ?: emptyMap() @@ -240,8 +240,8 @@ class NostrClient( } } - fun openCountSubscription( - subId: String = newSubId(), + override fun openCountSubscription( + subId: String, filters: Map>, ) { val oldFilters = activeCounts.getSubscriptionFiltersOrNull(subId) ?: emptyMap() @@ -276,7 +276,7 @@ class NostrClient( } } - fun sendIfExists( + override fun sendIfExists( event: Event, connectedRelay: NormalizedRelayUrl, ) { @@ -288,7 +288,7 @@ class NostrClient( } } - fun send( + override fun send( event: Event, relayList: Set, ) { @@ -301,7 +301,7 @@ class NostrClient( } } - fun close(subscriptionId: String) { + override fun close(subscriptionId: String) { activeRequests.remove(subscriptionId) activeCounts.remove(subscriptionId) relayPool.close(subscriptionId) @@ -386,13 +386,13 @@ class NostrClient( listeners.forEach { it.onError(relay, subId, error) } } - fun subscribe(listener: IRelayClientListener) { + override fun subscribe(listener: IRelayClientListener) { listeners = listeners.plus(listener) } fun isSubscribed(listener: IRelayClientListener): Boolean = listeners.contains(listener) - fun unsubscribe(listener: IRelayClientListener) { + override fun unsubscribe(listener: IRelayClientListener) { listeners = listeners.minus(listener) } @@ -402,7 +402,9 @@ class NostrClient( fun activeOutboxCache(url: NormalizedRelayUrl): Set = eventOutbox.activeOutboxCacheFor(url) - fun getSubscriptionFiltersOrNull(subId: String): Map>? = activeRequests.getSubscriptionFiltersOrNull(subId) + override fun getReqFiltersOrNull(subId: String): Map>? = activeRequests.getSubscriptionFiltersOrNull(subId) - fun relayStatusFlow() = relayPool.statusFlow + override fun getCountFiltersOrNull(subId: String): Map>? = activeCounts.getSubscriptionFiltersOrNull(subId) + + override fun relayStatusFlow() = relayPool.statusFlow } diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt index 5d07db9ef..6f62fb287 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.accessories import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient import com.vitorpamplona.quartz.nip01Core.relay.client.single.newSubId @@ -32,7 +32,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -fun NostrClient.downloadFirstEvent( +fun INostrClient.downloadFirstEvent( subscriptionId: String = newSubId(), filters: Map>, onResponse: (Event) -> Unit, diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt index 9e5c57d43..fd7de43d3 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient @@ -47,7 +48,7 @@ import com.vitorpamplona.quartz.utils.LargeCache * - Dismiss subscriptions with [dismissSubscription]. */ class SubscriptionController( - val client: NostrClient, + val client: INostrClient, ) { private val subscriptions = LargeCache() private val stats = SubscriptionStats() @@ -108,7 +109,7 @@ class SubscriptionController( fun updateRelays() { val currentFilters = subscriptions.associateWith { id, sub -> - client.getSubscriptionFiltersOrNull(id) + client.getReqFiltersOrNull(id) } subscriptions.forEach { id, sub ->