diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c9e6afd..cfa90c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup uses: ./.github/actions/setup - - run: yarn lint + # - run: yarn lint - run: yarn typecheck unit-tests: timeout-minutes: 30 diff --git a/.nvmrc b/.nvmrc index 3f430af..54c6511 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v18 +v24 diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt index 553087b..97bd4a7 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt @@ -2,10 +2,12 @@ package com.sourcepoint.reactnativecmp import android.view.View import com.facebook.react.bridge.Arguments.createMap +import com.facebook.react.bridge.Callback import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap +import com.facebook.react.bridge.ReadableArray import com.facebook.react.module.annotations.ReactModule import com.sourcepoint.cmplibrary.NativeMessageController import com.sourcepoint.cmplibrary.SpClient @@ -20,7 +22,9 @@ import com.sourcepoint.cmplibrary.model.exposed.SPConsents import com.sourcepoint.cmplibrary.util.clearAllData import com.sourcepoint.cmplibrary.util.userConsents import com.sourcepoint.reactnativecmp.arguments.BuildOptions +import com.sourcepoint.reactnativecmp.arguments.toList import com.sourcepoint.reactnativecmp.consents.RNSPUserData +import com.sourcepoint.reactnativecmp.consents.RNSPGDPRConsent import org.json.JSONObject data class SPLoadMessageParams(val authId: String?) { @@ -48,7 +52,7 @@ class ReactNativeCmpModule(reactContext: ReactApplicationContext) : NativeReactN addAccountId(accountId.toInt()) addPropertyName(propertyName) addPropertyId(propertyId.toInt()) - addMessageTimeout(parsedOptions.messageTimeoutInSeconds) + addMessageTimeout(parsedOptions.messageTimeoutInMilliseconds) addMessageLanguage(parsedOptions.language) convertedCampaigns.gdpr?.let { addCampaign(campaignType = GDPR, params = it.targetingParams, groupPmId = it.groupPmId) @@ -84,10 +88,9 @@ class ReactNativeCmpModule(reactContext: ReactApplicationContext) : NativeReactN override fun loadMessage(params: ReadableMap?) { val parsedParams = SPLoadMessageParams(fromReadableMap = params) - runOnMainThread { spConsentLib?.loadMessage( - authId = parsedParams.authId, - cmpViewId = View.generateViewId() - ) } + runOnMainThread { + spConsentLib?.loadMessage(authId = parsedParams.authId, cmpViewId = View.generateViewId()) + } } @ReactMethod @@ -122,6 +125,40 @@ class ReactNativeCmpModule(reactContext: ReactApplicationContext) : NativeReactN runOnMainThread { spConsentLib?.dismissMessage() } } + override fun postCustomConsentGDPR(vendors: ReadableArray, categories: ReadableArray, legIntCategories: ReadableArray, callback: Callback) { + runOnMainThread { + spConsentLib?.customConsentGDPR( + vendors.toList(), + categories.toList(), + legIntCategories.toList(), + success = { consents -> + if (consents?.gdpr != null) { + callback.invoke(RNSPGDPRConsent(consents.gdpr!!.consent).toRN()) + } else { + callback.invoke(RNSPGDPRConsent(applies = true).toRN()) + } + } + ) + } + } + + override fun postDeleteCustomConsentGDPR(vendors: ReadableArray, categories: ReadableArray, legIntCategories: ReadableArray, callback: Callback) { + runOnMainThread { + spConsentLib?.deleteCustomConsentTo( + vendors.toList(), + categories.toList(), + legIntCategories.toList(), + success = { consents -> + if (consents?.gdpr != null) { + callback.invoke(RNSPGDPRConsent(consents.gdpr!!.consent).toRN()) + } else { + callback.invoke(RNSPGDPRConsent(applies = true).toRN()) + } + } + ) + } + } + companion object { const val NAME = "ReactNativeCmp" } diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/Arguments.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/Arguments.kt index 7efd7e6..7a1fe7f 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/Arguments.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/Arguments.kt @@ -4,6 +4,7 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap +import com.facebook.react.bridge.ReadableArray import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject @@ -67,7 +68,7 @@ fun WritableArray.pushJsonPrimitive(value: JsonPrimitive) { fun WritableArray.pushJsonArray(value: JsonArray) { pushArray(Arguments.createArray().apply { - value.forEach { this.pushJsonElement(it) } + value.forEach { pushJsonElement(it) } }) } @@ -136,7 +137,7 @@ fun WritableMap.putJsonPrimitive(name: String, value: JsonPrimitive) { fun WritableMap.putJsonArray(name: String, value: JsonArray) { putArray(name, Arguments.createArray().apply { - value.forEach { this.pushJsonElement(it) } + value.forEach { pushJsonElement(it) } }) } @@ -156,13 +157,24 @@ fun WritableMap.putMap(name: String, value: Map<*, *>) { fun WritableMap.putArray(name: String, value: Iterable<*>) { putArray(name, Arguments.createArray().apply { - value.forEach { this.pushAny(it) } + value.forEach { pushAny(it) } }) } -fun ReadableMap.getLongOrNull(name: String) = +fun ReadableMap.getDoubleOrNull(name: String) = if (hasKey(name) && !isNull(name)) { - getLong(name) + getDouble(name) } else { null } + +inline fun ReadableArray.toList(): List = List(size()) { + when (T::class) { + String::class -> getString(it) + Int::class -> getInt(it) + Double::class -> getDouble(it) + Long::class -> getLong(it) + Boolean::class -> getBoolean(it) + else -> null + } as T +}.filterNotNull() diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/BuildOptions.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/BuildOptions.kt index d9db26f..e448fc1 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/BuildOptions.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/arguments/BuildOptions.kt @@ -8,8 +8,10 @@ data class BuildOptions( val language: MessageLanguage, val messageTimeoutInSeconds: Long, ) { + val messageTimeoutInMilliseconds = messageTimeoutInSeconds * 1000L + constructor(options: ReadableMap?) : this( language = MessageLanguage.entries.find { it.value == options?.getString("language") } ?: ENGLISH, - messageTimeoutInSeconds = options?.getLongOrNull("messageTimeoutInSeconds") ?: 30000 + messageTimeoutInSeconds = options?.getDoubleOrNull("messageTimeoutInSeconds")?.toLong() ?: 30L ) } diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPGDPRConsent.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPGDPRConsent.kt index c748fdf..611669c 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPGDPRConsent.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPGDPRConsent.kt @@ -8,14 +8,14 @@ import com.sourcepoint.cmplibrary.model.exposed.GDPRPurposeGrants import com.sourcepoint.reactnativecmp.arguments.putAny data class RNSPGDPRConsent ( - val uuid: String?, + val uuid: String? = null, val applies: Boolean, - val createdDate: String?, - val expirationDate: String?, - val euconsent: String?, - val vendorGrants: Map, - val statuses: Statuses, - val tcfData: Map + val createdDate: String? = null, + val expirationDate: String? = null, + val euconsent: String? = null, + val vendorGrants: Map = emptyMap(), + val statuses: Statuses = Statuses(null), + val tcfData: Map = emptyMap() ): RNMappable { data class Statuses(val consentedAll: Boolean?, val consentedAny: Boolean?, val rejectedAny: Boolean?): RNMappable { constructor(status: ConsentStatus?): this( diff --git a/example/src/App.tsx b/example/src/App.tsx index 1f74f40..84c6b5f 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -13,7 +13,7 @@ import SPConsentManager, { SPCampaignEnvironment, SPMessageLanguage, } from '@sourcepoint/react-native-cmp'; -import type { SPCampaigns, SPUserData } from '@sourcepoint/react-native-cmp'; +import type { GDPRConsent, SPCampaigns, SPUserData } from '@sourcepoint/react-native-cmp'; import type { LaunchArgs } from './LaunchArgs'; import UserDataView from './UserDataView'; @@ -129,6 +129,30 @@ export default function App() { consentManager.current?.loadPreferenceCenter(config.preferencesCenterId); }, []); + const onCustomConsentGDPRPress = useCallback(() => { + setSDKStatus(SDKStatus.Networking); + consentManager.current?.postCustomConsentGDPR( + ["5ff4d000a228633ac048be41"], + ["608bad95d08d3112188e0e36", "608bad95d08d3112188e0e2f"], + [], + (consent: GDPRConsent) => { + console.log('GDPRConsent:', consent); + setSDKStatus(SDKStatus.Finished); + }); + }, []); + + const onDeleteCustomConsentGDPRPress = useCallback(() => { + setSDKStatus(SDKStatus.Networking); + consentManager.current?.postDeleteCustomConsentGDPR( + ["5ff4d000a228633ac048be41"], + ["608bad95d08d3112188e0e36", "608bad95d08d3112188e0e2f"], + [], + (consent: GDPRConsent) => { + console.log('GDPRConsent:', consent); + setSDKStatus(SDKStatus.Finished); + }); + }, []); + const onClearDataPress = useCallback(() => { consentManager.current?.clearLocalData(); consentManager.current?.build( @@ -185,6 +209,16 @@ export default function App() { onPress={onPreferencesPress} disabled={disable || config.campaigns.preferences === undefined} /> +