@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.
Files changed (88) hide show
  1. package/LICENSE +178 -0
  2. package/README.md +714 -0
  3. package/android/build.gradle +90 -0
  4. package/android/consumer-rules.pro +1 -0
  5. package/android/gradle.properties +3 -0
  6. package/android/settings.gradle +9 -0
  7. package/android/src/main/AndroidManifest.xml +3 -0
  8. package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +384 -0
  9. package/android/src/main/java/tools/amply/sdk/reactnative/AmplyPackage.kt +39 -0
  10. package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +30 -0
  11. package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +296 -0
  12. package/android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt +10 -0
  13. package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetType.kt +42 -0
  14. package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetTypeMapper.kt +38 -0
  15. package/android/src/main/java/tools/amply/sdk/reactnative/model/DeepLinkPayload.kt +8 -0
  16. package/android/src/main/java/tools/amply/sdk/reactnative/model/EventEnvelope.kt +9 -0
  17. package/android/src/main/jni/AmplyTurboModule.cpp +29 -0
  18. package/android/src/main/jni/CMakeLists.txt +76 -0
  19. package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +75 -0
  20. package/android/src/newarch/jni/AmplyReactNative-generated.cpp +77 -0
  21. package/android/src/newarch/jni/AmplyReactNative.h +31 -0
  22. package/android/src/newarch/jni/CMakeLists.txt +40 -0
  23. package/app.plugin.js +1 -0
  24. package/dist/index.js +272 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/index.mjs +234 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/plugin/index.d.ts +6 -0
  29. package/dist/plugin/index.d.ts.map +1 -0
  30. package/dist/plugin/index.js +186 -0
  31. package/dist/plugin/index.js.map +1 -0
  32. package/dist/plugin/index.mjs +169 -0
  33. package/dist/plugin/index.mjs.map +1 -0
  34. package/dist/plugin/src/index.d.ts +6 -0
  35. package/dist/plugin/src/index.d.ts.map +1 -0
  36. package/dist/plugin/src/index.js +3 -0
  37. package/dist/plugin/src/withAmply.d.ts +30 -0
  38. package/dist/plugin/src/withAmply.d.ts.map +1 -0
  39. package/dist/plugin/src/withAmply.js +51 -0
  40. package/dist/plugin/withAmply.d.ts +12 -0
  41. package/dist/plugin/withAmply.d.ts.map +1 -0
  42. package/dist/src/__tests__/index.test.d.ts +2 -0
  43. package/dist/src/__tests__/index.test.d.ts.map +1 -0
  44. package/dist/src/__tests__/index.test.js +70 -0
  45. package/dist/src/hooks/useAmplySystemEvents.d.ts +12 -0
  46. package/dist/src/hooks/useAmplySystemEvents.d.ts.map +1 -0
  47. package/dist/src/hooks/useAmplySystemEvents.js +56 -0
  48. package/dist/src/index.d.ts +32 -0
  49. package/dist/src/index.d.ts.map +1 -0
  50. package/dist/src/index.js +80 -0
  51. package/dist/src/nativeModule.d.ts +5 -0
  52. package/dist/src/nativeModule.d.ts.map +1 -0
  53. package/dist/src/nativeModule.js +48 -0
  54. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +75 -0
  55. package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -0
  56. package/dist/src/nativeSpecs/NativeAmplyModule.js +2 -0
  57. package/dist/src/systemEventUtils.d.ts +3 -0
  58. package/dist/src/systemEventUtils.d.ts.map +1 -0
  59. package/dist/src/systemEventUtils.js +30 -0
  60. package/dist/src/systemEvents.d.ts +6 -0
  61. package/dist/src/systemEvents.d.ts.map +1 -0
  62. package/dist/src/systemEvents.js +8 -0
  63. package/dist/tsconfig.tsbuildinfo +1 -0
  64. package/docs/ARCHITECTURE.md +1115 -0
  65. package/expo-module.config.json +11 -0
  66. package/ios/AmplyReactNative.podspec +32 -0
  67. package/ios/README.md +11 -0
  68. package/ios/Sources/AmplyReactNative/AmplyModule.mm +332 -0
  69. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +111 -0
  70. package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +152 -0
  71. package/package.json +71 -0
  72. package/plugin/build/index.d.ts +5 -0
  73. package/plugin/build/index.js +8 -0
  74. package/plugin/build/withAmply.d.ts +29 -0
  75. package/plugin/build/withAmply.js +53 -0
  76. package/plugin/src/index.ts +7 -0
  77. package/plugin/src/withAmply.ts +68 -0
  78. package/plugin/tsconfig.json +8 -0
  79. package/plugin/tsconfig.tsbuildinfo +1 -0
  80. package/react-native.config.js +34 -0
  81. package/scripts/codegen.js +212 -0
  82. package/src/__tests__/index.test.ts +92 -0
  83. package/src/hooks/useAmplySystemEvents.ts +75 -0
  84. package/src/index.ts +115 -0
  85. package/src/nativeModule.ts +65 -0
  86. package/src/nativeSpecs/NativeAmplyModule.ts +80 -0
  87. package/src/systemEventUtils.ts +35 -0
  88. 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,3 @@
1
+ android.useAndroidX=true
2
+ android.nonTransitiveRClass=true
3
+ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
@@ -0,0 +1,9 @@
1
+ pluginManagement {
2
+ repositories {
3
+ gradlePluginPortal()
4
+ google()
5
+ mavenCentral()
6
+ }
7
+ }
8
+
9
+ include ':amplyreactnative'
@@ -0,0 +1,3 @@
1
+ <manifest>
2
+ <application />
3
+ </manifest>
@@ -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
+ }