Skip to content

feat(MSDK-3297): pass unsavedVendorLIDecisions through denyAllForTCF#190

Merged
islameldesoky95 merged 3 commits intomasterfrom
feature/MSDK-3297-LI-Deny-all-vendors
Mar 18, 2026
Merged

feat(MSDK-3297): pass unsavedVendorLIDecisions through denyAllForTCF#190
islameldesoky95 merged 3 commits intomasterfrom
feature/MSDK-3297-LI-Deny-all-vendors

Conversation

@islameldesoky95
Copy link
Collaborator

@islameldesoky95 islameldesoky95 commented Mar 12, 2026

User description

Summary by CodeRabbit

  • New Features

    • Denial flow now accepts and applies vendor-level decisions in addition to purpose-level decisions, enabling finer-grained control when denying consents.
  • Refactor

    • Consent decision processing unified across platforms so purpose and vendor decisions are handled consistently in the SDK.

CodeAnt-AI Description

Pass unsaved vendor legitimate-interest decisions through TCF "Deny All"

What Changed

  • Deny-all-for-TCF now accepts and applies unsaved vendor legitimate-interest decisions in addition to purpose legitimate-interest decisions, so vendor-level LI choices are considered when denying consent.
  • JavaScript API and native bindings (iOS and Android) were extended to accept a vendor LI list; calls without the new argument remain compatible (default empty list).
  • The service consent list returned after a deny-all-for-TCF call reflects the combined purpose and vendor LI inputs provided at call time.

Impact

✅ Considers vendor legitimate-interest during TCF deny-all
✅ Preserves unsaved vendor choices when denying all
✅ Fewer incorrect vendor consents after deny-all

💡 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.

@codeant-ai
Copy link

codeant-ai bot commented Mar 12, 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

Pass unsavedVendorLIDecisions through denyAllForTCF on all layers

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add unsavedVendorLIDecisions parameter to denyAllForTCF method across all layers
• Update Android implementation to deserialize vendor LI decisions
• Refactor iOS implementation with extracted helper method
• Update TypeScript interfaces and implementations with vendor decisions support
Diagram
flowchart LR
  A["denyAllForTCF API"] -->|Add Parameter| B["unsavedVendorLIDecisions"]
  B -->|Android| C["Deserialize to Map"]
  B -->|iOS| D["Extract LI Decisions"]
  B -->|TypeScript| E["Update Interfaces"]
  C --> F["Pass to Native Layer"]
  D --> F
  E --> F
Loading

Grey Divider

File Changes

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

Add vendor LI decisions parameter to Android module

• Add unsavedVendorLIDecisions parameter to denyAllForTCF method signature
• Deserialize vendor LI decisions using deserializePurposeLIDecisionsMap() helper
• Pass deserialized vendor decisions to native denyAllForTCF call

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


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

Update abstract method signature with vendor decisions

• Add unsavedVendorLIDecisions parameter to abstract denyAllForTCF method

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


3. ios/RNUsercentricsModule.swift ✨ Enhancement +16/-10

Add vendor decisions and refactor iOS implementation

• Add unsavedVendorLIDecisions parameter to denyAllForTCF method
• Extract decision mapping logic into new extractLIDecisionsMap helper method
• Refactor method to use helper for both purpose and vendor LI decisions
• Simplify decision extraction with reduce(into:) functional approach

ios/RNUsercentricsModule.swift


View more (2)
4. src/NativeUsercentrics.ts ✨ Enhancement +2/-1

Update TypeScript native interface with vendor decisions

• Import TCFUserDecisionOnVendor type from models
• Add unsavedVendorLIDecisions parameter to denyAllForTCF method signature

src/NativeUsercentrics.ts


5. src/Usercentrics.tsx ✨ Enhancement +3/-2

Update TypeScript wrapper with vendor decisions support

• Import TCFUserDecisionOnVendor type
• Add unsavedVendorLIDecisions parameter with default empty array to denyAllForTCF
• Pass vendor decisions to native module call

src/Usercentrics.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 12, 2026

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. iOS denyAllForTCF compile error🐞 Bug ✓ Correctness
Description
ios/RNUsercentricsModule.swift now calls usercentricsManager.denyAllForTCF with an
unsavedVendorLIDecisions argument, but ios/Manager/UsercentricsManager.swift only defines a
3-parameter denyAllForTCF. This is a build-breaking Swift compile-time mismatch.
Code

ios/RNUsercentricsModule.swift[R160-172]

   @objc func denyAllForTCF(_ fromLayer: Double,
                            consentType: Double,
                            unsavedPurposeLIDecisions: [NSDictionary],
+                             unsavedVendorLIDecisions: [NSDictionary],
                            resolve: @escaping RCTPromiseResolveBlock,
                            reject: @escaping RCTPromiseRejectBlock) -> Void {
-        var decisions: [KotlinInt: KotlinBoolean]? = nil
-        if !unsavedPurposeLIDecisions.isEmpty {
-            decisions = [:]
-            for dict in unsavedPurposeLIDecisions {
-                if let id = dict["id"] as? Int,
-                   let consent = dict["legitimateInterestConsent"] as? Bool {
-                    decisions?[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent)
-                }
+        let services = usercentricsManager.denyAllForTCF(
+            fromLayer: .initialize(from: Int(fromLayer)),
+            consentType: .initialize(from: Int(consentType)),
+            unsavedPurposeLIDecisions: extractLIDecisionsMap(unsavedPurposeLIDecisions),
+            unsavedVendorLIDecisions: extractLIDecisionsMap(unsavedVendorLIDecisions)
+        )
+        resolve(services.toListOfDictionary())
Evidence
The module calls a 4-parameter manager API, but the manager protocol/implementation only
declares/implements a 3-parameter API, so the call cannot resolve during compilation.

ios/RNUsercentricsModule.swift[160-172]
ios/Manager/UsercentricsManager.swift[28-33]
ios/Manager/UsercentricsManager.swift[115-117]

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

## Issue description
`RNUsercentricsModule.denyAllForTCF` was updated to pass `unsavedVendorLIDecisions`, but `UsercentricsManager` (protocol + implementation) still exposes the old 3-argument `denyAllForTCF`. This causes an iOS compile error.
### Issue Context
The Swift module now requires `UsercentricsManager.denyAllForTCF(..., unsavedVendorLIDecisions: ...)`.
### Fix Focus Areas
- ios/Manager/UsercentricsManager.swift[24-40]
- ios/Manager/UsercentricsManager.swift[107-117]
- ios/RNUsercentricsModule.swift[160-173]

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


2. iOS bridge export mismatch🐞 Bug ✓ Correctness
Description
JS now calls denyAllForTCF with 4 arguments, but the iOS RCT_EXTERN_METHOD export and
RNUsercentricsModuleSpec.h still declare the 3-argument variant. This will make the method
unavailable or fail at runtime / during new-architecture builds due to signature mismatch.
Code

src/Usercentrics.tsx[R110-113]

+    denyAllForTCF: async (fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType, unsavedPurposeLIDecisions: TCFUserDecisionOnPurpose[] = [], unsavedVendorLIDecisions: TCFUserDecisionOnVendor[] = []): Promise<Array<UsercentricsServiceConsent>> => {
       await RNUsercentricsModule.isReady();
-        return RNUsercentricsModule.denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions);
+        return RNUsercentricsModule.denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions, unsavedVendorLIDecisions);
   },
Evidence
The JS wrapper passes unsavedVendorLIDecisions, while the Objective-C++ bridge export and the iOS
spec header still only include unsavedPurposeLIDecisions, so the bridge doesn’t match the JS call
shape.

src/Usercentrics.tsx[110-113]
ios/RNUsercentricsModule.mm[72-76]
ios/RNUsercentricsModuleSpec.h[75-79]

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

## Issue description
The JS layer now calls `denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions, unsavedVendorLIDecisions)`, but the iOS bridge export and spec header still expose the old signature without `unsavedVendorLIDecisions`.
### Issue Context
This mismatch typically causes runtime errors like “method not recognized / wrong number of arguments” or codegen/new-arch build failures.
### Fix Focus Areas
- ios/RNUsercentricsModule.mm[72-77]
- ios/RNUsercentricsModuleSpec.h[75-80]
- ios/RNUsercentricsModule.swift[160-165]
- src/Usercentrics.tsx[110-113]

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


3. Fabric spec signature stale🐞 Bug ✓ Correctness
Description
src/NativeUsercentrics.ts updates denyAllForTCF to include unsavedVendorLIDecisions, but
src/fabric/NativeUsercentricsModule.ts still declares the old 3-argument signature. This breaks spec
consistency for the New Architecture/Fabric path.
Code

src/NativeUsercentrics.ts[R47-51]

 acceptAll(consentType: number): Promise<Array<UsercentricsServiceConsent>>;
 acceptAllForTCF(fromLayer: number, consentType: number): Promise<Array<UsercentricsServiceConsent>>;
 denyAll(consentType: number): Promise<Array<UsercentricsServiceConsent>>;
-  denyAllForTCF(fromLayer: number, consentType: number, unsavedPurposeLIDecisions: Array<TCFUserDecisionOnPurpose>): Promise<Array<UsercentricsServiceConsent>>;
+  denyAllForTCF(fromLayer: number, consentType: number, unsavedPurposeLIDecisions: Array<TCFUserDecisionOnPurpose>, unsavedVendorLIDecisions: Array<TCFUserDecisionOnVendor>): Promise<Array<UsercentricsServiceConsent>>;
 
Evidence
There are two TurboModule specs in the repo; the main spec was updated to require the new parameter,
while the Fabric spec still declares the old signature, leading to codegen/type mismatches.

src/NativeUsercentrics.ts[46-51]
src/fabric/NativeUsercentricsModule.ts[32-37]

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

## Issue description
`denyAllForTCF` was updated in `src/NativeUsercentrics.ts`, but the Fabric spec still has the old parameter list.
### Issue Context
In RN New Architecture, inconsistent specs can break codegen or lead to calling native methods with the wrong arity.
### Fix Focus Areas
- src/NativeUsercentrics.ts[46-51]
- src/fabric/NativeUsercentricsModule.ts[32-37]

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


View more (2)
4. Android tests signature mismatch 🐞 Bug ⛯ Reliability
Description
Android instrumentation tests still call RNUsercentricsModule.denyAllForTCF with the old parameter
list and mock UsercentricsSDK.denyAllForTCF with only 3 parameters, while the module now
requires/passes unsavedVendorLIDecisions. This will fail androidTest compilation.
Code

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[R152-156]

+    override fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, unsavedVendorLIDecisions: ReadableArray, promise: Promise) {
       promise.resolve(
           usercentricsProxy.instance.denyAllForTCF(
-                TCFDecisionUILayer.values()[fromLayer.toInt()], UsercentricsConsentType.values()[consentType.toInt()], unsavedPurposeLIDecisions.deserializePurposeLIDecisionsMap()
+                TCFDecisionUILayer.values()[fromLayer.toInt()], UsercentricsConsentType.values()[consentType.toInt()], unsavedPurposeLIDecisions.deserializePurposeLIDecisionsMap(), unsavedVendorLIDecisions.deserializePurposeLIDecisionsMap()
           ).toWritableArray()
Evidence
The module signature was changed to add unsavedVendorLIDecisions, but the androidTest still uses
the old method arity both for the module call and the SDK mock/verify arity.

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[151-156]
android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt[515-536]

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

## Issue description
The Android module signature for `denyAllForTCF` now includes `unsavedVendorLIDecisions`, but android instrumentation tests still compile against the old signature and mock the SDK method with the old arity.
### Issue Context
`androidTest` compilation will fail due to mismatched method signatures.
### Fix Focus Areas
- android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt[151-156]
- android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt[515-537]

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


5. iOS example tests outdated 🐞 Bug ⛯ Reliability
Description
The iOS example test still calls denyAllForTCF without the new unsavedVendorLIDecisions parameter,
but RNUsercentricsModule.denyAllForTCF now requires it. This will fail compiling the example test
target.
Code

ios/RNUsercentricsModule.swift[R160-165]

   @objc func denyAllForTCF(_ fromLayer: Double,
                            consentType: Double,
                            unsavedPurposeLIDecisions: [NSDictionary],
+                             unsavedVendorLIDecisions: [NSDictionary],
                            resolve: @escaping RCTPromiseResolveBlock,
                            reject: @escaping RCTPromiseRejectBlock) -> Void {
-        var decisions: [KotlinInt: KotlinBoolean]? = nil
-        if !unsavedPurposeLIDecisions.isEmpty {
-            decisions = [:]
-            for dict in unsavedPurposeLIDecisions {
-                if let id = dict["id"] as? Int,
-                   let consent = dict["legitimateInterestConsent"] as? Bool {
-                    decisions?[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent)
-                }
Evidence
The example test target calls the old Swift method signature, but the module method now requires the
extra vendor decisions argument.

ios/RNUsercentricsModule.swift[160-165]
example/ios/exampleTests/RNUsercentricsModuleTests.swift[319-323]

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

## Issue description
The example iOS tests still call `denyAllForTCF` using the old parameter list.
### Issue Context
The module method signature now requires `unsavedVendorLIDecisions`.
### Fix Focus Areas
- ios/RNUsercentricsModule.swift[160-165]
- example/ios/exampleTests/RNUsercentricsModuleTests.swift[319-323]

ⓘ 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

@codeant-ai codeant-ai bot added the size:S This PR changes 10-29 lines, ignoring generated files label Mar 12, 2026
@codeant-ai
Copy link

codeant-ai bot commented Mar 12, 2026

Sequence Diagram

This PR extends the denyAllForTCF flow to carry both purpose and vendor legitimate interest decisions from the JavaScript API through the React Native bridge into native execution. The core change is consistent parameter propagation across TypeScript, Android, and iOS layers before applying TCF deny all.

sequenceDiagram
    participant App
    participant UsercentricsAPI
    participant RNBridge
    participant NativeModule
    participant NativeSDK

    App->>UsercentricsAPI: denyAllForTCF with purpose decisions and vendor decisions
    UsercentricsAPI->>RNBridge: Forward both decision lists
    RNBridge->>NativeModule: Invoke denyAllForTCF with both lists
    NativeModule->>NativeSDK: Convert lists to LI maps and apply TCF deny all
    NativeSDK-->>App: Return updated service consents
Loading

Generated by CodeAnt AI

@pantoaibot
Copy link

pantoaibot bot commented Mar 12, 2026

PR Summary:

Add support for passing unsaved vendor legitimate-interest decisions into denyAllForTCF across Android, iOS, and JS interfaces.

  • Android (android/src/main/.../RNUsercentricsModule.kt, RNUsercentricsModuleSpec.kt)

    • Added unsavedVendorLIDecisions parameter to denyAllForTCF and pass it to usercentricsProxy.instance.denyAllForTCF via deserializePurposeLIDecisionsMap().
    • Updated the module spec signature to include the new ReadableArray parameter.
  • iOS (ios/RNUsercentricsModule.swift)

    • Added unsavedVendorLIDecisions parameter to denyAllForTCF.
    • Replaced manual conversion logic with a helper extractLIDecisionsMap(_:) to convert arrays of NSDictionary into KotlinInt: KotlinBoolean maps and pass both purpose and vendor LI maps to denyAllForTCF.
    • Keeps behavior identical but now includes vendor LI decisions if provided.
  • JS/TypeScript (src/NativeUsercentrics.ts, src/Usercentrics.tsx)

    • Updated Spec and wrapper to accept TCFUserDecisionOnVendor and an unsavedVendorLIDecisions argument.
    • User-facing denyAllForTCF gains an optional unsavedVendorLIDecisions param (defaults to []) and forwards it to the native module.
    • Updated imports/types to include TCFUserDecisionOnVendor.

Notes / migration impact

  • JS wrapper provides a default empty array, so typical JS usage is non-breaking. Native signature changed; any direct native consumers or custom native bridges must be updated to match the new parameter.

Reviewed by Panto AI

@codeant-ai
Copy link

codeant-ai bot commented Mar 12, 2026

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Possible Bug
    Using enum lookup with raw indices (e.g. TCFDecisionUILayer.values()[fromLayer.toInt()] and UsercentricsConsentType.values()[consentType.toInt()]) can throw IndexOutOfBoundsException if the incoming numeric values are out of range. Validate or safely map indexes before use.

  • Error handling
    The method deserializes unsavedPurposeLIDecisions and unsavedVendorLIDecisions inline and then immediately resolves the Promise. If deserialization throws an exception (or returns unexpected data), the exception will propagate and the Promise may never be rejected with a clear error. Wrap deserialization and the call in safe error handling and reject the Promise on error.

  • Null-safety risk
    The new unsavedVendorLIDecisions parameter is typed as non-null ReadableArray. If JS does not pass this argument (or passes null), implementations may receive null at runtime and risk NPEs. Consider making the parameter nullable or adding defensive handling.

  • Integer conversion
    The code converts id (Swift Int) to Int32 when creating KotlinInt. If id comes from an external source it may exceed Int32 range, causing overflow or incorrect values. Validate bounds before conversion.

  • Native bridge mismatch
    The denyAllForTCF signature was extended with two new array parameters. Ensure the native module implementations (both platforms) and any JS consumers are updated accordingly — mismatched method arity between JS interface and native module can cause runtime failures.

@pantoaibot
Copy link

pantoaibot bot commented Mar 12, 2026

Reviewed up to commit:054cfd53461551e74c956206f146b724df5447b7

Additional Suggestion
ios/RNUsercentricsModule.mm, line:57-87 Objective-C bridge declarations were not updated. The RN export for denyAllForTCF currently declares only unsavedPurposeLIDecisions (see ios/RNUsercentricsModule.mm lines 57-87). Add the new parameter unsavedVendorLIDecisions:(NSArray)unsavedVendorLIDecisions to the RCT_EXTERN_METHOD declaration and regenerate the bridging headers so the JS-to-native bridge matches the Swift implementation.
RCT_EXTERN_METHOD(denyAllForTCF:(double)fromLayer
                  consentType:(double)consentType
                  unsavedPurposeLIDecisions:(NSArray)unsavedPurposeLIDecisions
                  unsavedVendorLIDecisions:(NSArray)unsavedVendorLIDecisions
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
ios/RNUsercentricsModuleSpec.h, line:60-90 The Objective-C module spec header still exposes the old denyAllForTCF signature (unsavedPurposeLIDecisions only). Update the method signature in RNUsercentricsModuleSpec.h to include unsavedVendorLIDecisions:(NSArray *)unsavedVendorLIDecisions so generated bindings and consumers have the correct signature.
// Consent Actions
- (void)acceptAll:(double)consentType
          resolve:(RCTPromiseResolveBlock)resolve
           reject:(RCTPromiseRejectBlock)reject;

- (void)acceptAllForTCF:(double)fromLayer
            consentType:(double)consentType
                resolve:(RCTPromiseResolveBlock)resolve
                 reject:(RCTPromiseRejectBlock)reject;

- (void)denyAll:(double)consentType
        resolve:(RCTPromiseResolveBlock)resolve
         reject:(RCTPromiseRejectBlock)reject;

- (void)denyAllForTCF:(double)fromLayer
          consentType:(double)consentType
unsavedPurposeLIDecisions:(NSArray<NSDictionary *> *)unsavedPurposeLIDecisions
 unsavedVendorLIDecisions:(NSArray<NSDictionary *> *)unsavedVendorLIDecisions
              resolve:(RCTPromiseResolveBlock)resolve
               reject:(RCTPromiseRejectBlock)reject;

- (void)saveDecisions:(NSArray<NSDictionary *> *)decisions
          consentType:(double)consentType
              resolve:(RCTPromiseResolveBlock)resolve
               reject:(RCTPromiseRejectBlock)reject;

- (void)saveDecisionsForTCF:(NSDictionary *)tcfDecisions
                  fromLayer:(double)fromLayer
              saveDecisions:(NSArray<NSDictionary *> *)saveDecisions
                consentType:(double)consentType
                    resolve:(RCTPromiseResolveBlock)resolve
                     reject:(RCTPromiseRejectBlock)reject;
ios/Manager/UsercentricsManager.swift, line:16-131 The manager protocol/implementation currently declares denyAllForTCF(with only unsavedPurposeLIDecisions) (see this file lines 16-46 and 100-131 in reference). Update the protocol and all implementations to accept unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]? as an additional parameter. Update all fakes and example/samples that implement this protocol (FakeUsercentricsManager.swift in example/sample) to avoid runtime/compile failures.
// ios/Manager/UsercentricsManager.swift
protocol UsercentricsManaging {
    func getControllerId() -> String
    func getConsents() -> [UsercentricsServiceConsent]
    func getCMPData() -> UsercentricsCMPData
    func getUserSessionData() -> String
    func getUSPData() -> CCPAData
    func getTCFData(callback: @escaping (TCFData) -> Void)
    func getABTestingVariant() -> String?
    func getAdditionalConsentModeData() -> AdditionalConsentModeData

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

    func acceptAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
    func acceptAll(consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]

    func denyAllForTCF(
        fromLayer: TCFDecisionUILayer,
        consentType: UsercentricsConsentType,
        unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
        unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    ) -> [UsercentricsServiceConsent]
    func denyAll(consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]

    func saveDecisionsForTCF(
        tcfDecisions: TCFUserDecisions,
        fromLayer: TCFDecisionUILayer,
        serviceDecisions: [UserDecision],
        consentType: UsercentricsConsentType
    ) -> [UsercentricsServiceConsent]
    func saveDecisions(decisions: [UserDecision], consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
    func saveOptOutForCCPA(isOptedOut: Bool, consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
    func setCMPId(id: Int32)
    func setABTestingVariant(variant: String)
    func track(event: UsercentricsAnalyticsEventType)

    func clearUserSession(onSuccess: @escaping ((UsercentricsReadyStatus) -> Void), onError: @escaping ((Error) -> Void))
}

final class UsercentricsManager: UsercentricsManaging {
    // ... other methods ...

    func denyAllForTCF(
        fromLayer: TCFDecisionUILayer,
        consentType: UsercentricsConsentType,
        unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
        unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    ) -> [UsercentricsServiceConsent] {
        return UsercentricsCore.shared.denyAllForTCF(
            fromLayer: fromLayer,
            consentType: consentType,
            unsavedPurposeLIDecisions: unsavedPurposeLIDecisions,
            unsavedVendorLIDecisions: unsavedVendorLIDecisions
        )
    }
}

// example/ios/exampleTests/Fake/FakeUsercentricsManager.swift
final class FakeUsercentricsManager: UsercentricsManaging {
    // ... other fakes ...

    var denyAllForTCFConsentType: UsercentricsConsentType?
    var denyAllForTCFFromLayer: TCFDecisionUILayer?
    var denyAllForTCFUnsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?
    var denyAllForTCFUnsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    var denyAllForTCFResponse: [UsercentricsServiceConsent]?

    func denyAllForTCF(
        fromLayer: TCFDecisionUILayer,
        consentType: UsercentricsConsentType,
        unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
        unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    ) -> [UsercentricsServiceConsent] {
        self.denyAllForTCFConsentType = consentType
        self.denyAllForTCFFromLayer = fromLayer
        self.denyAllForTCFUnsavedPurposeLIDecisions = unsavedPurposeLIDecisions
        self.denyAllForTCFUnsavedVendorLIDecisions = unsavedVendorLIDecisions
        return denyAllForTCFResponse!
    }
}

// sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift
final class FakeUsercentricsManager: UsercentricsManaging {
    // ... other fakes ...

    var denyAllForTCFConsentType: UsercentricsConsentType?
    var denyAllForTCFFromLayer: TCFDecisionUILayer?
    var denyAllForTCFUnsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?
    var denyAllForTCFUnsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    var denyAllForTCFResponse: [UsercentricsServiceConsent]?

    func denyAllForTCF(
        fromLayer: TCFDecisionUILayer,
        consentType: UsercentricsConsentType,
        unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
        unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
    ) -> [UsercentricsServiceConsent] {
        self.denyAllForTCFConsentType = consentType
        self.denyAllForTCFFromLayer = fromLayer
        self.denyAllForTCFUnsavedPurposeLIDecisions = unsavedPurposeLIDecisions
        self.denyAllForTCFUnsavedVendorLIDecisions = unsavedVendorLIDecisions
        return denyAllForTCFResponse!
    }
}
src/fabric/NativeUsercentricsModule.ts, line:21-51 The Fabric/TurboModule declaration (src/fabric/NativeUsercentricsModule.ts) still exposes denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions) without the new unsavedVendorLIDecisions parameter (reference lines 21-51). Update this Fabric interface to match the changed signature so TurboModule consumers and type-checking remain consistent. After changing, rebuild TypeScript artifacts and ensure TurboModuleRegistry.get consumers are type-compatible.
// src/fabric/NativeUsercentricsModule.ts
export interface Spec extends TurboModule {
  // ...

  // Consent Actions
  acceptAll(consentType: number): Promise<Array<Object>>;
  acceptAllForTCF(fromLayer: number, consentType: number): Promise<Array<Object>>;
  denyAll(consentType: number): Promise<Array<Object>>;
  denyAllForTCF(
    fromLayer: number,
    consentType: number,
    unsavedPurposeLIDecisions: Array<Object>,
    unsavedVendorLIDecisions: Array<Object>
  ): Promise<Array<Object>>;
  
  saveDecisions(decisions: Array<Object>, consentType: number): Promise<Array<Object>>;
  saveDecisionsForTCF(
    tcfDecisions: Object,
    fromLayer: number,
    saveDecisions: Array<Object>,
    consentType: number
  ): Promise<Array<Object>>;
  // ...
}
Others - Testing and example apps: many tests and example/sample code still call denyAllForTCF with the old 3-argument signature (see android test RNUsercentricsModuleTest.kt lines ~501-549 and ios RNUsercentricsModuleTests.swift lines ~304-363 in references). Update all unit tests, fakes, and example usages to include the new unsavedVendorLIDecisions parameter (use [] or null as appropriate). Run the test suites (both Android and iOS) after changes to catch regressions.

Reviewed by Panto AI

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

The pull request extends the denyAllForTCF API to accept an additional unsavedVendorLIDecisions parameter and propagates vendor LI decision maps through TypeScript, native bridges (Android/iOS), manager, and core calls.

Changes

Cohort / File(s) Summary
Android Native Modules
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt, android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
Added unsavedVendorLIDecisions: ReadableArray to denyAllForTCF signatures; deserializes vendor decisions and passes vendor map into usercentricsProxy.instance.denyAllForTCF.
iOS Native Module & Bridge
ios/RNUsercentricsModule.swift, ios/RNUsercentricsModule.mm
Extended denyAllForTCF signature to include unsavedVendorLIDecisions; added extractLIDecisionsMap helper in Swift to convert arrays to [KotlinInt: KotlinBoolean]?; updated ObjC bridge extern to expose new parameter.
iOS Manager / Core Binding
ios/Manager/UsercentricsManager.swift
Updated protocol and implementation of denyAllForTCF to accept and forward unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]? to core.
TypeScript Public API & Fabric Spec
src/Usercentrics.tsx, src/NativeUsercentrics.ts, src/fabric/NativeUsercentricsModule.ts
Imported TCFUserDecisionOnVendor type and extended denyAllForTCF signatures to accept unsavedVendorLIDecisions (default [] in public API) and forward it to native module calls.

Sequence Diagram(s)

sequenceDiagram
    participant JS as JS Layer
    participant RN as RN Native Module
    participant Platform as Platform Bridge (iOS/Android)
    participant Core as Usercentrics Core

    JS->>RN: denyAllForTCF(fromLayer, consentType, unsavedPurpose..., unsavedVendor...)
    RN->>Platform: serialize arrays -> (purposeMap, vendorMap)
    Platform->>Core: denyAllForTCF(fromLayer, consentType, purposeMap, vendorMap)
    Core-->>Platform: updated services
    Platform-->>RN: services (resolved)
    RN-->>JS: Promise.resolve(services)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 1/5

Suggested reviewers

  • uc-brunosilva
  • uc-brunosouza

Poem

🐰
I hop through bridged decisions bright,
Mapping vendors, purposes — right.
With tiny paws I pass the map,
Deny the LI, then home I nap. 🥕✨

🚥 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 PR title accurately describes the main change: adding unsavedVendorLIDecisions parameter to denyAllForTCF across all platform implementations, which is the primary focus of this changeset.

✏️ 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-3297-LI-Deny-all-vendors
📝 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

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

@codeant-ai
Copy link

codeant-ai bot commented Mar 12, 2026

CodeAnt AI finished reviewing your PR.

@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: [❌]

Failed test name: ""

Failure summary:

  • The GitHub Action failed during the iOS build/test step because Swift compilation failed in
    ios/RNUsercentricsModule.swift.
  • Xcode reported a compiler error at ios/RNUsercentricsModule.swift:170:39: extra argument
    'unsavedVendorLIDecisions' in call, indicating the code is calling a function/initializer with an
    argument label that is not present in the target API signature.
  • Because the build failed, Xcode testing was cancelled (Testing cancelled because the build failed)
    and the job exited with code 65.
Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

950:  Installing React-RCTText (0.81.4)
951:  Installing React-RCTVibration (0.81.4)
952:  Installing React-RuntimeApple (0.81.4)
953:  Installing React-RuntimeCore (0.81.4)
954:  Installing React-RuntimeHermes (0.81.4)
955:  Installing React-callinvoker (0.81.4)
956:  Installing React-cxxreact (0.81.4)
957:  Installing React-debug (0.81.4)
958:  Installing React-defaultsnativemodule (0.81.4)
959:  Installing React-domnativemodule (0.81.4)
960:  Installing React-featureflags (0.81.4)
961:  Installing React-featureflagsnativemodule (0.81.4)
962:  Installing React-graphics (0.81.4)
963:  Installing React-hermes (0.81.4)
964:  Installing React-idlecallbacksnativemodule (0.81.4)
965:  Installing React-jserrorhandler (0.81.4)
966:  Installing React-jsi (0.81.4)
...

1204:  ▸ Copying ExecutionContextManager.h
1205:  ▸ Copying ExecutionContext.h
1206:  ▸ Copying ConsoleMessage.h
1207:  ▸ Copying Base64.h
1208:  ▸ Copying React-jsiexecutor-umbrella.h
1209:  ▸ Copying JSINativeModules.h
1210:  ▸ Copying JSIExecutor.h
1211:  ▸ Copying threadsafe.h
1212:  ▸ Copying jsilib.h
1213:  ▸ Copying jsi.h
1214:  ▸ Copying jsi-inl.h
1215:  ▸ Copying instrumentation.h
1216:  ▸ Copying decorator.h
1217:  ▸ Copying React-jsi-umbrella.h
1218:  ▸ Copying JSIDynamic.h
1219:  ▸ Copying React-jserrorhandler-umbrella.h
1220:  ▸ Copying React-idlecallbacksnativemodule-umbrella.h
1221:  ▸ Copying React-hermes-umbrella.h
1222:  ▸ Copying HermesExecutorFactory.h
1223:  ▸ Copying React-graphics-umbrella.h
1224:  ▸ Copying React-featureflagsnativemodule-umbrella.h
1225:  ▸ Copying React-featureflags-umbrella.h
1226:  ▸ Copying React-domnativemodule-umbrella.h
1227:  ▸ Copying React-defaultsnativemodule-umbrella.h
1228:  ▸ Copying React-debug-umbrella.h
1229:  ▸ Copying TraceSection.h
1230:  ▸ Copying SystraceSection.h
1231:  ▸ Copying SharedProxyCxxModule.h
1232:  ▸ Copying RecoverableError.h
1233:  ▸ Copying ReactNativeVersion.h
...

1236:  ▸ Copying RAMBundleRegistry.h
1237:  ▸ Copying NativeToJsBridge.h
1238:  ▸ Copying NativeModule.h
1239:  ▸ Copying MoveWrapper.h
1240:  ▸ Copying ModuleRegistry.h
1241:  ▸ Copying MethodCall.h
1242:  ▸ Copying MessageQueueThread.h
1243:  ▸ Copying JsArgumentHelpers.h
1244:  ▸ Copying JsArgumentHelpers-inl.h
1245:  ▸ Copying JSModulesUnbundle.h
1246:  ▸ Copying JSIndexedRAMBundle.h
1247:  ▸ Copying JSExecutor.h
1248:  ▸ Copying JSBundleType.h
1249:  ▸ Copying JSBigString.h
1250:  ▸ Copying Instance.h
1251:  ▸ Copying ErrorUtils.h
1252:  ▸ Copying CxxNativeModule.h
...

1595:  ▸ Copying RCTI18nUtil.h
1596:  ▸ Copying RCTI18nManager.h
1597:  ▸ Copying RCTHTTPRequestHandler.h
1598:  ▸ Copying RCTGIFImageDecoder.h
1599:  ▸ Copying RCTFrameUpdate.h
1600:  ▸ Copying RCTFrameAnimation.h
1601:  ▸ Copying RCTFont.h
1602:  ▸ Copying RCTFileRequestHandler.h
1603:  ▸ Copying RCTFileReaderModule.h
1604:  ▸ Copying RCTFPSGraph.h
1605:  ▸ Copying RCTExceptionsManager.h
1606:  ▸ Copying RCTEventEmitter.h
1607:  ▸ Copying RCTEventDispatcherProtocol.h
1608:  ▸ Copying RCTEventDispatcher.h
1609:  ▸ Copying RCTEventAnimation.h
1610:  ▸ Copying RCTErrorInfo.h
1611:  ▸ Copying RCTErrorCustomizer.h
1612:  ▸ Copying RCTDynamicTypeRamp.h
...

1824:  ▸ Compiling SocketRocket-dummy.m
1825:  ▸ Compiling SRWebSocket.m
1826:  ▸ Compiling SRURLUtilities.m
1827:  ▸ Compiling SRSecurityPolicy.m
1828:  ▸ Compiling SRSIMDHelpers.m
1829:  ▸ Compiling SRRunLoopThread.m
1830:  ▸ Compiling SRRandom.m
1831:  ▸ Compiling SRProxyConnect.m
1832:  ▸ Compiling SRPinningSecurityPolicy.m
1833:  ▸ Compiling SRMutex.m
1834:  ▸ Compiling SRLog.m
1835:  ▸ Compiling SRIOConsumerPool.m
1836:  ▸ Compiling SRIOConsumer.m
1837:  ▸ Compiling SRHash.m
1838:  ▸ Compiling SRHTTPConnectMessage.m
1839:  ▸ Compiling SRError.m
1840:  ▸ Compiling SRDelegateController.m
...

2148:  ▸ Compiling RCTInputAccessoryViewManager.mm
2149:  ▸ Compiling RCTInputAccessoryViewContent.mm
2150:  ▸ Compiling RCTInputAccessoryView.mm
2151:  ▸ Compiling RCTInputAccessoryShadowView.mm
2152:  ▸ Compiling RCTDynamicTypeRamp.mm
2153:  ▸ Compiling RCTConvert+Text.mm
2154:  ▸ Compiling RCTBaseTextViewManager.mm
2155:  ▸ Compiling RCTBaseTextShadowView.mm
2156:  ▸ Compiling RCTBaseTextInputViewManager.mm
2157:  ▸ Compiling RCTBaseTextInputView.mm
2158:  ▸ Compiling RCTBaseTextInputShadowView.mm
2159:  ▸ Compiling RCTBackedTextInputDelegateAdapter.mm
2160:  ▸ Touching ReactCommon.framework (in target 'ReactCommon' from project 'Pods')
2161:  ▸ Compiling RCTText_vers.c
2162:  ▸ Compiling React-RCTText-dummy.m
2163:  ▸ Processing React-jserrorhandler-Info.plist
2164:  ▸ Compiling StackTraceParser.cpp
2165:  ▸ Running script 'Create Symlinks to Header Folders'
2166:  ▸ Compiling JsErrorHandler.cpp
2167:  ▸ Compiling NSTextStorage+FontScaling.m
2168:  ▸ Compiling React_jserrorhandler_vers.c
2169:  ▸ Compiling React-jserrorhandler-dummy.m
2170:  ▸ Touching React_graphics.framework (in target 'React-graphics' from project 'Pods')
...

2261:  ▸ Compiling RCTModalManager.m
2262:  ▸ Compiling RCTModalHostViewManager.m
2263:  ▸ Compiling RCTModalHostViewController.m
2264:  ▸ Compiling RCTModalHostView.m
2265:  ▸ Compiling RCTLayoutAnimationGroup.m
2266:  ▸ Compiling RCTLayoutAnimation.m
2267:  ▸ Compiling RCTLayout.m
2268:  ▸ Compiling RCTKeyCommands.m
2269:  ▸ Compiling RCTJSThread.m
2270:  ▸ Compiling RCTJSStackFrame.m
2271:  ▸ Compiling RCTI18nUtil.m
2272:  ▸ Compiling RCTImageSource.m
2273:  ▸ Compiling RCTFrameUpdate.m
2274:  ▸ Compiling RCTEventEmitter.m
2275:  ▸ Compiling RCTEventDispatcher.m
2276:  ▸ Compiling RCTErrorInfo.m
2277:  ▸ Compiling RCTDisplayLink.m
...

2283:  ▸ Compiling RCTConstants.m
2284:  ▸ Compiling RCTComponentEvent.m
2285:  ▸ Compiling RCTCallableJSModules.m
2286:  ▸ Compiling RCTConvert+CoreLocation.m
2287:  ▸ Compiling RCTBundleManager.m
2288:  ▸ Compiling RCTBridgeModuleDecorator.m
2289:  ▸ Compiling RCTBridgeConstants.m
2290:  ▸ Compiling RCTBorderDrawing.m
2291:  ▸ Compiling RCTAssert.m
2292:  ▸ Compiling RCTActivityIndicatorViewManager.m
2293:  ▸ Running script 'Create Symlinks to Header Folders'
2294:  ▸ Compiling RCTActivityIndicatorView.m
2295:  ▸ Compiling RCTTypedModuleConstants.mm
2296:  ▸ Compiling RCTConvertHelpers.mm
2297:  ▸ Compiling RCTTypeSafety_vers.c
2298:  ▸ Touching React_jserrorhandler.framework (in target 'React-jserrorhandler' from project 'Pods')
2299:  ▸ Touching React.framework (in target 'React-Core' from project 'Pods')
...

2770:  ❌  /Users/runner/work/react-native-sdk/react-native-sdk/ios/RNUsercentricsModule.swift:170:39: extra argument 'unsavedVendorLIDecisions' in call
2771:  unsavedVendorLIDecisions: extractLIDecisionsMap(unsavedVendorLIDecisions)
2772:  ~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~
2773:  ▸ Compiling RNCWebViewImpl.m
2774:  ▸ Compiling RNCWebViewDecisionManager.m
2775:  ▸ Compiling RNCWKProcessPoolManager.m
2776:  ▸ Compiling RCTConvert+WKDataDetectorTypes.m
2777:  ⚠️  /Users/runner/work/react-native-sdk/react-native-sdk/ios/Extensions/TCFUserDecisions+Dict.swift:76:13: variable 'consent' was never mutated; consider changing to 'let' constant
2778:  var consent = dict["consent"] as? Bool
2779:  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2780:  Run script build phase '[CP-User] [Hermes] Replace Hermes for the right configuration, if needed' 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 'hermes-engine' from project 'Pods')
2781:  Run script build phase 'Bundle React Native code and images' 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 'sample' from project 'sample')
2782:  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 'fmt' from project 'Pods')
2783:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/node_modules/react-native/ReactCommon/yoga/yoga/enums/Align.h (in target 'Yoga' from project 'Pods')
2784:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/node_modules/react-native/ReactCommon/yoga/yoga/enums/FlexDirection.h (in target 'Yoga' from project 'Pods')
2785:  Testing failed:
2786:  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 'Yoga' from project 'Pods')
2787:  Extra argument 'unsavedVendorLIDecisions' in call
2788:  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 'ReactCommon' from project 'Pods')
2789:  Testing cancelled because the build failed.
2790:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/ComponentDescriptors.h (in target 'ReactCodegen' from project 'Pods')
2791:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/ComponentDescriptors.h (in target 'ReactCodegen' from project 'Pods')
2792:  ** TEST FAILED **
2793:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/ComponentDescriptors.h (in target 'ReactCodegen' from project 'Pods')
2794:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/EventEmitters.h (in target 'ReactCodegen' from project 'Pods')
2795:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/EventEmitters.h (in target 'ReactCodegen' from project 'Pods')
2796:  The following build commands failed:
2797:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/EventEmitters.h (in target 'ReactCodegen' from project 'Pods')
2798:  SwiftCompile normal arm64 /Users/runner/work/react-native-sdk/react-native-sdk/ios/RNUsercentricsModule.swift (in target 'react-native-usercentrics' from project 'Pods')
2799:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/Props.h (in target 'ReactCodegen' from project 'Pods')
2800:  SwiftCompile normal arm64 Compiling\ BannerSettings+Dict.swift,\ CCPAData+Dict.swift,\ DispatchQueueManager.swift,\ PresentationViewController.swift,\ ReadyStatus+Dict.swift,\ RNUsercentricsModule.swift,\ RNUsercentricsModuleError.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/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 /Users/runner/work/react-native-sdk/react-native-sdk/ios/Errors/RNUsercentricsModuleError.swift (in target 'react-native-usercentrics' from project 'Pods')
2801:  Skipping duplicate build file in Copy Headers build phase: /Users/runner/work/react-native-sdk/react-native-sdk/sample/ios/build/generated/ios/react/renderer/components/safeareacontext/Props.h (in target 'ReactCodegen' from project 'Pods')
...

2825:  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')
2826:  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')
2827:  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')
2828:  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')
2829:  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')
2830:  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')
2831:  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')
2832:  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')
2833:  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')
2834:  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')
2835:  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')
2836:  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')
2837:  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')
2838:  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')
2839:  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')
2840:  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')
2841:  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')
...

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 'React-defaultsnativemodule' 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-debug' 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-RuntimeHermes' 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-RuntimeCore' 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-RuntimeApple' 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-RCTRuntime' 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-RCTFBReactNativeSpec' 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-NativeModulesApple' 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-Mapbuffer' 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-ImageManager' 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-FabricImage' 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-FabricComponents' 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-Fabric' 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 'RCT-Folly' from project 'Pods')
2860:  Testing workspace sample with scheme sample
2861:  (3 failures)
2862:  ##[error]Process completed with exit code 65.
2863:  ##[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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/RNUsercentricsModule.swift`:
- Around line 175-180: The extractLIDecisionsMap helper currently drops entries
when "legitimateInterestConsent" is missing; update it so for each dict with an
"id" (function extractLIDecisionsMap), if "legitimateInterestConsent" is present
use its Bool value, otherwise treat it as false (like Android), and always
insert result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consentValue) so
missing keys map to false instead of being omitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf3278a2-345d-4905-a0a3-a7dcffaaab36

📥 Commits

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

📒 Files selected for processing (5)
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
  • ios/RNUsercentricsModule.swift
  • src/NativeUsercentrics.ts
  • src/Usercentrics.tsx

@codeant-ai
Copy link

codeant-ai bot commented Mar 12, 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
Copy link

codeant-ai bot commented Mar 12, 2026

Sequence Diagram

This PR extends the denyAllForTCF flow to carry both unsaved purpose and vendor legitimate interest decisions from JavaScript through native bindings into the core SDK. As a result, returned service consents reflect both decision sets when deny all is executed.

sequenceDiagram
    participant App
    participant JS SDK
    participant Native Bridge
    participant Usercentrics Core

    App->>JS SDK: Call denyAllForTCF with purpose and vendor LI decisions
    JS SDK->>Native Bridge: Forward fromLayer consentType and both decision lists
    Native Bridge->>Native Bridge: Convert both decision lists to LI maps
    Native Bridge->>Usercentrics Core: denyAllForTCF with purpose and vendor LI maps
    Usercentrics Core-->>App: Return updated service consents
Loading

Generated by CodeAnt AI

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

codeant-ai bot commented Mar 12, 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/Manager/UsercentricsManager.swift`:
- Around line 115-117: The FakeUsercentricsManager mock's denyAllForTCF
signature doesn't match the UsercentricsManager protocol (missing
unsavedVendorLIDecisions); update FakeUsercentricsManager.den yAllForTCF to the
four-parameter signature (fromLayer: TCFDecisionUILayer, consentType:
UsercentricsConsentType, unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?) and add a stored property
denyAllForTCFUnsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]? to capture
the argument; adjust any test usages to pass the new parameter and assign it
into the new property so the mock behavior matches UsercentricsCore/shared
expectations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fa9419a-eb72-4f24-b356-85d99f932600

📥 Commits

Reviewing files that changed from the base of the PR and between 054cfd5 and 4475503.

📒 Files selected for processing (3)
  • ios/Manager/UsercentricsManager.swift
  • ios/RNUsercentricsModule.mm
  • src/fabric/NativeUsercentricsModule.ts

@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:S This PR changes 10-29 lines, ignoring generated files and removed size:S This PR changes 10-29 lines, ignoring generated files labels Mar 18, 2026
@codeant-ai
Copy link

codeant-ai bot commented Mar 18, 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)

205-210: ⚠️ Potential issue | 🟠 Major

Keep missing LI flags consistent with Android.

android/src/main/java/com/usercentrics/reactnative/extensions/UserDecisionExtensions.kt defaults a missing legitimateInterestConsent to false, but this helper drops the entry entirely. The same JS payload can therefore produce different deny-all inputs on iOS and Android.

Suggested parity fix
     private func extractLIDecisionsMap(_ decisions: [NSDictionary]) -> [KotlinInt: KotlinBoolean]? {
         guard !decisions.isEmpty else { return nil }
         return decisions.reduce(into: [:]) { result, dict in
-            if let id = dict["id"] as? Int,
-               let consent = dict["legitimateInterestConsent"] as? Bool {
-                result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent)
-            }
+            guard let id = dict["id"] as? Int else { return }
+            let consent = (dict["legitimateInterestConsent"] as? Bool) ?? false
+            result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent)
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/RNUsercentricsModule.swift` around lines 205 - 210, The
extractLIDecisionsMap function currently drops entries when
"legitimateInterestConsent" is missing, causing iOS to differ from Android;
update extractLIDecisionsMap so that when a valid "id" is found but
"legitimateInterestConsent" is absent or not a Bool, you still add an entry
mapping KotlinInt(int: Int32(id)) to KotlinBoolean(bool: false) (retain skipping
only when "id" is missing or invalid), ensuring parity with the Android
UserDecisionExtensions behavior.
🤖 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 205-210: The extractLIDecisionsMap function currently drops
entries when "legitimateInterestConsent" is missing, causing iOS to differ from
Android; update extractLIDecisionsMap so that when a valid "id" is found but
"legitimateInterestConsent" is absent or not a Bool, you still add an entry
mapping KotlinInt(int: Int32(id)) to KotlinBoolean(bool: false) (retain skipping
only when "id" is missing or invalid), ensuring parity with the Android
UserDecisionExtensions behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6742151f-3703-45e9-bbeb-20cd3f485d82

📥 Commits

Reviewing files that changed from the base of the PR and between 4475503 and 7cc5a45.

📒 Files selected for processing (8)
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
  • ios/Manager/UsercentricsManager.swift
  • ios/RNUsercentricsModule.mm
  • ios/RNUsercentricsModule.swift
  • src/NativeUsercentrics.ts
  • src/Usercentrics.tsx
  • src/fabric/NativeUsercentricsModule.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/Usercentrics.tsx
  • ios/Manager/UsercentricsManager.swift
  • android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt

@islameldesoky95 islameldesoky95 merged commit c096115 into master Mar 18, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants