diff --git a/sample/.gitignore b/sample/.gitignore index de99955..c655978 100644 --- a/sample/.gitignore +++ b/sample/.gitignore @@ -53,6 +53,10 @@ yarn-error.log **/fastlane/screenshots **/fastlane/test_output +# Firebase config files — must be added manually (see README for instructions) +google-services.json +GoogleService-Info.plist + # Bundle artifact *.jsbundle diff --git a/sample/README.md b/sample/README.md index 3e2c3f8..87406d6 100644 --- a/sample/README.md +++ b/sample/README.md @@ -1,97 +1,352 @@ -This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). +# Usercentrics React Native SDK — Sample App -# Getting Started +This is the reference sample application for the [Usercentrics React Native SDK](https://github.com/Usercentrics/react-native-sdk). It demonstrates the full SDK integration across the predefined UI, custom UI, webview session handoff, GPP, and consent mediation with Firebase Analytics. -> **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. +## Prerequisites -## Step 1: Start Metro +Before running the sample app, ensure your environment meets the following requirements. -First, you will need to run **Metro**, the JavaScript build tool for React Native. +### All Platforms +- **Node.js 18.0+** (LTS recommended) +- **npm 9.0+** or **Yarn 1.22+** +- **React Native 0.74.3+** +- **Watchman** (recommended for file watching) -To start the Metro dev server, run the following command from the root of your React Native project: +### Android +- **Java 17+** (OpenJDK or Oracle JDK) +- **Android SDK** with API 34+ platforms installed +- **Android NDK 25+** +- **Gradle 8.2+** +- A running Android Emulator (AVD) or a connected physical device -```sh -# Using npm -npm start +### iOS (macOS only) +- **Xcode 15.0+** +- **CocoaPods 1.12+** +- An iOS Simulator or connected physical device -# OR using Yarn -yarn start +--- + +## Project Structure + +``` +sample/ +├── src/ +│ ├── App.tsx # SDK initialisation and navigation root +│ └── screens/ +│ ├── Home.tsx # Main consent flow (auto status check, first/second layer) +│ ├── CustomUI.tsx # Programmatic API demo (GDPR / TCF / CCPA) +│ ├── WebviewIntegrationScreen.tsx # In-App SDK → Browser SDK session handoff +│ └── GppTestingScreen.tsx # GPP string and section-change event testing +├── android/ # Android project (Kotlin, Firebase integrated) +│ ├── app/ +│ │ ├── build.gradle.kts # Firebase BOM, google-services plugin +│ │ └── src/main/AndroidManifest.xml# Firebase collection flags (all false by default) +│ └── build.gradle.kts # google-services classpath +└── ios/ # iOS project (Swift, Firebase integrated) + ├── Podfile # FirebaseAnalytics pod + └── sample/ + ├── AppDelegate.swift # FirebaseApp.configure() call + ├── Info.plist # FIREBASE_ANALYTICS_COLLECTION_ENABLED = false + └── PrivacyInfo.xcprivacy # NSUserDefaults access reasons for Firebase ``` -## Step 2: Build and run your app +--- -With Metro running, open a new terminal window/pane from the root of your React Native project, and use one of the following commands to build and run your Android or iOS app: +## SDK Initialisation -### Android +The SDK is initialised once on app start in `src/App.tsx`: + +```tsx +// Set to true when testing consent mediation. Keep false for normal builds. +const MEDIATION_TEST_ENABLED = true; +const SETTINGS_ID = '3C9-yvno8-dEzy'; + +React.useEffect(() => { + Usercentrics.configure({ + settingsId: SETTINGS_ID, + loggerLevel: UsercentricsLoggerLevel.debug, + consentMediation: MEDIATION_TEST_ENABLED, + }); +}, []); +``` + +`MEDIATION_TEST_ENABLED` and `SETTINGS_ID` are the single place to control mediation mode and the target Usercentrics configuration. No other files need to change — toggle the flag and rebuild. + +--- + +## Running the Sample App + +### Step 1 — Install Dependencies + +From the **repo root**: ```sh -# Using npm -npm run android +npm install --legacy-peer-deps +``` + +Then from the **`sample/` folder**: -# OR using Yarn -yarn android +```sh +npm install --legacy-peer-deps ``` -### iOS +### Step 2 — Run on Android + +Ensure a running emulator or connected device is available, then from the `sample/` folder: -For iOS, remember to install CocoaPods dependencies (this only needs to be run on first clone or after updating native deps). +```sh +npm run android +# or +npx react-native run-android +``` -The first time you create a new project, run the Ruby bundler to install CocoaPods itself: +To list available AVDs and launch one: ```sh -bundle install +emulator -list-avds +emulator -avd ``` -Then, and every time you update your native dependencies, run: +> **Port conflict:** If Metro is already running on port 8081, stop it first (`kill $(lsof -t -i:8081)`) or run `npm run fix-metro` to clear it. + +### Step 3 — Run on iOS (macOS only) + +Install CocoaPods dependencies from the `sample/` folder: ```sh -bundle exec pod install +npx pod-install ``` -For more information, please visit [CocoaPods Getting Started guide](https://guides.cocoapods.org/using/getting-started.html). +> Re-run `npx pod-install` any time you change `ios/Podfile` or pull changes that modify native dependencies. + +Then run the app: ```sh -# Using npm npm run ios +# or +npx react-native run-ios +``` + +To target a specific simulator: + +```sh +npx react-native run-ios --simulator "iPhone 15 Pro" +``` + +List available simulators: -# OR using Yarn -yarn ios +```sh +xcrun simctl list devices available ``` -If everything is set up correctly, you should see your new app running in the Android Emulator, iOS Simulator, or your connected device. +### Step 4 — Start Metro Manually (optional) + +`react-native run-android` / `run-ios` auto-start Metro. If you need to start it separately: + +```sh +npm start +``` + +--- + +## Screen Guide + +### Home Screen + +Entry point of the consent flow. On launch it calls `Usercentrics.status()` automatically: + +- If `shouldCollectConsent` is `true` → the first-layer banner is shown immediately +- Otherwise → existing consents are applied via `applyConsent()` + +Buttons available: + +| Button | Action | +|--------|--------| +| Show First Layer | Opens the predefined consent banner | +| Show Second Layer | Opens the detailed consent preferences screen | +| Customization Example 1 / 2 | Opens the first layer with custom `BannerSettings` | +| Custom UI | Navigates to the Custom UI screen | +| Webview Integration | Navigates to the Webview Integration screen | +| GPP Testing | Navigates to the GPP Testing screen | + +### Custom UI Screen + +Demonstrates the programmatic SDK API for building a fully custom consent UI. Adapts to the active legal framework: -This is one way to run your app — you can also build it directly from Android Studio or Xcode. +| Framework | APIs exercised | +|-----------|---------------| +| GDPR (default) | `getCMPData`, `acceptAll`, `denyAll`, `saveDecisions`, `getConsents`, `changeLanguage` | +| TCF | `getCMPData`, `getTCFData`, `setCMPId`, `acceptAllForTCF`, `denyAllForTCF`, `saveDecisionsForTCF` | +| CCPA | `getCMPData`, `saveOptOutForCCPA` | -## Step 3: Modify your app +All consent outputs are logged to the console. Open Logcat (Android) or the Xcode console (iOS) to inspect them. -Now that you have successfully run the app, let's make changes! +### Webview Integration Screen -Open `App.tsx` in your text editor of choice and make some changes. When you save, your app will automatically update and reflect these changes — this is powered by [Fast Refresh](https://reactnative.dev/docs/fast-refresh). +Demonstrates passing the native consent session to a WebView so the Browser SDK can inherit it without re-asking for consent. It: -When you want to forcefully reload, for example to reset the state of your app, you can perform a full reload: +1. Calls `Usercentrics.getUserSessionData()` to retrieve the encoded session token +2. Injects it into the WebView as `window.UC_UI_USER_SESSION_DATA` before page load +3. The Browser SDK loaded in the WebView reads this value and skips the consent banner -- **Android**: Press the R key twice or select **"Reload"** from the **Dev Menu**, accessed via Ctrl + M (Windows/Linux) or Cmd ⌘ + M (macOS). -- **iOS**: Press R in iOS Simulator. +### GPP Testing Screen -## Congratulations! :tada: +Exercises the Global Privacy Platform (GPP) API: -You've successfully run and modified your React Native App. :partying_face: +| Action | API | +|--------|-----| +| Read the current GPP string | `Usercentrics.getGPPString()` | +| Read full GPP data as JSON | `Usercentrics.getGPPData()` | +| Write a consent signal | `Usercentrics.setGPPConsent('usnat', 'SaleOptOut', 2)` | +| Listen to section changes | `Usercentrics.onGppSectionChange(callback)` | -### Now what? +Section-change events are displayed in real time on screen. -- If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). -- If you're curious to learn more about React Native, check out the [docs](https://reactnative.dev/docs/getting-started). +--- -# Troubleshooting +## Consent Mediation -If you're having issues getting the above steps to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. +Consent Mediation lets the Usercentrics SDK automatically propagate user consent decisions to third-party SDKs — no manual wiring required in your app code. -# Learn More +### How It Works -To learn more about React Native, take a look at the following resources: +When `consentMediation: true` is passed to `Usercentrics.configure()`, the SDK intercepts consent decisions and forwards them to every registered mediation partner at the correct point in the consent lifecycle. + +### Enabling / Disabling Mediation + +Open `src/App.tsx` and toggle the constant at the top: + +```tsx +// true → SDK automatically propagates consent to Firebase and other partners +// false → standard mode, no automatic propagation +const MEDIATION_TEST_ENABLED = true; +``` + +Rebuild and relaunch after changing the value. + +### Firebase Analytics Integration + +Firebase Analytics is included as a reference mediation partner. It is configured to collect **no data** until Usercentrics grants consent. + +**Android setup** +- `com.google.gms:google-services:4.4.2` classpath in `android/build.gradle.kts` +- `com.google.gms.google-services` plugin in `android/app/build.gradle.kts` +- Firebase BOM `33.13.0` + `firebase-analytics` in `android/app/build.gradle.kts` +- All collection flags default to `false` in `AndroidManifest.xml`: + +```xml + + + + + +``` + +**iOS setup** +- `pod 'FirebaseAnalytics'` in `ios/Podfile` +- `FirebaseApp.configure()` at the top of `application(_:didFinishLaunchingWithOptions:)` in `AppDelegate.swift` +- `FIREBASE_ANALYTICS_COLLECTION_ENABLED` set to `false` in `ios/sample/Info.plist` +- `PrivacyInfo.xcprivacy` updated with the NSUserDefaults access reasons required by Firebase + +### Firebase Config Files + +Firebase requires platform-specific config files that contain project credentials. These files are **not stored in this repository** and must be added manually before building. + +| Platform | File | Location | +|----------|------|----------| +| Android | `google-services.json` | `sample/android/app/` | +| iOS | `GoogleService-Info.plist` | `sample/ios/sample/` | + +**Steps to add the files:** + +1. Obtain the `google-services.json` (Android) and `GoogleService-Info.plist` (iOS) from the Firebase Console for the Firebase project used with this sample app. +2. Place each file in the path shown in the table above. +3. For iOS: the Xcode project already has `GoogleService-Info.plist` wired as a build resource in `project.pbxproj` — no Xcode changes needed. +4. For Android: the `google-services` Gradle plugin is already configured in `android/build.gradle.kts` and `android/app/build.gradle.kts` — no Gradle changes needed. +5. Build and run the app as described in [Running the Sample App](#running-the-sample-app). + +> Both files are listed in `.gitignore` and will not be accidentally committed. + +### Testing Mediation Step by Step + +1. Set `MEDIATION_TEST_ENABLED = true` in `src/App.tsx` +2. Place the Firebase config files in the correct locations (see above) +3. Build and run the app on a device or simulator +4. On first launch the consent banner appears automatically — accept or deny +5. Check the logs to confirm Firebase received the correct consent signal: + - **Android**: Logcat → filter by `Firebase` or `Usercentrics` + - **iOS**: Xcode console → filter by `Firebase` or `Usercentrics` +6. To re-test: clear app data (Android) or reinstall the app (iOS) to reset consent state, then relaunch + +For full documentation see the [Usercentrics Consent Mediation guide](https://usercentrics.com/docs/apps/features/consent-mediation/#enable-mediation). + +--- + +## New Architecture + +The sample app runs with **React Native New Architecture (Fabric + TurboModules) enabled by default**. + +To toggle it, change the following two files and ensure they match: + +**`ios/Podfile`** +```ruby +$RCT_NEW_ARCH_ENABLED = true # set to false for Legacy Architecture +``` + +**`android/gradle.properties`** +```properties +newArchEnabled=true # set to false for Legacy Architecture +``` + +After changing the iOS flag, run `npx pod-install` from the `sample/` folder before rebuilding. + +--- + +## Available Scripts + +| Script | Description | +|--------|-------------| +| `npm run android` | Run the Android app | +| `npm run ios` | Run the iOS app | +| `npm start` | Start the Metro bundler | +| `npm run android-build` | Assemble a debug APK via Gradle | +| `npm run clean-and-build` | Clean all caches and rebuild | +| `npm run clean-all-caches` | Clean project and system caches | +| `npm run fix-sandbox` | Fix Xcode sandbox permission issues | +| `npm run fix-metro` | Kill stale Metro processes and clear the Metro cache | +| `npm run build-ios` | Run the iOS build validation script | +| `npm test` | Run unit tests | +| `npm run lint` | Run ESLint | + +--- + +## Troubleshooting + +**Metro port conflict** +```sh +npm run fix-metro +# or manually: +kill $(lsof -t -i:8081) +``` + +**iOS pod issues** +```sh +npx pod-install +``` +Re-run after any Podfile change or native dependency update. + +**Android emulator not found** +```sh +emulator -list-avds # list available AVDs +emulator -avd # start one before running the app +``` + +**Full clean and reinstall** +```sh +npm run clean-all-caches +npm install --legacy-peer-deps +npx pod-install # iOS only +``` -- [React Native Website](https://reactnative.dev) - learn more about React Native. -- [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. -- [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. -- [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. -- [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. +**General React Native issues** +See the [React Native Troubleshooting guide](https://reactnative.dev/docs/troubleshooting). diff --git a/sample/android/app/build.gradle.kts b/sample/android/app/build.gradle.kts index 2191b1e..4c62974 100644 --- a/sample/android/app/build.gradle.kts +++ b/sample/android/app/build.gradle.kts @@ -2,6 +2,8 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") id("com.facebook.react") + // Uncomment to enable Firebase locally (requires google-services.json in sample/android/app/) + // id("com.google.gms.google-services") } android { @@ -11,7 +13,7 @@ android { namespace = "com.usercentrics.reactnativesdk.sample" defaultConfig { - applicationId = "com.usercentrics.reactnativesdk.sample" + applicationId = "com.usercentrics.sdk.mediation.test" minSdk = versions.versions.minSdk.get().toInt() targetSdk = versions.versions.targetSdk.get().toInt() versionCode = 1 @@ -78,6 +80,10 @@ dependencies { // Flipper (debug only) debugImplementation(versions.bundles.flipper.debug) + // Firebase + implementation(platform("com.google.firebase:firebase-bom:33.13.0")) + implementation("com.google.firebase:firebase-analytics") + // Tests androidTestImplementation("com.facebook.react:react-android:${versions.versions.reactNative.get()}") androidTestImplementation(project(":react-native-usercentrics")) diff --git a/sample/android/app/src/main/AndroidManifest.xml b/sample/android/app/src/main/AndroidManifest.xml index 4e831e6..39a9a8c 100644 --- a/sample/android/app/src/main/AndroidManifest.xml +++ b/sample/android/app/src/main/AndroidManifest.xml @@ -11,6 +11,12 @@ android:allowBackup="false" android:theme="@style/AppTheme" android:supportsRtl="true"> + + + + + + :static target 'sample' do config = use_native_modules! + pod 'FirebaseAnalytics' + use_react_native!( :path => config[:reactNativePath], :hermes_enabled => true, diff --git a/sample/ios/Podfile.lock b/sample/ios/Podfile.lock index 7c51baa..0abfcb7 100644 --- a/sample/ios/Podfile.lock +++ b/sample/ios/Podfile.lock @@ -3,11 +3,100 @@ PODS: - DoubleConversion (1.1.6) - fast_float (8.0.0) - FBLazyVector (0.81.4) + - FirebaseAnalytics (12.12.1): + - FirebaseAnalytics/Default (= 12.12.1) + - FirebaseCore (~> 12.12.0) + - FirebaseInstallations (~> 12.12.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseAnalytics/Default (12.12.1): + - FirebaseCore (~> 12.12.0) + - FirebaseInstallations (~> 12.12.0) + - GoogleAppMeasurement/Default (= 12.12.1) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - FirebaseCore (12.12.1): + - FirebaseCoreInternal (~> 12.12.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreInternal (12.12.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseInstallations (12.12.0): + - FirebaseCore (~> 12.12.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - PromisesObjC (~> 2.4) - fmt (11.0.2) - glog (0.3.5) + - GoogleAdsOnDeviceConversion (3.5.0): + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Core (12.12.1): + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/Default (12.12.1): + - GoogleAdsOnDeviceConversion (~> 3.5.0) + - GoogleAppMeasurement/Core (= 12.12.1) + - GoogleAppMeasurement/IdentitySupport (= 12.12.1) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleAppMeasurement/IdentitySupport (12.12.1): + - GoogleAppMeasurement/Core (= 12.12.1) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/MethodSwizzler (~> 8.1) + - GoogleUtilities/Network (~> 8.1) + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - nanopb (~> 3.30910.0) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/MethodSwizzler (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy - hermes-engine (0.81.4): - hermes-engine/Pre-built (= 0.81.4) - hermes-engine/Pre-built (0.81.4) + - nanopb (3.30910.0): + - nanopb/decode (= 3.30910.0) + - nanopb/encode (= 3.30910.0) + - nanopb/decode (3.30910.0) + - nanopb/encode (3.30910.0) + - PromisesObjC (2.4.0) - RCT-Folly (2024.11.18.00): - boost - DoubleConversion @@ -2467,6 +2556,7 @@ DEPENDENCIES: - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - fast_float (from `../node_modules/react-native/third-party-podspecs/fast_float.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - FirebaseAnalytics - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) @@ -2543,6 +2633,15 @@ DEPENDENCIES: SPEC REPOS: trunk: + - FirebaseAnalytics + - FirebaseCore + - FirebaseCoreInternal + - FirebaseInstallations + - GoogleAdsOnDeviceConversion + - GoogleAppMeasurement + - GoogleUtilities + - nanopb + - PromisesObjC - SocketRocket - Usercentrics - UsercentricsUI @@ -2705,81 +2804,90 @@ SPEC CHECKSUMS: DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: 941bef1c8eeabd9fe1f501e30a5220beee913886 + FirebaseAnalytics: a5f875a3cb86be4b4b5a8c0c4a8cb8d6d99d2de9 + FirebaseCore: 86241206e656f5c80c995e370e6c975913b9b284 + FirebaseCoreInternal: 7c12fc3011d889085e765e317d7b9fd1cef97af9 + FirebaseInstallations: 4e6e162aa4abaaeeeb01dd00179dfc5ad9c2194e fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + GoogleAdsOnDeviceConversion: 914d95386d0dd6815e8b1d70c465fe1d13312a1e + GoogleAppMeasurement: f0781decd2e148270c47caec6cafb33775dbf657 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 - RCT-Folly: 59ec0ac1f2f39672a0c6e6cecdd39383b764646f + nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 RCTDeprecation: c0ed3249a97243002615517dff789bf4666cf585 RCTRequired: 58719f5124f9267b5f9649c08bf23d9aea845b23 RCTTypeSafety: 4aefa8328ab1f86da273f08517f1f6b343f6c2cc React: 2073376f47c71b7e9a0af7535986a77522ce1049 React-callinvoker: 751b6f2c83347a0486391c3f266f291f0f53b27e - React-Core: 7195661f0b48e7ea46c3360ccb575288a20c932c - React-CoreModules: 14f0054ab46000dd3b816d6528af3bd600d82073 - React-cxxreact: 7f602425c63096c398dac13cd7a300efd7c281ae + React-Core: dff5d29973349b11dd6631c9498456d75f846d5e + React-CoreModules: c0ae04452e4c5d30e06f8e94692a49107657f537 + React-cxxreact: 376fd672c95dfb64ad5cc246e6a1e9edb78dec4c React-debug: d4955c86870792887ed695df6ebf0e94e39dc7e1 - React-defaultsnativemodule: e741702f0e585c2f252cf1797ae7556312a5e43f - React-domnativemodule: 71832948d5efe4231231929f3ab8fb43c60e64be - React-Fabric: 40b52987bbf49a5eb3963d69eb79ee5fb474497d - React-FabricComponents: ac181f57440b220bc5c0c73a213c8f0beb4b402c - React-FabricImage: c32725d2935166d14fb6a0248ad5eec890a6665b - React-featureflags: f9cadeda57aa490c9c7a1df9af6866ef68bbddf6 - React-featureflagsnativemodule: 2c4196feb481fe502e4549bf8cff78cb98514b59 - React-graphics: b9a2c17b8baafe92ab5aad8ba940c30428cf9c99 - React-hermes: 0a167bbb02c242664745e82154578c64e90a88e5 - React-idlecallbacksnativemodule: 0950653cf076a6f98fe33403a70f9ab8506940bc - React-ImageManager: f2f1f5496db3912ebbc166701a381cea102123fd - React-jserrorhandler: ca36f91ee924e45aee9c14e5529ef7b94dcbfb8f - React-jsi: 9c27d27d3007b73c702ad3fd5a6166557c741020 - React-jsiexecutor: 2b24f4ed4026344a27f717bf947a434cbbeeff7a - React-jsinspector: 4bba4426916dbad83fd71eef70350cdf6bac70d1 - React-jsinspectorcdp: 2bde8377dc70d07c213c270135aaf3e9b660d6df - React-jsinspectornetwork: 25a94605232a7f5b9e74f54a1422a69baecf0517 - React-jsinspectortracing: ccae54ad4669316451af1297cc6cbd731a098ca5 - React-jsitooling: 754bebd7e20c271797bfa0df835b33dacfbf4821 - React-jsitracing: 339c27481f2fa42c0a71afcec86cf46022fdbf20 - React-logger: 1767babce2d28c3251039ce05556714a2c8c6ded - React-Mapbuffer: f84e59c14ff145295fbd029c5be16805aabe98d2 - React-microtasksnativemodule: 584eb07c9b1f1e684fe63b7fae61ed865f8f228f - react-native-safe-area-context: 7e0ba374906d8f5009aaf96cd19d4866d8de342b - react-native-usercentrics: afcbd017f0cc55e0e5c86123e24f6529ae13a669 - react-native-webview: 21fdd62caca650645e429b4a84941626612616ef - React-NativeModulesApple: dcfbe72c5a47baec0699a2935c080b7de0c8657b + React-defaultsnativemodule: bd2b805c6daa85d430d034aa748544b377ada152 + React-domnativemodule: b5c04a4a74ed9c3cb25adc72583b017868600464 + React-Fabric: 93a9ff378f1edf29e9a22a24ad55a1be061e7985 + React-FabricComponents: 83bd54366d4ecb8bec563aa1a78d49915763d503 + React-FabricImage: 8bcd88e553047d4ed5c7ea3def8d6c0e3dd88cfc + React-featureflags: 4ea691ab154d505277859416aa226ae32edeef5f + React-featureflagsnativemodule: b8f00b01436294a30dc62fb5e50b70aa3910309c + React-graphics: d6207795fe822668daeb9c6e1f1470a8500d9eec + React-hermes: fcbdc45ecf38259fe3b12642bd0757c52270a107 + React-idlecallbacksnativemodule: f390a518e1a862453f45f86a1bc248350634d858 + React-ImageManager: acb99e093632b7fc2953dd45f2abaeeea2d9588e + React-jserrorhandler: 958ab9afbe7acdbfe8ca225f7503313409b1319a + React-jsi: 59ec3190dd364cca86a58869e7755477d2468948 + React-jsiexecutor: b87d78a2e8dd7a6f56e9cdac038da45de98c944f + React-jsinspector: 9c33e0c4eeeb10a23b61c4501947b57977980e0e + React-jsinspectorcdp: d7b2c3feddd3669f0eaad2ac1e0f7afbc1d1cf18 + React-jsinspectornetwork: 696d0cf07016e69c053deffba30003fa448904a3 + React-jsinspectortracing: 05d49cd8795db15a279eab6f7604dfa9fe9622f1 + React-jsitooling: 0f9894c3656c3c13d4fcfe6e1dc964fd340acf49 + React-jsitracing: dc11027f9e4e829d32bf17626ec831581ea05223 + React-logger: a3cb5b29c32b8e447b5a96919340e89334062b48 + React-Mapbuffer: e4a65db5f4df53369f39558c0cf2f480f6d3d6c7 + React-microtasksnativemodule: 86334c5c06315e0bccb7b6e6f2c905e92f98b615 + react-native-safe-area-context: eda63a662750758c1fdd7e719c9f1026c8d161cb + react-native-usercentrics: 14eb286f5a9ce2139840b558ed48e1c8fc986040 + react-native-webview: 83c663c5bdf1357d3e7c00986260cb888ea0e328 + React-NativeModulesApple: 8c7eb6057b00c191a11ad5ced41826ec5a0e4d78 React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d - React-perflogger: a03d913e3205b00aee4128082abe42fd45ce0c98 - React-performancetimeline: e07fcee93986259c74a5be1a98770ed82086fe5b + React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 + React-performancetimeline: c6c9393c1a0453a51e1852e3531defe60790b36c React-RCTActionSheet: 42195ae666e6d79b4af2346770f765b7c29435b9 - React-RCTAnimation: 5c10527683128c56ff2c09297fb080f7c35bd293 - React-RCTAppDelegate: c616bd5b0d12f0b21dfacee9cd2d512c6df013aa - React-RCTBlob: 6e3757bdd7dce6fd9788c0dd675fd6b6c432db9d - React-RCTFabric: 25825d88450a5a076f8a31282f3ad745741283f4 - React-RCTFBReactNativeSpec: 672c5e8f3b94bd17979df3ef27ef84bfd4317a5a - React-RCTImage: a3482fe1ae562d1bab08b42d4670a7c9a21813cd - React-RCTLinking: d82b9adb141aef9d2b38d446b837ae7017ab60aa - React-RCTNetwork: fa9350dd99354c5695964f589bd4790bdd4f6a85 - React-RCTRuntime: c52d15ec0e57604245394a1dbef4eb6994716568 - React-RCTSettings: b7f4a03f44dba1d3a4dc6770843547b203ca9129 - React-RCTText: 91dc597a5f6b27fd1048bb287c41ea05eeca9333 - React-RCTVibration: 27b09ddf74bddfa30a58d20e48f885ea6ed6c9d9 + React-RCTAnimation: fa103ccc3503b1ed8dedca7e62e7823937748843 + React-RCTAppDelegate: 665d4baf19424cef08276e9ac0d8771eec4519f9 + React-RCTBlob: 0fa9530c255644db095f2c4fd8d89738d9d9ecc0 + React-RCTFabric: 95eb4a92c5c166e21bae07231d327174e56f202d + React-RCTFBReactNativeSpec: fd66225b71f902a8bfa939fb5f7ec743958298df + React-RCTImage: ba824e61ce2e920a239a65d130b83c3a1d426dff + React-RCTLinking: d2dc199c37e71e6f505d9eca3e5c33be930014d4 + React-RCTNetwork: 87137d4b9bd77e5068f854dd5c1f30d4b072faf6 + React-RCTRuntime: b10bd5e5506af0d6205c4101dd1560fe7beead95 + React-RCTSettings: 71f5c7fd7b5f4e725a4e2114a4b4373d0e46048f + React-RCTText: b94d4699b49285bee22b8ebf768924d607eccee3 + React-RCTVibration: 6e3993c4f6c36a3899059f9a9ead560ddaf5a7d7 React-rendererconsistency: 612d0f6603d9837bb1236d7fd5194203b35c8799 - React-renderercss: 5cc9e5e6732dc124dee16b7ab8f48e0b60b3f31d - React-rendererdebug: 224a1beff9e5d5bc537e72b454135006a5c02a52 - React-RuntimeApple: 9bd8789d7b1d0b5502911da80943b3b2fddfe753 - React-RuntimeCore: 9277538145df1bf2c31432870a308357e34098b2 - React-runtimeexecutor: 69ea4689569738c4ecc4086fde2b30967e19101f - React-RuntimeHermes: 8f59a450f31b741dcf2cc979cb0568a30c5fe1a0 - React-runtimescheduler: 75dfc03be8e0a25751a162acb4ff96be4cc020dc - React-timing: d85ab9efe229cc4145f8f21be0af6c150d3d4682 - React-utils: bb55410c0db3a7f57b9518e3dcf76ab77a0a157e - ReactAppDependencyProvider: b20fba6c3d091a393925890009999472c8f94d95 - ReactCodegen: 07322ec16b66c5f5d7ce7a7cadaba401ecb81908 - ReactCommon: a42100667ef42807c485a579847a5ec2c99e0a82 - RNScreens: 656e050942ae9445f5cc45d05d57f13ce7a4c8e4 + React-renderercss: e5c2c3b84976f7a587cde8423c671db07a6a77da + React-rendererdebug: cc7a6131733605b8897754f72c0c35c79f77da9e + React-RuntimeApple: 3f96102fc1ebf738d36719cdce5422a5769293fb + React-RuntimeCore: f05563107927f155180dfa008fed2ac1316a6aec + React-runtimeexecutor: dd3ec3b76761b43e7b37d07a70de91fc1dd24e7e + React-RuntimeHermes: 7fcb384acc111ea21bcffe2e4a15f31b58bb702e + React-runtimescheduler: 7d2eaa4e7d652a391f47df7ff510260413429bd9 + React-timing: f5d4ba74be96a24b9b2a1a910142ed14e03013d9 + React-utils: eb92d1db56a9bb5911b2c77fb4c2e8d331c8b9dd + ReactAppDependencyProvider: 433ddfb4536948630aadd5bd925aff8a632d2fe3 + ReactCodegen: 2cfa890e84ecf7f3a708f1ed9c0f2c0b22a23c9a + ReactCommon: e9ab32f1d1482d207867b4fdd139361302b9dcc6 + RNScreens: e902eba58a27d3ad399a495d578e8aba3ea0f490 SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 Usercentrics: 26f9f31e4e83cc0de49c3913577c6a819ba172f8 UsercentricsUI: 3ed95d8cae63a71e67c28402e8fe7367e2b14179 Yoga: 9b30b783a17681321b52ac507a37219d7d795ace -PODFILE CHECKSUM: 46220983e9c4beebff1cdf7816b6101b73175c12 +PODFILE CHECKSUM: 8d257452e9e69d13384a99ee3cd38b42636521da COCOAPODS: 1.16.2 diff --git a/sample/ios/sample.xcodeproj/project.pbxproj b/sample/ios/sample.xcodeproj/project.pbxproj index 8ecbf2d..a3fc94f 100644 --- a/sample/ios/sample.xcodeproj/project.pbxproj +++ b/sample/ios/sample.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; D3AD968898A2D5F12A3B498E /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; + DA0000021CF9000F007C117D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA0000011CF9000F007C117D /* GoogleService-Info.plist */; }; E15BA0BEFE259BD9E92A30B3 /* Pods_sampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EF340075CFB0AF66FD7B255 /* Pods_sampleTests.framework */; }; /* End PBXBuildFile section */ @@ -38,6 +39,7 @@ 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = sample/LaunchScreen.storyboard; sourceTree = ""; }; A131F51045AC368AA4449B3F /* Pods_sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_sample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B876D4792E95685D00879086 /* sampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = sampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + DA0000011CF9000F007C117D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "sample/GoogleService-Info.plist"; sourceTree = ""; }; E06B5EC4B4478FD5056A0028 /* Pods-sampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-sampleTests.release.xcconfig"; path = "Target Support Files/Pods-sampleTests/Pods-sampleTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -80,6 +82,7 @@ 13B07FB61A68108700A75B9A /* Info.plist */, 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, + DA0000011CF9000F007C117D /* GoogleService-Info.plist */, ); name = sample; sourceTree = ""; @@ -386,7 +389,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.usercentrics.reactnativesdk.sample; + PRODUCT_BUNDLE_IDENTIFIER = com.usercentrics.sdk.mediation.test; PRODUCT_NAME = sample; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/sample-Bridging-Header.h"; @@ -418,7 +421,7 @@ "-ObjC", "-lc++", ); - PRODUCT_BUNDLE_IDENTIFIER = com.usercentrics.reactnativesdk.sample; + PRODUCT_BUNDLE_IDENTIFIER = com.usercentrics.sdk.mediation.test; PRODUCT_NAME = sample; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/sample-Bridging-Header.h"; diff --git a/sample/ios/sample/AppDelegate.swift b/sample/ios/sample/AppDelegate.swift index a8ecb75..699c814 100644 --- a/sample/ios/sample/AppDelegate.swift +++ b/sample/ios/sample/AppDelegate.swift @@ -1,3 +1,5 @@ +// Uncomment to enable Firebase locally (requires GoogleService-Info.plist in sample/ios/sample/) +// import FirebaseCore import UIKit import React import React_RCTAppDelegate @@ -14,6 +16,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { + // Uncomment to enable Firebase locally (requires GoogleService-Info.plist in sample/ios/sample/) + // FirebaseApp.configure() let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider() diff --git a/sample/ios/sample/Info.plist b/sample/ios/sample/Info.plist index d818575..a14c38d 100644 --- a/sample/ios/sample/Info.plist +++ b/sample/ios/sample/Info.plist @@ -2,6 +2,8 @@ + FIREBASE_ANALYTICS_COLLECTION_ENABLED + CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/sample/ios/sample/PrivacyInfo.xcprivacy b/sample/ios/sample/PrivacyInfo.xcprivacy index 41b8317..3ea8e1f 100644 --- a/sample/ios/sample/PrivacyInfo.xcprivacy +++ b/sample/ios/sample/PrivacyInfo.xcprivacy @@ -6,18 +6,20 @@ NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - C617.1 + CA92.1 + 1C8F.1 + C56D.1 NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPICategoryFileTimestamp NSPrivacyAccessedAPITypeReasons - CA92.1 + C617.1 diff --git a/sample/src/App.tsx b/sample/src/App.tsx index d1e400d..bc8d366 100644 --- a/sample/src/App.tsx +++ b/sample/src/App.tsx @@ -1,35 +1,34 @@ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import * as React from 'react'; -import { Usercentrics, UsercentricsOptions } from '@usercentrics/react-native-sdk'; +import { Usercentrics, UsercentricsLoggerLevel } from '@usercentrics/react-native-sdk'; import { CustomScreen, GppTestingScreen, HomeScreen, WebviewIntegrationScreen } from './screens'; +// Set to true when testing consent mediation. Keep false for normal builds. +const MEDIATION_TEST_ENABLED = true; +const SETTINGS_ID = '3C9-yvno8-dEzy'; + const Stack = createNativeStackNavigator(); const App = () => { - React.useEffect(() => { - let options: UsercentricsOptions = { settingsId: "Yi9N3aXia" }; - Usercentrics.configure(options); - }, []); + React.useEffect(() => { + Usercentrics.configure({ + settingsId: SETTINGS_ID, + loggerLevel: UsercentricsLoggerLevel.debug, + consentMediation: MEDIATION_TEST_ENABLED, + }); + }, []); + + return ( + + + + + + + + + ); +}; - return ( - - - - - - - - - ) -} -export default App; \ No newline at end of file +export default App; diff --git a/sample/src/screens/Home.tsx b/sample/src/screens/Home.tsx index 27c35d1..e207ecb 100644 --- a/sample/src/screens/Home.tsx +++ b/sample/src/screens/Home.tsx @@ -1,107 +1,98 @@ import React from 'react'; import { Button, StyleSheet, View } from 'react-native'; import { - BannerSettings, Usercentrics -} from '../../../src/index'; -import { - customizationExampleOne, - customizationExampleTwo -} from './CustomizationExamples'; + BannerSettings, + Usercentrics, + UsercentricsConsentUserResponse, + UsercentricsServiceConsent, +} from '@usercentrics/react-native-sdk'; +import { customizationExampleOne, customizationExampleTwo } from './CustomizationExamples'; export const HomeScreen = ({ navigation }: { navigation: any }) => { + React.useEffect(() => { + Usercentrics.status().then(status => { + if (status.shouldCollectConsent) { + showFirstLayer(); + } else { + applyConsent(status.consents); + } + }); + }, []); + async function showFirstLayer(bannerSettings: BannerSettings = new BannerSettings()) { const response = await Usercentrics.showFirstLayer(bannerSettings); + handleUserResponse(response); } async function showSecondLayer() { - const bannerSettings: BannerSettings = { - secondLayerStyleSettings: { - showCloseButton: true - } - }; + const response = await Usercentrics.showSecondLayer({ + secondLayerStyleSettings: { showCloseButton: true }, + }); + handleUserResponse(response); + } - const response = await Usercentrics.showSecondLayer(bannerSettings); + function handleUserResponse(response: UsercentricsConsentUserResponse | null) { + console.log('Consents ->', response?.consents); + console.log('User Interaction ->', response?.userInteraction); + console.log('Controller Id ->', response?.controllerId); + applyConsent(response?.consents); } - async function getBannerSettings() { - const variant = await Usercentrics.getABTestingVariant() - let bannerSettings: BannerSettings; + function applyConsent(_consents?: UsercentricsServiceConsent[] | null) { + // https://docs.usercentrics.com/cmp_in_app_sdk/latest/apply_consent/apply-consent/#apply-consent-to-each-service + } + // A/B testing example — use Usercentrics native variant + async function getBannerSettings() { + const variant = await Usercentrics.getABTestingVariant(); switch (variant) { - case "variantA": - return bannerSettings = {/* settings for the banner with variantA */ }; - case "variantB": - return bannerSettings = {/* settings for the banner with variantB */ }; + case 'variantA': + return {/* BannerSettings for variantA */} as BannerSettings; + case 'variantB': + return {/* BannerSettings for variantB */} as BannerSettings; default: - return bannerSettings = {/* default banner settings*/ }; + return {/* default BannerSettings */} as BannerSettings; } } - //'Activate with third-party tool' option + // A/B testing example — use a third-party tool for variant resolution async function getBannerSettingsThirdPartyTool() { - const variant = ThirdPartyTool.getABTestingVariant() - let bannerSettings: BannerSettings; - + const variant = ThirdPartyTool.getABTestingVariant(); switch (variant) { - case "variantA": - return bannerSettings = {/* settings for the banner with variantA */ variantName: "variantA" }; - case "variantB": - return bannerSettings = {/* settings for the banner with variantB */ variantName: "variantB" }; + case 'variantA': + return {/* BannerSettings for variantA */ variantName: 'variantA'} as BannerSettings; + case 'variantB': + return {/* BannerSettings for variantB */ variantName: 'variantB'} as BannerSettings; default: - return bannerSettings = {/* default banner settings*/variantName: "variantC" }; + return {/* default BannerSettings */ variantName: 'variantC'} as BannerSettings; } } const ThirdPartyTool = { - getABTestingVariant: (): String | null => { - const variants = ["variantA", "variantB"] - const random = Math.floor(Math.random() * variants.length); - return variants[random]; - } - } - - const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'space-evenly', - height: 200, - } - }) + getABTestingVariant: (): string | null => { + const variants = ['variantA', 'variantB']; + return variants[Math.floor(Math.random() * variants.length)]; + }, + }; return ( - -