rn-linkrunner 1.1.2 → 2.0.1

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,226 @@
1
+ package io.linkrunner.utils
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.bridge.WritableMap
6
+ import io.linkrunner.sdk.models.PaymentStatus
7
+ import io.linkrunner.sdk.models.PaymentType
8
+ import io.linkrunner.sdk.models.request.CapturePaymentRequest
9
+ import io.linkrunner.sdk.models.request.RemovePaymentRequest
10
+ import io.linkrunner.sdk.models.request.UserDataRequest
11
+ import io.linkrunner.sdk.models.IntegrationData
12
+ import io.linkrunner.sdk.models.response.AttributionData
13
+ import io.linkrunner.sdk.models.response.ClientCampaignData
14
+ import io.linkrunner.sdk.models.response.GeneralResponse
15
+ import io.linkrunner.sdk.models.response.IPLocationData
16
+
17
+ /**
18
+ * Utility class to convert React Native ReadableMap objects to Kotlin model objects and vice versa
19
+ */
20
+ object ModelConverter {
21
+
22
+ /**
23
+ * Convert GeneralResponse to WritableMap
24
+ */
25
+ fun fromGeneralResponse(response: GeneralResponse?): WritableMap? {
26
+ if (response == null) {
27
+ return null
28
+ }
29
+
30
+ val map = Arguments.createMap()
31
+
32
+ // Add IP location data if available
33
+ response.ipLocationData?.let { ipData ->
34
+ map.putMap("ip_location_data", fromIPLocationData(ipData))
35
+ }
36
+
37
+ // Add deeplink if available
38
+ response.deeplink?.let { deeplink ->
39
+ map.putString("deeplink", deeplink)
40
+ }
41
+
42
+ // Add root domain if available
43
+ response.rootDomain?.let { rootDomain ->
44
+ map.putBoolean("root_domain", rootDomain)
45
+ }
46
+
47
+ return map
48
+ }
49
+
50
+ /**
51
+ * Convert ClientCampaignData to WritableMap
52
+ */
53
+ fun fromClientCampaignData(data: ClientCampaignData?): WritableMap? {
54
+ if (data == null) {
55
+ return null
56
+ }
57
+
58
+ val map = Arguments.createMap()
59
+
60
+ // Add required fields
61
+ map.putString("id", data.id)
62
+ map.putString("name", data.name)
63
+ map.putString("type", data.type)
64
+
65
+ // Add optional fields if available
66
+ data.adNetwork?.let { adNetwork ->
67
+ map.putString("adNetwork", adNetwork)
68
+ }
69
+
70
+ data.groupName?.let { groupName ->
71
+ map.putString("groupName", groupName)
72
+ }
73
+
74
+ data.assetGroupName?.let { assetGroupName ->
75
+ map.putString("assetGroupName", assetGroupName)
76
+ }
77
+
78
+ data.assetName?.let { assetName ->
79
+ map.putString("assetName", assetName)
80
+ }
81
+
82
+ return map
83
+ }
84
+
85
+ /**
86
+ * Convert IPLocationData to WritableMap
87
+ */
88
+ fun fromIPLocationData(data: IPLocationData?): WritableMap? {
89
+ if (data == null) {
90
+ return null
91
+ }
92
+
93
+ val map = Arguments.createMap()
94
+
95
+ // Add all fields if available
96
+ data.ip?.let { ip ->
97
+ map.putString("ip", ip)
98
+ }
99
+
100
+ data.city?.let { city ->
101
+ map.putString("city", city)
102
+ }
103
+
104
+ data.countryLong?.let { countryLong ->
105
+ map.putString("countryLong", countryLong)
106
+ }
107
+
108
+ data.countryShort?.let { countryShort ->
109
+ map.putString("countryShort", countryShort)
110
+ }
111
+
112
+ data.latitude?.let { latitude ->
113
+ map.putDouble("latitude", latitude)
114
+ }
115
+
116
+ data.longitude?.let { longitude ->
117
+ map.putDouble("longitude", longitude)
118
+ }
119
+
120
+ data.region?.let { region ->
121
+ map.putString("region", region)
122
+ }
123
+
124
+ data.timeZone?.let { timeZone ->
125
+ map.putString("timeZone", timeZone)
126
+ }
127
+
128
+ data.zipCode?.let { zipCode ->
129
+ map.putString("zipCode", zipCode)
130
+ }
131
+
132
+ return map
133
+ }
134
+
135
+ /**
136
+ * Convert AttributionData to WritableMap
137
+ */
138
+ fun fromAttributionData(data: AttributionData?): WritableMap {
139
+ val map = Arguments.createMap()
140
+
141
+ if (data != null) {
142
+ // Add the deeplink if it exists
143
+ data.deeplink?.let { deeplink ->
144
+ map.putString("deeplink", deeplink)
145
+ }
146
+
147
+ // Convert campaign data to a WritableMap if it exists
148
+ data.campaignData?.let { campaignData ->
149
+ val campaignDataMap = Arguments.createMap()
150
+ campaignDataMap.putString("id", campaignData.id)
151
+ campaignDataMap.putString("name", campaignData.name)
152
+
153
+ campaignData.adNetwork?.let { adNetwork ->
154
+ campaignDataMap.putString("adNetwork", adNetwork)
155
+ }
156
+
157
+ campaignDataMap.putString("type", campaignData.type)
158
+ campaignDataMap.putString("installedAt", campaignData.installedAt)
159
+
160
+ campaignData.storeClickAt?.let { storeClickAt ->
161
+ campaignDataMap.putString("storeClickAt", storeClickAt)
162
+ }
163
+
164
+ campaignDataMap.putString("groupName", campaignData.groupName)
165
+ campaignDataMap.putString("assetName", campaignData.assetName)
166
+ campaignDataMap.putString("assetGroupName", campaignData.assetGroupName)
167
+
168
+ map.putMap("campaignData", campaignDataMap)
169
+ }
170
+ }
171
+
172
+ return map
173
+ }
174
+
175
+ /**
176
+ * Convert a ReadableMap to UserDataRequest
177
+ */
178
+ fun toUserDataRequest(map: Map<String, Any>): UserDataRequest {
179
+ return UserDataRequest(
180
+ id = map["id"] as? String ?: "",
181
+ name = map["name"] as? String,
182
+ phone = map["phone"] as? String,
183
+ email = map["email"] as? String,
184
+ mixpanelDistinctId = map["mixpanel_distinct_id"] as? String,
185
+ amplitudeDeviceId = map["amplitude_device_id"] as? String,
186
+ posthogDistinctId = map["posthog_distinct_id"] as? String,
187
+ userCreatedAt = map["user_created_at"] as? String,
188
+ isFirstTimeUser = map["is_first_time_user"] as? Boolean
189
+ )
190
+ }
191
+
192
+ /**
193
+ * Convert a Map to IntegrationData
194
+ */
195
+ fun toIntegrationData(map: Map<String, Any>): IntegrationData {
196
+ return IntegrationData(
197
+ clevertapId = map["clevertapId"] as? String
198
+ )
199
+ }
200
+
201
+ /**
202
+ * Convert a ReadableMap to CapturePaymentRequest
203
+ */
204
+ fun toCapturePaymentRequest(map: Map<String, Any>): CapturePaymentRequest {
205
+ val typeString = map["type"] as? String ?: "DEFAULT"
206
+ val statusString = map["status"] as? String ?: "PAYMENT_COMPLETED"
207
+
208
+ return CapturePaymentRequest(
209
+ paymentId = map["paymentId"] as? String ?: "",
210
+ userId = map["userId"] as? String ?: "",
211
+ amount = (map["amount"] as? Number)?.toDouble() ?: 0.0,
212
+ type = PaymentType.valueOf(typeString),
213
+ status = PaymentStatus.valueOf(statusString)
214
+ )
215
+ }
216
+
217
+ /**
218
+ * Convert a ReadableMap to RemovePaymentRequest
219
+ */
220
+ fun toRemovePaymentRequest(map: Map<String, Any>): RemovePaymentRequest {
221
+ return RemovePaymentRequest(
222
+ paymentId = map["paymentId"] as? String ?: "",
223
+ userId = map["userId"] as? String ?: ""
224
+ )
225
+ }
226
+ }
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface LinkrunnerSDK : NSObject <RCTBridgeModule>
4
+
5
+ @end
@@ -0,0 +1,31 @@
1
+ #import "React/RCTBridgeModule.h"
2
+
3
+ // first parameter is the name of the native module
4
+ // second parameter is the name of the native class that implements the module
5
+ @interface RCT_EXTERN_REMAP_MODULE(LinkrunnerSDK, LinkrunnerSDK, NSObject)
6
+
7
+ RCT_EXTERN_METHOD(initializeSDK:(NSDictionary *)dict)
8
+
9
+ RCT_EXTERN_METHOD(signup:(NSDictionary *)userData
10
+ data:(NSDictionary *)data)
11
+
12
+ RCT_EXTERN_METHOD(setUserData:(NSDictionary *)userData)
13
+
14
+ RCT_EXTERN_METHOD(trackEvent:(NSString *)eventName eventData:(NSDictionary *)eventData)
15
+
16
+ RCT_EXTERN_METHOD(capturePayment:(NSDictionary *)paymentData)
17
+
18
+ RCT_EXTERN_METHOD(removePayment:(NSDictionary *)paymentData)
19
+
20
+ RCT_EXTERN_METHOD(enablePIIHashing:(BOOL)enabled)
21
+
22
+ RCT_EXTERN_METHOD(requestTrackingAuthorization)
23
+
24
+ RCT_EXTERN_METHOD(getAttributionData:(RCTPromiseResolveBlock)resolve
25
+ rejecter:(RCTPromiseRejectBlock)reject)
26
+
27
+ RCT_EXTERN_METHOD(setAdditionalData:(NSDictionary *)integrationDataDict
28
+ resolver:(RCTPromiseResolveBlock)resolve
29
+ rejecter:(RCTPromiseRejectBlock)reject)
30
+
31
+ @end
@@ -0,0 +1,218 @@
1
+ import Foundation
2
+ import LinkrunnerKit;
3
+ import React
4
+
5
+ @objc(LinkrunnerSDK)
6
+ class LinkrunnerSDK: NSObject {
7
+
8
+ private var linkrunnerSDK: LinkrunnerKit.LinkrunnerSDK!
9
+
10
+ override init() {
11
+ linkrunnerSDK = LinkrunnerKit.LinkrunnerSDK.shared
12
+ }
13
+
14
+
15
+ // MARK: - Native SDK Method Wrappers
16
+
17
+ @objc func initializeSDK(_ dict: NSDictionary) -> Void {
18
+ guard let token = dict["token"] as? String else {
19
+ print("Linkrunner: token is required")
20
+ return
21
+ }
22
+
23
+ let secretKey = dict["secretKey"] as? String
24
+ let keyId = dict["keyId"] as? String
25
+ let debug = dict["debug"] as? Bool ?? false
26
+
27
+
28
+ Task {
29
+ do {
30
+ try await linkrunnerSDK.initialize(token: token, secretKey: secretKey, keyId: keyId, debug: debug)
31
+ print("Linkrunner: SDK initialized successfully")
32
+ } catch {
33
+ print("Linkrunner: Failed to initialize SDK: \(error)")
34
+ }
35
+ }
36
+ }
37
+
38
+ @objc func signup(_ userData: NSDictionary, data: NSDictionary? = nil) -> Void {
39
+ guard let id = userData["id"] as? String else {
40
+ print("Linkrunner: User ID is required")
41
+ return
42
+ }
43
+
44
+ let userDataObj = UserData(
45
+ id: id,
46
+ name: userData["name"] as? String,
47
+ phone: userData["phone"] as? String,
48
+ email: userData["email"] as? String,
49
+ isFirstTimeUser: userData["is_first_time_user"] as? Bool,
50
+ userCreatedAt: userData["user_created_at"] as? String,
51
+ mixPanelDistinctId: userData["mixpanel_distinct_id"] as? String,
52
+ amplitudeDeviceId: userData["amplitude_device_id"] as? String,
53
+ posthogDistinctId: userData["posthog_distinct_id"] as? String
54
+ )
55
+
56
+ Task {
57
+ do {
58
+ if #available(iOS 15.0, *) {
59
+ try await linkrunnerSDK.signup(
60
+ userData: userDataObj,
61
+ additionalData: data as? [String: Any]
62
+ )
63
+ return
64
+ } else {
65
+ print("UNSUPPORTED_VERSION: iOS 15.0 or later is required")
66
+ }
67
+ } catch {
68
+ print("SIGNUP_ERROR", "Failed to complete signup: \(error.localizedDescription)", error)
69
+ }
70
+ }
71
+ }
72
+
73
+ @objc func setUserData(_ userData: NSDictionary) -> Void {
74
+ guard let id = userData["id"] as? String else {
75
+ print("Linkrunner: User ID is required")
76
+ return
77
+ }
78
+
79
+ let userDataObj = UserData(
80
+ id: id,
81
+ name: userData["name"] as? String,
82
+ phone: userData["phone"] as? String,
83
+ email: userData["email"] as? String,
84
+ isFirstTimeUser: userData["is_first_time_user"] as? Bool,
85
+ userCreatedAt: userData["user_created_at"] as? String,
86
+ mixPanelDistinctId: userData["mixpanel_distinct_id"] as? String,
87
+ amplitudeDeviceId: userData["amplitude_device_id"] as? String,
88
+ posthogDistinctId: userData["posthog_distinct_id"] as? String
89
+ )
90
+
91
+ Task {
92
+ do {
93
+ try await linkrunnerSDK.setUserData(userDataObj)
94
+ print("Linkrunner: User data set successfully")
95
+ } catch {
96
+ print("Linkrunner: Failed to set user data: \(error)")
97
+ }
98
+ }
99
+ }
100
+
101
+ @objc func trackEvent(_ eventName: NSString, eventData: NSDictionary?) -> Void {
102
+ Task {
103
+ do {
104
+ try await linkrunnerSDK.trackEvent(eventName: eventName as String, eventData: eventData as? [String: Any])
105
+ print("Linkrunner: Event tracked successfully")
106
+ } catch {
107
+ print("Linkrunner: Failed to track event: \(error)")
108
+ }
109
+ }
110
+ }
111
+
112
+ @objc func capturePayment(_ paymentData: NSDictionary) -> Void {
113
+ guard let userId = paymentData["userId"] as? String,
114
+ let amount = paymentData["amount"] as? Double else {
115
+ print("Linkrunner: userId and amount are required for payment capture")
116
+ return
117
+ }
118
+
119
+ let paymentId = paymentData["paymentId"] as? String
120
+ let typeString = paymentData["type"] as? String ?? "DEFAULT"
121
+ let statusString = paymentData["status"] as? String ?? "PAYMENT_COMPLETED"
122
+
123
+ // Convert strings to enums
124
+ let paymentType = PaymentType(rawValue: typeString) ?? .default
125
+ let paymentStatus = PaymentStatus(rawValue: statusString) ?? .completed
126
+
127
+ Task {
128
+ do {
129
+ try await linkrunnerSDK.capturePayment(
130
+ amount: amount,
131
+ userId: userId,
132
+ paymentId: paymentId,
133
+ type: paymentType,
134
+ status: paymentStatus
135
+ )
136
+ print("Linkrunner: Payment captured successfully")
137
+ } catch {
138
+ print("Linkrunner: Failed to capture payment: \(error)")
139
+ }
140
+ }
141
+ }
142
+
143
+ @objc func removePayment(_ paymentData: NSDictionary) -> Void {
144
+ guard let userId = paymentData["userId"] as? String else {
145
+ print("Linkrunner: userId is required for payment removal")
146
+ return
147
+ }
148
+
149
+ let paymentId = paymentData["paymentId"] as? String
150
+
151
+ Task {
152
+ do {
153
+ try await linkrunnerSDK.removePayment(userId: userId, paymentId: paymentId)
154
+ print("Linkrunner: Payment removed successfully")
155
+ } catch {
156
+ print("Linkrunner: Failed to remove payment: \(error)")
157
+ }
158
+ }
159
+ }
160
+
161
+ @objc func enablePIIHashing(_ enabled: Bool) -> Void {
162
+ Task {
163
+ await linkrunnerSDK.enablePIIHashing(enabled)
164
+ print("Linkrunner: PII hashing \(enabled ? "enabled" : "disabled")")
165
+ }
166
+ }
167
+
168
+ @objc func requestTrackingAuthorization() -> Void {
169
+ linkrunnerSDK.requestTrackingAuthorization { status in
170
+ print("Linkrunner: Tracking authorization status: \(status.rawValue)")
171
+ }
172
+ }
173
+
174
+ @objc func getAttributionData(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
175
+ Task {
176
+ do {
177
+ let attributionData = try await linkrunnerSDK.getAttributionData()
178
+ resolve(attributionData.toDictionary())
179
+ } catch {
180
+ reject("ATTRIBUTION_ERROR", "Failed to get attribution data: \(error.localizedDescription)", error)
181
+ }
182
+ }
183
+ }
184
+
185
+ @objc func setAdditionalData(_ integrationDataDict: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) -> Void {
186
+ if integrationDataDict.count == 0 {
187
+ reject("ADDITIONAL_DATA_ERROR", "Integration data is required", NSError(domain: "LinkrunnerSDK", code: 1, userInfo: nil))
188
+ return
189
+ }
190
+
191
+ let clevertapId = integrationDataDict["clevertapId"] as? String
192
+
193
+ let integrationData = IntegrationData(clevertapId: clevertapId)
194
+
195
+ Task {
196
+ do {
197
+ if #available(iOS 15.0, *) {
198
+ try await linkrunnerSDK.setAdditionalData(integrationData)
199
+
200
+ let response: [String: Any] = [
201
+ "status": "success",
202
+ "message": "Additional data set successfully"
203
+ ]
204
+ resolve(response)
205
+ } else {
206
+ reject("UNSUPPORTED_VERSION", "iOS 15.0 or later is required", NSError(domain: "LinkrunnerSDK", code: 2, userInfo: nil))
207
+ }
208
+ } catch {
209
+ reject("ADDITIONAL_DATA_ERROR", "Failed to set additional data: \(error.localizedDescription)", error)
210
+ }
211
+ }
212
+ }
213
+
214
+ }
215
+
216
+ // Type definition needed for RCTPromiseResolveBlock
217
+ typealias RCTPromiseResolveBlock = (Any?) -> Void
218
+ typealias RCTPromiseRejectBlock = (String, String?, Error?) -> Void
package/ios/Podfile ADDED
@@ -0,0 +1,19 @@
1
+ # Define the global platform for your project
2
+ platform :ios, '15.0'
3
+
4
+ # Specify that this is a library, not an application
5
+ install! 'cocoapods', :deterministic_uuids => false
6
+
7
+ # This is needed since there's no Xcode project yet
8
+ abstract_target 'LinkrunnerSDK' do
9
+ # Comment the next line if you don't want to use dynamic frameworks
10
+ use_frameworks!
11
+
12
+ # Pods for LinkrunnerSDK
13
+
14
+ # Add the linkrunner-ios-sdk dependency
15
+ pod 'LinkrunnerKit', '3.0.2'
16
+
17
+
18
+ # Add any other dependencies your SDK needs here
19
+ end