diff --git a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt index daf0906..7ad0cd6 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiInteractionHandler.kt @@ -67,83 +67,88 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext /** * Starts the flow by performing an authorization request to the configured server. * This will perform attestation and obtain an API access token - * - * @throws FailedHaapiRequestException if the request fails */ - @Throws(FailedHaapiRequestException::class) - fun startAuthentication(onSuccess: (HaapiResponse) -> Unit) { - withHaapiManager(onSuccess) { haapiManager, context -> + fun startAuthentication( + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.start(context) } } - @Throws(FailedHaapiRequestException::class) - fun followLink(link: Link, onSuccess: (HaapiResponse) -> Unit) { - withHaapiManager(onSuccess) { haapiManager, context -> + fun followLink( + link: Link, + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit + ) { + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.followLink(link, context) } } - @Throws(FailedHaapiRequestException::class) fun submitForm( form: FormActionModel, - parameters: Map, onSuccess: (HaapiResponse) -> Unit + parameters: Map, + onSuccess: (HaapiResponse) -> Unit, + onError: (FailedHaapiRequestException) -> Unit ) { - withHaapiManager(onSuccess) { haapiManager, context -> + withHaapiManager(onSuccess, onError) { haapiManager, context -> haapiManager.submitForm(form, parameters, context) } } - @Throws(FailedTokenManagerRequestException::class) - fun exchangeCode(codeResponse: OAuthAuthorizationResponseStep, onSuccess: (TokenResponse) -> Unit) { - withTokenManager(onSuccess) { tokenManager, context -> + fun exchangeCode( + codeResponse: OAuthAuthorizationResponseStep, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedTokenManagerRequestException) -> Unit + ) { + withTokenManager(onSuccess, onError) { tokenManager, context -> tokenManager.fetchAccessToken(codeResponse.properties.code, context) } } - @Throws(FailedTokenManagerRequestException::class) - fun refreshAccessToken(refreshToken: String, onSuccess: (TokenResponse) -> Unit) { + fun refreshAccessToken( + refreshToken: String, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedTokenManagerRequestException) -> Unit + ) { Log.d(TAG, "Refreshing access token") - - try { - withTokenManager(onSuccess) { tokenManager, coroutineContext -> - tokenManager.refreshAccessToken(refreshToken, coroutineContext) - } - } catch (e: Exception) { - Log.d(TAG, "Failed to refresh tokens: ${e.message}") - throw FailedTokenManagerRequestException("Failed to refresh token", e) + withTokenManager(onSuccess, onError) { tokenManager, coroutineContext -> + tokenManager.refreshAccessToken(refreshToken, coroutineContext) } } - fun logoutAndRevokeTokens(accessToken: String, refreshToken: String? = null) { - try { - if (refreshToken != null) { - Log.d(TAG, "Revoking refresh token") - withTokenManager { tokenManager, context -> - tokenManager.revokeRefreshToken(refreshToken!!, context) - null - } - } else { - Log.d(TAG, "Revoking access token") - withTokenManager { tokenManager, context -> - tokenManager.revokeAccessToken(accessToken, context) - null - } + fun logoutAndRevokeTokens( + accessToken: String, + refreshToken: String? = null, + onSuccess: (TokenResponse) -> Unit, + onError: (FailedTokenManagerRequestException) -> Unit + ) { + if (refreshToken != null) { + Log.d(TAG, "Revoking refresh token") + withTokenManager(onSuccess, onError) { tokenManager, context -> + tokenManager.revokeRefreshToken(refreshToken, context) + null + } + } else { + Log.d(TAG, "Revoking access token") + withTokenManager(onSuccess, onError) { tokenManager, context -> + tokenManager.revokeAccessToken(accessToken, context) + null } - } catch (e: Exception) { - Log.d(TAG, "Failed to revoke tokens: ${e.message}") } - _accessorRepository?.close() + closeHaapiConnection() } fun closeHaapiConnection() { _accessorRepository?.close() } - @Throws(FailedHaapiRequestException::class) private fun withHaapiManager( onSuccess: (HaapiResponse) -> Unit, + onError: ((FailedHaapiRequestException) -> Unit)? = null, accessorRequest: suspend (manager: HaapiManager, context: CoroutineContext) -> HaapiResponse ) { _eventEmitter.sendEvent(HaapiLoading) @@ -155,18 +160,25 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext val response = accessorRequest(manager, this.coroutineContext) onSuccess(response) } catch (e: Exception) { - Log.w(TAG, "Failed to make HAAPI request: ${e.message}") + Log.w(TAG, "Failed to make Haapi request: ${e.message}") + _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) + if (onError != null) { + onError( + FailedHaapiRequestException( + "Failed to make Haapi request: ${e.message}", + e + ) + ) + } + } finally { _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) - throw FailedHaapiRequestException("Failed to make HAAPI request: ${e.message}", e) } - - _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } } - @Throws(FailedTokenManagerRequestException::class) private fun withTokenManager( onSuccess: ((TokenResponse) -> Unit)? = null, + onError: ((FailedTokenManagerRequestException) -> Unit)? = null, accessorRequest: suspend (tokenManager: OAuthTokenManager, coroutineContext: CoroutineContext) -> TokenResponse? ) { _eventEmitter.sendEvent(HaapiLoading) @@ -180,12 +192,19 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext onSuccess(response) } } catch (e: Exception) { - Log.w(TAG, "Failed to make token request: ${e.message}") + Log.w(TAG, "Failed to make HAAPI token request: ${e.message}") + _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) + if (onError != null) { + onError( + FailedTokenManagerRequestException( + "Failed to make HAAPI token request: ${e.message}", + e + ) + ) + } + } finally { _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) - throw FailedTokenManagerRequestException("Failed to make token request", e) } - - _eventEmitter.sendEvent(EventType.HaapiFinishedLoading) } } @@ -199,4 +218,4 @@ class HaapiInteractionHandler(private val _reactContext: ReactApplicationContext throw HaapiNotInitializedException() } -} \ No newline at end of file +} diff --git a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt index 861b851..d2f676a 100644 --- a/android/src/main/java/io/curity/haapi/react/HaapiModule.kt +++ b/android/src/main/java/io/curity/haapi/react/HaapiModule.kt @@ -51,7 +51,8 @@ import se.curity.identityserver.haapi.android.sdk.models.oauth.TokenResponse const val TAG = "HaapiNative" -class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(_reactContext), +class HaapiModule(private val _reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(_reactContext), LifecycleEventListener { override fun getName() = "HaapiModule" @@ -64,8 +65,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon private val _webAuthnHandler = WebAuthnHandler(_reactContext) init { - HaapiLogger.enabled = true - HaapiLogger.isDebugEnabled = true + HaapiLogger.enabled = BuildConfig.DEBUG + HaapiLogger.isDebugEnabled = BuildConfig.DEBUG _reactContext.addLifecycleEventListener(this) } @@ -94,65 +95,74 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon @ReactMethod fun start(promise: Promise) { Log.d(TAG, "Start was called") - - try { - _handler.startAuthentication { response -> handleHaapiResponse(response, promise) } - } catch (e: Exception) { - Log.e(TAG, e.message ?: "Failed to attest $e") - rejectRequest(e, promise) - } + _handler.startAuthentication( + onSuccess = { response -> handleHaapiResponse(response, promise) }, + onError = { e -> + Log.e(TAG, e.message ?: "Failed to attest $e") + rejectRequest(e, promise) + } + ) } @ReactMethod fun logout(promise: Promise) { Log.d(TAG, "Logout was called, revoking tokens") - if (_tokenResponse != null) { - _handler.logoutAndRevokeTokens(_tokenResponse!!.accessToken, _tokenResponse!!.refreshToken) + _handler.logoutAndRevokeTokens( + _tokenResponse!!.accessToken, + _tokenResponse!!.refreshToken, + onSuccess = { + _tokenResponse = null + resolveRequest(LoggedOut, "{}", promise) + }, + onError = { e -> + Log.w(TAG, "Failed to logout: ${e.message}") + rejectRequest(e, promise) + } + ) } else { _handler.closeHaapiConnection() + _tokenResponse = null + resolveRequest(LoggedOut, "{}", promise) } - - _tokenResponse = null - resolveRequest(LoggedOut, "{}", promise) } @ReactMethod fun refreshAccessToken(refreshToken: String, promise: Promise) { Log.d(TAG, "Refreshing access token") - - try { - _handler.refreshAccessToken(refreshToken) { tokenResponse -> + _handler.refreshAccessToken( + refreshToken, + onSuccess = { tokenResponse -> handleTokenResponse(tokenResponse, promise) + }, + onError = { e -> + rejectRequest(e, promise) } - } catch (e: Exception) { - Log.d(TAG, "Failed to revoke tokens: ${e.message}") - rejectRequest(e, promise) - } + ) } @ReactMethod fun navigate(linkMap: ReadableMap, promise: Promise) { - val linkJson = _gson.toJson(linkMap.toHashMap()) val link = _gson.fromJson(linkJson, Link::class.java) - try { - _handler.followLink(link) { response -> handleHaapiResponse(response, promise) } - } catch (e: Exception) { - Log.d(TAG, "Failed to navigate to link: ${e.message}") - rejectRequest(e, promise) - } + + _handler.followLink( + link, + onSuccess = { response -> handleHaapiResponse(response, promise) }, + onError = { e -> + Log.d(TAG, "Failed to navigate to link: ${e.message}") + rejectRequest(e, promise) + } + ) } @ReactMethod fun submitForm(actionMap: ReadableMap, parameters: ReadableMap, promise: Promise) { - val action = findAction(actionMap, _haapiResponse as HaapiRepresentation) if (action == null) { Log.d(TAG, "Failed to find action to submit. Possible re-submit") return } - submitModel(action.model, parameters.toHashMap(), promise) } @@ -172,29 +182,36 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon } - private fun submitModel(model: FormActionModel, parameters: Map, promise: Promise) { + private fun submitModel( + model: FormActionModel, + parameters: Map, + promise: Promise + ) { Log.d(TAG, "Submitting form $model") - try { - _handler.submitForm(model, parameters) { response -> + _handler.submitForm( + model, + parameters, + onSuccess = { response -> handleHaapiResponse(response, promise) + }, + onError = { e -> + Log.w(TAG, "Failed to submit form: ${e.message}") + rejectRequest(e, promise) } - } catch (e: Exception) { - Log.w(TAG, "Failed to submit form: ${e.message}") - rejectRequest(e, promise) - } - + ) } private fun handleCodeResponse(response: OAuthAuthorizationResponseStep, promise: Promise) { - try { - _handler.exchangeCode(response) { tokenResponse -> + _handler.exchangeCode( + response, + onSuccess = { tokenResponse -> handleTokenResponse(tokenResponse, promise) + }, + onError = { e -> + Log.w(TAG, "Failed to exchange code: ${e.message}") + rejectRequest(e, promise) } - - } catch (e: Exception) { - Log.w(TAG, "Failed to exchange code: ${e.message}") - rejectRequest(e, promise) - } + ) } private fun handleTokenResponse(tokenResponse: TokenResponse, promise: Promise) { @@ -212,7 +229,8 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon } else { tokenResponse as ErrorTokenResponse val errorResponseMap = mapOf( - "error" to tokenResponse.error, "error_description" to tokenResponse.errorDescription + "error" to tokenResponse.error, + "error_description" to tokenResponse.errorDescription ) resolveRequest(TokenResponseError, JsonUtil.toJsonString(errorResponseMap), promise) } @@ -241,9 +259,15 @@ class HaapiModule(private val _reactContext: ReactApplicationContext) : ReactCon _haapiResponse = response when (response) { - is WebAuthnRegistrationClientOperationStep -> handleWebAuthnRegistration(response, promise) + is WebAuthnRegistrationClientOperationStep -> handleWebAuthnRegistration( + response, + promise + ) - is WebAuthnAuthenticationClientOperationStep -> handleWebAuthnAuthentication(response, promise) + is WebAuthnAuthenticationClientOperationStep -> handleWebAuthnAuthentication( + response, + promise + ) is AuthenticatorSelectorStep -> resolveRequest( AuthenticationSelectorStep, response.toJsonString(), promise