refactor: simplify NostrClient API for beginner-friendliness

Comprehensive rename of the NostrClient relay infrastructure to use
intuitive, self-documenting names instead of protocol-level jargon.

Interface/class renames:
- INostrClient → NostrClient (interface)
- NostrClient → DefaultNostrClient (implementation)
- IRequestListener → SubscriptionListener
- IRelayClientListener → RelayConnectionListener
- IOpenNostrRequest → SubscriptionHandle
- NostrClientStaticReq → StaticSubscription
- NostrClientDynamicReq → DynamicSubscription

Method renames:
- openReqSubscription() → subscribe()
- close(subId) → unsubscribe(subId)
- send() → publish()
- queryCount() → count()
- subscribe/unsubscribe(listener) → addConnectionListener/removeConnectionListener
- renewFilters() → syncFilters()

Callback renames:
- onEose → onCaughtUp
- isLive → isRealTime
- onStartReq → onSubscriptionStarted
- onCloseReq → onSubscriptionClosed

Extension function renames:
- query() → fetchAll()
- downloadFirstEvent() → fetchFirst()
- req() → subscribe()
- reqAsFlow() → subscribeAsFlow()
- reqUntilEoseAsFlow() → fetchAsFlow()
- sendAndWaitForResponse() → publishAndConfirm()
- queryCountSuspend() → count()
- queryCountMergedHll() → countMerged()
- reqBypassingRelayLimits() → fetchAllPages()
- updateFilter() → refresh() on SubscriptionHandle

https://claude.ai/code/session_01JPcYCcRx5eZN4GvgGxGwbf
This commit is contained in:
Claude
2026-03-28 03:23:20 +00:00
parent 0054c01921
commit 4e2717c5c5
169 changed files with 888 additions and 892 deletions
@@ -193,7 +193,7 @@ fun main() {
}
Window(
onCloseRequest = ::exitApplication,
onSubscriptionCloseduest = ::exitApplication,
state = windowState,
title = "Amethyst",
) {
@@ -30,8 +30,7 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
@@ -132,11 +131,11 @@ class AccountManager internal constructor(
// --- Dedicated NIP-46 client (isolated from general relay pool) ---
private val nip46ClientMutex = Mutex()
private var nip46Client: NostrClient? = null
private var nip46Client: DefaultDefaultNostrClient? = null
private suspend fun getOrCreateNip46Client(): NostrClient =
private suspend fun getOrCreateNip46Client(): DefaultNostrClient =
nip46ClientMutex.withLock {
nip46Client ?: NostrClient(
nip46Client ?: DefaultNostrClient(
BasicOkHttpWebSocket.Builder(DesktopHttpClient::getHttpClient),
).also {
nip46Client = it
@@ -156,7 +155,7 @@ class AccountManager internal constructor(
* but we must wait for the websocket to be ready before sending requests.
*/
private suspend fun awaitNip46RelayConnection(
client: NostrClient,
client: DefaultNostrClient,
targetRelays: Set<NormalizedRelayUrl>,
) {
withTimeout(NIP46_RELAY_CONNECT_TIMEOUT_MS) {
@@ -180,8 +179,8 @@ class AccountManager internal constructor(
}
}
private fun createLoginRelayListener(): IRelayClientListener =
object : IRelayClientListener {
private fun createLoginRelayListener(): RelayConnectionListener =
object : RelayConnectionListener {
override fun onConnected(
relay: IRelayClient,
pingMillis: Int,
@@ -303,7 +302,7 @@ class AccountManager internal constructor(
suspend fun loginWithBunker(bunkerUri: String): Result<AccountState.LoggedIn> {
val listener = createLoginRelayListener()
var client: NostrClient? = null
var client: DefaultDefaultNostrClient? = null
try {
val ephemeralKeyPair = KeyPair()
val ephemeralSigner = NostrSignerInternal(ephemeralKeyPair)
@@ -316,7 +315,7 @@ class AccountManager internal constructor(
LoginProgress.ConnectingToRelays(
relaysFromUri.associateWith { RelayLoginStatus.CONNECTING },
)
nip46Client.subscribe(listener)
nip46Client.addConnectionListener(listener)
_loginProgress.value =
LoginProgress.WaitingForSigner(
@@ -356,7 +355,7 @@ class AccountManager internal constructor(
return Result.failure(Exception("Connection failed: ${e.message}"))
} finally {
_loginProgress.value = null
client?.unsubscribe(listener)
client?.removeConnectionListener(listener)
}
}
@@ -364,7 +363,7 @@ class AccountManager internal constructor(
suspend fun loginWithNostrConnect(onUriGenerated: (String) -> Unit): Result<AccountState.LoggedIn> {
val listener = createLoginRelayListener()
var client: NostrClient? = null
var client: DefaultDefaultNostrClient? = null
try {
val ephemeralKeyPair = KeyPair()
val uriData = NostrConnectLoginUseCase.generateUri(ephemeralKeyPair, NIP46_RELAYS, "Amethyst%20Desktop")
@@ -376,7 +375,7 @@ class AccountManager internal constructor(
LoginProgress.ConnectingToRelays(
uriData.relays.associateWith { RelayLoginStatus.CONNECTING },
)
nip46Client.subscribe(listener)
nip46Client.addConnectionListener(listener)
onUriGenerated(uriData.uri)
@@ -415,7 +414,7 @@ class AccountManager internal constructor(
return Result.failure(Exception("Connection failed: ${e.message}"))
} finally {
_loginProgress.value = null
client?.unsubscribe(listener)
client?.removeConnectionListener(listener)
}
}
@@ -140,7 +140,7 @@ fun ChessScreen(
onEvent = { event, _, _, _ ->
viewModel.handleIncomingEvent(event)
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
// ChessLobbyLogic handles loading state internally
},
)
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.desktop.network
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
@@ -43,11 +43,11 @@ import kotlinx.coroutines.flow.asStateFlow
*/
open class RelayConnectionManager(
websocketBuilder: WebsocketBuilder,
) : IRelayClientListener {
private val _client = NostrClient(websocketBuilder)
) : RelayConnectionListener {
private val _client = DefaultNostrClient(websocketBuilder)
/** Exposes the underlying INostrClient for subscription coordinators */
val client: com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient get() = _client
/** Exposes the underlying NostrClient for subscription coordinators */
val client: com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient get() = _client
private val _relayStatuses = MutableStateFlow<Map<NormalizedRelayUrl, RelayStatus>>(emptyMap())
val relayStatuses: StateFlow<Map<NormalizedRelayUrl, RelayStatus>> = _relayStatuses.asStateFlow()
@@ -56,7 +56,7 @@ open class RelayConnectionManager(
val availableRelays: StateFlow<Set<NormalizedRelayUrl>> = _client.availableRelaysFlow()
init {
_client.subscribe(this)
_client.addConnectionListener(this)
}
fun connect() {
@@ -85,21 +85,21 @@ open class RelayConnectionManager(
subId: String,
filters: List<Filter>,
relays: Set<NormalizedRelayUrl> = availableRelays.value,
listener: IRequestListener? = null,
listener: SubscriptionListener? = null,
) {
val filterMap = relays.associateWith { filters }
_client.openReqSubscription(subId, filterMap, listener)
_client.subscribe(subId, filterMap, listener)
}
fun unsubscribe(subId: String) {
_client.close(subId)
_client.unsubscribe(subId)
}
fun send(
fun publish(
event: Event,
relays: Set<NormalizedRelayUrl> = connectedRelays.value,
) {
_client.send(event, relays)
_client.publish(event, relays)
}
/**
@@ -107,21 +107,21 @@ open class RelayConnectionManager(
*/
fun broadcastToAll(event: Event) {
val connected = connectedRelays.value
send(event, connected)
publish(event, connected)
}
/**
* Sends an event to a specific relay (for NWC).
* Adds the relay if not already in the list.
*/
fun sendToRelay(
fun publishToRelay(
relay: NormalizedRelayUrl,
event: Event,
) {
if (relay !in availableRelays.value) {
updateRelayStatus(relay) { it.copy(connected = false, error = null) }
}
_client.send(event, setOf(relay))
_client.publish(event, setOf(relay))
}
/**
@@ -138,14 +138,14 @@ open class RelayConnectionManager(
updateRelayStatus(relay) { it.copy(connected = false, error = null) }
}
val filterMap = mapOf(relay to filters)
_client.openReqSubscription(
_client.subscribe(
subId = subId,
filters = filterMap,
listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -162,7 +162,7 @@ open class RelayConnectionManager(
relay: NormalizedRelayUrl,
subId: String,
) {
_client.close(subId)
_client.unsubscribe(subId)
}
private fun updateRelayStatus(
@@ -103,7 +103,7 @@ class NwcPaymentHandler(
zappedNote?.addZapPayment(requestNote, null)
// Send request to wallet's relay
relayManager.sendToRelay(nwcConnection.relayUri, requestEvent)
relayManager.publishToRelay(nwcConnection.relayUri, requestEvent)
// Subscribe and wait for response with timeout
return withTimeoutOrNull(timeoutMs) {
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.desktop.chess.DesktopChessEventCache
import com.vitorpamplona.amethyst.desktop.network.RelayConnectionManager
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.groupByRelay
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip64Chess.challenge.accept.LiveChessGameAcceptEvent
@@ -58,7 +58,7 @@ private val CHESS_EVENT_KINDS =
class DesktopChessSubscriptionController(
private val relayManager: RelayConnectionManager,
private val onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
private val onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
private val onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
) : ChessSubscriptionController {
private val _currentState = MutableStateFlow<ChessSubscriptionState?>(null)
override val currentState: StateFlow<ChessSubscriptionState?> = _currentState.asStateFlow()
@@ -95,10 +95,10 @@ class DesktopChessSubscriptionController(
filters = allFilters,
relays = state.relays,
listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -106,16 +106,16 @@ class DesktopChessSubscriptionController(
if (event.kind in CHESS_EVENT_KINDS) {
DesktopChessEventCache.add(event)
}
onEvent(event, isLive, relay, forFilters)
onEvent(event, isRealTime, relay, forFilters)
}
override fun onEose(
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
// Track EOSE time for this relay for efficient re-subscription
relayEoseTimes[relay] = TimeUtils.now()
onEose(relay, forFilters)
onCaughtUp(relay, forFilters)
}
},
)
@@ -162,7 +162,7 @@ class DesktopChessSubscriptionController(
* @param activeGameIds Set of game IDs the user is actively playing (for game-specific filters)
* @param opponentPubkeys Set of opponent pubkeys for active games (for move filtering)
* @param onEvent Callback for incoming events
* @param onEose Callback for EOSE (End of Stored Events)
* @param onCaughtUp Callback for EOSE (End of Stored Events)
*/
fun createChessSubscriptionWithGames(
relays: Set<NormalizedRelayUrl>,
@@ -170,7 +170,7 @@ fun createChessSubscriptionWithGames(
activeGameIds: Set<String> = emptySet(),
opponentPubkeys: Set<String> = emptySet(),
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (relays.isEmpty()) return null
@@ -197,7 +197,7 @@ fun createChessSubscriptionWithGames(
filters = filters,
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -207,11 +207,11 @@ fun createChessSubscriptionWithGames(
*/
@Deprecated(
"Use createChessSubscriptionWithGames for active game support",
ReplaceWith("createChessSubscriptionWithGames(relays, userPubkey, emptySet(), emptySet(), onEvent, onEose)"),
ReplaceWith("createChessSubscriptionWithGames(relays, userPubkey, emptySet(), emptySet(), onEvent, onCaughtUp)"),
)
fun createChessSubscription(
relays: Set<NormalizedRelayUrl>,
userPubkey: String,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? = createChessSubscriptionWithGames(relays, userPubkey, emptySet(), emptySet(), onEvent, onEose)
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? = createChessSubscriptionWithGames(relays, userPubkey, emptySet(), emptySet(), onEvent, onCaughtUp)
@@ -30,8 +30,8 @@ import com.vitorpamplona.amethyst.desktop.model.DesktopDmRelayState
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.CoroutineScope
@@ -73,7 +73,7 @@ import kotlin.time.Duration.Companion.seconds
* ```
*/
class DesktopRelaySubscriptionsCoordinator(
private val client: INostrClient,
private val client: NostrClient,
private val scope: CoroutineScope,
private val indexRelays: Set<NormalizedRelayUrl>,
private val localCache: DesktopLocalCache,
@@ -153,7 +153,7 @@ class DesktopRelaySubscriptionsCoordinator(
// Cancel any existing subscription with this ID
screenSubscriptions.remove(subId)?.cancel()
client.close(subId)
client.unsubscribe(subId)
if (noteIds.isEmpty() || relays.isEmpty()) return subId
@@ -177,10 +177,10 @@ class DesktopRelaySubscriptionsCoordinator(
)
val listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -190,7 +190,7 @@ class DesktopRelaySubscriptionsCoordinator(
val job =
scope.launch {
client.openReqSubscription(
client.subscribe(
subId = subId,
filters = relays.associateWith { filters },
listener = listener,
@@ -206,7 +206,7 @@ class DesktopRelaySubscriptionsCoordinator(
*/
fun releaseInteractions(subId: String) {
screenSubscriptions.remove(subId)?.cancel()
client.close(subId)
client.unsubscribe(subId)
}
/**
@@ -217,7 +217,7 @@ class DesktopRelaySubscriptionsCoordinator(
// Start rate limiter to process queued metadata requests
rateLimiter.start { pubkey ->
// When rate limiter dequeues a pubkey, subscribe to its metadata
client.openReqSubscription(
client.subscribe(
filters =
indexRelays.associateWith {
listOf(
@@ -281,10 +281,10 @@ class DesktopRelaySubscriptionsCoordinator(
if (inboxRelays.isEmpty() && outboxRelays.isEmpty()) return
val listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -296,7 +296,7 @@ class DesktopRelaySubscriptionsCoordinator(
if (inboxRelays.isNotEmpty()) {
val inboxSubId = generateSubId("dm-inbox-${userPubKeyHex.take(8)}")
activeDmSubIds.add(inboxSubId)
client.openReqSubscription(
client.subscribe(
subId = inboxSubId,
filters =
inboxRelays.associateWith {
@@ -310,7 +310,7 @@ class DesktopRelaySubscriptionsCoordinator(
if (outboxRelays.isNotEmpty()) {
val outboxSubId = generateSubId("dm-outbox-${userPubKeyHex.take(8)}")
activeDmSubIds.add(outboxSubId)
client.openReqSubscription(
client.subscribe(
subId = outboxSubId,
filters =
outboxRelays.associateWith {
@@ -324,7 +324,7 @@ class DesktopRelaySubscriptionsCoordinator(
if (inboxRelays.isNotEmpty()) {
val giftWrapSubId = generateSubId("giftwrap-${userPubKeyHex.take(8)}")
activeDmSubIds.add(giftWrapSubId)
client.openReqSubscription(
client.subscribe(
subId = giftWrapSubId,
filters =
inboxRelays.associateWith {
@@ -340,7 +340,7 @@ class DesktopRelaySubscriptionsCoordinator(
*/
fun unsubscribeFromDms() {
activeDmSubIds.forEach { subId ->
client.close(subId)
client.unsubscribe(subId)
}
activeDmSubIds.clear()
}
@@ -353,7 +353,7 @@ class DesktopRelaySubscriptionsCoordinator(
// Clean up screen-triggered subscriptions
screenSubscriptions.forEach { (subId, job) ->
job.cancel()
client.close(subId)
client.unsubscribe(subId)
}
screenSubscriptions.clear()
_lastEventAt.value = null
@@ -39,14 +39,14 @@ fun createGlobalFeedSubscription(
relays: Set<NormalizedRelayUrl>,
limit: Int = 50,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("global-feed"),
filters = listOf(FilterBuilders.textNotesGlobal(limit = limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -57,7 +57,7 @@ fun createFollowingFeedSubscription(
followedUsers: List<String>,
limit: Int = 50,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (followedUsers.isEmpty()) return null
@@ -66,7 +66,7 @@ fun createFollowingFeedSubscription(
filters = listOf(FilterBuilders.textNotesFromAuthors(followedUsers, limit = limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -77,14 +77,14 @@ fun createContactListSubscription(
relays: Set<NormalizedRelayUrl>,
pubKeyHex: String,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("contacts-${pubKeyHex.take(8)}"),
filters = listOf(FilterBuilders.contactList(pubKeyHex)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -94,14 +94,14 @@ fun createNoteSubscription(
relays: Set<NormalizedRelayUrl>,
noteId: String,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("note-${noteId.take(8)}"),
filters = listOf(FilterBuilders.byIds(listOf(noteId))),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -115,7 +115,7 @@ fun createThreadRepliesSubscription(
noteId: String,
limit: Int = 200,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("thread-${noteId.take(8)}"),
@@ -129,7 +129,7 @@ fun createThreadRepliesSubscription(
),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -144,7 +144,7 @@ fun createSearchPeopleSubscription(
searchQuery: String,
limit: Int = 50,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onClosed: (NormalizedRelayUrl, String, List<Filter>?) -> Unit = { _, _, _ -> },
): SubscriptionConfig? {
if (searchQuery.isBlank()) return null
@@ -154,7 +154,7 @@ fun createSearchPeopleSubscription(
filters = listOf(FilterBuilders.searchPeople(searchQuery, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
onClosed = onClosed,
)
}
@@ -171,7 +171,7 @@ fun createSearchNotesSubscription(
searchQuery: String,
limit: Int = 50,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (searchQuery.isBlank()) return null
@@ -180,7 +180,7 @@ fun createSearchNotesSubscription(
filters = listOf(FilterBuilders.searchNotes(searchQuery, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -195,7 +195,7 @@ fun createZapsSubscription(
eventIds: List<String>,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (eventIds.isEmpty()) return null
@@ -204,7 +204,7 @@ fun createZapsSubscription(
filters = listOf(FilterBuilders.zapsForEvents(eventIds, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -219,7 +219,7 @@ fun createReactionsSubscription(
eventIds: List<String>,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (eventIds.isEmpty()) return null
@@ -228,7 +228,7 @@ fun createReactionsSubscription(
filters = listOf(FilterBuilders.reactionsForEvents(eventIds, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -243,7 +243,7 @@ fun createRepliesSubscription(
eventIds: List<String>,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (eventIds.isEmpty()) return null
@@ -252,7 +252,7 @@ fun createRepliesSubscription(
filters = listOf(FilterBuilders.repliesForEvents(eventIds, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -267,7 +267,7 @@ fun createRepostsSubscription(
eventIds: List<String>,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (eventIds.isEmpty()) return null
@@ -276,7 +276,7 @@ fun createRepostsSubscription(
filters = listOf(FilterBuilders.repostsForEvents(eventIds, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -287,14 +287,14 @@ fun createLongFormFeedSubscription(
relays: Set<NormalizedRelayUrl>,
limit: Int = 30,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("longform-feed"),
filters = listOf(FilterBuilders.longFormGlobal(limit = limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -305,7 +305,7 @@ fun createFollowingLongFormFeedSubscription(
followedUsers: List<String>,
limit: Int = 30,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (followedUsers.isEmpty()) return null
@@ -314,7 +314,7 @@ fun createFollowingLongFormFeedSubscription(
filters = listOf(FilterBuilders.longFormFromAuthors(followedUsers, limit = limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -333,7 +333,7 @@ fun createChessSubscription(
userPubkey: String,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("chess-${userPubkey.take(8)}"),
@@ -348,5 +348,5 @@ fun createChessSubscription(
),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
@@ -144,7 +144,7 @@ fun createNip04DmInboxSubscription(
since: Long? = null,
limit: Int? = null,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (relays.isEmpty() || userPubKeyHex.length != 64) return null
@@ -153,7 +153,7 @@ fun createNip04DmInboxSubscription(
filters = listOf(FilterDMs.nip04ToMe(userPubKeyHex, since, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -167,7 +167,7 @@ fun createNip04DmOutboxSubscription(
since: Long? = null,
limit: Int? = null,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (relays.isEmpty() || userPubKeyHex.length != 64) return null
@@ -176,7 +176,7 @@ fun createNip04DmOutboxSubscription(
filters = listOf(FilterDMs.nip04FromMe(userPubKeyHex, since, limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -189,7 +189,7 @@ fun createGiftWrapSubscription(
userPubKeyHex: HexKey,
since: Long? = null,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (relays.isEmpty() || userPubKeyHex.length != 64) return null
@@ -198,7 +198,7 @@ fun createGiftWrapSubscription(
filters = listOf(FilterDMs.giftWrapsToMe(userPubKeyHex, since)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -213,7 +213,7 @@ fun createDmConversationSubscription(
conversationPubKeys: Set<HexKey>,
since: Long? = null,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (conversationPubKeys.isEmpty() || userPubKeyHex.length != 64) return null
@@ -225,6 +225,6 @@ fun createDmConversationSubscription(
filters = FilterDMs.nip04Conversation(userPubKeyHex, conversationPubKeys, since),
relays = allRelays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -32,7 +32,7 @@ fun createMetadataSubscription(
relays: Set<NormalizedRelayUrl>,
pubKeyHex: String,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
// Validate pubkey length
if (pubKeyHex.length != 64) {
@@ -43,7 +43,7 @@ fun createMetadataSubscription(
filters = listOf(FilterBuilders.userMetadata(pubKeyHex)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -56,7 +56,7 @@ fun createBatchMetadataSubscription(
relays: Set<NormalizedRelayUrl>,
pubKeyHexList: List<String>,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
// Filter out invalid pubkeys
val validPubkeys = pubKeyHexList.filter { it.length == 64 }
@@ -67,7 +67,7 @@ fun createBatchMetadataSubscription(
filters = listOf(FilterBuilders.userMetadataBatch(validPubkeys)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -79,7 +79,7 @@ fun createMetadataListSubscription(
relays: Set<NormalizedRelayUrl>,
pubKeys: List<String>,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig? {
if (pubKeys.isEmpty()) return null
@@ -88,7 +88,7 @@ fun createMetadataListSubscription(
filters = listOf(FilterBuilders.userMetadataMultiple(pubKeys)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
}
@@ -100,7 +100,7 @@ fun createUserPostsSubscription(
pubKeyHex: String,
limit: Int = 50,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("posts-${pubKeyHex.take(8)}"),
@@ -111,7 +111,7 @@ fun createUserPostsSubscription(
),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
/**
@@ -122,12 +122,12 @@ fun createNotificationsSubscription(
pubKeyHex: String,
limit: Int = 100,
onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
): SubscriptionConfig =
SubscriptionConfig(
subId = generateSubId("notif-${pubKeyHex.take(8)}"),
filters = listOf(FilterBuilders.notificationsForUser(pubKeyHex, limit = limit)),
relays = relays,
onEvent = onEvent,
onEose = onEose,
onCaughtUp = onCaughtUp,
)
@@ -25,7 +25,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import com.vitorpamplona.amethyst.desktop.network.RelayConnectionManager
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -45,7 +45,7 @@ data class SubscriptionConfig(
val filters: List<Filter>,
val relays: Set<NormalizedRelayUrl>,
val onEvent: (Event, Boolean, NormalizedRelayUrl, List<Filter>?) -> Unit,
val onEose: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
val onCaughtUp: (NormalizedRelayUrl, List<Filter>?) -> Unit = { _, _ -> },
val onClosed: (NormalizedRelayUrl, String, List<Filter>?) -> Unit = { _, _, _ -> },
)
@@ -80,21 +80,21 @@ fun rememberSubscription(
filters = cfg.filters,
relays = cfg.relays,
listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
cfg.onEvent(event, isLive, relay, forFilters)
cfg.onEvent(event, isRealTime, relay, forFilters)
}
override fun onEose(
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
cfg.onEose(relay, forFilters)
cfg.onCaughtUp(relay, forFilters)
}
override fun onClosed(
@@ -175,7 +175,7 @@ fun ArticleEditorScreen(
)
// TODO: send() is fire-and-forget; markPublished runs before relay ack.
// Consider waiting for relay OK response before marking as published.
relayManager.send(event)
relayManager.publish(event)
draftStore.markPublished(slug)
onPublished()
} catch (e: Exception) {
@@ -222,7 +222,7 @@ fun ArticleReaderScreen(
}
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
eoseReceived = true
},
)
@@ -335,7 +335,7 @@ fun ArticleReaderScreen(
.toSet()
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -168,7 +168,7 @@ fun BookmarksScreen(
publicBookmarkIds = pubIds
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
hasReceivedEose = true
isLoading = false
},
@@ -214,7 +214,7 @@ fun BookmarksScreen(
subscriptionsCoordinator?.consumeEvent(event, relay)
publicEventState.addItem(event)
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -236,7 +236,7 @@ fun BookmarksScreen(
subscriptionsCoordinator?.consumeEvent(event, relay)
privateEventState.addItem(event)
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -170,7 +170,7 @@ fun FeedScreen(
val connectedRelays by relayManager.connectedRelays.collectAsState()
val followedUsers by localCache.followedUsers.collectAsState()
// Available relay URLs — openReqSubscription triggers connection on-demand
// Available relay URLs — subscribe triggers connection on-demand
val allRelayUrls = remember(relayStatuses) { relayStatuses.keys }
var replyToEvent by remember { mutableStateOf<Event?>(null) }
@@ -72,7 +72,7 @@ import com.vitorpamplona.amethyst.desktop.nwc.NwcPaymentHandler
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
@@ -436,10 +436,10 @@ private suspend fun fetchMetadataForUsers(
filters = filters,
relays = relays,
listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -457,7 +457,7 @@ private suspend fun fetchMetadataForUsers(
}
}
override fun onEose(
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -1058,10 +1058,10 @@ private suspend fun fetchUserLightningAddress(
filters = filters,
relays = relays,
listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -1078,7 +1078,7 @@ private suspend fun fetchUserLightningAddress(
}
}
override fun onEose(
override fun onCaughtUp(
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -228,7 +228,7 @@ fun NotificationsScreen(
notificationState.addItem(notification)
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
eoseReceivedCount++
},
)
@@ -261,7 +261,7 @@ fun ReadsScreen(
eventState.addItem(event)
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
eoseReceivedCount++
},
)
@@ -277,7 +277,7 @@ fun ReadsScreen(
eventState.addItem(event)
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
eoseReceivedCount++
},
)
@@ -172,7 +172,7 @@ fun SearchScreen(
}
}
// NIP-50 people search subscription (use allRelayUrls — openReqSubscription will connect)
// NIP-50 people search subscription (use allRelayUrls — subscribe will connect)
rememberSubscription(connectedRelays, debouncedQuery, relayManager = relayManager) {
if (allRelayUrls.isEmpty() || debouncedQuery.isEmpty) {
return@rememberSubscription null
@@ -202,7 +202,7 @@ fun SearchScreen(
}
}
},
onEose = { relay, _ ->
onCaughtUp = { relay, _ ->
state.updateRelayState(relay.url, RelaySyncStatus.EOSE_RECEIVED)
state.stopSearching("people-search")
},
@@ -236,7 +236,7 @@ fun SearchScreen(
}
}
},
onEose = { relay, _ ->
onCaughtUp = { relay, _ ->
state.updateRelayState(relay.url, RelaySyncStatus.EOSE_RECEIVED)
state.stopSearching("adv-search")
},
@@ -131,7 +131,7 @@ fun ThreadScreen(
subscriptionsCoordinator?.consumeEvent(event, relay)
levelCache[event.id] = 0
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
rootNoteEoseReceived = true
},
)
@@ -149,7 +149,7 @@ fun ThreadScreen(
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -190,7 +190,7 @@ fun UserProfileScreen(
onEvent = { event, _, relay, _ ->
subscriptionsCoordinator?.consumeEvent(event, relay)
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -232,7 +232,7 @@ fun UserProfileScreen(
contactListLoaded = true
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
eoseReceivedCount++
// Wait for EOSE from at least 2 relays or all relays before enabling button
@@ -294,7 +294,7 @@ fun UserProfileScreen(
localCache.cacheFollowingCount(pubKeyHex, count)
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -329,7 +329,7 @@ fun UserProfileScreen(
localCache.cacheFollowerCount(pubKeyHex, count)
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -356,7 +356,7 @@ fun UserProfileScreen(
pictureEvents.add(event)
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -383,7 +383,7 @@ fun UserProfileScreen(
articleEvents.add(event)
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -410,7 +410,7 @@ fun UserProfileScreen(
highlightEvents.add(event)
}
},
onEose = { _, _ -> },
onCaughtUp = { _, _ -> },
)
} else {
null
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
import com.vitorpamplona.amethyst.desktop.network.RelayConnectionManager
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
@@ -161,10 +161,10 @@ class ChatroomListState(
)
val listener =
object : IRequestListener {
object : SubscriptionListener {
override fun onEvent(
event: Event,
isLive: Boolean,
isRealTime: Boolean,
relay: NormalizedRelayUrl,
forFilters: List<Filter>?,
) {
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.desktop.ui.chats
import com.vitorpamplona.amethyst.commons.ui.chat.DmBroadcastStatus
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.sendAndWaitForResponseDetailed
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.publishAndConfirmDetailed
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class DmSendTracker(
private val client: INostrClient,
private val client: NostrClient,
) {
private val _status = MutableStateFlow<DmBroadcastStatus>(DmBroadcastStatus.Idle)
val status: StateFlow<DmBroadcastStatus> = _status.asStateFlow()
@@ -56,7 +56,7 @@ class DmSendTracker(
val allSuccessful = mutableSetOf<NormalizedRelayUrl>()
for ((event, relays) in events) {
val results = client.sendAndWaitForResponseDetailed(event, relays, 10)
val results = client.publishAndConfirmDetailed(event, relays, 10)
allSuccessful.addAll(results.filter { it.value }.keys)
_status.value = DmBroadcastStatus.Sending(allSuccessful.size, totalRelays)
}
@@ -103,7 +103,7 @@ fun NewDmDialog(
}
}
},
onEose = { _, _ ->
onCaughtUp = { _, _ ->
searchState.endRelaySearch()
},
)
@@ -27,9 +27,9 @@ import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscription
import com.vitorpamplona.amethyst.desktop.viewmodels.DesktopFeedViewModel
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -50,7 +50,7 @@ import kotlin.test.assertTrue
/**
* Integration tests for the Coordinator Cache ViewModel pipeline.
*
* These tests use a stub INostrClient (no real relay connections) and exercise
* These tests use a stub NostrClient (no real relay connections) and exercise
* the full event consumption path through DesktopRelaySubscriptionsCoordinator.
*
* Key invariant being tested: when coordinator.consumeEvent() is called,
@@ -65,11 +65,11 @@ class CoordinatorPipelineTest {
private suspend fun waitForBundler() = delay(600)
/**
* Stub INostrClient records subscription calls but doesn't connect to any relay.
* Stub NostrClient records subscription calls but doesn't connect to any relay.
* This lets us test the coordinator's event routing without network dependencies.
*/
private class StubNostrClient : INostrClient {
val openedSubs = mutableMapOf<String, Pair<Map<NormalizedRelayUrl, List<Filter>>, IRequestListener?>>()
private class StubNostrClient : NostrClient {
val openedSubs = mutableMapOf<String, Pair<Map<NormalizedRelayUrl, List<Filter>>, SubscriptionListener?>>()
override fun connectedRelaysFlow(): StateFlow<Set<NormalizedRelayUrl>> = MutableStateFlow(emptySet())
@@ -88,17 +88,17 @@ class CoordinatorPipelineTest {
override fun isActive() = false
override fun renewFilters(relay: IRelayClient) {}
override fun syncFilters(relay: IRelayClient) {}
override fun openReqSubscription(
override fun subscribe(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
listener: IRequestListener?,
listener: SubscriptionListener?,
) {
openedSubs[subId] = filters to listener
}
override fun queryCount(
override fun count(
subId: String,
filters: Map<NormalizedRelayUrl, List<Filter>>,
) {}
@@ -112,9 +112,9 @@ class CoordinatorPipelineTest {
relayList: Set<NormalizedRelayUrl>,
) {}
override fun subscribe(listener: IRelayClientListener) {}
override fun addConnectionListener(listener: RelayConnectionListener) {}
override fun unsubscribe(listener: IRelayClientListener) {}
override fun removeConnectionListener(listener: RelayConnectionListener) {}
override fun getReqFiltersOrNull(subId: String): Map<NormalizedRelayUrl, List<Filter>>? = null
@@ -376,7 +376,7 @@ class CoordinatorPipelineTest {
val noteIds = listOf("n1".padEnd(64, '0'))
val subId = coordinator.requestInteractions(noteIds, setOf(relayUrl))
// openReqSubscription is launched in scope — wait for it
// subscribe is launched in scope — wait for it
delay(200)
assertTrue(client.openedSubs.containsKey(subId), "Interaction subscription should be opened")