diff --git a/.cursor/rules/android-native.mdc b/.cursor/rules/android-native.mdc new file mode 100644 index 0000000..e036f4b --- /dev/null +++ b/.cursor/rules/android-native.mdc @@ -0,0 +1,27 @@ +--- +description: Android native module conventions (Kotlin, Gradle) +globs: "android/**/*.kt,android/**/*.kts,android/**/*.java" +alwaysApply: false +--- + +# Android Native Module + +## Structure + +- `src/main/java/com/usercentrics/reactnative/` — Kotlin module, package, and bridge classes. +- `src/main/jni/` — C++ JNI bindings for Fabric/TurboModules (auto-generated via codegen). +- `src/test/` — JUnit + MockK unit tests. +- `src/androidTest/` — Instrumented tests. + +## Conventions + +- Kotlin for all new code. No Java unless maintaining legacy. +- Gradle Kotlin DSL (`build.gradle.kts`). +- JNI/C++ files under `src/main/jni/` are generated by `scripts/generate-codegen-jni.js` — do not hand-edit. + +## Build + +- Min SDK: 26 (Android 8.0). +- Java 17 for compilation. +- Gradle 8.13. +- Tests run via `./gradlew :react-native-usercentrics:test` from `sample/android/`. diff --git a/.cursor/rules/ci-github-actions.mdc b/.cursor/rules/ci-github-actions.mdc new file mode 100644 index 0000000..40e3141 --- /dev/null +++ b/.cursor/rules/ci-github-actions.mdc @@ -0,0 +1,34 @@ +--- +description: CI/CD and GitHub Actions conventions +globs: '.github/**/*.yml,.github/**/*.yaml' +alwaysApply: false +--- + +# CI/CD — GitHub Actions + +## Workflows + +- **`ci.yml`** — Runs on PRs to `master`. Jobs: `test-rn`, `test-ios`, `test-android`. +- **`release.yml`** — Triggers on GitHub release creation. Publishes to npm. + +## CI Jobs + +### test-rn (Ubuntu) + +- Node 20, `yarn install --frozen-lockfile`, `yarn compile`, `yarn test`, `assert_export.sh`. + +### test-ios (macOS 14) + +- Xcode 16.1, Ruby 3.2, CocoaPods via Bundler, `xcodebuild test` on iPhone 16 Pro simulator. + +### test-android (macOS 14) + +- Java 17, Gradle 8.13, `./gradlew :react-native-usercentrics:test`. + +## Conventions + +- Always use `--frozen-lockfile` (yarn) or `npm ci` (npm) for deterministic installs. +- Use pinned action versions (`@v1`, `@v2`, `@v4`) — prefer SHA pinning for security-critical actions. +- Do not store secrets in workflow files. Use GitHub Secrets and reference via `${{ secrets.NAME }}`. +- Snyk scanning runs in the pipeline — ensure no new critical/high vulnerabilities are introduced. +- Keep CI fast: run JS tests first (`test-rn`), gate native tests behind it with `needs: test-rn`. diff --git a/.cursor/rules/ios-native.mdc b/.cursor/rules/ios-native.mdc new file mode 100644 index 0000000..5704729 --- /dev/null +++ b/.cursor/rules/ios-native.mdc @@ -0,0 +1,28 @@ +--- +description: iOS native module conventions (Swift, ObjC++) +globs: "ios/**/*.swift,ios/**/*.mm,ios/**/*.h" +alwaysApply: false +--- + +# iOS Native Module + +## Structure + +- `RNUsercentricsModule.swift` — Main module class, bridge methods. +- `RNUsercentricsModule.mm` — ObjC++ bridge for TurboModule registration. +- `Manager/` — Swift helpers/managers that wrap the native Usercentrics iOS SDK. +- `Extensions/` — Swift extensions for type conversions (NSDictionary <-> Swift models). +- `Errors/` — Typed error definitions for the bridge layer. + +## Conventions + +- Use Swift for all new logic. ObjC++ only for required bridge glue. +- Bridging header: `RNUsercentricsModule-Bridging-Header.h`. +- Codegen spec header: `RNUsercentricsModuleSpec.h` (auto-generated, do not edit). +- The podspec is `react-native-usercentrics.podspec` at the repo root. + +## Build + +- Minimum iOS deployment target: 16.0. +- CocoaPods managed via Bundler (`bundle exec pod install`). +- Xcode tests live in `sample/ios/sampleTests/` (XCTest, Swift). diff --git a/.cursor/rules/project-overview.mdc b/.cursor/rules/project-overview.mdc new file mode 100644 index 0000000..fc5a9b5 --- /dev/null +++ b/.cursor/rules/project-overview.mdc @@ -0,0 +1,31 @@ +--- +description: Project overview and architecture for @usercentrics/react-native-sdk +alwaysApply: true +--- + +# Project Overview + +This is `@usercentrics/react-native-sdk` — a React Native bridge for the Usercentrics Consent Management Platform (CMP). + +## Architecture + +- **`src/`** — TypeScript SDK source. Models, Fabric TurboModule spec, and the main `Usercentrics` API. +- **`android/`** — Kotlin native module with C++ JNI bindings (Fabric/TurboModules). +- **`ios/`** — Swift native module with ObjC++ bridge (Fabric/TurboModules). +- **`sample/`** — Primary sample app (RN 0.81, used in CI). +- **`example/`** — Secondary example app (RN 0.78). +- **`legacy-sample/`** — Legacy sample (older RN, Java/Flow). Do not modify unless explicitly asked. + +## Key Conventions + +- Package manager: **yarn 1.x** (classic). Use `yarn install --frozen-lockfile` in CI. +- Compiled output goes to `lib/` via `tsc`. Entry point: `lib/index.js`. +- React Native Codegen is used for Fabric/TurboModule specs in `src/fabric/`. +- The SDK has zero runtime dependencies — only peer deps on `react` and `react-native`. +- All public types/APIs must be re-exported from `src/index.tsx` (enforced by `scripts/assert_export.sh`). + +## Formatting + +- Prettier: single quotes, no semicolons, JSX single quotes. +- ESLint: `@react-native-community` config + `simple-import-sort` plugin. +- Imports must be sorted via `simple-import-sort`. diff --git a/.cursor/rules/react-native.mdc b/.cursor/rules/react-native.mdc new file mode 100644 index 0000000..6384c38 --- /dev/null +++ b/.cursor/rules/react-native.mdc @@ -0,0 +1,30 @@ +--- +description: React Native patterns for SDK and sample apps +globs: "**/*.tsx,**/*.jsx" +alwaysApply: false +--- + +# React Native Patterns + +## SDK Bridge Layer + +- The SDK exposes a singleton `Usercentrics` object (not a React component). +- Native communication goes through `NativeModules` / TurboModules, not direct native calls. +- All native method signatures must match across TS spec, Kotlin, and Swift. + +## Sample App + +- The sample app in `sample/` is the canonical integration test app. +- Metro config uses `watchFolders` to include the parent SDK directory. +- `react-native.config.js` in sample links back to the parent SDK for auto-linking. + +## Component Patterns + +- Functional components only. No class components. +- Use React hooks for state and lifecycle. +- Keep screen components in a flat structure. + +## Navigation + +- The sample app uses React Navigation for screen routing. +- Screens: Home, CustomUI, CustomizationExamples, WebviewIntegration. diff --git a/.cursor/rules/security-snyk.mdc b/.cursor/rules/security-snyk.mdc new file mode 100644 index 0000000..17ea616 --- /dev/null +++ b/.cursor/rules/security-snyk.mdc @@ -0,0 +1,57 @@ +--- +description: Security best practices and Snyk compliance rules +alwaysApply: true +--- + +# Security & Snyk Compliance + +Snyk runs in CI pipelines to scan for vulnerabilities. Follow these rules to avoid introducing issues. + +## Dependency Security + +- **Never** pin exact versions of transitive dependencies to work around vulnerabilities — fix at the source. +- Keep dependencies up to date. Prefer `^` ranges in `package.json` to allow patch updates. +- Do not add dependencies unless strictly necessary. This SDK has **zero** runtime dependencies by design. +- When adding devDependencies, verify they don't have known critical/high CVEs (`npx snyk test` locally). +- Never commit lock files with known vulnerable dependency resolutions without a remediation plan. + +## Secrets & Credentials + +- **Never** hardcode API keys, tokens, secrets, or credentials in source code. +- Never commit `.env` files, `google-services.json`, keystores (`*.jks`, `*.keystore`), or private keys. +- Use environment variables for any sensitive configuration in CI/CD. +- If a secret is needed in a sample app, use a placeholder value and document the setup. + +## Code Security (Snyk Code) + +- Avoid `eval()`, `Function()` constructor, and dynamic code execution. +- Never use `dangerouslySetInnerHTML` without sanitization. +- Sanitize and validate all data crossing the native bridge boundary. +- Do not disable SSL/TLS certificate validation in any code. +- Avoid hardcoded IP addresses or URLs pointing to internal infrastructure. +- Use parameterized queries — never concatenate user input into commands or queries. + +## Native Code Security + +### iOS +- Do not disable App Transport Security (ATS) in production. +- Avoid storing sensitive data in `UserDefaults` — use Keychain for secrets. +- Do not log sensitive user data (PII, consent tokens) in release builds. + +### Android +- Do not set `android:debuggable="true"` in release manifests. +- Do not set `android:allowBackup="true"` without encrypting backup data. +- Avoid using `MODE_WORLD_READABLE` or `MODE_WORLD_WRITEABLE` for file storage. +- Do not log sensitive data with `Log.d`/`Log.v` in release builds — use `Log.e` sparingly. +- Use `ProGuard`/`R8` for release builds to strip debug info. + +## Open Source License Compliance + +- Only add dependencies with licenses compatible with Apache 2.0 (this project's license). +- Snyk also checks license compliance. Avoid GPL-licensed dependencies. + +## Supply Chain + +- Always use `--frozen-lockfile` / `npm ci` in CI to ensure reproducible builds. +- Do not run arbitrary `postinstall` scripts from untrusted packages. +- Review new dependencies before adding them — check download counts, maintenance status, and known issues. diff --git a/.cursor/rules/testing.mdc b/.cursor/rules/testing.mdc new file mode 100644 index 0000000..d467e51 --- /dev/null +++ b/.cursor/rules/testing.mdc @@ -0,0 +1,33 @@ +--- +description: Testing conventions for the SDK +globs: "**/__tests__/**,**/*.test.*,**/sampleTests/**,**/src/test/**,**/src/androidTest/**" +alwaysApply: false +--- + +# Testing + +## JavaScript / TypeScript (Jest) + +- Tests live in `src/__tests__/`. +- Jest config is in `package.json` under the `jest` key (preset: `react-native`). +- Mock native modules in `src/__tests__/mocks.ts`. +- Run with `yarn test`. + +## iOS (XCTest) + +- Tests live in `sample/ios/sampleTests/`. +- Written in Swift, targeting the sample app scheme. +- Run via `xcodebuild test` against `sample.xcworkspace`. + +## Android (JUnit + MockK) + +- Unit tests: `android/src/test/`. +- Instrumented tests: `android/src/androidTest/`. +- Run via `./gradlew :react-native-usercentrics:test` from `sample/android/`. + +## Guidelines + +- Test the public API surface, not internal implementation details. +- Native bridge methods should have corresponding tests on both platforms. +- When adding a new public method to `Usercentrics`, add a Jest test and at minimum one native platform test. +- Keep mocks minimal — only mock the native module boundary. diff --git a/.cursor/rules/typescript-conventions.mdc b/.cursor/rules/typescript-conventions.mdc new file mode 100644 index 0000000..e20e680 --- /dev/null +++ b/.cursor/rules/typescript-conventions.mdc @@ -0,0 +1,36 @@ +--- +description: TypeScript conventions for the SDK source code +globs: src/**/*.ts,src/**/*.tsx +alwaysApply: false +--- + +# TypeScript Conventions + +## Style + +- Strict mode is enabled (`"strict": true` in tsconfig). +- Target: ES2018. Module: ESNext. Module resolution: Node. +- No semicolons, single quotes, JSX single quotes (Prettier enforced). + +## Exports + +- Every public type, enum, interface, and function **must** be re-exported from `src/index.tsx`. +- The CI script `scripts/assert_export.sh` will fail if exports are missing. + +## Models + +- Model files live in `src/models/`. One model concern per file. +- Use TypeScript `enum` for SDK enums (matching native SDK enums). +- Use `interface` for data structures returned from native side. + +## Fabric / TurboModules + +- The TurboModule spec lives in `src/fabric/NativeUsercentricsModule.ts`. +- It uses `TurboModuleRegistry` and `codegenNativeComponent`. +- Codegen config is in `package.json` under `codegenConfig`. +- Changes to the spec require regenerating JNI bindings (`npm run prepublishOnly`). + +## Error Handling + +- Native bridge calls can throw. Always handle errors from `NativeModules` calls. +- Use typed error objects rather than raw string checks. diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..d63bd79 --- /dev/null +++ b/.cursorignore @@ -0,0 +1,113 @@ +# === Dependencies === +node_modules/ +sample/node_modules/ +example/node_modules/ +legacy-sample/node_modules/ +jspm_packages/ +web_modules/ +bower_components/ + +# === Build outputs === +lib/ +**/build/ +.gradle/ +.gradle +buck-out/ +.cxx/ +.externalNativeBuild/ +DerivedData/ + +# === iOS artifacts === +**/Pods/ +sample/vendor/bundle/ +*.ipa +*.dSYM.zip +*.dSYM +xcuserdata/ + +# === Android artifacts === +*.apk +*.aab +*.hprof +local.properties +captures/ + +# === Package archives === +*.tgz +*.tar.gz +*.zip + +# === IDE / Editor === +.idea/ +*.iml +.vscode-test +*.xccheckout +*.xcscmblueprint +*.xcuserstate + +# === OS files === +.DS_Store +._* +.AppleDouble +.LSOverride +Thumbs.db +.Spotlight-V100 +.Trashes + +# === Logs === +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# === Coverage / test output === +coverage/ +*.lcov +.nyc_output + +# === Caches === +.eslintcache +.stylelintcache +*.tsbuildinfo +.cache/ +.parcel-cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ +.metro-health-check* +.fusebox/ + +# === Secrets / environment === +.env +.env.* +!.env.example +google-services.json +*.jks +*.keystore +!debug.keystore + +# === Expo / Metro === +.expo +__generated__ + +# === Yarn v2+ === +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# === Buck === +.buckconfig.local +.buckd/ +.buckversion +.fakebuckversion + +# === Misc generated === +.docusaurus +.serverless/ +.dynamodb/ +.temp +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/.cursorindexingignore b/.cursorindexingignore new file mode 100644 index 0000000..3e9538d --- /dev/null +++ b/.cursorindexingignore @@ -0,0 +1,48 @@ +# === Lock files (large, not useful for indexing) === +package-lock.json +sample/package-lock.json +example/package-lock.json +**/Podfile.lock +**/Gemfile.lock +yarn.lock + +# === Xcode project files (huge, binary-like) === +**/*.pbxproj +**/*.xcworkspacedata + +# === Legacy & secondary sample apps === +legacy-sample/ +example/ + +# === Gradle wrapper binaries === +**/gradle/wrapper/gradle-wrapper.jar + +# === Codegen / JNI generated files === +android/src/main/jni/ +sample/ios/build/generated/ + +# === GitHub templates & workflows === +.github/ISSUE_TEMPLATE/ + +# === Patch files === +sample/patches/ + +# === Ruby / CocoaPods config === +sample/Gemfile +sample/.ruby-version + +# === Script files (utility, not core logic) === +scripts/ +sample/scripts/ + +# === License / changelog === +LICENSE +CHANGELOG.md + +# === Xcode build-phase scripts === +**/*.xctestplan + +# === Android generated === +android/build.gradle.kts +android/settings.gradle.kts +android/gradle.properties diff --git a/sample/.ruby-version b/sample/.ruby-version new file mode 100644 index 0000000..944880f --- /dev/null +++ b/sample/.ruby-version @@ -0,0 +1 @@ +3.2.0 diff --git a/sample/Gemfile.lock b/sample/Gemfile.lock new file mode 100644 index 0000000..b5234e9 --- /dev/null +++ b/sample/Gemfile.lock @@ -0,0 +1,126 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + activesupport (7.2.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + claide (1.1.0) + cocoapods (1.16.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.16.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + drb (2.2.3) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.17.2) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) + concurrent-ruby (~> 1.0) + json (2.18.0) + logger (1.7.0) + minitest (6.0.0) + prism (~> 1.5) + molinillo (0.8.0) + mutex_m (0.3.0) + nanaimo (0.4.0) + nap (1.1.0) + netrc (0.11.0) + prism (1.7.0) + public_suffix (4.0.7) + rexml (3.4.4) + rouge (3.28.0) + ruby-macho (2.5.1) + securerandom (0.4.1) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + +PLATFORMS + ruby + +DEPENDENCIES + activesupport (>= 7.0) + benchmark + bigdecimal + cocoapods (~> 1.16.2) + concurrent-ruby + logger + mutex_m + xcodeproj (~> 1.27.0) + xcpretty + +RUBY VERSION + ruby 3.2.0p0 + +BUNDLED WITH + 2.4.1