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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
[Release Notes](https://docs.usercentrics.com/cmp_in_app_sdk/latest/about/history/)
### 2.29.1 – Aug 5, 2026
## Features
* **[Unity]** Added `isExempt` to the Unity `ServiceConsent` model, propagating the statistical exception flag to the Unity bridge

### 2.29.0 – Aug 5, 2026
## Features
* **[Mediation]** Added Amazon Consent Signal (ACS) mediation support for Android and iOS, including Adjust adapter integration
* **[PUR Compliance]** Added statistical exception disclosure and toggle UI to the first and second layer, and excluded statistical exception services from Reject All/Deny All, supporting the UK DUAA statistical-exception requirement
* **[Unity]** Automated the Unity-flavored SPM release pipeline, publishing `usercentrics-spm-ui`/`usercentrics-spm-sdk` package tags for Unity releases

## Fixes
* **[Android]** Fixed consent banner rendering behind system bars on edge-to-edge displays
* **[Android]** Fixed `ExternalLinkSpan.onClick` crash on invalid/empty link URLs
* **[iOS]** Fixed app hang caused by synchronous HTML parsing in first-layer banner/content text
* **[Android/iOS]** Removed residual `assert(false)` calls in Additional Consent Mode and CCPA save paths that could crash debug builds
* **[TV]** Fixed `DPAD_DOWN` navigation escaping the second-layer detail list boundary

## Chores
* **[Unity]** Stripped debug symbols from iOS core release binaries, reducing device binary size by ~31% with no functional change

### 2.28.2 – Jul 22, 2026
## Fixes
* **[iOS]** Rebuilt `UsercentricsUI.xcframework` with the Xcode 26 / Swift 6.3.3 toolchain, fixing build failures on Xcode 26.6 for apps consuming 2.27.1/2.28.0 (including via the React Native bridge)
Expand Down
95 changes: 95 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# CLAUDE.md

Guidance for Claude Code when working in this repo. This file is read automatically — keep it accurate as the release process evolves.

## Repo purpose

`@usercentrics/react-native-sdk` — React Native bridge/wrapper around the native Usercentrics iOS (`UsercentricsUI`/`Usercentrics` CocoaPods) and Android (`com.usercentrics.sdk:usercentrics-ui`) SDKs. The `sample/` directory is a full RN sample app used to build and test the native bridges (Xcode workspace under `sample/ios`, Gradle project under `sample/android`).

## Release process ("faz a release X.Y.Z")

When asked to prepare/cut a release, treat it as a pure **version bump** — the underlying native SDK versions (iOS `Usercentrics`/`UsercentricsUI`, Android `usercentrics-ui`) must already be published on CocoaPods trunk / Maven before you start, and the CHANGELOG entry for the new version is normally written by the user beforehand. Verify both before touching anything (see checklist).

### 1. Pre-flight checklist

- [ ] Confirm CHANGELOG.md already has a `### X.Y.Z – <Month> <Day>, <Year>` section at the top (see format below). If missing, ask — don't invent changelog content.
- [ ] Confirm you're on a dedicated branch (e.g. `bump/X.Y.Z`), never commit version bumps directly to `master`.
- [ ] Confirm the target native SDK version is actually resolvable:
- iOS: `bundle exec pod repo update` inside `sample/ios`, then check `~/.cocoapods/repos/cocoapods/Specs/e/f/5/UsercentricsUI/X.Y.Z/` exists (or search CocoaPods trunk).
- Android: the version must exist on Maven Central / the configured repo for `com.usercentrics.sdk:usercentrics-ui`.
- If the native version isn't published yet, stop and say so — bumping to an unpublished version will make `pod install` and Gradle sync fail in CI.

### 2. Files that encode the version number (bump ALL of them, in this order)

| File | Field(s) |
|---|---|
| `package.json` | `"version"` **and** `"iosPackageVersion"` (both, they can diverge conceptually but are bumped together) |
| `android/build.gradle.kts` | `val usercentricsVersion = "X.Y.Z"` (line 1) |
| `package-lock.json` | regenerated automatically by `npm install` — don't hand-edit |
| `sample/ios/Podfile.lock` | regenerated by CocoaPods — don't hand-edit (see step 4) |

CHANGELOG.md format (top of file, newest first):
```
### X.Y.Z – Mon D, YYYY
## Features
* **[Component]** Description
## Fixes
* **[Component]** Description
## Chores
* **[Component]** Description
```
`## Features` / `## Fixes` / `## Chores` sections are optional — omit any with nothing to report.

### 3. Update package/gradle version strings

Plain string edits — no tooling needed:
```bash
# package.json: bump "version" and "iosPackageVersion"
# android/build.gradle.kts: bump usercentricsVersion
```

### 4. Regenerate lockfiles

```bash
npm install # refreshes package-lock.json "version" fields; also runs `yarn compile` via prepare
cd sample/ios
bundle exec pod repo update # fetch latest trunk specs (required if the native version was just published)
bundle exec pod update UsercentricsUI # bumps UsercentricsUI + Usercentrics + react-native-usercentrics in Podfile.lock
```
Notes:
- `pod update UsercentricsUI` (not plain `pod install`) is required because the Podfile.lock has the *old* version pinned as a hard constraint — a plain `pod install` will refuse to move it and tell you to run `pod update <pod>`.
- The resulting `Podfile.lock` diff will include **a lot of unrelated `SPEC CHECKSUMS` churn** for `React-*` pods even though their versions don't change. This is expected and fine to commit as-is: CI's iOS job always runs `pod deintegrate && pod cache clean --all && bundle exec pod install --repo-update` from scratch (see `.github/workflows/ci.yml`), so whatever you commit here gets regenerated by CI anyway — it is not validated byte-for-byte. Sanity-check it's deterministic by running `pod install` a second time and confirming no further diff.
- The `sample` app's own `package-lock.json`/`node_modules` are independent from the root ones and are normally **not** touched by a version bump (the sample doesn't pin the SDK version itself, it consumes it via a local path dependency).

### 5. Run tests before pushing (don't skip — this is what keeps CI green)

```bash
npm run compile # tsc -p . — must be clean
npm test # jest src/__tests__/index.test.ts — must be 100% passing
```
- `npm run lint` currently has **57 pre-existing errors / ~4700 warnings on master** — this is known tech debt, unrelated to version bumps. Don't try to fix it as part of a release; just confirm the error count didn't *increase* vs. `master` (`git stash` and re-run to compare if in doubt).
- Full native test suites (`xcodebuild test` on `sampleTests`, `./gradlew koverXmlReport`) are what CI's `test-ios` / `test-android` jobs run — they're slow (multi-minute Xcode builds) and CI already re-resolves pods from scratch, so don't try to fully reproduce them locally as a gate. It's fine to let CI be the source of truth for those; just make sure the pod resolution step (`pod install`) itself succeeds locally first, since that's the part a bad version bump most commonly breaks.
- `scripts/assert_export.sh` (run in CI's `test-rn` job) checks every file in `src/models/` is re-exported from `src/models/index.tsx` — irrelevant to a pure version bump but worth knowing it exists if a release also lands new model files.

### 6. Commit

Match the existing convention exactly (see `cb74f4b2`, `3380b40`, `7b13655`, `ae95d6e` for precedent):
```
Release: X.Y.Z

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
```
Files in the commit: `CHANGELOG.md`, `package.json`, `package-lock.json`, `android/build.gradle.kts`, `sample/ios/Podfile.lock`. Nothing else — a release commit should never carry unrelated code changes.

### 7. After merge

Actual `npm publish` happens via `.github/workflows/release.yml`, triggered by a **GitHub Release being created** (not by this commit landing on master). That step is out of scope for the bump itself — flag it to the user rather than trying to trigger it.

## CI reference (`.github/workflows/ci.yml`)

Runs on every PR into `master`:
- `test-rn` (ubuntu): `yarn install --frozen-lockfile` → `yarn compile` → `yarn test` → `scripts/assert_export.sh`
- `test-ios` (macos-14, Xcode 16.1): fresh `pod deintegrate` + `pod cache clean --all` + `pod install --repo-update`, then `xcodebuild test` on the `sampleTests` scheme
- `test-android` (macOS-14, Java 17): `./gradlew :react-native-usercentrics:koverXmlReport`

`RCT_NEW_ARCH_ENABLED` in the iOS job's `env:` is actually irrelevant — `sample/ios/Podfile` hardcodes `$RCT_NEW_ARCH_ENABLED = true` and sets `ENV['RCT_NEW_ARCH_ENABLED']` *from* that, not the other way round. Always assume New Architecture (Fabric + TurboModules) is on for both local and CI pod resolution.
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.28.2"
val usercentricsVersion = "2.29.1"
Comment thread
uc-brunosilva marked this conversation as resolved.
val reactNativeVersion = "+"

fun BooleanProperty(name: String): Boolean {
Expand Down
12 changes: 6 additions & 6 deletions ios/Manager/UsercentricsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public protocol UsercentricsManager {
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 denyAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType, unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?, unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?, unsavedServiceDecisions: [String: KotlinBoolean]?) -> [UsercentricsServiceConsent]
func denyAll(consentType: UsercentricsConsentType, unsavedServiceDecisions: [String: KotlinBoolean]?) -> [UsercentricsServiceConsent]
Comment thread
uc-brunosilva marked this conversation as resolved.
Comment thread
uc-brunosilva marked this conversation as resolved.

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

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)
func denyAllForTCF(fromLayer: TCFDecisionUILayer, consentType: UsercentricsConsentType, unsavedPurposeLIDecisions: [KotlinInt: KotlinBoolean]?, unsavedVendorLIDecisions: [KotlinInt: KotlinBoolean]?, unsavedServiceDecisions: [String: KotlinBoolean]?) -> [UsercentricsServiceConsent] {
return UsercentricsCore.shared.denyAllForTCF(fromLayer: fromLayer, consentType: consentType, unsavedPurposeLIDecisions: unsavedPurposeLIDecisions, unsavedVendorLIDecisions: unsavedVendorLIDecisions, unsavedServiceDecisions: unsavedServiceDecisions)
}

func denyAll(consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent] {
return UsercentricsCore.shared.denyAll(consentType: consentType)
func denyAll(consentType: UsercentricsConsentType, unsavedServiceDecisions: [String: KotlinBoolean]?) -> [UsercentricsServiceConsent] {
return UsercentricsCore.shared.denyAll(consentType: consentType, unsavedServiceDecisions: unsavedServiceDecisions)
}

func saveDecisionsForTCF(tcfDecisions: TCFUserDecisions, fromLayer: TCFDecisionUILayer, serviceDecisions: [UserDecision], consentType: UsercentricsConsentType) -> [UsercentricsServiceConsent] {
Expand Down
5 changes: 3 additions & 2 deletions ios/RNUsercentricsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ class RNUsercentricsModule: RCTEventEmitter {
fromLayer: .initialize(from: Int(fromLayer)),
consentType: .initialize(from: Int(consentType)),
unsavedPurposeLIDecisions: extractLIDecisionsMap(unsavedPurposeLIDecisions),
unsavedVendorLIDecisions: extractLIDecisionsMap(unsavedVendorLIDecisions)
unsavedVendorLIDecisions: extractLIDecisionsMap(unsavedVendorLIDecisions),
unsavedServiceDecisions: nil
Comment thread
uc-brunosilva marked this conversation as resolved.
)
resolve(services.toListOfDictionary())
}
Expand All @@ -220,7 +221,7 @@ class RNUsercentricsModule: RCTEventEmitter {
@objc func denyAll(_ consentType: Double,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) -> Void {
let services = usercentricsManager.denyAll(consentType: .initialize(from: Int(consentType)))
let services = usercentricsManager.denyAll(consentType: .initialize(from: Int(consentType)), unsavedServiceDecisions: nil)
resolve(services.toListOfDictionary())
}

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.28.2",
"version": "2.29.1",
"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.28.2",
"iosPackageVersion": "2.29.1",
Comment thread
uc-brunosilva marked this conversation as resolved.
"license": "SEE LICENSE IN LICENSE",
"files": [
"android",
Expand Down
Loading
Loading