Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
package com.usercentrics.reactnative

import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.usercentrics.sdk.UsercentricsDisposableEvent
import com.usercentrics.sdk.UsercentricsEvent
import com.usercentrics.reactnative.api.UsercentricsProxy
import com.usercentrics.reactnative.extensions.*
import com.usercentrics.sdk.UsercentricsAnalyticsEventType
import com.usercentrics.sdk.models.settings.UsercentricsConsentType
import com.usercentrics.sdk.services.gpp.GppSectionChangePayload
import com.usercentrics.sdk.services.tcf.TCFDecisionUILayer

internal class RNUsercentricsModule(
reactContext: ReactApplicationContext,
private val usercentricsProxy: UsercentricsProxy,
private val reactContextProvider: ReactContextProvider,
) : RNUsercentricsModuleSpec(reactContext) {
private var gppSectionChangeSubscription: UsercentricsDisposableEvent<GppSectionChangePayload>? = null
private var gppSectionChangeListenersCount = 0

override fun getName() = NAME

Expand Down Expand Up @@ -121,6 +127,22 @@ internal class RNUsercentricsModule(
promise.resolve(usercentricsProxy.instance.getUSPData().serialize())
}

@ReactMethod
override fun getGPPData(promise: Promise) {
promise.resolve(usercentricsProxy.instance.getGPPData().serializeGppData())
}

@ReactMethod
override fun getGPPString(promise: Promise) {
promise.resolve(usercentricsProxy.instance.getGPPString())
}

@ReactMethod
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
val parsedValue = readableMapValueToAny(value) ?: return
usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
Comment on lines +141 to +143
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't turn { value: null } into a silent no-op.

Line 142 collapses both “missing key” and explicit null into return. The JS API accepts unknown, and ios/RNUsercentricsModule.swift forwards the wrapped null value instead of swallowing it, so Android currently behaves differently for callers trying to clear a GPP field.

🐛 Proposed fix
     override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
-        val parsedValue = readableMapValueToAny(value) ?: return
-        usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
+        if (!value.hasKey("value")) return
+        val parsedValue = readableMapValueToAny(value)
+        usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
val parsedValue = readableMapValueToAny(value) ?: return
usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
if (!value.hasKey("value")) return
val parsedValue = readableMapValueToAny(value)
usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt`
around lines 141 - 143, The current setGPPConsent implementation treats both a
missing "value" key and an explicit null value as a no-op because
readableMapValueToAny() returning null causes an early return; change
setGPPConsent to distinguish "no key" from "key present with null" by checking
the ReadableMap for the "value" key instead of simply returning on parsedValue
== null, and when the key is present but null forward an explicit null
placeholder to usercentricsProxy.instance.setGPPConsent (so
readableMapValueToAny, setGPPConsent, and the call to
usercentricsProxy.instance.setGPPConsent are adjusted to propagate an explicit
null value rather than swallowing it).

}
Comment on lines +140 to +144
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: When the JS layer calls the GPP consent API with an explicit null value (e.g. to clear a field), the Android bridge silently returns early because readableMapValueToAny returns null and ?: return short-circuits, so the underlying SDK never receives the update, unlike the iOS implementation which forwards an NSNull(); this leads to inconsistent behavior and prevents clearing GPP values on Android. [logic error]

Severity Level: Major ⚠️
- ⚠️ Android `Usercentrics.setGPPConsent` ignores explicit null values.
- ⚠️ GPP consent clearing inconsistent between iOS and Android bridges.
- ⚠️ Android GPP state may not reset when apps expect.
- ⚠️ Public API in `src/Usercentrics.tsx` behaves differently per platform.
Suggested change
@ReactMethod
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
val parsedValue = readableMapValueToAny(value) ?: return
usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
}
@ReactMethod
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
if (!value.hasKey("value")) return
val parsedValue = if (value.getType("value") == ReadableType.Null) {
null
} else {
readableMapValueToAny(value)
}
usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
}
Steps of Reproduction ✅
1. From JS, call the public API `Usercentrics.setGPPConsent("usnat", "SaleOptOut", null)`
(defined in `src/Usercentrics.tsx:150-153`, which wraps the value into `{ value }` and
calls `RNUsercentricsModule.setGPPConsent`).

2. On Android, React Native bridges this call into
`RNUsercentricsModule.setGPPConsent(sectionName, fieldName, value: ReadableMap)` in
`android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt:140-144`,
where `value` is a `ReadableMap` containing key `"value"` with type `ReadableType.Null`.

3. Inside `setGPPConsent`, the helper `readableMapValueToAny` at
`RNUsercentricsModule.kt:275-284` is invoked; because the `"value"` key is present and its
type is `ReadableType.Null`, this function returns `null`, causing the Elvis operator `?:
return` at line 142 to short‑circuit and exit `setGPPConsent` without calling
`usercentricsProxy.instance.setGPPConsent(...)`.

4. On iOS, the same JS call is bridged via `ios/RNUsercentricsModule.swift:157-160`, where
`setGPPConsent(_:fieldName:value:)` unwraps `value["value"] ?? NSNull()` and *always*
calls `usercentricsManager.setGPPConsent(...)`, passing `NSNull()` when the JS value is
null, so the GPP consent update/clear is applied on iOS but is a no‑op on Android.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
**Line:** 140:144
**Comment:**
	*Logic Error: When the JS layer calls the GPP consent API with an explicit `null` value (e.g. to clear a field), the Android bridge silently returns early because `readableMapValueToAny` returns `null` and `?: return` short-circuits, so the underlying SDK never receives the update, unlike the iOS implementation which forwards an `NSNull()`; this leads to inconsistent behavior and prevents clearing GPP values on Android.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
👍 | 👎

Comment on lines +141 to +144

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Null gpp consent ignored 🐞 Bug ✓ Correctness

Android RNUsercentricsModule.setGPPConsent silently no-ops when JS passes a null value because
readableMapValueToAny returns null and the method returns early. This prevents clearing a GPP field
on Android and diverges from iOS which forwards the null (as NSNull).
Agent Prompt
### Issue description
Android `setGPPConsent` drops updates when the JS wrapper contains `{ value: null }`, because the code returns early on `null`.

### Issue Context
iOS forwards the wrapped value even when it is null (`NSNull()`), so Android/iOS behavior diverges and Android cannot clear a GPP field.

### Fix Focus Areas
- android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[141-144]
- android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[275-284]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


@ReactMethod
override fun changeLanguage(language: String, promise: Promise) {
usercentricsProxy.instance.changeLanguage(language, {
Expand Down Expand Up @@ -216,11 +238,73 @@ internal class RNUsercentricsModule(
})
}

@ReactMethod
override fun addListener(eventName: String) {
if (eventName != ON_GPP_SECTION_CHANGE_EVENT) return

gppSectionChangeListenersCount++
if (gppSectionChangeSubscription != null) return

gppSectionChangeSubscription = UsercentricsEvent.onGppSectionChange { payload ->
emitEvent(ON_GPP_SECTION_CHANGE_EVENT, payload.serializeGppPayload())
}
}

@ReactMethod
override fun removeListeners(count: Double) {
gppSectionChangeListenersCount = (gppSectionChangeListenersCount - count.toInt()).coerceAtLeast(0)
if (gppSectionChangeListenersCount == 0) {
gppSectionChangeSubscription?.dispose()
gppSectionChangeSubscription = null
}
}

override fun invalidate() {
gppSectionChangeSubscription?.dispose()
gppSectionChangeSubscription = null
gppSectionChangeListenersCount = 0
super.invalidate()
}

private fun emitEvent(eventName: String, payload: WritableMap) {
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, payload)
}

private fun readableMapValueToAny(map: ReadableMap): Any? {
if (!map.hasKey("value")) return null
return when (map.getType("value")) {
ReadableType.Null -> null
ReadableType.Boolean -> map.getBoolean("value")
ReadableType.Number -> normalizeNumber(map.getDouble("value"))
ReadableType.String -> map.getString("value")
ReadableType.Map -> normalizeCompositeValue(map.getMap("value")?.toHashMap())
ReadableType.Array -> normalizeCompositeValue(map.getArray("value")?.toArrayList())
}
}

private fun normalizeCompositeValue(value: Any?): Any? {
return when (value) {
is Double -> normalizeNumber(value)
is ArrayList<*> -> value.map { normalizeCompositeValue(it) }
is HashMap<*, *> -> value.entries.associate { (key, nestedValue) ->
key.toString() to normalizeCompositeValue(nestedValue)
}
else -> value
}
}

private fun normalizeNumber(value: Double): Any {
return if (value % 1.0 == 0.0) value.toInt() else value
}
Comment on lines +275 to +300
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[VALIDATION] readableMapValueToAny currently expects the incoming ReadableMap to have a 'value' key and silently returns when missing. This makes setGPPConsent a no-op if callers don't wrap the primitive in { value }. Consider: (a) accepting primitive values directly (overload or document expected shape), (b) returning an explicit error/log if 'value' key is missing so the caller learns why nothing happened, or (c) add input validation and a Promise rejection/throw for malformed input to avoid silently ignoring calls.

@ReactMethod
override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
    if (!value.hasKey("value")) {
        // Option A: surface an explicit error so JS can react
        throw IllegalArgumentException("setGPPConsent: 'value' key is missing from payload. Expected shape: { value: any }")
        // or, if you prefer non-throwing behavior, log and return:
        // Log.w(NAME, "setGPPConsent called without 'value' key. Expected shape: { value: any }")
        // return
    }

    val parsedValue = readableMapValueToAny(value)
    usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
}


private fun runOnUiThread(block: () -> Unit) {
UiThreadUtil.runOnUiThread(block)
}

companion object {
const val NAME = "RNUsercentricsModule"
const val ON_GPP_SECTION_CHANGE_EVENT = "onGppSectionChange"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ abstract class RNUsercentricsModuleSpec internal constructor(context: ReactAppli
@ReactMethod
abstract fun getUSPData(promise: Promise)

@ReactMethod
abstract fun getGPPData(promise: Promise)

@ReactMethod
abstract fun getGPPString(promise: Promise)

@ReactMethod
abstract fun getABTestingVariant(promise: Promise)

Expand All @@ -60,6 +66,9 @@ abstract class RNUsercentricsModuleSpec internal constructor(context: ReactAppli
@ReactMethod
abstract fun setABTestingVariant(variant: String)

@ReactMethod
abstract fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap)

@ReactMethod
abstract fun changeLanguage(language: String, promise: Promise)

Expand Down Expand Up @@ -93,6 +102,12 @@ abstract class RNUsercentricsModuleSpec internal constructor(context: ReactAppli
@ReactMethod
abstract fun track(event: Double)

@ReactMethod
abstract fun addListener(eventName: String)

@ReactMethod
abstract fun removeListeners(count: Double)

companion object {
const val NAME = "RNUsercentricsModule"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.usercentrics.reactnative.extensions

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.usercentrics.sdk.services.gpp.GppData
import com.usercentrics.sdk.services.gpp.GppSectionChangePayload

internal fun GppData.serializeGppData(): WritableMap {
val sectionsMap = Arguments.createMap()
sections.forEach { (sectionName, fields) ->
val fieldsMap = Arguments.createMap()
fields.forEach { (fieldName, value) ->
when (value) {
null -> fieldsMap.putNull(fieldName)
is Boolean -> fieldsMap.putBoolean(fieldName, value)
is Int -> fieldsMap.putInt(fieldName, value)
is Double -> fieldsMap.putDouble(fieldName, value)
is String -> fieldsMap.putString(fieldName, value)
else -> fieldsMap.putString(fieldName, value.toString())
}
Comment on lines +10 to +20

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Gpp composite values lost 🐞 Bug ✓ Correctness

Android getGPPData serialization stringifies any non-primitive section field values, losing
structure for arrays/maps. Because Android setGPPConsent explicitly supports Map/Array inputs,
setting composite values cannot round-trip back through getGPPData.
Agent Prompt
### Issue description
`GppData.serializeGppData()` stringifies non-primitive values, so composite consent values (arrays/maps) cannot be round-tripped.

### Issue Context
The same module supports setting composite values via `setGPPConsent` (ReadableType.Map/Array), so `getGPPData` should preserve these structures.

### Fix Focus Areas
- android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt[12-20]
- android/src/main/java/com/usercentrics/reactnative/extensions/ReadableMapExtensions.kt[32-116]
- android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[277-296]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

}
sectionsMap.putMap(sectionName, fieldsMap)
}

val result = Arguments.createMap()
result.putString("gppString", gppString)
result.putArray("applicableSections", applicableSections.serialize())
result.putMap("sections", sectionsMap)
return result
Comment on lines +8 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[REFACTORING] Current serialization handles only Int/Double/Boolean/String and falls back to toString() for other numeric types or nested structures. Improve robustness by: (1) treating all numeric types via 'is Number' and mapping integers vs floats appropriately (e.g. detect integral numbers and use putInt, otherwise putDouble), (2) recursively serializing nested maps/arrays into WritableMap/WritableArray (instead of toString()) so complex section values are preserved, and (3) explicitly handling Long/Float/Short types that may appear in the SDK.

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.usercentrics.sdk.services.gpp.GppData
import com.usercentrics.sdk.services.gpp.GppSectionChangePayload

private fun Any?.toWritableValue(): Any? = when (this) {
    null -> null
    is Boolean -> this
    is Number -> {
        // Preserve integers as Int, others as Double
        val doubleValue = this.toDouble()
        if (doubleValue % 1.0 == 0.0 && doubleValue <= Int.MAX_VALUE && doubleValue >= Int.MIN_VALUE) {
            doubleValue.toInt()
        } else {
            doubleValue
        }
    }
    is Map<*, *> -> this.toWritableMap()
    is Iterable<*> -> this.toWritableArray()
    else -> this.toString()
}

private fun Map<*, *>.toWritableMap(): WritableMap {
    val map = Arguments.createMap()
    for ((key, value) in this) {
        val name = key?.toString() ?: continue
        when (val v = value.toWritableValue()) {
            null -> map.putNull(name)
            is Boolean -> map.putBoolean(name, v)
            is Int -> map.putInt(name, v)
            is Double -> map.putDouble(name, v)
            is String -> map.putString(name, v)
            is WritableMap -> map.putMap(name, v)
            is WritableArray -> map.putArray(name, v)
            else -> map.putString(name, v.toString())
        }
    }
    return map
}

private fun Iterable<*>.toWritableArray(): WritableArray {
    val array = Arguments.createArray()
    for (item in this) {
        when (val v = item.toWritableValue()) {
            null -> array.pushNull()
            is Boolean -> array.pushBoolean(v)
            is Int -> array.pushInt(v)
            is Double -> array.pushDouble(v)
            is String -> array.pushString(v)
            is WritableMap -> array.pushMap(v)
            is WritableArray -> array.pushArray(v)
            else -> array.pushString(v.toString())
        }
    }
    return array
}

internal fun GppData.serializeGppData(): WritableMap {
    val sectionsMap = Arguments.createMap()
    sections.forEach { (sectionName, fields) ->
        val fieldsMap = (fields as? Map<*, *>)?.toWritableMap() ?: Arguments.createMap()
        sectionsMap.putMap(sectionName, fieldsMap)
    }

    return Arguments.createMap().apply {
        putString("gppString", gppString)
        putArray("applicableSections", applicableSections.serialize())
        putMap("sections", sectionsMap)
    }
}

internal fun GppSectionChangePayload.serializeGppPayload(): WritableMap {
    return Arguments.createMap().apply {
        putString("data", data)
    }
}

}

internal fun GppSectionChangePayload.serializeGppPayload(): WritableMap {
val result = Arguments.createMap()
result.putString("data", data)
return result
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,6 @@ private fun TCF2Settings.serialize(): WritableMap {
"disabledSpecialFeatures" to disabledSpecialFeatures,
"firstLayerShowDescriptions" to firstLayerShowDescriptions,
"hideNonIabOnFirstLayer" to hideNonIabOnFirstLayer,
"resurfacePeriodEnded" to resurfacePeriodEnded,
"resurfacePurposeChanged" to resurfacePurposeChanged,
"resurfaceVendorAdded" to resurfaceVendorAdded,
Comment on lines 219 to 223

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Missing resurfaceperiodended key 🐞 Bug ✓ Correctness

Android TCF2Settings.serialize no longer includes resurfacePeriodEnded in the object returned to JS.
The TypeScript TCF2Settings model still requires this field and iOS continues to provide it, causing
Android/iOS payload divergence and missing data on Android.
Agent Prompt
### Issue description
Android `TCF2Settings.serialize()` omits the `resurfacePeriodEnded` field, breaking the TS model contract and iOS parity.

### Issue Context
TS `TCF2Settings` requires `resurfacePeriodEnded: boolean` and iOS still serializes it.

### Fix Focus Areas
- android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt[184-245]
- src/models/TCF2Settings.tsx[30-55]
- ios/Extensions/UsercentricsCMPData+Dict.swift[204-214]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

"firstLayerDescription" to firstLayerDescription,
Expand Down
25 changes: 25 additions & 0 deletions example/ios/exampleTests/Fake/FakeUsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,31 @@ final class FakeUsercentricsManager: UsercentricsManager {
return getUSPDataResponse!
}

var getGPPDataResponse: GppData?
func getGPPData() -> GppData {
return getGPPDataResponse!
}

var getGPPStringResponse: String?
func getGPPString() -> String? {
return getGPPStringResponse
}

var setGPPConsentSectionName: String?
var setGPPConsentFieldName: String?
var setGPPConsentValue: Any?
func setGPPConsent(sectionName: String, fieldName: String, value: Any) {
self.setGPPConsentSectionName = sectionName
self.setGPPConsentFieldName = fieldName
self.setGPPConsentValue = value
}

var gppSectionChangeDisposableEvent = UsercentricsDisposableEvent<GppSectionChangePayload>()
func onGppSectionChange(callback: @escaping (GppSectionChangePayload) -> Void) -> UsercentricsDisposableEvent<GppSectionChangePayload> {
gppSectionChangeDisposableEvent.callback = callback
return gppSectionChangeDisposableEvent
}

var getTCFDataResponse: TCFData?
func getTCFData(callback: @escaping (TCFData) -> Void) {
callback(getTCFDataResponse!)
Expand Down
12 changes: 12 additions & 0 deletions ios/Extensions/GppData+Dict.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation
import Usercentrics

extension GppData {
func toDictionary() -> NSDictionary {
return [
"gppString": self.gppString,
"applicableSections": self.applicableSections,
"sections": self.sections,
]
}
Comment on lines +4 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[REFACTORING] toDictionary returns Swift collections directly — ensure values are converted to Foundation types expected by React bridge (NSArray/NSDictionary) and that nested values are serialized safely (numbers, booleans, nested maps/arrays). Prefer mapping applicableSections -> NSArray and sections -> NSDictionary by explicitly transforming elements to Foundation types to avoid unexpected bridging behavior at runtime.

import Foundation
import Usercentrics

extension GppData {
    func toDictionary() -> NSDictionary {
        return [
            "gppString": gppString as NSString,
            "applicableSections": applicableSections as NSArray,
            "sections": sections as NSDictionary
        ]
    }
}

}
10 changes: 10 additions & 0 deletions ios/Extensions/GppSectionChangePayload+Dict.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation
import Usercentrics

extension GppSectionChangePayload {
func toDictionary() -> NSDictionary {
return [
"data": self.data
]
}
}
1 change: 0 additions & 1 deletion ios/Extensions/UsercentricsCMPData+Dict.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ extension TCF2Settings {
"disabledSpecialFeatures" : self.disabledSpecialFeatures,
"firstLayerShowDescriptions" : self.firstLayerShowDescriptions,
"hideNonIabOnFirstLayer" : self.hideNonIabOnFirstLayer,
"resurfacePeriodEnded" : self.resurfacePeriodEnded,
"resurfacePurposeChanged" : self.resurfacePurposeChanged,
"resurfaceVendorAdded" : self.resurfaceVendorAdded,
"firstLayerDescription" : self.firstLayerDescription as Any,
Expand Down
20 changes: 20 additions & 0 deletions ios/Manager/UsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ public protocol UsercentricsManager {
func getCMPData() -> UsercentricsCMPData
func getUserSessionData() -> String
func getUSPData() -> CCPAData
func getGPPData() -> GppData
func getGPPString() -> String?
func setGPPConsent(sectionName: String, fieldName: String, value: Any)
func getTCFData(callback: @escaping (TCFData) -> Void)
func getABTestingVariant() -> String?
func getAdditionalConsentModeData() -> AdditionalConsentModeData
func onGppSectionChange(callback: @escaping (GppSectionChangePayload) -> Void) -> UsercentricsDisposableEvent<GppSectionChangePayload>

func changeLanguage(language: String, onSuccess: @escaping (() -> Void), onFailure: @escaping ((Error) -> Void))

Expand Down Expand Up @@ -92,6 +96,22 @@ final class UsercentricsManagerImplementation: UsercentricsManager {
return UsercentricsCore.shared.getUSPData()
}

func getGPPData() -> GppData {
return UsercentricsCore.shared.getGPPData()
}

func getGPPString() -> String? {
return UsercentricsCore.shared.getGPPString()
}

func setGPPConsent(sectionName: String, fieldName: String, value: Any) {
UsercentricsCore.shared.setGPPConsent(sectionName: sectionName, fieldName: fieldName, value: value)
}

func onGppSectionChange(callback: @escaping (GppSectionChangePayload) -> Void) -> UsercentricsDisposableEvent<GppSectionChangePayload> {
return UsercentricsEvent.shared.onGppSectionChange(callback: callback)
}

func getTCFData(callback: @escaping (TCFData) -> Void) {
UsercentricsCore.shared.getTCFData(callback: callback)
}
Expand Down
10 changes: 10 additions & 0 deletions ios/RNUsercentricsModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject)
RCT_EXTERN_METHOD(getUSPData:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getGPPData:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getGPPString:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(getTCFData:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

Expand Down Expand Up @@ -100,6 +106,10 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject)

RCT_EXTERN_METHOD(setABTestingVariant:(NSString *)variant)

RCT_EXTERN_METHOD(setGPPConsent:(NSString *)sectionName
fieldName:(NSString *)fieldName
value:(NSDictionary *)value)

RCT_EXTERN_METHOD(track:(double)event)

RCT_EXTERN_METHOD(clearUserSession:(RCTPromiseResolveBlock)resolve
Expand Down
Loading
Loading