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.
- package/android/build.gradle +33 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksActivityLifecycle.kt +34 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksBridgeHandler.kt +172 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksModule.kt +299 -0
- package/android/src/main/java/live/smartlinks/reactnative/SmartLinksPackage.kt +16 -0
- package/ios/Linklytics/DeviceInfo.swift +114 -0
- package/ios/Linklytics/Linklytics.swift +581 -0
- package/ios/Linklytics/LinklyticsInterface.swift +10 -0
- package/ios/Linklytics/Managers/BatchEventManager.swift +218 -0
- package/ios/Linklytics/Managers/OfflineCache.swift +35 -0
- package/ios/Linklytics/Managers/SQLiteStorage.swift +225 -0
- package/ios/Linklytics/Models/AppResponse.swift +16 -0
- package/ios/Linklytics/Models/DeepLinkRoute.swift +24 -0
- package/ios/Linklytics/Models/DeferredLinkResponse.swift +81 -0
- package/ios/Linklytics/Models/DynamicLinkResponse.swift +13 -0
- package/ios/Linklytics/Models/EventResponse.swift +14 -0
- package/ios/Linklytics/Models/OfflineEvent.swift +80 -0
- package/ios/Linklytics/NetworkManager.swift +360 -0
- package/ios/Linklytics/SdkConstants.swift +29 -0
- package/ios/PrivacyInfo.xcprivacy +14 -0
- package/ios/SmartLinksModule.m +150 -0
- package/ios/SmartLinksModule.swift +281 -0
- package/package.json +41 -0
- package/react-native-smartlinks.podspec +27 -0
- package/react-native.config.js +12 -0
- 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,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
|
+
}
|