diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 1129c7773..9101a08fb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -75,9 +75,10 @@ class Amethyst : Application() { // App services that should be run as soon as there are subscribers to their flows val locationManager = LocationState(this, applicationIOScope) - val torManager = TorManager(this, applicationIOScope) val connManager = ConnectivityManager(this, applicationIOScope) + val torManager = TorManager(this, applicationIOScope) + // Service that will run at all times to receive events from Pokey val pokeyReceiver = PokeyReceiver() @@ -109,7 +110,7 @@ class Amethyst : Application() { val client: NostrClient = NostrClient(websocketBuilder, applicationIOScope) // Watches for changes on Tor and Relay List Settings - val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, applicationIOScope) + val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, torManager, applicationIOScope) // Verifies and inserts in the cache from all relays, all subscriptions val cacheClientConnector = CacheClientConnector(client, cache) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt index ff69703b4..978a23425 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt @@ -63,6 +63,12 @@ class ConnectivityFlow( Log.d("ConnectivityFlow", "onCapabilitiesChanged ${network.networkHandle} $isMobile") trySend(ConnectivityStatus.Active(network.networkHandle, isMobile)) } + + override fun onLost(network: Network) { + super.onLost(network) + Log.d("ConnectivityFlow", "onLost ${network.networkHandle} ") + trySend(ConnectivityStatus.Off) + } } connectivityManager.registerDefaultNetworkCallback(networkCallback) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt index fdc54672d..a9bcf18dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt @@ -21,14 +21,20 @@ package com.vitorpamplona.amethyst.service.relayClient import android.util.Log +import com.vitorpamplona.amethyst.model.torState.TorRelayEvaluation import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager +import com.vitorpamplona.amethyst.service.connectivity.ConnectivityStatus import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.amethyst.service.okhttp.ProxySettingsAnchor +import com.vitorpamplona.amethyst.ui.tor.TorManager +import com.vitorpamplona.amethyst.ui.tor.TorServiceStatus import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.NonCancellable.isActive import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flowOn @@ -36,14 +42,24 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import net.freehaven.tor.control.TorControlCommands +import okhttp3.OkHttpClient class RelayProxyClientConnector( val torProxySettingsAnchor: ProxySettingsAnchor, val okHttpClients: DualHttpClientManager, val connManager: ConnectivityManager, val client: NostrClient, + val torManager: TorManager, val scope: CoroutineScope, ) { + data class RelayServiceInfra( + val torSettings: StateFlow, + val torConnection: OkHttpClient, + val clearConnection: OkHttpClient, + val connectivity: ConnectivityStatus, + ) + @OptIn(FlowPreview::class) val relayServices = combine( @@ -52,11 +68,35 @@ class RelayProxyClientConnector( okHttpClients.defaultHttpClientWithoutProxy, connManager.status, ) { torSettings, torConnection, clearConnection, connectivity -> - torSettings.hashCode() + torConnection.hashCode() + clearConnection.hashCode() + connectivity.hashCode() + RelayServiceInfra(torSettings, torConnection, clearConnection, connectivity) }.debounce(100) .onEach { - Log.d("ManageRelayServices", "Relay Services have changed") - client.reconnect(true) + if (it.connectivity is ConnectivityStatus.Off) { + Log.d("ManageRelayServices", "Pausing Relay Services ${it.connectivity}") + if (client.isActive()) { + client.disconnect() + } + val torStatus = torManager.status.value + if (torStatus is TorServiceStatus.Active) { + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_DORMANT) + Log.d("ManageRelayServices", "Pausing Tor Activity") + } + } else if (it.connectivity is ConnectivityStatus.Active && !client.isActive()) { + Log.d("ManageRelayServices", "Resuming Relay Services") + + val torStatus = torManager.status.value + if (torStatus is TorServiceStatus.Active) { + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_ACTIVE) + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_NEWNYM) + Log.d("ManageRelayServices", "Resuming Tor Activity with new nym") + } + + // only calls this if the client is not active. Otherwise goes to the else below + client.connect() + } else { + Log.d("ManageRelayServices", "Relay Services have changed, reconnecting relays that need to") + client.reconnect(true) + } }.onStart { Log.d("ManageRelayServices", "Resuming Relay Services") client.connect() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt index b5e831e9b..3aeabc0fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt @@ -138,7 +138,6 @@ fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) { fun ManageRelayServices(accountViewModel: AccountViewModel) { val relayServices by Amethyst.instance.relayProxyClientConnector.relayServices .collectAsStateWithLifecycle() - Log.d("ManageRelayServices", "Relay Services changed $relayServices") } @Composable 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 5cd75cd5c..e161be392 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 @@ -62,7 +62,7 @@ class TorService( delay(100) } - trySend(TorServiceStatus.Active(torService.socksPort)) + trySend(TorServiceStatus.Active(torService.socksPort, torService.torControlConnection)) Log.d("TorService", "Tor Service Connected ${torService.socksPort}") } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt index cb220ce2c..98269d3dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt @@ -20,9 +20,12 @@ */ package com.vitorpamplona.amethyst.ui.tor +import net.freehaven.tor.control.TorControlConnection + sealed class TorServiceStatus { data class Active( val port: Int, + val torControlConnection: TorControlConnection, ) : TorServiceStatus() object Off : TorServiceStatus() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt index ac17b0764..e25057691 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt @@ -133,6 +133,8 @@ class NostrClient( relayPool.disconnect() } + fun isActive() = isActive + @Synchronized fun reconnect(onlyIfChanged: Boolean = false) { if (onlyIfChanged) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt index deb33b5db..6e7fd5c9e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt @@ -75,23 +75,27 @@ class RelayPool( fun reconnectIfNeedsToORIfItIsTime() { if (lastReconnectCall < TimeUtils.oneMinuteAgo()) { - relays.forEach { url, relay -> - if (relay.isConnected()) { - if (relay.needsToReconnect()) { - // network has changed, force reconnect - relay.disconnect() - relay.connect() - } - } else { - // relay is not connected. Connect if it is time - relay.connectAndSyncFiltersIfDisconnected() - } - } + reconnectIfNeedsTo() lastReconnectCall = TimeUtils.now() } } + fun reconnectIfNeedsTo() { + relays.forEach { url, relay -> + if (relay.isConnected()) { + if (relay.needsToReconnect()) { + // network has changed, force reconnect + relay.disconnect() + relay.connect() + } + } else { + // relay is not connected. Connect if it is time + relay.connectAndSyncFiltersIfDisconnected() + } + } + } + fun connect() = relays.forEach { url, relay -> relay.connect()