Creates an interface for NostrClient

This commit is contained in:
Vitor Pamplona
2025-09-06 10:23:33 -04:00
parent a03ba7b260
commit 69e3d2d0d8
9 changed files with 143 additions and 30 deletions
@@ -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<RelayPool.RelayPoolStatus>
fun connect()
fun disconnect()
fun isActive(): Boolean
fun openReqSubscription(
subId: String = newSubId(),
filters: Map<NormalizedRelayUrl, List<Filter>>,
)
fun openCountSubscription(
subId: String = newSubId(),
filters: Map<NormalizedRelayUrl, List<Filter>>,
)
fun close(subscriptionId: String)
fun sendIfExists(
event: Event,
connectedRelay: NormalizedRelayUrl,
)
fun send(
event: Event,
relayList: Set<NormalizedRelayUrl>,
)
fun subscribe(listener: IRelayClientListener)
fun unsubscribe(listener: IRelayClientListener)
fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>?
fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>?
}
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<NormalizedRelayUrl, List<Filter>>,
) { }
override fun openCountSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) { }
override fun close(subscriptionId: String) { }
override fun sendIfExists(
event: Event,
connectedRelay: NormalizedRelayUrl,
) { }
override fun send(
event: Event,
relayList: Set<NormalizedRelayUrl>,
) { }
override fun subscribe(listener: IRelayClientListener) {}
override fun unsubscribe(listener: IRelayClientListener) {}
override fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = null
override fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = null
}
@@ -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<NormalizedRelayUrl, List<Filter>>,
) {
val oldFilters = activeRequests.getSubscriptionFiltersOrNull(subId) ?: emptyMap()
@@ -240,8 +240,8 @@ class NostrClient(
}
}
fun openCountSubscription(
subId: String = newSubId(),
override fun openCountSubscription(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) {
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<NormalizedRelayUrl>,
) {
@@ -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<HexKey> = eventOutbox.activeOutboxCacheFor(url)
fun getSubscriptionFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeRequests.getSubscriptionFiltersOrNull(subId)
override fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeRequests.getSubscriptionFiltersOrNull(subId)
fun relayStatusFlow() = relayPool.statusFlow
override fun getCountFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = activeCounts.getSubscriptionFiltersOrNull(subId)
override fun relayStatusFlow() = relayPool.statusFlow
}
@@ -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<NormalizedRelayUrl, List<Filter>>,
onResponse: (Event) -> Unit,
@@ -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<String, Subscription>()
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 ->