rn-linkrunner 1.1.1 → 2.0.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.
@@ -0,0 +1,398 @@
1
+ package io.linkrunner
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
8
+ import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableMap
10
+ import com.facebook.react.bridge.WritableMap
11
+ import io.linkrunner.sdk.BuildConfig
12
+ import io.linkrunner.sdk.LinkRunner
13
+ import io.linkrunner.sdk.models.request.UserDataRequest
14
+ import io.linkrunner.sdk.models.IntegrationData
15
+ import io.linkrunner.utils.MapUtils
16
+ import io.linkrunner.utils.ModelConverter
17
+ import kotlinx.coroutines.CoroutineScope
18
+ import kotlinx.coroutines.Dispatchers
19
+ import kotlinx.coroutines.SupervisorJob
20
+ import kotlinx.coroutines.launch
21
+ import kotlinx.coroutines.withContext
22
+
23
+ class LinkrunnerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
24
+
25
+ private val TAG = "LinkrunnerModule"
26
+ private val linkrunnerSDK = LinkRunner.getInstance()
27
+ private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
28
+
29
+ override fun getName(): String {
30
+ return "LinkrunnerSDK"
31
+ }
32
+
33
+ @ReactMethod
34
+ fun init(token: String, options: ReadableMap?, promise: Promise) {
35
+ try {
36
+ // Extract optional parameters
37
+ val secretKey = options?.getString("secretKey")
38
+ val keyId = options?.getString("keyId")
39
+
40
+ if (token.isEmpty()) {
41
+ promise.reject("INIT_ERROR", "Token is required")
42
+ return
43
+ }
44
+
45
+ moduleScope.launch {
46
+ try {
47
+ val result = linkrunnerSDK.init(
48
+ context = reactContext,
49
+ token = token,
50
+ secretKey = secretKey,
51
+ keyId = keyId
52
+ )
53
+
54
+ withContext(Dispatchers.Main) {
55
+ if (result.isSuccess) {
56
+ val response = Arguments.createMap()
57
+ response.putString("status", "success")
58
+ response.putString("message", "Linkrunner SDK initialized successfully")
59
+ promise.resolve(response)
60
+ } else {
61
+ promise.reject(
62
+ "INIT_ERROR",
63
+ "Failed to initialize Linkrunner: ${result.exceptionOrNull()?.message}",
64
+ result.exceptionOrNull()
65
+ )
66
+ }
67
+ }
68
+ } catch (e: Exception) {
69
+ withContext(Dispatchers.Main) {
70
+ promise.reject("INIT_ERROR", "Exception calling init: ${e.message}", e)
71
+ }
72
+ }
73
+ }
74
+ } catch (e: Exception) {
75
+ promise.reject("INIT_ERROR", "Failed to initialize Linkrunner: ${e.message}", e)
76
+ }
77
+ }
78
+
79
+ @ReactMethod
80
+ fun signup(userData: ReadableMap, data: ReadableMap?, promise: Promise) {
81
+ try {
82
+ // Convert ReadableMap to native Map for userData
83
+ val userDataMap = MapUtils.readableMapToMap(userData)
84
+
85
+ // Convert ReadableMap to native Map for additionalData
86
+ val additionalData = if (data != null && !data.toHashMap().isEmpty()) {
87
+ MapUtils.readableMapToMap(data)
88
+ } else {
89
+ null
90
+ }
91
+
92
+ moduleScope.launch {
93
+ try {
94
+ val result = linkrunnerSDK.signup(
95
+ userData = ModelConverter.toUserDataRequest(userDataMap),
96
+ additionalData = additionalData
97
+ )
98
+
99
+ withContext(Dispatchers.Main) {
100
+ if (result.isSuccess) {
101
+ val response = Arguments.createMap()
102
+ response.putString("status", "success")
103
+ response.putString("message", "User signed up successfully")
104
+ promise.resolve(response)
105
+ } else {
106
+ promise.reject(
107
+ "SIGNUP_ERROR",
108
+ "Failed to signup user: ${result.exceptionOrNull()?.message}",
109
+ result.exceptionOrNull()
110
+ )
111
+ }
112
+ }
113
+ } catch (e: Exception) {
114
+ withContext(Dispatchers.Main) {
115
+ promise.reject("SIGNUP_ERROR", "Exception during signup: ${e.message}", e)
116
+ }
117
+ }
118
+ }
119
+ } catch (e: Exception) {
120
+ promise.reject("SIGNUP_ERROR", "Failed to signup user: ${e.message}", e)
121
+ }
122
+ }
123
+
124
+ @ReactMethod
125
+ fun setUserData(userData: ReadableMap, promise: Promise) {
126
+ try {
127
+ // Convert ReadableMap to native Map for userData
128
+ val userDataMap = MapUtils.readableMapToMap(userData)
129
+
130
+ moduleScope.launch {
131
+ try {
132
+ val result = linkrunnerSDK.setUserData(ModelConverter.toUserDataRequest(userDataMap))
133
+
134
+ withContext(Dispatchers.Main) {
135
+ if (result.isSuccess) {
136
+ val response = Arguments.createMap()
137
+ response.putString("status", "success")
138
+ response.putString("message", "User data set successfully")
139
+ promise.resolve(response)
140
+ } else {
141
+ promise.reject(
142
+ "SET_USER_DATA_ERROR",
143
+ "Failed to set user data: ${result.exceptionOrNull()?.message}",
144
+ result.exceptionOrNull()
145
+ )
146
+ }
147
+ }
148
+ } catch (e: Exception) {
149
+ withContext(Dispatchers.Main) {
150
+ promise.reject("SET_USER_DATA_ERROR", "Exception setting user data: ${e.message}", e)
151
+ }
152
+ }
153
+ }
154
+ } catch (e: Exception) {
155
+ promise.reject("SET_USER_DATA_ERROR", "Failed to set user data: ${e.message}", e)
156
+ }
157
+ }
158
+
159
+ @ReactMethod
160
+ fun trackEvent(eventName: String, eventData: ReadableMap?, promise: Promise) {
161
+ if (eventName.isEmpty()) {
162
+ promise.reject("TRACK_EVENT_ERROR", "Event name is required")
163
+ return
164
+ }
165
+
166
+ try {
167
+ val eventDataMap = if (eventData != null) {
168
+ MapUtils.readableMapToMap(eventData)
169
+ } else {
170
+ emptyMap()
171
+ }
172
+
173
+ moduleScope.launch {
174
+ try {
175
+ val result = linkrunnerSDK.trackEvent(eventName, eventDataMap)
176
+
177
+ withContext(Dispatchers.Main) {
178
+ if (result.isSuccess) {
179
+ val response = Arguments.createMap()
180
+ response.putString("status", "success")
181
+ response.putString("message", "Event tracked successfully")
182
+ promise.resolve(response)
183
+ } else {
184
+ promise.reject(
185
+ "TRACK_EVENT_ERROR",
186
+ "Failed to track event: ${result.exceptionOrNull()?.message}",
187
+ result.exceptionOrNull()
188
+ )
189
+ }
190
+ }
191
+ } catch (e: Exception) {
192
+ withContext(Dispatchers.Main) {
193
+ promise.reject("TRACK_EVENT_ERROR", "Exception tracking event: ${e.message}", e)
194
+ }
195
+ }
196
+ }
197
+ } catch (e: Exception) {
198
+ promise.reject("TRACK_EVENT_ERROR", "Failed to track event: ${e.message}", e)
199
+ }
200
+ }
201
+
202
+ @ReactMethod
203
+ fun capturePayment(paymentData: ReadableMap, promise: Promise) {
204
+ if (paymentData == null) {
205
+ promise.reject("PAYMENT_ERROR", "Payment data is required")
206
+ return
207
+ }
208
+
209
+ try {
210
+ val paymentDataMap = MapUtils.readableMapToMap(paymentData)
211
+
212
+ moduleScope.launch {
213
+ try {
214
+ val result = linkrunnerSDK.capturePayment(ModelConverter.toCapturePaymentRequest(paymentDataMap))
215
+
216
+ withContext(Dispatchers.Main) {
217
+ if (result.isSuccess) {
218
+ val response = Arguments.createMap()
219
+ response.putString("status", "success")
220
+ response.putString("message", "Payment captured successfully")
221
+ promise.resolve(response)
222
+ } else {
223
+ promise.reject(
224
+ "PAYMENT_ERROR",
225
+ "Failed to capture payment: ${result.exceptionOrNull()?.message}",
226
+ result.exceptionOrNull()
227
+ )
228
+ }
229
+ }
230
+ } catch (e: Exception) {
231
+ withContext(Dispatchers.Main) {
232
+ promise.reject("PAYMENT_ERROR", "Exception capturing payment: ${e.message}", e)
233
+ }
234
+ }
235
+ }
236
+ } catch (e: Exception) {
237
+ promise.reject("PAYMENT_ERROR", "Failed to capture payment: ${e.message}", e)
238
+ }
239
+ }
240
+
241
+ @ReactMethod
242
+ fun removePayment(paymentData: ReadableMap, promise: Promise) {
243
+ if (paymentData == null) {
244
+ promise.reject("REMOVE_PAYMENT_ERROR", "Payment data is required")
245
+ return
246
+ }
247
+
248
+ try {
249
+ val paymentDataMap = MapUtils.readableMapToMap(paymentData)
250
+
251
+ moduleScope.launch {
252
+ try {
253
+ val result = linkrunnerSDK.removePayment(ModelConverter.toRemovePaymentRequest(paymentDataMap))
254
+
255
+ withContext(Dispatchers.Main) {
256
+ if (result.isSuccess) {
257
+ val response = Arguments.createMap()
258
+ response.putString("status", "success")
259
+ response.putString("message", "Payment removed successfully")
260
+ promise.resolve(response)
261
+ } else {
262
+ promise.reject(
263
+ "REMOVE_PAYMENT_ERROR",
264
+ "Failed to remove payment: ${result.exceptionOrNull()?.message}",
265
+ result.exceptionOrNull()
266
+ )
267
+ }
268
+ }
269
+ } catch (e: Exception) {
270
+ withContext(Dispatchers.Main) {
271
+ promise.reject("REMOVE_PAYMENT_ERROR", "Exception removing payment: ${e.message}", e)
272
+ }
273
+ }
274
+ }
275
+ } catch (e: Exception) {
276
+ promise.reject("REMOVE_PAYMENT_ERROR", "Failed to remove payment: ${e.message}", e)
277
+ }
278
+ }
279
+
280
+ @ReactMethod
281
+ fun getAttributionData(promise: Promise) {
282
+ moduleScope.launch {
283
+ try {
284
+ val result = linkrunnerSDK.getAttributionData()
285
+
286
+ withContext(Dispatchers.Main) {
287
+ if (result.isSuccess) {
288
+ val attributionData = result.getOrNull()
289
+
290
+ // Convert the attribution data to a WritableMap
291
+ val response = Arguments.createMap()
292
+
293
+ if (attributionData != null) {
294
+ // Add the deeplink if it exists
295
+ attributionData.deeplink?.let { deeplink ->
296
+ response.putString("deeplink", deeplink)
297
+ }
298
+
299
+ // Convert campaign data to a WritableMap if it exists
300
+ attributionData.campaignData?.let { campaignData ->
301
+ val campaignDataMap = Arguments.createMap()
302
+ campaignDataMap.putString("id", campaignData.id)
303
+ campaignDataMap.putString("name", campaignData.name)
304
+
305
+ campaignData.adNetwork?.let { adNetwork ->
306
+ campaignDataMap.putString("adNetwork", adNetwork)
307
+ }
308
+
309
+ campaignDataMap.putString("type", campaignData.type)
310
+ campaignDataMap.putString("installedAt", campaignData.installedAt)
311
+
312
+ campaignData.storeClickAt?.let { storeClickAt ->
313
+ campaignDataMap.putString("storeClickAt", storeClickAt)
314
+ }
315
+
316
+ campaignDataMap.putString("groupName", campaignData.groupName)
317
+ campaignDataMap.putString("assetName", campaignData.assetName)
318
+ campaignDataMap.putString("assetGroupName", campaignData.assetGroupName)
319
+
320
+ response.putMap("campaignData", campaignDataMap)
321
+ }
322
+ }
323
+
324
+ response.putString("status", "success")
325
+ response.putString("message", "Attribution data retrieved successfully")
326
+
327
+ promise.resolve(response)
328
+ } else {
329
+ promise.reject(
330
+ "ATTRIBUTION_DATA_ERROR",
331
+ "Failed to get attribution data: ${result.exceptionOrNull()?.message}",
332
+ result.exceptionOrNull()
333
+ )
334
+ }
335
+ }
336
+ } catch (e: Exception) {
337
+ withContext(Dispatchers.Main) {
338
+ promise.reject("ATTRIBUTION_DATA_ERROR", "Exception getting attribution data: ${e.message}", e)
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ @ReactMethod
345
+ fun setAdditionalData(integrationDataMap: ReadableMap?, promise: Promise) {
346
+ if (integrationDataMap == null || integrationDataMap.toHashMap().isEmpty()) {
347
+ promise.reject("ADDITIONAL_DATA_ERROR", "Integration data is required")
348
+ return
349
+ }
350
+
351
+ try {
352
+ val additionalData = MapUtils.readableMapToMap(integrationDataMap)
353
+
354
+ moduleScope.launch {
355
+ try {
356
+ // Convert Map to IntegrationData
357
+ val integrationData = ModelConverter.toIntegrationData(additionalData)
358
+ val result = linkrunnerSDK.setAdditionalData(integrationData)
359
+
360
+ withContext(Dispatchers.Main) {
361
+ if (result.isSuccess) {
362
+ val response = Arguments.createMap()
363
+ response.putString("status", "success")
364
+ response.putString("message", "Additional data set successfully")
365
+ promise.resolve(response)
366
+ } else {
367
+ promise.reject(
368
+ "ADDITIONAL_DATA_ERROR",
369
+ "Failed to set additional data: ${result.exceptionOrNull()?.message}",
370
+ result.exceptionOrNull()
371
+ )
372
+ }
373
+ }
374
+ } catch (e: Exception) {
375
+ withContext(Dispatchers.Main) {
376
+ promise.reject("ADDITIONAL_DATA_ERROR", "Exception setting additional data: ${e.message}", e)
377
+ }
378
+ }
379
+ }
380
+ } catch (e: Exception) {
381
+ promise.reject("ADDITIONAL_DATA_ERROR", "Failed to set additional data: ${e.message}", e)
382
+ }
383
+ }
384
+
385
+ @ReactMethod
386
+ fun enablePIIHashing(enabled: Boolean) {
387
+ try {
388
+ // Call the SDK's enablePIIHashing method
389
+ linkrunnerSDK.enablePIIHashing(enabled)
390
+
391
+ if (BuildConfig.DEBUG) {
392
+ Log.d(TAG, "Linkrunner: PII hashing ${if (enabled) "enabled" else "disabled"}")
393
+ }
394
+ } catch (e: Exception) {
395
+ Log.e(TAG, "Failed to ${if (enabled) "enable" else "disable"} PII hashing", e)
396
+ }
397
+ }
398
+ }
@@ -0,0 +1,16 @@
1
+ package io.linkrunner
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class LinkrunnerPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(LinkrunnerModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,136 @@
1
+ package io.linkrunner.utils
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import com.facebook.react.bridge.ReadableMap
6
+ import com.facebook.react.bridge.ReadableType
7
+ import com.facebook.react.bridge.WritableArray
8
+ import com.facebook.react.bridge.WritableMap
9
+
10
+ /**
11
+ * Utility functions to convert between React Native types and Kotlin types
12
+ */
13
+ object MapUtils {
14
+
15
+ /**
16
+ * Convert ReadableMap to Kotlin Map
17
+ */
18
+ fun readableMapToMap(readableMap: ReadableMap): Map<String, Any> {
19
+ val map = mutableMapOf<String, Any>()
20
+ val iterator = readableMap.keySetIterator()
21
+ while (iterator.hasNextKey()) {
22
+ val key = iterator.nextKey()
23
+ when (readableMap.getType(key)) {
24
+ ReadableType.Null -> map[key] = ""
25
+ ReadableType.Boolean -> map[key] = readableMap.getBoolean(key)
26
+ ReadableType.Number -> map[key] = readableMap.getDouble(key)
27
+ ReadableType.String -> map[key] = readableMap.getString(key) ?: ""
28
+ ReadableType.Map -> map[key] = readableMapToMap(readableMap.getMap(key)!!)
29
+ ReadableType.Array -> map[key] = readableArrayToList(readableMap.getArray(key)!!)
30
+ }
31
+ }
32
+ return map
33
+ }
34
+
35
+ /**
36
+ * Convert ReadableArray to Kotlin List
37
+ */
38
+ private fun readableArrayToList(readableArray: ReadableArray): List<Any> {
39
+ val list = mutableListOf<Any>()
40
+ for (i in 0 until readableArray.size()) {
41
+ when (readableArray.getType(i)) {
42
+ ReadableType.Null -> list.add("")
43
+ ReadableType.Boolean -> list.add(readableArray.getBoolean(i))
44
+ ReadableType.Number -> list.add(readableArray.getDouble(i))
45
+ ReadableType.String -> list.add(readableArray.getString(i) ?: "")
46
+ ReadableType.Map -> list.add(readableMapToMap(readableArray.getMap(i)!!))
47
+ ReadableType.Array -> list.add(readableArrayToList(readableArray.getArray(i)!!))
48
+ }
49
+ }
50
+ return list
51
+ }
52
+
53
+ /**
54
+ * Convert Kotlin Map to WritableMap
55
+ */
56
+ fun mapToWritableMap(map: Map<String, Any?>?): WritableMap {
57
+ val writableMap = Arguments.createMap()
58
+
59
+ if (map == null) {
60
+ return writableMap
61
+ }
62
+
63
+ for ((key, value) in map) {
64
+ when (value) {
65
+ null -> writableMap.putNull(key)
66
+ is Boolean -> writableMap.putBoolean(key, value)
67
+ is Int -> writableMap.putInt(key, value)
68
+ is Double -> writableMap.putDouble(key, value)
69
+ is Float -> writableMap.putDouble(key, value.toDouble())
70
+ is String -> writableMap.putString(key, value)
71
+ is Map<*, *> -> {
72
+ @Suppress("UNCHECKED_CAST")
73
+ writableMap.putMap(key, mapToWritableMap(value as Map<String, Any?>))
74
+ }
75
+ is List<*> -> {
76
+ @Suppress("UNCHECKED_CAST")
77
+ writableMap.putArray(key, listToWritableArray(value as List<Any?>))
78
+ }
79
+ else -> writableMap.putString(key, value.toString())
80
+ }
81
+ }
82
+
83
+ return writableMap
84
+ }
85
+
86
+ /**
87
+ * Convert Kotlin List to WritableArray
88
+ */
89
+ fun listToWritableArray(list: List<Any?>?): WritableArray {
90
+ val writableArray = Arguments.createArray()
91
+
92
+ if (list == null) {
93
+ return writableArray
94
+ }
95
+
96
+ for (value in list) {
97
+ when (value) {
98
+ null -> writableArray.pushNull()
99
+ is Boolean -> writableArray.pushBoolean(value)
100
+ is Int -> writableArray.pushInt(value)
101
+ is Double -> writableArray.pushDouble(value)
102
+ is Float -> writableArray.pushDouble(value.toDouble())
103
+ is String -> writableArray.pushString(value)
104
+ is Map<*, *> -> {
105
+ @Suppress("UNCHECKED_CAST")
106
+ writableArray.pushMap(mapToWritableMap(value as Map<String, Any?>))
107
+ }
108
+ is List<*> -> {
109
+ @Suppress("UNCHECKED_CAST")
110
+ writableArray.pushArray(listToWritableArray(value as List<Any?>))
111
+ }
112
+ else -> writableArray.pushString(value.toString())
113
+ }
114
+ }
115
+
116
+ return writableArray
117
+ }
118
+
119
+ /**
120
+ * Extension functions for safe retrieval from maps
121
+ */
122
+ inline fun <reified T> Map<String, Any?>.get(key: String, defaultValue: T): T {
123
+ val value = this[key]
124
+ return when {
125
+ value == null -> defaultValue
126
+ value is T -> value
127
+ T::class.java == String::class.java && value != null -> value.toString() as T
128
+ T::class.java == Int::class.java && value is Number -> value.toInt() as T
129
+ T::class.java == Double::class.java && value is Number -> value.toDouble() as T
130
+ T::class.java == Float::class.java && value is Number -> value.toFloat() as T
131
+ T::class.java == Long::class.java && value is Number -> value.toLong() as T
132
+ T::class.java == Boolean::class.java && value is Number -> (value.toInt() != 0) as T
133
+ else -> defaultValue
134
+ }
135
+ }
136
+ }