@amplytools/react-native-amply-sdk 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/LICENSE +178 -0
- package/README.md +714 -0
- package/android/build.gradle +90 -0
- package/android/consumer-rules.pro +1 -0
- package/android/gradle.properties +3 -0
- package/android/settings.gradle +9 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +384 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyPackage.kt +39 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +30 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +296 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt +10 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetType.kt +42 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetTypeMapper.kt +38 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DeepLinkPayload.kt +8 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/EventEnvelope.kt +9 -0
- package/android/src/main/jni/AmplyTurboModule.cpp +29 -0
- package/android/src/main/jni/CMakeLists.txt +76 -0
- package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +75 -0
- package/android/src/newarch/jni/AmplyReactNative-generated.cpp +77 -0
- package/android/src/newarch/jni/AmplyReactNative.h +31 -0
- package/android/src/newarch/jni/CMakeLists.txt +40 -0
- package/app.plugin.js +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +234 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin/index.d.ts +6 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +186 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/index.mjs +169 -0
- package/dist/plugin/index.mjs.map +1 -0
- package/dist/plugin/src/index.d.ts +6 -0
- package/dist/plugin/src/index.d.ts.map +1 -0
- package/dist/plugin/src/index.js +3 -0
- package/dist/plugin/src/withAmply.d.ts +30 -0
- package/dist/plugin/src/withAmply.d.ts.map +1 -0
- package/dist/plugin/src/withAmply.js +51 -0
- package/dist/plugin/withAmply.d.ts +12 -0
- package/dist/plugin/withAmply.d.ts.map +1 -0
- package/dist/src/__tests__/index.test.d.ts +2 -0
- package/dist/src/__tests__/index.test.d.ts.map +1 -0
- package/dist/src/__tests__/index.test.js +70 -0
- package/dist/src/hooks/useAmplySystemEvents.d.ts +12 -0
- package/dist/src/hooks/useAmplySystemEvents.d.ts.map +1 -0
- package/dist/src/hooks/useAmplySystemEvents.js +56 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +80 -0
- package/dist/src/nativeModule.d.ts +5 -0
- package/dist/src/nativeModule.d.ts.map +1 -0
- package/dist/src/nativeModule.js +48 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +75 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.js +2 -0
- package/dist/src/systemEventUtils.d.ts +3 -0
- package/dist/src/systemEventUtils.d.ts.map +1 -0
- package/dist/src/systemEventUtils.js +30 -0
- package/dist/src/systemEvents.d.ts +6 -0
- package/dist/src/systemEvents.d.ts.map +1 -0
- package/dist/src/systemEvents.js +8 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/ARCHITECTURE.md +1115 -0
- package/expo-module.config.json +11 -0
- package/ios/AmplyReactNative.podspec +32 -0
- package/ios/README.md +11 -0
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +332 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +111 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +152 -0
- package/package.json +71 -0
- package/plugin/build/index.d.ts +5 -0
- package/plugin/build/index.js +8 -0
- package/plugin/build/withAmply.d.ts +29 -0
- package/plugin/build/withAmply.js +53 -0
- package/plugin/src/index.ts +7 -0
- package/plugin/src/withAmply.ts +68 -0
- package/plugin/tsconfig.json +8 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/react-native.config.js +34 -0
- package/scripts/codegen.js +212 -0
- package/src/__tests__/index.test.ts +92 -0
- package/src/hooks/useAmplySystemEvents.ts +75 -0
- package/src/index.ts +115 -0
- package/src/nativeModule.ts +65 -0
- package/src/nativeSpecs/NativeAmplyModule.ts +80 -0
- package/src/systemEventUtils.ts +35 -0
- package/src/systemEvents.ts +13 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext {
|
|
3
|
+
kotlin_version = "1.9.24"
|
|
4
|
+
}
|
|
5
|
+
repositories {
|
|
6
|
+
google()
|
|
7
|
+
mavenCentral()
|
|
8
|
+
}
|
|
9
|
+
dependencies {
|
|
10
|
+
classpath("com.android.tools.build:gradle:8.2.2")
|
|
11
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
apply plugin: 'com.android.library'
|
|
16
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
17
|
+
|
|
18
|
+
def amplySdkVersion = project.findProperty('amplySdkVersion') ?: System.getenv('AMPLY_SDK_VERSION') ?: '0.1.7'
|
|
19
|
+
group = 'tools.amply'
|
|
20
|
+
version = amplySdkVersion
|
|
21
|
+
|
|
22
|
+
def reactNativeRootDir = file("$projectDir/../node_modules/react-native")
|
|
23
|
+
def reactNativeArchitectures =
|
|
24
|
+
project.hasProperty("reactNativeArchitectures") ?
|
|
25
|
+
project.getProperty("reactNativeArchitectures").split(",") :
|
|
26
|
+
["armeabi-v7a", "arm64-v8a", "x86", "x86_64"]
|
|
27
|
+
|
|
28
|
+
android {
|
|
29
|
+
namespace 'tools.amply.sdk.reactnative'
|
|
30
|
+
compileSdkVersion 34
|
|
31
|
+
|
|
32
|
+
defaultConfig {
|
|
33
|
+
minSdkVersion 24
|
|
34
|
+
targetSdkVersion 34
|
|
35
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
36
|
+
|
|
37
|
+
ndk {
|
|
38
|
+
abiFilters(*reactNativeArchitectures)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Передаём путь в RN CMake (оба CMakeLists.txt используют это)
|
|
42
|
+
externalNativeBuild {
|
|
43
|
+
cmake {
|
|
44
|
+
arguments "-DREACT_NATIVE_DIR=${reactNativeRootDir.absolutePath}", "-DANDROID_STL=c++_shared"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
compileOptions {
|
|
50
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
51
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
kotlinOptions {
|
|
55
|
+
jvmTarget = '17'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
lintOptions {
|
|
59
|
+
abortOnError false
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
buildFeatures {
|
|
63
|
+
prefab true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
sourceSets {
|
|
67
|
+
main {
|
|
68
|
+
java.srcDirs += ["src/newarch/java"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
externalNativeBuild {
|
|
73
|
+
cmake {
|
|
74
|
+
path file("src/main/jni/CMakeLists.txt")
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
repositories {
|
|
80
|
+
mavenLocal()
|
|
81
|
+
google()
|
|
82
|
+
mavenCentral()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
dependencies {
|
|
86
|
+
implementation("com.facebook.react:react-android")
|
|
87
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
|
88
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
|
|
89
|
+
implementation("tools.amply:sdk-android:${amplySdkVersion}")
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Add consumer ProGuard rules if Amply SDK requires adjustments.
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
package tools.amply.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import tools.amply.sdk.reactnative.core.AmplyClient
|
|
5
|
+
import tools.amply.sdk.reactnative.core.DefaultAmplyClient
|
|
6
|
+
import tools.amply.sdk.reactnative.model.AmplyInitializationOptions
|
|
7
|
+
import tools.amply.sdk.reactnative.model.DataSetType
|
|
8
|
+
import tools.amply.sdk.reactnative.model.DataSetType.EventParam
|
|
9
|
+
import tools.amply.sdk.reactnative.model.DataSetType.Events
|
|
10
|
+
import tools.amply.sdk.reactnative.model.DataSetType.TriggeredEvent
|
|
11
|
+
import tools.amply.sdk.reactnative.model.EventEnvelope
|
|
12
|
+
import tools.amply.sdk.reactnative.NativeAmplyModuleSpec
|
|
13
|
+
import com.facebook.react.bridge.Arguments
|
|
14
|
+
import com.facebook.react.bridge.Dynamic
|
|
15
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
16
|
+
import com.facebook.react.bridge.Promise
|
|
17
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
18
|
+
import com.facebook.react.bridge.ReadableArray
|
|
19
|
+
import com.facebook.react.bridge.ReadableMap
|
|
20
|
+
import com.facebook.react.bridge.ReadableType
|
|
21
|
+
import com.facebook.react.bridge.WritableArray
|
|
22
|
+
import com.facebook.react.bridge.WritableMap
|
|
23
|
+
import kotlinx.coroutines.CoroutineScope
|
|
24
|
+
import kotlinx.coroutines.Dispatchers
|
|
25
|
+
import kotlinx.coroutines.Job
|
|
26
|
+
import kotlinx.coroutines.SupervisorJob
|
|
27
|
+
import kotlinx.coroutines.cancel
|
|
28
|
+
import kotlinx.coroutines.flow.collectLatest
|
|
29
|
+
import kotlinx.coroutines.launch
|
|
30
|
+
import com.facebook.soloader.SoLoader
|
|
31
|
+
|
|
32
|
+
class AmplyModule(reactContext: ReactApplicationContext) :
|
|
33
|
+
NativeAmplyModuleSpec(reactContext), LifecycleEventListener {
|
|
34
|
+
|
|
35
|
+
private val client: AmplyClient =
|
|
36
|
+
DefaultAmplyClient(reactApplicationContext.applicationContext as Application)
|
|
37
|
+
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
|
38
|
+
|
|
39
|
+
private var deepLinkJob: Job? = null
|
|
40
|
+
private var lastEmittedDeepLinkId: Long? = null
|
|
41
|
+
private var lifecycleRegistered = false
|
|
42
|
+
private var systemEventsJob: Job? = null
|
|
43
|
+
|
|
44
|
+
override fun getName(): String = NAME
|
|
45
|
+
|
|
46
|
+
override fun initialize() {
|
|
47
|
+
super.initialize()
|
|
48
|
+
if (!lifecycleRegistered) {
|
|
49
|
+
reactApplicationContext.addLifecycleEventListener(this)
|
|
50
|
+
lifecycleRegistered = true
|
|
51
|
+
}
|
|
52
|
+
client.onHostResume(reactApplicationContext.currentActivity)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---- methods, объявленные в NativeAmplyModuleSpec, реализация твоя ----
|
|
56
|
+
|
|
57
|
+
override fun initialize(config: ReadableMap, promise: Promise) {
|
|
58
|
+
scope.launch {
|
|
59
|
+
try {
|
|
60
|
+
client.initialize(config.toInitializationOptions())
|
|
61
|
+
ensureSystemEventCollection()
|
|
62
|
+
promise.resolve(null)
|
|
63
|
+
} catch (throwable: Throwable) {
|
|
64
|
+
promise.reject(INIT_ERROR, throwable)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun isInitialized(): Boolean = client.isInitialized()
|
|
70
|
+
|
|
71
|
+
override fun track(payload: ReadableMap, promise: Promise) {
|
|
72
|
+
val name = payload.getString("name")
|
|
73
|
+
if (name.isNullOrBlank()) {
|
|
74
|
+
promise.reject(ARGUMENT_ERROR, "Event 'name' is required")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
val properties = payload.getMap("properties")?.toHashMap()
|
|
79
|
+
|
|
80
|
+
scope.launch {
|
|
81
|
+
try {
|
|
82
|
+
client.track(name, properties)
|
|
83
|
+
promise.resolve(null)
|
|
84
|
+
} catch (throwable: Throwable) {
|
|
85
|
+
promise.reject(TRACK_ERROR, throwable)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override fun getRecentEvents(limit: Double, promise: Promise) {
|
|
91
|
+
scope.launch {
|
|
92
|
+
try {
|
|
93
|
+
val events = client.getRecentEvents(limit.toInt())
|
|
94
|
+
promise.resolve(events.toWritableArray())
|
|
95
|
+
} catch (throwable: Throwable) {
|
|
96
|
+
promise.reject(EVENTS_ERROR, throwable)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun getDataSetSnapshot(type: ReadableMap, promise: Promise) {
|
|
102
|
+
val datasetType = try {
|
|
103
|
+
type.toDataSetType()
|
|
104
|
+
} catch (error: IllegalArgumentException) {
|
|
105
|
+
promise.reject(ARGUMENT_ERROR, error)
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
scope.launch {
|
|
110
|
+
try {
|
|
111
|
+
val snapshot = client.getDataSetSnapshot(datasetType)
|
|
112
|
+
promise.resolve(snapshot.toWritableMap())
|
|
113
|
+
} catch (throwable: Throwable) {
|
|
114
|
+
promise.reject(DATASET_ERROR, throwable)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override fun registerDeepLinkListener() {
|
|
120
|
+
if (deepLinkJob == null) {
|
|
121
|
+
deepLinkJob = scope.launch {
|
|
122
|
+
client.deepLinkEvents.collectLatest { payload ->
|
|
123
|
+
if (payload.sequenceId == lastEmittedDeepLinkId) {
|
|
124
|
+
return@collectLatest
|
|
125
|
+
}
|
|
126
|
+
lastEmittedDeepLinkId = payload.sequenceId
|
|
127
|
+
android.util.Log.i(
|
|
128
|
+
TAG,
|
|
129
|
+
"Emitting deep link to JS sequenceId=${payload.sequenceId} url=${payload.url}"
|
|
130
|
+
)
|
|
131
|
+
emitDeepLink(payload.url, payload.info, payload.consumed)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
android.util.Log.i(TAG, "Started deep link collection job")
|
|
135
|
+
}
|
|
136
|
+
client.registerDeepLinkListener()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
override fun addListener(eventName: String) {
|
|
140
|
+
// Required by RN EventEmitter contracts. The native TurboModule infrastructure
|
|
141
|
+
// handles the actual listener bookkeeping through the C++ event emitter.
|
|
142
|
+
// This method is called by the TurboModule infrastructure but we don't need
|
|
143
|
+
// to do anything here as event emission is managed at the C++ level.
|
|
144
|
+
android.util.Log.d(TAG, "addListener called for event: $eventName")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override fun removeListeners(count: Double) {
|
|
148
|
+
// Required by RN EventEmitter contracts. The native TurboModule infrastructure
|
|
149
|
+
// handles removing listeners through the C++ event emitter.
|
|
150
|
+
// This is called when JavaScript explicitly removes listeners.
|
|
151
|
+
android.util.Log.d(TAG, "removeListeners called with count: $count")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---- lifecycle / cleanup ----
|
|
155
|
+
|
|
156
|
+
override fun invalidate() {
|
|
157
|
+
super.invalidate()
|
|
158
|
+
if (lifecycleRegistered) {
|
|
159
|
+
reactApplicationContext.removeLifecycleEventListener(this)
|
|
160
|
+
lifecycleRegistered = false
|
|
161
|
+
}
|
|
162
|
+
scope.cancel()
|
|
163
|
+
deepLinkJob = null
|
|
164
|
+
systemEventsJob?.cancel()
|
|
165
|
+
systemEventsJob = null
|
|
166
|
+
lastEmittedDeepLinkId = null
|
|
167
|
+
client.shutdown()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun emitDeepLink(url: String, info: Map<String, Any?>, consumed: Boolean) {
|
|
171
|
+
val map = Arguments.createMap().apply {
|
|
172
|
+
putString("url", url)
|
|
173
|
+
putMap("info", info.toWritableMap())
|
|
174
|
+
putBoolean("consumed", consumed)
|
|
175
|
+
}
|
|
176
|
+
// метод emitOnDeepLink приходит из NativeAmplyModuleSpec (codegen)
|
|
177
|
+
android.util.Log.d(TAG, "emitDeepLink called: url=$url, consumed=$consumed")
|
|
178
|
+
emitOnDeepLink(map)
|
|
179
|
+
android.util.Log.d(TAG, "emitOnDeepLink completed")
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun emitSystemEvent(event: EventEnvelope) {
|
|
183
|
+
emitOnSystemEvent(event.toWritableMap())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private fun ensureSystemEventCollection() {
|
|
187
|
+
if (systemEventsJob == null) {
|
|
188
|
+
systemEventsJob = scope.launch {
|
|
189
|
+
client.systemEvents.collectLatest { event ->
|
|
190
|
+
emitSystemEvent(event)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
client.registerSystemEventListener()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---- твои helper-методы без изменений ----
|
|
198
|
+
|
|
199
|
+
private fun ReadableMap.toInitializationOptions(): AmplyInitializationOptions {
|
|
200
|
+
val appId = getString("appId") ?: throw IllegalArgumentException("'appId' is required")
|
|
201
|
+
val apiKeyPublic = getString("apiKeyPublic") ?: throw IllegalArgumentException("'apiKeyPublic' is required")
|
|
202
|
+
|
|
203
|
+
val apiKeySecret = if (hasKey("apiKeySecret")) getString("apiKeySecret") else null
|
|
204
|
+
val endpoint = if (hasKey("endpoint")) getString("endpoint") else null
|
|
205
|
+
val datasetPrefetch = if (hasKey("datasetPrefetch")) {
|
|
206
|
+
getArray("datasetPrefetch")?.toDataSetTypes()
|
|
207
|
+
} else {
|
|
208
|
+
null
|
|
209
|
+
}
|
|
210
|
+
val defaultConfig = if (hasKey("defaultConfig")) getString("defaultConfig") else null
|
|
211
|
+
|
|
212
|
+
return AmplyInitializationOptions(
|
|
213
|
+
appId = appId,
|
|
214
|
+
apiKeyPublic = apiKeyPublic,
|
|
215
|
+
apiKeySecret = apiKeySecret,
|
|
216
|
+
endpoint = endpoint,
|
|
217
|
+
datasetPrefetch = datasetPrefetch,
|
|
218
|
+
defaultConfig = defaultConfig,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun ReadableArray.toDataSetTypes(): List<DataSetType> = buildList {
|
|
223
|
+
for (index in 0 until size()) {
|
|
224
|
+
val map = getMap(index) ?: continue
|
|
225
|
+
add(map.toDataSetType())
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private fun ReadableMap.toDataSetType(): DataSetType {
|
|
230
|
+
val kind = getString("kind") ?: throw IllegalArgumentException("DataSetType.kind is required")
|
|
231
|
+
return when (kind) {
|
|
232
|
+
"@device" -> DataSetType.Device
|
|
233
|
+
"@user" -> DataSetType.User
|
|
234
|
+
"@session" -> DataSetType.Session
|
|
235
|
+
"@triggeredEvent" -> {
|
|
236
|
+
val data = getMap("data") ?: throw IllegalArgumentException("TriggeredEvent data is required")
|
|
237
|
+
val countStrategy = TriggeredEvent.CountStrategy.fromWireName(data.getString("countStrategy"))
|
|
238
|
+
?: throw IllegalArgumentException("TriggeredEvent.countStrategy is required")
|
|
239
|
+
val params = data.getArray("params")?.toEventParams().orEmpty()
|
|
240
|
+
val eventName = if (data.hasKey("eventName")) data.getString("eventName") else null
|
|
241
|
+
DataSetType.TriggeredEvent(countStrategy, params, eventName)
|
|
242
|
+
}
|
|
243
|
+
"@events" -> {
|
|
244
|
+
val data = getArray("data") ?: throw IllegalArgumentException("Events data list is required")
|
|
245
|
+
DataSetType.Events(data.toEvents())
|
|
246
|
+
}
|
|
247
|
+
else -> throw IllegalArgumentException("Unsupported DataSetType kind: $kind")
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private fun ReadableArray.toEventParams(): List<EventParam> = buildList {
|
|
252
|
+
for (index in 0 until size()) {
|
|
253
|
+
val map = getMap(index) ?: continue
|
|
254
|
+
val name = map.getString("name") ?: continue
|
|
255
|
+
val value = if (map.hasKey("value")) map.getDynamic("value")?.toAny() else null
|
|
256
|
+
add(EventParam(name, value))
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private fun ReadableArray.toEvents(): List<Events.Event> = buildList {
|
|
261
|
+
for (index in 0 until size()) {
|
|
262
|
+
val map = getMap(index) ?: continue
|
|
263
|
+
val name = map.getString("name") ?: continue
|
|
264
|
+
val type = Events.EventType.fromWireName(map.getString("type")) ?: Events.EventType.CUSTOM
|
|
265
|
+
val params = map.getArray("params")?.toEventParams().orEmpty()
|
|
266
|
+
add(Events.Event(name, type, params))
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private fun EventEnvelope.toWritableMap(): WritableMap =
|
|
271
|
+
Arguments.createMap().apply {
|
|
272
|
+
id?.let { putString("id", it) }
|
|
273
|
+
putString("name", name)
|
|
274
|
+
putString("type", type)
|
|
275
|
+
putDouble("timestamp", timestamp.toDouble())
|
|
276
|
+
putMap("properties", properties.toWritableMap())
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private fun List<EventEnvelope>.toWritableArray(): WritableArray {
|
|
280
|
+
val array = Arguments.createArray()
|
|
281
|
+
forEach { event ->
|
|
282
|
+
array.pushMap(event.toWritableMap())
|
|
283
|
+
}
|
|
284
|
+
return array
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private fun Map<String, Any?>.toWritableMap(): WritableMap = Arguments.createMap().also { map ->
|
|
288
|
+
forEach { (key, value) -> map.putDynamic(key, value) }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun List<*>.toWritableDynamicArray(): WritableArray = Arguments.createArray().also { array ->
|
|
292
|
+
forEach { array.pushDynamic(it) }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private fun WritableMap.putDynamic(key: String, value: Any?) {
|
|
296
|
+
when (value) {
|
|
297
|
+
null -> putNull(key)
|
|
298
|
+
is Boolean -> putBoolean(key, value)
|
|
299
|
+
is Number -> putDouble(key, value.toDouble())
|
|
300
|
+
is String -> putString(key, value)
|
|
301
|
+
is Map<*, *> -> putMap(key, (value as? Map<String, Any?>)?.toWritableMap())
|
|
302
|
+
is List<*> -> putArray(key, value.toWritableDynamicArray())
|
|
303
|
+
else -> putString(key, value.toString())
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private fun WritableArray.pushDynamic(value: Any?) {
|
|
308
|
+
when (value) {
|
|
309
|
+
null -> pushNull()
|
|
310
|
+
is Boolean -> pushBoolean(value)
|
|
311
|
+
is Number -> pushDouble(value.toDouble())
|
|
312
|
+
is String -> pushString(value)
|
|
313
|
+
is Map<*, *> -> pushMap((value as? Map<String, Any?>)?.toWritableMap())
|
|
314
|
+
is List<*> -> pushArray(value.toWritableDynamicArray())
|
|
315
|
+
else -> pushString(value.toString())
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private fun Dynamic.toAny(): Any? =
|
|
320
|
+
when (type) {
|
|
321
|
+
ReadableType.Null -> null
|
|
322
|
+
ReadableType.Boolean -> asBoolean()
|
|
323
|
+
ReadableType.Number -> asDouble().toNormalizedNumber()
|
|
324
|
+
ReadableType.String -> asString()
|
|
325
|
+
ReadableType.Map -> asMap()?.toHashMap()
|
|
326
|
+
ReadableType.Array -> asArray()?.toNativeList()
|
|
327
|
+
else -> null
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private fun ReadableArray.toNativeList(): List<Any?> = buildList {
|
|
331
|
+
for (index in 0 until size()) {
|
|
332
|
+
add(
|
|
333
|
+
when (getType(index)) {
|
|
334
|
+
ReadableType.Null -> null
|
|
335
|
+
ReadableType.Boolean -> getBoolean(index)
|
|
336
|
+
ReadableType.Number -> getDouble(index).toNormalizedNumber()
|
|
337
|
+
ReadableType.String -> getString(index)
|
|
338
|
+
ReadableType.Map -> getMap(index)?.toHashMap()
|
|
339
|
+
ReadableType.Array -> getArray(index)?.toNativeList()
|
|
340
|
+
else -> null
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private fun Double.toNormalizedNumber(): Number {
|
|
347
|
+
val longValue = toLong()
|
|
348
|
+
return if (this == longValue.toDouble()) {
|
|
349
|
+
if (longValue in Int.MIN_VALUE..Int.MAX_VALUE) longValue.toInt() else longValue
|
|
350
|
+
} else {
|
|
351
|
+
this
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ---- LifecycleEventListener ----
|
|
356
|
+
|
|
357
|
+
override fun onHostResume() {
|
|
358
|
+
client.onHostResume(reactApplicationContext.currentActivity)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
override fun onHostPause() {
|
|
362
|
+
// No-op; Amply session tracker responds to primed lifecycle callbacks.
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
override fun onHostDestroy() {
|
|
366
|
+
// No-op.
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
companion object {
|
|
370
|
+
init {
|
|
371
|
+
SoLoader.loadLibrary("AmplyReactNative")
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ВАЖНО: это имя должно совпадать с тем, что запрашивает JS через TurboModuleRegistry.get(...)
|
|
375
|
+
const val NAME = "Amply"
|
|
376
|
+
private const val TAG = "AmplyReactNative"
|
|
377
|
+
|
|
378
|
+
private const val INIT_ERROR = "AMP_INIT_FAILED"
|
|
379
|
+
private const val ARGUMENT_ERROR = "AMP_INVALID_ARGUMENT"
|
|
380
|
+
private const val TRACK_ERROR = "AMP_TRACK_FAILED"
|
|
381
|
+
private const val EVENTS_ERROR = "AMP_EVENTS_FAILED"
|
|
382
|
+
private const val DATASET_ERROR = "AMP_DATASET_FAILED"
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package tools.amply.sdk.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import com.facebook.react.uimanager.ViewManager
|
|
9
|
+
|
|
10
|
+
class AmplyPackage : TurboReactPackage() {
|
|
11
|
+
|
|
12
|
+
override fun getModule(
|
|
13
|
+
name: String,
|
|
14
|
+
reactContext: ReactApplicationContext
|
|
15
|
+
): NativeModule? =
|
|
16
|
+
when (name) {
|
|
17
|
+
AmplyModule.NAME -> AmplyModule(reactContext)
|
|
18
|
+
else -> null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
22
|
+
val moduleInfos = mapOf(
|
|
23
|
+
AmplyModule.NAME to ReactModuleInfo(
|
|
24
|
+
AmplyModule.NAME,
|
|
25
|
+
AmplyModule::class.java.name,
|
|
26
|
+
false,
|
|
27
|
+
false,
|
|
28
|
+
false,
|
|
29
|
+
false,
|
|
30
|
+
true,
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
return ReactModuleInfoProvider { moduleInfos }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun createViewManagers(
|
|
37
|
+
reactContext: ReactApplicationContext
|
|
38
|
+
): List<ViewManager<*, *>> = emptyList()
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package tools.amply.sdk.reactnative.core
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import tools.amply.sdk.reactnative.model.AmplyInitializationOptions
|
|
5
|
+
import tools.amply.sdk.reactnative.model.DataSetType
|
|
6
|
+
import tools.amply.sdk.reactnative.model.DeepLinkPayload
|
|
7
|
+
import tools.amply.sdk.reactnative.model.EventEnvelope
|
|
8
|
+
import kotlinx.coroutines.flow.SharedFlow
|
|
9
|
+
|
|
10
|
+
interface AmplyClient {
|
|
11
|
+
val deepLinkEvents: SharedFlow<DeepLinkPayload>
|
|
12
|
+
val systemEvents: SharedFlow<EventEnvelope>
|
|
13
|
+
|
|
14
|
+
suspend fun initialize(options: AmplyInitializationOptions)
|
|
15
|
+
|
|
16
|
+
fun isInitialized(): Boolean
|
|
17
|
+
|
|
18
|
+
suspend fun track(name: String, properties: Map<String, Any?>?)
|
|
19
|
+
|
|
20
|
+
suspend fun getRecentEvents(limit: Int): List<EventEnvelope>
|
|
21
|
+
|
|
22
|
+
suspend fun getDataSetSnapshot(type: DataSetType): Map<String, Any?>
|
|
23
|
+
|
|
24
|
+
fun registerDeepLinkListener()
|
|
25
|
+
fun registerSystemEventListener()
|
|
26
|
+
|
|
27
|
+
fun onHostResume(activity: Activity?)
|
|
28
|
+
|
|
29
|
+
fun shutdown()
|
|
30
|
+
}
|