package com.contentsquare.rn.csq.autocapture import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Dynamic import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableMap import io.heap.core.logs.HeapLogger import io.heap.core.support.HeapBridgeSupport import io.heap.core.support.InvalidParametersException import io.heap.core.support.UnknownMethodException import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import com.contentsquare.rn.csq.InvocationEventEmitter import com.contentsquare.rn.csq.utils.* /** * Core autocapture logic for CSQ/Heap integration. * This class handles communication between React Native and the Heap native SDK, * and works with both old and new React Native architectures. * * @param context The React Application Context * @param eventEmitter Interface for emitting events to JavaScript */ class CSQAutocapture( private val context: ReactApplicationContext, private val eventEmitter: InvocationEventEmitter ) : HeapBridgeSupport.Delegate { private val bridgeSupport = HeapBridgeSupport(context) private val asyncExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() } init { // Set up the delegate bridgeSupport.delegate = this } /** * Executes a block of code asynchronously with error handling. */ fun executeAsync(block: () -> Unit) { synchronized(asyncExecutor) { if (asyncExecutor.isShutdown) return@synchronized asyncExecutor.execute { try { block() } catch (td: ThreadDeath) { // Always rethrow a ThreadDeath, according to the Android docs. throw td } catch (t: Throwable) { HeapLogger.error( "Heap encountered an error in the runtime bridge", "HeapReactNativeBridge", t ) } } } } /** * Handles method invocations from JavaScript to native. * @param method The name of the method to invoke * @param arguments The arguments passed from JavaScript * @param promise Promise to resolve/reject with the result */ fun handleInvocation(method: String, arguments: ReadableMap, promise: Promise) { try { @Suppress("UNCHECKED_CAST") val argumentsMap = arguments.toMap() as Map val result = bridgeSupport.handleInvocation(method, argumentsMap) promise.resolve(result?.toWritable()) } catch (e: UnknownMethodException) { promise.reject( "NOT_IMPLEMENTED", "The method $method is not implemented in the Java runtime.", e ) } catch (e: InvalidParametersException) { promise.reject( "BAD_ARGS", "Invalid arguments sent to $method in the Java runtime. See logs for details.", e ) } catch (e: Throwable) { promise.reject( "UNKNOWN", "An unknown error occurred while calling $method in the Java runtime. See logs for details.", e ) } } /** * Handles callback results from JavaScript to native. * @param callbackId The unique identifier for the callback * @param data The data returned from JavaScript * @param error Any error message from JavaScript */ fun handleResult(callbackId: String, data: Dynamic?, error: String?) { executeAsync { bridgeSupport.handleResult(callbackId, data?.toAny(), error) } } /** * Handles callback results from JavaScript to native (ReadableMap variant for new arch). * @param callbackId The unique identifier for the callback * @param data The data returned from JavaScript as ReadableMap * @param error Any error message from JavaScript */ fun handleResult(callbackId: String, data: ReadableMap?, error: String?) { executeAsync { bridgeSupport.handleResult(callbackId, data?.toMap(), error) } } /** * Implementation of HeapBridgeSupport.Delegate. * Called when the native SDK needs to invoke a method in JavaScript. * @param method The method name to invoke in JavaScript * @param arguments The arguments to pass to JavaScript * @param callbackId Optional callback ID for handling responses */ override fun sendInvocation(method: String, arguments: Map<*, *>?, callbackId: String?) { executeAsync { try { val payload = Arguments.createMap().apply { putString("type", "invocation") putString("method", method) arguments?.let { putMap("arguments", it.toWritableMap()) } callbackId?.let { putString("callbackId", callbackId) } } eventEmitter.sendInvocationEvent(payload) } catch (t: Throwable) { if (callbackId != null) { bridgeSupport.handleResult( callbackId, null, "Sending invocation failed with $t." ) } } } } }