Skip to content

Conversation

@sharifhh
Copy link

@sharifhh sharifhh commented Jan 21, 2026

User description

User description

Codegen generates Double for number, you can check here https://reactnative.dev/docs/appendix

right now your whole TurboModule doesn't work for functions that takes number primitive type.


PR Type

Bug fix


Description

  • Replace Int with Double for number parameters in TurboModule methods

  • Align with React Native codegen specification for number primitives

  • Fix compatibility with Android New Architecture


Diagram Walkthrough

flowchart LR
  A["RNUsercentricsModuleSpec"] -->|"Update parameter types"| B["Int to Double conversion"]
  B -->|"Affects methods"| C["setCMPId, acceptAll, denyAll, track, etc."]
  C -->|"Ensures compatibility"| D["React Native codegen spec"]
Loading

File Walkthrough

Relevant files
Bug fix
RNUsercentricsModuleSpec.kt
Convert Int parameters to Double in TurboModule spec         

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt

  • Changed parameter type from Int to Double in setCMPId method
  • Updated acceptAll, acceptAllForTCF, denyAll, denyAllForTCF methods to
    use Double for numeric parameters
  • Modified saveDecisions and saveDecisionsForTCF methods to accept
    Double instead of Int
  • Changed saveOptOutForCCPA and track methods to use Double for numeric
    parameters
+10/-10 


CodeAnt-AI Description

Use Double for numeric TurboModule parameters to fix Android New Architecture compatibility

What Changed

  • All numeric parameters exposed to JS were changed from Int to Double in the TurboModule spec and implementation.
  • Calls from JS now pass Double values and are converted back to Int before using enum indexes or native APIs, fixing methods such as setCMPId, acceptAll, denyAll, acceptAllForTCF, denyAllForTCF, saveDecisions, saveDecisionsForTCF, saveOptOutForCCPA and track.
  • Promises and returned arrays/maps behavior is unchanged; this only affects how numeric arguments are received from JS to native.

Impact

✅ Fixes TurboModule numeric parameter failures on Android New Architecture
✅ Consent actions (accept/deny/track/save) invoked from JS work reliably
✅ Prevents runtime type errors for numeric inputs in the native module

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

  • Refactor
    • Updated Android native module parameters to support floating-point number inputs across consent management methods.

✏️ Tip: You can customize this high-level summary in your review settings.

@codeant-ai
Copy link

codeant-ai bot commented Jan 21, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 21, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@coderabbitai
Copy link

coderabbitai bot commented Jan 21, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Parameter types across nine ReactMethod signatures in the Usercentrics React Native module are converted from Int to Double. Implementation methods include corresponding .toInt() conversions before enum array index lookups to maintain semantic equivalence. No control flow or error handling changes are made.

Changes

Cohort / File(s) Summary
Module Interface Specification
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
Abstract method parameter types changed from Int to Double across nine methods: setCMPId, acceptAll, acceptAllForTCF, denyAll, denyAllForTCF, saveDecisions, saveDecisionsForTCF, saveOptOutForCCPA, track. Return types and method names unchanged.
Module Implementation
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
Implementation methods updated to match interface signatures. All enum index accesses converted with .toInt() calls: TCFDecisionUILayer.values()[fromLayer.toInt()], UsercentricsConsentType.values()[consentType.toInt()], UsercentricsAnalyticsEventType.values()[event.toInt()].

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

Review effort 2/5

Suggested reviewers

  • souzabrunoj
  • uc-brunosouza

Poem

🐰 Double the precision, halves the bytes?
No wait—we shrink it back with .toInt() rites!
Parameter types now float on high,
Till enums call them back to earth nearby. 🌱

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix Android New Arch Issue' is vague and generic, using non-specific language ('Issue') that doesn't clearly convey the actual change (parameter type conversion from Int to Double for TurboModule compatibility). Consider a more descriptive title like 'Change TurboModule numeric parameters from Int to Double for New Arch compatibility' to clearly communicate the nature of the fix.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codeant-ai codeant-ai bot added the size:M This PR changes 30-99 lines, ignoring generated files label Jan 21, 2026
@pantoaibot
Copy link

pantoaibot bot commented Jan 21, 2026

PR Summary:

Update RN Usercentrics module spec to use Double for numeric params to fix React Native New Architecture (codegen/TurboModule) type mismatches.

Changes:

  • File modified: android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
  • Replaced Int parameters with Double for methods exposed to JS:
    • setCMPId(id)
    • acceptAll(consentType)
    • acceptAllForTCF(fromLayer, consentType)
    • denyAll(consentType)
    • denyAllForTCF(fromLayer, consentType, unsavedPurposeLIDecisions)
    • saveDecisions(decisions, consentType)
    • saveDecisionsForTCF(tcfDecisions, fromLayer, saveDecisions, consentType, promise)
    • saveOptOutForCCPA(isOptedOut, consentType)
    • track(event)
  • No changes to method names, return types, or promise usage.
  • Purpose: avoid runtime/class-cast errors under RN New Architecture where JS numbers are treated as Double by codegen.

Impact:

  • Compatibility fix for TurboModules / new RN codegen; rebuild required. No functional or behavioral logic changes.

Reviewed by Panto AI

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Cast and validate Double to Int

In the implementation of acceptAll, convert the consentType from Double to Int
and validate that it is a whole number to avoid precision issues.

android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt [67]

-abstract fun acceptAll(consentType: Double, promise: Promise)
+override fun acceptAll(consentType: Double, promise: Promise) {
+    val typeInt = consentType.toInt().takeIf { it.toDouble() == consentType }
+        ?: return promise.reject("INVALID_ARGUMENT", "consentType must be an integer")
+    core.acceptAll(typeInt, promise)
+}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential issue where fractional Double values from JavaScript could cause problems when an Int is expected, and it proposes a robust validation and conversion strategy for the implementation class.

Medium
  • More

@codeant-ai
Copy link

codeant-ai bot commented Jan 21, 2026

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Enum index bounds
    Several methods convert incoming Double parameters to Int and use them to index enum arrays (e.g. TCFDecisionUILayer.values()[index], UsercentricsConsentType.values()[index], UsercentricsAnalyticsEventType.values()[index]) without validating the resulting index. If the JS layer sends an out-of-range number, negative value, NaN or Infinity, this will throw IndexOutOfBoundsException or produce unexpected behaviour. Caller input should be validated and handled gracefully.

  • Non-finite Double handling
    Double inputs may be NaN or infinite. Calling .toInt() on such values yields surprising results. Inputs should be checked with isFinite() (or isNaN()/isInfinite()) and handled (reject promise or use fallback).

  • Precision & range checks
    JavaScript numbers are doubles and can contain fractional values or values outside Int range. Methods that interpret these numbers as enum IDs or indexes (e.g. consentType, fromLayer, event) must validate that values are finite integers and within expected bounds before converting to Int to avoid subtle bugs or overflow.

  • Truncation / rounding semantics
    Converting Double to Int via toInt() truncates toward zero. If JS can send non-integer numbers (e.g. 1.9) this silently changes meaning. Decide whether values should be rounded, floored, or rejected and apply consistent conversion. This affects setCMPId, track and all enum-indexing usages.

  • Required runtime conversion
    The spec signatures were changed from Int to Double. Any existing module implementation that previously accepted Int must now convert Double to the integer types the underlying SDK expects. Ensure all implementations cast safely (with validation) and update call sites to avoid ClassCast or logic errors.


@ReactMethod
abstract fun setCMPId(id: Int)
abstract fun setCMPId(id: Double)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Non-nullable Double parameter will be compiled to a primitive double and will throw a NullPointerException if the JS bridge passes null; make the parameter nullable (Double?) so it accepts boxed null values coming from JS interop. [null pointer]

Severity Level: Critical 🚨
- ❌ Native module call setCMPId may crash on null input.
- ⚠️ JS callers that omit id receive failed native invocation.
Suggested change
abstract fun setCMPId(id: Double)
abstract fun setCMPId(id: Double?)
Steps of Reproduction ✅
1. Build and run the Android app with this PR code so the TurboModule is registered (file:
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt,
declaration at line 58).

2. From JavaScript, call the native method exported by the TurboModule (e.g.,
RNUsercentricsModule.setCMPId(null) or omit the numeric argument so JS sends
null/undefined).

3. The React Native bridge invokes the generated/native implementation for the spec which
maps to the abstract signature at RNUsercentricsModuleSpec.kt:58; the bridge passes a
boxed Double which can be null.

4. Kotlin's non-nullable Double compiles to a primitive double and the runtime attempts to
unbox the null, causing a NullPointerException at the unboxing site and the native call to
fail.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
**Line:** 58:58
**Comment:**
	*Null Pointer: Non-nullable `Double` parameter will be compiled to a primitive `double` and will throw a NullPointerException if the JS bridge passes null; make the parameter nullable (`Double?`) so it accepts boxed null values coming from JS interop.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.


@ReactMethod
abstract fun acceptAllForTCF(fromLayer: Int, consentType: Int, promise: Promise)
abstract fun acceptAllForTCF(fromLayer: Double, consentType: Double, promise: Promise)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Non-nullable Double parameters in the bridge method can cause unboxing NullPointerExceptions if JS sends null; declare fromLayer and consentType as nullable Double? so the module safely accepts boxed null values from the JS side. [null pointer]

Severity Level: Critical 🚨
- ❌ acceptAllForTCF native calls may throw runtime exceptions.
- ⚠️ Consent flows relying on TCF acceptance break unpredictably.
Suggested change
abstract fun acceptAllForTCF(fromLayer: Double, consentType: Double, promise: Promise)
abstract fun acceptAllForTCF(fromLayer: Double?, consentType: Double?, promise: Promise)
Steps of Reproduction ✅
1. Deploy the Android app containing RNUsercentricsModuleSpec (file path:
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt, method at
line 70).

2. From the React Native JS layer, invoke acceptAllForTCF with missing/null numeric args
(e.g., RNUsercentricsModule.acceptAllForTCF(null, null, promise)).

3. The TurboModule bridge calls into the native implementation bound to the abstract
signature at RNUsercentricsModuleSpec.kt:70 and forwards boxed Double values.

4. Because the signature requires non-nullable Double, the JVM will unbox the null and
throw an NPE, causing the native method to fail and the promise to reject unexpectedly.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
**Line:** 70:70
**Comment:**
	*Null Pointer: Non-nullable `Double` parameters in the bridge method can cause unboxing NullPointerExceptions if JS sends null; declare `fromLayer` and `consentType` as nullable `Double?` so the module safely accepts boxed null values from the JS side.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.


@ReactMethod
abstract fun denyAllForTCF(fromLayer: Int, consentType: Int, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)
abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Method accepts a ReadableArray from JS alongside Double values; the consentType Double should be nullable (Double?) to avoid unboxing NullPointerException if JS passes null/undefined for numeric args. [null pointer]

Severity Level: Critical 🚨
- ❌ denyAllForTCF can crash when JS supplies null numerics.
- ⚠️ Purpose-level decision processing may be interrupted.
Suggested change
abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)
abstract fun denyAllForTCF(fromLayer: Double?, consentType: Double?, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)
Steps of Reproduction ✅
1. Install the PR version of the Android module so RNUsercentricsModuleSpec is used (see
file android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt,
method at line 76).

2. From JS, call denyAllForTCF and pass null for numeric args while providing a
ReadableArray (e.g., RNUsercentricsModule.denyAllForTCF(null, null, someArray, promise)).

3. The TurboModule bridge forwards boxed numeric values to the native method bound to
RNUsercentricsModuleSpec.kt:76.

4. The non-nullable Double parameters force JVM unboxing of null, causing an NPE and
failing the native denyAllForTCF execution and promise resolution.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
**Line:** 76:76
**Comment:**
	*Null Pointer: Method accepts a `ReadableArray` from JS alongside `Double` values; the `consentType` Double should be nullable (`Double?`) to avoid unboxing NullPointerException if JS passes null/undefined for numeric args.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.


@ReactMethod
abstract fun track(event: Int)
abstract fun track(event: Double)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The track method's event parameter is declared as non-nullable Double which will be compiled to a primitive and will NPE if JS sends null; change it to nullable Double? (or a boxed type) to safely accept bridge values. [null pointer]

Severity Level: Critical 🚨
- ❌ Analytics track calls may throw runtime exceptions.
- ⚠️ Event tracking reliability degraded on null inputs.
Suggested change
abstract fun track(event: Double)
abstract fun track(event: Double?)
Steps of Reproduction ✅
1. Run the Android app including this PR so the TurboModule and RNUsercentricsModuleSpec
are active (file:
android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt, line 94).

2. From JS, call the tracking method with null or undefined event (e.g.,
RNUsercentricsModule.track(null) or omit event).

3. The React Native bridge invokes the native implementation tied to
RNUsercentricsModuleSpec.kt:94, passing a boxed Double that may be null.

4. The non-nullable Double requires unboxing; if null is received the JVM throws an NPE
and the track call fails silently or raises an exception on the native side.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModuleSpec.kt
**Line:** 94:94
**Comment:**
	*Null Pointer: The `track` method's `event` parameter is declared as non-nullable `Double` which will be compiled to a primitive and will NPE if JS sends null; change it to nullable `Double?` (or a boxed type) to safely accept bridge values.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines 58 to 94
abstract fun setCMPId(id: Double)

@ReactMethod
abstract fun setABTestingVariant(variant: String)

@ReactMethod
abstract fun changeLanguage(language: String, promise: Promise)

@ReactMethod
abstract fun acceptAll(consentType: Int, promise: Promise)
abstract fun acceptAll(consentType: Double, promise: Promise)

@ReactMethod
abstract fun acceptAllForTCF(fromLayer: Int, consentType: Int, promise: Promise)
abstract fun acceptAllForTCF(fromLayer: Double, consentType: Double, promise: Promise)

@ReactMethod
abstract fun denyAll(consentType: Int, promise: Promise)
abstract fun denyAll(consentType: Double, promise: Promise)

@ReactMethod
abstract fun denyAllForTCF(fromLayer: Int, consentType: Int, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)
abstract fun denyAllForTCF(fromLayer: Double, consentType: Double, unsavedPurposeLIDecisions: ReadableArray, promise: Promise)

@ReactMethod
abstract fun saveDecisions(decisions: ReadableArray, consentType: Int, promise: Promise)
abstract fun saveDecisions(decisions: ReadableArray, consentType: Double, promise: Promise)

@ReactMethod
abstract fun saveDecisionsForTCF(
tcfDecisions: ReadableMap,
fromLayer: Int,
fromLayer: Double,
saveDecisions: ReadableArray,
consentType: Int,
consentType: Double,
promise: Promise
)

@ReactMethod
abstract fun saveOptOutForCCPA(isOptedOut: Boolean, consentType: Int, promise: Promise)
abstract fun saveOptOutForCCPA(isOptedOut: Boolean, consentType: Double, promise: Promise)

@ReactMethod
abstract fun track(event: Int)
abstract fun track(event: Double)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] You changed multiple method signatures from Int to Double in the TurboModule spec. This is the correct direction for New Architecture codegen (number -> Double), but you must also update the concrete RNUsercentricsModule implementation and any call sites/tests to accept Double and convert safely. Right now tests such as testSetCMPId and others (see RNUsercentricsModuleTest.kt around lines 341-382 and 473-691) call methods with Ints and expect integer/enums on the Kotlin side. Actionable steps:

  • Update RNUsercentricsModule method signatures to accept Double (or keep Double in spec and add overloads) so the generated interface matches the implementation.
  • Immediately convert Double -> the needed Kotlin types inside the implementation (e.g. toInt() and map to enums) with validation (see other suggestions). If you leave only spec changes without updating implementation/tests, the build will fail or runtime mapping will be incorrect.
// In RNUsercentricsModule.kt (implementation)

@ReactMethod
override fun setCMPId(id: Double) {
    val intId = id.toInt()
    usercentricsProxy.setCMPId(intId)
}

@ReactMethod
override fun acceptAll(consentType: Double, promise: Promise) {
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    handleResult(promise) {
        usercentricsProxy.acceptAll(type)
    }
}

@ReactMethod
override fun acceptAllForTCF(fromLayer: Double, consentType: Double, promise: Promise) {
    val layer = TCFDecisionUILayer.values()[fromLayer.toInt()]
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    handleResult(promise) {
        usercentricsProxy.acceptAllForTCF(layer, type)
    }
}

@ReactMethod
override fun denyAll(consentType: Double, promise: Promise) {
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    handleResult(promise) {
        usercentricsProxy.denyAll(type)
    }
}

@ReactMethod
override fun denyAllForTCF(
    fromLayer: Double,
    consentType: Double,
    unsavedPurposeLIDecisions: ReadableArray,
    promise: Promise,
) {
    val layer = TCFDecisionUILayer.values()[fromLayer.toInt()]
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    val liDecisions = unsavedPurposeLIDecisions.toUserDecisionsOrNull()
    handleResult(promise) {
        usercentricsProxy.denyAllForTCF(layer, type, liDecisions)
    }
}

@ReactMethod
override fun saveDecisions(
    decisions: ReadableArray,
    consentType: Double,
    promise: Promise,
) {
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    val mapped = decisions.toUserDecisions()
    handleResult(promise) {
        usercentricsProxy.saveDecisions(mapped, type)
    }
}

@ReactMethod
override fun saveDecisionsForTCF(
    tcfDecisions: ReadableMap,
    fromLayer: Double,
    saveDecisions: ReadableArray,
    consentType: Double,
    promise: Promise,
) {
    val layer = TCFDecisionUILayer.values()[fromLayer.toInt()]
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    val tcf = tcfDecisions.toTCFUserDecisions()
    val mapped = saveDecisions.toUserDecisions()
    handleResult(promise) {
        usercentricsProxy.saveDecisionsForTCF(tcf, layer, mapped, type)
    }
}

@ReactMethod
override fun saveOptOutForCCPA(
    isOptedOut: Boolean,
    consentType: Double,
    promise: Promise,
) {
    val type = UsercentricsConsentType.values()[consentType.toInt()]
    handleResult(promise) {
        usercentricsProxy.saveOptOutForCCPA(isOptedOut, type)
    }
}

@ReactMethod
override fun track(event: Double) {
    val eventType = UsercentricsAnalyticsEventType.values()[event.toInt()]
    usercentricsProxy.track(eventType)
}

@pantoaibot
Copy link

pantoaibot bot commented Jan 21, 2026

Reviewed up to commit:90a4c939250d3e4ddeac3d88392464511c21a8d1

Additional Suggestion
Others - Introduce a single helper utility (e.g. NumberUtils or BridgeConverters) that centralizes conversions from Double -> Int and Double -> Enum (with bounds checks). Many methods require the same conversions (consentType, fromLayer, event, cmpId). Centralizing avoids duplication and makes it easier to maintain behavior (rounding/truncation policy, error messages). Example helper signatures: - fun doubleToIntStrict(value: Double, fieldName: String): Int - fun doubleToEnumOrdinal>(value: Double, values: Array, fieldName: String): T Use these helpers in every bridge method instead of repeating conversion logic.
// New file: android/src/main/java/com/usercentrics/reactnative/util/BridgeConverters.kt

package com.usercentrics.reactnative.util

import com.facebook.react.bridge.Promise

internal object BridgeConverters {

    fun doubleToIntStrict(value: Double, fieldName: String, promise: Promise? = null): Int {
        if (!value.isFinite()) {
            val message = "Invalid $fieldName: value must be finite"
            promise?.reject(IllegalArgumentException(message))
            throw IllegalArgumentException(message)
        }

        val intValue = value.toInt()
        if (intValue.toDouble() != value) {
            val message = "Invalid $fieldName: $value is not an integer"
            promise?.reject(IllegalArgumentException(message))
            throw IllegalArgumentException(message)
        }

        return intValue
    }

    fun <T : Enum<T>> doubleToEnumOrdinal(
        value: Double,
        values: Array<T>,
        fieldName: String,
        promise: Promise? = null,
    ): T {
        val index = doubleToIntStrict(value, fieldName, promise)
        if (index !in values.indices) {
            val message = "Invalid $fieldName: index $index out of bounds [0, ${values.lastIndex}]"
            promise?.reject(IllegalArgumentException(message))
            throw IllegalArgumentException(message)
        }
        return values[index]
    }
}

// Usage example in RNUsercentricsModule.kt

@ReactMethod
override fun track(event: Double) {
    val eventType = BridgeConverters.doubleToEnumOrdinal(
        value = event,
        values = UsercentricsAnalyticsEventType.values(),
        fieldName = "event"
    )
    usercentricsProxy.track(eventType)
}
  • Add validation for incoming Double values before converting: check value.isFinite() and not NaN, then ensure it is within the expected integer range and an integer when applicable (or define explicit rounding). For enum ordinals (e.g. fromLayer, consentType, event) validate that the resulting Int is within values().indices and return/reject with a clear error if out of bounds. Example behavior: if value is fractional, either reject with an error indicating invalid parameter type or explicitly apply Math.floor/round with documentation. This prevents silent wrong enum mapping or ArrayIndexOutOfBounds exceptions at runtime.
// Example validation usage in RNUsercentricsModule.kt

@ReactMethod
override fun acceptAll(consentType: Double, promise: Promise) {
    try {
        val type = BridgeConverters.doubleToEnumOrdinal(
            value = consentType,
            values = UsercentricsConsentType.values(),
            fieldName = "consentType",
            promise = promise,
        )

        handleResult(promise) {
            usercentricsProxy.acceptAll(type)
        }
    } catch (_: IllegalArgumentException) {
        // Promise already rejected in converter
    }
}

@ReactMethod
override fun setCMPId(id: Double, promise: Promise) {
    try {
        val intId = BridgeConverters.doubleToIntStrict(id, "cmpId", promise)
        usercentricsProxy.setCMPId(intId)
        promise.resolve(null)
    } catch (_: IllegalArgumentException) {
        // Promise already rejected
    }
}

Reviewed by Panto AI

@codeant-ai
Copy link

codeant-ai bot commented Jan 21, 2026

CodeAnt AI finished reviewing your PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt`:
- Around line 206-208: The track method currently indexes
UsercentricsAnalyticsEventType.values() with event.toInt() which can throw
ArrayIndexOutOfBounds; update RNUsercentricsModule.track to validate the index
before using it (e.g., obtain vals = UsercentricsAnalyticsEventType.values(),
check eventIndex >= 0 && eventIndex < vals.size), and if out of range handle
gracefully (log via usercentricsProxy.instance or no-op/return) instead of
indexing directly into UsercentricsAnalyticsEventType.values().
- Around line 134-140: The acceptAllForTCF method currently indexes enums
directly with TCFDecisionUILayer.values()[fromLayer.toInt()] and
UsercentricsConsentType.values()[consentType.toInt()] which can throw
ArrayIndexOutOfBoundsException for invalid Double inputs; add defensive
validation or conversion helper(s) (e.g., getTCFDecisionUILayer(fromLayer:
Double) and getConsentType(consentType: Double)) that convert toInt(), check
index is within values().indices, and throw/return a clear error; wrap the enum
access in try-catch in acceptAllForTCF and call promise.reject(...) with a
descriptive message on failure, otherwise resolve as before.

Comment on lines +134 to 140
override fun acceptAllForTCF(fromLayer: Double, consentType: Double, promise: Promise) {
promise.resolve(
usercentricsProxy.instance.acceptAllForTCF(
TCFDecisionUILayer.values()[fromLayer], UsercentricsConsentType.values()[consentType]
TCFDecisionUILayer.values()[fromLayer.toInt()], UsercentricsConsentType.values()[consentType.toInt()]
).toWritableArray()
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential ArrayIndexOutOfBoundsException from unchecked enum index access.

Using .values()[fromLayer.toInt()] and .values()[consentType.toInt()] without bounds validation can cause runtime crashes if JavaScript passes invalid values (negative numbers, values exceeding enum size, or fractional values that truncate unexpectedly).

While this pattern existed before the IntDouble change, the wider Double range and truncation behavior (e.g., 2.92) increase the surface for unexpected inputs.

Consider adding defensive bounds checking, or at minimum, wrapping enum access in a try-catch to provide a meaningful error message back to the JS layer via promise.reject().

Example defensive approach
private fun getConsentType(value: Double): UsercentricsConsentType {
    val index = value.toInt()
    val values = UsercentricsConsentType.values()
    require(index in values.indices) { "Invalid consentType: $value" }
    return values[index]
}
🤖 Prompt for AI Agents
In `@android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt`
around lines 134 - 140, The acceptAllForTCF method currently indexes enums
directly with TCFDecisionUILayer.values()[fromLayer.toInt()] and
UsercentricsConsentType.values()[consentType.toInt()] which can throw
ArrayIndexOutOfBoundsException for invalid Double inputs; add defensive
validation or conversion helper(s) (e.g., getTCFDecisionUILayer(fromLayer:
Double) and getConsentType(consentType: Double)) that convert toInt(), check
index is within values().indices, and throw/return a clear error; wrap the enum
access in try-catch in acceptAllForTCF and call promise.reject(...) with a
descriptive message on failure, otherwise resolve as before.

Comment on lines +206 to 208
override fun track(event: Double) {
usercentricsProxy.instance.track(UsercentricsAnalyticsEventType.values()[event.toInt()])
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same bounds-check concern applies here.

UsercentricsAnalyticsEventType.values()[event.toInt()] has the same risk as the consent-type methods. If this enum is controlled by the SDK and validated elsewhere, this may be acceptable, but the same defensive pattern would improve robustness.

🤖 Prompt for AI Agents
In `@android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt`
around lines 206 - 208, The track method currently indexes
UsercentricsAnalyticsEventType.values() with event.toInt() which can throw
ArrayIndexOutOfBounds; update RNUsercentricsModule.track to validate the index
before using it (e.g., obtain vals = UsercentricsAnalyticsEventType.values(),
check eventIndex >= 0 && eventIndex < vals.size), and if out of range handle
gracefully (log via usercentricsProxy.instance or no-op/return) instead of
indexing directly into UsercentricsAnalyticsEventType.values().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Review effort 1/5 size:M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant