react-native-carrot-quest 0.1.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.
Files changed (121) hide show
  1. package/CarrotQuest.podspec +34 -0
  2. package/LICENSE +20 -0
  3. package/README.md +457 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +136 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +11 -0
  8. package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuest.kt +585 -0
  9. package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestPackage.kt +22 -0
  10. package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestSetup.kt +472 -0
  11. package/ios/CarrotQuest.swift +588 -0
  12. package/ios/CarrotQuestSetup.swift +333 -0
  13. package/lib/module/CarrotQuest.nitro.js +4 -0
  14. package/lib/module/CarrotQuest.nitro.js.map +1 -0
  15. package/lib/module/carrot-quest.js +136 -0
  16. package/lib/module/carrot-quest.js.map +1 -0
  17. package/lib/module/carrot-quest.native.js +155 -0
  18. package/lib/module/carrot-quest.native.js.map +1 -0
  19. package/lib/module/index.js +6 -0
  20. package/lib/module/index.js.map +1 -0
  21. package/lib/module/package.json +1 -0
  22. package/lib/module/subscriptions.js +76 -0
  23. package/lib/module/subscriptions.js.map +1 -0
  24. package/lib/typescript/package.json +1 -0
  25. package/lib/typescript/src/CarrotQuest.nitro.d.ts +394 -0
  26. package/lib/typescript/src/CarrotQuest.nitro.d.ts.map +1 -0
  27. package/lib/typescript/src/carrot-quest.d.ts +37 -0
  28. package/lib/typescript/src/carrot-quest.d.ts.map +1 -0
  29. package/lib/typescript/src/carrot-quest.native.d.ts +37 -0
  30. package/lib/typescript/src/carrot-quest.native.d.ts.map +1 -0
  31. package/lib/typescript/src/index.d.ts +6 -0
  32. package/lib/typescript/src/index.d.ts.map +1 -0
  33. package/lib/typescript/src/subscriptions.d.ts +32 -0
  34. package/lib/typescript/src/subscriptions.d.ts.map +1 -0
  35. package/nitro.json +23 -0
  36. package/nitrogen/generated/android/c++/JCarrotQuestEventValue.cpp +30 -0
  37. package/nitrogen/generated/android/c++/JCarrotQuestEventValue.hpp +83 -0
  38. package/nitrogen/generated/android/c++/JCarrotQuestLogCategory.hpp +76 -0
  39. package/nitrogen/generated/android/c++/JCarrotQuestLogEntry.hpp +100 -0
  40. package/nitrogen/generated/android/c++/JCarrotQuestLogLevel.hpp +70 -0
  41. package/nitrogen/generated/android/c++/JCarrotQuestProperty.hpp +74 -0
  42. package/nitrogen/generated/android/c++/JCarrotQuestPropertyKind.hpp +61 -0
  43. package/nitrogen/generated/android/c++/JCarrotQuestPropertyOperation.hpp +73 -0
  44. package/nitrogen/generated/android/c++/JCarrotQuestSetupOptions.hpp +94 -0
  45. package/nitrogen/generated/android/c++/JCarrotQuestTheme.hpp +64 -0
  46. package/nitrogen/generated/android/c++/JCarrotQuestUrlSource.hpp +64 -0
  47. package/nitrogen/generated/android/c++/JCarrotQuestUserProperties.hpp +84 -0
  48. package/nitrogen/generated/android/c++/JFunc_void_CarrotQuestLogEntry.hpp +84 -0
  49. package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
  50. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  51. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  52. package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.cpp +388 -0
  53. package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.hpp +96 -0
  54. package/nitrogen/generated/android/carrotquest+autolinking.cmake +82 -0
  55. package/nitrogen/generated/android/carrotquest+autolinking.gradle +27 -0
  56. package/nitrogen/generated/android/carrotquestOnLoad.cpp +62 -0
  57. package/nitrogen/generated/android/carrotquestOnLoad.hpp +34 -0
  58. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestEventValue.kt +75 -0
  59. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogCategory.kt +29 -0
  60. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogEntry.kt +81 -0
  61. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogLevel.kt +27 -0
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestProperty.kt +66 -0
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyKind.kt +24 -0
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyOperation.kt +28 -0
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestSetupOptions.kt +91 -0
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestTheme.kt +25 -0
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUrlSource.kt +25 -0
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUserProperties.kt +66 -0
  69. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_CarrotQuestLogEntry.kt +80 -0
  70. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_bool.kt +80 -0
  71. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_double.kt +80 -0
  72. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_std__string.kt +80 -0
  73. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/HybridCarrotQuestSpec.kt +209 -0
  74. package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/carrotquestOnLoad.kt +35 -0
  75. package/nitrogen/generated/ios/CarrotQuest+autolinking.rb +62 -0
  76. package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.cpp +97 -0
  77. package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.hpp +637 -0
  78. package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Umbrella.hpp +80 -0
  79. package/nitrogen/generated/ios/CarrotQuestAutolinking.mm +33 -0
  80. package/nitrogen/generated/ios/CarrotQuestAutolinking.swift +26 -0
  81. package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.cpp +11 -0
  82. package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.hpp +344 -0
  83. package/nitrogen/generated/ios/swift/CarrotQuestEventValue.swift +32 -0
  84. package/nitrogen/generated/ios/swift/CarrotQuestLogCategory.swift +64 -0
  85. package/nitrogen/generated/ios/swift/CarrotQuestLogEntry.swift +86 -0
  86. package/nitrogen/generated/ios/swift/CarrotQuestLogLevel.swift +56 -0
  87. package/nitrogen/generated/ios/swift/CarrotQuestProperty.swift +56 -0
  88. package/nitrogen/generated/ios/swift/CarrotQuestPropertyKind.swift +44 -0
  89. package/nitrogen/generated/ios/swift/CarrotQuestPropertyOperation.swift +60 -0
  90. package/nitrogen/generated/ios/swift/CarrotQuestSetupOptions.swift +172 -0
  91. package/nitrogen/generated/ios/swift/CarrotQuestTheme.swift +48 -0
  92. package/nitrogen/generated/ios/swift/CarrotQuestUrlSource.swift +48 -0
  93. package/nitrogen/generated/ios/swift/CarrotQuestUserProperties.swift +110 -0
  94. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  95. package/nitrogen/generated/ios/swift/Func_void_CarrotQuestLogEntry.swift +46 -0
  96. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  97. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  98. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  99. package/nitrogen/generated/ios/swift/Func_void_std__optional_double_.swift +53 -0
  100. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
  101. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  102. package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec.swift +88 -0
  103. package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec_cxx.swift +674 -0
  104. package/nitrogen/generated/shared/c++/CarrotQuestLogCategory.hpp +100 -0
  105. package/nitrogen/generated/shared/c++/CarrotQuestLogEntry.hpp +114 -0
  106. package/nitrogen/generated/shared/c++/CarrotQuestLogLevel.hpp +92 -0
  107. package/nitrogen/generated/shared/c++/CarrotQuestProperty.hpp +101 -0
  108. package/nitrogen/generated/shared/c++/CarrotQuestPropertyKind.hpp +80 -0
  109. package/nitrogen/generated/shared/c++/CarrotQuestPropertyOperation.hpp +96 -0
  110. package/nitrogen/generated/shared/c++/CarrotQuestSetupOptions.hpp +121 -0
  111. package/nitrogen/generated/shared/c++/CarrotQuestTheme.hpp +84 -0
  112. package/nitrogen/generated/shared/c++/CarrotQuestUrlSource.hpp +84 -0
  113. package/nitrogen/generated/shared/c++/CarrotQuestUserProperties.hpp +97 -0
  114. package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.cpp +55 -0
  115. package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.hpp +118 -0
  116. package/package.json +140 -0
  117. package/src/CarrotQuest.nitro.ts +493 -0
  118. package/src/carrot-quest.native.tsx +241 -0
  119. package/src/carrot-quest.tsx +207 -0
  120. package/src/index.tsx +57 -0
  121. package/src/subscriptions.ts +83 -0
@@ -0,0 +1,585 @@
1
+ package com.margelo.nitro.carrotquest
2
+
3
+ import com.facebook.proguard.annotations.DoNotStrip
4
+ import com.margelo.nitro.NitroModules
5
+ import com.margelo.nitro.core.Promise
6
+ import io.carrotquest_sdk.android.Callback
7
+ import io.carrotquest_sdk.android.Carrot
8
+ import io.carrotquest_sdk.android.core.logging.SdkLogCategory
9
+ import io.carrotquest_sdk.android.core.logging.SdkLogLevel
10
+ import io.carrotquest_sdk.android.core.logging.SdkLogSink
11
+ import io.carrotquest_sdk.android.core.main.ThemeSdk
12
+ import io.carrotquest_sdk.android.models.CarrotUserProperty
13
+ import io.carrotquest_sdk.android.models.EcommerceUserProperty
14
+ import io.carrotquest_sdk.android.models.EventParams
15
+ import io.carrotquest_sdk.android.models.Operation
16
+ import io.carrotquest_sdk.android.models.UserProperty
17
+ import org.json.JSONObject
18
+
19
+ /**
20
+ * Kotlin implementation of the `CarrotQuest` hybrid object.
21
+ *
22
+ * Targets Carrot Android SDK 3.x. The 2.x line is not supported: it exposes a
23
+ * callback-free `setup`, a `setDebug` toggle and JSON-string event params, all
24
+ * of which 3.x removed.
25
+ */
26
+ @DoNotStrip
27
+ class CarrotQuest : HybridCarrotQuestSpec() {
28
+
29
+ // MARK: - Lifecycle
30
+
31
+ override val isConfigured: Boolean
32
+ get() = CarrotQuestSetup.isConfigured
33
+
34
+ override fun setup(apiKey: String, options: CarrotQuestSetupOptions?): Promise<Unit> {
35
+ val promise = Promise<Unit>()
36
+
37
+ val context = NitroModules.applicationContext
38
+
39
+ if (context == null) {
40
+ promise.reject(IllegalStateException("No React application context available."))
41
+ return promise
42
+ }
43
+
44
+ CarrotQuestSetup.configure(
45
+ context = context,
46
+ apiKey = apiKey,
47
+ locale = options?.locale,
48
+ theme = options?.theme?.let(::nativeTheme),
49
+ useEuServer = options?.useEuServer,
50
+ parentActivityClassName = options?.parentActivityClassName,
51
+ notificationIconResourceName = options?.notificationIconResourceName,
52
+ logLevel = options?.logLevel?.let(::nativeLogLevel),
53
+ logIncludeSensitive = options?.logIncludeSensitive,
54
+ ) { error ->
55
+ if (error == null) promise.resolve(Unit) else promise.reject(error)
56
+ }
57
+
58
+ return promise
59
+ }
60
+
61
+ override fun getSdkVersion(): String = Carrot.getVersion()
62
+
63
+ /** Keep setup readiness inside the native package, never in consuming apps. */
64
+ private fun <T> whenConfigured(promise: Promise<T>, operation: () -> Unit) {
65
+ CarrotQuestSetup.whenConfigured ready@ { error ->
66
+ if (error != null) {
67
+ promise.reject(error)
68
+ return@ready
69
+ }
70
+
71
+ try {
72
+ operation()
73
+ } catch (operationError: Throwable) {
74
+ promise.reject(operationError)
75
+ }
76
+ }
77
+ }
78
+
79
+ // MARK: - Authentication
80
+
81
+ override fun auth(userId: String, userAuthKey: String): Promise<String?> {
82
+ return authenticate { callback -> Carrot.auth(userId, userAuthKey, callback) }
83
+ }
84
+
85
+ override fun hashedAuth(userId: String, hash: String): Promise<String?> {
86
+ return authenticate { callback -> Carrot.hashedAuth(userId, hash, callback) }
87
+ }
88
+
89
+ /** Shared plumbing for the two auth flows. */
90
+ private fun authenticate(call: (Callback<String>) -> Unit): Promise<String?> {
91
+ val promise = Promise<String?>()
92
+
93
+ whenConfigured(promise) {
94
+ call(
95
+ object : Callback<String> {
96
+ override fun onResponse(result: String?) {
97
+ promise.resolve(result)
98
+ }
99
+
100
+ override fun onFailure(t: Throwable) {
101
+ promise.reject(t)
102
+ }
103
+ },
104
+ )
105
+ }
106
+
107
+ return promise
108
+ }
109
+
110
+ override fun logout(): Promise<Unit> {
111
+ val promise = Promise<Unit>()
112
+
113
+ whenConfigured(promise) {
114
+ Carrot.deInit(
115
+ object : Callback<Boolean> {
116
+ override fun onResponse(result: Boolean?) {
117
+ // The SDK signals success with `true`. Reinitialising on anything
118
+ // else would leave the previous user authenticated while logout()
119
+ // resolved.
120
+ if (result != true) {
121
+ promise.reject(CarrotQuestLogoutRejectedException())
122
+ return
123
+ }
124
+
125
+ // deInit tears the SDK down completely, so it has to be set up again
126
+ // before the next auth call can succeed. Only report success once
127
+ // that re-initialisation has actually settled.
128
+ CarrotQuestSetup.reinitialiseAfterLogout { error ->
129
+ if (error == null) promise.resolve(Unit) else promise.reject(error)
130
+ }
131
+ }
132
+
133
+ override fun onFailure(t: Throwable) {
134
+ promise.reject(t)
135
+ }
136
+ },
137
+ )
138
+ }
139
+
140
+ return promise
141
+ }
142
+
143
+ // MARK: - Chat UI
144
+
145
+ override fun openChat(): Promise<Unit> {
146
+ val promise = Promise<Unit>()
147
+
148
+ whenConfigured(promise) operation@ {
149
+ val activity = NitroModules.applicationContext?.currentActivity
150
+
151
+ if (activity == null) {
152
+ promise.reject(CarrotQuestNoActivityException())
153
+ return@operation
154
+ }
155
+
156
+ activity.runOnUiThread {
157
+ try {
158
+ Carrot.openChat(activity)
159
+ promise.resolve(Unit)
160
+ } catch (error: Throwable) {
161
+ promise.reject(error)
162
+ }
163
+ }
164
+ }
165
+
166
+ return promise
167
+ }
168
+
169
+ override fun closeChat(): Promise<Unit> {
170
+ // iOS-only: the Android SDK exposes no programmatic close, only the
171
+ // close callback wired up in onChatVisibilityChanged.
172
+ return Promise.resolved(Unit)
173
+ }
174
+
175
+ override val isChatOpen: Boolean
176
+ get() = false // iOS-only; the Android SDK exposes no visibility getter.
177
+
178
+ override fun onChatVisibilityChanged(listener: (visible: Boolean) -> Unit) {
179
+ CarrotQuestSetup.runWhenConfigured("onChatVisibilityChanged") {
180
+ // Android only signals close, never open — documented on the spec.
181
+ Carrot.setCloseChatCallback(
182
+ object : Callback<Boolean> {
183
+ override fun onResponse(result: Boolean?) {
184
+ listener(false)
185
+ }
186
+
187
+ override fun onFailure(t: Throwable) = Unit
188
+ },
189
+ )
190
+ }
191
+ }
192
+
193
+ override fun setTheme(theme: CarrotQuestTheme) {
194
+ CarrotQuestSetup.runWhenConfigured("setTheme") {
195
+ Carrot.setTheme(nativeTheme(theme))
196
+ }
197
+ }
198
+
199
+ private fun nativeTheme(theme: CarrotQuestTheme): ThemeSdk = when (theme) {
200
+ CarrotQuestTheme.LIGHT -> ThemeSdk.LIGHT
201
+ CarrotQuestTheme.DARK -> ThemeSdk.DARK
202
+ CarrotQuestTheme.FROMDEVICE -> ThemeSdk.FROM_DEVICE
203
+ CarrotQuestTheme.FROMWEB -> ThemeSdk.FROM_WEB
204
+ }
205
+
206
+ private fun nativeLogLevel(level: CarrotQuestLogLevel): SdkLogLevel = when (level) {
207
+ CarrotQuestLogLevel.NONE -> SdkLogLevel.NONE
208
+ CarrotQuestLogLevel.ERROR -> SdkLogLevel.ERROR
209
+ CarrotQuestLogLevel.WARN -> SdkLogLevel.WARN
210
+ CarrotQuestLogLevel.INFO -> SdkLogLevel.INFO
211
+ CarrotQuestLogLevel.DEBUGGING -> SdkLogLevel.DEBUG
212
+ CarrotQuestLogLevel.VERBOSE -> SdkLogLevel.VERBOSE
213
+ }
214
+
215
+ // MARK: - Unread state
216
+
217
+ override fun getUnreadConversationsCount(): Promise<Double> {
218
+ val promise = Promise<Double>()
219
+
220
+ whenConfigured(promise) {
221
+ promise.resolve(Carrot.getUnreadConversations().size.toDouble())
222
+ }
223
+
224
+ return promise
225
+ }
226
+
227
+ override fun getUnreadMessagesCount(): Promise<Double?> {
228
+ // iOS-only: Android exposes unread conversations but not a message count.
229
+ return Promise.resolved(null)
230
+ }
231
+
232
+ override fun onUnreadConversationsChanged(listener: (count: Double) -> Unit) {
233
+ CarrotQuestSetup.runWhenConfigured("onUnreadConversationsChanged") {
234
+ // The SDK declares Callback<List<String>> — a list of conversation ids.
235
+ Carrot.setUnreadConversationsCallback(
236
+ object : Callback<List<String>> {
237
+ override fun onResponse(result: List<String>?) {
238
+ listener((result?.size ?: 0).toDouble())
239
+ }
240
+
241
+ override fun onFailure(t: Throwable) = Unit
242
+ },
243
+ )
244
+ }
245
+ }
246
+
247
+ // MARK: - User properties
248
+
249
+ override fun setUserProperties(properties: CarrotQuestUserProperties) {
250
+ val userProperties = makeUserProperties(properties)
251
+
252
+ if (userProperties.isEmpty()) return
253
+
254
+ CarrotQuestSetup.runWhenConfigured("setUserProperties") {
255
+ Carrot.setUserProperty(userProperties)
256
+ }
257
+ }
258
+
259
+ override fun setProperties(properties: Array<CarrotQuestProperty>) {
260
+ val userProperties = properties.mapNotNull(::makeProperty)
261
+
262
+ if (userProperties.isEmpty()) return
263
+
264
+ CarrotQuestSetup.runWhenConfigured("setProperties") {
265
+ Carrot.setUserProperty(userProperties)
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Map the ergonomic struct onto Carrot's property types.
271
+ *
272
+ * Empty values are dropped rather than written as blank strings, and a
273
+ * `custom` key that collides with a system property is ignored so the typed
274
+ * field always wins.
275
+ */
276
+ private fun makeUserProperties(
277
+ properties: CarrotQuestUserProperties,
278
+ ): List<UserProperty> {
279
+ val result = mutableListOf<UserProperty>()
280
+
281
+ val systemProperties = listOf(
282
+ Triple("name", CarrotUserProperty.Property.NAME, properties.name),
283
+ Triple("phone", CarrotUserProperty.Property.PHONE, properties.phone),
284
+ Triple("email", CarrotUserProperty.Property.EMAIL, properties.email),
285
+ )
286
+
287
+ val reservedKeys = systemProperties.map { it.first }.toSet()
288
+
289
+ for ((_, key, value) in systemProperties) {
290
+ if (value.isNullOrEmpty()) continue
291
+
292
+ result.add(CarrotUserProperty(key, value))
293
+ }
294
+
295
+ properties.custom?.forEach { (key, value) ->
296
+ if (key.isEmpty() || value.isEmpty()) return@forEach
297
+ if (key in reservedKeys) return@forEach
298
+
299
+ result.add(UserProperty(key, value))
300
+ }
301
+
302
+ return result
303
+ }
304
+
305
+ private fun makeProperty(property: CarrotQuestProperty): UserProperty? {
306
+ if (property.key.isEmpty() || property.value.isEmpty()) return null
307
+
308
+ val operation = nativeOperation(property.operation ?: CarrotQuestPropertyOperation.UPDATEORCREATE)
309
+
310
+ return when (property.kind ?: CarrotQuestPropertyKind.CUSTOM) {
311
+ CarrotQuestPropertyKind.SYSTEM -> systemKey(property.key)
312
+ ?.let { CarrotUserProperty(operation, it, property.value) }
313
+ // Unknown system key — fall back to a free-form property rather than
314
+ // silently dropping the value.
315
+ ?: UserProperty(operation, property.key, property.value)
316
+
317
+ CarrotQuestPropertyKind.ECOMMERCE -> ecommerceKey(property.key)
318
+ ?.let { EcommerceUserProperty(operation, it, property.value) }
319
+ ?: UserProperty(operation, property.key, property.value)
320
+
321
+ CarrotQuestPropertyKind.CUSTOM -> UserProperty(operation, property.key, property.value)
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Map JS key names onto the SDK's enums explicitly rather than by name
327
+ * lookup, so a rename in either direction is a compile error here.
328
+ *
329
+ * Android's [CarrotUserProperty.Property] only covers name/phone/email; the
330
+ * push and UTM keys iOS exposes as typed cases are plain custom keys here.
331
+ */
332
+ private fun systemKey(key: String): CarrotUserProperty.Property? = when (key) {
333
+ "name" -> CarrotUserProperty.Property.NAME
334
+ "phone" -> CarrotUserProperty.Property.PHONE
335
+ "email" -> CarrotUserProperty.Property.EMAIL
336
+ else -> null
337
+ }
338
+
339
+ private fun ecommerceKey(key: String): EcommerceUserProperty.Property? = when (key) {
340
+ "cartAmount" -> EcommerceUserProperty.Property.CART_AMOUNT
341
+ "viewedProducts" -> EcommerceUserProperty.Property.VIEWED_PRODUCTS
342
+ "cartItems" -> EcommerceUserProperty.Property.CART_ITEMS
343
+ "lastOrderStatus" -> EcommerceUserProperty.Property.LAST_ORDER_STATUS
344
+ "lastPayment" -> EcommerceUserProperty.Property.LAST_PAYMENT
345
+ "revenue" -> EcommerceUserProperty.Property.REVENUE
346
+ "profit" -> EcommerceUserProperty.Property.PROFIT
347
+ "group" -> EcommerceUserProperty.Property.GROUP
348
+ "discount" -> EcommerceUserProperty.Property.DISCOUNT
349
+ "ordersCount" -> EcommerceUserProperty.Property.ORDERS_COUNT
350
+ "orderedItems" -> EcommerceUserProperty.Property.ORDERED_ITEMS
351
+ "orderedCategories" -> EcommerceUserProperty.Property.ORDERED_CATEGORIES
352
+ "viewedCategories" -> EcommerceUserProperty.Property.VIEWED_CATEGORIES
353
+ else -> null
354
+ }
355
+
356
+ private fun nativeOperation(operation: CarrotQuestPropertyOperation): Operation =
357
+ when (operation) {
358
+ CarrotQuestPropertyOperation.UPDATEORCREATE -> Operation.UPDATE_OR_CREATE
359
+ CarrotQuestPropertyOperation.SETONCE -> Operation.SET_ONCE
360
+ CarrotQuestPropertyOperation.ADD -> Operation.ADD
361
+ CarrotQuestPropertyOperation.DELETE -> Operation.DELETE
362
+ CarrotQuestPropertyOperation.APPEND -> Operation.APPEND
363
+ CarrotQuestPropertyOperation.UNION -> Operation.UNION
364
+ CarrotQuestPropertyOperation.EXCLUDE -> Operation.EXCLUDE
365
+ }
366
+
367
+ // MARK: - Tracking
368
+
369
+ override fun trackEvent(name: String, params: Map<String, CarrotQuestEventValue>?) {
370
+ if (name.isEmpty()) return
371
+
372
+ CarrotQuestSetup.runWhenConfigured("trackEvent") {
373
+ if (params.isNullOrEmpty()) {
374
+ Carrot.trackEvent(name)
375
+ } else {
376
+ // SDK 3.x replaced the JSON-string overload with a typed EventParams,
377
+ // so values keep their JSON type instead of being stringified.
378
+ val builder = EventParams.builder()
379
+
380
+ params.forEach { (key, value) ->
381
+ when (value) {
382
+ is CarrotQuestEventValue.First -> builder.put(key, value.value)
383
+ is CarrotQuestEventValue.Second -> builder.put(key, value.value)
384
+ is CarrotQuestEventValue.Third -> putNumber(builder, key, value.value)
385
+ }
386
+ }
387
+
388
+ Carrot.trackEvent(name, builder.build())
389
+ }
390
+ }
391
+ }
392
+
393
+ /**
394
+ * JS has a single number type, so every numeric arrives as a Double. Send
395
+ * whole numbers as Long to avoid `1` turning into `1.0` on the wire.
396
+ */
397
+ private fun putNumber(builder: EventParams.Builder, key: String, value: Double) {
398
+ val isWhole = value.isFinite() && value == Math.floor(value) &&
399
+ value >= Long.MIN_VALUE.toDouble() && value <= Long.MAX_VALUE.toDouble()
400
+
401
+ if (isWhole) {
402
+ builder.put(key, value.toLong())
403
+ } else {
404
+ builder.put(key, value)
405
+ }
406
+ }
407
+
408
+ override fun trackScreen(name: String) {
409
+ if (name.isEmpty()) return
410
+
411
+ CarrotQuestSetup.runWhenConfigured("trackScreen") {
412
+ Carrot.trackScreen(name)
413
+ }
414
+ }
415
+
416
+ override fun trackUtm(url: String) {
417
+ if (url.isEmpty()) return
418
+
419
+ Carrot.trackUtm(url)
420
+ }
421
+
422
+ // MARK: - Push notifications
423
+
424
+ override fun setPushToken(token: String) {
425
+ if (token.isEmpty()) return
426
+
427
+ CarrotQuestSetup.runWhenConfigured("setPushToken") {
428
+ Carrot.sendPushToken(token)
429
+ }
430
+ }
431
+
432
+ override fun deletePushToken() {
433
+ // iOS-only: the Android SDK exposes no token deletion.
434
+ }
435
+
436
+ override fun pushNotificationsUnsubscribe(): Promise<Unit> {
437
+ return runCatchingPromise { Carrot.pushNotificationsUnsubscribe() }
438
+ }
439
+
440
+ override fun pushCampaignsUnsubscribe(): Promise<Unit> {
441
+ return runCatchingPromise { Carrot.pushCampaignsUnsubscribe() }
442
+ }
443
+
444
+ override fun isCarrotPush(payloadJson: String): Boolean {
445
+ val payload = decodePayload(payloadJson) ?: return false
446
+
447
+ return runCatching { Carrot.isCarrotPush(payload) }.getOrDefault(false)
448
+ }
449
+
450
+ override fun isAutoMessage(payloadJson: String): Boolean {
451
+ val payload = decodePayload(payloadJson) ?: return false
452
+
453
+ return runCatching { Carrot.isAutoMessage(payload) }.getOrDefault(false)
454
+ }
455
+
456
+ override fun handlePushNotification(payloadJson: String) {
457
+ val payload = decodePayload(payloadJson) ?: throw CarrotQuestInvalidPayloadException()
458
+ val context = NitroModules.applicationContext ?: return
459
+
460
+ CarrotQuestSetup.runWhenConfigured("handlePushNotification") {
461
+ Carrot.sendPushNotification(payload, context)
462
+ }
463
+ }
464
+
465
+ override fun handlePushClick(payloadJson: String, openLink: Boolean) {
466
+ // iOS-only: on Android the tap is routed through the notification's own
467
+ // pending intent, which the SDK builds itself.
468
+ }
469
+
470
+ override fun getPushLink(payloadJson: String): String? {
471
+ // SDK 3.x removed getPushActionUrl and offers no replacement accessor.
472
+ return null
473
+ }
474
+
475
+ override fun wasPushShownEarlier(payloadJson: String): Promise<Boolean> {
476
+ // iOS-only: duplicate suppression relies on the App Group store, which has
477
+ // no Android equivalent.
478
+ return Promise.resolved(false)
479
+ }
480
+
481
+ /**
482
+ * Decode a JSON payload into the string map the SDK's push helpers expect.
483
+ *
484
+ * Nested values are re-encoded rather than flattened, matching how FCM
485
+ * delivers a data payload.
486
+ */
487
+ private fun decodePayload(payloadJson: String): Map<String, String>? {
488
+ if (payloadJson.isEmpty()) return null
489
+
490
+ return runCatching {
491
+ val json = JSONObject(payloadJson)
492
+ val result = mutableMapOf<String, String>()
493
+
494
+ json.keys().forEach { key ->
495
+ val value = json.get(key)
496
+ result[key] = if (value is JSONObject || value is org.json.JSONArray) {
497
+ value.toString()
498
+ } else {
499
+ value.toString()
500
+ }
501
+ }
502
+
503
+ result
504
+ }.getOrNull()
505
+ }
506
+
507
+ private fun runCatchingPromise(block: () -> Unit): Promise<Unit> {
508
+ val promise = Promise<Unit>()
509
+
510
+ whenConfigured(promise) {
511
+ block()
512
+ promise.resolve(Unit)
513
+ }
514
+
515
+ return promise
516
+ }
517
+
518
+ // MARK: - Deep links
519
+
520
+ override fun onUrlOpen(source: CarrotQuestUrlSource, listener: (url: String) -> Unit) {
521
+ // iOS-only: the Android SDK routes links through the parent activity and
522
+ // exposes no interception hook.
523
+ }
524
+
525
+ override fun openBrowserLink(url: String) {
526
+ // iOS-only.
527
+ }
528
+
529
+ override fun openUniversalLink(url: String) {
530
+ // iOS-only.
531
+ }
532
+
533
+ // MARK: - Diagnostics
534
+
535
+ override fun getDiagnostics(): String? {
536
+ return runCatching { Carrot.getDiagnostics().toFormattedString() }.getOrNull()
537
+ }
538
+
539
+ override fun onLog(listener: (entry: CarrotQuestLogEntry) -> Unit) {
540
+ Carrot.setLogSink(
541
+ SdkLogSink { entry ->
542
+ listener(
543
+ CarrotQuestLogEntry(
544
+ timestampMs = entry.timestampMs.toDouble(),
545
+ level = jsLogLevel(entry.level),
546
+ category = jsLogCategory(entry.category),
547
+ tag = entry.tag,
548
+ message = entry.message,
549
+ // Already Map<String, String>; orEmpty() only guards the platform
550
+ // type, which Kotlin cannot prove non-null.
551
+ fields = entry.fields.orEmpty(),
552
+ error = entry.throwable?.toString(),
553
+ ),
554
+ )
555
+ },
556
+ )
557
+ }
558
+
559
+ override fun offLog() {
560
+ // setLogSink takes a non-null sink, so detaching means installing an inert
561
+ // one. The SDK keeps logging internally; what stops is the bridge traffic.
562
+ Carrot.setLogSink(SdkLogSink { })
563
+ }
564
+
565
+ private fun jsLogLevel(level: SdkLogLevel): CarrotQuestLogLevel = when (level) {
566
+ SdkLogLevel.NONE -> CarrotQuestLogLevel.NONE
567
+ SdkLogLevel.ERROR -> CarrotQuestLogLevel.ERROR
568
+ SdkLogLevel.WARN -> CarrotQuestLogLevel.WARN
569
+ SdkLogLevel.INFO -> CarrotQuestLogLevel.INFO
570
+ SdkLogLevel.DEBUG -> CarrotQuestLogLevel.DEBUGGING
571
+ SdkLogLevel.VERBOSE -> CarrotQuestLogLevel.VERBOSE
572
+ }
573
+
574
+ private fun jsLogCategory(category: SdkLogCategory): CarrotQuestLogCategory =
575
+ when (category) {
576
+ SdkLogCategory.GENERAL -> CarrotQuestLogCategory.GENERAL
577
+ SdkLogCategory.NETWORK -> CarrotQuestLogCategory.NETWORK
578
+ SdkLogCategory.CONNECTIVITY -> CarrotQuestLogCategory.CONNECTIVITY
579
+ SdkLogCategory.AUTH -> CarrotQuestLogCategory.AUTH
580
+ SdkLogCategory.REALTIME -> CarrotQuestLogCategory.REALTIME
581
+ SdkLogCategory.LIFECYCLE -> CarrotQuestLogCategory.LIFECYCLE
582
+ SdkLogCategory.DATABASE -> CarrotQuestLogCategory.DATABASE
583
+ SdkLogCategory.PUSH -> CarrotQuestLogCategory.PUSH
584
+ }
585
+ }
@@ -0,0 +1,22 @@
1
+ package com.margelo.nitro.carrotquest
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+
8
+ class CarrotQuestPackage : BaseReactPackage() {
9
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
10
+ return null
11
+ }
12
+
13
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
14
+ return ReactModuleInfoProvider { HashMap() }
15
+ }
16
+
17
+ companion object {
18
+ init {
19
+ System.loadLibrary("carrotquest")
20
+ }
21
+ }
22
+ }