diff --git a/android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt b/android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt index 2db01e8..f0668ae 100644 --- a/android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt +++ b/android/src/androidTest/java/com/usercentrics/reactnative/RNUsercentricsModuleTest.kt @@ -42,6 +42,7 @@ class RNUsercentricsModuleTest { putString("version", "1.2.3") putInt("networkMode", 1) putInt("initTimeoutMillis", 10000) + putString("controllerId", "a".repeat(64)) } private val bannerSettingsMap = mapOf( @@ -128,6 +129,7 @@ class RNUsercentricsModuleTest { assertEquals("1.2.3", usercentricsProxy.initializeOptionsArgument?.version) assertEquals(NetworkMode.EU, usercentricsProxy.initializeOptionsArgument?.networkMode) assertEquals(10000L, usercentricsProxy.initializeOptionsArgument?.initTimeoutMillis) + assertEquals("a".repeat(64), usercentricsProxy.initializeOptionsArgument?.controllerId) } @Test diff --git a/android/src/main/java/com/usercentrics/reactnative/extensions/UserOptionsExtensions.kt b/android/src/main/java/com/usercentrics/reactnative/extensions/UserOptionsExtensions.kt index f4508ac..4ee43e4 100644 --- a/android/src/main/java/com/usercentrics/reactnative/extensions/UserOptionsExtensions.kt +++ b/android/src/main/java/com/usercentrics/reactnative/extensions/UserOptionsExtensions.kt @@ -56,6 +56,10 @@ internal fun ReadableMap.usercentricsOptionsFromMap(): UsercentricsOptions { options.initTimeoutMillis = it.toLong() } + getString("controllerId")?.let { + options.controllerId = it + } + getMap("bannerCustomization")?.let { options.bannerCustomization = it.bannerInitCustomizationFromMap() } diff --git a/ios/Extensions/UsercentricsOptions+Dict.swift b/ios/Extensions/UsercentricsOptions+Dict.swift index 42788e2..3102ce2 100644 --- a/ios/Extensions/UsercentricsOptions+Dict.swift +++ b/ios/Extensions/UsercentricsOptions+Dict.swift @@ -41,6 +41,10 @@ public extension UsercentricsOptions { options.initTimeoutMillis = Int64(initTimeoutMillis) } + if let controllerId = dictionary["controllerId"] as? String { + options.controllerId = controllerId + } + if let bannerCustomizationDict = dictionary["bannerCustomization"] as? NSDictionary { options.bannerCustomization = BannerInitCustomization(from: bannerCustomizationDict) } diff --git a/sample/ios/sampleTests/UsercentricsOptionsDictTests.swift b/sample/ios/sampleTests/UsercentricsOptionsDictTests.swift index c5ee058..65f36d6 100644 --- a/sample/ios/sampleTests/UsercentricsOptionsDictTests.swift +++ b/sample/ios/sampleTests/UsercentricsOptionsDictTests.swift @@ -15,6 +15,7 @@ class UsercentricsOptionsDictTests: XCTestCase { "version": "1.2.3", "networkMode": 1, "initTimeoutMillis": 1500, + "controllerId": String(repeating: "a", count: 64), ] @@ -28,6 +29,7 @@ class UsercentricsOptionsDictTests: XCTestCase { XCTAssertEqual(1000, usercentricsOptionsFromDict.timeoutMillis) XCTAssertEqual(.eu, usercentricsOptionsFromDict.networkMode) XCTAssertEqual(1500, usercentricsOptionsFromDict.initTimeoutMillis) + XCTAssertEqual(String(repeating: "a", count: 64), usercentricsOptionsFromDict.controllerId) } func testInitializeWithoutSettingsIdShouldNotInitialize() { diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 1430de4..caba82e 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -89,6 +89,21 @@ describe('Test Usercentrics Module', () => { expect(call).toBe(options) }) + test('testConfigureBridgeWithControllerId', () => { + const controllerId = "a".repeat(64); + const options = new UsercentricsOptions({ + settingsId: "abc", + ruleSetId: "qwer", + controllerId + }); + + Usercentrics.configure(options); + const calls = RNUsercentricsModule.configure.mock.calls; + const call = calls[calls.length - 1][0]; + expect(call).toBe(options); + expect(call.controllerId).toBe(controllerId); + }) + test('testConfigureBridgeWithBannerCustomization', () => { const bannerCustomization = new BannerInitCustomization({ paddingTop: 16, diff --git a/src/models/UsercentricsOptions.tsx b/src/models/UsercentricsOptions.tsx index f88855c..276a0d3 100644 --- a/src/models/UsercentricsOptions.tsx +++ b/src/models/UsercentricsOptions.tsx @@ -10,6 +10,20 @@ export class UsercentricsOptions { networkMode?: NetworkMode; consentMediation?: Boolean; initTimeoutMillis?: number; + /** + * Optional controllerId to inject at SDK initialisation. + * + * Use this to preserve user identity across login/logout flows that clear local consent + * storage. Store the controllerId (via `Usercentrics.getControllerId()`) server-side after + * the first successful init, then pass it here on every subsequent login. The SDK will skip + * generating a new ID and use this value instead. It is persisted to local storage, so + * subsequent re-initialisations without this option continue to use it. + * + * The value must be a 64-character lowercase hexadecimal string (the format produced + * internally by the SDK). Invalid values are ignored with a warning log and the SDK falls + * back to its normal ID resolution (stored → generated). + */ + controllerId?: string; /** * @deprecated bannerCustomization is deprecated and will be removed in a future release. * Configure banner appearance via the Usercentrics dashboard instead. @@ -26,6 +40,7 @@ export class UsercentricsOptions { networkMode = undefined, consentMediation = undefined, initTimeoutMillis = undefined, + controllerId = undefined, bannerCustomization = undefined }: { settingsId?: string, @@ -37,6 +52,7 @@ export class UsercentricsOptions { networkMode?: NetworkMode, consentMediation?: Boolean, initTimeoutMillis?: number, + controllerId?: string, bannerCustomization?: BannerInitCustomization }) { this.settingsId = settingsId; @@ -48,6 +64,7 @@ export class UsercentricsOptions { this.networkMode = networkMode this.consentMediation = consentMediation this.initTimeoutMillis = initTimeoutMillis + this.controllerId = controllerId this.bannerCustomization = bannerCustomization } }