diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt index 73a7ff78b..0dcdbb60b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactory.kt @@ -46,10 +46,16 @@ class OkHttpClientFactory( .addNetworkInterceptor(keyDecryptor) .build() + private var lastProxy: Proxy? = null + fun buildHttpClient( proxy: Proxy?, timeoutSeconds: Int, ): OkHttpClient { + if (proxy != lastProxy) { + rootClient.connectionPool.evictAll() + lastProxy = proxy + } val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds return rootClient .newBuilder() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt index 1729027c3..556c91bf0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/OkHttpClientFactoryForRelays.kt @@ -37,6 +37,7 @@ class OkHttpClientFactoryForRelays( const val DEFAULT_IS_MOBILE: Boolean = false const val DEFAULT_TIMEOUT_ON_WIFI_SECS: Int = 10 const val DEFAULT_TIMEOUT_ON_MOBILE_SECS: Int = 30 + const val WEBSOCKET_PING_INTERVAL_SECS: Long = 120 private fun isEmulator(): Boolean = Build.FINGERPRINT.startsWith("generic") || @@ -76,10 +77,16 @@ class OkHttpClientFactoryForRelays( .addInterceptor(DefaultContentTypeInterceptor(userAgent)) .build() + private var lastProxy: Proxy? = null + fun buildHttpClient( proxy: Proxy?, timeoutSeconds: Int, ): OkHttpClient { + if (proxy != lastProxy) { + rootClient.connectionPool.evictAll() + lastProxy = proxy + } val seconds = if (proxy != null) timeoutSeconds * 3 else timeoutSeconds return rootClient .newBuilder() @@ -87,6 +94,7 @@ class OkHttpClientFactoryForRelays( .connectTimeout(Duration.ofSeconds(seconds.toLong())) .readTimeout(Duration.ofSeconds(seconds.toLong() * 3)) .writeTimeout(Duration.ofSeconds(seconds.toLong() * 3)) + .pingInterval(Duration.ofSeconds(WEBSOCKET_PING_INTERVAL_SECS)) .build() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt index 04540157a..b74f56753 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt @@ -54,6 +54,34 @@ class TorService( private val _status = MutableStateFlow(TorServiceStatus.Off) val status: StateFlow = _status.asStateFlow() + private fun artiDataDir() = File(context.filesDir, "arti") + + /** + * Clears the Arti cache directory (consensus, relay descriptors) while + * preserving the state directory (guard selection). This forces a fresh + * consensus download on the next bootstrap, preventing stale cached data + * from causing circuit failures. + */ + private fun clearArtiCache() { + val cacheDir = File(artiDataDir(), "cache") + if (cacheDir.exists()) { + cacheDir.deleteRecursively() + Log.d("TorService") { "Cleared Arti cache directory" } + } + } + + /** + * Clears all Arti persistent data (state + cache). Used as a last resort + * when initialization fails, to recover from corrupted state. + */ + private fun clearAllArtiData() { + val dataDir = artiDataDir() + if (dataDir.exists()) { + dataDir.deleteRecursively() + Log.d("TorService") { "Cleared all Arti data" } + } + } + /** * Initialize the TorClient (once) and start the SOCKS proxy. * Must be called from a coroutine on [Dispatchers.IO]. @@ -90,12 +118,21 @@ class TorService( } } - val dataDir = File(context.filesDir, "arti").absolutePath + // Clear cached consensus/descriptors so Arti bootstraps with + // fresh network data, preventing stale guards/circuits. + clearArtiCache() + + val dataDir = artiDataDir().absolutePath Log.d("TorService") { "Initializing Arti with data dir: $dataDir" } - val initResult = ArtiNative.initialize(dataDir) + var initResult = ArtiNative.initialize(dataDir) if (initResult != 0) { - Log.e("TorService") { "Failed to initialize Arti: error $initResult" } + Log.e("TorService") { "Failed to initialize Arti: error $initResult, clearing data and retrying" } + clearAllArtiData() + initResult = ArtiNative.initialize(dataDir) + } + if (initResult != 0) { + Log.e("TorService") { "Failed to initialize Arti on retry: error $initResult" } initialized.set(false) _status.value = TorServiceStatus.Off return@withContext diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt index e83256988..33877f0f8 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt @@ -228,7 +228,7 @@ open class BasicRelayClient( } fun dontTryAgainForALongTime() { - delayToConnectInSeconds = TimeUtils.ONE_DAY + delayToConnectInSeconds = TimeUtils.FIVE_MINUTES } override fun sendOrConnectAndSync(cmd: Command) {