Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions libs/logging/src/main/kotlin/com/getcode/utils/ErrorUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,24 @@ object ErrorUtils {
throwable is SocketException ||
throwable.cause is SocketException

private val gmsTransientMessages = setOf("SERVICE_NOT_AVAILABLE", "FIS_AUTH_ERROR", "MISSING_INSTANCEID_SERVICE", "TOO_MANY_REGISTRATIONS")
/**
* Error strings Google Play Services returns for a failed FCM registration. They describe the
* device's GMS state or the FCM backend, not app code, so they stay out of Bugsnag. Firebase
* wraps them as `IOException("FCM Registration failed!")` -> `ExecutionException` ->
* `IOException(<code>)`, which is why the cause chain is walked.
*/
private val gmsTransientMessages = setOf(
"SERVICE_NOT_AVAILABLE",
"FIS_AUTH_ERROR",
"MISSING_INSTANCEID_SERVICE",
"TOO_MANY_REGISTRATIONS",
"AUTHENTICATION_FAILED",
"INTERNAL_SERVER_ERROR",
"InternalServerError",
"PHONE_REGISTRATION_ERROR",
)

private fun isGmsTransientError(throwable: Throwable): Boolean =
internal fun isGmsTransientError(throwable: Throwable): Boolean =
generateSequence(throwable) { it.cause }
.any { it is java.io.IOException && it.message in gmsTransientMessages }

Expand Down
22 changes: 22 additions & 0 deletions libs/logging/src/test/kotlin/com/getcode/utils/ErrorUtilsTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.getcode.utils

import java.io.IOException
import java.net.UnknownHostException
import java.util.concurrent.ExecutionException
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
Expand Down Expand Up @@ -34,4 +36,24 @@ class ErrorUtilsTest {
assertFalse(ErrorUtils.shouldReport(error, error))
assertFalse(ErrorUtils.shouldReport(Fault(), error))
}

@Test
fun `a wrapped FCM registration failure is a transient GMS error`() {
for (code in listOf("AUTHENTICATION_FAILED", "INTERNAL_SERVER_ERROR", "SERVICE_NOT_AVAILABLE")) {
val error = IOException("FCM Registration failed!", ExecutionException(IOException(code)))
assertTrue(ErrorUtils.isGmsTransientError(error), code)
}
}

@Test
fun `a bare GMS error code is a transient GMS error`() {
assertTrue(ErrorUtils.isGmsTransientError(IOException("InternalServerError")))
}

@Test
fun `an unrelated IOException is not a transient GMS error`() {
val error = IOException("FCM Registration failed!", ExecutionException(IOException("disk full")))
assertFalse(ErrorUtils.isGmsTransientError(error))
assertFalse(ErrorUtils.isGmsTransientError(Fault()))
}
}
Loading