Skip to content

Commit 88da9d5

Browse files
authored
Merge pull request #2439 from switchifyapp/fix/retain-ble-pairing-token-2438
Retain BLE pairing tokens after auth failures
2 parents 67dea21 + bc8b494 commit 88da9d5

4 files changed

Lines changed: 103 additions & 21 deletions

File tree

app/src/main/java/com/enaboapps/switchify/pc/PcConnectionViewModel.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,6 @@ class PcConnectionViewModel(
311311
setIdle(pc.desktopId, PcRowStatus.Connected, null)
312312
}
313313
is PcPingResult.AuthFailed -> {
314-
tokenStore.clearToken(pc.desktopId)
315-
tokenRevision.update { it + 1 }
316314
PcConnectionStateHolder.setDisconnected()
317315
setIdle(pc.desktopId, PcRowStatus.Failed, ping.message)
318316
}
@@ -344,8 +342,6 @@ class PcConnectionViewModel(
344342
setIdle(pc.desktopId, PcRowStatus.Connected, null)
345343
}
346344
is PcPingResult.AuthFailed -> {
347-
tokenStore.clearToken(pc.desktopId)
348-
tokenRevision.update { it + 1 }
349345
PcConnectionStateHolder.setDisconnected()
350346
setIdle(pc.desktopId, PcRowStatus.Failed, result.message)
351347
}

app/src/main/java/com/enaboapps/switchify/pc/PcServiceConnectionController.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ class PcServiceConnectionController(
147147
isAuthFailure = { it is PcCommandResult.AuthFailed }
148148
)
149149
if (result is PcCommandResult.AuthFailed) {
150-
tokenStore.clearToken(session.desktopId)
151150
closeLiveConnection(PcControlCloseReason.AuthFailure)
152151
_state.value = PcServiceConnectionState.Failed(result.message)
153152
PcConnectionStateHolder.setDisconnected()
@@ -254,7 +253,6 @@ class PcServiceConnectionController(
254253
openLiveControlSession(session, pc.displayName)
255254
}
256255
is PcPingResult.AuthFailed -> {
257-
tokenStore.clearToken(pc.desktopId)
258256
PcConnectionStateHolder.setDisconnected()
259257
_state.value = PcServiceConnectionState.Failed(EXPIRED_MESSAGE)
260258
PcServiceConnectResult.Failed(PcErrorReason.AuthExpired, EXPIRED_MESSAGE)
@@ -283,7 +281,6 @@ class PcServiceConnectionController(
283281
PcServiceConnectResult.Connected(session, displayName)
284282
}
285283
is PcLiveControlResult.AuthFailed -> {
286-
tokenStore.clearToken(session.desktopId)
287284
closeLiveConnection(PcControlCloseReason.AuthFailure)
288285
clearLiveState()
289286
PcConnectionStateHolder.setDisconnected()
@@ -333,7 +330,6 @@ class PcServiceConnectionController(
333330
when (val result = connection.checkHealth()) {
334331
PcCommandResult.Ack -> Unit
335332
is PcCommandResult.AuthFailed -> {
336-
tokenStore.clearToken(session.desktopId)
337333
closeLiveConnection(PcControlCloseReason.AuthFailure)
338334
clearLiveState()
339335
PcConnectionStateHolder.setDisconnected()
@@ -372,7 +368,6 @@ class PcServiceConnectionController(
372368
return@launch
373369
}
374370
is PcLiveControlResult.AuthFailed -> {
375-
tokenStore.clearToken(session.desktopId)
376371
closeLiveConnection(PcControlCloseReason.AuthFailure)
377372
clearLiveState()
378373
PcConnectionStateHolder.setDisconnected()

app/src/test/java/com/enaboapps/switchify/pc/PcConnectionViewModelTest.kt

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ class PcConnectionViewModelTest {
240240
}
241241

242242
@Test
243-
fun invalidSavedTokenClearsTokenAndOffersRequestAccess() = runTest(dispatcher) {
243+
fun invalidSavedTokenKeepsTokenAndOffersConnect() = runTest(dispatcher) {
244244
val discovery = FakeDiscovery(listOf(pc))
245245
val tokens = FakeTokenStore(initialTokens = mutableMapOf("desktop-1" to "token"))
246246
val connector = FakeConnector(pingResult = PcPingResult.AuthFailed())
@@ -251,9 +251,10 @@ class PcConnectionViewModelTest {
251251
advanceUntilIdle()
252252

253253
assertEquals(PC_AUTH_RETRY_ATTEMPTS, connector.pingCalls)
254-
assertNull(tokens.getToken("desktop-1"))
254+
assertEquals("token", tokens.getToken("desktop-1"))
255255
assertEquals("Connection expired. Request access again.", viewModel.uiState.value.message)
256-
assertEquals("Request access", viewModel.uiState.value.discoveredPcs.first().actionText)
256+
assertEquals("Connect", viewModel.uiState.value.discoveredPcs.first().actionText)
257+
assertEquals(true, viewModel.uiState.value.discoveredPcs.first().canUnpair)
257258
}
258259

259260
@Test
@@ -334,6 +335,29 @@ class PcConnectionViewModelTest {
334335
assertEquals("AA:BB:CC:DD:EE:FF", connector.pingPcs.single().bluetoothEndpoint?.deviceAddress)
335336
}
336337

338+
@Test
339+
fun savedBluetoothPairingAuthFailureRemainsRetryable() = runTest(dispatcher) {
340+
val discovery = FakeDiscovery(emptyList())
341+
val tokens = FakeTokenStore(
342+
initialTokens = mutableMapOf("desktop-1" to "token"),
343+
initialLastEndpointIds = mutableMapOf("desktop-1" to "AA:BB:CC:DD:EE:FF"),
344+
initialServiceNames = mutableMapOf("desktop-1" to "Switchify PC")
345+
)
346+
val connector = FakeConnector(pingResult = PcPingResult.AuthFailed())
347+
val viewModel = viewModel(discovery, tokens, connector)
348+
advanceUntilIdle()
349+
350+
viewModel.connectSavedPairing("desktop-1")
351+
advanceUntilIdle()
352+
353+
assertEquals(PC_AUTH_RETRY_ATTEMPTS, connector.pingCalls)
354+
assertEquals("token", tokens.getToken("desktop-1"))
355+
val savedPairing = viewModel.uiState.value.savedPairings.single()
356+
assertEquals("desktop-1", savedPairing.desktopId)
357+
assertEquals(true, savedPairing.canConnect)
358+
assertEquals("Connection expired. Request access again.", viewModel.uiState.value.message)
359+
}
360+
337361
@Test
338362
fun savedPairingsExcludeDiscoveredPc() = runTest(dispatcher) {
339363
val discovery = FakeDiscovery(listOf(pc))
@@ -449,7 +473,7 @@ class PcConnectionViewModelTest {
449473
}
450474

451475
@Test
452-
fun postPairingAuthFailureRetriesBeforeClearingToken() = runTest(dispatcher) {
476+
fun postPairingAuthFailureRetriesWithoutSavingToken() = runTest(dispatcher) {
453477
val discovery = FakeDiscovery(listOf(pc))
454478
val tokens = FakeTokenStore()
455479
val connector = FakeConnector(

app/src/test/java/com/enaboapps/switchify/pc/PcServiceConnectionControllerTest.kt

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ class PcServiceConnectionControllerTest {
119119
}
120120

121121
@Test
122-
fun connectToAuthFailureOpeningLiveSessionClearsToken() = runTest(dispatcher) {
122+
fun connectToAuthFailureOpeningLiveSessionKeepsToken() = runTest(dispatcher) {
123123
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
124124
val connector = FakeConnector(
125125
pingResult = PcPingResult.Connected("AA:BB:CC:DD:EE:FF"),
@@ -130,7 +130,7 @@ class PcServiceConnectionControllerTest {
130130
val result = controller.connectTo(pc)
131131

132132
assertTrue(result is PcServiceConnectResult.Failed)
133-
assertNull(tokens.getToken("desktop-1"))
133+
assertEquals("token", tokens.getToken("desktop-1"))
134134
assertTrue(PcConnectionStateHolder.connectionState.value is PcConnectionState.Disconnected)
135135
}
136136

@@ -171,6 +171,23 @@ class PcServiceConnectionControllerTest {
171171
assertTrue(result is PcCommandResult.Failed)
172172
}
173173

174+
@Test
175+
fun commandAuthFailureKeepsToken() = runTest(dispatcher) {
176+
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
177+
val connector = FakeConnector(
178+
pingResult = PcPingResult.Connected("AA:BB:CC:DD:EE:FF"),
179+
commandResult = PcCommandResult.AuthFailed()
180+
)
181+
val controller = controller(tokens, connector)
182+
controller.connectTo(pc)
183+
184+
val result = controller.sendControlCommand(PcControlCommand.LeftClick)
185+
186+
assertTrue(result is PcCommandResult.AuthFailed)
187+
assertEquals("token", tokens.getToken("desktop-1"))
188+
assertTrue(PcConnectionStateHolder.connectionState.value is PcConnectionState.Disconnected)
189+
}
190+
174191
@Test
175192
fun onPcUiPausedShutsDownAfterGrace() = runTest(dispatcher) {
176193
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
@@ -230,6 +247,48 @@ class PcServiceConnectionControllerTest {
230247
controller.disconnect()
231248
}
232249

250+
@Test
251+
fun heartbeatAuthFailureKeepsToken() = runTest(dispatcher) {
252+
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
253+
val connector = FakeConnector(
254+
pingResult = PcPingResult.Connected("AA:BB:CC:DD:EE:FF"),
255+
healthResults = listOf(PcCommandResult.AuthFailed())
256+
)
257+
val controller = controller(tokens, connector)
258+
controller.connectTo(pc)
259+
controller.onPcUiResumed()
260+
261+
advanceTimeBy(5_001)
262+
runCurrent()
263+
264+
assertEquals("token", tokens.getToken("desktop-1"))
265+
assertTrue(PcConnectionStateHolder.connectionState.value is PcConnectionState.Disconnected)
266+
assertTrue(controller.state.value is PcServiceConnectionState.Failed)
267+
}
268+
269+
@Test
270+
fun reconnectAuthFailureKeepsToken() = runTest(dispatcher) {
271+
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
272+
val connector = FakeConnector(
273+
pingResult = PcPingResult.Connected("AA:BB:CC:DD:EE:FF"),
274+
liveResults = listOf(
275+
PcLiveControlResult.Connected(FakeLiveConnection()),
276+
PcLiveControlResult.AuthFailed()
277+
)
278+
)
279+
val controller = controller(tokens, connector)
280+
controller.connectTo(pc)
281+
controller.onPcUiResumed()
282+
runCurrent()
283+
284+
connector.liveConnections.single().eventsFlow.tryEmit(PcControlConnectionEvent.Disconnected)
285+
runCurrent()
286+
287+
assertEquals("token", tokens.getToken("desktop-1"))
288+
assertTrue(PcConnectionStateHolder.connectionState.value is PcConnectionState.Disconnected)
289+
assertTrue(controller.state.value is PcServiceConnectionState.Failed)
290+
}
291+
233292
@Test
234293
fun bluetoothSavedTokenReconnectUsesBluetoothSession() = runTest(dispatcher) {
235294
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
@@ -268,7 +327,7 @@ class PcServiceConnectionControllerTest {
268327
}
269328

270329
@Test
271-
fun invalidSavedTokenClearsTokenAndDisconnects() = runTest(dispatcher) {
330+
fun invalidSavedTokenKeepsTokenAndDisconnects() = runTest(dispatcher) {
272331
val tokens = FakeTokenStore(mutableMapOf("desktop-1" to "token"))
273332
val connector = FakeConnector(PcPingResult.AuthFailed())
274333
val controller = controller(tokens, connector)
@@ -278,7 +337,7 @@ class PcServiceConnectionControllerTest {
278337
assertTrue(result is PcServiceConnectResult.Failed)
279338
assertEquals(PcErrorReason.AuthExpired, (result as PcServiceConnectResult.Failed).reason)
280339
assertEquals(PC_AUTH_RETRY_ATTEMPTS, connector.pingCalls)
281-
assertNull(tokens.getToken("desktop-1"))
340+
assertEquals("token", tokens.getToken("desktop-1"))
282341
assertTrue(PcConnectionStateHolder.connectionState.value is PcConnectionState.Disconnected)
283342
}
284343

@@ -502,6 +561,7 @@ class PcServiceConnectionControllerTest {
502561
private val pairingResultsByDesktop: Map<String, PcPairingResult> = emptyMap(),
503562
private val healthResults: List<PcCommandResult> = emptyList(),
504563
private val liveResults: List<PcLiveControlResult> = emptyList(),
564+
private val commandResult: PcCommandResult = PcCommandResult.Ack,
505565
pingResults: List<PcPingResult> = emptyList()
506566
) : PcConnector {
507567
private val queuedPingResults = ArrayDeque(pingResults)
@@ -529,9 +589,15 @@ class PcServiceConnectionControllerTest {
529589

530590
override suspend fun openControlSession(session: PcAuthenticatedSession): PcLiveControlResult {
531591
openControlSessionCalls++
532-
queuedLiveResults.removeFirstOrNull()?.let { return it }
592+
queuedLiveResults.removeFirstOrNull()?.let {
593+
if (it is PcLiveControlResult.Connected && it.connection is FakeLiveConnection) {
594+
liveConnections += it.connection
595+
}
596+
return it
597+
}
533598
val connection = FakeLiveConnection(
534-
onHealth = { queuedHealthResults.removeFirstOrNull() ?: PcCommandResult.Ack }
599+
onHealth = { queuedHealthResults.removeFirstOrNull() ?: PcCommandResult.Ack },
600+
onCommand = { commandResult }
535601
)
536602
liveConnections += connection
537603
return PcLiveControlResult.Connected(connection)
@@ -548,7 +614,8 @@ class PcServiceConnectionControllerTest {
548614
}
549615

550616
private class FakeLiveConnection(
551-
private val onHealth: suspend () -> PcCommandResult = { PcCommandResult.Ack }
617+
private val onHealth: suspend () -> PcCommandResult = { PcCommandResult.Ack },
618+
private val onCommand: suspend () -> PcCommandResult = { PcCommandResult.Ack }
552619
) : PcControlConnection {
553620
override val pointerProfile: PcPointerMovementProfile? = null
554621
val eventsFlow = MutableSharedFlow<PcControlConnectionEvent>(extraBufferCapacity = 8)
@@ -563,7 +630,7 @@ class PcServiceConnectionControllerTest {
563630

564631
override suspend fun sendCommand(command: PcControlCommand): PcCommandResult {
565632
commands += command
566-
return PcCommandResult.Ack
633+
return onCommand()
567634
}
568635

569636
override fun close(reason: PcControlCloseReason) {

0 commit comments

Comments
 (0)