Fix session verification incorrectly displaying as 'not verified' when the user opened the app with no network connection.

It turns out `encryptionService.verificationState()` runs a network request that will cause a deadlock when it fails.

Also fixed another deadlock that caused the screen to remain blank sometimes after logging in, because DataStore got stuck when checking the `skipVerification` state for some reason I don't fully understand.
This commit is contained in:
Jorge Martín 2024-06-28 11:28:00 +02:00
parent a7a2b4727f
commit 16545ce60e
2 changed files with 45 additions and 12 deletions

View file

@ -119,8 +119,14 @@ class DefaultFtueService @Inject constructor(
emit(SessionVerifiedStatus.NotVerified) emit(SessionVerifiedStatus.NotVerified)
} }
.first() .first()
val skipVerification = suspend { sessionPreferencesStore.isSessionVerificationSkipped().first() } // For some obscure reason we need to call this *before* we check the `readyVerifiedSessionStatus`, otherwise there's a deadlock
return readyVerifiedSessionStatus == SessionVerifiedStatus.NotVerified && !skipVerification() // It seems like a DataStore bug
val skipVerification = canSkipVerification()
return readyVerifiedSessionStatus == SessionVerifiedStatus.NotVerified && !skipVerification
}
private suspend fun canSkipVerification(): Boolean {
return sessionPreferencesStore.isSessionVerificationSkipped().first()
} }
private suspend fun needsAnalyticsOptIn(): Boolean { private suspend fun needsAnalyticsOptIn(): Boolean {

View file

@ -61,7 +61,7 @@ class RustSessionVerificationService(
private val verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener { private val verificationStateListenerTaskHandle = encryptionService.verificationStateListener(object : VerificationStateListener {
override fun onUpdate(status: VerificationState) { override fun onUpdate(status: VerificationState) {
Timber.d("New verification state: $status") Timber.d("New verification state: $status")
updateVerificationStatus(status) sessionCoroutineScope.launch { updateVerificationStatus() }
} }
}) })
@ -70,7 +70,7 @@ class RustSessionVerificationService(
override fun onUpdate(status: RecoveryState) { override fun onUpdate(status: RecoveryState) {
Timber.d("New recovery state: $status") Timber.d("New recovery state: $status")
// We could check the `RecoveryState`, but it's easier to just use the verification state directly // We could check the `RecoveryState`, but it's easier to just use the verification state directly
updateVerificationStatus(encryptionService.verificationState()) sessionCoroutineScope.launch { updateVerificationStatus() }
} }
}) })
@ -92,15 +92,16 @@ class RustSessionVerificationService(
init { init {
// Update initial state in case sliding sync isn't ready // Update initial state in case sliding sync isn't ready
updateVerificationStatus(encryptionService.verificationState()) sessionCoroutineScope.launch { updateVerificationStatus() }
isReady.onEach { isReady -> isReady.onEach { isReady ->
if (isReady) { if (isReady) {
Timber.d("Starting verification service") Timber.d("Starting verification service")
// Immediate status update // Immediate status update
updateVerificationStatus(encryptionService.verificationState()) updateVerificationStatus()
} else { } else {
Timber.d("Stopping verification service") Timber.d("Stopping verification service")
updateVerificationStatus()
} }
} }
.launchIn(sessionCoroutineScope) .launchIn(sessionCoroutineScope)
@ -161,8 +162,9 @@ class RustSessionVerificationService(
} }
} }
.onSuccess { .onSuccess {
updateVerificationStatus(VerificationState.VERIFIED) // Order here is important, first set the flow state as finished, then update the verification status
_verificationFlowState.value = VerificationFlowState.Finished _verificationFlowState.value = VerificationFlowState.Finished
updateVerificationStatus()
} }
.onFailure { .onFailure {
Timber.e(it, "Verification finished, but the Rust SDK still reports the session as unverified.") Timber.e(it, "Verification finished, but the Rust SDK still reports the session as unverified.")
@ -200,11 +202,36 @@ class RustSessionVerificationService(
} }
} }
private fun updateVerificationStatus(verificationState: VerificationState) { private suspend fun updateVerificationStatus() {
_sessionVerifiedStatus.value = when (verificationState) { if (verificationFlowState.value == VerificationFlowState.Finished) {
VerificationState.UNKNOWN -> SessionVerifiedStatus.Unknown // Calling `encryptionService.verificationState()` performs a network call and it will deadlock if there is no network
VerificationState.VERIFIED -> SessionVerifiedStatus.Verified // So we need to check that *only* if we know there is network connection, which is the case when the verification flow just finished
VerificationState.UNVERIFIED -> SessionVerifiedStatus.NotVerified Timber.d("Updating verification status: flow just finished")
runCatching {
encryptionService.waitForE2eeInitializationTasks()
}.onSuccess {
_sessionVerifiedStatus.value = when (encryptionService.verificationState()) {
VerificationState.UNKNOWN -> SessionVerifiedStatus.Unknown
VerificationState.VERIFIED -> SessionVerifiedStatus.Verified
VerificationState.UNVERIFIED -> SessionVerifiedStatus.NotVerified
}
Timber.d("New verification status: ${_sessionVerifiedStatus.value}")
}
} else {
// Otherwise, just check the current verification status from the session verification controller instead
Timber.d("Updating verification status: flow is pending or was finished some time ago")
runCatching {
if (!this@RustSessionVerificationService::verificationController.isInitialized) {
verificationController = client.getSessionVerificationController()
verificationController.setDelegate(this@RustSessionVerificationService)
}
_sessionVerifiedStatus.value = if (verificationController.isVerified()) {
SessionVerifiedStatus.Verified
} else {
SessionVerifiedStatus.NotVerified
}
Timber.d("New verification status: ${_sessionVerifiedStatus.value}")
}
} }
} }
} }