Skip to content

Commit

Permalink
Updated error handling (#392)
Browse files Browse the repository at this point in the history
* Add error handling to background job polling

* Fix Interceptor error handling

* Update CHANGELOG
  • Loading branch information
vanshg authored Jun 25, 2024
1 parent 681ecf3 commit 8a5324b
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 33 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Release Notes

## Unreleased

* Fixed a bug where some failed authentication requests were incorrectly handled
* Fixed a bug where errors with no code were not being handled correctly

## 10.1.6

* Update generic errors with actual platform errors
Expand Down
2 changes: 1 addition & 1 deletion lib/src/main/java/com/smileidentity/models/Models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import com.squareup.moshi.JsonClass
import java.io.Serializable
import kotlinx.parcelize.Parcelize

@Suppress("CanBeParameter", "MemberVisibilityCanBePrivate")
@Suppress("MemberVisibilityCanBePrivate")
@Parcelize
class SmileIDException(val details: Details) : Exception(details.message), Parcelable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.smileidentity.models.AuthenticationRequest
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Request
import okio.IOException
import timber.log.Timber

annotation class SmileHeaderAuth
Expand All @@ -20,7 +21,19 @@ object SmileHeaderAuthInterceptor : Interceptor {
request.getCustomAnnotation(SmileHeaderAuth::class.java) ?: return chain.proceed(request)
Timber.v("SmileHeaderAuthInterceptor: Interceptor called")
val authResponse = runBlocking {
SmileID.api.authenticate(AuthenticationRequest(enrollment = true))
try {
SmileID.api.authenticate(AuthenticationRequest(enrollment = true))
} catch (e: Exception) {
// https://stackoverflow.com/a/58711127/3831060
// OkHttp only propagates IOExceptions, so we need to catch HttpException (which can
// occur when credentials are not valid, for example) and rethrow it, so that the
// exception handlers at the application level can handle it. We add the caught
// exception as a suppressed exception to the IOException that we throw, so that
// [getExceptionHandler] can still access the original exception.
throw IOException("Error authenticating request").apply {
addSuppressed(e)
}
}
}
val newRequest = request.newBuilder()
.header("SmileID-Partner-ID", SmileID.config.partnerId)
Expand Down
39 changes: 10 additions & 29 deletions lib/src/main/java/com/smileidentity/util/Util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,10 @@ internal fun postProcessImage(
*
* @param proxy Callback to be invoked with the exception
*/
fun getExceptionHandler(proxy: (Throwable) -> Unit) = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable, "Error during coroutine execution")
fun getExceptionHandler(proxy: (Throwable) -> Unit) = CoroutineExceptionHandler { _, parentThrow ->
Timber.e(parentThrow, "Error during coroutine execution")
// Check suppressed to handle cases where auth fails within the Interceptor
val throwable = parentThrow.suppressed.firstOrNull() ?: parentThrow
val converted = if (throwable is HttpException) {
val adapter = moshi.adapter(SmileIDException.Details::class.java)
try {
Expand Down Expand Up @@ -265,35 +267,11 @@ fun getExceptionHandler(proxy: (Throwable) -> Unit) = CoroutineExceptionHandler

@Stable
sealed interface StringResource {
fun resolve(context: Context): String
data class Text(val text: String) : StringResource

data class Text(val text: String) : StringResource {
override fun resolve(context: Context): String {
return text
}
}

data class ResId(@StringRes val stringId: Int) : StringResource {
override fun resolve(context: Context): String {
return context.getString(stringId)
}
}
data class ResId(@StringRes val stringId: Int) : StringResource

data class ResIdFromSmileIDException(val exception: SmileIDException) : StringResource {
@SuppressLint("DiscouragedApi") // this way of obtaining identifiers is really slow
override fun resolve(context: Context): String {
val resourceName = "si_error_message_${exception.details.code}"
val resourceId = context.resources.getIdentifier(
/* name = */
resourceName,
/* defType = */
"string",
/* defPackage = */
context.packageName,
)
return context.getString(resourceId)
}
}
data class ResIdFromSmileIDException(val exception: SmileIDException) : StringResource

@SuppressLint("DiscouragedApi") // this way of obtaining identifiers is really slow
@Composable
Expand All @@ -302,6 +280,9 @@ sealed interface StringResource {
is ResId -> stringResource(id = stringId)
is ResIdFromSmileIDException -> {
val context = LocalContext.current
if (exception.details.code == null) {
return exception.details.message
}
val resourceName = "si_error_message_${exception.details.code}"
val resourceId = context.resources.getIdentifier(
/* name = */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.smileidentity.sample.model.Job
import com.smileidentity.sample.model.getCurrentTimeAsHumanReadableTimestamp
import com.smileidentity.sample.model.toJob
import com.smileidentity.sample.repo.DataStoreRepository
import com.smileidentity.util.getExceptionHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
Expand Down Expand Up @@ -69,7 +70,14 @@ class MainScreenViewModel : ViewModel() {
private var pendingJobCountJob = createPendingJobCountPoller()
private var backgroundJobsPollingJob = createBackgroundJobsPoller()

private fun createBackgroundJobsPoller() = viewModelScope.launch {
private fun createBackgroundJobsPoller() = viewModelScope.launch(
getExceptionHandler { throwable ->
Timber.e(throwable, "Background job polling failed")
_uiState.update {
it.copy(snackbarMessage = "Background job polling failed: ${throwable.message}")
}
},
) {
val authRequest = AuthenticationRequest(SmartSelfieEnrollment)
val authResponse = SmileID.api.authenticate(authRequest)
DataStoreRepository.getPendingJobs(SmileID.config.partnerId, !SmileID.useSandbox)
Expand All @@ -95,6 +103,7 @@ class MainScreenViewModel : ViewModel() {
BiometricKyc -> SmileID.api.pollBiometricKycJobStatus(request)
EnhancedDocumentVerification ->
SmileID.api.pollEnhancedDocumentVerificationJobStatus(request)

else -> {
Timber.e("Unexpected pending job: $job")
throw IllegalStateException("Unexpected pending job: $job")
Expand Down Expand Up @@ -145,7 +154,14 @@ class MainScreenViewModel : ViewModel() {
}
}

private fun createPendingJobCountPoller() = viewModelScope.launch {
private fun createPendingJobCountPoller() = viewModelScope.launch(
getExceptionHandler { throwable ->
Timber.e(throwable, "Pending job count poller failed")
_uiState.update {
it.copy(snackbarMessage = "Pending job count poller failed: ${throwable.message}")
}
},
) {
DataStoreRepository.getPendingJobs(SmileID.config.partnerId, !SmileID.useSandbox)
.distinctUntilChanged()
.map { it.size }
Expand Down

0 comments on commit 8a5324b

Please sign in to comment.