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.
- package/CarrotQuest.podspec +34 -0
- package/LICENSE +20 -0
- package/README.md +457 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +136 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +11 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuest.kt +585 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestPackage.kt +22 -0
- package/android/src/main/java/com/margelo/nitro/carrotquest/CarrotQuestSetup.kt +472 -0
- package/ios/CarrotQuest.swift +588 -0
- package/ios/CarrotQuestSetup.swift +333 -0
- package/lib/module/CarrotQuest.nitro.js +4 -0
- package/lib/module/CarrotQuest.nitro.js.map +1 -0
- package/lib/module/carrot-quest.js +136 -0
- package/lib/module/carrot-quest.js.map +1 -0
- package/lib/module/carrot-quest.native.js +155 -0
- package/lib/module/carrot-quest.native.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/subscriptions.js +76 -0
- package/lib/module/subscriptions.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/CarrotQuest.nitro.d.ts +394 -0
- package/lib/typescript/src/CarrotQuest.nitro.d.ts.map +1 -0
- package/lib/typescript/src/carrot-quest.d.ts +37 -0
- package/lib/typescript/src/carrot-quest.d.ts.map +1 -0
- package/lib/typescript/src/carrot-quest.native.d.ts +37 -0
- package/lib/typescript/src/carrot-quest.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/subscriptions.d.ts +32 -0
- package/lib/typescript/src/subscriptions.d.ts.map +1 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/android/c++/JCarrotQuestEventValue.cpp +30 -0
- package/nitrogen/generated/android/c++/JCarrotQuestEventValue.hpp +83 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogCategory.hpp +76 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogEntry.hpp +100 -0
- package/nitrogen/generated/android/c++/JCarrotQuestLogLevel.hpp +70 -0
- package/nitrogen/generated/android/c++/JCarrotQuestProperty.hpp +74 -0
- package/nitrogen/generated/android/c++/JCarrotQuestPropertyKind.hpp +61 -0
- package/nitrogen/generated/android/c++/JCarrotQuestPropertyOperation.hpp +73 -0
- package/nitrogen/generated/android/c++/JCarrotQuestSetupOptions.hpp +94 -0
- package/nitrogen/generated/android/c++/JCarrotQuestTheme.hpp +64 -0
- package/nitrogen/generated/android/c++/JCarrotQuestUrlSource.hpp +64 -0
- package/nitrogen/generated/android/c++/JCarrotQuestUserProperties.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_CarrotQuestLogEntry.hpp +84 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.cpp +388 -0
- package/nitrogen/generated/android/c++/JHybridCarrotQuestSpec.hpp +96 -0
- package/nitrogen/generated/android/carrotquest+autolinking.cmake +82 -0
- package/nitrogen/generated/android/carrotquest+autolinking.gradle +27 -0
- package/nitrogen/generated/android/carrotquestOnLoad.cpp +62 -0
- package/nitrogen/generated/android/carrotquestOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestEventValue.kt +75 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogCategory.kt +29 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogEntry.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestLogLevel.kt +27 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestProperty.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyKind.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestPropertyOperation.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestSetupOptions.kt +91 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestTheme.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUrlSource.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/CarrotQuestUserProperties.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_CarrotQuestLogEntry.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/Func_void_std__string.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/HybridCarrotQuestSpec.kt +209 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/carrotquest/carrotquestOnLoad.kt +35 -0
- package/nitrogen/generated/ios/CarrotQuest+autolinking.rb +62 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.cpp +97 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Bridge.hpp +637 -0
- package/nitrogen/generated/ios/CarrotQuest-Swift-Cxx-Umbrella.hpp +80 -0
- package/nitrogen/generated/ios/CarrotQuestAutolinking.mm +33 -0
- package/nitrogen/generated/ios/CarrotQuestAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridCarrotQuestSpecSwift.hpp +344 -0
- package/nitrogen/generated/ios/swift/CarrotQuestEventValue.swift +32 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogCategory.swift +64 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogEntry.swift +86 -0
- package/nitrogen/generated/ios/swift/CarrotQuestLogLevel.swift +56 -0
- package/nitrogen/generated/ios/swift/CarrotQuestProperty.swift +56 -0
- package/nitrogen/generated/ios/swift/CarrotQuestPropertyKind.swift +44 -0
- package/nitrogen/generated/ios/swift/CarrotQuestPropertyOperation.swift +60 -0
- package/nitrogen/generated/ios/swift/CarrotQuestSetupOptions.swift +172 -0
- package/nitrogen/generated/ios/swift/CarrotQuestTheme.swift +48 -0
- package/nitrogen/generated/ios/swift/CarrotQuestUrlSource.swift +48 -0
- package/nitrogen/generated/ios/swift/CarrotQuestUserProperties.swift +110 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_CarrotQuestLogEntry.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_double_.swift +53 -0
- package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +53 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec.swift +88 -0
- package/nitrogen/generated/ios/swift/HybridCarrotQuestSpec_cxx.swift +674 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogCategory.hpp +100 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogEntry.hpp +114 -0
- package/nitrogen/generated/shared/c++/CarrotQuestLogLevel.hpp +92 -0
- package/nitrogen/generated/shared/c++/CarrotQuestProperty.hpp +101 -0
- package/nitrogen/generated/shared/c++/CarrotQuestPropertyKind.hpp +80 -0
- package/nitrogen/generated/shared/c++/CarrotQuestPropertyOperation.hpp +96 -0
- package/nitrogen/generated/shared/c++/CarrotQuestSetupOptions.hpp +121 -0
- package/nitrogen/generated/shared/c++/CarrotQuestTheme.hpp +84 -0
- package/nitrogen/generated/shared/c++/CarrotQuestUrlSource.hpp +84 -0
- package/nitrogen/generated/shared/c++/CarrotQuestUserProperties.hpp +97 -0
- package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.cpp +55 -0
- package/nitrogen/generated/shared/c++/HybridCarrotQuestSpec.hpp +118 -0
- package/package.json +140 -0
- package/src/CarrotQuest.nitro.ts +493 -0
- package/src/carrot-quest.native.tsx +241 -0
- package/src/carrot-quest.tsx +207 -0
- package/src/index.tsx +57 -0
- 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
|
+
}
|