Skip to content

Android: Refactor withTokenManager for Better Error Handling #30

@christianbach

Description

@christianbach

We’re seeing issues with withTokenManager. It doesn’t handle errors properly, especially in the logoutAndRevokeTokens function. Exceptions are not propagated correctly, and we’re running into issues like missing error_uri when calling the revokeRefreshToken method. (this is happening in production)

Our current solution is to not call logout on android devices.

Stack trace:

01-03 21:22:35.027 31480 31613 E AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
01-03 21:22:35.027 31480 31613 E AndroidRuntime: Process: io.montrose.mobile, PID: 31480
01-03 21:22:35.027 31480 31613 E AndroidRuntime: io.curity.haapi.react.FailedTokenManagerRequestException: Failed to make token request
01-03 21:22:35.027 31480 31613 E AndroidRuntime: Caused by: org.json.JSONException: No value for error_uri

Current Behavior:

  • The exception is caught but not properly handled or propagated back.
  • The event HaapiFinishedLoading isn’t always triggered.

Suggested Fixes:

  1. withTokenManager: Use CompletableDeferred to ensure we handle the async result correctly. Complete it with an exception in case of failure, and always trigger HaapiFinishedLoading in finally. Please not that this should also apply to withHaapiManager
@Throws(FailedTokenManagerRequestException::class)
private fun withTokenManager(
    onSuccess: ((TokenResponse) -> Unit)? = null,
    accessorRequest: suspend (tokenManager: OAuthTokenManager, coroutineContext: CoroutineContext) -> TokenResponse?
) {
    _eventEmitter.sendEvent(HaapiLoading)

    val manager = _accessorRepository?.accessor?.oAuthTokenManager ?: throw notInitialized()

    val result = CompletableDeferred<Unit>()

    _haapiScope.launch {
        try {
            val response = accessorRequest(manager, this.coroutineContext)
            if (onSuccess != null && response != null) {
                onSuccess(response)
            }
            result.complete(Unit)
        } catch (e: Exception) {
            Log.w(TAG, "Failed to make token request: ${e.message}")
            _eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
            result.completeExceptionally(FailedTokenManagerRequestException("Failed to make token request", e))
        } finally {
            _eventEmitter.sendEvent(EventType.HaapiFinishedLoading)
        }
    }

    try {
        result.await()
    } catch (e: FailedTokenManagerRequestException) {
        throw e
    }
}
  1. logout: Reject the promise with a more detailed error message, so that the React Native side properly handles errors.
@ReactMethod
fun logout(promise: Promise) {
    Log.d(TAG, "Logout was called, revoking tokens")

    try {
        if (_tokenResponse != null) {
            _handler.logoutAndRevokeTokens(_tokenResponse!!.accessToken, _tokenResponse!!.refreshToken)
        } else {
            _handler.closeHaapiConnection()
        }
        _tokenResponse = null
        resolveRequest(LoggedOut, "{}", promise)
    } catch (e: Exception) {
        Log.e(TAG, "Failed to logout: ${e.message}")
        rejectRequest("Logout failed", e.message ?: "Unknown error", promise)
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions