@cookieinformation/react-native-sdk 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/android/build.gradle +67 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/mobileconsentssdk/CookieInformationRNSDKModule.kt +455 -0
- package/android/src/main/java/expo/modules/mobileconsentssdk/UiParsing.kt +84 -0
- package/build/CookieInformationRNSDKModule.d.ts +106 -0
- package/build/CookieInformationRNSDKModule.d.ts.map +1 -0
- package/build/CookieInformationRNSDKModule.js +14 -0
- package/build/CookieInformationRNSDKModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +6 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +17 -0
- package/ios/CookieInformationRNSDK.podspec +30 -0
- package/ios/CookieInformationRNSDKModule.swift +235 -0
- package/ios/UiParsing.swift +58 -0
- package/package.json +73 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
package expo.modules.mobileconsentssdk
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import androidx.activity.ComponentActivity
|
|
5
|
+
import com.cookieinformation.mobileconsents.sdk.ui.ConsentsUISDK
|
|
6
|
+
import androidx.compose.material3.ColorScheme
|
|
7
|
+
import androidx.compose.material3.Typography
|
|
8
|
+
import com.cookieinformation.mobileconsents.core.ConsentSDK
|
|
9
|
+
import com.cookieinformation.mobileconsents.core.domain.entities.ConsentItemOption
|
|
10
|
+
|
|
11
|
+
import expo.modules.kotlin.Promise
|
|
12
|
+
import expo.modules.kotlin.exception.CodedException
|
|
13
|
+
import expo.modules.kotlin.modules.Module
|
|
14
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
15
|
+
import kotlinx.coroutines.CoroutineScope
|
|
16
|
+
import kotlinx.coroutines.Dispatchers
|
|
17
|
+
import kotlinx.coroutines.launch
|
|
18
|
+
import kotlinx.coroutines.withContext
|
|
19
|
+
|
|
20
|
+
class CookieInformationRNSDKModule : Module() {
|
|
21
|
+
private val tag: String = "CookieInformationSDK"
|
|
22
|
+
private var initError: String? = "Not initialized (call initialize)"
|
|
23
|
+
private var sdkReady: Boolean = false
|
|
24
|
+
|
|
25
|
+
private var cookieInformationSDK: ConsentSDK? = null
|
|
26
|
+
private var sdkConfig: SDKConfig? = null
|
|
27
|
+
|
|
28
|
+
private data class SDKConfig(
|
|
29
|
+
val clientID: String,
|
|
30
|
+
val clientSecret: String,
|
|
31
|
+
val solutionID: String,
|
|
32
|
+
val languageCode: String?,
|
|
33
|
+
val lightColorScheme: ColorScheme?,
|
|
34
|
+
val darkColorScheme: ColorScheme?,
|
|
35
|
+
val typography: Typography?
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
private class ActivityUnavailableException : CodedException("Activity unavailable")
|
|
39
|
+
private class SDKInitException(message: String) : CodedException(message)
|
|
40
|
+
private class ConsentException(message: String) : CodedException(message)
|
|
41
|
+
|
|
42
|
+
private fun withSDK(promise: Promise, action: suspend (ComponentActivity) -> Unit) {
|
|
43
|
+
val activity = appContext.activityProvider?.currentActivity as? ComponentActivity
|
|
44
|
+
if (activity == null) {
|
|
45
|
+
Log.w(tag, "No active Activity")
|
|
46
|
+
promise.reject(ActivityUnavailableException())
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!sdkReady || initError != null) {
|
|
51
|
+
Log.w(tag, "SDK not initialized: $initError")
|
|
52
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized: $initError"))
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
57
|
+
action(activity)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private suspend fun initializeConsentsCoreSDK(activity: ComponentActivity, config: SDKConfig) {
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
val localeLanguage = config.languageCode ?: try {
|
|
65
|
+
ConsentsUISDK.languageCode
|
|
66
|
+
} catch (e: Exception) {
|
|
67
|
+
activity.resources.configuration.locales.get(0).language.uppercase()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
cookieInformationSDK = ConsentSDK(
|
|
71
|
+
context = activity,
|
|
72
|
+
clientID = config.clientID,
|
|
73
|
+
clientSecret = config.clientSecret,
|
|
74
|
+
solutionId = config.solutionID,
|
|
75
|
+
language = localeLanguage
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
cookieInformationSDK?.init()?.onSuccess {
|
|
79
|
+
cookieInformationSDK?.cacheLatestConsentSolution()
|
|
80
|
+
cookieInformationSDK?.getLatestSavedUserConsents()?.onSuccess { consent ->
|
|
81
|
+
Log.i(tag, "Latest saved consents: $consent")
|
|
82
|
+
}
|
|
83
|
+
Log.i(tag, "Core SDK initialized")
|
|
84
|
+
}?.onFailure { error ->
|
|
85
|
+
Log.e(tag, "Core SDK init failure", error)
|
|
86
|
+
initError = "Core SDK init failed: ${error.message}"
|
|
87
|
+
}
|
|
88
|
+
} catch (e: Exception) {
|
|
89
|
+
Log.e(tag, "Core SDK setup error", e)
|
|
90
|
+
initError = "Core SDK setup failed: ${e.message}"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private fun initializeConsentsUISDK(activity: ComponentActivity, config: SDKConfig) {
|
|
95
|
+
try {
|
|
96
|
+
val localeLanguage = config.languageCode ?: try {
|
|
97
|
+
ConsentsUISDK.languageCode
|
|
98
|
+
} catch (e: Exception) {
|
|
99
|
+
activity.resources.configuration.locales.get(0).language.uppercase()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ConsentsUISDK.init(
|
|
103
|
+
clientID = config.clientID,
|
|
104
|
+
clientSecret = config.clientSecret,
|
|
105
|
+
solutionId = config.solutionID,
|
|
106
|
+
languageCode = localeLanguage,
|
|
107
|
+
customLightColorScheme = config.lightColorScheme,
|
|
108
|
+
customDarkColorScheme = config.darkColorScheme,
|
|
109
|
+
typography = config.typography,
|
|
110
|
+
context = activity
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
sdkReady = true
|
|
114
|
+
initError = null
|
|
115
|
+
Log.i(tag, "UI SDK initialized")
|
|
116
|
+
} catch (e: Exception) {
|
|
117
|
+
initError = e.message ?: "Unexpected error during SDK initialization"
|
|
118
|
+
sdkReady = false
|
|
119
|
+
Log.e(tag, "UI SDK init error", e)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
override fun definition() = ModuleDefinition {
|
|
124
|
+
Name("CookieInformationRNSDK")
|
|
125
|
+
|
|
126
|
+
OnCreate {
|
|
127
|
+
sdkReady = false
|
|
128
|
+
initError = "Not initialized (call initialize)"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
AsyncFunction("initialize") { options: Map<String, Any?>, promise: Promise ->
|
|
132
|
+
val activity = appContext.activityProvider?.currentActivity as? ComponentActivity
|
|
133
|
+
if (activity == null) {
|
|
134
|
+
promise.reject(ActivityUnavailableException())
|
|
135
|
+
return@AsyncFunction
|
|
136
|
+
}
|
|
137
|
+
val clientId = options["clientId"] as? String
|
|
138
|
+
val clientSecret = options["clientSecret"] as? String
|
|
139
|
+
val solutionId = options["solutionId"] as? String
|
|
140
|
+
if (clientId.isNullOrEmpty() || clientSecret.isNullOrEmpty() || solutionId.isNullOrEmpty()) {
|
|
141
|
+
promise.reject(SDKInitException("clientId, clientSecret, and solutionId are required"))
|
|
142
|
+
return@AsyncFunction
|
|
143
|
+
}
|
|
144
|
+
val languageCode = (options["languageCode"] as? String)?.trim()?.uppercase()
|
|
145
|
+
val ui = options["ui"] as? Map<*, *>
|
|
146
|
+
val androidUi = ui?.get("android") as? Map<*, *>
|
|
147
|
+
val lightColorScheme = UiParsing.buildColorScheme(androidUi?.get("lightColorScheme") as? Map<*, *>, isDark = false)
|
|
148
|
+
val darkColorScheme = UiParsing.buildColorScheme(androidUi?.get("darkColorScheme") as? Map<*, *>, isDark = true)
|
|
149
|
+
val typography = UiParsing.buildTypography(androidUi?.get("typography") as? Map<*, *>, activity)
|
|
150
|
+
sdkConfig = SDKConfig(
|
|
151
|
+
clientID = clientId,
|
|
152
|
+
clientSecret = clientSecret,
|
|
153
|
+
solutionID = solutionId,
|
|
154
|
+
languageCode = languageCode,
|
|
155
|
+
lightColorScheme = lightColorScheme,
|
|
156
|
+
darkColorScheme = darkColorScheme,
|
|
157
|
+
typography = typography
|
|
158
|
+
)
|
|
159
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
160
|
+
val config = sdkConfig
|
|
161
|
+
if (config == null) {
|
|
162
|
+
promise.reject(SDKInitException("Required configuration missing"))
|
|
163
|
+
return@launch
|
|
164
|
+
}
|
|
165
|
+
initializeConsentsUISDK(activity, config)
|
|
166
|
+
initializeConsentsCoreSDK(activity, config)
|
|
167
|
+
sdkReady = true
|
|
168
|
+
initError = null
|
|
169
|
+
promise.resolve(null)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
AsyncFunction("showPrivacyPopUp") { promise: Promise ->
|
|
174
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
175
|
+
try {
|
|
176
|
+
withSDK(promise) { activity ->
|
|
177
|
+
ConsentsUISDK.showPrivacyPopup(activity).collect { result ->
|
|
178
|
+
result.fold(
|
|
179
|
+
onSuccess = { consentItems ->
|
|
180
|
+
val consentMap = mutableMapOf<String, Boolean>()
|
|
181
|
+
consentItems.forEach { userConsent ->
|
|
182
|
+
consentMap[userConsent.title] = userConsent.accepted
|
|
183
|
+
}
|
|
184
|
+
promise.resolve(consentMap)
|
|
185
|
+
},
|
|
186
|
+
onFailure = { throwable ->
|
|
187
|
+
val errorMsg =
|
|
188
|
+
throwable.message ?: "Consent dialog could not be shown"
|
|
189
|
+
Log.w(tag, errorMsg, throwable)
|
|
190
|
+
promise.reject(ConsentException(errorMsg))
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch (e: Exception) {
|
|
196
|
+
val errorMsg = e.message ?: "Unexpected issue while opening consent dialog"
|
|
197
|
+
Log.w(tag, errorMsg, e)
|
|
198
|
+
promise.reject(ConsentException(errorMsg))
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
AsyncFunction("showPrivacyPopUpIfNeeded") { options: Map<String, Any?>?, promise: Promise ->
|
|
204
|
+
val userId = options?.get("userId") as? String
|
|
205
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
206
|
+
try {
|
|
207
|
+
withSDK(promise) { activity ->
|
|
208
|
+
ConsentsUISDK.showPrivacyPopupIfNeeded(activity, userId).collect { result ->
|
|
209
|
+
result.fold(
|
|
210
|
+
onSuccess = { consentItems ->
|
|
211
|
+
val consentMap = mutableMapOf<String, Boolean>()
|
|
212
|
+
consentItems.forEach { userConsent ->
|
|
213
|
+
consentMap[userConsent.title] = userConsent.accepted
|
|
214
|
+
}
|
|
215
|
+
promise.resolve(consentMap)
|
|
216
|
+
},
|
|
217
|
+
onFailure = { throwable ->
|
|
218
|
+
val errorMsg =
|
|
219
|
+
throwable.message ?: "Consent dialog could not be shown"
|
|
220
|
+
Log.w(tag, errorMsg, throwable)
|
|
221
|
+
promise.reject(ConsentException(errorMsg))
|
|
222
|
+
}
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (e: Exception) {
|
|
227
|
+
val errorMsg = e.message ?: "Unexpected issue while opening consent dialog"
|
|
228
|
+
Log.w(tag, errorMsg, e)
|
|
229
|
+
promise.reject(ConsentException(errorMsg))
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
AsyncFunction("acceptAllConsents") { userId: String?, promise: Promise ->
|
|
235
|
+
withSDK(promise) {
|
|
236
|
+
val sdk = cookieInformationSDK
|
|
237
|
+
if (sdk == null) {
|
|
238
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized"))
|
|
239
|
+
return@withSDK
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
243
|
+
try {
|
|
244
|
+
sdk.deleteUserData(userId).getOrThrow()
|
|
245
|
+
sdk.init().getOrThrow()
|
|
246
|
+
sdk.cacheLatestConsentSolution().getOrThrow()
|
|
247
|
+
|
|
248
|
+
val items = sdk.getLatestSavedUserConsents(userId).getOrElse { emptyList() }
|
|
249
|
+
if (items.isEmpty()) {
|
|
250
|
+
throw IllegalStateException("No consent items found. Make sure to cache the consent solution first.")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
val options = items.map { consentItem ->
|
|
254
|
+
ConsentItemOption(
|
|
255
|
+
consentItemId = consentItem.id,
|
|
256
|
+
accepted = true
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
val savedConsents = sdk.saveConsents(userId, options).getOrThrow()
|
|
260
|
+
|
|
261
|
+
val consentsList = savedConsents.map { consent ->
|
|
262
|
+
mapOf(
|
|
263
|
+
"id" to consent.id,
|
|
264
|
+
"universalId" to consent.universalId,
|
|
265
|
+
"title" to consent.title,
|
|
266
|
+
"description" to consent.description,
|
|
267
|
+
"required" to consent.required,
|
|
268
|
+
"type" to consent.type.type,
|
|
269
|
+
"accepted" to consent.accepted
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
withContext(Dispatchers.Main) {
|
|
274
|
+
promise.resolve(
|
|
275
|
+
mapOf(
|
|
276
|
+
"success" to true,
|
|
277
|
+
"message" to "All consents saved successfully",
|
|
278
|
+
"consents" to consentsList,
|
|
279
|
+
"count" to savedConsents.size
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
} catch (e: Exception) {
|
|
284
|
+
withContext(Dispatchers.Main) {
|
|
285
|
+
promise.reject(
|
|
286
|
+
ConsentException(
|
|
287
|
+
e.message ?: "Unable to save consent selections"
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
AsyncFunction("removeStoredConsents") { userId: String?, promise: Promise ->
|
|
297
|
+
withSDK(promise) { activity ->
|
|
298
|
+
ConsentsUISDK.deleteLocalConsentsData(activity, userId).fold(
|
|
299
|
+
onSuccess = { promise.resolve(null) },
|
|
300
|
+
onFailure = { e ->
|
|
301
|
+
val msg = e.message ?: "Failed to remove stored consents"
|
|
302
|
+
Log.w(tag, msg, e)
|
|
303
|
+
promise.reject(ConsentException(msg))
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
AsyncFunction("cacheConsentSolution") { promise: Promise ->
|
|
310
|
+
withSDK(promise) {
|
|
311
|
+
val sdk = cookieInformationSDK
|
|
312
|
+
if (sdk == null) {
|
|
313
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized"))
|
|
314
|
+
return@withSDK
|
|
315
|
+
}
|
|
316
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
317
|
+
try {
|
|
318
|
+
sdk.cacheLatestConsentSolution().fold(
|
|
319
|
+
onSuccess = {
|
|
320
|
+
val items = sdk.getLatestSavedUserConsents(null).getOrElse { emptyList() }
|
|
321
|
+
val consentsList = items.map { consent ->
|
|
322
|
+
mapOf(
|
|
323
|
+
"id" to consent.id,
|
|
324
|
+
"universalId" to consent.universalId,
|
|
325
|
+
"title" to consent.title,
|
|
326
|
+
"description" to consent.description,
|
|
327
|
+
"required" to consent.required,
|
|
328
|
+
"type" to consent.type.type,
|
|
329
|
+
"accepted" to consent.accepted
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
withContext(Dispatchers.Main) {
|
|
333
|
+
promise.resolve(mapOf("consentItems" to consentsList))
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
onFailure = { e ->
|
|
337
|
+
val msg = e.message ?: "Failed to cache consent solution"
|
|
338
|
+
Log.w(tag, msg, e)
|
|
339
|
+
withContext(Dispatchers.Main) {
|
|
340
|
+
promise.reject(ConsentException(msg))
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
} catch (e: Exception) {
|
|
345
|
+
val msg = e.message ?: "Failed to cache consent solution"
|
|
346
|
+
Log.w(tag, msg, e)
|
|
347
|
+
withContext(Dispatchers.Main) {
|
|
348
|
+
promise.reject(ConsentException(msg))
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
AsyncFunction("synchronizeIfNeeded") { promise: Promise ->
|
|
356
|
+
withSDK(promise) {
|
|
357
|
+
val sdk = cookieInformationSDK
|
|
358
|
+
if (sdk == null) {
|
|
359
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized"))
|
|
360
|
+
return@withSDK
|
|
361
|
+
}
|
|
362
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
363
|
+
try {
|
|
364
|
+
sdk.resendAllFailedSaveConsentsRequests()
|
|
365
|
+
withContext(Dispatchers.Main) {
|
|
366
|
+
promise.resolve(null)
|
|
367
|
+
}
|
|
368
|
+
} catch (e: Exception) {
|
|
369
|
+
val msg = e.message ?: "Failed to sync consents"
|
|
370
|
+
Log.w(tag, msg, e)
|
|
371
|
+
withContext(Dispatchers.Main) {
|
|
372
|
+
promise.reject(ConsentException(msg))
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
AsyncFunction("getSavedConsents") { userId: String?, promise: Promise ->
|
|
380
|
+
withSDK(promise) {
|
|
381
|
+
val sdk = cookieInformationSDK
|
|
382
|
+
if (sdk == null) {
|
|
383
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized"))
|
|
384
|
+
return@withSDK
|
|
385
|
+
}
|
|
386
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
387
|
+
try {
|
|
388
|
+
val items = sdk.getLatestSavedUserConsents(userId).getOrElse { emptyList() }
|
|
389
|
+
val consentsList = items.map { consent ->
|
|
390
|
+
mapOf(
|
|
391
|
+
"id" to consent.id,
|
|
392
|
+
"universalId" to consent.universalId,
|
|
393
|
+
"title" to consent.title,
|
|
394
|
+
"description" to consent.description,
|
|
395
|
+
"required" to consent.required,
|
|
396
|
+
"type" to consent.type.type,
|
|
397
|
+
"accepted" to consent.accepted
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
withContext(Dispatchers.Main) {
|
|
401
|
+
promise.resolve(
|
|
402
|
+
mapOf("consentItems" to consentsList)
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
} catch (e: Exception) {
|
|
406
|
+
val msg = e.message ?: "Failed to read saved consents"
|
|
407
|
+
Log.w(tag, msg, e)
|
|
408
|
+
withContext(Dispatchers.Main) {
|
|
409
|
+
promise.reject(ConsentException(msg))
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
AsyncFunction("saveConsents") { consentItemsRaw: List<Map<String, Any?>>, customData: Map<String, Any?>?, userId: String?, _: String?, promise: Promise ->
|
|
417
|
+
withSDK(promise) {
|
|
418
|
+
val sdk = cookieInformationSDK
|
|
419
|
+
if (sdk == null) {
|
|
420
|
+
promise.reject(SDKInitException("CookieInformationSDK not initialized"))
|
|
421
|
+
return@withSDK
|
|
422
|
+
}
|
|
423
|
+
val options = consentItemsRaw.mapNotNull { map ->
|
|
424
|
+
val id = (map["id"] as? Number)?.toLong() ?: return@mapNotNull null
|
|
425
|
+
val accepted = map["accepted"] as? Boolean ?: return@mapNotNull null
|
|
426
|
+
ConsentItemOption(consentItemId = id, accepted = accepted)
|
|
427
|
+
}
|
|
428
|
+
if (options.isEmpty()) {
|
|
429
|
+
promise.reject(ConsentException("At least one consent item is required"))
|
|
430
|
+
return@withSDK
|
|
431
|
+
}
|
|
432
|
+
CoroutineScope(Dispatchers.IO).launch {
|
|
433
|
+
try {
|
|
434
|
+
sdk.saveConsents(userId, options).getOrThrow()
|
|
435
|
+
withContext(Dispatchers.Main) {
|
|
436
|
+
promise.resolve(
|
|
437
|
+
mapOf(
|
|
438
|
+
"success" to true,
|
|
439
|
+
"savedCount" to options.size
|
|
440
|
+
)
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
} catch (e: Exception) {
|
|
444
|
+
val msg = e.message ?: "Failed to save consents"
|
|
445
|
+
Log.w(tag, msg, e)
|
|
446
|
+
withContext(Dispatchers.Main) {
|
|
447
|
+
promise.reject(ConsentException(msg))
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package expo.modules.mobileconsentssdk
|
|
2
|
+
|
|
3
|
+
import androidx.activity.ComponentActivity
|
|
4
|
+
import androidx.compose.material3.ColorScheme
|
|
5
|
+
import androidx.compose.material3.Typography
|
|
6
|
+
import androidx.compose.material3.darkColorScheme
|
|
7
|
+
import androidx.compose.material3.lightColorScheme
|
|
8
|
+
import androidx.compose.ui.graphics.Color
|
|
9
|
+
import androidx.compose.ui.text.TextStyle
|
|
10
|
+
import androidx.compose.ui.text.font.Font
|
|
11
|
+
import androidx.compose.ui.text.font.FontFamily
|
|
12
|
+
import androidx.compose.ui.unit.sp
|
|
13
|
+
|
|
14
|
+
object UiParsing {
|
|
15
|
+
fun parseColorCode(value: Any?): Int? {
|
|
16
|
+
return when (value) {
|
|
17
|
+
is Number -> value.toInt()
|
|
18
|
+
is String -> parseHexColorString(value)
|
|
19
|
+
else -> null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun parseHexColorString(value: String): Int? {
|
|
24
|
+
val hex = value.trim().removePrefix("#")
|
|
25
|
+
val normalized = when (hex.length) {
|
|
26
|
+
6 -> "FF$hex"
|
|
27
|
+
8 -> hex
|
|
28
|
+
else -> return null
|
|
29
|
+
}
|
|
30
|
+
return try {
|
|
31
|
+
normalized.toLong(16).toInt()
|
|
32
|
+
} catch (_: Exception) {
|
|
33
|
+
null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fun buildColorScheme(map: Map<*, *>?, isDark: Boolean): ColorScheme? {
|
|
38
|
+
if (map == null) return null
|
|
39
|
+
val primary = parseColorCode(map["primary"])?.let { Color(it) }
|
|
40
|
+
val secondary = parseColorCode(map["secondary"])?.let { Color(it) }
|
|
41
|
+
val tertiary = parseColorCode(map["tertiary"])?.let { Color(it) }
|
|
42
|
+
if (primary == null && secondary == null && tertiary == null) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
return if (isDark) {
|
|
46
|
+
val defaults = darkColorScheme()
|
|
47
|
+
darkColorScheme(
|
|
48
|
+
primary = primary ?: defaults.primary,
|
|
49
|
+
secondary = secondary ?: defaults.secondary,
|
|
50
|
+
tertiary = tertiary ?: defaults.tertiary
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
val defaults = lightColorScheme()
|
|
54
|
+
lightColorScheme(
|
|
55
|
+
primary = primary ?: defaults.primary,
|
|
56
|
+
secondary = secondary ?: defaults.secondary,
|
|
57
|
+
tertiary = tertiary ?: defaults.tertiary
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fun buildTypography(map: Map<*, *>?, activity: ComponentActivity): Typography? {
|
|
63
|
+
if (map == null) return null
|
|
64
|
+
val bodyMedium = parseTextStyle(map["bodyMedium"] as? Map<*, *>, activity)
|
|
65
|
+
if (bodyMedium == null) return null
|
|
66
|
+
return Typography().copy(bodyMedium = bodyMedium)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private fun parseTextStyle(map: Map<*, *>?, activity: ComponentActivity): TextStyle? {
|
|
70
|
+
if (map == null) return null
|
|
71
|
+
val size = (map["size"] as? Number)?.toInt()
|
|
72
|
+
val fontName = map["font"] as? String
|
|
73
|
+
val fontFamily = fontName?.let {
|
|
74
|
+
val resId = activity.resources.getIdentifier(it, "font", activity.packageName)
|
|
75
|
+
if (resId == 0) null else FontFamily(Font(resId))
|
|
76
|
+
}
|
|
77
|
+
return when {
|
|
78
|
+
fontFamily != null && size != null -> TextStyle(fontFamily = fontFamily, fontSize = size.sp)
|
|
79
|
+
fontFamily != null -> TextStyle(fontFamily = fontFamily)
|
|
80
|
+
size != null -> TextStyle(fontSize = size.sp)
|
|
81
|
+
else -> null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent choices keyed by purpose/category.
|
|
3
|
+
* iOS uses ConsentItemType raw values: "necessary", "marketing", "statistical", "functional", "custom".
|
|
4
|
+
* Android uses consent item titles (localized). Custom purposes and other keys may appear.
|
|
5
|
+
*/
|
|
6
|
+
interface TrackingConsents {
|
|
7
|
+
necessary?: boolean;
|
|
8
|
+
marketing?: boolean;
|
|
9
|
+
statistical?: boolean;
|
|
10
|
+
functional?: boolean;
|
|
11
|
+
custom?: boolean;
|
|
12
|
+
[key: string]: boolean | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Consent item shape (id, universalId, title, description, required, type, accepted). Returned by getSavedConsents/cacheConsentSolution; pass to saveConsents. */
|
|
15
|
+
export interface ConsentItem {
|
|
16
|
+
id: number;
|
|
17
|
+
universalId: string;
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
required: boolean;
|
|
21
|
+
type: string;
|
|
22
|
+
accepted: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface AcceptAllConsentsResponse {
|
|
25
|
+
success: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
consents: ConsentItem[];
|
|
28
|
+
count: number;
|
|
29
|
+
}
|
|
30
|
+
export interface InitializeOptions {
|
|
31
|
+
clientId: string;
|
|
32
|
+
clientSecret: string;
|
|
33
|
+
solutionId: string;
|
|
34
|
+
/** The SDK uses languageCode if provided; otherwise it uses the device locale. */
|
|
35
|
+
languageCode?: string | null;
|
|
36
|
+
/** iOS only. Ignored on Android. */
|
|
37
|
+
enableNetworkLogger?: boolean | null;
|
|
38
|
+
ui?: UiOptions | null;
|
|
39
|
+
}
|
|
40
|
+
export interface UiOptions {
|
|
41
|
+
ios?: IosUiOptions | null;
|
|
42
|
+
android?: AndroidUiOptions | null;
|
|
43
|
+
}
|
|
44
|
+
export interface IosUiOptions {
|
|
45
|
+
/** Hex color like "#RRGGBB" or "#AARRGGBB". */
|
|
46
|
+
accentColor?: string | null;
|
|
47
|
+
fontSet?: FontSet | null;
|
|
48
|
+
}
|
|
49
|
+
export interface FontSet {
|
|
50
|
+
largeTitle?: FontSpec | null;
|
|
51
|
+
body?: FontSpec | null;
|
|
52
|
+
bold?: FontSpec | null;
|
|
53
|
+
}
|
|
54
|
+
export interface FontSpec {
|
|
55
|
+
/** iOS font name. If omitted, system font is used. */
|
|
56
|
+
name?: string | null;
|
|
57
|
+
size?: number | null;
|
|
58
|
+
weight?: 'regular' | 'medium' | 'semibold' | 'bold' | null;
|
|
59
|
+
}
|
|
60
|
+
export interface AndroidUiOptions {
|
|
61
|
+
lightColorScheme?: ColorScheme | null;
|
|
62
|
+
darkColorScheme?: ColorScheme | null;
|
|
63
|
+
typography?: Typography | null;
|
|
64
|
+
}
|
|
65
|
+
export interface ColorScheme {
|
|
66
|
+
primary?: string | null;
|
|
67
|
+
secondary?: string | null;
|
|
68
|
+
tertiary?: string | null;
|
|
69
|
+
}
|
|
70
|
+
export interface Typography {
|
|
71
|
+
bodyMedium?: TextStyle | null;
|
|
72
|
+
}
|
|
73
|
+
export interface TextStyle {
|
|
74
|
+
/** Android font resource name in res/font (e.g. "inter_bold"). */
|
|
75
|
+
font?: string | null;
|
|
76
|
+
size?: number | null;
|
|
77
|
+
}
|
|
78
|
+
interface CacheConsentSolutionResponse {
|
|
79
|
+
consentItems: ConsentItem[];
|
|
80
|
+
/** Consent solution version ID (iOS only). */
|
|
81
|
+
consentSolutionVersionId?: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Confirmation returned when consent was successfully saved to the server.
|
|
85
|
+
*/
|
|
86
|
+
export interface SaveConsentsResponse {
|
|
87
|
+
success: true;
|
|
88
|
+
/** Number of consent items that were saved. */
|
|
89
|
+
savedCount: number;
|
|
90
|
+
}
|
|
91
|
+
declare const _default: {
|
|
92
|
+
initialize: (options: InitializeOptions) => Promise<void>;
|
|
93
|
+
showPrivacyPopUp: () => Promise<TrackingConsents>;
|
|
94
|
+
showPrivacyPopUpIfNeeded: (options?: {
|
|
95
|
+
ignoreVersionChanges?: boolean;
|
|
96
|
+
userId?: string | null;
|
|
97
|
+
}) => Promise<TrackingConsents>;
|
|
98
|
+
acceptAllConsents: (userId?: string) => Promise<AcceptAllConsentsResponse>;
|
|
99
|
+
removeStoredConsents: (userId?: string | null) => Promise<void>;
|
|
100
|
+
cacheConsentSolution: () => Promise<CacheConsentSolutionResponse>;
|
|
101
|
+
synchronizeIfNeeded: () => Promise<void>;
|
|
102
|
+
getSavedConsents: (userId?: string | null) => Promise<CacheConsentSolutionResponse>;
|
|
103
|
+
saveConsents: (consentItems: ConsentItem[], customData?: Record<string, string> | null, userId?: string | null, consentSolutionVersionId?: string | null) => Promise<SaveConsentsResponse>;
|
|
104
|
+
};
|
|
105
|
+
export default _default;
|
|
106
|
+
//# sourceMappingURL=CookieInformationRNSDKModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CookieInformationRNSDKModule.d.ts","sourceRoot":"","sources":["../src/CookieInformationRNSDKModule.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,UAAU,gBAAgB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CACpC;AAED,mKAAmK;AACnK,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,yBAAyB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oCAAoC;IACpC,mBAAmB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACrC,EAAE,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACrC,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,UAAU,4BAA4B;IACpC,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,IAAI,CAAC;IACd,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;CACpB;;0BA0EuB,iBAAiB;4BA7DnB,OAAO,CAAC,gBAAgB,CAAC;yCAgEjC;QAAE,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;iCAvD3C,MAAM,KAAG,OAAO,CAAC,yBAAyB,CAAC;oCA0DtC,MAAM,GAAG,IAAI;gCA5CrB,OAAO,CAAC,4BAA4B,CAAC;+BAItC,OAAO,CAAC,IAAI,CAAC;gCA4CR,MAAM,GAAG,IAAI;iCAGzB,WAAW,EAAE,eACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,WACjC,MAAM,GAAG,IAAI,6BACK,MAAM,GAAG,IAAI;;AAjB5C,wBAyBE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { requireNativeModule } from 'expo';
|
|
2
|
+
const native = requireNativeModule('CookieInformationRNSDK');
|
|
3
|
+
export default {
|
|
4
|
+
initialize: (options) => native.initialize(options),
|
|
5
|
+
showPrivacyPopUp: native.showPrivacyPopUp.bind(native),
|
|
6
|
+
showPrivacyPopUpIfNeeded: (options) => native.showPrivacyPopUpIfNeeded(options ?? {}),
|
|
7
|
+
acceptAllConsents: native.acceptAllConsents.bind(native),
|
|
8
|
+
removeStoredConsents: (userId) => native.removeStoredConsents(userId ?? null),
|
|
9
|
+
cacheConsentSolution: native.cacheConsentSolution.bind(native),
|
|
10
|
+
synchronizeIfNeeded: native.synchronizeIfNeeded.bind(native),
|
|
11
|
+
getSavedConsents: (userId) => native.getSavedConsents(userId ?? null),
|
|
12
|
+
saveConsents: (consentItems, customData, userId, consentSolutionVersionId) => native.saveConsents(consentItems, customData ?? null, userId ?? null, consentSolutionVersionId ?? null),
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=CookieInformationRNSDKModule.js.map
|