diff --git a/CHANGELOG.md b/CHANGELOG.md index a7e3b77..d6d9df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ [Release Notes](https://docs.usercentrics.com/cmp_in_app_sdk/latest/about/history/) +### 2.32.0 – Sep 30, 2026 +## Features +* **[PUR — Consent or Pay]** Added the Consent-or-Pay 1st-layer banner ("Reject & Subscribe" card, subscriber-login link, `onLoginClicked`/`onSubscribeClicked` callbacks), a setting to control TCF signal retention on rejection plus a subscription-lapse reset API, and a fix so Deny All on the 2nd layer without an active subscription redirects back to the 1st layer instead of silently force-consenting +* **[CTV]** Added an "Accept All" button to the second layer and synced TV SDK behavior with the Admin Interface configuration + +## Fixes +* **[TCF]** Fixed "Show CMP only to EU and EEA users" having no effect when TCF is enabled, which could resurface the banner to non-EU users +* **[Android]** Fixed CMP banner content overlapping camera cutouts and the side navigation bar on edge-to-edge landscape layouts +* **[iOS — Mediation]** Fixed a crash in Consent Mediation with Adjust v5 caused by a selector regression in the Adjust adapter +* **[iOS]** Fixed long TCF first layer non-IAB section titles being truncated instead of wrapping +* **[Unity]** Fixed obsolete `Object.FindObjectOfType(Type)` usage (Unity SDK repo) + +## Chores +* **[Release Engineering]** Unified the native and Unity release into a single CI pipeline run with parallel steps +* **[App Scanner]** Upgraded the Cloud Armor WAF module (CRS 3.3 → 4.22) + ### 2.31.0 – Sep 10, 2026 ## Features * **[Android]** Added edge-to-edge display support for the consent banner, correctly handling system bar insets diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 00b39c7..e9105a2 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -1,4 +1,4 @@ -val usercentricsVersion = "2.31.0" +val usercentricsVersion = "2.32.0" val reactNativeVersion = "+" fun BooleanProperty(name: String): Boolean { diff --git a/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt b/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt index 08760ff..ef2857a 100644 --- a/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt +++ b/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt @@ -277,6 +277,15 @@ internal class RNUsercentricsModule( }) } + @ReactMethod + override fun notifySubscriptionLapsed(promise: Promise) { + usercentricsProxy.instance.notifySubscriptionLapsed({ + promise.resolve(null) + }, { + promise.reject(it) + }) + } + @ReactMethod override fun addListener(eventName: String) { if (eventName != ON_GPP_SECTION_CHANGE_EVENT) return diff --git a/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt b/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt index 8aa52d7..52e17da 100644 --- a/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt +++ b/android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt @@ -39,6 +39,9 @@ abstract class RNUsercentricsModuleSpec internal constructor(context: ReactAppli @ReactMethod abstract fun notifySubscribeSuccess(promise: Promise) + @ReactMethod + abstract fun notifySubscriptionLapsed(promise: Promise) + @ReactMethod abstract fun getConsents(promise: Promise) diff --git a/ios/Manager/UsercentricsManager.swift b/ios/Manager/UsercentricsManager.swift index b108a9c..b18cdca 100644 --- a/ios/Manager/UsercentricsManager.swift +++ b/ios/Manager/UsercentricsManager.swift @@ -15,6 +15,7 @@ public protocol UsercentricsManager { func notifyLoginSuccess(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) func notifySubscribeSuccess(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) + func notifySubscriptionLapsed(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) func showSecondLayer(bannerSettings: BannerSettings?, dismissViewHandler: @escaping (UsercentricsConsentUserResponse) -> Void) @@ -81,6 +82,10 @@ final class UsercentricsManagerImplementation: UsercentricsManager { UsercentricsCore.shared.notifySubscribeSuccess(onSuccess: onSuccess, onError: onError) } + func notifySubscriptionLapsed(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) { + UsercentricsCore.shared.notifySubscriptionLapsed(onSuccess: onSuccess, onError: onError) + } + func showSecondLayer(bannerSettings: BannerSettings?, dismissViewHandler: @escaping (UsercentricsConsentUserResponse) -> Void) { UsercentricsBanner(bannerSettings: bannerSettings).showSecondLayer(completionHandler: dismissViewHandler) diff --git a/ios/RNUsercentricsModule.mm b/ios/RNUsercentricsModule.mm index 7487ec7..f572565 100644 --- a/ios/RNUsercentricsModule.mm +++ b/ios/RNUsercentricsModule.mm @@ -114,4 +114,7 @@ @interface RCT_EXTERN_MODULE(RNUsercentricsModule, NSObject) RCT_EXTERN_METHOD(notifySubscribeSuccess:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) + +RCT_EXTERN_METHOD(notifySubscriptionLapsed:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) @end diff --git a/ios/RNUsercentricsModule.swift b/ios/RNUsercentricsModule.swift index d4783f3..f341d1d 100644 --- a/ios/RNUsercentricsModule.swift +++ b/ios/RNUsercentricsModule.swift @@ -290,6 +290,14 @@ class RNUsercentricsModule: RCTEventEmitter { } } + @objc func notifySubscriptionLapsed(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void { + usercentricsManager.notifySubscriptionLapsed { + resolve(nil) + } onError: { error in + reject("usercentrics_reactNative_notifySubscriptionLapsed_error", error.localizedDescription, error) + } + } + private static let onGppSectionChangeEvent = "onGppSectionChange" private static let onLoginClickedEvent = "onLoginClicked" private static let onSubscribeClickedEvent = "onSubscribeClicked" diff --git a/ios/RNUsercentricsModuleSpec.h b/ios/RNUsercentricsModuleSpec.h index 47f99e4..84a22a5 100644 --- a/ios/RNUsercentricsModuleSpec.h +++ b/ios/RNUsercentricsModuleSpec.h @@ -36,6 +36,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)notifySubscribeSuccess:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; +- (void)notifySubscriptionLapsed:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject; + // Data Retrieval - (void)getConsents:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject; diff --git a/package-lock.json b/package-lock.json index 2de0f13..731230f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@usercentrics/react-native-sdk", - "version": "2.31.0", + "version": "2.32.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@usercentrics/react-native-sdk", - "version": "2.31.0", + "version": "2.32.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@babel/core": "^7.25.10", diff --git a/package.json b/package.json index 80c1133..49cdbeb 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "@usercentrics/react-native-sdk", - "version": "2.31.0", + "version": "2.32.0", "description": "Usercentrics SDK", "homepage": "https://usercentrics.com", "main": "lib/index.js", "types": "lib/index.d.ts", "author": "Usercentrics ", "iosPackageName": "react-native-usercentrics", - "iosPackageVersion": "2.31.0", + "iosPackageVersion": "2.32.0", "license": "SEE LICENSE IN LICENSE", "files": [ "android", diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 3359a18..9edec92 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -1924,7 +1924,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-usercentrics (2.31.0): + - react-native-usercentrics (2.32.0): - boost - DoubleConversion - fast_float @@ -1951,7 +1951,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - SocketRocket - - UsercentricsUI (= 2.31.0) + - UsercentricsUI (= 2.32.0) - Yoga - react-native-webview (13.16.1): - boost @@ -2545,10 +2545,14 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga + - Sentry (8.58.4): + - Sentry/Core (= 8.58.4) + - Sentry/Core (8.58.4) - SocketRocket (0.7.1) - - Usercentrics (2.31.0) - - UsercentricsUI (2.31.0): - - Usercentrics (= 2.31.0) + - Usercentrics (2.32.0): + - Sentry (~> 8.52) + - UsercentricsUI (2.32.0): + - Usercentrics (= 2.32.0) - Yoga (0.0.0) DEPENDENCIES: @@ -2642,6 +2646,7 @@ SPEC REPOS: - GoogleUtilities - nanopb - PromisesObjC + - Sentry - SocketRocket - Usercentrics - UsercentricsUI @@ -2850,7 +2855,7 @@ SPEC CHECKSUMS: React-Mapbuffer: e4a65db5f4df53369f39558c0cf2f480f6d3d6c7 React-microtasksnativemodule: 86334c5c06315e0bccb7b6e6f2c905e92f98b615 react-native-safe-area-context: eda63a662750758c1fdd7e719c9f1026c8d161cb - react-native-usercentrics: b5d8232a076e87895a90e1f768d31d4726df5bae + react-native-usercentrics: ac0c8523f802eec858de7ef1dc5fa7b79e92c5c2 react-native-webview: 83c663c5bdf1357d3e7c00986260cb888ea0e328 React-NativeModulesApple: 8c7eb6057b00c191a11ad5ced41826ec5a0e4d78 React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d @@ -2883,9 +2888,10 @@ SPEC CHECKSUMS: ReactCodegen: 2cfa890e84ecf7f3a708f1ed9c0f2c0b22a23c9a ReactCommon: e9ab32f1d1482d207867b4fdd139361302b9dcc6 RNScreens: e902eba58a27d3ad399a495d578e8aba3ea0f490 + Sentry: 5321d74bdd28d6f6f8beaa1ca06f1fdc746bff49 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 - Usercentrics: 907aa15a6dee4335364105859a1ce0cc4834979d - UsercentricsUI: d7242e2bfc4205135a258d3eb79dc4bf551669ba + Usercentrics: 7bc3a4042e492b9204ed137ce5c6806365e9e0e4 + UsercentricsUI: 0bf3d1a5ff33d34ff98be8645cc0358bc213b37b Yoga: 9b30b783a17681321b52ac507a37219d7d795ace PODFILE CHECKSUM: 8d257452e9e69d13384a99ee3cd38b42636521da diff --git a/sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift b/sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift index 6db7021..0f5d251 100644 --- a/sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift +++ b/sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift @@ -272,4 +272,13 @@ final class FakeUsercentricsManager: UsercentricsManager { } onSuccess() } + + var notifySubscriptionLapsedError: Error? + func notifySubscriptionLapsed(onSuccess: @escaping (() -> Void), onError: @escaping ((Error) -> Void)) { + if let notifySubscriptionLapsedError = notifySubscriptionLapsedError { + onError(notifySubscriptionLapsedError) + return + } + onSuccess() + } } diff --git a/sample/src/screens/Home.tsx b/sample/src/screens/Home.tsx index 01ce7b9..7dd07cf 100644 --- a/sample/src/screens/Home.tsx +++ b/sample/src/screens/Home.tsx @@ -73,6 +73,17 @@ export const HomeScreen = ({ navigation }: { navigation: any }) => { }; }, []); + async function notifySubscriptionLapsed() { + try { + await Usercentrics.notifySubscriptionLapsed(); + console.log('[Usercentrics] notifySubscriptionLapsed done'); + Alert.alert('notifySubscriptionLapsed', 'Subscription reset'); + } catch (e) { + console.error('[Usercentrics] notifySubscriptionLapsed failed:', e); + Alert.alert('notifySubscriptionLapsed failed', String(e)); + } + } + async function showSecondLayer() { try { const response = await Usercentrics.showSecondLayer({ @@ -129,6 +140,7 @@ export const HomeScreen = ({ navigation }: { navigation: any }) => {