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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Comment thread
islameldesoky95 marked this conversation as resolved.

### 2.31.0 – Sep 10, 2026
## Features
* **[Android]** Added edge-to-edge display support for the consent banner, correctly handling system bar insets
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.31.0"
val usercentricsVersion = "2.32.0"
Comment thread
islameldesoky95 marked this conversation as resolved.
val reactNativeVersion = "+"

fun BooleanProperty(name: String): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions ios/Manager/UsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Comment thread
islameldesoky95 marked this conversation as resolved.

func showSecondLayer(bannerSettings: BannerSettings?,
dismissViewHandler: @escaping (UsercentricsConsentUserResponse) -> Void) {
UsercentricsBanner(bannerSettings: bannerSettings).showSecondLayer(completionHandler: dismissViewHandler)
Expand Down
3 changes: 3 additions & 0 deletions ios/RNUsercentricsModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 8 additions & 0 deletions ios/RNUsercentricsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions ios/RNUsercentricsModuleSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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 <developer@usercentrics.com>",
"iosPackageName": "react-native-usercentrics",
"iosPackageVersion": "2.31.0",
"iosPackageVersion": "2.32.0",
Comment thread
islameldesoky95 marked this conversation as resolved.
"license": "SEE LICENSE IN LICENSE",
"files": [
"android",
Expand Down
40 changes: 40 additions & 0 deletions sample/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ platform :ios, '15.1'

use_frameworks! :linkage => :static

# WORKAROUND: Usercentrics.xcframework links `-framework Sentry` eagerly at the binary level
# (a real dynamic dyld load command), but `use_frameworks! :linkage => :static` above makes
# CocoaPods build every pod — including Sentry — as a static archive with no Sentry.framework
# bundle ever embedded in the app. That crashes every launch with "Library not loaded:
# @rpath/Sentry.framework/Sentry". Force only the Sentry pod to build as a dynamic framework
# so the real Sentry.framework exists and gets embedded, matching what the binary expects.
pre_install do |installer|
installer.pod_targets.each do |pod_target|
if pod_target.name.start_with?('Sentry')
def pod_target.build_type
Pod::BuildType.dynamic_framework
end
end
end
end

target 'sample' do
config = use_native_modules!

Expand Down Expand Up @@ -77,7 +93,31 @@ target 'sample' do
# C++ settings
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++20'
config.build_settings['CLANG_CXX_LIBRARY'] = 'libc++'

# WORKAROUND (MSDK-4832): native UsercentricsUI.xcframework 2.32.0 (and 2.31.1+) ships
# arm64-only iOS/tvOS Simulator slices — the old x86_64 fat-binary slice was reduced to a
# dSYM-only stub when Sentry was linked in (EXCLUDED_ARCHS added alongside it, unnecessarily:
# the real Sentry-Dynamic xcframework does ship x86_64). Causes a dyld "signal abrt before
# starting test execution" crash on any x86_64 Simulator destination. Exclude x86_64 here
# until a corrected native SDK version is published (tracked in mobile-sdk MSDK-4832).
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64'
Comment thread
islameldesoky95 marked this conversation as resolved.
end
end

# WORKAROUND (MSDK-4832, continued): excluding x86_64 on the Pods project's own targets
# (above) isn't enough on its own — the "sample" app/test target lives in sample.xcodeproj,
# not Pods.xcodeproj, and Xcode still builds the full arm64+x86_64 ARCHS_STANDARD set for
# "generic/platform=iOS Simulator" destinations regardless of ONLY_ACTIVE_ARCH. Without this,
# xctest still tries to launch an x86_64 slice that links against Pods frameworks with no
# x86_64 slice, crashing with "Test crashed with signal abrt before starting test execution."
# Mirror the exclusion onto the main user project until a corrected native SDK version ships.
installer.aggregate_targets.each do |aggregate_target|
aggregate_target.user_project.native_targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64'
end
end
aggregate_target.user_project.save
end

# RN 0.81 + Fabric: ReactCodegen can end up with several PBXFileReferences whose path is only
Expand Down
24 changes: 15 additions & 9 deletions sample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -2642,6 +2646,7 @@ SPEC REPOS:
- GoogleUtilities
- nanopb
- PromisesObjC
- Sentry
- SocketRocket
- Usercentrics
- UsercentricsUI
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2883,11 +2888,12 @@ 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
PODFILE CHECKSUM: 58bddb926e31074bb71fa08e588e08b2cfe944ee

COCOAPODS: 1.16.2
9 changes: 9 additions & 0 deletions sample/ios/sampleTests/Fake/FakeUsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
12 changes: 12 additions & 0 deletions sample/src/screens/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -129,6 +140,7 @@ export const HomeScreen = ({ navigation }: { navigation: any }) => {
<Button onPress={async () => { await Usercentrics.status(); navigation.navigate('CustomUI'); }} title="Custom UI" />
<Button onPress={async () => { await Usercentrics.status(); navigation.navigate('WebviewIntegration'); }} title="Webview Integration" />
<Button onPress={() => navigation.navigate('GPPTesting')} title="GPP Testing" />
<Button onPress={notifySubscriptionLapsed} title="Notify Subscription Lapsed" />
</View>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/NativeUsercentrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface Spec extends TurboModule {
// Consent or Pay
notifyLoginSuccess(): Promise<void>;
notifySubscribeSuccess(): Promise<void>;
notifySubscriptionLapsed(): Promise<void>;

// Data Retrieval
getConsents(): Promise<Array<UsercentricsServiceConsent>>;
Expand Down
8 changes: 8 additions & 0 deletions src/Usercentrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,12 @@ export const Usercentrics = {
await RNUsercentricsModule.isReady();
return RNUsercentricsModule.notifySubscribeSuccess();
},

// Notifies the SDK that a previously active Consent-or-Pay subscription has lapsed,
// resetting the subscriber flag so the host app can re-surface the banner. Does not
// alter any existing consent data, which remains accurate.
notifySubscriptionLapsed: async (): Promise<void> => {
await RNUsercentricsModule.isReady();
return RNUsercentricsModule.notifySubscriptionLapsed();
},
}
22 changes: 22 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jest.mock("react-native", () => {
reset: jest.fn(),
getDpsMetadata: jest.fn(),
clearUserSession: jest.fn(),
notifySubscriptionLapsed: jest.fn(),
addListener: jest.fn(),
Comment thread
islameldesoky95 marked this conversation as resolved.
removeListeners: jest.fn()
};
Expand Down Expand Up @@ -633,4 +634,25 @@ describe('Test Usercentrics Module', () => {
expect(e).toBe("Failed");
}
})

test('testNotifySubscriptionLapsed', async () => {
RNUsercentricsModule.notifySubscriptionLapsed.mockImplementationOnce(
(): Promise<any> => Promise.resolve()
)

await Usercentrics.notifySubscriptionLapsed();
expect(RNUsercentricsModule.notifySubscriptionLapsed).toHaveBeenCalled();
})

test('testNotifySubscriptionLapsedWithError', async () => {
RNUsercentricsModule.notifySubscriptionLapsed.mockImplementationOnce(
(): Promise<any> => Promise.reject("Failed")
)

try {
await Usercentrics.notifySubscriptionLapsed();
} catch (e) {
expect(e).toBe("Failed");
}
})
})
1 change: 1 addition & 0 deletions src/fabric/NativeUsercentricsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Spec extends TurboModule {
// Consent or Pay
notifyLoginSuccess(): Promise<void>;
notifySubscribeSuccess(): Promise<void>;
notifySubscriptionLapsed(): Promise<void>;

// Data Retrieval
getConsents(): Promise<Array<Object>>;
Expand Down
Loading