package com.topon import android.app.Activity import android.text.TextUtils import com.secmtp.sdk.core.api.ATSDK import com.secmtp.sdk.core.api.NetTrafficeCallback import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.WritableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.modules.core.DeviceEventManagerModule import java.lang.reflect.Proxy import java.util.concurrent.atomic.AtomicBoolean import org.json.JSONObject @ReactModule(name = ToponModule.NAME) class ToponModule(reactContext: ReactApplicationContext) : NativeToponSpec(reactContext) { private val rewardedHelpers = mutableMapOf() private val interstitialHelpers = mutableMapOf() private val bannerHelpers = mutableMapOf() private val splashHelpers = mutableMapOf() private val nativeHelpers = mutableMapOf() private var listenerCount: Int = 0 override fun getName(): String = NAME internal fun currentActivitySafe(): Activity? = getCurrentActivity() internal fun reactContext(): ReactApplicationContext = reactApplicationContext override fun addListener(eventType: String) { listenerCount += 1 } override fun removeListeners(count: Double) { listenerCount = (listenerCount - count.toInt()).coerceAtLeast(0) } fun sendEvent(eventName: String, data: WritableMap) { if (listenerCount <= 0) { return } reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, data) } override fun init(appId: String, appKey: String) { MsgTools.printMsg("initSDK: $appId:$appKey") ATSDK.init(reactApplicationContext, appId, appKey) } override fun getSDKVersionName(promise: Promise) { val sdkVersionName = ATSDK.getSDKVersionName() MsgTools.printMsg("getSDKVersionName: $sdkVersionName") promise.resolve(sdkVersionName) } override fun isCnSDK(promise: Promise) { val isCn = ATSDK.isCnSDK() MsgTools.printMsg("isCnSDK: $isCn") promise.resolve(isCn) } override fun setExcludeMyOfferPkgList(packages: ReadableArray) { val packageList = packages.toArrayList().mapNotNull { it as? String } if (packageList.isNotEmpty()) { packageList.forEach { MsgTools.printMsg("exclude MyOffer: $it") } ATSDK.setExcludePackageList(packageList) } } override fun initCustomMap(customMapJson: String) { MsgTools.printMsg("initCustomMap: $customMapJson") if (!TextUtils.isEmpty(customMapJson)) { val map = CommonUtil.jsonStringToMap(customMapJson) ATSDK.initCustomMap(map) } } override fun setPlacementCustomMap(placementId: String, customMapJson: String) { MsgTools.printMsg("setPlacementCustomMap: $placementId:$customMapJson") if (!TextUtils.isEmpty(customMapJson)) { val map = CommonUtil.jsonStringToMap(customMapJson) ATSDK.initPlacementCustomMap(placementId, map) } } override fun setGDPRLevel(level: Double) { val gdprLevel = level.toInt() MsgTools.printMsg("setGDPRLevel: $gdprLevel") ATSDK.setGDPRUploadDataLevel(reactApplicationContext, gdprLevel) } override fun getGDPRLevel(promise: Promise) { val gdprLevel = ATSDK.getGDPRDataLevel(reactApplicationContext) MsgTools.printMsg("getGDPRLevel: $gdprLevel") promise.resolve(gdprLevel) } override fun getUserLocation(promise: Promise) { MsgTools.printMsg("getUserLocation") ATSDK.checkIsEuTraffic(reactApplicationContext, object : NetTrafficeCallback { override fun onResultCallback(result: Boolean) { MsgTools.printMsg("getUserLocation - onResultCallback: $result") promise.resolve(if (result) 1 else 2) } override fun onErrorCallback(error: String) { MsgTools.printMsg("getUserLocation - onErrorCallback: $error") promise.resolve(2) } }) } override fun showGDPRAuth() { MsgTools.printMsg("showGDPRAuth") currentActivitySafe()?.runOnUiThread { ATSDK.showGdprAuth(reactApplicationContext) } } override fun showGDPRConsentDialog(appId: String, promise: Promise) { MsgTools.printMsg("showGDPRConsentDialog") showGdprConsentDialogInternal(appId, false, promise) } override fun showGDPRConsentSecondDialog(appId: String, promise: Promise) { MsgTools.printMsg("showGDPRConsentSecondDialog") showGdprConsentDialogInternal(appId, true, promise) } override fun setUMPTestDeviceId(deviceId: String) { MsgTools.printMsg("setUMPTestDeviceId: $deviceId") val activity = currentActivitySafe() if (activity == null) { MsgTools.printMsg("setUMPTestDeviceId: no activity") return } activity.runOnUiThread { try { val builderClass = Class.forName("com.secmtp.sdk.core.api.ATDebuggerConfig\$Builder") val builder = builderClass.getDeclaredConstructor().newInstance() val setDeviceMethod = builderClass.getMethod("setUMPTestDeviceId", String::class.java) setDeviceMethod.invoke(builder, deviceId) val config = builderClass.getMethod("build").invoke(builder) val setConfigMethod = ATSDK::class.java.getMethod( "setDebuggerConfig", android.content.Context::class.java, String::class.java, Class.forName("com.secmtp.sdk.core.api.ATDebuggerConfig") ) setConfigMethod.invoke(null, activity, "", config) } catch (error: Exception) { MsgTools.printMsg("setUMPTestDeviceId error: ${error.message}") } } } private fun showGdprConsentDialogInternal( appId: String, isSecond: Boolean, promise: Promise ) { val activity = currentActivitySafe() if (activity == null) { promise.reject("NO_ACTIVITY", "Current activity is null.") return } val resolved = AtomicBoolean(false) activity.runOnUiThread { val methodName = if (isSecond) { "showGDPRConsentSecondDialog" } else { "showGDPRConsentDialog" } try { val method = ATSDK::class.java.methods.firstOrNull { candidate -> candidate.name == methodName && (candidate.parameterTypes.size == 2 || candidate.parameterTypes.size == 3) } if (method == null) { val authMethod = ATSDK::class.java.methods.firstOrNull { candidate -> candidate.name == "showGdprAuth" && candidate.parameterTypes.size == 2 } if (authMethod != null) { val parameterTypes = authMethod.parameterTypes val listenerIndex = parameterTypes.indexOfFirst { type -> type.methods.any { it.name == "onDismiss" } } if (listenerIndex != -1) { val listenerType = parameterTypes[listenerIndex] val listener = Proxy.newProxyInstance( listenerType.classLoader, arrayOf(listenerType) ) { proxy, callbackMethod, args -> when (callbackMethod.name) { "hashCode" -> System.identityHashCode(proxy) "equals" -> proxy === args?.getOrNull(0) "toString" -> "ToponConsentListenerProxy" "onDismiss" -> { val rawInfo = args?.firstOrNull() if (resolved.compareAndSet(false, true)) { promise.resolve(buildGdprConsentResult(rawInfo, true)) } defaultReturnValue(callbackMethod.returnType) } else -> defaultReturnValue(callbackMethod.returnType) } } val args = arrayOfNulls(parameterTypes.size) parameterTypes.forEachIndexed { index, type -> args[index] = when { index == listenerIndex -> listener Activity::class.java.isAssignableFrom(type) || android.content.Context::class.java.isAssignableFrom(type) -> activity else -> null } } authMethod.invoke(null, *args) return@runOnUiThread } } ATSDK.showGdprAuth(reactApplicationContext) if (resolved.compareAndSet(false, true)) { promise.resolve(buildGdprConsentResult(null, true)) } return@runOnUiThread } val parameterTypes = method.parameterTypes val listenerIndex = parameterTypes.indexOfFirst { type -> type.methods.any { it.name == "onDismiss" } } if (listenerIndex == -1) { ATSDK.showGdprAuth(reactApplicationContext) if (resolved.compareAndSet(false, true)) { promise.resolve(buildGdprConsentResult(null, true)) } return@runOnUiThread } val listenerType = parameterTypes[listenerIndex] val listener = Proxy.newProxyInstance( listenerType.classLoader, arrayOf(listenerType) ) { proxy, callbackMethod, args -> when (callbackMethod.name) { "hashCode" -> System.identityHashCode(proxy) "equals" -> proxy === args?.getOrNull(0) "toString" -> "ToponConsentListenerProxy" "onDismiss" -> { val rawInfo = args?.firstOrNull() if (resolved.compareAndSet(false, true)) { promise.resolve(buildGdprConsentResult(rawInfo, false)) } defaultReturnValue(callbackMethod.returnType) } else -> defaultReturnValue(callbackMethod.returnType) } } val args = arrayOfNulls(parameterTypes.size) parameterTypes.forEachIndexed { index, type -> args[index] = when { index == listenerIndex -> listener type == String::class.java -> appId Activity::class.java.isAssignableFrom(type) || android.content.Context::class.java.isAssignableFrom(type) -> activity else -> null } } method.invoke(null, *args) } catch (error: Throwable) { MsgTools.printMsg("showGdprConsentDialogInternal error: ${error.message}") try { ATSDK.showGdprAuth(reactApplicationContext) if (resolved.compareAndSet(false, true)) { promise.resolve(buildGdprConsentResult(null, true)) } } catch (fallbackError: Throwable) { if (resolved.compareAndSet(false, true)) { promise.reject("GDPR_CONSENT_ERROR", fallbackError) } } } } } private fun buildGdprConsentResult(rawInfo: Any?, fallback: Boolean): String { return try { val result = JSONObject() result.put("gdprLevel", ATSDK.getGDPRDataLevel(reactApplicationContext)) if (rawInfo != null) { result.put("raw", rawInfo.toString()) } if (fallback) { result.put("fallback", true) } result.toString() } catch (_: Exception) { "{}" } } private fun defaultReturnValue(returnType: Class<*>): Any? { if (!returnType.isPrimitive) { return null } return when (returnType) { java.lang.Boolean.TYPE -> false java.lang.Byte.TYPE -> 0.toByte() java.lang.Short.TYPE -> 0.toShort() java.lang.Integer.TYPE -> 0 java.lang.Long.TYPE -> 0L java.lang.Float.TYPE -> 0f java.lang.Double.TYPE -> 0.0 java.lang.Character.TYPE -> 0.toChar() java.lang.Void.TYPE -> null else -> null } } override fun setLogDebug(isDebug: Boolean) { MsgTools.setLogDebug(isDebug) MsgTools.printMsg("setLogDebug: $isDebug") ATSDK.setNetworkLogDebug(isDebug) } override fun deniedUploadDeviceInfo(keys: ReadableArray) { val keyList = keys.toArrayList().mapNotNull { it as? String } if (keyList.isNotEmpty()) { keyList.forEach { MsgTools.printMsg("deniedUploadDeviceInfo: $it") } ATSDK.deniedUploadDeviceInfo(*keyList.toTypedArray()) } } override fun rewardedLoadAd(placementId: String, settingsJson: String) { rewardedHelper(placementId).loadRewardedVideo(placementId, settingsJson) } override fun rewardedShowAd(placementId: String) { rewardedHelper(placementId).showVideo("") } override fun rewardedShowAdInScenario(placementId: String, scenario: String) { rewardedHelper(placementId).showVideo(scenario) } override fun rewardedHasAdReady(placementId: String, promise: Promise) { promise.resolve(rewardedHelper(placementId).isAdReady()) } override fun rewardedCheckAdStatus(placementId: String, promise: Promise) { promise.resolve(rewardedHelper(placementId).checkAdStatus()) } override fun interstitialLoadAd(placementId: String, settingsJson: String) { interstitialHelper(placementId).loadInterstitial(placementId, settingsJson) } override fun interstitialShowAd(placementId: String) { interstitialHelper(placementId).showInterstitial("") } override fun interstitialShowAdInScenario(placementId: String, scenario: String) { interstitialHelper(placementId).showInterstitial(scenario) } override fun interstitialHasAdReady(placementId: String, promise: Promise) { promise.resolve(interstitialHelper(placementId).isAdReady()) } override fun interstitialCheckAdStatus(placementId: String, promise: Promise) { promise.resolve(interstitialHelper(placementId).checkAdStatus()) } override fun bannerLoadAd(placementId: String, settingsJson: String) { bannerHelper(placementId).loadBanner(placementId, settingsJson) } override fun bannerShowAdInRectangle(placementId: String, rectJson: String) { bannerHelper(placementId).showBannerWithRect(rectJson, "") } override fun bannerShowAdInPosition(placementId: String, position: String) { bannerHelper(placementId).showBannerWithPosition(position, "") } override fun bannerShowAdInRectangleAndScenario( placementId: String, rectJson: String, scenario: String ) { bannerHelper(placementId).showBannerWithRect(rectJson, scenario) } override fun bannerShowAdInPositionAndScenario( placementId: String, position: String, scenario: String ) { bannerHelper(placementId).showBannerWithPosition(position, scenario) } override fun bannerHideAd(placementId: String) { bannerHelper(placementId).hideBanner() } override fun bannerReShowAd(placementId: String) { bannerHelper(placementId).reshowBanner() } override fun bannerRemoveAd(placementId: String) { bannerHelper(placementId).removeBanner() } override fun bannerHasAdReady(placementId: String, promise: Promise) { promise.resolve(bannerHelper(placementId).isAdReady()) } override fun bannerCheckAdStatus(placementId: String, promise: Promise) { promise.resolve(bannerHelper(placementId).checkAdStatus()) } override fun splashLoadAd(placementId: String, settingsJson: String) { splashHelper(placementId).loadSplash(placementId, settingsJson) } override fun splashShowAd(placementId: String) { splashHelper(placementId).showSplash("") } override fun splashShowAdInScenario(placementId: String, scenario: String) { splashHelper(placementId).showSplash(scenario) } override fun splashHasAdReady(placementId: String, promise: Promise) { promise.resolve(splashHelper(placementId).isAdReady()) } override fun splashCheckAdStatus(placementId: String, promise: Promise) { promise.resolve(splashHelper(placementId).checkAdStatus()) } override fun nativeLoadAd(placementId: String, settingsJson: String) { nativeHelper(placementId).loadNative(placementId, settingsJson) } override fun nativeShowAd(placementId: String, viewTagsJson: String) { nativeHelper(placementId).showNative(viewTagsJson, "") } override fun nativeShowAdInScenario( placementId: String, viewTagsJson: String, scenario: String ) { nativeHelper(placementId).showNative(viewTagsJson, scenario) } override fun nativeHasAdReady(placementId: String, promise: Promise) { promise.resolve(nativeHelper(placementId).isAdReady()) } override fun nativeCheckAdStatus(placementId: String, promise: Promise) { promise.resolve(nativeHelper(placementId).checkAdStatus()) } override fun nativeGetAdMaterial(placementId: String, promise: Promise) { promise.resolve(nativeHelper(placementId).getAdMaterial()) } override fun nativeRemoveAd(placementId: String) { nativeHelper(placementId).removeNative() } private fun rewardedHelper(placementId: String): RewardedVideoHelper = rewardedHelpers.getOrPut(placementId) { RewardedVideoHelper(this) } private fun interstitialHelper(placementId: String): InterstitialHelper = interstitialHelpers.getOrPut(placementId) { InterstitialHelper(this) } private fun bannerHelper(placementId: String): BannerHelper = bannerHelpers.getOrPut(placementId) { BannerHelper(this) } private fun splashHelper(placementId: String): SplashHelper = splashHelpers.getOrPut(placementId) { SplashHelper(this) } private fun nativeHelper(placementId: String): NativeAdHelper = nativeHelpers.getOrPut(placementId) { NativeAdHelper(this) } companion object { const val NAME = "Topon" } }