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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[Release Notes](https://docs.usercentrics.com/cmp_in_app_sdk/latest/about/history/)

### 2.24.0Oct 31, 2025
## React Native
* Added Fabric renderer compatibility
### 2.24.2Dec 5, 2025
## Improvement
* Fix react native issues

### 2.24.1 – Oct 31, 2025
## Improvement
Expand All @@ -11,6 +11,10 @@
* Special Features Section: Added support for the new Special Features/Purposes section.
* Fix issue where the SDK failed to initialize offline.

### 2.24.0 – Oct 31, 2025
## React Native
* Added Fabric renderer compatibility

### 2.23.3 – Oct 6, 2025
## React Native
* Updated React Native to 0.79.6 to support 16 KB page sizes
Expand Down
2 changes: 1 addition & 1 deletion android/build-legacy.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def usercentrics_version = "2.24.1"
def usercentrics_version = "2.24.2"
version usercentrics_version

buildscript {
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val usercentricsVersion = "2.24.1"
val usercentricsVersion = "2.24.2"
Copy link

Choose a reason for hiding this comment

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

Suggestion: Hardcoded dependency version: embedding "2.24.2" directly in the script makes it harder to override or keep consistent across modules and CI; prefer reading the version from a project property (with a fallback) so callers can override it without editing the file. [configuration]

Severity Level: Minor ⚠️

Suggested change
val usercentricsVersion = "2.24.2"
val usercentricsVersion: String = (project.findProperty("usercentricsVersion") as String?) ?: "2.24.2"
Why it matters? ⭐

The suggestion addresses a real, practical configuration shortcoming: embedding the dependency version directly in the Kotlin DSL makes it harder to override from CI or other modules and to keep versions consistent across a multi-module build. Replacing the literal with a project property fallback is a low-risk, high-value improvement that enables -P overrides (or a central versions catalog) without changing runtime behaviour when no override is provided.

Note: the proposed cast in the improved_code (project.findProperty("usercentricsVersion") as String?) can be brittle if the property isn't a String; prefer project.findProperty("usercentricsVersion")?.toString() ?: "2.24.2" for a safer conversion.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/build.gradle.kts
**Line:** 1:1
**Comment:**
	*Configuration: Hardcoded dependency version: embedding "2.24.2" directly in the script makes it harder to override or keep consistent across modules and CI; prefer reading the version from a project property (with a fallback) so callers can override it without editing the file.

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.

val reactNativeVersion = "+"

fun BooleanProperty(name: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ class RNUsercentricsModuleTest {
val usercentricsSDK = mockk<UsercentricsSDK>()
every {
usercentricsSDK.denyAllForTCF(
any(), any()
any(), any(), any()
)
}.returns(GetConsentsMock.fakeWithData)

Expand All @@ -527,11 +527,12 @@ class RNUsercentricsModuleTest {
RNUsercentricsModule(contextMock, usercentricsProxy, ReactContextProviderMock())

val promise = FakePromise()
module.denyAllForTCF(0, 0, promise)
val emptyArray = JavaOnlyArray()
module.denyAllForTCF(0, 0, emptyArray, promise)

verify(exactly = 1) {
usercentricsSDK.denyAllForTCF(
TCFDecisionUILayer.FIRST_LAYER, UsercentricsConsentType.EXPLICIT
TCFDecisionUILayer.FIRST_LAYER, UsercentricsConsentType.EXPLICIT, null
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,10 @@ internal class RNUsercentricsModule(
}

@ReactMethod
override fun denyAllForTCF(fromLayer: Int, consentType: Int, promise: Promise) {
override fun denyAllForTCF(fromLayer: Int, consentType: Int, unsavedPurposeLIDecisions: ReadableArray, promise: Promise) {
promise.resolve(
usercentricsProxy.instance.denyAllForTCF(
TCFDecisionUILayer.values()[fromLayer], UsercentricsConsentType.values()[consentType]
TCFDecisionUILayer.values()[fromLayer], UsercentricsConsentType.values()[consentType], unsavedPurposeLIDecisions.deserializePurposeLIDecisionsMap()
).toWritableArray()
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ abstract class RNUsercentricsModuleSpec internal constructor(context: ReactAppli
abstract fun denyAll(consentType: Int, promise: Promise)

@ReactMethod
abstract fun denyAllForTCF(fromLayer: Int, consentType: Int, promise: Promise)
abstract fun denyAllForTCF(fromLayer: Int, consentType: Int, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)

@ReactMethod
abstract fun saveDecisions(decisions: ReadableArray, consentType: Int, promise: Promise)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ internal fun ReadableArray.deserializeUserDecision(): List<UserDecision> {
return decisionList
}

internal fun ReadableArray.deserializePurposeLIDecisionsMap(): Map<Int, Boolean>? {
if (size() == 0) return null
val map = mutableMapOf<Int, Boolean>()
for (i in 0 until size()) {
getMap(i)?.let { readableMap ->
val id = readableMap.getInt("id")
val consent = readableMap.getBooleanOrNull("legitimateInterestConsent") ?: false
map[id] = consent
}
}
return map
}

internal fun ReadableMap.deserializeTCFUserDecisions(): TCFUserDecisions {
val purposes = getArray("purposes")?.let { purpose ->
val list = mutableListOf<TCFUserDecisionOnPurpose>()
Expand Down
4 changes: 3 additions & 1 deletion example/ios/exampleTests/Fake/FakeUsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ final class FakeUsercentricsManager: UsercentricsManager {

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

Expand Down
3 changes: 2 additions & 1 deletion example/ios/exampleTests/Mock/CMPData+Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ extension TCF2Settings {
acmV2Enabled: true,
selectedATPIds: [43,46,55],
resurfaceATPListChanged: false,
atpListTitle: "Google Providers")
atpListTitle: "Google Providers",
maintainLegitimateInterest: false)
}
}

Expand Down
4 changes: 2 additions & 2 deletions example/ios/exampleTests/RNUsercentricsModuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ class RNUsercentricsModuleTests: XCTestCase {
func testDenyAllForTCF() {
fakeUsercentrics.denyAllForTCFResponse = [.mock()]

module.denyAllForTCF(0, consentType: 0) { [unowned self] result in
module.denyAllForTCF(0, consentType: 0, unsavedPurposeLIDecisions: []) { [unowned self] result in
guard
let result = result as? [NSDictionary],
let consent = result.first
Expand All @@ -343,7 +343,7 @@ class RNUsercentricsModuleTests: XCTestCase {

func testDenyAllForTCFSecondLayer() {
fakeUsercentrics.denyAllForTCFResponse = [.mock()]
module.denyAllForTCF(1, consentType: 1) { [unowned self] _ in
module.denyAllForTCF(1, consentType: 1, unsavedPurposeLIDecisions: []) { [unowned self] _ in
XCTAssertEqual(.implicit, self.fakeUsercentrics.denyAllForTCFConsentType!)
XCTAssertEqual(.secondLayer, self.fakeUsercentrics.denyAllForTCFFromLayer!)
} reject: { _, _, _ in
Expand Down
6 changes: 3 additions & 3 deletions ios/Manager/UsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public protocol UsercentricsManager {
func acceptAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]
func acceptAll(consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent]

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

func saveDecisionsForTCF(tcfDecisions: TCFUserDecisions,
Expand Down Expand Up @@ -112,8 +112,8 @@ final class UsercentricsManagerImplementation: UsercentricsManager {
return UsercentricsCore.shared.acceptAll(consentType: consentType)
}

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

func denyAll(consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent] {
Expand Down
1 change: 1 addition & 0 deletions ios/RNUsercentricsModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject)

RCT_EXTERN_METHOD(denyAllForTCF:(NSInteger *)fromLayer
consentType:(NSInteger)consentType
unsavedPurposeLIDecisions:(NSArray)unsavedPurposeLIDecisions
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)

Expand Down
13 changes: 12 additions & 1 deletion ios/RNUsercentricsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,20 @@ class RNUsercentricsModule: NSObject {

@objc func denyAllForTCF(_ fromLayer: Int,
consentType: Int,
unsavedPurposeLIDecisions: [NSDictionary],
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) -> Void {
let services = usercentricsManager.denyAllForTCF(fromLayer: .initialize(from: fromLayer), consentType: .initialize(from: consentType))
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: fromLayer), consentType: .initialize(from: consentType), unsavedPurposeLIDecisions: decisions)
resolve(services.toListOfDictionary())
}

Expand Down
1 change: 1 addition & 0 deletions ios/RNUsercentricsModuleSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ NS_ASSUME_NONNULL_BEGIN

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

Expand Down
Loading
Loading