Skip to content

Commit 7583336

Browse files
committed
fix: preserve reconnect cancellation
1 parent a5d4dcb commit 7583336

2 files changed

Lines changed: 58 additions & 34 deletions

File tree

app/src/main/java/to/bitkit/repositories/TrezorRepo.kt

Lines changed: 40 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -645,42 +645,48 @@ class TrezorRepo @Inject constructor(
645645
if (_state.value.isConnecting) {
646646
return@withContext Result.failure(AppError("Connection already in progress"))
647647
}
648-
runCatching {
649-
_state.update { it.copy(isConnecting = true, error = null) }
650-
Logger.debug("Started known-device reconnect for '$deviceId'", context = TAG)
651-
Logger.debug("Awaiting setup for reconnect", context = TAG)
652-
awaitSetup()
653-
Logger.debug("Completed setup for reconnect", context = TAG)
654-
if (forceSession) {
655-
Logger.debug("Closing stale session before reconnect for '$deviceId'", context = TAG)
656-
disconnectStaleSession(deviceId)
648+
var startedConnecting = false
649+
try {
650+
runSuspendCatching {
651+
startedConnecting = true
652+
_state.update { it.copy(isConnecting = true, error = null) }
653+
Logger.debug("Started known-device reconnect for '$deviceId'", context = TAG)
654+
Logger.debug("Awaiting setup for reconnect", context = TAG)
655+
awaitSetup()
656+
Logger.debug("Completed setup for reconnect", context = TAG)
657+
if (forceSession) {
658+
Logger.debug("Closing stale session before reconnect for '$deviceId'", context = TAG)
659+
disconnectStaleSession(deviceId)
660+
}
661+
Logger.debug("Scanning for reconnect devices", context = TAG)
662+
val knownDevices = (_state.value.knownDevices + loadKnownDevices()).distinctBy { it.id }
663+
val knownDevice = knownDevices.find { it.matches(deviceId) }
664+
val scannedDevices = trezorService.scan()
665+
Logger.debug(
666+
"Found '${scannedDevices.size}' reconnect devices '${scannedDevices.map { it.id }}'",
667+
context = TAG,
668+
)
669+
// Honor the transport the user selected — connect to exactly the
670+
// entry they tapped instead of overriding Bluetooth with USB.
671+
val device = scannedDevices.find { it.id == deviceId }
672+
?: knownDevice?.takeIf { it.transportType == TransportType.BLUETOOTH }?.toDeviceInfo()
673+
?: throw AppError("Device not found nearby — is it powered on?")
674+
Logger.debug("Found reconnect device '${device.id}'", context = TAG)
675+
Logger.debug("Calling THP reconnect for '${device.id}'", context = TAG)
676+
val features = connectWithThpRetry(device.id, trezorUiHandler.currentSelection())
677+
Logger.debug("Connected known device '${device.id}'", context = TAG)
678+
addOrUpdateKnownDevice(device, features)
679+
_state.update { it.copy(connected = ConnectedTrezorDevice(id = device.id, features = features)) }
680+
Logger.info("Reconnected known device '${device.id}'", context = TAG)
681+
features
682+
}.onFailure { e ->
683+
Logger.error("Connect known device failed", e, context = TAG)
684+
_state.update { it.copy(error = e.message) }
657685
}
658-
Logger.debug("Scanning for reconnect devices", context = TAG)
659-
val knownDevices = (_state.value.knownDevices + loadKnownDevices()).distinctBy { it.id }
660-
val knownDevice = knownDevices.find { it.matches(deviceId) }
661-
val scannedDevices = trezorService.scan()
662-
Logger.debug(
663-
"Found '${scannedDevices.size}' reconnect devices '${scannedDevices.map { it.id }}'",
664-
context = TAG,
665-
)
666-
// Honor the transport the user selected — connect to exactly the
667-
// entry they tapped instead of overriding Bluetooth with USB.
668-
val device = scannedDevices.find { it.id == deviceId }
669-
?: knownDevice?.takeIf { it.transportType == TransportType.BLUETOOTH }?.toDeviceInfo()
670-
?: throw AppError("Device not found nearby — is it powered on?")
671-
Logger.debug("Found reconnect device '${device.id}'", context = TAG)
672-
Logger.debug("Calling THP reconnect for '${device.id}'", context = TAG)
673-
val features = connectWithThpRetry(device.id, trezorUiHandler.currentSelection())
674-
Logger.debug("Connected known device '${device.id}'", context = TAG)
675-
addOrUpdateKnownDevice(device, features)
676-
_state.update {
677-
it.copy(isConnecting = false, connected = ConnectedTrezorDevice(id = device.id, features = features))
686+
} finally {
687+
if (startedConnecting) {
688+
_state.update { it.copy(isConnecting = false) }
678689
}
679-
Logger.info("Reconnected known device '${device.id}'", context = TAG)
680-
features
681-
}.onFailure { e ->
682-
Logger.error("Connect known device failed", e, context = TAG)
683-
_state.update { it.copy(isConnecting = false, error = e.message) }
684690
}
685691
}
686692

app/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.synonym.bitkitcore.TrezorSignedMessageResponse
1313
import com.synonym.bitkitcore.TrezorTransportType
1414
import com.synonym.bitkitcore.TrezorTransportWriteResult
1515
import com.synonym.bitkitcore.WalletSelection
16+
import kotlinx.coroutines.CancellationException
1617
import kotlinx.coroutines.ExperimentalCoroutinesApi
1718
import kotlinx.coroutines.flow.MutableSharedFlow
1819
import kotlinx.coroutines.flow.MutableStateFlow
@@ -44,6 +45,7 @@ import to.bitkit.test.BaseUnitTest
4445
import to.bitkit.utils.AppError
4546
import java.util.UUID
4647
import kotlin.test.assertEquals
48+
import kotlin.test.assertFailsWith
4749
import kotlin.test.assertFalse
4850
import kotlin.test.assertNotNull
4951
import kotlin.test.assertNull
@@ -1124,6 +1126,22 @@ class TrezorRepoTest : BaseUnitTest() {
11241126
verify(trezorService).connect(eq(bleDeviceId), any())
11251127
}
11261128

1129+
@Test
1130+
fun `connectKnownDevice should rethrow cancellation and clear connecting state`() = test {
1131+
val cancellation = CancellationException("cancelled")
1132+
whenever(trezorService.scan()).thenAnswer { throw cancellation }
1133+
sut = createSut()
1134+
1135+
sut.initialize()
1136+
val thrown = assertFailsWith<CancellationException> {
1137+
sut.connectKnownDevice(DEVICE_ID)
1138+
}
1139+
1140+
assertEquals(cancellation.message, thrown.message)
1141+
assertFalse(sut.state.value.isConnecting)
1142+
assertNull(sut.state.value.error)
1143+
}
1144+
11271145
@Test
11281146
fun `ensureConnected returns current selected device without reconnecting`() = test {
11291147
val features = mockFeatures()

0 commit comments

Comments
 (0)