feat(MSDK-4088): Support PL-5714 new COP restriction fields across TS, Android and iOS#218
Conversation
…, Android and iOS
Adds ConsentOrPayRestriction class and two new PL-5714 fields:
- TCF2ChangedPurposes.consentOrPay — array of {id, value: FLEXIBLE|MANDATORY}
- TCF2Settings.specialFeaturesConsentOrPay — same format
Both Android and iOS bridges now serialize these fields alongside the legacy
publisherRestrictions/specialFeatures maps for backwards compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
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 · |
📝 WalkthroughWalkthroughThis PR extends the React Native SDK's TCF2 settings model across TypeScript, iOS, and Android by introducing ChangesConsent-or-Pay Restrictions Data Flow
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Review Summary by QodoSupport PL-5714 consent-or-pay restriction fields across platforms
WalkthroughsDescription• Add ConsentOrPayRestriction class with isFlexible() helper method • Support new PL-5714 fields in TCF2Settings and TCF2ChangedPurposes • Serialize specialFeaturesConsentOrPay in Android and iOS bridges • Serialize consentOrPay array in TCF2ChangedPurposes across all platforms Diagramflowchart LR
A["PL-5714 Schema"] -->|"New Fields"| B["ConsentOrPayRestriction"]
B -->|"Added to"| C["TCF2Settings"]
B -->|"Added to"| D["TCF2ChangedPurposes"]
C -->|"Serialized by"| E["Android Bridge"]
C -->|"Serialized by"| F["iOS Bridge"]
D -->|"Serialized by"| E
D -->|"Serialized by"| F
E -->|"TypeScript Model"| G["TCF2Settings.tsx"]
F -->|"TypeScript Model"| G
File Changes1. src/models/TCF2Settings.tsx
|
Code Review by Qodo
Context used✅ Tickets:
MSDK-4088 1. Null vs missing divergence
|
|
PR Summary: Add support for new Consent-or-Pay (COP) restriction fields (ConsentOrPayRestriction) across TypeScript models and native (Android/iOS) serialization so COP special-feature restrictions are passed through the bridge. Changes:
Notes and compatibility:
|
| private fun ConsentOrPayRestriction.serialize(): Map<String, Any?> = mapOf( | ||
| "id" to id, | ||
| "value" to value, | ||
| ) |
There was a problem hiding this comment.
[VALIDATION] ConsentOrPayRestriction.serialize() returns id and value directly. Ensure the SDK's ConsentOrPayRestriction.value is a JSON-friendly type (String). If the SDK defines value as an enum, explicitly serialize it to a string (e.g. value.name or value.toString().uppercase()) so the JS side receives the expected 'FLEXIBLE'|'MANDATORY' strings. Also consider normalizing casing (uppercase) to match the TypeScript expectation.
private fun ConsentOrPayRestriction.serialize(): Map<String, Any?> = mapOf(
"id" to id,
"value" to value.toString().uppercase(),
)| "resurfacePeriod": self.resurfacePeriod, | ||
| "consentOrPay": self.consentOrPay?.toDictionary() as Any, | ||
| "mandatoryLabel": self.mandatoryLabel, | ||
| "specialFeaturesConsentOrPay": self.specialFeaturesConsentOrPay?.map { $0.toDictionary() } as Any, |
There was a problem hiding this comment.
[REFACTORING] You added "specialFeaturesConsentOrPay": self.specialFeaturesConsentOrPay?.map { $0.toDictionary() } as Any. Ensure the resulting array elements are JSON-serializable types (String/Int/Bool/NSDictionary). If ConsentOrPayRestriction.value is an enum on the native SDK, convert it to rawValue or String(describing:) in toDictionary() so the JS bridge receives plain strings (matching the TS 'FLEXIBLE'|'MANDATORY' expectation).
extension ConsentOrPayRestriction {
func toDictionary() -> [String: Any] {
return [
"id": self.id,
"value": (self.value as? CustomStringConvertible)?.description ?? String(describing: self.value),
]
}
}| selectedATPIds: number[] | ||
| consentOrPay?: TCF2ConsentOrPaySettings | ||
| mandatoryLabel: string | ||
| specialFeaturesConsentOrPay?: ConsentOrPayRestriction[] |
There was a problem hiding this comment.
[REFACTORING] You introduced specialFeaturesConsentOrPay?: ConsentOrPayRestriction[] and the ConsentOrPayRestriction class with value: string. For stronger type-safety, change the value type to a string literal union: value: 'FLEXIBLE' | 'MANDATORY'. This prevents invalid values being created in TypeScript consumers. Also remove the unnecessary optional-chaining in isFlexible(): use return this.value.toUpperCase() === 'FLEXIBLE' (value is non-nullable if typed as the union).
export class ConsentOrPayRestriction {
id: number
value: 'FLEXIBLE' | 'MANDATORY'
constructor(id: number, value: 'FLEXIBLE' | 'MANDATORY') {
this.id = id
this.value = value
}
isFlexible(): boolean {
return this.value.toUpperCase() === 'FLEXIBLE'
}
}|
Reviewed up to commit:3de3c6bb9cd5691cc494d9215a431450771e6097 Additional SuggestionOthers- Update native/unit mocks and expected snapshots to include the new fields: Android GetCMPDataMock.expectedSettings (and related expectedTCF2Settings) must include specialFeaturesConsentOrPay and changedPurposes.consentOrPay entries. Without updates tests will fail. See android/src/androidTest/mock/GetCMPDataMock.kt referenced in this PR for locations to update.// android/src/androidTest/java/com/usercentrics/reactnative/mock/GetCMPDataMock.kt
import com.usercentrics.sdk.v2.settings.data.ConsentOrPayRestriction
// when creating fakeTCF2Settings
private val fakeTCF2Settings = TCF2Settings(
// ...existing args...
changedPurposes = TCF2ChangedPurposes(
purposes = listOf(1, 2, 3),
legIntPurposes = listOf(1, 2, 3),
consentOrPay = listOf(
ConsentOrPayRestriction(id = 1, value = "FLEXIBLE"),
ConsentOrPayRestriction(id = 2, value = "MANDATORY"),
),
),
// ...existing args...
specialFeaturesConsentOrPay = listOf(
ConsentOrPayRestriction(id = 1, value = "FLEXIBLE"),
),
)
// in expectedTCF2Settings
private val expectedTCF2Settings = hashMapOf(
// ...existing expectations...
"changedPurposes" to hashMapOf(
"purposes" to listOf(1, 2, 3),
"legIntPurposes" to listOf(1, 2, 3),
"consentOrPay" to listOf(
hashMapOf("id" to 1, "value" to "FLEXIBLE"),
hashMapOf("id" to 2, "value" to "MANDATORY"),
),
),
"specialFeaturesConsentOrPay" to listOf(
hashMapOf("id" to 1, "value" to "FLEXIBLE"),
),
)
// sample/ios/sampleTests/Mock/CMPData+Mock.swift
extension TCF2ChangedPurposes {
static func mock() -> TCF2ChangedPurposes {
return .init(
purposes: [1,2,3],
legIntPurposes: [1,2,3],
notAllowedPurposes: [1,2,3],
consentOrPay: [ConsentOrPayRestriction(id: 1, value: .flexible)]
)
}
}
extension TCF2Settings {
static func mock() -> TCF2Settings {
return .init(
/* existing args... */
consentOrPay: nil,
mandatoryLabel: "Mandatory",
specialFeaturesConsentOrPay: [ConsentOrPayRestriction(id: 1, value: .flexible)]
)
}
} |
CI Feedback 🧐A test triggered by this PR failed. Here is an AI-generated analysis of the failure:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/models/TCF2Settings.tsx (1)
223-235: ⚡ Quick winConsider using a union type for stricter type safety on the value field.
The
valuefield is currently typed asstring, but based on the PR objectives, it should only be'FLEXIBLE'or'MANDATORY'. Using a union type would provide compile-time safety and better autocomplete support.🔧 Proposed enhancement for type safety
+export type ConsentOrPayValue = 'FLEXIBLE' | 'MANDATORY' + export class ConsentOrPayRestriction { id: number - value: string + value: ConsentOrPayValue - constructor(id: number, value: string) { + constructor(id: number, value: ConsentOrPayValue) { this.id = id this.value = value } isFlexible(): boolean { - return this.value?.toUpperCase() === 'FLEXIBLE' + return this.value === 'FLEXIBLE' } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/models/TCF2Settings.tsx` around lines 223 - 235, Change the value field and constructor parameter on class ConsentOrPayRestriction from type string to a string literal union 'FLEXIBLE' | 'MANDATORY' (i.e., value: 'FLEXIBLE' | 'MANDATORY'), update the constructor signature accordingly, and simplify isFlexible() to return this.value === 'FLEXIBLE' (remove the optional chaining and toUpperCase). Also search for and update any call sites that construct ConsentOrPayRestriction to pass one of the two allowed literals to match the new type.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/models/TCF2Settings.tsx`:
- Around line 223-235: Change the value field and constructor parameter on class
ConsentOrPayRestriction from type string to a string literal union 'FLEXIBLE' |
'MANDATORY' (i.e., value: 'FLEXIBLE' | 'MANDATORY'), update the constructor
signature accordingly, and simplify isFlexible() to return this.value ===
'FLEXIBLE' (remove the optional chaining and toUpperCase). Also search for and
update any call sites that construct ConsentOrPayRestriction to pass one of the
two allowed literals to match the new type.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 69fe2d36-1aea-488b-9770-fdcae7838051
📒 Files selected for processing (3)
android/src/main/java/com/usercentrics/reactnative/extensions/UsercentricsCMPDataExtensions.ktios/Extensions/UsercentricsCMPData+Dict.swiftsrc/models/TCF2Settings.tsx
Summary
Adds support for the new PL-5714 JSON schema fields across all three bridge layers.
New class:
ConsentOrPayRestriction—{id: number, value: 'FLEXIBLE'|'MANDATORY'}withisFlexible()helperUpdated:
TCF2ChangedPurposes— newconsentOrPay?: ConsentOrPayRestriction[]fieldUpdated:
TCF2Settings— newspecialFeaturesConsentOrPay?: ConsentOrPayRestriction[]fieldBoth Android and iOS bridges serialize the new fields. Old
publisherRestrictionsandspecialFeaturesmaps are kept for backwards compatibility.Part of the PUR model fix tracked in MSDK-4088. Native SDK changes are in mobile-sdk PR #2401.
Test plan
ConsentOrPayRestrictionexported correctly, new fields present onTCF2SettingsandTCF2ChangedPurposesUsercentricsCMPDataExtensionsserializesspecialFeaturesConsentOrPayandchangedPurposes.consentOrPayUsercentricsCMPData+Dictserializes both new fields🤖 Generated with Claude Code
Summary by CodeRabbit
New Features