diff --git a/README.md b/README.md index dc20eab..4d2178e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Refer to the table below regarding the different campaigns that can be implement |---------------------|-----------------------------------------------------------------| | `gdpr: {}` | Used if your property runs a GDPR TCF or GDPR Standard campaign | | `usnat: {}` | Used if your property runs a U.S. Multi-State Privacy campaign | +| `preferences: {}` | Used if your property runs a Preferences campaign | ### Set up callbacks in instance of `SPConsentManager` diff --git a/ReactNativeCmp.podspec b/ReactNativeCmp.podspec index e20312e..0db9912 100644 --- a/ReactNativeCmp.podspec +++ b/ReactNativeCmp.podspec @@ -15,7 +15,7 @@ Pod::Spec.new do |s| s.platforms = { :ios => min_ios_version_supported } s.source = { :git => "https://github.com/SourcePointUSA/react-native-sourcepoint-cmp.git", :tag => "#{s.version}" } - s.dependency "ConsentViewController", "7.9.0" + s.dependency "ConsentViewController", "7.9.1" s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" s.private_header_files = "ios/**/*.h" diff --git a/android/build.gradle b/android/build.gradle index acbda97..622a223 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -74,8 +74,10 @@ def kotlin_version = getExtOrDefault("kotlinVersion") dependencies { implementation "com.facebook.react:react-android" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.6.1" - implementation "com.sourcepoint.cmplibrary:cmplibrary:7.13.0" + implementation "com.sourcepoint.cmplibrary:cmplibrary:7.14.1-beta2" } react { diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/RNSourcepointCmpTypes.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/RNSourcepointCmpTypes.kt index bee42e7..7a7a89d 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/RNSourcepointCmpTypes.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/RNSourcepointCmpTypes.kt @@ -3,6 +3,7 @@ package com.sourcepoint.reactnativecmp import com.facebook.react.bridge.ReadableMap import com.sourcepoint.cmplibrary.model.CampaignsEnv import com.sourcepoint.cmplibrary.model.exposed.ActionType +import com.sourcepoint.cmplibrary.model.exposed.ActionType.* import com.sourcepoint.cmplibrary.model.exposed.TargetingParam fun campaignsEnvFrom(rawValue: String?): CampaignsEnv? = @@ -14,7 +15,8 @@ fun campaignsEnvFrom(rawValue: String?): CampaignsEnv? = data class SPCampaign( val rawTargetingParam: ReadableMap?, - val supportLegacyUSPString: Boolean + val supportLegacyUSPString: Boolean, + val groupPmId: String? = null, ) { val targetingParams = rawTargetingParam?.toHashMap()?.map { TargetingParam(it.key, it.value.toString()) } ?: emptyList() } @@ -22,6 +24,7 @@ data class SPCampaign( data class SPCampaigns( val gdpr: SPCampaign?, val usnat: SPCampaign?, + val preferences: SPCampaign?, val environment: CampaignsEnv? ) @@ -31,24 +34,26 @@ enum class RNSourcepointActionType { companion object { fun from(spAction: ActionType): RNSourcepointActionType = when (spAction) { - ActionType.ACCEPT_ALL -> acceptAll - ActionType.REJECT_ALL -> rejectAll - ActionType.SHOW_OPTIONS -> showPrivacyManager - ActionType.SAVE_AND_EXIT -> saveAndExit - ActionType.MSG_CANCEL -> dismiss - ActionType.PM_DISMISS -> pmCancel + ACCEPT_ALL -> acceptAll + REJECT_ALL -> rejectAll + SHOW_OPTIONS -> showPrivacyManager + SAVE_AND_EXIT -> saveAndExit + MSG_CANCEL -> dismiss + PM_DISMISS -> pmCancel else -> unknown } } } fun ReadableMap.SPCampaign() = SPCampaign( - rawTargetingParam = this.getMap("targetingParams"), - supportLegacyUSPString = if(this.hasKey("supportLegacyUSPString")) this.getBoolean("supportLegacyUSPString") else false + rawTargetingParam = getMap("targetingParams"), + supportLegacyUSPString = if(hasKey("supportLegacyUSPString")) getBoolean("supportLegacyUSPString") else false, + groupPmId = getString("id") ) fun ReadableMap.SPCampaigns() = SPCampaigns( gdpr = this.getMap("gdpr")?.SPCampaign(), usnat = this.getMap("usnat")?.SPCampaign(), + preferences = this.getMap("preferences")?.SPCampaign(), environment = campaignsEnvFrom(rawValue = this.getString("environment")) ) diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt index 35a00ac..f41be37 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/ReactNativeCmpModule.kt @@ -15,7 +15,6 @@ import com.sourcepoint.cmplibrary.creation.ConfigOption import com.sourcepoint.cmplibrary.creation.SpConfigDataBuilder import com.sourcepoint.cmplibrary.creation.makeConsentLib import com.sourcepoint.cmplibrary.data.network.util.CampaignType -import com.sourcepoint.cmplibrary.model.CampaignsEnv import com.sourcepoint.cmplibrary.model.ConsentAction import com.sourcepoint.cmplibrary.model.exposed.SPConsents import com.sourcepoint.cmplibrary.util.clearAllData @@ -38,31 +37,29 @@ class ReactNativeCmpModule(reactContext: ReactApplicationContext) : NativeReactN override fun build( accountId: Double, propertyId: Double, - propertyName: String?, - campaigns: ReadableMap? + propertyName: String, + campaigns: ReadableMap ) { - val convertedCampaigns = campaigns?.SPCampaigns() ?: SPCampaigns( - gdpr = null, - usnat = null, - environment = CampaignsEnv.PUBLIC - ) - + val convertedCampaigns = campaigns.SPCampaigns() val config = SpConfigDataBuilder().apply { addAccountId(accountId.toInt()) - addPropertyName(propertyName ?: "") + addPropertyName(propertyName) addPropertyId(propertyId.toInt()) addMessageTimeout(30000) convertedCampaigns.gdpr?.let { - addCampaign(campaignType = CampaignType.GDPR, params = it.targetingParams, groupPmId = null) + addCampaign(campaignType = CampaignType.GDPR, params = it.targetingParams, groupPmId = it.groupPmId) } convertedCampaigns.usnat?.let { addCampaign( campaignType = CampaignType.USNAT, params = it.targetingParams, - groupPmId = null, + groupPmId = it.groupPmId, configParams = if(it.supportLegacyUSPString) setOf(ConfigOption.SUPPORT_LEGACY_USPSTRING) else emptySet() ) } + convertedCampaigns.preferences?.let { + addCampaign(campaignType = CampaignType.PREFERENCES, params = it.targetingParams, groupPmId = it.groupPmId) + } }.build() reactApplicationContext.currentActivity?.let { diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPPreferencesConsent.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPPreferencesConsent.kt new file mode 100644 index 0000000..7ab71b6 --- /dev/null +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPPreferencesConsent.kt @@ -0,0 +1,70 @@ +package com.sourcepoint.reactnativecmp.consents + +import com.facebook.react.bridge.Arguments.createArray +import com.facebook.react.bridge.Arguments.createMap +import com.facebook.react.bridge.ReadableMap +import com.sourcepoint.cmplibrary.model.exposed.PreferencesConsent + +data class RNSPPreferencesConsent( + val dateCreated: String?, + val uuid: String?, + val status: List, + val rejectedStatus: List +): RNMappable { + data class Status( + val categoryId: Int, + val channels: List, + val changed: Boolean?, + val dateConsented: String?, + val subType: String? + ): RNMappable { + data class Channel(val id: Int, val status: Boolean): RNMappable { + override fun toRN(): ReadableMap = createMap().apply { + putInt("id", id) + putBoolean("status", status) + } + } + + override fun toRN(): ReadableMap = createMap().apply { + putInt("categoryId", categoryId) + putArray("channels", createArray().apply { channels.forEach { pushMap(it.toRN()) } }) + changed?.let { putBoolean("changed", it) } + putString("dateConsented", dateConsented) + putString("subType", subType) + } + } + + constructor(preferences: PreferencesConsent) : this( + dateCreated = preferences.dateCreated?.toString(), + uuid = preferences.uuid, + status = preferences.status?.map { status -> + Status( + categoryId = status.categoryId, + channels = status.channels?.map { channel -> + Status.Channel(id = channel.id, status = channel.status) + } ?: emptyList(), + changed = status.changed, + dateConsented = status.dateConsented?.toString(), + subType = status.subType?.toString() + ) + } ?: emptyList(), + rejectedStatus = preferences.rejectedStatus?.map { status -> + Status( + categoryId = status.categoryId, + channels = status.channels?.map { channel -> + Status.Channel(id = channel.id, status = channel.status) + } ?: emptyList(), + changed = status.changed, + dateConsented = status.dateConsented?.toString(), + subType = status.subType?.toString() + ) + } ?: emptyList() + ) + + override fun toRN(): ReadableMap = createMap().apply { + putString("dateCreated", dateCreated) + putString("uuid", uuid) + putArray("status", createArray().apply { status.forEach { pushMap(it.toRN()) } }) + putArray("rejectedStatus", createArray().apply { rejectedStatus.forEach { pushMap(it.toRN()) } }) + } +} diff --git a/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPUserData.kt b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPUserData.kt index 52c0828..a57bb4c 100644 --- a/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPUserData.kt +++ b/android/src/main/java/com/sourcepoint/reactnativecmp/consents/RNSPUserData.kt @@ -1,7 +1,9 @@ package com.sourcepoint.reactnativecmp.consents +import com.facebook.react.bridge.Arguments.createArray import com.facebook.react.bridge.Arguments.createMap import com.facebook.react.bridge.ReadableMap +import com.sourcepoint.cmplibrary.model.exposed.PreferencesConsent import com.sourcepoint.cmplibrary.model.exposed.SPConsents interface RNMappable { @@ -10,15 +12,18 @@ interface RNMappable { data class RNSPUserData( val gdpr: RNSPGDPRConsent?, - val usnat: RNSPUSNatConsent? + val usnat: RNSPUSNatConsent?, + val preferences: RNSPPreferencesConsent? ): RNMappable { constructor(spData: SPConsents): this( gdpr = spData.gdpr?.let { RNSPGDPRConsent(gdpr = it.consent)}, - usnat = spData.usNat?.let { RNSPUSNatConsent(usnat = it.consent)} + usnat = spData.usNat?.let { RNSPUSNatConsent(usnat = it.consent)}, + preferences = spData.preferences?.let { RNSPPreferencesConsent(preferences = it.consent) } ) override fun toRN(): ReadableMap = createMap().apply { gdpr?.let { putMap("gdpr", it.toRN()) } usnat?.let { putMap("usnat", it.toRN()) } + preferences?.let { putMap("preferences", it.toRN()) } } } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 7e46adc..d13d881 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -108,14 +108,18 @@ android { proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } + compileOptions { + coreLibraryDesugaringEnabled = true + } } dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") - androidTestImplementation "androidx.test:core:1.5.0" + androidTestImplementation "androidx.test:core:1.6.1" androidTestImplementation('com.wix:detox:+') - implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.appcompat:appcompat:1.7.1' + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 940e109..e33948b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,8 +1,8 @@ PODS: - boost (1.84.0) - - ConsentViewController (7.9.0): + - ConsentViewController (7.9.1): - Down (~> 0.11.0) - - SPMobileCore (= 0.1.6) + - SPMobileCore (= 0.1.7) - DoubleConversion (1.1.6) - fast_float (6.1.4) - FBLazyVector (0.79.2) @@ -1660,7 +1660,7 @@ PODS: - React-perflogger (= 0.79.2) - React-utils (= 0.79.2) - ReactNativeCmp (1.0.0): - - ConsentViewController (= 7.9.0) + - ConsentViewController (= 7.9.1) - DoubleConversion - glog - hermes-engine @@ -1685,7 +1685,7 @@ PODS: - ReactCommon/turbomodule/core - Yoga - SocketRocket (0.7.1) - - SPMobileCore (0.1.6) + - SPMobileCore (0.1.7) - Yoga (0.0.0) DEPENDENCIES: @@ -1919,7 +1919,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - ConsentViewController: 7da0b7f3bd74e363485206c319520dfbe883a96c + ConsentViewController: cdd57de154538b2240285cdb5480d3e2fe067fa0 DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: 84b955f7b4da8b895faf5946f73748267347c975 @@ -1989,11 +1989,11 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 04d5eb15eb46be6720e17a4a7fa92940a776e584 ReactCodegen: c63eda03ba1d94353fb97b031fc84f75a0d125ba ReactCommon: 76d2dc87136d0a667678668b86f0fca0c16fdeb0 - ReactNativeCmp: 4bcdd0a68a21041dce201de86d75eb096dbb7043 + ReactNativeCmp: 176df14602bf3363e3fe2e1c4bef16ab8ad91a6f SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - SPMobileCore: 984461b7c9a6a0dd27542113b20739c21ca14e1c + SPMobileCore: 880a2689597578811af343a9626311d34b462a50 Yoga: c758bfb934100bb4bf9cbaccb52557cee35e8bdf -PODFILE CHECKSUM: 4c1a09dd81fa25367b522651dd339c901af5b344 +PODFILE CHECKSUM: decdc7519d77aa5eae65b167fa59bcfce25e15d2 COCOAPODS: 1.15.2 diff --git a/example/ios/ReactNativeCmpExample.xcodeproj/project.pbxproj b/example/ios/ReactNativeCmpExample.xcodeproj/project.pbxproj index 5ba6caa..b27fe0b 100644 --- a/example/ios/ReactNativeCmpExample.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeCmpExample.xcodeproj/project.pbxproj @@ -8,9 +8,9 @@ /* Begin PBXBuildFile section */ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 61D9D761CDC288DEE247261E /* libPods-ReactNativeCmpExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 211CB3AA9C2412621C003F1E /* libPods-ReactNativeCmpExample.a */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - CA03CB8852F78B93285CA723 /* libPods-ReactNativeCmpExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB5670AFF428DFA5A8384295 /* libPods-ReactNativeCmpExample.a */; }; E6E443B76B817E6505F1B1F9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; /* End PBXBuildFile section */ @@ -19,12 +19,12 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNativeCmpExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNativeCmpExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = ReactNativeCmpExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 31CCB622C214C12764CEAADE /* Pods-ReactNativeCmpExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeCmpExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample.debug.xcconfig"; sourceTree = ""; }; - 6DD6EB445154A2C15CE36466 /* Pods-ReactNativeCmpExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeCmpExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample.release.xcconfig"; sourceTree = ""; }; + 211CB3AA9C2412621C003F1E /* libPods-ReactNativeCmpExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeCmpExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4BD6E6926CA8A0C4AD0C9C0A /* Pods-ReactNativeCmpExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeCmpExample.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample.release.xcconfig"; sourceTree = ""; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = ReactNativeCmpExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNativeCmpExample/LaunchScreen.storyboard; sourceTree = ""; }; - BB5670AFF428DFA5A8384295 /* libPods-ReactNativeCmpExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeCmpExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + F51D9FF29768BE602B992311 /* Pods-ReactNativeCmpExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeCmpExample.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -32,7 +32,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - CA03CB8852F78B93285CA723 /* libPods-ReactNativeCmpExample.a in Frameworks */, + 61D9D761CDC288DEE247261E /* libPods-ReactNativeCmpExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -55,7 +55,7 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - BB5670AFF428DFA5A8384295 /* libPods-ReactNativeCmpExample.a */, + 211CB3AA9C2412621C003F1E /* libPods-ReactNativeCmpExample.a */, ); name = Frameworks; sourceTree = ""; @@ -92,8 +92,8 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 31CCB622C214C12764CEAADE /* Pods-ReactNativeCmpExample.debug.xcconfig */, - 6DD6EB445154A2C15CE36466 /* Pods-ReactNativeCmpExample.release.xcconfig */, + F51D9FF29768BE602B992311 /* Pods-ReactNativeCmpExample.debug.xcconfig */, + 4BD6E6926CA8A0C4AD0C9C0A /* Pods-ReactNativeCmpExample.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -105,13 +105,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNativeCmpExample" */; buildPhases = ( - E3579CAF557DCE756F92561E /* [CP] Check Pods Manifest.lock */, + 58E36A2ABFA7F25FB9F1862D /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 31C54422AA064C2BA4E6E77B /* [CP] Embed Pods Frameworks */, - C21966D3DFB7C73D825EE9A6 /* [CP] Copy Pods Resources */, + DA31EC456BC4FEF95753A61E /* [CP] Embed Pods Frameworks */, + EFF9C7DD88E2C850D8CBBF2B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -183,60 +183,60 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 31C54422AA064C2BA4E6E77B /* [CP] Embed Pods Frameworks */ = { + 58E36A2ABFA7F25FB9F1862D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNativeCmpExample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - C21966D3DFB7C73D825EE9A6 /* [CP] Copy Pods Resources */ = { + DA31EC456BC4FEF95753A61E /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E3579CAF557DCE756F92561E /* [CP] Check Pods Manifest.lock */ = { + EFF9C7DD88E2C850D8CBBF2B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-ReactNativeCmpExample-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNativeCmpExample/Pods-ReactNativeCmpExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -255,7 +255,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 31CCB622C214C12764CEAADE /* Pods-ReactNativeCmpExample.debug.xcconfig */; + baseConfigurationReference = F51D9FF29768BE602B992311 /* Pods-ReactNativeCmpExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -283,7 +283,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6DD6EB445154A2C15CE36466 /* Pods-ReactNativeCmpExample.release.xcconfig */; + baseConfigurationReference = 4BD6E6926CA8A0C4AD0C9C0A /* Pods-ReactNativeCmpExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/example/src/App.tsx b/example/src/App.tsx index c77b3aa..4a1870c 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -36,6 +36,7 @@ const config = { campaigns: { gdpr: {}, usnat: { supportLegacyUSPString: true }, + preferences: {}, environment: SPCampaignEnvironment.Public, } as SPCampaigns, ...launchArgs?.config, diff --git a/ios/RNSPUserData/RNSPPreferencesConsent.swift b/ios/RNSPUserData/RNSPPreferencesConsent.swift new file mode 100644 index 0000000..1065312 --- /dev/null +++ b/ios/RNSPUserData/RNSPPreferencesConsent.swift @@ -0,0 +1,66 @@ +import ConsentViewController + +struct RNSPPreferencesConsent: Encodable { + struct Status: Encodable { + let categoryId: Int + let channels: [Channel] + let changed: Bool? + let dateConsented: SPDate? + let subType: SubType? + } + + struct Channel: Encodable { + let id: Int + let status: Bool + } + + enum SubType: String, Encodable { + case AIPolicy, TermsAndConditions, PrivacyPolicy, LegalPolicy, TermsOfSale, Unknown + + init(from subType: SPPreferencesConsent.SubType) { + switch subType { + case .AIPolicy: self = .AIPolicy + case .TermsAndConditions: self = .TermsAndConditions + case .PrivacyPolicy: self = .PrivacyPolicy + case .LegalPolicy: self = .LegalPolicy + case .TermsOfSale: self = .TermsOfSale + default: self = .Unknown + } + } + } + + let dateCreated: SPDate + let uuid: String? + let status: [Status] + let rejectedStatus: [Status] +} + +extension RNSPPreferencesConsent.Status { + init(from status: SPPreferencesConsent.Status) { + categoryId = status.categoryId + channels = status.channels.map { RNSPPreferencesConsent.Channel(id: $0.id, status: $0.status) } + changed = status.changed + dateConsented = status.dateConsented + subType = status.subType.flatMap { RNSPPreferencesConsent.SubType(from: $0) } + } +} + +extension RNSPPreferencesConsent.Channel { + init(from channel: SPPreferencesConsent.Channel) { + id = channel.id + status = channel.status + } +} + +extension RNSPPreferencesConsent { + init?(from preferences: SPPreferencesConsent?) { + guard let preferences = preferences else { return nil } + + self.init( + dateCreated: preferences.dateCreated, + uuid: preferences.uuid, + status: preferences.status.map { Status(from: $0) }, + rejectedStatus: preferences.rejectedStatus.map { Status(from: $0) } + ) + } +} diff --git a/ios/RNSPUserData/RNSPUserData.swift b/ios/RNSPUserData/RNSPUserData.swift index d223da3..e9c72ed 100644 --- a/ios/RNSPUserData/RNSPUserData.swift +++ b/ios/RNSPUserData/RNSPUserData.swift @@ -11,13 +11,16 @@ import ConsentViewController struct RNSPUserData: Encodable { let gdpr: RNSPGDPRConsent? let usnat: RNSPUSNatConsent? + let preferences: RNSPPreferencesConsent? init( gdpr: RNSPGDPRConsent? = nil, - usnat: RNSPUSNatConsent? = nil + usnat: RNSPUSNatConsent? = nil, + preferences: RNSPPreferencesConsent? = nil ) { self.gdpr = gdpr self.usnat = usnat + self.preferences = preferences } } @@ -30,7 +33,8 @@ extension RNSPUserData { self.init( gdpr: RNSPGDPRConsent(from: sdkConsents.gdpr?.consents), - usnat: RNSPUSNatConsent(from: sdkConsents.usnat?.consents) + usnat: RNSPUSNatConsent(from: sdkConsents.usnat?.consents), + preferences: RNSPPreferencesConsent(from: sdkConsents.preferences?.consents) ) } } diff --git a/ios/ReactNativeCmp.mm b/ios/ReactNativeCmp.mm index 83fec6d..025d2ae 100644 --- a/ios/ReactNativeCmp.mm +++ b/ios/ReactNativeCmp.mm @@ -13,6 +13,7 @@ - (void)build:(double)accountId propertyId:(double)propertyId propertyName:(nonn RNSPCampaign *gdpr = nil; RNSPCampaign *usnat = nil; + RNSPCampaign *preferences = nil; if (campaigns.gdpr().has_value()) { auto gdprCampaign = campaigns.gdpr().value(); @@ -33,10 +34,20 @@ - (void)build:(double)accountId propertyId:(double)propertyId propertyName:(nonn supportLegacyUSPString:legacy]; } + if (campaigns.preferences().has_value()) { + auto preferencesCampaign = campaigns.preferences().value(); + NSDictionary *targetingParams = (NSDictionary *)preferencesCampaign.targetingParams(); + + preferences = [[RNSPCampaign alloc] initWithTargetingParams:targetingParams ?: @{} + groupPmId:nil + supportLegacyUSPString:false]; + } + RNSPCampaigns *internalCampaigns = [[RNSPCampaigns alloc] initWithGdpr: gdpr ccpa:nil usnat:usnat ios14:nil + preferences:preferences environment:RNSPCampaignEnvPublic]; [sdk diff --git a/ios/Wrappers/RNSPCampaigns.swift b/ios/Wrappers/RNSPCampaigns.swift index c017166..74a80de 100644 --- a/ios/Wrappers/RNSPCampaigns.swift +++ b/ios/Wrappers/RNSPCampaigns.swift @@ -38,7 +38,7 @@ import ConsentViewController } @objcMembers public class RNSPCampaigns: NSObject { - @objc public let gdpr, ccpa, usnat, ios14: RNSPCampaign? + @objc public let gdpr, ccpa, usnat, ios14, preferences: RNSPCampaign? @objc public let environment: RNSPCampaignEnv @objc public init( @@ -46,12 +46,14 @@ import ConsentViewController ccpa: RNSPCampaign? = nil, usnat: RNSPCampaign? = nil, ios14: RNSPCampaign? = nil, + preferences: RNSPCampaign? = nil, environment: RNSPCampaignEnv = .Public ) { self.gdpr = gdpr self.ccpa = ccpa self.usnat = usnat self.ios14 = ios14 + self.preferences = preferences self.environment = environment } @@ -61,6 +63,7 @@ import ConsentViewController ccpa: ccpa?.toSP(), usnat: usnat?.toSP(), ios14: ios14?.toSP(), + preferences: preferences?.toSP(), environment: environment.toSP() ) } diff --git a/src/NativeReactNativeCmp.ts b/src/NativeReactNativeCmp.ts index a294c8b..c6cee5c 100644 --- a/src/NativeReactNativeCmp.ts +++ b/src/NativeReactNativeCmp.ts @@ -25,6 +25,7 @@ export const enum SPActionType { export type SPCampaigns = { gdpr?: SPCampaign; usnat?: SPCampaign; + preferences?: SPCampaign; environment?: SPCampaignEnvironment; }; @@ -86,6 +87,7 @@ export type USNatConsent = { export type SPUserData = { gdpr?: GDPRConsent; usnat?: USNatConsent; + preferences?: PreferencesConsent; }; export type LoadMessageParams = { @@ -97,6 +99,34 @@ export type SPAction = { customActionId?: string; }; +export type PreferencesSubType = + | 'AIPolicy' + | 'TermsAndConditions' + | 'PrivacyPolicy' + | 'LegalPolicy' + | 'TermsOfSale' + | 'Unknown'; + +export type PreferencesChannel = { + id: number; + status: boolean; +}; + +export type PreferencesStatus = { + categoryId: number; + channels: PreferencesChannel[]; + changed?: boolean; + dateConsented?: string; + subType?: PreferencesSubType; +}; + +export type PreferencesConsent = { + dateCreated: string; + uuid?: string; + status: PreferencesStatus[]; + rejectedStatus: PreferencesStatus[]; +}; + export interface Spec extends TurboModule { build( accountId: number,