package com.smartechbasereactnative import android.content.Intent import android.location.Location import android.os.Handler import android.os.Looper import android.util.Log import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeArray import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.DeviceEventManagerModule import com.netcore.android.SMTBundleKeys import com.netcore.android.Smartech import com.netcore.android.contentpz.SMTWidgetListener import com.netcore.android.contentpz.model.SMTWidget import org.json.JSONArray import org.json.JSONObject import java.lang.ref.WeakReference private lateinit var smartech: Smartech private lateinit var reactContext: ReactApplicationContext class SmartechBaseReactNativeImpl(private val reactApplicationContext: ReactApplicationContext) : SmartechDeeplinkCallbackHandler, SMTWidgetListener { companion object { const val NAME = "SmartechBaseReactNative" private val TAG = SmartechBaseReactNativeImpl::class.java.simpleName // Deeplink constants const val SMARTECH_DEEPLINK = "SmartechDeeplink" const val SMARTECH_WIDGET = "SmartechWidgetDataReceived" private const val SMT_DEEPLINK_SOURCE_IDENTIFIER = "smtDeeplinkSource" private const val SMT_DEEPLINK_IDENTIFIER = "smtDeeplink" private const val SMT_PAYLOAD_IDENTIFIER = "smtPayload" private const val SMT_CUSTOM_PAYLOAD_IDENTIFIER = "smtCustomPayload" private const val SMT_IS_DEEPLINK_FROM_BG = "smtIsDeeplinkFromBg" // For legacy support //private const val SMARTECH_DEEPLINK_NOTIFICATION = "SmartechDeeplinkNotification" private const val SMARTECH_DEEPLINK_IDENTIFIER = "deeplink" private const val SMARTECH_CUSTOM_PAYLOAD_IDENTIFIER = "customPayload" var isSmartechDeeplinkInit = false // Timing constants private const val MAX_DELAY_TIME = 3000L private const val DELAY_DIFF = 100L private const val START_DELAY_TIME = 400L } init { reactContext = reactApplicationContext; smartech = Smartech.getInstance(WeakReference(reactContext)) smartech.setWidgetListener(this) initSDK() } fun getConstants(): Map = mapOf( SMARTECH_DEEPLINK to SMARTECH_DEEPLINK, SMARTECH_WIDGET to SMARTECH_WIDGET ) private fun initSDK() { try { SmartechBasePlugin.instance.setDeeplinkHandlerListener(this) } catch (e: Exception) { println("ERROR: [initSDK] Failed to initialize Smartech SDK: ${e.message}") e.printStackTrace() } } // App tracking methods fun trackAppInstall(): Boolean { return try { smartech.trackAppInstall() true } catch (e: Throwable) { println("ERROR: [trackAppInstall] Failed to track app install: ${e.message}") e.printStackTrace() false } } fun trackAppUpdate(): Boolean { return try { smartech.trackAppUpdate() true } catch (e: Throwable) { println("ERROR: [trackAppUpdate] Failed to track app update: ${e.message}") e.printStackTrace() false } } fun trackAppInstallUpdateBySmartech(): Boolean { return try { smartech.trackAppInstallUpdateBySmartech() true } catch (e: Throwable) { println("ERROR: [trackAppInstallUpdateBySmartech] Failed to track app install/update by Smartech: ${e.message}") e.printStackTrace() false } } fun trackEvent(eventName: String, payload: ReadableMap): Boolean { return try { if (eventName.isEmpty()) { println("ERROR: [trackEvent] Event name is empty - unable to track event. Please provide a valid event name.") return false } val hMapPayload = SmartechHelper.convertReadableMapToHashMap(payload) smartech.trackEvent(eventName, hMapPayload) true } catch (e: Throwable) { println("ERROR: [trackEvent] Failed to track event '$eventName': ${e.message}") e.printStackTrace() false } } // User identity methods fun login(identity: String): Boolean { return try { if (identity.isEmpty()) { println("ERROR: [login] User identity is empty - unable to login. Please provide a valid identity.") return false } smartech.apply { setUserIdentity(identity) login(identity) } true } catch (e: Throwable) { println("ERROR: [login] Failed to login user with identity '$identity': ${e.message}") e.printStackTrace() false } } fun logoutAndClearUserIdentity(isLogout: Boolean): Boolean { return try { smartech.logoutAndClearUserIdentity(isLogout) true } catch (e: Throwable) { println("ERROR: [logoutAndClearUserIdentity] Failed to logout and clear user identity: ${e.message}") e.printStackTrace() false } } fun setUserIdentity(identity: String, callback: Callback) { try { if (identity.isEmpty()) { callbackHandlerError(callback, "User identity cannot be empty. Please provide a valid identity.") return } smartech.setUserIdentity(identity) callbackHandler(callback, "Identity set successfully.") } catch (e: Throwable) { e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to set user identity") } } fun getUserIdentity(callback: Callback) { try { val userIdentity = smartech.getUserIdentity() callbackHandler(callback, userIdentity) } catch (e: Throwable) { println("ERROR: [getUserIdentity] Failed to get user identity: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get user identity") } } fun clearUserIdentity(): Boolean { return try { smartech.clearUserIdentity() true } catch (e: Throwable) { println("ERROR: [clearUserIdentity] Failed to clear user identity: ${e.message}") e.printStackTrace() false } } fun updateUserProfile(profileData: ReadableMap): Boolean { return try { val hmapProfile = SmartechHelper.convertReadableMapToHashMap(profileData) smartech.updateUserProfile(hmapProfile) true } catch (e: Throwable) { println("ERROR: [updateUserProfile] Failed to update user profile: ${e.message}") e.printStackTrace() false } } fun optTracking(value: Boolean): Boolean { return try { smartech.optTracking(value) true } catch (e: Throwable) { println("ERROR: [optTracking] Failed to set tracking opt status to $value: ${e.message}") e.printStackTrace() false } } fun hasOptedTracking(callback: Callback) { try { val isTracking = smartech.hasOptedTracking() callbackHandler(callback, isTracking) } catch (e: Throwable) { println("ERROR: [hasOptedTracking] Failed to check tracking opt status: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to check tracking opt status") } } fun optInAppMessage(value: Boolean): Boolean { return try { smartech.optInAppMessage(value) true } catch (e: Throwable) { println("ERROR: [optInAppMessage] Failed to set in-app message opt status to $value: ${e.message}") e.printStackTrace() false } } fun hasOptedInAppMessage(callback: Callback) { try { val isInAppOpted = smartech.hasOptedInAppMessage() callbackHandler(callback, isInAppOpted) } catch (e: Throwable) { println("ERROR: [hasOptedInAppMessage] Failed to check in-app message opt status: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to check in-app message opt status") } } // Location methods fun setUserLocation(latitude: Double, longitude: Double): Boolean { return try { val location = Location("Smartech").apply { setLatitude(latitude) setLongitude(longitude) } smartech.setUserLocation(location) true } catch (e: Throwable) { println("ERROR: [setUserLocation] Failed to set user location (lat: $latitude, lng: $longitude): ${e.message}") e.printStackTrace() false } } // Helper methods fun getAppId(callback: Callback) { try { val appId = smartech.getAppID() ?: "" callbackHandler(callback, appId) } catch (e: Throwable) { println("ERROR: [getAppId] Failed to get app ID: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get app ID") } } fun getDeviceGuid(callback: Callback) { try { val guid = smartech.getDeviceUniqueId() callbackHandler(callback, guid) } catch (e: Throwable) { println("ERROR: [getDeviceGuid] Failed to get device GUID: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get device GUID") } } fun getNetcoreUnbxdIdentity(callback: Callback) { try { val unbxdIdentity = smartech.getNetcoreUnbxdIdentity() callbackHandler(callback, unbxdIdentity) } catch (e: Throwable) { println("ERROR: [getNetcoreUnbxdIdentity] Failed to get Netcore Unbxd identity: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get device GUID") } } fun getPartnerParametersString(callback: Callback) { try { val partnerParameters = smartech.getPartnerParametersString() callbackHandler(callback, partnerParameters) } catch (e: Throwable) { println("ERROR: [getDeviceGuid] Failed to get Partner parameters: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get device GUID") } } fun getSDKVersion(callback: Callback) { try { val sdkVersion = smartech.getSDKVersion() callbackHandler(callback, sdkVersion) } catch (e: Throwable) { println("ERROR: [getSDKVersion] Failed to get SDK version: ${e.message}") e.printStackTrace() callbackHandlerError(callback, e.message ?: "Failed to get SDK version") } } fun getWidgetByName(name: String) { try { if (name.isEmpty()) { println("ERROR: [getWidgetByName] Widget name is empty - unable to retrieve widget. Please provide a valid widget name.") return } smartech.getWidgetByName(name) } catch (e: Throwable) { println("ERROR: [getWidgetByName] Failed to get widget by name '$name': ${e.message}") e.printStackTrace() } } fun getWidgetByNames(names: ReadableArray) { try { val stringList = mutableListOf() for (i in 0 until names.size()) { names.getString(i)?.let { stringList.add(it) } } if (stringList.isEmpty()) { println("ERROR: [getWidgetByNames] No valid widget names provided - unable to retrieve widgets.") return } // Convert to Array if needed val widgetNameArray = stringList.toTypedArray() smartech.getWidgetByNames(widgetNameArray) } catch (e: Throwable) { println("ERROR: [getWidgetByNames] Failed to get widgets by names: ${e.message}") e.printStackTrace() } } fun getAllWidgets() { try { smartech.getAllWidgets() } catch (e: Throwable) { println("ERROR: [getAllWidgets] Failed to get all widgets: ${e.message}") e.printStackTrace() } } fun getAllWidgetNames(callback: Callback) { try { val names = smartech.getAllWidgetNames() val writableArray = WritableNativeArray() names.forEach { name -> writableArray.pushString(name) } callbackHandler(callback, writableArray) } catch (e: Throwable) { println("ERROR: [getAllWidgetNames] Failed to get all widget names: ${e.message}") e.printStackTrace() } } override fun onDeeplinkIntentReceived(intent: Intent?) { try { val deeplinkPayload = processDeeplinkIntent(intent) println("SmartechBaseReactNativeImpl - Deeplink Payload: $deeplinkPayload") val isFromBg = if (deeplinkPayload.hasKey(SMT_IS_DEEPLINK_FROM_BG) && !deeplinkPayload.isNull(SMT_IS_DEEPLINK_FROM_BG) ) { deeplinkPayload.getBoolean(SMT_IS_DEEPLINK_FROM_BG) } else { false } println("SmartechBaseReactNativeImpl - SmtIsDeeplinkFromBg value: $isFromBg") if (isFromBg) { if (isSmartechDeeplinkInit) { sendDeeplinkCallback(deeplinkPayload) } else { val backgroundThread = Thread { handleDeeplinkInTerminatedState(START_DELAY_TIME, deeplinkPayload) } println("SmartechBaseReactNativeModule - Background thread started: ${backgroundThread.name}") backgroundThread.start() } } else { sendDeeplinkCallback(deeplinkPayload) } } catch (e: Throwable) { e.printStackTrace() } } private fun processDeeplinkIntent(intent: Intent?): ReadableMap { val smtData = WritableNativeMap() intent?.extras?.let { bundleExtra -> var smtDeeplinkSource = "" var smtDeeplink = "" var smtPayload = "" var smtCustomPayload = "" var isFromBg = false if (bundleExtra.containsKey(SMTBundleKeys.SMT_KEY_DEEPLINK_SOURCE)) { smtDeeplinkSource = bundleExtra.getString(SMTBundleKeys.SMT_KEY_DEEPLINK_SOURCE) ?: "" } else { Log.v(TAG, "does not have deeplink source.") } if (bundleExtra.containsKey(SMTBundleKeys.SMT_KEY_DEEPLINK)) { smtDeeplink = bundleExtra.getString(SMTBundleKeys.SMT_KEY_DEEPLINK) ?: "" } else { Log.v(TAG, "does not have deeplink path.") } if (bundleExtra.containsKey(SMTBundleKeys.SMT_KEY_PAYLOAD)) { smtPayload = bundleExtra.getString(SMTBundleKeys.SMT_KEY_PAYLOAD) ?: "" } else { Log.v(TAG, "does not have smt payload.") } if (bundleExtra.containsKey(SMTBundleKeys.SMT_KEY_CUSTOM_PAYLOAD)) { smtCustomPayload = bundleExtra.getString(SMTBundleKeys.SMT_KEY_CUSTOM_PAYLOAD) ?: "" } else { Log.v(TAG, "does not have custom payload.") } if (bundleExtra.containsKey(SMTBundleKeys.SMT_KEY_IS_DEEPLINK_FROM_BG)) { isFromBg = bundleExtra.getBoolean(SMTBundleKeys.SMT_KEY_IS_DEEPLINK_FROM_BG, false) } try { smtData.putString(SMT_DEEPLINK_SOURCE_IDENTIFIER, smtDeeplinkSource) smtData.putString(SMT_DEEPLINK_IDENTIFIER, smtDeeplink) val rMapPayload = if (smtPayload.isNotEmpty()) { jsonToWritableMap(JSONObject(smtPayload)) } else null smtData.putMap(SMT_PAYLOAD_IDENTIFIER, rMapPayload) val rMapCustomPayload = if (smtCustomPayload.isNotEmpty()) { jsonToWritableMap(JSONObject(smtCustomPayload)) } else null smtData.putMap(SMT_CUSTOM_PAYLOAD_IDENTIFIER, rMapCustomPayload) // Legacy support smtData.putString(SMARTECH_DEEPLINK_IDENTIFIER, smtDeeplink) smtData.putString(SMARTECH_CUSTOM_PAYLOAD_IDENTIFIER, smtCustomPayload) smtData.putBoolean(SMT_IS_DEEPLINK_FROM_BG, isFromBg) } catch (e: Throwable) { e.printStackTrace() } } return smtData } fun setDeeplinkInit() { try { isSmartechDeeplinkInit = true } catch (e: Throwable) { e.printStackTrace() } } fun sendWidgetData(hMapWidget: HashMap) { val data = getWidgetDataAsWritableMap(data = hMapWidget) try { println("CPZ : Sending widget data from Native to JS.") reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(SMARTECH_WIDGET, data) } catch (e: Throwable) { println("ERROR: [sendWidgetData] Failed to send widget data to React Native: ${e.message}") e.printStackTrace() } } private fun sendDeeplinkCallback(deeplinkPayload: ReadableMap) { try { reactContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(SMARTECH_DEEPLINK, deeplinkPayload) } catch (e: Throwable) { println("ERROR: [sendDeeplinkCallback] Failed to send deeplink callback to React Native: ${e.message}") e.printStackTrace() } } private fun handleDeeplinkInTerminatedState(delay: Long, deeplinkPayload: ReadableMap) { try { Handler(Looper.getMainLooper()).postDelayed({ try { Log.i( TAG, "Deeplink Handler - delay is: $delay seconds & isSmartechDeeplinkInit: $isSmartechDeeplinkInit" ) if (isSmartechDeeplinkInit) { sendDeeplinkCallback(deeplinkPayload) } else { if (delay < MAX_DELAY_TIME) { handleDeeplinkInTerminatedState(delay + DELAY_DIFF, deeplinkPayload) } else { Log.i(TAG, "Maximum retries reached. Invoke Deeplink callback") sendDeeplinkCallback(deeplinkPayload) } } } catch (e: Throwable) { e.printStackTrace() sendDeeplinkCallback(deeplinkPayload) } }, delay) } catch (e: Throwable) { e.printStackTrace() sendDeeplinkCallback(deeplinkPayload) } } // JSON conversion utilities private fun jsonToWritableMap(jsonObject: JSONObject?): WritableMap? { if (jsonObject == null) return null val writableMap = WritableNativeMap() val iterator = jsonObject.keys() if (!iterator.hasNext()) return null while (iterator.hasNext()) { val key = iterator.next() try { when (val value = jsonObject.get(key)) { null, JSONObject.NULL -> writableMap.putNull(key) is Boolean -> writableMap.putBoolean(key, value) is Int -> writableMap.putInt(key, value) is Double, is Long, is Float -> { writableMap.putDouble(key, value.toString().toDouble()) } is String -> writableMap.putString(key, value) is JSONObject -> writableMap.putMap(key, jsonToWritableMap(value)) is JSONArray -> writableMap.putArray(key, jsonArrayToWritableArray(value)) else -> { if (value.javaClass.isEnum) { writableMap.putString(key, value.toString()) } } } } catch (e: Throwable) { e.printStackTrace() } } return writableMap } private fun jsonArrayToWritableArray(jsonArray: JSONArray?): WritableArray? { if (jsonArray == null || jsonArray.length() <= 0) return null val writableArray = WritableNativeArray() for (i in 0 until jsonArray.length()) { try { when (val value = jsonArray.get(i)) { null, JSONObject.NULL -> writableArray.pushNull() is Boolean -> writableArray.pushBoolean(value) is Int -> writableArray.pushInt(value) is Double, is Long, is Float -> { writableArray.pushDouble(value.toString().toDouble()) } is String -> writableArray.pushString(value) is JSONObject -> writableArray.pushMap(jsonToWritableMap(value)) is JSONArray -> writableArray.pushArray(jsonArrayToWritableArray(value)) else -> { if (value.javaClass.isEnum) { writableArray.pushString(value.toString()) } } } } catch (e: Throwable) { e.printStackTrace() } } return writableArray } private fun callbackHandler(callback: Callback?, response: Any) { if (callback == null) { Log.i(TAG, "Callback is null.") return } try { callback.invoke(null, response) } catch (e: Throwable) { e.printStackTrace() } } private fun callbackHandlerError(callback: Callback?, errorMessage: String) { if (callback == null) { Log.i(TAG, "Callback is null.") return } Log.e(TAG, "Error occurred: $errorMessage") try { callback.invoke(errorMessage, null) // Error: error=errorMessage, result=null } catch (e: Throwable) { e.printStackTrace() } } private fun getWidgetDataAsWritableMap(data: HashMap): WritableMap { val writableMap = WritableNativeMap() try { data.forEach { (key, widget) -> widget?.let { val widgetMap = convertWidgetToWritableMap(it) writableMap.putMap(key, widgetMap) } } } catch (e: Exception) { e.printStackTrace() } return writableMap } private fun convertWidgetToWritableMap(widget: SMTWidget): WritableMap { val widgetMap = WritableNativeMap() try { widgetMap.putString("layoutType", widget.layoutType) widgetMap.putString("widgetName", widget.widgetName) widgetMap.putInt("widgetId", widget.widgetId) widgetMap.putInt("campaignId", widget.campaignId) widgetMap.putInt("audienceId", widget.audienceId) widgetMap.putString("contentId", widget.contentId) // Convert content widget.content?.let { content -> val contentMap = WritableNativeMap() contentMap.putString("title", content.title) contentMap.putString("message", content.message) contentMap.putString("mediaUrl", content.mediaUrl) contentMap.putString("deeplinkUrl", content.deeplinkUrl) contentMap.putString("backgroundColor", content.backgroundColor) // Convert background gradient val gradientArray = WritableNativeArray() content.backgroundGradient?.forEach { gradient -> gradientArray.pushString(gradient) } contentMap.putArray("backgroundGradient", gradientArray) // Convert action buttons val buttonsArray = WritableNativeArray() content.actionButtons?.forEach { button -> val buttonMap = WritableNativeMap() buttonMap.putString("actionName", button.actionName) buttonMap.putString("actionDeeplink", button.actionDeeplink) buttonMap.putInt("actionType", button.actionType) buttonMap.putString("cpText", button.cpText) buttonMap.putString("backgroundColor", button.backgroundColor) buttonMap.putString("textColor", button.textColor) buttonsArray.pushMap(buttonMap) } contentMap.putArray("actionButtons", buttonsArray) // Convert custom key-value params for content val contentCustomParams = WritableNativeMap() content.customKeyValueParams?.forEach { (key, value) -> contentCustomParams.putString(key, value.toString()) } contentMap.putMap("customKeyValueParams", contentCustomParams) widgetMap.putMap("content", contentMap) } // Convert GA params val gaParamsMap = WritableNativeMap() widget.gaParams?.forEach { (key, value) -> gaParamsMap.putString(key, value) } widgetMap.putMap("gaParams", gaParamsMap) // Convert widget-level custom key-value params val customParamsMap = WritableNativeMap() widget.customKeyValueParams?.forEach { (key, value) -> customParamsMap.putString(key, value.toString()) } widgetMap.putMap("customKeyValueParams", customParamsMap) } catch (e: Exception) { println("ERROR: [convertWidgetToWritableMap] Failed to convert SMTWidget to WritableMap: ${e.message}") } return widgetMap } fun trackWidgetAsViewed(widgetMap: ReadableMap?) { try { if (widgetMap == null) { println("ERROR: [trackWidgetAsViewed] Widget payload is null - unable to track widget view event. Please ensure a valid widget object is passed to this method.") return } val widget = convertReadableMapToSMTWidget(widgetMap) smartech.trackWidgetAsViewed(widget) } catch (e: Exception) { println("ERROR: [trackWidgetAsViewed] Failed to track widget click event - ${e.message}") e.printStackTrace() } } fun trackWidgetAsClicked(widgetMap: ReadableMap?) { try { if (widgetMap == null) { println("ERROR: [trackWidgetAsClicked] Widget payload is null - unable to track widget click event. Please ensure a valid widget object is passed to this method.") return } val widget = convertReadableMapToSMTWidget(widgetMap) smartech.trackWidgetAsClicked(widget) } catch (e: Exception) { println("ERROR: [trackWidgetAsClicked] Failed to track widget click event - ${e.message}") e.printStackTrace() } } private fun convertReadableMapToSMTWidget(widgetMap: ReadableMap): SMTWidget { val widget = SMTWidget() try { if (widgetMap.hasKey("campaignId") && !widgetMap.isNull("campaignId")) { widget.campaignId = widgetMap.getInt("campaignId") } if (widgetMap.hasKey("widgetName") && !widgetMap.isNull("widgetName")) { widget.widgetName = widgetMap.getString("widgetName") ?: "" } if (widgetMap.hasKey("audienceId") && !widgetMap.isNull("audienceId")) { widget.audienceId = widgetMap.getInt("audienceId") } if (widgetMap.hasKey("contentId") && !widgetMap.isNull("contentId")) { widget.contentId = widgetMap.getString("contentId") ?: "" } } catch (e: Exception) { println("ERROR: [convertReadableMapToSMTWidget] Failed to convert ReadableMap to SMTWidget: ${e.message}") e.printStackTrace() } return widget } override fun onWidgetsLoaded(data: HashMap) { println("CPZ : Widget data is received onWidgetsLoaded") sendWidgetData(data) } }