Skip to content

[MSDK-3287] GPP implementation#189

Merged
uc-brunosilva merged 3 commits intomasterfrom
feature/MSDK-3287
Mar 18, 2026
Merged

[MSDK-3287] GPP implementation#189
uc-brunosilva merged 3 commits intomasterfrom
feature/MSDK-3287

Conversation

@uc-brunosilva
Copy link
Collaborator

@uc-brunosilva uc-brunosilva commented Mar 10, 2026

User description

Add GPP (Global Privacy Platform) support to the React Native SDK:

  • Wire getGPPData(), getGPPString(), setGPPConsent() bridge methods for Android and iOS
  • Add onGppSectionChange event listener via RCTDeviceEventEmitter
  • Define GppData and GppSectionChangePayload TypeScript models
  • Serialize GPP data structures on both native platforms
  • Add GPP testing screen to the sample app
  • Add unit tests for all GPP methods

Android:

image

iOS:

image

Summary by CodeRabbit

  • New Features
    • Added GPP support: retrieve GPP data and GPP string, set GPP consent by section/field, and subscribe to real-time GPP section-change events.
  • Samples
    • Added a GPP Testing screen to view GPP string/data, set consents, and observe live section-change payloads.
  • Tests
    • Added unit tests and mocks covering the new GPP APIs and events.

CodeAnt-AI Description

Add Global Privacy Platform (GPP) APIs and real-time events to the React Native SDK

What Changed

  • Apps can read the raw GPP string and structured GPP data from JavaScript (getGPPString(), getGPPData()).
  • Apps can update GPP consent for a specific section/field from JavaScript (setGPPConsent(...)).
  • Apps can subscribe to onGppSectionChange events to receive real-time notifications when a GPP section changes.
  • Sample app includes a GPP testing screen to view GPP string/data, trigger consent changes, and see live section-change events.
  • Unit tests and test doubles were added/updated to cover new GPP APIs and event plumbing on both Android and iOS.

Impact

✅ Can read GPP string and structured sections from JS
✅ Can set section-level GPP consent from JS
✅ Receives live onGppSectionChange events in the app

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Add GPP (Global Privacy Platform) support to the React Native SDK:

- Wire getGPPData(), getGPPString(), setGPPConsent() bridge methods for Android and iOS
- Add onGppSectionChange event listener via RCTDeviceEventEmitter
- Define GppData and GppSectionChangePayload TypeScript models
- Serialize GPP data structures on both native platforms
- Add GPP testing screen to the sample app
- Add unit tests for all GPP methods

Made-with: Cursor
@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@qodo-code-review
Copy link

Review Summary by Qodo

Add Global Privacy Platform (GPP) support to React Native SDK

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add GPP (Global Privacy Platform) support with three bridge methods
  - getGPPData() returns GPP data structure with string, sections, applicable sections
  - getGPPString() returns GPP consent string
  - setGPPConsent() updates GPP consent for specific section and field
• Implement onGppSectionChange event listener on both Android and iOS platforms
  - Android uses DeviceEventManagerModule.RCTDeviceEventEmitter for event emission
  - iOS extends RCTEventEmitter with startObserving() and stopObserving() lifecycle
• Add TypeScript models GppData and GppSectionChangePayload for type safety
• Create GPP testing screen in sample app with UI for all GPP operations
• Add comprehensive unit tests for all new GPP methods and event handling
Diagram
flowchart LR
  A["Native Layer<br/>Android & iOS"] -->|Bridge Methods| B["RNUsercentricsModule"]
  B -->|getGPPData| C["GppData Model"]
  B -->|getGPPString| D["GPP String"]
  B -->|setGPPConsent| E["Update Consent"]
  A -->|Event Emission| F["onGppSectionChange"]
  F -->|NativeEventEmitter| G["JavaScript Layer"]
  G -->|Callback| H["GppSectionChangePayload"]
  I["Sample App"] -->|Testing UI| J["GppTestingScreen"]
Loading

Grey Divider

File Changes

1. android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt ✨ Enhancement +84/-0

Wire GPP bridge methods and event listener for Android

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt


2. android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt ✨ Enhancement +15/-0

Define abstract GPP method signatures in module spec

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt


3. android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt ✨ Enhancement +36/-0

Serialize GPP data structures to React Native WritableMap

android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt


View more (21)
4. android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt 🐞 Bug fix +0/-1

Remove deprecated resurfacePeriodEnded field from serialization

android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt


5. ios/Extensions/GppData+Dict.swift ✨ Enhancement +12/-0

Add GppData extension for dictionary serialization

ios/Extensions/GppData+Dict.swift


6. ios/Extensions/GppSectionChangePayload+Dict.swift ✨ Enhancement +10/-0

Add GppSectionChangePayload extension for dictionary serialization

ios/Extensions/GppSectionChangePayload+Dict.swift


7. ios/Manager/UsercentricsManager.swift ✨ Enhancement +20/-0

Define GPP protocol methods in UsercentricsManager interface

ios/Manager/UsercentricsManager.swift


8. ios/RNUsercentricsModule.swift ✨ Enhancement +37/-1

Implement GPP methods and event listener on iOS platform

ios/RNUsercentricsModule.swift


9. ios/RNUsercentricsModule.mm ✨ Enhancement +10/-0

Export GPP bridge methods to Objective-C module interface

ios/RNUsercentricsModule.mm


10. ios/RNUsercentricsModuleSpec.h ✨ Enhancement +9/-0

Define GPP method signatures in iOS module specification

ios/RNUsercentricsModuleSpec.h


11. example/ios/exampleTests/Fake/FakeUsercentricsManager.swift 🧪 Tests +25/-0

Add GPP mock methods to example test fake manager

example/ios/exampleTests/Fake/FakeUsercentricsManager.swift


12. sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift 🧪 Tests +25/-0

Add GPP mock methods to sample test fake manager

sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift


13. src/NativeUsercentrics.ts ✨ Enhancement +7/-0

Add GPP method signatures to TurboModule interface

src/NativeUsercentrics.ts


14. src/fabric/NativeUsercentricsModule.ts ✨ Enhancement +6/-0

Add GPP method signatures to Fabric module interface

src/fabric/NativeUsercentricsModule.ts


15. src/models/GppData.tsx ✨ Enhancement +15/-0

Define GppData TypeScript model class

src/models/GppData.tsx


16. src/models/GppSectionChangePayload.tsx ✨ Enhancement +7/-0

Define GppSectionChangePayload TypeScript model class

src/models/GppSectionChangePayload.tsx


17. src/models/index.tsx ✨ Enhancement +2/-0

Export new GPP model classes from models barrel file

src/models/index.tsx


18. src/Usercentrics.tsx ✨ Enhancement +23/-1

Implement GPP methods and event listener in public API

src/Usercentrics.tsx


19. src/__tests__/index.test.ts 🧪 Tests +34/-1

Add unit tests for all GPP methods and event handling

src/tests/index.test.ts


20. src/__tests__/mocks.ts 🧪 Tests +16/-0

Add GPP data mock examples for testing

src/tests/mocks.ts


21. sample/src/App.tsx ✨ Enhancement +5/-3

Add GPP testing screen to sample app navigation

sample/src/App.tsx


22. sample/src/screens/GppTestingScreen.tsx ✨ Enhancement +89/-0

Create GPP testing UI screen with all GPP operations

sample/src/screens/GppTestingScreen.tsx


23. sample/src/screens/Home.tsx ✨ Enhancement +5/-6

Add navigation button to GPP testing screen

sample/src/screens/Home.tsx


24. sample/src/screens/index.tsx ✨ Enhancement +1/-0

Export GppTestingScreen from screens barrel file

sample/src/screens/index.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 10, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Null GPP consent ignored🐞 Bug ✓ Correctness
Description
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).
Code

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[R141-144]

+    override fun setGPPConsent(sectionName: String, fieldName: String, value: ReadableMap) {
+        val parsedValue = readableMapValueToAny(value) ?: return
+        usercentricsProxy.instance.setGPPConsent(sectionName, fieldName, parsedValue)
+    }
Evidence
On Android, ReadableType.Null maps to null, and setGPPConsent returns early on null, dropping the
update. iOS forwards the wrapped value even when it is null (NSNull), so platform behavior diverges.

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[140-144]
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[275-284]
ios/RNUsercentricsModule.swift[157-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


2. GPP composite values lost🐞 Bug ✓ Correctness
Description
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.
Code

android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt[R10-20]

+    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())
+            }
Evidence
The Android bridge accepts composite values (ReadableType.Map/Array) and normalizes them into nested
Map/List types, but getGPPData serialization converts unknown types to strings instead of preserving
structure. The repo already has recursive Map/List -> WritableMap/WritableArray utilities that would
correctly serialize nested structures.

android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt[8-23]
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[277-294]
android/src/main/java/com/usercentrics/reactnative/extensions/ReadableMapExtensions.kt[74-110]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


3. Missing resurfacePeriodEnded key🐞 Bug ✓ Correctness
Description
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.
Code

android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt[R219-223]

      "disabledSpecialFeatures" to disabledSpecialFeatures,
      "firstLayerShowDescriptions" to firstLayerShowDescriptions,
      "hideNonIabOnFirstLayer" to hideNonIabOnFirstLayer,
-        "resurfacePeriodEnded" to resurfacePeriodEnded,
      "resurfacePurposeChanged" to resurfacePurposeChanged,
      "resurfaceVendorAdded" to resurfaceVendorAdded,
Evidence
The Android serialization map jumps from hideNonIabOnFirstLayer directly to resurfacePurposeChanged,
omitting resurfacePeriodEnded. The TS model defines resurfacePeriodEnded as a required boolean, and
iOS serialization still includes it, confirming the contract mismatch is Android-only.

android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt[219-223]
src/models/TCF2Settings.tsx[36-40]
ios/Extensions/UsercentricsCMPData+Dict.swift[209-213]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## 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


View more (1)
4. iOS EventEmitter bridge mismatch🐞 Bug ⛯ Reliability
Description
The iOS module now subclasses RCTEventEmitter, but the ObjC extern module still declares it as an
NSObject-based module. This mismatch can prevent React Native from treating the module as an event
emitter, breaking onGppSectionChange subscriptions created via NativeEventEmitter.
Code

ios/RNUsercentricsModule.swift[20]

+class RNUsercentricsModule: RCTEventEmitter {
Evidence
Swift implements RCTEventEmitter behaviors (supportedEvents/startObserving/stopObserving) but the
ObjC bridge registers the module as NSObject, while JS constructs NativeEventEmitter over the
module. If the bridge doesn’t reflect the event-emitter superclass, listener lifecycle may not be
wired correctly and events may not be delivered.

ios/RNUsercentricsModule.swift[19-52]
ios/RNUsercentricsModule.mm[10-12]
src/Usercentrics.tsx[23-26]
src/Usercentrics.tsx[165-167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
iOS Swift module subclasses `RCTEventEmitter`, but `RNUsercentricsModule.mm` still declares it as an `NSObject` module, which can break event-emitter wiring.
### Issue Context
JS uses `new NativeEventEmitter(RNUsercentricsModule)` and subscribes to `onGppSectionChange`, relying on proper event-emitter integration.
### Fix Focus Areas
- ios/RNUsercentricsModule.swift[19-52]
- ios/RNUsercentricsModule.mm[8-12]
- src/Usercentrics.tsx[23-26]

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds GPP (Global Privacy Platform) support across platforms: new APIs to get GPP data/string, set GPP consent, and emit onGppSectionChange events. Changes touch Android and iOS native modules, TypeScript models and public API, tests/fakes, and the sample app with a GPP testing screen.

Changes

Cohort / File(s) Summary
Android Native GPP API
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt, android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
Added getGPPData, getGPPString, setGPPConsent, event listener methods (addListener, removeListeners, invalidate), event subscription and emit logic for GPP section changes.
Android GPP Serialization
android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt
New helpers to convert GppData and GppSectionChangePayload into RN WritableMap/WritableArray.
Android CMP serialization tweak
android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt
Switched to compatibility accessor for resurfacePeriodEnded via reflection.
iOS Native GPP API & Bridge
ios/RNUsercentricsModule.mm, ios/RNUsercentricsModule.swift, ios/RNUsercentricsModuleSpec.h
Exposed getGPPData, getGPPString, setGPPConsent to RN; module now extends RCTEventEmitter and implements supportedEvents, startObserving, stopObserving to emit onGppSectionChange.
iOS GPP Serialization
ios/Extensions/GppData+Dict.swift, ios/Extensions/GppSectionChangePayload+Dict.swift
Added toDictionary() helpers to convert GPP types to Foundation dictionaries/arrays for bridging.
iOS Manager Integration
ios/Manager/UsercentricsManager.swift
Protocol and implementation extended with getGPPData, getGPPString, setGPPConsent, and onGppSectionChange delegating to core/event APIs.
TypeScript models & exports
src/models/GppData.tsx, src/models/GppSectionChangePayload.tsx, src/models/index.tsx
Added GppData and GppSectionChangePayload classes and re-exported them.
JS Public API & Fabric module
src/NativeUsercentrics.ts, src/Usercentrics.tsx, src/fabric/NativeUsercentricsModule.ts
Added getGPPData, getGPPString, setGPPConsent, event wiring (onGppSectionChange, addListener, removeListeners) and NativeEventEmitter integration.
Tests & Mocks
src/__tests__/index.test.ts, src/__tests__/mocks.ts, example/ios/.../FakeUsercentricsManager.swift, sample/ios/.../FakeUsercentricsManager.swift
Added GPP examples/mocks, unit tests and fake manager implementations including disposable event for section changes.
Sample app updates
sample/src/App.tsx, sample/src/screens/Home.tsx, sample/src/screens/GppTestingScreen.tsx, sample/src/screens/index.tsx
Added GppTestingScreen and route, UI to call new GPP APIs, minor Home screen changes and removed an unused logger import.
iOS CMP serialization tweak
ios/Extensions/UsercentricsCMPData+Dict.swift
Removed resurfacePeriodEnded entry from TCF2Settings dictionary output.

Sequence Diagram(s)

sequenceDiagram
    participant JS as JavaScript/React
    participant Bridge as RN Bridge / Native Module
    participant NativeCore as Usercentrics Core
    participant EventMgr as UsercentricsEvent

    JS->>Bridge: getGPPData()
    Bridge->>NativeCore: getGPPData()
    NativeCore-->>Bridge: GppData
    Bridge-->>JS: Promise resolved (serialized GppData)

    JS->>Bridge: setGPPConsent(section, field, value)
    Bridge->>NativeCore: setGPPConsent(...)
    NativeCore-->>Bridge: ack/void
Loading
sequenceDiagram
    participant JS as JavaScript/React
    participant Bridge as RNUsercentricsModule
    participant NativeEvent as UsercentricsEvent
    participant JSCallback as JS listener

    JS->>Bridge: addListener("onGppSectionChange")
    Bridge->>NativeEvent: register onGppSectionChange callback
    NativeEvent-->>Bridge: emits GppSectionChangePayload
    Bridge-->>JSCallback: emit "onGppSectionChange" via RN emitter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: fabric support #167 — Related native RN module and fabric/TurboModule interface changes that touch the JS/native surface.

Suggested labels

size:XL

Suggested reviewers

  • uc-brunosouza
  • islameldesoky95
  • souzabrunoj

Poem

🐰 I hop and I listen, a GPP tune I bring,

Consent and payloads now dance and sing,
From Kotlin to Swift, events take flight,
JS calls and bridges hum through the night,
A happy rabbit cheers — privacy takes wing!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[MSDK-3287] GPP implementation' clearly describes the main change: adding GPP (Global Privacy Platform) support to the React Native SDK. It's specific, concise, and directly reflects the PR's primary objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/MSDK-3287
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Mar 10, 2026
@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

Sequence Diagram

This PR adds React Native support for retrieving GPP data/string and updating GPP consent, wiring these calls through the native bridges to the Usercentrics core and exposing GPP section change notifications back to the app.

sequenceDiagram
    participant App
    participant ReactNativeSDK
    participant UsercentricsCore

    App->>ReactNativeSDK: Request GPP data or string
    ReactNativeSDK->>UsercentricsCore: Get GPP data and string
    UsercentricsCore-->>ReactNativeSDK: GPP data and string
    ReactNativeSDK-->>App: Resolve promise with GPP data/string

    App->>ReactNativeSDK: setGPPConsent for a section
    ReactNativeSDK->>UsercentricsCore: Update GPP consent value
    UsercentricsCore-->>ReactNativeSDK: Notify GPP section change
    ReactNativeSDK-->>App: Invoke onGppSectionChange callback
Loading

Generated by CodeAnt AI

@pantoaibot
Copy link

pantoaibot bot commented Mar 10, 2026

PR Summary:

Implements GPP (Global Privacy Platform) support: adds GPP data access, GPP consent setters, and a GPP section change event across native Android/iOS and JS.

Changes:

  • New public JS APIs:
    • Usercentrics.getGPPData(), getGPPString(), setGPPConsent(section, field, value)
    • Usercentrics.onGppSectionChange(callback) (event subscription via addListener/removeListeners)
    • Added models: GppData, GppSectionChangePayload and exported them.
  • Android:
    • RNUsercentricsModule: added getGPPData, getGPPString, setGPPConsent, addListener/removeListeners, event emission (onGppSectionChange) and lifecycle cleanup.
    • Helpers to convert ReadableMap to native Any for setGPPConsent and number normalization.
    • New extensions: GppData.serializeGppData() and GppSectionChangePayload.serializeGppPayload().
    • Updated RNUsercentricsModuleSpec to declare new native methods.
  • iOS:
    • UsercentricsManager protocol/implementation updated to include getGPPData/getGPPString/setGPPConsent and onGppSectionChange.
    • RNUsercentricsModule (Swift) now subclasses RCTEventEmitter and implements supportedEvents/startObserving/stopObserving to forward onGppSectionChange events to JS.
    • RNUsercentricsModule.mm and RNUsercentricsModuleSpec.h updated to expose new native methods.
    • Added GppData+Dict and GppSectionChangePayload+Dict extensions for bridging.
  • Native bridging / TurboModule updates:
    • NativeUsercentrics (TS) and fabric TurboModule spec updated to include GPP methods and addListener/removeListeners.
  • Tests & samples:
    • Unit test mocks and tests added for getGPPData, getGPPString and setGPPConsent.
    • Sample app: added GPPTesting screen and navigation entry demonstrating GPP APIs and event handling.
  • Misc:
    • Minor cleanup in CMP data extension (removed unused mapping entry).

Compatibility notes:

  • Adds new methods and events (non-breaking for existing calls). iOS RNUsercentricsModule implementation now inherits RCTEventEmitter to support events — internal change but enables event delivery to JS.

Reviewed by Panto AI

@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

CodeAnt AI finished reviewing your PR.

@pantoaibot
Copy link

pantoaibot bot commented Mar 10, 2026

Reviewed up to commit:a7bea02ac299987074920df6e7a918f60597186f

Reviewed by Panto AI

@qodo-code-review
Copy link

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: test-ios

Failed stage: Run tests [❌]

Failed test name: ""

Failure summary:

The GitHub Action failed during the iOS build/test step because Xcode could not compile Swift
sources for the CocoaPods target react-native-usercentrics, which caused the build to fail and tests
to be cancelled (exit code 65).

Key compile errors reported in the log:
- Cannot find type 'GppSectionChangePayload' in scope
(repeated multiple times), indicating a missing/undefined Swift type used by the
react-native-usercentrics code (notably involving GppSectionChangePayload+Dict.swift).
- Overriding
declaration requires an 'override' keyword (Swift compilation error).
- Redundant conformance of
'RNUsercentricsModule' to protocol 'RCTBridgeModule' (Swift error/warning treated as failure in this
build).

The failing build step is explicitly:
- Command SwiftCompile failed with a nonzero exit code
-
Failed build commands include EmitSwiftModule normal arm64 / SwiftEmitModule normal arm64 /
SwiftCompile normal arm64 for target react-native-usercentrics (from project Pods), compiling files
including /ios/Extensions/GppSectionChangePayload+Dict.swift and /ios/RNUsercentricsModule.swift.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

960:  Installing React-RCTText (0.81.4)
961:  Installing React-RCTVibration (0.81.4)
962:  Installing React-RuntimeApple (0.81.4)
963:  Installing React-RuntimeCore (0.81.4)
964:  Installing React-RuntimeHermes (0.81.4)
965:  Installing React-callinvoker (0.81.4)
966:  Installing React-cxxreact (0.81.4)
967:  Installing React-debug (0.81.4)
968:  Installing React-defaultsnativemodule (0.81.4)
969:  Installing React-domnativemodule (0.81.4)
970:  Installing React-featureflags (0.81.4)
971:  Installing React-featureflagsnativemodule (0.81.4)
972:  Installing React-graphics (0.81.4)
973:  Installing React-hermes (0.81.4)
974:  Installing React-idlecallbacksnativemodule (0.81.4)
975:  Installing React-jserrorhandler (0.81.4)
976:  Installing React-jsi (0.81.4)
...

1214:  ▸ Copying ExecutionContextManager.h
1215:  ▸ Copying ExecutionContext.h
1216:  ▸ Copying ConsoleMessage.h
1217:  ▸ Copying Base64.h
1218:  ▸ Copying React-jsiexecutor-umbrella.h
1219:  ▸ Copying JSINativeModules.h
1220:  ▸ Copying JSIExecutor.h
1221:  ▸ Copying threadsafe.h
1222:  ▸ Copying jsilib.h
1223:  ▸ Copying jsi.h
1224:  ▸ Copying jsi-inl.h
1225:  ▸ Copying instrumentation.h
1226:  ▸ Copying decorator.h
1227:  ▸ Copying React-jsi-umbrella.h
1228:  ▸ Copying JSIDynamic.h
1229:  ▸ Copying React-jserrorhandler-umbrella.h
1230:  ▸ Copying React-idlecallbacksnativemodule-umbrella.h
1231:  ▸ Copying React-hermes-umbrella.h
1232:  ▸ Copying HermesExecutorFactory.h
1233:  ▸ Copying React-graphics-umbrella.h
1234:  ▸ Copying React-featureflagsnativemodule-umbrella.h
1235:  ▸ Copying React-featureflags-umbrella.h
1236:  ▸ Copying React-domnativemodule-umbrella.h
1237:  ▸ Copying React-defaultsnativemodule-umbrella.h
1238:  ▸ Copying React-debug-umbrella.h
1239:  ▸ Copying TraceSection.h
1240:  ▸ Copying SystraceSection.h
1241:  ▸ Copying SharedProxyCxxModule.h
1242:  ▸ Copying RecoverableError.h
1243:  ▸ Copying ReactNativeVersion.h
...

1246:  ▸ Copying RAMBundleRegistry.h
1247:  ▸ Copying NativeToJsBridge.h
1248:  ▸ Copying NativeModule.h
1249:  ▸ Copying MoveWrapper.h
1250:  ▸ Copying ModuleRegistry.h
1251:  ▸ Copying MethodCall.h
1252:  ▸ Copying MessageQueueThread.h
1253:  ▸ Copying JsArgumentHelpers.h
1254:  ▸ Copying JsArgumentHelpers-inl.h
1255:  ▸ Copying JSModulesUnbundle.h
1256:  ▸ Copying JSIndexedRAMBundle.h
1257:  ▸ Copying JSExecutor.h
1258:  ▸ Copying JSBundleType.h
1259:  ▸ Copying JSBigString.h
1260:  ▸ Copying Instance.h
1261:  ▸ Copying ErrorUtils.h
1262:  ▸ Copying CxxNativeModule.h
...

1605:  ▸ Copying RCTI18nUtil.h
1606:  ▸ Copying RCTI18nManager.h
1607:  ▸ Copying RCTHTTPRequestHandler.h
1608:  ▸ Copying RCTGIFImageDecoder.h
1609:  ▸ Copying RCTFrameUpdate.h
1610:  ▸ Copying RCTFrameAnimation.h
1611:  ▸ Copying RCTFont.h
1612:  ▸ Copying RCTFileRequestHandler.h
1613:  ▸ Copying RCTFileReaderModule.h
1614:  ▸ Copying RCTFPSGraph.h
1615:  ▸ Copying RCTExceptionsManager.h
1616:  ▸ Copying RCTEventEmitter.h
1617:  ▸ Copying RCTEventDispatcherProtocol.h
1618:  ▸ Copying RCTEventDispatcher.h
1619:  ▸ Copying RCTEventAnimation.h
1620:  ▸ Copying RCTErrorInfo.h
1621:  ▸ Copying RCTErrorCustomizer.h
1622:  ▸ Copying RCTDynamicTypeRamp.h
...

1851:  ▸ Compiling SocketRocket-dummy.m
1852:  ▸ Compiling SRWebSocket.m
1853:  ▸ Compiling SRURLUtilities.m
1854:  ▸ Compiling SRSecurityPolicy.m
1855:  ▸ Compiling SRSIMDHelpers.m
1856:  ▸ Compiling SRRunLoopThread.m
1857:  ▸ Compiling SRRandom.m
1858:  ▸ Compiling SRProxyConnect.m
1859:  ▸ Compiling SRPinningSecurityPolicy.m
1860:  ▸ Compiling SRMutex.m
1861:  ▸ Compiling SRLog.m
1862:  ▸ Compiling SRIOConsumerPool.m
1863:  ▸ Compiling SRIOConsumer.m
1864:  ▸ Compiling SRHash.m
1865:  ▸ Compiling SRHTTPConnectMessage.m
1866:  ▸ Compiling SRError.m
1867:  ▸ Compiling SRDelegateController.m
...

2207:  ▸ Compiling RCTModalManager.m
2208:  ▸ Compiling RCTModalHostViewManager.m
2209:  ▸ Compiling RCTModalHostViewController.m
2210:  ▸ Compiling RCTModalHostView.m
2211:  ▸ Compiling RCTLayoutAnimationGroup.m
2212:  ▸ Compiling RCTLayoutAnimation.m
2213:  ▸ Compiling RCTLayout.m
2214:  ▸ Compiling RCTJSThread.m
2215:  ▸ Compiling RCTKeyCommands.m
2216:  ▸ Compiling RCTJSStackFrame.m
2217:  ▸ Compiling RCTImageSource.m
2218:  ▸ Compiling RCTI18nUtil.m
2219:  ▸ Compiling RCTEventEmitter.m
2220:  ▸ Compiling RCTFrameUpdate.m
2221:  ▸ Compiling RCTEventDispatcher.m
2222:  ▸ Compiling RCTErrorInfo.m
2223:  ▸ Compiling RCTDisplayLink.m
...

2277:  ▸ Compiling RCTRawTextShadowView.mm
2278:  ▸ Compiling RCTMultilineTextInputViewManager.mm
2279:  ▸ Compiling RCTMultilineTextInputView.mm
2280:  ▸ Compiling RCTInputAccessoryViewManager.mm
2281:  ▸ Compiling RCTInputAccessoryViewContent.mm
2282:  ▸ Compiling RCTInputAccessoryView.mm
2283:  ▸ Compiling RCTInputAccessoryShadowView.mm
2284:  ▸ Compiling RCTDynamicTypeRamp.mm
2285:  ▸ Compiling RCTConvert+Text.mm
2286:  ▸ Compiling RCTBaseTextViewManager.mm
2287:  ▸ Compiling RCTBaseTextShadowView.mm
2288:  ▸ Compiling RCTBaseTextInputViewManager.mm
2289:  ▸ Compiling RCTBaseTextInputView.mm
2290:  ▸ Compiling RCTBaseTextInputShadowView.mm
2291:  ▸ Compiling RCTBackedTextInputDelegateAdapter.mm
2292:  ▸ Processing React-jserrorhandler-Info.plist
2293:  ▸ Compiling RCTText_vers.c
2294:  ▸ Compiling StackTraceParser.cpp
2295:  ▸ Compiling JsErrorHandler.cpp
2296:  ▸ Compiling React-RCTText-dummy.m
2297:  ▸ Compiling NSTextStorage+FontScaling.m
2298:  ▸ Compiling React_jserrorhandler_vers.c
2299:  ▸ Compiling React-jserrorhandler-dummy.m
2300:  ▸ Running script 'Create Symlinks to Header Folders'
2301:  ▸ Touching reacthermes.framework (in target 'React-hermes' from project 'Pods')
2302:  ▸ Processing React-Core-Info.plist
2303:  ▸ Touching React_graphics.framework (in target 'React-graphics' from project 'Pods')
2304:  ▸ Compiling RCTTypedModuleConstants.mm
2305:  ▸ Compiling RCTConvertHelpers.mm
2306:  ▸ Compiling RCTTypeSafety_vers.c
2307:  ▸ Running script 'Create Symlinks to Header Folders'
2308:  ▸ Touching React_jserrorhandler.framework (in target 'React-jserrorhandler' from project 'Pods')
2309:  ▸ Touching React.framework (in target 'React-Core' from project 'Pods')
...

2844:  Skipping duplicate build file in Compile Sources build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/States.cpp (in target 'ReactCodegen' from project 'Pods')
2845:  Skipping duplicate build file in Compile Sources build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/States.cpp (in target 'ReactCodegen' from project 'Pods')
2846:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'ReactCodegen' from project 'Pods')
2847:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-utils' from project 'Pods')
2848:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-runtimescheduler' from project 'Pods')
2849:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-runtimeexecutor' from project 'Pods')
2850:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-rendererdebug' from project 'Pods')
2851:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-renderercss' from project 'Pods')
2852:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-rendererconsistency' from project 'Pods')
2853:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-performancetimeline' from project 'Pods')
2854:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-microtasksnativemodule' from project 'Pods')
2855:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-jsitooling' from project 'Pods')
2856:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-jsinspectortracing' from project 'Pods')
2857:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-jsinspectornetwork' from project 'Pods')
2858:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-jsinspectorcdp' from project 'Pods')
2859:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-jserrorhandler' from project 'Pods')
2860:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-idlecallbacksnativemodule' from project 'Pods')
2861:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-graphics' from project 'Pods')
2862:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-featureflagsnativemodule' from project 'Pods')
2863:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-featureflags' from project 'Pods')
2864:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-domnativemodule' from project 'Pods')
2865:  Testing failed:
2866:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-defaultsnativemodule' from project 'Pods')
...

2874:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-RCTFBReactNativeSpec' from project 'Pods')
2875:  Cannot find type 'GppSectionChangePayload' in scope
2876:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-NativeModulesApple' from project 'Pods')
2877:  Cannot find type 'GppSectionChangePayload' in scope
2878:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-Mapbuffer' from project 'Pods')
2879:  Overriding declaration requires an 'override' keyword
2880:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-ImageManager' from project 'Pods')
2881:  Overriding declaration requires an 'override' keyword
2882:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-FabricImage' from project 'Pods')
2883:  Redundant conformance of 'RNUsercentricsModule' to protocol 'RCTBridgeModule'
2884:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-FabricComponents' from project 'Pods')
2885:  Cannot find type 'GppSectionChangePayload' in scope
2886:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'React-Fabric' from project 'Pods')
2887:  Cannot find type 'GppSectionChangePayload' in scope
2888:  Run script build phase 'Create Symlinks to Header Folders' will be run during every build because it does not specify any outputs. To address this issue, either add output dependencies to the script phase, or configure it to run in every build by unchecking "Based on dependency analysis" in the script phase. (in target 'RCT-Folly' from project 'Pods')
2889:  Command SwiftCompile failed with a nonzero exit code
2890:  Testing cancelled because the build failed.
2891:  ** TEST FAILED **
2892:  The following build commands failed:
2893:  EmitSwiftModule normal arm64 (in target 'react-native-usercentrics' from project 'Pods')
2894:  SwiftEmitModule normal arm64 Emitting\ module\ for\ react_native_usercentrics (in target 'react-native-usercentrics' from project 'Pods')
2895:  SwiftCompile normal arm64 Compiling\ BannerSettings+Dict.swift,\ CCPAData+Dict.swift,\ DispatchQueueManager.swift,\ GppData+Dict.swift,\ GppSectionChangePayload+Dict.swift,\ PresentationViewController.swift,\ ReadyStatus+Dict.swift,\ RNUsercentricsModule.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/BannerSettings+Dict.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/CCPAData+Dict.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Manager/DispatchQueueManager.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/GppData+Dict.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/GppSectionChangePayload+Dict.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Manager/PresentationViewController.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/ReadyStatus+Dict.swift /Users/runner/work/react-native-sdk/react-native-sdk/ios/RNUsercentricsModule.swift (in target 'react-native-usercentrics' from project 'Pods')
2896:  Testing workspace sample with scheme sample
2897:  (4 failures)
2898:  ##[error]Process completed with exit code 65.
2899:  ##[group]Run actions/upload-artifact@v4

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
sample/src/screens/GppTestingScreen.tsx (1)

20-37: Consider adding error handling to async operations.

The async handlers call SDK methods without try-catch blocks. If the SDK isn't properly initialized or an error occurs, the app could display unclear errors to testers.

💡 Proposed fix to add error handling
 const fetchGppString = async () => {
+    try {
         const value = await Usercentrics.getGPPString();
         setGppString(value);
+    } catch (error) {
+        setGppString(`Error: ${error}`);
+    }
 };

 const fetchGppData = async () => {
+    try {
         const value = await Usercentrics.getGPPData();
         const asJson = JSON.stringify(value, null, 2);
         setGppDataJson(asJson);
+    } catch (error) {
+        setGppDataJson(`Error: ${error}`);
+    }
 };

 const setUsNatSaleOptOut = async () => {
+    try {
         await Usercentrics.setGPPConsent('usnat', 'SaleOptOut', 2);
+    } catch (error) {
+        console.error('Failed to set usnat consent:', error);
+    }
 };

 const setUsFlSaleOptOut = async () => {
+    try {
         await Usercentrics.setGPPConsent('usfl', 'SaleOptOut', 2);
+    } catch (error) {
+        console.error('Failed to set usfl consent:', error);
+    }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sample/src/screens/GppTestingScreen.tsx` around lines 20 - 37, The async
handlers fetchGppString, fetchGppData, setUsNatSaleOptOut, and setUsFlSaleOptOut
lack error handling; wrap each function body in a try-catch, await the SDK call
inside try, handle errors in catch by logging the error (e.g., console.error or
process logger) and updating UI state (e.g., set an error string or toast) so
failures are visible to testers; ensure you preserve existing success behavior
(setGppString/setGppDataJson) and do not swallow errors silently.
android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt (1)

12-20: Add missing List and Map type handling to GppDataExtensions.

The when block only handles primitives (Boolean, Int, Double, String, null). Since GppData.sections contains Map<String, Any>, fields can hold arrays or nested objects. These will fall back to toString(), corrupting complex data. The codebase already has List<*>.serialize() and Map<String, Any?>.toWritableMap() extensions that properly handle these types.

♻️ Add support for List and Map types
 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)
+        is List<*> -> fieldsMap.putArray(fieldName, value.serialize())
+        is Map<*, *> -> {
+            `@Suppress`("UNCHECKED_CAST")
+            fieldsMap.putMap(fieldName, (value as Map<String, Any>).toWritableMap())
+        }
         else -> fieldsMap.putString(fieldName, value.toString())
     }
 }
🤖 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/extensions/GppDataExtensions.kt`
around lines 12 - 20, The when-block in GppDataExtensions.kt only handles
primitives and falls back to toString() for complex types; update the
fields.forEach handling to detect List and Map values and serialize them using
the existing List<*>.serialize() and Map<String, Any?>.toWritableMap()
extensions (e.g., add an is List<*> branch to call fieldsMap.putArray(fieldName,
value.serialize()) and an is Map<*,*> branch that safely casts to Map<String,
Any?> and calls fieldsMap.putMap(fieldName, value.toWritableMap())), ensuring
nullability is respected and avoiding the toString() fallback for nested
structures.
src/models/GppData.tsx (1)

1-15: Consider using an interface instead of a class, though verify alignment with architectural patterns.

The native bridge returns plain JavaScript objects, not class instances. Using export class GppData is semantically less precise than an interface since Usercentrics.getGPPData() returns raw bridge objects without calling this constructor. However, all 45+ other models in src/models/ consistently use the export class pattern. Changing only GppData to an interface would break this established architectural consistency.

This is not blocking and carries minimal risk (no instanceof checks are used with this class in the codebase), but the pattern choice should align with a broader decision about how models in this project are standardized.

💡 Suggested alternative using interface (if adopting interface pattern project-wide)
-export class GppData {
-    gppString: string
-    applicableSections: number[]
-    sections: Record<string, Record<string, unknown>>
-
-    constructor(
-        gppString: string,
-        applicableSections: number[],
-        sections: Record<string, Record<string, unknown>>
-    ) {
-        this.gppString = gppString
-        this.applicableSections = applicableSections
-        this.sections = sections
-    }
-}
+export interface GppData {
+    gppString: string
+    applicableSections: number[]
+    sections: Record<string, Record<string, unknown>>
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/GppData.tsx` around lines 1 - 15, The GppData type is declared as
a class but native bridge returns plain objects (Usercentrics.getGPPData()), so
either convert GppData to an interface or keep the class for consistency across
models; to fix, choose one approach and apply it consistently: if you opt to use
interface, change export class GppData to export interface GppData { gppString:
string; applicableSections: number[]; sections: Record<string, Record<string,
unknown>> } and update any other model files to interfaces, or if you keep the
class (recommended for alignment with the 45+ existing models) leave export
class GppData as-is but add a short comment near the GppData declaration noting
that bridge returns plain objects and instances are not required (reference
symbols: GppData and Usercentrics.getGPPData).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt`:
- Around line 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).

In `@ios/RNUsercentricsModule.swift`:
- Around line 38-52: The subscription is created inside queue.async which can
race with immediate JS calls like setGPPConsent and miss the first event; change
startObserving to register gppSectionChangeSubscription synchronously (remove
the outer queue.async and create the subscription immediately in startObserving)
while keeping event dispatching onto queue if needed (i.e., inside the
onGppSectionChange handler use queue.async { self.sendEvent(...) }). Keep
stopObserving disposing gppSectionChangeSubscription on the queue (queue.async {
... }) and reference the existing gppSectionChangeSubscription,
usercentricsManager.onGppSectionChange, startObserving, stopObserving,
setGPPConsent, and onGppSectionChangeEvent symbols when making the change.

In `@ios/RNUsercentricsModuleSpec.h`:
- Around line 63-65: The spec declares setGPPConsent with parameter type `id`
for `value` but the implementations (RNUsercentricsModule.mm and
RNUsercentricsModule.swift) expect an NSDictionary; update the
RNUsercentricsModuleSpec declaration for the method `-
(void)setGPPConsent:(NSString *)sectionName fieldName:(NSString *)fieldName
value:(id)value;` to use `NSDictionary *value` instead of `id` so the
Objective-C bridge matches the .mm and Swift implementations.

---

Nitpick comments:
In
`@android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt`:
- Around line 12-20: The when-block in GppDataExtensions.kt only handles
primitives and falls back to toString() for complex types; update the
fields.forEach handling to detect List and Map values and serialize them using
the existing List<*>.serialize() and Map<String, Any?>.toWritableMap()
extensions (e.g., add an is List<*> branch to call fieldsMap.putArray(fieldName,
value.serialize()) and an is Map<*,*> branch that safely casts to Map<String,
Any?> and calls fieldsMap.putMap(fieldName, value.toWritableMap())), ensuring
nullability is respected and avoiding the toString() fallback for nested
structures.

In `@sample/src/screens/GppTestingScreen.tsx`:
- Around line 20-37: The async handlers fetchGppString, fetchGppData,
setUsNatSaleOptOut, and setUsFlSaleOptOut lack error handling; wrap each
function body in a try-catch, await the SDK call inside try, handle errors in
catch by logging the error (e.g., console.error or process logger) and updating
UI state (e.g., set an error string or toast) so failures are visible to
testers; ensure you preserve existing success behavior
(setGppString/setGppDataJson) and do not swallow errors silently.

In `@src/models/GppData.tsx`:
- Around line 1-15: The GppData type is declared as a class but native bridge
returns plain objects (Usercentrics.getGPPData()), so either convert GppData to
an interface or keep the class for consistency across models; to fix, choose one
approach and apply it consistently: if you opt to use interface, change export
class GppData to export interface GppData { gppString: string;
applicableSections: number[]; sections: Record<string, Record<string, unknown>>
} and update any other model files to interfaces, or if you keep the class
(recommended for alignment with the 45+ existing models) leave export class
GppData as-is but add a short comment near the GppData declaration noting that
bridge returns plain objects and instances are not required (reference symbols:
GppData and Usercentrics.getGPPData).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d60dae1-efab-4668-99bb-2df74c7d6f6c

📥 Commits

Reviewing files that changed from the base of the PR and between 6434203 and a7bea02.

📒 Files selected for processing (24)
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
  • android/src/main/java/com/usercentrics/reactnative/extensions/GppDataExtensions.kt
  • android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt
  • example/ios/exampleTests/Fake/FakeUsercentricsManager.swift
  • ios/Extensions/GppData+Dict.swift
  • ios/Extensions/GppSectionChangePayload+Dict.swift
  • ios/Manager/UsercentricsManager.swift
  • ios/RNUsercentricsModule.mm
  • ios/RNUsercentricsModule.swift
  • ios/RNUsercentricsModuleSpec.h
  • sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift
  • sample/src/App.tsx
  • sample/src/screens/GppTestingScreen.tsx
  • sample/src/screens/Home.tsx
  • sample/src/screens/index.tsx
  • src/NativeUsercentrics.ts
  • src/Usercentrics.tsx
  • src/__tests__/index.test.ts
  • src/__tests__/mocks.ts
  • src/fabric/NativeUsercentricsModule.ts
  • src/models/GppData.tsx
  • src/models/GppSectionChangePayload.tsx
  • src/models/index.tsx
💤 Files with no reviewable changes (1)
  • android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.kt

@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai bot added size:L This PR changes 100-499 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Mar 10, 2026
@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

Sequence Diagram

This PR adds Global Privacy Platform support so React Native apps can read GPP string/data, update GPP consent, and receive GPP section change events from the native Usercentrics core.

sequenceDiagram
    participant App
    participant SDK
    participant UsercentricsCore

    App->>SDK: Request GPP data or GPP string
    SDK->>UsercentricsCore: Get GPP data or string
    UsercentricsCore-->>SDK: Return GPP information
    SDK-->>App: Resolve promise with GPP information

    App->>SDK: setGPPConsent(section, field, value)
    SDK->>UsercentricsCore: Apply GPP consent update

    UsercentricsCore-->>SDK: GPP section change event
    SDK-->>App: Trigger onGppSectionChange callback with payload
Loading

Generated by CodeAnt AI

@codeant-ai
Copy link

codeant-ai bot commented Mar 10, 2026

CodeAnt AI Incremental review completed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
ios/RNUsercentricsModule.swift (1)

38-52: ⚠️ Potential issue | 🟠 Major

Register the GPP observer synchronously in startObserving.

queue.async still leaves a window where JS can subscribe and immediately call setGPPConsent before gppSectionChangeSubscription exists, so the first onGppSectionChange event can be missed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/RNUsercentricsModule.swift` around lines 38 - 52, The GPP observer is
registered asynchronously which can miss the first event; change startObserving
to register the subscription synchronously on queue (replace queue.async with
queue.sync) so gppSectionChangeSubscription is set before JS can call
setGPPConsent; keep the same weak self guard and disposal logic (stopObserving
can remain using queue.async) and reference the startObserving/stopObserving
methods, gppSectionChangeSubscription property, and
usercentricsManager.onGppSectionChange callback when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ios/RNUsercentricsModule.swift`:
- Around line 38-52: The GPP observer is registered asynchronously which can
miss the first event; change startObserving to register the subscription
synchronously on queue (replace queue.async with queue.sync) so
gppSectionChangeSubscription is set before JS can call setGPPConsent; keep the
same weak self guard and disposal logic (stopObserving can remain using
queue.async) and reference the startObserving/stopObserving methods,
gppSectionChangeSubscription property, and
usercentricsManager.onGppSectionChange callback when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6eb66210-10d3-4980-9a93-defbabbf8d1c

📥 Commits

Reviewing files that changed from the base of the PR and between a7bea02 and 04cfb1c.

📒 Files selected for processing (2)
  • ios/Extensions/UsercentricsCMPData+Dict.swift
  • ios/RNUsercentricsModule.swift
💤 Files with no reviewable changes (1)
  • ios/Extensions/UsercentricsCMPData+Dict.swift

Copy link
Collaborator

@islameldesoky95 islameldesoky95 left a comment

Choose a reason for hiding this comment

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

I would suggest a run on the iOS as well just to make sure things working for the new arch and fabric and that they are compatible

@codeant-ai
Copy link

codeant-ai bot commented Mar 18, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Mar 18, 2026
@uc-brunosilva uc-brunosilva merged commit 8ee96f5 into master Mar 18, 2026
1 of 4 checks passed
@codeant-ai
Copy link

codeant-ai bot commented Mar 18, 2026

CodeAnt AI Incremental review completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants