From 550fd3e527dda2ef52a6725c532e3f9758107617 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sat, 6 Jun 2026 15:46:13 +0700 Subject: [PATCH] ensure relay disconnect on background --- .../commonMain/kotlin/su/reya/coop/Nostr.kt | 61 ++++++++++++++----- .../kotlin/su/reya/coop/NostrViewModel.kt | 20 +++++- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt index 6e624ba..3536641 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt @@ -38,6 +38,7 @@ import rust.nostr.sdk.PublicKey import rust.nostr.sdk.RelayCapabilities import rust.nostr.sdk.RelayMessageEnum import rust.nostr.sdk.RelayMetadata +import rust.nostr.sdk.RelayStatus import rust.nostr.sdk.RelayUrl import rust.nostr.sdk.ReqExitPolicy import rust.nostr.sdk.ReqTarget @@ -59,6 +60,18 @@ import kotlin.time.Duration.Companion.milliseconds object NostrManager { val instance = Nostr() + + val BOOTSTRAP_RELAYS = listOf( + "wss://relay.primal.net", + "wss://user.kindpag.es", + "wss://purplepag.es" + ) + + val INDEXER_RELAY = listOf( + "wss://indexer.coracle.social", + ) + + val ALL_RELAYS = BOOTSTRAP_RELAYS + INDEXER_RELAY } class Nostr { @@ -75,7 +88,6 @@ class Nostr { private val isInitialized = MutableStateFlow(false) - // Add these to the Nostr class private val _newEvents = MutableSharedFlow(extraBufferCapacity = 100) val newEvents = _newEvents.asSharedFlow() @@ -144,24 +156,43 @@ class Nostr { } suspend fun connectBootstrapRelays() { - // Bootstrap relays - client?.addRelay(RelayUrl.parse("wss://relay.primal.net")) - client?.addRelay(RelayUrl.parse("wss://user.kindpag.es")) - client?.addRelay(RelayUrl.parse("wss://purplepag.es")) + NostrManager.BOOTSTRAP_RELAYS.forEach { url -> + client?.addRelay(RelayUrl.parse(url)) + } + NostrManager.INDEXER_RELAY.forEach { url -> + client?.addRelay( + url = RelayUrl.parse(url), + capabilities = RelayCapabilities.gossip() + ) + } + // Connect to all bootstrap relays + client?.connect() + } - - // Indexer relay for NIP-65 discovery - client?.addRelay( - url = RelayUrl.parse("wss://indexer.coracle.social"), - capabilities = RelayCapabilities.gossip() - ) - - // Connect to all bootstrap relays and wait for all connections to be established - client?.connect(Duration.parse("2s")) + suspend fun reconnect() { + NostrManager.ALL_RELAYS.forEach { url -> + try { + client?.relay(RelayUrl.parse(url)).let { relay -> + if (relay != null) { + if (relay.status() != RelayStatus.CONNECTED) { + relay.connect() + } + } + } + } catch (e: Exception) { + println("Failed to reconnect relay: ${e.message}") + } + } } suspend fun disconnect() { - client?.shutdown() + NostrManager.ALL_RELAYS.forEach { url -> + try { + client?.disconnectRelay(RelayUrl.parse(url)) + } catch (e: Exception) { + println("Failed to disconnect relay: ${e.message}") + } + } } suspend fun exit() { diff --git a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt index e2b850a..735e031 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.serialization.kotlinx.json.json +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -15,6 +16,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.json.Json import rust.nostr.sdk.AsyncNostrSigner @@ -85,6 +87,9 @@ class NostrViewModel( // Check local stored secret (secret key or bunker) login() + // Automatically reconnect bootstrap relays + reconnect() + // Observe the signer state and verify the relay list observeSignerAndCheckRelays() @@ -100,7 +105,13 @@ class NostrViewModel( override fun onCleared() { super.onCleared() - // TODO: optimize relay connection + + // Disconnect to all bootstrap relays + viewModelScope.launch { + withContext(NonCancellable) { + nostr.disconnect() + } + } } private fun showError(message: String) { @@ -116,6 +127,13 @@ class NostrViewModel( } } + private fun reconnect() { + viewModelScope.launch { + nostr.waitUntilInitialized() + nostr.reconnect() + } + } + private fun runObserver() { viewModelScope.launch { // Observe new messages