package com.complycubereactnative import android.app.Activity import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import android.content.Intent import com.complycube.sdk.ComplyCubeSdk import com.complycube.sdk.common.data.ClientAuth import com.complycube.sdk.common.data.Result import org.json.JSONArray import org.json.JSONObject import com.facebook.react.ReactApplication import com.facebook.react.bridge.ReactContext import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter import android.util.Log class ComplyCubeActivity : ComponentActivity() { private var hasEmittedResult = false private val eventBuffer = mutableListOf>() private fun emitToJS(eventName: String, payload: String) { val app = this.application as? ReactApplication val activityContext = resolveActivityReactContext(app) val moduleContext = ComplyCubeModule.lastReactContext val reactContext = moduleContext ?: activityContext Log.d( "ComplyCubeActivity", "emitToJS: moduleContext=" + (moduleContext != null) + ", activityContext=" + (activityContext != null) + ", reactContext=" + (reactContext != null) + ", event=" + eventName ) if (reactContext == null) { Log.w("ComplyCubeActivity", "JS not ready. Buffering event: $eventName") eventBuffer.add(eventName to payload) return } try { // Flush any buffered events first eventBuffer.forEach { (event, pay) -> Log.d("ComplyCubeActivity", "Replaying buffered event: $event") reactContext .getJSModule(RCTDeviceEventEmitter::class.java) .emit(event, pay) } eventBuffer.clear() // Emit the current event Log.d("ComplyCubeActivity", "JS is ready. Emitting event: $eventName") reactContext .getJSModule(RCTDeviceEventEmitter::class.java) .emit(eventName, payload) } catch (t: Throwable) { Log.w("ComplyCubeActivity", "JS emit failed. Buffering event: $eventName", t) eventBuffer.add(eventName to payload) } } private fun resolveActivityReactContext(app: ReactApplication?): ReactContext? { if (app == null) return null resolveBridgelessReactContext(app)?.let { return it } return resolveLegacyReactContext(app) } // Prefer ReactHost on new architecture without hard-linking this package to newer RN APIs. private fun resolveBridgelessReactContext(app: ReactApplication): ReactContext? { return try { val reactHost = app.javaClass.methods .firstOrNull { it.name == "getReactHost" && it.parameterCount == 0 } ?.invoke(app) ?: return null reactHost.javaClass.methods .firstOrNull { it.name == "getCurrentReactContext" && it.parameterCount == 0 } ?.invoke(reactHost) as? ReactContext } catch (t: Throwable) { Log.w("ComplyCubeActivity", "Unable to access ReactHost current context", t) null } } private fun resolveLegacyReactContext(app: ReactApplication): ReactContext? { return try { app.reactNativeHost.reactInstanceManager.currentReactContext } catch (t: Throwable) { Log.w("ComplyCubeActivity", "Unable to access ReactNativeHost current context", t) null } } // Removed onResume() replay logic - now handled in emitToJS override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val raw = intent.getStringExtra("EXTRA_SETTINGS_JSON") if (raw.isNullOrBlank()) { returnError("BAD_CONFIG", "Missing settings.") return } // Parse JSON into a HashMap that ComplyCubeSettings.create expects val settingsMap: HashMap = try { JSONObject(raw).toHashMap() } catch (_: Throwable) { returnError("BAD_CONFIG", "Invalid settings JSON.") return } val settings: com.complycube.reactnative.config.ComplyCubeSettings = try { com.complycube.reactnative.config.ComplyCubeSettings.create(settingsMap) } catch (e: Throwable) { returnError("BAD_CONFIG", e.message ?: "Invalid settings.") return } if (!hasEmittedResult) { val builder = ComplyCubeSdk.Builder(this) { sdkResult -> when (sdkResult) { is Result.Canceled -> returnError("CANCELED", "Session was canceled by the user.") is Result.Error -> returnError("ERROR", sdkResult.message) is Result.Success -> { val documentIds = ArrayList() val poaIds = ArrayList() var selfiePhoto = "" var selfieVideo = "" for (stage in sdkResult.stages) { when (stage) { is Result.StageResult.Document -> documentIds.add(stage.id) is Result.StageResult.ProofOfAddress -> poaIds.add(stage.id) is Result.StageResult.LivePhoto -> selfiePhoto = stage.id is Result.StageResult.LiveVideo -> selfieVideo = stage.id else -> {} } } val payload = JSONObject().apply { put("livePhotoId", selfiePhoto) put("liveVideoId", selfieVideo) put( "ids", JSONObject().apply { put("documentIds", JSONArray(documentIds)) put("poaIds", JSONArray(poaIds)) } ) } returnSuccess(payload) } else -> {} } } // Stages vs workflow if (settings.workflowTemplateId.isBlank()) { if (settings.stages.isNotEmpty()) { builder.withStages( settings.stages.first(), *settings.stages.drop(1).toTypedArray() ) } } else { builder.withWorkflowTemplateId(settings.workflowTemplateId) } // Countries (optional) if (settings.countries.isNotEmpty()) { builder.withCountries( settings.countries.first(), *settings.countries.drop(1).toTypedArray() ) } applyAppearance( builder, settings.designTokens, settings.lookAndFeel, { tokens -> withDesignTokens(tokens) Unit }, { lookAndFeel -> withLookAndFeel(lookAndFeel) Unit } ) builder.withEventHandler(enable = true) { sdkEvent -> val payload = JSONObject() .put("type", "telemetry") .put("event", sdkEvent.toString()) .put("ts", System.currentTimeMillis()) .toString() // Emit to JS (will buffer if needed) emitToJS("ComplyCubeEvent", payload) } // Start builder.start( ClientAuth( settings.clientToken, settings.clientID ) ) } onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { returnError("CANCELED", "User pressed back.") } } ) } // ---------- Result helpers (now return VALID JSON + proper result codes) ---------- private fun returnSuccess(payload: JSONObject) { if (hasEmittedResult) return hasEmittedResult = true val returnIntent = Intent().apply { val resultJson = JSONObject().apply { put("type", "SUCCESS") put("payload", payload) }.toString() putExtra("result", resultJson) } setResult(Activity.RESULT_OK, returnIntent) finish() } private fun returnError(type: String, message: String) { if (hasEmittedResult) return hasEmittedResult = true val returnIntent = Intent().apply { val resultJson = JSONObject().apply { put("type", type) put("message", message) }.toString() putExtra("result", resultJson) } // IMPORTANT: non-success => RESULT_CANCELED setResult(Activity.RESULT_CANCELED, returnIntent) finish() } } // --- JSON helper extensions (unchanged) --- private fun JSONObject.toHashMap(): HashMap { val map = HashMap() val iter = keys() while (iter.hasNext()) { val k = iter.next() val v = this.get(k) map[k] = when (v) { is JSONObject -> v.toHashMap() is JSONArray -> v.toArrayList() else -> v } } return map } private fun JSONArray.toArrayList(): ArrayList { val list = ArrayList(length()) for (i in 0 until length()) { val v = get(i) list.add( when (v) { is JSONObject -> v.toHashMap() is JSONArray -> v.toArrayList() else -> v } ) } return list }