feat(MSDK-3297): pass unsavedVendorLIDecisions through denyAllForTCF#190
feat(MSDK-3297): pass unsavedVendorLIDecisions through denyAllForTCF#190islameldesoky95 wants to merge 2 commits intomasterfrom
Conversation
|
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 · |
Review Summary by QodoPass unsavedVendorLIDecisions through denyAllForTCF on all layers
WalkthroughsDescription• 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 Diagramflowchart 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
File Changes1. android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
|
Code Review by Qodo
1.
|
Sequence DiagramThis 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
Generated by CodeAnt AI |
|
PR Summary: Add support for passing unsaved vendor legitimate-interest decisions into denyAllForTCF across Android, iOS, and JS interfaces.
Notes / migration impact
|
Nitpicks 🔍
|
| 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() |
There was a problem hiding this comment.
4. Android tests signature mismatch 🐞 Bug ⛯ Reliability
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.
Agent Prompt
### 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
| @objc func denyAllForTCF(_ fromLayer: Double, | ||
| consentType: Double, | ||
| unsavedPurposeLIDecisions: [NSDictionary], | ||
| unsavedVendorLIDecisions: [NSDictionary], | ||
| resolve: @escaping RCTPromiseResolveBlock, | ||
| reject: @escaping RCTPromiseRejectBlock) -> Void { |
There was a problem hiding this comment.
5. Ios example tests outdated 🐞 Bug ⛯ Reliability
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.
Agent Prompt
### 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
| @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()) | ||
| } | ||
|
|
||
| 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) | ||
| } |
There was a problem hiding this comment.
[CRITICAL_BUG] You added a new parameter unsavedVendorLIDecisions and now call usercentricsManager.denyAllForTCF(...) with both unsavedPurposeLIDecisions and unsavedVendorLIDecisions (see new lines ~160-171). The UsercentricsManager protocol and its implementations still expose denyAllForTCF(fromLayer:..., consentType:..., unsavedPurposeLIDecisions:...) (reference: ios/Manager/UsercentricsManager.swift lines 16-46 and 100-131). This will cause a compile error. Actionable steps: update the UsercentricsManager protocol to accept the new unsavedVendorLIDecisions parameter, update all concrete implementations / fake implementations (example/sample projects and tests), and run a clean build. Also ensure the new helper extractLIDecisionsMap handles edge cases and returns nil when appropriate (you already guard emptiness, good).
// ios/Manager/UsercentricsManager.swift
protocol UsercentricsManaging {
// ... existing methods ...
func denyAllForTCF(
fromLayer: TCFDecisionUILayer,
consentType: UsercentricsConsentType,
unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?,
unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?
) -> [UsercentricsServiceConsent]
}
final class UsercentricsManager: UsercentricsManaging {
// ... existing impl ...
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
class FakeUsercentricsManager: UsercentricsManaging {
// ... existing 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!
}
}
// ios/RNUsercentricsModule.mm
RCT_EXTERN_METHOD(denyAllForTCF:(double)fromLayer
consentType:(double)consentType
unsavedPurposeLIDecisions:(NSArray)unsavedPurposeLIDecisions
unsavedVendorLIDecisions:(NSArray)unsavedVendorLIDecisions
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
// ios/RNUsercentricsModuleSpec.h
- (void)denyAllForTCF:(double)fromLayer
consentType:(double)consentType
unsavedPurposeLIDecisions:(NSArray<NSDictionary *> *)unsavedPurposeLIDecisions
unsavedVendorLIDecisions:(NSArray<NSDictionary *> *)unsavedVendorLIDecisions
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject;| 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() |
There was a problem hiding this comment.
[REFACTORING] You reuse deserializePurposeLIDecisionsMap() for unsavedVendorLIDecisions (line ~155). If the deserializer is purpose-specific it may be misleading; either rename the helper to a generic deserializeLIDecisionsMap() if it handles both purposes and vendors, or add a separate vendor-specific deserializer for clarity. This avoids confusion for future maintainers and potential subtle bugs if vendor/purpose payloads differ.
@ReactMethod
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(),
unsavedVendorLIDecisions.deserializeVendorLIDecisionsMap()
).toWritableArray()
)
}|
Reviewed up to commit:054cfd53461551e74c956206f146b724df5447b7 Additional Suggestionios/RNUsercentricsModule.mm, line:57-87Objective-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-90The 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-131The 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-51The 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. |
📝 WalkthroughWalkthroughThe pull request extends the denyAllForTCF method across Android, iOS (including ObjC bridge), and TypeScript layers to accept an additional unsavedVendorLIDecisions parameter and propagate vendor LI decision maps through native bridges and manager/core calls. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
| 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() |
There was a problem hiding this comment.
Suggestion: unsavedVendorLIDecisions is deserialized with a helper that force-defaults missing legitimateInterestConsent values to false. Since this field is optional in the JS model, missing values get converted into explicit denials, which can overwrite vendor LI state incorrectly. Build the vendor map by only including entries where legitimateInterestConsent is present. [logic error]
Severity Level: Critical 🚨
- ❌ Android denies vendor LI when value omitted.
- ⚠️ Cross-platform consent behavior diverges from iOS.
- ❌ Incorrect LI decisions forwarded to Usercentrics SDK.| TCFDecisionUILayer.values()[fromLayer.toInt()], UsercentricsConsentType.values()[consentType.toInt()], unsavedPurposeLIDecisions.deserializePurposeLIDecisionsMap(), unsavedVendorLIDecisions.deserializePurposeLIDecisionsMap() | |
| TCFDecisionUILayer.values()[fromLayer.toInt()], | |
| UsercentricsConsentType.values()[consentType.toInt()], | |
| unsavedPurposeLIDecisions.deserializePurposeLIDecisionsMap(), | |
| unsavedVendorLIDecisions | |
| .takeIf { it.size() > 0 } | |
| ?.let { decisions -> | |
| buildMap { | |
| for (i in 0 until decisions.size()) { | |
| decisions.getMap(i)?.let { decision -> | |
| decision.getBooleanOrNull("legitimateInterestConsent")?.let { consent -> | |
| put(decision.getInt("id"), consent) | |
| } | |
| } | |
| } | |
| }.takeIf { it.isNotEmpty() } | |
| } |
Steps of Reproduction ✅
1. Call the public JS API `Usercentrics.denyAllForTCF(...)` in
`src/Usercentrics.tsx:110-113`, passing `unsavedVendorLIDecisions` with vendor objects
missing `legitimateInterestConsent` (allowed by model typing in
`src/models/TCFUserDecisions.tsx:51-60`, where `legitimateInterestConsent?: boolean` is
optional).
2. The call is bridged to Android `RNUsercentricsModule.denyAllForTCF(...)` at
`android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt:152-156`,
which forwards `unsavedVendorLIDecisions.deserializePurposeLIDecisionsMap()`.
3. In
`android/src/main/java/com/usercentrics/reactnative/extensions/UserDecisionExtensions.kt:28-35`,
`deserializePurposeLIDecisionsMap()` converts missing `legitimateInterestConsent` with `?:
false`, then inserts `map[id] = consent`, turning an omitted value into explicit denial.
4. The resulting map is sent to SDK via `usercentricsProxy.instance.denyAllForTCF(...)`
(`RNUsercentricsModule.kt:154-155`), so vendor LI can be denied unintentionally; iOS
behavior (`ios/RNUsercentricsModule.swift:26-33`) only inserts entries when
`legitimateInterestConsent` exists, confirming Android-specific coercion.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
**Line:** 155:155
**Comment:**
*Logic Error: `unsavedVendorLIDecisions` is deserialized with a helper that force-defaults missing `legitimateInterestConsent` values to `false`. Since this field is optional in the JS model, missing values get converted into explicit denials, which can overwrite vendor LI state incorrectly. Build the vendor map by only including entries where `legitimateInterestConsent` is present.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.|
|
||
| @ReactMethod | ||
| abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, promise: Promise) | ||
| abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, unsavedVendorLIDecisions: ReadableArray, promise: Promise) |
There was a problem hiding this comment.
Suggestion: This signature change introduces a native contract mismatch: other integration points (notably the TurboModule codegen spec in src/fabric/NativeUsercentricsModule.ts and Objective-C extern declarations) still expose denyAllForTCF with only one LI decisions array. In new-architecture builds this causes argument-count/signature inconsistency, so the call can fail at runtime or silently drop the new vendor decisions argument. Keep the old signature here until all bridge specs are updated atomically in the same PR. [possible bug]
Severity Level: Critical 🚨
- ❌ TCF deny-all can fail on TurboModule path.
- ❌ iOS bridge declarations mismatch Swift method signature.
- ⚠️ Vendor LI decisions may be silently ignored.| abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, unsavedVendorLIDecisions: ReadableArray, promise: Promise) | |
| abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, promise: Promise) |
Steps of Reproduction ✅
1. Build the module with New Architecture enabled; `package.json:105-108` sets codegen
source to `./src/fabric`.
2. Inspect `src/fabric/NativeUsercentricsModule.ts:36`: `denyAllForTCF` still declares
only one LI array, while Android native spec now expects two arrays at
`android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt:76`.
3. Trigger TCF deny-all from JS via `src/Usercentrics.tsx:110-113`, which always calls
`RNUsercentricsModule.denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions,
unsavedVendorLIDecisions)`.
4. Runtime bridge path is `src/NativeUsercentrics.ts:66` (`TurboModuleRegistry.get`
first), so argument contract comes from codegen spec; mismatch can cause call failure or
dropped `unsavedVendorLIDecisions`.
5. iOS is also inconsistent: bridge extern/header expose one array
(`ios/RNUsercentricsModule.mm:72-76`, `ios/RNUsercentricsModuleSpec.h:75-20`), but Swift
implementation requires two (`ios/RNUsercentricsModule.swift:160-170`), confirming
cross-layer API drift is real.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
**Line:** 76:76
**Comment:**
*Possible Bug: This signature change introduces a native contract mismatch: other integration points (notably the TurboModule codegen spec in `src/fabric/NativeUsercentricsModule.ts` and Objective-C extern declarations) still expose `denyAllForTCF` with only one LI decisions array. In new-architecture builds this causes argument-count/signature inconsistency, so the call can fail at runtime or silently drop the new vendor decisions argument. Keep the old signature here until all bridge specs are updated atomically in the same PR.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| @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()) | ||
| } |
There was a problem hiding this comment.
Suggestion: The new denyAllForTCF Swift selector now requires unsavedVendorLIDecisions, but the iOS bridge/spec and manager contract still expose the old 3-argument method. This breaks the native contract and can fail at runtime with argument-count/selector mismatch. Keep this method signature backward-compatible until all bridge/spec/manager layers are updated together. [possible bug]
Severity Level: Critical 🚨
- ❌ iOS build breaks on denyAllForTCF manager call.
- ❌ Bridge selector mismatch blocks denyAllForTCF native invocation.
- ⚠️ TCF deny-all flow unavailable for iOS consumers.| @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()) | |
| } | |
| @objc func denyAllForTCF(_ fromLayer: Double, | |
| consentType: Double, | |
| unsavedPurposeLIDecisions: [NSDictionary], | |
| resolve: @escaping RCTPromiseResolveBlock, | |
| reject: @escaping RCTPromiseRejectBlock) -> Void { | |
| let services = usercentricsManager.denyAllForTCF( | |
| fromLayer: .initialize(from: Int(fromLayer)), | |
| consentType: .initialize(from: Int(consentType)), | |
| unsavedPurposeLIDecisions: extractLIDecisionsMap(unsavedPurposeLIDecisions) | |
| ) | |
| resolve(services.toListOfDictionary()) | |
| } |
Steps of Reproduction ✅
1. Build the iOS module after this PR change; `RNUsercentricsModule.swift:166-170` calls
`usercentricsManager.denyAllForTCF(..., unsavedVendorLIDecisions: ...)`.
2. Open `ios/Manager/UsercentricsManager.swift:31` and `:115-116`; protocol/implementation
only define `denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions)` with no
vendor argument.
3. Compiler cannot resolve that call signature in `RNUsercentricsModule.swift`, so iOS
build fails before runtime.
4. Even if compiled with manual workarounds, bridge contracts still mismatch:
`ios/RNUsercentricsModule.mm:72-77` and `ios/RNUsercentricsModuleSpec.h:75-79` export old
3-argument selector, while Swift exposes a 4-argument selector at
`ios/RNUsercentricsModule.swift:160-165`, breaking RN invocation path.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/RNUsercentricsModule.swift
**Line:** 160:173
**Comment:**
*Possible Bug: The new `denyAllForTCF` Swift selector now requires `unsavedVendorLIDecisions`, but the iOS bridge/spec and manager contract still expose the old 3-argument method. This breaks the native contract and can fail at runtime with argument-count/selector mismatch. Keep this method signature backward-compatible until all bridge/spec/manager layers are updated together.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| if let id = dict["id"] as? Int, | ||
| let consent = dict["legitimateInterestConsent"] as? Bool { | ||
| result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent) | ||
| } |
There was a problem hiding this comment.
Suggestion: extractLIDecisionsMap currently drops entries when legitimateInterestConsent is missing, which can silently lose user decisions; Android defaults missing values to false. Use the same fallback to keep behavior consistent and avoid accidental data loss. [logic error]
Severity Level: Major ⚠️
- ⚠️ iOS drops LI entries with missing booleans.
- ❌ Cross-platform TCF decisions become inconsistent.
- ⚠️ Deny-all may ignore intended purpose/vendor LI states.| if let id = dict["id"] as? Int, | |
| let consent = dict["legitimateInterestConsent"] as? Bool { | |
| result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent) | |
| } | |
| if let id = dict["id"] as? Int { | |
| let consent = (dict["legitimateInterestConsent"] as? Bool) ?? false | |
| result[KotlinInt(int: Int32(id))] = KotlinBoolean(bool: consent) | |
| } |
Steps of Reproduction ✅
1. Call public API `Usercentrics.denyAllForTCF(...)` from JS
(`src/Usercentrics.tsx:110-112`) with objects that include `id` but omit
`legitimateInterestConsent`.
2. This input is valid by type design because `legitimateInterestConsent` is optional in
`src/models/TCFUserDecisions.tsx:30` and `:55`.
3. iOS path reaches `extractLIDecisionsMap` (`ios/RNUsercentricsModule.swift:175-181`),
where missing `legitimateInterestConsent` fails the `if let` and drops that entry.
4. Android path for the same payload defaults missing value to `false`
(`android/.../UserDecisionExtensions.kt:34-35`), so platform behavior diverges and iOS
silently loses decisions.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** ios/RNUsercentricsModule.swift
**Line:** 178:181
**Comment:**
*Logic Error: `extractLIDecisionsMap` currently drops entries when `legitimateInterestConsent` is missing, which can silently lose user decisions; Android defaults missing values to `false`. Use the same fallback to keep behavior consistent and avoid accidental data loss.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.| 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); |
There was a problem hiding this comment.
Suggestion: The new native call always sends four arguments, but the iOS bridge export (RNUsercentricsModule.mm) and the TurboModule codegen spec (src/fabric/NativeUsercentricsModule.ts) still define denyAllForTCF with only one LI-decisions array. This creates a runtime argument-count mismatch (method not found/wrong arg count) on those paths. Add a compatibility dispatch so four arguments are only sent on the legacy Android path that supports them, and use the 3-argument call elsewhere until native contracts are fully aligned. [possible bug]
Severity Level: Critical 🚨
- ❌ TCF deny-all fails on iOS bridge path.
- ❌ Turbo deny-all call breaks from arity mismatch.
- ⚠️ Consent rejection action may not persist.| return RNUsercentricsModule.denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions, unsavedVendorLIDecisions); | |
| const isTurboModuleEnabled = (global as any).__turboModuleProxy != null; | |
| if (Platform.OS === 'android' && !isTurboModuleEnabled) { | |
| return RNUsercentricsModule.denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions, unsavedVendorLIDecisions); | |
| } | |
| return (RNUsercentricsModule.denyAllForTCF as any)(fromLayer, consentType, unsavedPurposeLIDecisions); |
Steps of Reproduction ✅
1. Trigger the TCF deny flow from real callers in `sample/src/screens/CustomUI.tsx:207` or
`example/src/screens/CustomUI.tsx:207`, which call
`Usercentrics.denyAllForTCF(TCFDecisionUILayer.firstLayer,
UsercentricsConsentType.explicit)`.
2. Execution enters `src/Usercentrics.tsx:110-112`; default params create both LI arrays
and JS always calls native with 4 arguments: `fromLayer, consentType,
unsavedPurposeLIDecisions, unsavedVendorLIDecisions`.
3. On iOS legacy bridge, exported native contract is still 3-arg (single LI array) in
`ios/RNUsercentricsModule.mm:72-75` and `ios/RNUsercentricsModuleSpec.h:75-78`; Turbo
codegen contract is also 3-arg in `src/fabric/NativeUsercentricsModule.ts:36`.
4. Because module resolution can use Turbo/bridge (`src/NativeUsercentrics.ts:66`, then
consumed in `src/Usercentrics.tsx:23`), invoking this path on non-Android-legacy runtimes
hits argument-count mismatch and `denyAllForTCF` fails at runtime.Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** src/Usercentrics.tsx
**Line:** 112:112
**Comment:**
*Possible Bug: The new native call always sends four arguments, but the iOS bridge export (`RNUsercentricsModule.mm`) and the TurboModule codegen spec (`src/fabric/NativeUsercentricsModule.ts`) still define `denyAllForTCF` with only one LI-decisions array. This creates a runtime argument-count mismatch (method not found/wrong arg count) on those paths. Add a compatibility dispatch so four arguments are only sent on the legacy Android path that supports them, and use the 3-argument call elsewhere until native contracts are fully aligned.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.|
CodeAnt AI finished reviewing your PR. |
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.ktandroid/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.ktios/RNUsercentricsModule.swiftsrc/NativeUsercentrics.tssrc/Usercentrics.tsx
| 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) |
There was a problem hiding this comment.
Keep missing LI flags consistent with Android.
This helper drops entries when legitimateInterestConsent is absent, but the Android bridge maps the same case to false. That makes denyAllForTCF produce different LI payloads across platforms for partial decision objects.
Suggested 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 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.
|
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 · |
Sequence DiagramThis 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
Generated by CodeAnt AI |
|
CodeAnt AI Incremental review completed. |
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
ios/Manager/UsercentricsManager.swiftios/RNUsercentricsModule.mmsrc/fabric/NativeUsercentricsModule.ts
| 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) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check the Usercentrics SDK version requirement and any documentation for denyAllForTCF signature
# Find Podspec or Package.swift to see SDK version constraints
echo "=== Checking Podspec for Usercentrics dependency version ==="
fd -e podspec --exec cat {}
echo ""
echo "=== Checking Package.swift for Usercentrics dependency ==="
fd "Package.swift" --exec cat {}
echo ""
echo "=== Checking Podfile for Usercentrics version ==="
fd "Podfile" --exec cat {}Repository: Usercentrics/react-native-sdk
Length of output: 50385
🏁 Script executed:
#!/bin/bash
# Find package.json to confirm the version
fd "package.json" --max-results 5 -x cat {}Repository: Usercentrics/react-native-sdk
Length of output: 284
🏁 Script executed:
#!/bin/bash
# Find and read package.json
fd "package.json" | head -1 | xargs catRepository: Usercentrics/react-native-sdk
Length of output: 2395
🏁 Script executed:
#!/bin/bash
# Look for the main package definition with iosPackageVersion
fd "package.json" | xargs grep -l "iosPackageVersion" 2>/dev/nullRepository: Usercentrics/react-native-sdk
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Find all package.json files and check for iosPackageVersion
find . -name "package.json" -type f | while read f; do
if grep -q "iosPackageVersion" "$f"; then
echo "=== $f ==="
cat "$f"
fi
doneRepository: Usercentrics/react-native-sdk
Length of output: 3211
🏁 Script executed:
#!/bin/bash
# Check if there's a CHANGELOG or release notes mentioning unsavedVendorLIDecisions
fd -e md -e txt | xargs grep -l "unsavedVendorLIDecisions\|2.25.1" 2>/dev/null | head -10Repository: Usercentrics/react-native-sdk
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Read the CHANGELOG and look for references to unsavedVendorLIDecisions or denyAllForTCF
cat CHANGELOG.md | head -200Repository: Usercentrics/react-native-sdk
Length of output: 7641
🏁 Script executed:
#!/bin/bash
# Look at the implementation to understand the signature
fd "UsercentricsManager.swift" -x cat {}Repository: Usercentrics/react-native-sdk
Length of output: 21193
Fix FakeUsercentricsManager mock to match protocol signature.
The protocol UsercentricsManager defines denyAllForTCF with four parameters including unsavedVendorLIDecisions, but the FakeUsercentricsManager test mock only accepts three parameters, omitting unsavedVendorLIDecisions. The test mock must match the protocol definition to maintain consistency.
Update FakeUsercentricsManager.denyAllForTCF signature:
// Current (incorrect):
func denyAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType, unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?) -> [UsercentricsServiceConsent]
// Should be:
func denyAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType, unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?, unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?) -> [UsercentricsServiceConsent]Also add a corresponding property to store the vendor LI decisions:
var denyAllForTCFUnsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ios/Manager/UsercentricsManager.swift` around lines 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.
User description
Summary by CodeRabbit
New Features
Refactor
CodeAnt-AI Description
Include unsaved vendor legitimate-interest decisions in TCF "Deny All"
What Changed
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:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
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:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
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.