react-native-smartlinks 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.
Files changed (27) hide show
  1. package/android/build.gradle +33 -0
  2. package/android/src/main/AndroidManifest.xml +4 -0
  3. package/android/src/main/java/live/smartlinks/reactnative/SmartLinksActivityLifecycle.kt +34 -0
  4. package/android/src/main/java/live/smartlinks/reactnative/SmartLinksBridgeHandler.kt +172 -0
  5. package/android/src/main/java/live/smartlinks/reactnative/SmartLinksModule.kt +299 -0
  6. package/android/src/main/java/live/smartlinks/reactnative/SmartLinksPackage.kt +16 -0
  7. package/ios/Linklytics/DeviceInfo.swift +114 -0
  8. package/ios/Linklytics/Linklytics.swift +581 -0
  9. package/ios/Linklytics/LinklyticsInterface.swift +10 -0
  10. package/ios/Linklytics/Managers/BatchEventManager.swift +218 -0
  11. package/ios/Linklytics/Managers/OfflineCache.swift +35 -0
  12. package/ios/Linklytics/Managers/SQLiteStorage.swift +225 -0
  13. package/ios/Linklytics/Models/AppResponse.swift +16 -0
  14. package/ios/Linklytics/Models/DeepLinkRoute.swift +24 -0
  15. package/ios/Linklytics/Models/DeferredLinkResponse.swift +81 -0
  16. package/ios/Linklytics/Models/DynamicLinkResponse.swift +13 -0
  17. package/ios/Linklytics/Models/EventResponse.swift +14 -0
  18. package/ios/Linklytics/Models/OfflineEvent.swift +80 -0
  19. package/ios/Linklytics/NetworkManager.swift +360 -0
  20. package/ios/Linklytics/SdkConstants.swift +29 -0
  21. package/ios/PrivacyInfo.xcprivacy +14 -0
  22. package/ios/SmartLinksModule.m +150 -0
  23. package/ios/SmartLinksModule.swift +281 -0
  24. package/package.json +41 -0
  25. package/react-native-smartlinks.podspec +27 -0
  26. package/react-native.config.js +12 -0
  27. package/src/index.ts +84 -0
@@ -0,0 +1,33 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+
4
+ android {
5
+ namespace 'live.smartlinks.reactnative'
6
+ compileSdk rootProject.ext.compileSdkVersion
7
+
8
+ defaultConfig {
9
+ minSdkVersion rootProject.ext.minSdkVersion
10
+ }
11
+
12
+ compileOptions {
13
+ sourceCompatibility JavaVersion.VERSION_17
14
+ targetCompatibility JavaVersion.VERSION_17
15
+ }
16
+
17
+ kotlinOptions {
18
+ jvmTarget = '17'
19
+ freeCompilerArgs += ['-Xskip-metadata-version-check']
20
+ }
21
+ }
22
+
23
+ repositories {
24
+ google()
25
+ mavenCentral()
26
+ }
27
+
28
+ dependencies {
29
+ implementation 'com.facebook.react:react-native:+'
30
+ implementation 'live.smartlinks:smartlinks-sdk-core:2.0.1'
31
+ // SmartLinks SDK is built with Kotlin 2.2 — required at runtime (SpillingKt, etc.)
32
+ implementation 'org.jetbrains.kotlin:kotlin-stdlib:2.2.20'
33
+ }
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3
+ package="live.smartlinks.reactnative">
4
+ </manifest>
@@ -0,0 +1,34 @@
1
+ package live.smartlinks.reactnative
2
+
3
+ import android.app.Activity
4
+ import android.app.Application
5
+ import android.content.Intent
6
+ import android.os.Bundle
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+
9
+ internal class SmartLinksActivityLifecycle(
10
+ private val reactContext: ReactApplicationContext,
11
+ private val module: SmartLinksModule,
12
+ ) : Application.ActivityLifecycleCallbacks {
13
+
14
+ override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
15
+ override fun onActivityStarted(activity: Activity) {}
16
+ override fun onActivityResumed(activity: Activity) {}
17
+ override fun onActivityPaused(activity: Activity) {}
18
+ override fun onActivityStopped(activity: Activity) {}
19
+ override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
20
+
21
+ override fun onActivityDestroyed(activity: Activity) {}
22
+
23
+ override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) {
24
+ activity.intent?.let { module.onNewIntent(it) }
25
+ }
26
+ }
27
+
28
+ internal fun registerActivityLifecycle(
29
+ reactContext: ReactApplicationContext,
30
+ module: SmartLinksModule,
31
+ ) {
32
+ val app = reactContext.applicationContext as Application
33
+ app.registerActivityLifecycleCallbacks(SmartLinksActivityLifecycle(reactContext, module))
34
+ }
@@ -0,0 +1,172 @@
1
+ package live.smartlinks.reactnative
2
+
3
+ import com.alkashier.smartlinks.bridge.SmartLinksBridgeApi
4
+ import com.facebook.react.bridge.Promise
5
+ import com.facebook.react.bridge.ReadableMap
6
+
7
+ internal object SmartLinksBridgeHandler {
8
+
9
+ fun fetchApps(promise: Promise) {
10
+ SmartLinksBridgeApi.fetchApps { json ->
11
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "fetchApps failed")
12
+ }
13
+ }
14
+
15
+ fun submitFeedback(args: ReadableMap, promise: Promise) {
16
+ SmartLinksBridgeApi.submitFeedback(
17
+ content = args.getString("content") ?: "",
18
+ platform = args.getString("platform") ?: "android",
19
+ version = args.getString("version") ?: "1.0",
20
+ name = if (args.hasKey("name")) args.getString("name") else null,
21
+ email = if (args.hasKey("email")) args.getString("email") else null,
22
+ isPremium = if (args.hasKey("isPremium")) args.getBoolean("isPremium") else false,
23
+ attachmentPath = if (args.hasKey("attachmentPath")) args.getString("attachmentPath") else null,
24
+ ) { json ->
25
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "submitFeedback failed")
26
+ }
27
+ }
28
+
29
+ fun fetchAds(type: String?, promise: Promise) {
30
+ SmartLinksBridgeApi.fetchAds(type) { json ->
31
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "fetchAds failed")
32
+ }
33
+ }
34
+
35
+ fun trackAdImpression(adId: Int, promise: Promise) {
36
+ SmartLinksBridgeApi.trackAdImpression(adId) { json ->
37
+ promise.resolve(json ?: """{"success":false}""")
38
+ }
39
+ }
40
+
41
+ fun trackAdClick(adId: Int, promise: Promise) {
42
+ SmartLinksBridgeApi.trackAdClick(adId) { json ->
43
+ promise.resolve(json ?: """{"success":false}""")
44
+ }
45
+ }
46
+
47
+ fun processAi(instructionsJson: String, files: Map<String, String>, promise: Promise) {
48
+ SmartLinksBridgeApi.processAi(instructionsJson, files) { json ->
49
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "processAi failed")
50
+ }
51
+ }
52
+
53
+ fun validatePromoCode(code: String, promise: Promise) {
54
+ SmartLinksBridgeApi.validatePromoCode(code) { json -> promise.resolve(json) }
55
+ }
56
+
57
+ fun redeemPromoCode(code: String, promise: Promise) {
58
+ SmartLinksBridgeApi.redeemPromoCode(code) { json -> promise.resolve(json) }
59
+ }
60
+
61
+ fun verifyGooglePurchase(productId: String, token: String, promise: Promise) {
62
+ SmartLinksBridgeApi.verifyGooglePurchase(productId, token) { json -> promise.resolve(json) }
63
+ }
64
+
65
+ fun verifyApplePurchase(receiptData: String, promise: Promise) {
66
+ SmartLinksBridgeApi.verifyApplePurchase(receiptData) { json -> promise.resolve(json) }
67
+ }
68
+
69
+ fun getCommunities(promise: Promise) {
70
+ SmartLinksBridgeApi.getCommunities { json ->
71
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "getCommunities failed")
72
+ }
73
+ }
74
+
75
+ fun getPosts(communityId: Int, authToken: String?, promise: Promise) {
76
+ SmartLinksBridgeApi.getPosts(communityId, authToken) { json ->
77
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "getPosts failed")
78
+ }
79
+ }
80
+
81
+ fun socialAuthGoogle(accessToken: String, project: String?, promise: Promise) {
82
+ SmartLinksBridgeApi.socialAuthGoogle(accessToken, project) { json -> promise.resolve(json) }
83
+ }
84
+
85
+ fun getBlogs(page: Int, search: String?, promise: Promise) {
86
+ SmartLinksBridgeApi.getBlogs(page, search) { json -> promise.resolve(json) }
87
+ }
88
+
89
+ fun getBlogPost(slug: String, promise: Promise) {
90
+ SmartLinksBridgeApi.getBlogPost(slug) { json -> promise.resolve(json) }
91
+ }
92
+
93
+ fun getCommunity(promise: Promise) {
94
+ SmartLinksBridgeApi.getCommunity { json ->
95
+ if (json != null) promise.resolve(json) else promise.reject("API_ERROR", "getCommunity failed")
96
+ }
97
+ }
98
+
99
+ fun togglePostLike(postId: Int, authToken: String, promise: Promise) {
100
+ SmartLinksBridgeApi.togglePostLike(postId, authToken) { json -> promise.resolve(json) }
101
+ }
102
+
103
+ fun createPost(
104
+ communityId: String,
105
+ title: String,
106
+ content: String,
107
+ authToken: String,
108
+ promise: Promise,
109
+ ) {
110
+ SmartLinksBridgeApi.createPost(communityId, title, content, authToken) { json ->
111
+ promise.resolve(json)
112
+ }
113
+ }
114
+
115
+ fun socialAuthApple(accessToken: String, project: String?, promise: Promise) {
116
+ SmartLinksBridgeApi.socialAuthApple(accessToken, project) { json -> promise.resolve(json) }
117
+ }
118
+
119
+ fun getComments(postId: String, authToken: String?, promise: Promise) {
120
+ SmartLinksBridgeApi.getComments(postId, authToken) { json -> promise.resolve(json) }
121
+ }
122
+
123
+ fun createComment(
124
+ postId: String,
125
+ content: String,
126
+ authToken: String,
127
+ parentId: String?,
128
+ promise: Promise,
129
+ ) {
130
+ SmartLinksBridgeApi.createComment(postId, content, authToken, parentId) { json ->
131
+ promise.resolve(json)
132
+ }
133
+ }
134
+
135
+ fun toggleCommentLike(commentId: String, authToken: String, promise: Promise) {
136
+ SmartLinksBridgeApi.toggleCommentLike(commentId, authToken) { json -> promise.resolve(json) }
137
+ }
138
+
139
+ fun fetchActiveNotificationAd(
140
+ appId: Int?,
141
+ language: String?,
142
+ isDebug: Boolean,
143
+ promise: Promise,
144
+ ) {
145
+ SmartLinksBridgeApi.fetchActiveNotificationAd(appId, language, isDebug) { json ->
146
+ promise.resolve(json)
147
+ }
148
+ }
149
+
150
+ fun registerDevice(
151
+ fcmToken: String,
152
+ platform: String?,
153
+ deviceType: String?,
154
+ appVersion: String?,
155
+ language: String?,
156
+ promise: Promise,
157
+ ) {
158
+ SmartLinksBridgeApi.registerDevice(
159
+ fcmToken, platform, deviceType, appVersion, language,
160
+ ) { json ->
161
+ promise.resolve(json)
162
+ }
163
+ }
164
+
165
+ fun trackNotificationAdImpression(adId: Int, promise: Promise) {
166
+ SmartLinksBridgeApi.trackNotificationAdImpression(adId) { json -> promise.resolve(json) }
167
+ }
168
+
169
+ fun trackNotificationAdClick(adId: Int, promise: Promise) {
170
+ SmartLinksBridgeApi.trackNotificationAdClick(adId) { json -> promise.resolve(json) }
171
+ }
172
+ }
@@ -0,0 +1,299 @@
1
+ package live.smartlinks.reactnative
2
+
3
+ import android.app.Activity
4
+ import android.content.Intent
5
+ import android.util.Log
6
+ import com.alkashier.smartlinks.SmartLinksIdentity
7
+ import com.alkashier.smartlinks.SmartLinksSdk
8
+ import com.alkashier.smartlinks.interfaces.OnFailureListener
9
+ import com.alkashier.smartlinks.interfaces.OnSuccessListener
10
+ import com.alkashier.smartlinks.models.PendingLinkData
11
+ import com.facebook.react.bridge.Arguments
12
+ import com.facebook.react.bridge.LifecycleEventListener
13
+ import com.facebook.react.bridge.Promise
14
+ import com.facebook.react.bridge.ReactApplicationContext
15
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
16
+ import com.facebook.react.bridge.ReactMethod
17
+ import com.facebook.react.bridge.ReadableMap
18
+ import com.facebook.react.bridge.WritableMap
19
+ import com.facebook.react.modules.core.DeviceEventManagerModule
20
+
21
+ class SmartLinksModule(private val reactContext: ReactApplicationContext) :
22
+ ReactContextBaseJavaModule(reactContext),
23
+ LifecycleEventListener {
24
+
25
+ private var linkListenerRegistered = false
26
+ private var pendingIntent: Intent? = null
27
+
28
+ init {
29
+ reactContext.addLifecycleEventListener(this)
30
+ registerActivityLifecycle(reactContext, this)
31
+ }
32
+
33
+ override fun getName(): String = "SmartLinks"
34
+
35
+ @ReactMethod
36
+ fun addListener(eventName: String) {
37
+ // Required for NativeEventEmitter on Android
38
+ }
39
+
40
+ @ReactMethod
41
+ fun removeListeners(count: Int) {
42
+ // Required for NativeEventEmitter on Android
43
+ }
44
+
45
+ @ReactMethod
46
+ fun initialize(apiKey: String, debugMode: Boolean, promise: Promise) {
47
+ try {
48
+ SmartLinksSdk.initialize(apiKey, reactContext.applicationContext, debugMode)
49
+ promise.resolve(true)
50
+ } catch (e: Exception) {
51
+ promise.reject("INIT_ERROR", e.message, e)
52
+ }
53
+ }
54
+
55
+ @ReactMethod
56
+ fun getAppUserId(promise: Promise) {
57
+ promise.resolve(SmartLinksIdentity.getAppUserId())
58
+ }
59
+
60
+ @ReactMethod
61
+ fun sendEvent(eventName: String, params: ReadableMap?, promise: Promise) {
62
+ try {
63
+ val map = params?.toHashMap()?.mapValues { it.value ?: "" } ?: emptyMap()
64
+ SmartLinksSdk.sendEvent(eventName, map, emptyMap(), reactContext.applicationContext)
65
+ promise.resolve(null)
66
+ } catch (e: Exception) {
67
+ promise.reject("EVENT_ERROR", e.message, e)
68
+ }
69
+ }
70
+
71
+ @ReactMethod
72
+ fun generateLink(route: String, params: ReadableMap?, shortLink: Boolean, promise: Promise) {
73
+ val map = params?.toHashMap()?.mapValues { it.value ?: "" } ?: emptyMap()
74
+ SmartLinksSdk.generateDynamicLink(route, map, shortLink) { link ->
75
+ promise.resolve(link)
76
+ }
77
+ }
78
+
79
+ @ReactMethod
80
+ fun registerLinkListener(promise: Promise) {
81
+ linkListenerRegistered = true
82
+ val activity = currentActivity
83
+ if (activity != null) {
84
+ handleIntent(activity.intent)
85
+ } else {
86
+ pendingIntent?.let { handleIntent(it) }
87
+ }
88
+ promise.resolve(null)
89
+ }
90
+
91
+ @ReactMethod
92
+ fun fetchApps(promise: Promise) {
93
+ SmartLinksBridgeHandler.fetchApps(promise)
94
+ }
95
+
96
+ @ReactMethod
97
+ fun submitFeedback(args: ReadableMap, promise: Promise) {
98
+ SmartLinksBridgeHandler.submitFeedback(args, promise)
99
+ }
100
+
101
+ @ReactMethod
102
+ fun fetchAds(type: String?, promise: Promise) {
103
+ SmartLinksBridgeHandler.fetchAds(type, promise)
104
+ }
105
+
106
+ @ReactMethod
107
+ fun trackAdImpression(adId: Int, promise: Promise) {
108
+ SmartLinksBridgeHandler.trackAdImpression(adId, promise)
109
+ }
110
+
111
+ @ReactMethod
112
+ fun trackAdClick(adId: Int, promise: Promise) {
113
+ SmartLinksBridgeHandler.trackAdClick(adId, promise)
114
+ }
115
+
116
+ @ReactMethod
117
+ fun processAi(instructionsJson: String, files: ReadableMap?, promise: Promise) {
118
+ val fileMap = files?.toHashMap()?.mapNotNull { (key, value) ->
119
+ value?.toString()?.let { key to it }
120
+ }?.toMap() ?: emptyMap()
121
+ SmartLinksBridgeHandler.processAi(instructionsJson, fileMap, promise)
122
+ }
123
+
124
+ @ReactMethod
125
+ fun validatePromoCode(code: String, promise: Promise) {
126
+ SmartLinksBridgeHandler.validatePromoCode(code, promise)
127
+ }
128
+
129
+ @ReactMethod
130
+ fun redeemPromoCode(code: String, promise: Promise) {
131
+ SmartLinksBridgeHandler.redeemPromoCode(code, promise)
132
+ }
133
+
134
+ @ReactMethod
135
+ fun verifyGooglePurchase(productId: String, token: String, promise: Promise) {
136
+ SmartLinksBridgeHandler.verifyGooglePurchase(productId, token, promise)
137
+ }
138
+
139
+ @ReactMethod
140
+ fun verifyApplePurchase(receiptData: String, promise: Promise) {
141
+ SmartLinksBridgeHandler.verifyApplePurchase(receiptData, promise)
142
+ }
143
+
144
+ @ReactMethod
145
+ fun getCommunities(promise: Promise) {
146
+ SmartLinksBridgeHandler.getCommunities(promise)
147
+ }
148
+
149
+ @ReactMethod
150
+ fun getPosts(communityId: Int, authToken: String?, promise: Promise) {
151
+ SmartLinksBridgeHandler.getPosts(communityId, authToken, promise)
152
+ }
153
+
154
+ @ReactMethod
155
+ fun socialAuthGoogle(accessToken: String, project: String?, promise: Promise) {
156
+ SmartLinksBridgeHandler.socialAuthGoogle(accessToken, project, promise)
157
+ }
158
+
159
+ @ReactMethod
160
+ fun getBlogs(page: Int, search: String?, promise: Promise) {
161
+ SmartLinksBridgeHandler.getBlogs(page, search, promise)
162
+ }
163
+
164
+ @ReactMethod
165
+ fun getBlogPost(slug: String, promise: Promise) {
166
+ SmartLinksBridgeHandler.getBlogPost(slug, promise)
167
+ }
168
+
169
+ @ReactMethod
170
+ fun getCommunity(promise: Promise) {
171
+ SmartLinksBridgeHandler.getCommunity(promise)
172
+ }
173
+
174
+ @ReactMethod
175
+ fun togglePostLike(postId: Int, authToken: String, promise: Promise) {
176
+ SmartLinksBridgeHandler.togglePostLike(postId, authToken, promise)
177
+ }
178
+
179
+ @ReactMethod
180
+ fun createPost(
181
+ communityId: String,
182
+ title: String,
183
+ content: String,
184
+ authToken: String,
185
+ promise: Promise,
186
+ ) {
187
+ SmartLinksBridgeHandler.createPost(communityId, title, content, authToken, promise)
188
+ }
189
+
190
+ @ReactMethod
191
+ fun socialAuthApple(accessToken: String, project: String?, promise: Promise) {
192
+ SmartLinksBridgeHandler.socialAuthApple(accessToken, project, promise)
193
+ }
194
+
195
+ @ReactMethod
196
+ fun getComments(postId: String, authToken: String?, promise: Promise) {
197
+ SmartLinksBridgeHandler.getComments(postId, authToken, promise)
198
+ }
199
+
200
+ @ReactMethod
201
+ fun createComment(
202
+ postId: String,
203
+ content: String,
204
+ authToken: String,
205
+ parentId: String?,
206
+ promise: Promise,
207
+ ) {
208
+ SmartLinksBridgeHandler.createComment(postId, content, authToken, parentId, promise)
209
+ }
210
+
211
+ @ReactMethod
212
+ fun toggleCommentLike(commentId: String, authToken: String, promise: Promise) {
213
+ SmartLinksBridgeHandler.toggleCommentLike(commentId, authToken, promise)
214
+ }
215
+
216
+ @ReactMethod
217
+ fun fetchActiveNotificationAd(
218
+ appId: Int?,
219
+ language: String?,
220
+ isDebug: Boolean,
221
+ promise: Promise,
222
+ ) {
223
+ SmartLinksBridgeHandler.fetchActiveNotificationAd(appId, language, isDebug, promise)
224
+ }
225
+
226
+ @ReactMethod
227
+ fun registerDevice(
228
+ fcmToken: String,
229
+ platform: String?,
230
+ deviceType: String?,
231
+ appVersion: String?,
232
+ language: String?,
233
+ promise: Promise,
234
+ ) {
235
+ SmartLinksBridgeHandler.registerDevice(
236
+ fcmToken, platform, deviceType, appVersion, language, promise,
237
+ )
238
+ }
239
+
240
+ @ReactMethod
241
+ fun trackNotificationAdImpression(adId: Int, promise: Promise) {
242
+ SmartLinksBridgeHandler.trackNotificationAdImpression(adId, promise)
243
+ }
244
+
245
+ @ReactMethod
246
+ fun trackNotificationAdClick(adId: Int, promise: Promise) {
247
+ SmartLinksBridgeHandler.trackNotificationAdClick(adId, promise)
248
+ }
249
+
250
+ override fun onHostResume() {
251
+ currentActivity?.intent?.let { handleIntent(it) }
252
+ }
253
+
254
+ override fun onHostPause() {}
255
+ override fun onHostDestroy() {}
256
+
257
+ fun onNewIntent(intent: Intent?) {
258
+ if (intent == null) return
259
+ if (!linkListenerRegistered) {
260
+ pendingIntent = intent
261
+ return
262
+ }
263
+ handleIntent(intent)
264
+ }
265
+
266
+ private fun handleIntent(intent: Intent?) {
267
+ SmartLinksSdk.getLink(intent)
268
+ .addOnSuccessListener(object : OnSuccessListener<PendingLinkData> {
269
+ override fun onSuccess(result: PendingLinkData?) {
270
+ pendingIntent = null
271
+ if (result != null) {
272
+ emitDeepLink(result)
273
+ }
274
+ }
275
+ })
276
+ .addOnFailureListener(object : OnFailureListener {
277
+ override fun onFailure(exception: Exception) {
278
+ Log.e(TAG, "getLink failed", exception)
279
+ }
280
+ })
281
+ }
282
+
283
+ private fun emitDeepLink(result: PendingLinkData) {
284
+ val map: WritableMap = Arguments.createMap()
285
+ map.putString("route", result.path?.toString() ?: "")
286
+ map.putString("link", result.link?.toString() ?: "")
287
+ map.putString("appSlug", result.appSlug ?: "")
288
+ val params = Arguments.createMap()
289
+ result.parameters?.forEach { (key, value) -> params.putString(key, value) }
290
+ map.putMap("params", params)
291
+ reactContext
292
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
293
+ .emit("SmartLinksDeepLink", map)
294
+ }
295
+
296
+ companion object {
297
+ private const val TAG = "SmartLinksRN"
298
+ }
299
+ }
@@ -0,0 +1,16 @@
1
+ package live.smartlinks.reactnative
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 SmartLinksPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(SmartLinksModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,114 @@
1
+ import UIKit
2
+ import Foundation
3
+ import CryptoKit
4
+ import CommonCrypto
5
+
6
+ struct DeviceInfo {
7
+ let platform: String
8
+ let osVersion: String
9
+ let deviceModel: String
10
+ let manufacturer: String
11
+ let brand: String
12
+ let appVersion: String
13
+ let appBundleId: String
14
+ let language: String
15
+ let timezone: String
16
+ let screenResolution: String
17
+ let networkType: String
18
+ let sdkVersion: String
19
+ let apiVersion: String
20
+
21
+ static func fromCurrentDevice(sdkVersion: String, apiVersion: String, networkType: String = "unknown") -> DeviceInfo {
22
+ let mainBundle = Bundle.main
23
+ let screen = UIScreen.main
24
+ let resolution = "\(Int(screen.nativeBounds.width))x\(Int(screen.nativeBounds.height))"
25
+
26
+ return DeviceInfo(
27
+ platform: "ios",
28
+ osVersion: UIDevice.current.systemVersion,
29
+ deviceModel: UIDevice.current.model,
30
+ manufacturer: "Apple",
31
+ brand: UIDevice.current.model,
32
+ appVersion: mainBundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown",
33
+ appBundleId: mainBundle.bundleIdentifier ?? "unknown",
34
+ language: Locale.current.languageCode ?? "en",
35
+ timezone: TimeZone.current.identifier,
36
+ screenResolution: resolution,
37
+ networkType: networkType,
38
+ sdkVersion: sdkVersion,
39
+ apiVersion: apiVersion
40
+ )
41
+ }
42
+
43
+ func toMap() -> [String: String] {
44
+ return [
45
+ "platform": platform,
46
+ "os_version": osVersion,
47
+ "device_model": deviceModel,
48
+ "manufacturer": manufacturer,
49
+ "brand": brand,
50
+ "app_version": appVersion,
51
+ "app_bundle_id": appBundleId,
52
+ "language": language,
53
+ "timezone": timezone,
54
+ "screen_resolution": screenResolution,
55
+ "network_type": networkType,
56
+ "sdk_version": sdkVersion,
57
+ "api_version": apiVersion
58
+ ]
59
+ }
60
+
61
+
62
+ private func fetchPublicIP(completion: @escaping (String?) -> Void) {
63
+ guard let url = URL(string: "https://api.ipify.org?format=text") else {
64
+ completion(nil)
65
+ return
66
+ }
67
+
68
+ let task = URLSession.shared.dataTask(with: url) { data, _, _ in
69
+ let ip = data.flatMap { String(data: $0, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) }
70
+ completion(ip)
71
+ }
72
+ task.resume()
73
+ }
74
+
75
+
76
+ func generateFingerprint(completion: @escaping (String?) -> Void) {
77
+
78
+ fetchPublicIP { ipAddress in
79
+ guard let ip = ipAddress else {
80
+ completion(nil)
81
+ return
82
+ }
83
+
84
+ // Safely get bundle identifier (fallback if nil)
85
+ let appBundleId = Bundle.main.bundleIdentifier ?? "unknown.bundle"
86
+
87
+
88
+ // Simplified fingerprinting to match Server Side logic consistently.
89
+ // Format: IP | BundleID | Platform (normalized to 'ios')
90
+ let platform = "ios"
91
+ let fingerprintString = "\(ip)|\(appBundleId)|\(platform)"
92
+
93
+ // Debug logging of fingerprint components
94
+ print("Linklytics: Fingerprint components:")
95
+ print(" - Combined string: \(fingerprintString)")
96
+
97
+ // Convert string to Data
98
+ let data = Data(fingerprintString.utf8)
99
+
100
+ // Allocate buffer for SHA256 digest
101
+ var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
102
+ data.withUnsafeBytes {
103
+ _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
104
+ }
105
+
106
+ // Convert hash to hex string
107
+ let hashString = hash.map { String(format: "%02x", $0) }.joined()
108
+
109
+ // Return first 32 characters
110
+ completion(String(hashString.prefix(32)))
111
+ }
112
+ }
113
+
114
+ }