-
Notifications
You must be signed in to change notification settings - Fork 662
Description
[READ] Step 1: Are you in the right place?
Issues filed here should be about bugs in the code in this repository. If you have a general
question, need help debugging, or fall into some other category use one of these other channels:
- For general technical questions, post a question on StackOverflow
with the firebase tag. - For general Firebase discussion, use the
firebase-talk google group. - For help troubleshooting your application that does not fall under one of the above categories,
reach out to the personalized Firebase support channel.
[REQUIRED] Step 2: Describe your environment
- Android Studio version: Otter Feature Drop 2025.2.2 Patch 1
- Firebase Component: App Check
- Component version: BOM 34.6.0
[REQUIRED] Step 3: Describe the problem
Steps to reproduce:
- Register your own
AppCheckProvider - Override
getToken()to return a task which throws an Exception with no message - Call
FirebaseAppCheck.getInstance().getToken() - Observe that Task throws an exception rather than returning an AppCheckTokenResult with
getError()which returns the thrown error
Relevant Code:
@OptIn(ExperimentalCoroutinesApi::class)
fun <T> Deferred<T>.asTask(): Task<T> {
val cancellation = CancellationTokenSource()
val source = TaskCompletionSource<T>(cancellation.token)
invokeOnCompletion callback@{
if (it is CancellationException) {
cancellation.cancel()
return@callback
}
val t = getCompletionExceptionOrNull()
if (t == null) {
source.setResult(getCompleted())
} else {
source.setException(t as? Exception ?: RuntimeExecutionException(t))
}
}
return source.task
}
class TestACP() : AppCheckProvider {
val scope = CoroutineScope(Dispatchers.IO)
override fun getToken(): Task<AppCheckToken> {
return scope.launch {
// some logic
throw MyOwnCustomExceptionWhichIsDetailedEnoughThatIDontNeedADetailMessage()
}.asTask()
}
}
class TestACPF() : AppCheckProviderFactory {
override fun create(firebaseApp: FirebaseApp): AppCheckProvider {
return TestACP()
}
}
// ...
firebaseAppCheck.installAppCheckProviderFactory(TestACPF())@Throws(Exception::class)
suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { continuation ->
this.addOnSuccessListener { continuation.resume(it) }
.addOnFailureListener { continuation.resumeWithException(it) }
.addOnCanceledListener { continuation.cancel() }
}
try {
val result = FirebaseAppCheck.getInstance().getToken().await()
} catch (e: Exception) {
// Exception thrown:
// java.lang.IllegalArgumentException: Detail message must not be empty
}This is caused by this code in Firebase:
Lines 209 to 215 in 6dab936
| // If the token exchange failed, return a dummy token for integrators to attach | |
| // in their headers. | |
| return Tasks.forResult( | |
| DefaultAppCheckTokenResult.constructFromError( | |
| new FirebaseException( | |
| appCheckTokenTask.getException().getMessage(), | |
| appCheckTokenTask.getException()))); |
Lines 232 to 238 in 6dab936
| // If the token exchange failed, return a dummy token for integrators to attach | |
| // in their headers. | |
| return Tasks.forResult( | |
| DefaultAppCheckTokenResult.constructFromError( | |
| new FirebaseException( | |
| appCheckTokenTask.getException().getMessage(), | |
| appCheckTokenTask.getException()))); |
FirebaseException requires a detail message to be provided, so when this is not provided by the implementation the prerequisite check fails:
public FirebaseException(@NonNull String detailMessage, @NonNull Throwable cause) {
Preconditions.checkNotEmpty(detailMessage, "Detail message must not be empty");
super(detailMessage, cause);
}The internal code should fall back to a default message when one is not provided rather than throwing an exception. Of particular note is the method's JavaDoc:
Requests an AppCheckTokenResult from the installed AppCheckFactory. This will always return a successful task, with an AppCheckTokenResult that contains either a valid token, or a dummy token and an error string.