package com.iproov.sdk import android.graphics.Bitmap import android.util.Base64 import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.WritableMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.iproov.sdk.api.IProov import com.iproov.sdk.api.exception.CameraException import com.iproov.sdk.api.exception.CameraPermissionException import com.iproov.sdk.api.exception.CaptureAlreadyActiveException import com.iproov.sdk.api.exception.FaceDetectorException import com.iproov.sdk.api.exception.IProovException import com.iproov.sdk.api.exception.InvalidOptionsException import com.iproov.sdk.api.exception.MultiWindowUnsupportedException import com.iproov.sdk.api.exception.NetworkException import com.iproov.sdk.api.exception.ServerException import com.iproov.sdk.api.exception.UnsupportedDeviceException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream class IProovFlowSessionHandler(val reactContext: ReactContext) { private var sessionStateJob: Job? = null private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.Main + job) fun observeSessionState(session: IProov.Session, whenReady: (() -> Unit)? = null) { sessionStateJob?.cancel() sessionStateJob = scope.launch(Dispatchers.IO) { session.state .onSubscription { whenReady?.invoke() } .collect { state -> if (sessionStateJob?.isActive == true) { withContext(Dispatchers.Main.immediate) { when (state) { is IProov.State.Starting -> { } is IProov.State.Connecting -> emitEvent(EVENT_CONNECTING) is IProov.State.Connected -> emitEvent(EVENT_CONNECTED) is IProov.State.Processing -> { val params = Arguments.createMap().apply { putDouble("progress", state.progress) putString("message", state.message) } emitEvent(EVENT_PROCESSING, params) } is IProov.State.Success -> { val params = Arguments.createMap() state.successResult.frame?.let { params.putString("frame", base64EncodeBitmap(it)) } emitEvent(EVENT_SUCCESS, params) } is IProov.State.Failure -> { val params = Arguments.createMap().apply { putString( "feedbackCode", state.failureResult.reason.feedbackCode ) putString( "reason", reactContext.getString(state.failureResult.reason.description) ) } state.failureResult.frame?.let { params.putString("frame", base64EncodeBitmap(it)) } emitEvent(EVENT_FAILURE, params) } is IProov.State.Error -> handleError(state.exception) is IProov.State.Canceled -> { val params = Arguments.createMap().apply { putString("canceler", state.canceler.name) } emitEvent(EVENT_CANCELED, params) } } } } } } } fun handleError(exception: IProovException) { exception.printStackTrace() val params = Arguments.createMap().apply { putString("error", toErrorString(exception)) putString("reason", exception.reason) putString("message", exception.localizedMessage) } emitEvent(EVENT_ERROR, params) } private fun toErrorString(e: IProovException): String = when (e) { is CaptureAlreadyActiveException -> "capture_already_active_error" is NetworkException -> "network_error" is CameraPermissionException -> "camera_permission_error" is ServerException -> "server_error" is MultiWindowUnsupportedException -> "multi_window_unsupported_error" is CameraException -> "camera_error" is FaceDetectorException -> "face_detector_error" is InvalidOptionsException -> "invalid_options_error" is UnsupportedDeviceException -> "unsupported_device_error" else -> "unexpected_error" } private fun base64EncodeBitmap(bitmap: Bitmap): String { val byteArrayOutputStream = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) val byteArray = byteArrayOutputStream.toByteArray() return Base64.encodeToString(byteArray, Base64.NO_WRAP) } private fun emitEvent(name: String, params: WritableMap? = null) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(name, params) } fun stop() { job.cancel() } }