package com.nimiq.plugins.sumsubsdk import android.app.Activity import android.util.Log import com.sumsub.log.logger.Logger import com.sumsub.sns.core.SNSMobileSDK import com.sumsub.sns.core.data.listener.SNSCompleteHandler import com.sumsub.sns.core.data.listener.SNSErrorHandler import com.sumsub.sns.core.data.listener.SNSEvent import com.sumsub.sns.core.data.listener.SNSEventHandler import com.sumsub.sns.core.data.listener.SNSStateChangedHandler import com.sumsub.sns.core.data.listener.TokenExpirationHandler import com.sumsub.sns.core.data.model.SNSCompletionResult import com.sumsub.sns.core.data.model.SNSException import com.sumsub.sns.core.data.model.SNSInitConfig import com.sumsub.sns.core.data.model.SNSSDKState import java.util.Locale import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit class SumsubMobileSdk { private var TAG = "SumsubMobileSdk" private var sdk: SNSMobileSDK.SDK? = null private var plugin: SumsubMobileSdkPlugin? = null private var newToken: String? = null private var tokenLatch: CountDownLatch? = null private var sdkStatus: SNSSDKState? = null var isInitialized: Boolean = false private set fun setToken(token: String) { newToken = token tokenLatch?.countDown() } private fun waitForToken(): String { tokenLatch = CountDownLatch(1) try { // Wait up to 30 seconds tokenLatch?.await(30, TimeUnit.SECONDS) } catch (e: InterruptedException) { // Handle interruption } val token = newToken newToken = null tokenLatch = null return token ?: "" } fun initialize( accessToken: String, plugin: SumsubMobileSdkPlugin, email: String?, phone: String?, debug: Boolean?, locale: String?, isAnalyticsEnabled: Boolean?, dismissalTimeInterval: Int? ) { this.plugin = plugin val tokenExpirationHandler = object : TokenExpirationHandler { override fun onTokenExpired(): String { plugin.requestNewToken() return waitForToken() } } val builder = SNSMobileSDK.Builder(plugin.activity) .withAccessToken(accessToken, onTokenExpiration = tokenExpirationHandler) if (email != null || phone != null) { builder.withConf(SNSInitConfig(email = email, phone = phone)) } if (locale != null) { builder.withLocale(Locale(locale)) } if (debug == true) { builder.withDebug(true) } if(dismissalTimeInterval != null) { builder.withAutoCloseOnApprove(dismissalTimeInterval) } if (isAnalyticsEnabled != null) { builder.withAnalyticsEnabled(isAnalyticsEnabled) } builder.withStateChangedHandler(getOnStateChangeListener()) .withCompleteHandler(getOnSDKCompletedHandler()) .withErrorHandler(getOnSDKErrorHandler()) .withEventHandler(getOnEventHandler()) .withLogTree(CustomTree()) sdk = builder.build() isInitialized = true sdkStatus = SNSSDKState.Ready } fun present(activity: Activity) { if (!isInitialized || sdk == null) { throw RuntimeException("SDK not initialized") } sdk?.launch() } fun dismiss() { sdk?.dismiss() } fun getVerificationStatus(): SNSSDKState? { return sdkStatus } // TODO: use new architecture (https://docs.sumsub.com/docs/callbacks-android#on-sdk-state-changes) private fun getOnStateChangeListener(): SNSStateChangedHandler = object : SNSStateChangedHandler { override fun onStateChanged(previousState: SNSSDKState, currentState: SNSSDKState) { Log.d(TAG, "State changed from $currentState to $previousState") sdkStatus = previousState // Extract status info based on the state val (statusName, description, details) = when (previousState) { is SNSSDKState.Ready -> Triple("SDK is ready", currentState.message, "") is SNSSDKState.Failed -> { when (previousState) { is SNSSDKState.Failed.ApplicantNotFound -> Triple("Applicant not found", currentState.message, "") is SNSSDKState.Failed.ApplicantMisconfigured -> Triple("Applicant misconfigured", currentState.message, "") is SNSSDKState.Failed.InitialLoadingFailed -> Triple("Initial loading failed", currentState.message, "") is SNSSDKState.Failed.InvalidParameters -> Triple("Invalid parameters", currentState.message, "") is SNSSDKState.Failed.NetworkError -> Triple("Network error", currentState.message, "") is SNSSDKState.Failed.Unauthorized -> Triple("Unauthorized", currentState.message, "Invalid token or a token can't be refreshed by the SDK. Please, check your token expiration handler") is SNSSDKState.Failed.Unknown -> Triple("Unknown error", currentState.message, "") } } is SNSSDKState.Initial -> Triple("No verification steps are passed yet", currentState.message, "") is SNSSDKState.Incomplete -> Triple("Some but not all verification steps are passed over", currentState.message, "") is SNSSDKState.Pending -> Triple("Verification is in pending state", currentState.message, "") is SNSSDKState.FinallyRejected -> Triple("Applicant has been finally rejected", currentState.message, "") is SNSSDKState.TemporarilyDeclined -> Triple("Applicant has been declined temporarily", currentState.message, "") is SNSSDKState.Approved -> Triple("Applicant has been approved", currentState.message, "") } // Notify status change for all states plugin?.notifyStatusChanged(currentState, previousState, statusName, description, details) // Additionally notify failure for error states if (previousState is SNSSDKState.Failed) { plugin?.notifyFailStatus(previousState.message) } // Notify Approval Status if (previousState is SNSSDKState.Approved) { plugin?.notifyApprovalStatus(true) } else if (previousState is SNSSDKState.FinallyRejected) { plugin?.notifyApprovalStatus(false) } } } private fun getOnSDKCompletedHandler(): SNSCompleteHandler = object : SNSCompleteHandler { override fun onComplete(result: SNSCompletionResult, state: SNSSDKState) { Log.d(TAG, "The SDK is finished. Result: $result, State: $state") when (result) { // A successful completion of the work of the SDK, including dismiss() call. is SNSCompletionResult.SuccessTermination -> plugin?.notifyDidDismiss(sdkStatus, state.message) // An error occurred. Look at the exception object to get more information. is SNSCompletionResult.AbnormalTermination -> plugin?.notifyFailStatus(result.exception?.message) } } } private fun getOnSDKErrorHandler(): SNSErrorHandler = object : SNSErrorHandler { override fun onError(exception: SNSException) { Log.d(TAG, "The SDK throws an exception. Exception: $exception") when (exception) { is SNSException.Api -> plugin?.notifyFailStatus("Api exception. ${exception.description}") is SNSException.Network -> plugin?.notifyFailStatus("Network exception.") is SNSException.Unknown -> plugin?.notifyFailStatus("Unknown exception.") } } } private fun getOnEventHandler(): SNSEventHandler = object : SNSEventHandler { override fun onEvent(event: SNSEvent) { plugin?.notifyEvent(event.eventType, event.payload) when (event) { is SNSEvent.SNSEventApplicantLoaded -> { val applicantId = event.payload?.get("applicantId") as String plugin?.notifyApplicantLoaded(applicantId) } is SNSEvent.SNSEventStepInitiated -> { val step = event.payload?.get("idDocSetType") as String plugin?.notifyStepInitiated(step) } is SNSEvent.SNSEventStepCompleted -> { val step = event.payload?.get("idDocSetType") as String val isCancelled = event.payload?.get("isCancelled") as Boolean plugin?.notifyStepCompleted(step, isCancelled) } is SNSEvent.SNSEventAnalytics -> { val eventName = event.payload?.get("eventName") as String val payloadObj = event.payload?.get("payload") val payload = if (payloadObj is Map<*, *>) { @Suppress("UNCHECKED_CAST") payloadObj as Map } else { emptyMap() } plugin?.notifyAnalyticsEvent(eventName, payload) } else -> { Log.d(TAG,"Unknown Event: event=${event.eventType}, payload=${event.payload}") } } } } inner class CustomTree : Logger { override fun d(tag: String, message: String, throwable: Throwable?) { Log.d(tag, message, throwable) plugin?.logHandler("DEBUG", message) } override fun e(tag: String, message: String, throwable: Throwable?) { Log.e(tag, message, throwable) plugin?.logHandler("ERROR", message) } override fun i(tag: String, message: String, throwable: Throwable?) { Log.i(tag, message, throwable) plugin?.logHandler("INFO", message) } override fun v(tag: String, message: String, throwable: Throwable?) { Log.v(tag, message, throwable) plugin?.logHandler("TRACE", message) } override fun w(tag: String, message: String, throwable: Throwable?) { Log.w(tag, message, throwable) plugin?.logHandler("WARNING", message) } } }