Correctly manages airplane mode changes: reconnects Tor and Relays

This commit is contained in:
Vitor Pamplona
2025-08-20 12:16:01 -04:00
parent 7339f0ea3b
commit 4065adac83
8 changed files with 74 additions and 19 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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<TorRelayEvaluation>,
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()

View File

@@ -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

View File

@@ -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}")
}
}

View File

@@ -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()

View File

@@ -133,6 +133,8 @@ class NostrClient(
relayPool.disconnect()
}
fun isActive() = isActive
@Synchronized
fun reconnect(onlyIfChanged: Boolean = false) {
if (onlyIfChanged) {

View File

@@ -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()