diff --git a/app/src/main/java/app/hermes/mobile/core/auth/PkceLoopbackAuthManager.kt b/app/src/main/java/app/hermes/mobile/core/auth/PkceLoopbackAuthManager.kt index aef7615..b90be5e 100644 --- a/app/src/main/java/app/hermes/mobile/core/auth/PkceLoopbackAuthManager.kt +++ b/app/src/main/java/app/hermes/mobile/core/auth/PkceLoopbackAuthManager.kt @@ -9,8 +9,8 @@ import app.hermes.mobile.core.network.HermesRestClient import app.hermes.mobile.core.security.TokenVault import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.job -import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import java.io.BufferedReader import java.io.InputStreamReader @@ -62,11 +62,6 @@ class PkceLoopbackAuthManager( val port = serverSocket.localPort serverSocket.soTimeout = 180_000 // 3 minutes timeout - currentCoroutineContext().job.invokeOnCompletion { - try { - serverSocket?.close() - } catch (_: Throwable) {} - } val redirectUri = "http://127.0.0.1:$port/callback" @@ -92,7 +87,22 @@ class PkceLoopbackAuthManager( var retries = 0 val MAX_RETRIES = 5 while (authCode == null && retries < MAX_RETRIES) { - val socket: Socket = runInterruptible(Dispatchers.IO) { serverSocket!!.accept() } + currentCoroutineContext().ensureActive() + val socket: Socket = suspendCancellableCoroutine { cont -> + cont.invokeOnCancellation { + try { + serverSocket?.close() + } catch (_: Throwable) {} + } + try { + val s = serverSocket!!.accept() + cont.resumeWith(Result.success(s)) + } catch (e: Throwable) { + if (!cont.isCancelled) { + cont.resumeWith(Result.failure(e)) + } + } + } try { authCode = handleCallbackSocket(socket, state) } catch (e: Exception) { diff --git a/app/src/main/java/app/hermes/mobile/core/runtime/HermesConnectionManager.kt b/app/src/main/java/app/hermes/mobile/core/runtime/HermesConnectionManager.kt index 3778c6d..9cdba5b 100644 --- a/app/src/main/java/app/hermes/mobile/core/runtime/HermesConnectionManager.kt +++ b/app/src/main/java/app/hermes/mobile/core/runtime/HermesConnectionManager.kt @@ -154,11 +154,13 @@ class HermesConnectionManager( suspend fun addHost(host: HermesHost) { hostDao.insertOrUpdateHost(host.toEntity()) + _hosts.value = _hosts.value.filter { it.id != host.id } + host getOrCreateRuntime(host) } suspend fun updateHost(host: HermesHost) { hostDao.insertOrUpdateHost(host.toEntity()) + _hosts.value = _hosts.value.map { if (it.id == host.id) host else it } val rt = runtimes[host.id] rt?.updateHost(host) } @@ -168,6 +170,7 @@ class HermesConnectionManager( rt?.close() tokenVault.clearTokens(hostId.value) hostDao.deleteHost(hostId.value) + _hosts.value = _hosts.value.filter { it.id != hostId } if (_activeHostId.value == hostId) { _activeHostId.value = _hosts.value.firstOrNull { it.id != hostId }?.id } @@ -175,6 +178,7 @@ class HermesConnectionManager( suspend fun connectHost(hostId: HermesHostId): Result { val host = _hosts.value.find { it.id == hostId } + ?: hostDao.getHost(hostId.value)?.toDomain() ?: return Result.failure(IllegalArgumentException("Host not found: ${hostId.value}")) val rt = getOrCreateRuntime(host) return rt.connect() diff --git a/app/src/test/java/app/hermes/mobile/core/auth/LoopbackCancellationTest.kt b/app/src/test/java/app/hermes/mobile/core/auth/LoopbackCancellationTest.kt index 14c48e9..d34ca0d 100644 --- a/app/src/test/java/app/hermes/mobile/core/auth/LoopbackCancellationTest.kt +++ b/app/src/test/java/app/hermes/mobile/core/auth/LoopbackCancellationTest.kt @@ -27,7 +27,8 @@ class LoopbackCancellationTest { ) val authUrlDeferred = CompletableDeferred() - val job = launch(Dispatchers.IO) { + val testScope = kotlinx.coroutines.CoroutineScope(Dispatchers.IO) + val job = testScope.launch { authManager.startAuthFlow( context = null, connectionId = "test-host",