@attentive-mobile/attentive-react-native-sdk 2.0.0-beta.6 → 2.0.0-beta.7
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/README.md +114 -9
- package/android/build.gradle +1 -1
- package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.kt +219 -21
- package/attentive-react-native-sdk.podspec +1 -1
- package/ios/Bridging/ATTNNativeSDK.swift +112 -49
- package/ios/Bridging/AttentiveSDKManager.swift +196 -27
- package/ios/Podfile +1 -1
- package/lib/commonjs/index.js +14 -16
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +14 -16
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +14 -16
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +18 -17
package/README.md
CHANGED
|
@@ -68,12 +68,59 @@ See [DEBUGGING.md](./DEBUGGING.md) for detailed information about debugging feat
|
|
|
68
68
|
|
|
69
69
|
### Initialize the SDK
|
|
70
70
|
|
|
71
|
+
> **Platform difference:** iOS and Android have different initialization requirements.
|
|
72
|
+
|
|
73
|
+
#### iOS — Initialize from TypeScript
|
|
74
|
+
|
|
75
|
+
On iOS, call `initialize` from TypeScript as early as possible (e.g. the root `App` component's `useEffect`):
|
|
76
|
+
|
|
71
77
|
```typescript
|
|
72
|
-
//
|
|
73
|
-
// Note: 'initialize' should only be called once per app session - if you call it multiple times it will throw an exception
|
|
78
|
+
// Called once per app session, before any other SDK operations.
|
|
74
79
|
Attentive.initialize(config);
|
|
75
80
|
```
|
|
76
81
|
|
|
82
|
+
#### Android — Initialize from Native Code
|
|
83
|
+
|
|
84
|
+
On Android, `AttentiveSdk.initialize()` **must** be called from your `Application.onCreate()` in native Kotlin/Java code. This is required so that lifecycle observers (e.g. `AppLaunchTracker`) are registered before the React Native bridge is ready. Calling `initialize()` from TypeScript on Android is a **no-op** — the SDK will not be started and all subsequent event calls will fail.
|
|
85
|
+
|
|
86
|
+
Add the following to your `MainApplication.kt` (or `MainApplication.java`):
|
|
87
|
+
|
|
88
|
+
```kotlin
|
|
89
|
+
import android.app.Application
|
|
90
|
+
import com.attentive.androidsdk.AttentiveConfig
|
|
91
|
+
import com.attentive.androidsdk.AttentiveSdk
|
|
92
|
+
import com.attentive.androidsdk.AttentiveLogLevel
|
|
93
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
94
|
+
|
|
95
|
+
class MainApplication : Application(), ReactApplication {
|
|
96
|
+
|
|
97
|
+
override fun onCreate() {
|
|
98
|
+
super.onCreate()
|
|
99
|
+
// ... your existing setup ...
|
|
100
|
+
initAttentiveSDK()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun initAttentiveSDK() {
|
|
104
|
+
val config = AttentiveConfig.Builder()
|
|
105
|
+
.applicationContext(this)
|
|
106
|
+
.domain("YOUR_ATTENTIVE_DOMAIN")
|
|
107
|
+
.mode(AttentiveConfig.Mode.PRODUCTION) // or Mode.DEBUG for testing
|
|
108
|
+
.skipFatigueOnCreatives(false)
|
|
109
|
+
.logLevel(AttentiveLogLevel.VERBOSE)
|
|
110
|
+
.build()
|
|
111
|
+
|
|
112
|
+
// AttentiveSdk.initialize registers lifecycle observers and must run on the main thread.
|
|
113
|
+
UiThreadUtil.runOnUiThread {
|
|
114
|
+
AttentiveSdk.initialize(config)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
After the native initialization, all other SDK operations (`identify`, `recordAddToCartEvent`, `recordPurchaseEvent`, etc.) are called from TypeScript as normal on both platforms. The TypeScript `initialize()` call is still required on iOS but is safely ignored on Android.
|
|
121
|
+
|
|
122
|
+
> **Tip:** If you see `[AttentiveSDK] recordAddToCartEvent failed — SDK may not be initialized` in your Android logcat, it means `AttentiveSdk.initialize()` was not called from native code before the event was recorded. Check your `Application.onCreate()` setup.
|
|
123
|
+
|
|
77
124
|
### Destroy the creative
|
|
78
125
|
|
|
79
126
|
```typescript
|
|
@@ -173,6 +220,25 @@ Attentive.identify({phone: '+15556667777'};)
|
|
|
173
220
|
|
|
174
221
|
The SDK supports push notification integration on both iOS (APNs) and Android (runtime permission + optional FCM). The following sections cover iOS-specific flows and a full **App events on Android** implementation that mirrors the behavior of the [Bonni](https://github.com/attentive-mobile/attentive-react-native-sdk/tree/main/Bonni) example app.
|
|
175
222
|
|
|
223
|
+
> **iOS — required setup:** Your AppDelegate **must** forward notification
|
|
224
|
+
> responses to the SDK for push tracking to work. Add this single line to your
|
|
225
|
+
> `userNotificationCenter(_:didReceive:withCompletionHandler:)`:
|
|
226
|
+
>
|
|
227
|
+
> ```swift
|
|
228
|
+
> AttentiveSDKManager.shared.handleNotificationResponse(response)
|
|
229
|
+
> ```
|
|
230
|
+
>
|
|
231
|
+
> Without this, push open and foreground push events **will not be tracked** on
|
|
232
|
+
> iOS. See [iOS AppDelegate Integration](#ios-appdelegate-integration) for full
|
|
233
|
+
> details.
|
|
234
|
+
>
|
|
235
|
+
> **Migrating from an earlier version?** If you previously called
|
|
236
|
+
> `AttentiveSDKManager.shared.handleForegroundPush(response:authorizationStatus:)`
|
|
237
|
+
> or `AttentiveSDKManager.shared.handlePushOpen(response:authorizationStatus:)`
|
|
238
|
+
> directly from your AppDelegate, **replace** that code with the single
|
|
239
|
+
> `handleNotificationResponse` call above. Using both will result in
|
|
240
|
+
> double-tracked events. The old methods are now deprecated.
|
|
241
|
+
|
|
176
242
|
---
|
|
177
243
|
|
|
178
244
|
### App Events on Android
|
|
@@ -205,7 +271,7 @@ This section describes how to implement Attentive app events on Android so they
|
|
|
205
271
|
</manifest>
|
|
206
272
|
```
|
|
207
273
|
|
|
208
|
-
2. **Initialize and identify first** –
|
|
274
|
+
2. **Initialize and identify first** – The SDK must be initialized natively from `Application.onCreate()` on Android (see [Android Native Initialization](#android--initialize-from-native-code) above). Then, in your app entry (e.g. root component `useEffect`), call `identify(identifiers)` before any push or app-event logic.
|
|
209
275
|
|
|
210
276
|
#### 1. On app launch (Android)
|
|
211
277
|
|
|
@@ -228,7 +294,13 @@ import {
|
|
|
228
294
|
} from 'attentive-react-native-sdk';
|
|
229
295
|
|
|
230
296
|
// Inside your root component (e.g. App.tsx useEffect):
|
|
231
|
-
|
|
297
|
+
|
|
298
|
+
// iOS only: initialize from TypeScript.
|
|
299
|
+
// Android: initialization must be done natively from Application.onCreate() — see README.
|
|
300
|
+
if (Platform.OS === 'ios') {
|
|
301
|
+
initialize(config);
|
|
302
|
+
}
|
|
303
|
+
|
|
232
304
|
identify({ email: 'user@example.com', clientUserId: 'id-123' });
|
|
233
305
|
|
|
234
306
|
if (Platform.OS === 'android') {
|
|
@@ -304,7 +376,7 @@ Get `authorizationStatus` via `getPushAuthorizationStatus()` when handling the e
|
|
|
304
376
|
|
|
305
377
|
The [Bonni](https://github.com/attentive-mobile/attentive-react-native-sdk/tree/main/Bonni) example app ([App.tsx](https://github.com/attentive-mobile/attentive-react-native-sdk/blob/main/Bonni/App.tsx)) implements the full flow:
|
|
306
378
|
|
|
307
|
-
1. **Launch:** `initialize` → `identify` →
|
|
379
|
+
1. **Launch:** Native `AttentiveSdk.initialize(config)` (from `Application.onCreate()`) → TypeScript `identify` → `getPushAuthorizationStatus()` → `handleRegularOpen(authStatus)` → `registerForPushNotifications()`.
|
|
308
380
|
2. **Foreground:** `AppState.addEventListener('change', …)` → when `active` and Android → `getPushAuthorizationStatus()` → `handleRegularOpen(authStatus)`.
|
|
309
381
|
3. **Optional:** When FCM token is available → `registerDeviceTokenWithCallback(token, authStatus, callback)` → in callback call `handleRegularOpen(authStatus)`.
|
|
310
382
|
4. **Optional:** When user opens a notification or receives one in foreground → `handlePushOpen` / `handleForegroundPush` with payload and status from `getPushAuthorizationStatus()`.
|
|
@@ -373,7 +445,40 @@ For proper push notification integration, your iOS AppDelegate needs to:
|
|
|
373
445
|
|
|
374
446
|
1. Request notification permissions via the SDK
|
|
375
447
|
2. Implement `application:didRegisterForRemoteNotificationsWithDeviceToken:` to register the token
|
|
376
|
-
3.
|
|
448
|
+
3. **Forward notification responses to the SDK for push-open tracking**
|
|
449
|
+
|
|
450
|
+
##### Push Open Tracking (Required)
|
|
451
|
+
|
|
452
|
+
Add **one line** to your AppDelegate's `didReceive` handler so the SDK can track
|
|
453
|
+
push opens and foreground push events. Without this, `handlePushOpen()` and
|
|
454
|
+
`handleForegroundPush()` called from JavaScript will not be able to track events
|
|
455
|
+
on iOS (the native SDK requires a `UNNotificationResponse` which cannot cross the
|
|
456
|
+
React Native bridge).
|
|
457
|
+
|
|
458
|
+
```swift
|
|
459
|
+
// In AppDelegate.swift — UNUserNotificationCenterDelegate
|
|
460
|
+
func userNotificationCenter(
|
|
461
|
+
_ center: UNUserNotificationCenter,
|
|
462
|
+
didReceive response: UNNotificationResponse,
|
|
463
|
+
withCompletionHandler completionHandler: @escaping () -> Void
|
|
464
|
+
) {
|
|
465
|
+
// Attentive push tracking (handles app-state + auth status automatically)
|
|
466
|
+
AttentiveSDKManager.shared.handleNotificationResponse(response)
|
|
467
|
+
|
|
468
|
+
// Forward to your push library (e.g. RNCPushNotificationIOS) for JS events
|
|
469
|
+
RNCPushNotificationIOS.didReceive(response)
|
|
470
|
+
completionHandler()
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
`handleNotificationResponse` automatically:
|
|
475
|
+
- Detects whether the app is in the foreground or background
|
|
476
|
+
- Fetches the current authorization status
|
|
477
|
+
- Calls the correct native SDK method (`handlePushOpen` or `handleForegroundPush`)
|
|
478
|
+
- Caches the response so the JS-side `handlePushOpen()` / `handleForegroundPush()` calls
|
|
479
|
+
are fulfilled without double-tracking
|
|
480
|
+
- **Cold-launch safe:** If the user taps a push while the app is killed, the
|
|
481
|
+
response is cached and automatically tracked once the SDK initializes
|
|
377
482
|
|
|
378
483
|
##### Callback-Based Registration (Recommended)
|
|
379
484
|
|
|
@@ -390,13 +495,13 @@ func application(
|
|
|
390
495
|
) {
|
|
391
496
|
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
392
497
|
let authStatus = settings.authorizationStatus
|
|
393
|
-
|
|
498
|
+
|
|
394
499
|
// Get SDK instance with proper type
|
|
395
500
|
guard let attentiveSdk = AttentiveSDKManager.shared.sdk as? ATTNNativeSDK else {
|
|
396
501
|
print("[Attentive] SDK not initialized")
|
|
397
502
|
return
|
|
398
503
|
}
|
|
399
|
-
|
|
504
|
+
|
|
400
505
|
// Register device token with callback
|
|
401
506
|
attentiveSdk.registerDeviceToken(
|
|
402
507
|
deviceToken,
|
|
@@ -407,7 +512,7 @@ func application(
|
|
|
407
512
|
if let error = error {
|
|
408
513
|
print("[Attentive] Registration failed: \(error.localizedDescription)")
|
|
409
514
|
}
|
|
410
|
-
|
|
515
|
+
|
|
411
516
|
// Trigger regular open event after registration
|
|
412
517
|
attentiveSdk.handleRegularOpen(authorizationStatus: authStatus)
|
|
413
518
|
}
|
package/android/build.gradle
CHANGED
|
@@ -85,7 +85,7 @@ dependencies {
|
|
|
85
85
|
// Use `api` so that the Attentive Android SDK types (AttentiveSdk, CustomEvent, etc.)
|
|
86
86
|
// are visible to app-level code (e.g. Bonni's AttentiveFirebaseMessagingService) that
|
|
87
87
|
// depends on this library and needs to call the SDK directly from native components.
|
|
88
|
-
api 'com.attentive:attentive-android-sdk:2.1.3
|
|
88
|
+
api 'com.attentive:attentive-android-sdk:2.1.3'
|
|
89
89
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.10"
|
|
90
90
|
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.10"))
|
|
91
91
|
|
package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.kt
CHANGED
|
@@ -57,10 +57,28 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* native
|
|
60
|
+
* TypeScript-facing initialize() — intentionally a no-op on Android.
|
|
61
|
+
*
|
|
62
|
+
* On Android, AttentiveSdk.initialize() MUST be called from your Application.onCreate()
|
|
63
|
+
* (native code) so that lifecycle observers (AppLaunchTracker, etc.) are registered
|
|
64
|
+
* before the React Native bridge is ready. Calling this from TypeScript on Android has
|
|
65
|
+
* no effect and will not initialize the SDK.
|
|
66
|
+
*
|
|
67
|
+
* Required native setup in your Application class:
|
|
68
|
+
* ```kotlin
|
|
69
|
+
* override fun onCreate() {
|
|
70
|
+
* super.onCreate()
|
|
71
|
+
* val config = AttentiveConfig.Builder()
|
|
72
|
+
* .applicationContext(this)
|
|
73
|
+
* .domain("YOUR_ATTENTIVE_DOMAIN")
|
|
74
|
+
* .mode(AttentiveConfig.Mode.PRODUCTION)
|
|
75
|
+
* .build()
|
|
76
|
+
* AttentiveSdk.initialize(config)
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* See the README.md "Android Native Initialization" section for the full guide.
|
|
81
|
+
* All other SDK operations (identify, recordEvent, push) are handled from TypeScript as normal.
|
|
64
82
|
*/
|
|
65
83
|
override fun initialize(
|
|
66
84
|
attentiveDomain: String,
|
|
@@ -68,8 +86,15 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
68
86
|
skipFatigueOnCreatives: Boolean,
|
|
69
87
|
enableDebugger: Boolean
|
|
70
88
|
) {
|
|
71
|
-
// Initialize debug helper
|
|
72
89
|
debugHelper.initialize(enableDebugger)
|
|
90
|
+
|
|
91
|
+
Log.w(
|
|
92
|
+
TAG,
|
|
93
|
+
"[AttentiveSDK] initialize() called from TypeScript is a NO-OP on Android. " +
|
|
94
|
+
"You must call AttentiveSdk.initialize(config) from your Application.onCreate() " +
|
|
95
|
+
"so that lifecycle observers are registered before the React Native bridge is ready. " +
|
|
96
|
+
"See README.md § 'Android Native Initialization' for the required setup."
|
|
97
|
+
)
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
override fun triggerCreative(creativeId: String?) {
|
|
@@ -157,15 +182,29 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
157
182
|
override fun recordProductViewEvent(items: ReadableArray, deeplink: String?) {
|
|
158
183
|
Log.i(TAG, "Sending product viewed event")
|
|
159
184
|
|
|
185
|
+
val itemsDebugData = if (debugHelper.isDebuggingEnabled()) extractItemsDebugData(items) else emptyList()
|
|
186
|
+
|
|
160
187
|
val itemsList = buildItems(items)
|
|
161
188
|
val productViewEvent = ProductViewEvent.Builder().items(itemsList).deeplink(deeplink).build()
|
|
162
189
|
|
|
163
|
-
|
|
190
|
+
try {
|
|
191
|
+
AttentiveSdk.recordEvent(productViewEvent)
|
|
192
|
+
} catch (e: Exception) {
|
|
193
|
+
Log.e(
|
|
194
|
+
TAG,
|
|
195
|
+
"[AttentiveSDK] recordProductViewEvent failed — SDK may not be initialized. " +
|
|
196
|
+
"On Android, call AttentiveSdk.initialize(config) from Application.onCreate() " +
|
|
197
|
+
"before recording events. Error: ${e.message}"
|
|
198
|
+
)
|
|
199
|
+
return
|
|
200
|
+
}
|
|
164
201
|
|
|
165
202
|
if (debugHelper.isDebuggingEnabled()) {
|
|
166
203
|
val debugData = mutableMapOf<String, Any>()
|
|
167
204
|
debugData["items_count"] = itemsList.size.toString()
|
|
168
205
|
debugData["deeplink"] = deeplink ?: ""
|
|
206
|
+
debugData["all_items"] = itemsDebugData
|
|
207
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
169
208
|
debugHelper.showDebugInfo("Product View Event", debugData)
|
|
170
209
|
}
|
|
171
210
|
}
|
|
@@ -177,6 +216,9 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
177
216
|
cartCoupon: String?
|
|
178
217
|
) {
|
|
179
218
|
Log.i(TAG, "Sending purchase event")
|
|
219
|
+
|
|
220
|
+
val itemsDebugData = if (debugHelper.isDebuggingEnabled()) extractItemsDebugData(items) else emptyList()
|
|
221
|
+
|
|
180
222
|
val order = Order.Builder().orderId(orderId).build()
|
|
181
223
|
val itemsList = buildItems(items)
|
|
182
224
|
val purchaseBuilder = PurchaseEvent.Builder(itemsList, order)
|
|
@@ -185,14 +227,26 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
185
227
|
}
|
|
186
228
|
val purchaseEvent = purchaseBuilder.build()
|
|
187
229
|
|
|
188
|
-
|
|
230
|
+
try {
|
|
231
|
+
AttentiveSdk.recordEvent(purchaseEvent)
|
|
232
|
+
} catch (e: Exception) {
|
|
233
|
+
Log.e(
|
|
234
|
+
TAG,
|
|
235
|
+
"[AttentiveSDK] recordPurchaseEvent failed — SDK may not be initialized. " +
|
|
236
|
+
"On Android, call AttentiveSdk.initialize(config) from Application.onCreate() " +
|
|
237
|
+
"before recording events. Error: ${e.message}"
|
|
238
|
+
)
|
|
239
|
+
return
|
|
240
|
+
}
|
|
189
241
|
|
|
190
242
|
if (debugHelper.isDebuggingEnabled()) {
|
|
191
243
|
val debugData = mutableMapOf<String, Any>()
|
|
192
244
|
debugData["items_count"] = itemsList.size.toString()
|
|
193
245
|
debugData["order_id"] = orderId
|
|
194
|
-
if (cartId
|
|
195
|
-
if (cartCoupon
|
|
246
|
+
if (!cartId.isNullOrEmpty()) debugData["cart_id"] = cartId!!
|
|
247
|
+
if (!cartCoupon.isNullOrEmpty()) debugData["cart_coupon"] = cartCoupon!!
|
|
248
|
+
debugData["all_items"] = itemsDebugData
|
|
249
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
196
250
|
debugHelper.showDebugInfo("Purchase Event", debugData)
|
|
197
251
|
}
|
|
198
252
|
}
|
|
@@ -200,15 +254,30 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
200
254
|
override fun recordAddToCartEvent(items: ReadableArray, deeplink: String?) {
|
|
201
255
|
Log.i(TAG, "Sending add to cart event")
|
|
202
256
|
|
|
257
|
+
// Extract raw debug data before building items so all bridge fields are preserved
|
|
258
|
+
val itemsDebugData = if (debugHelper.isDebuggingEnabled()) extractItemsDebugData(items) else emptyList()
|
|
259
|
+
|
|
203
260
|
val itemsList = buildItems(items)
|
|
204
261
|
val addToCartEvent = AddToCartEvent.Builder().items(itemsList).deeplink(deeplink).build()
|
|
205
262
|
|
|
206
|
-
|
|
263
|
+
try {
|
|
264
|
+
AttentiveSdk.recordEvent(addToCartEvent)
|
|
265
|
+
} catch (e: Exception) {
|
|
266
|
+
Log.e(
|
|
267
|
+
TAG,
|
|
268
|
+
"[AttentiveSDK] recordAddToCartEvent failed — SDK may not be initialized. " +
|
|
269
|
+
"On Android, call AttentiveSdk.initialize(config) from Application.onCreate() " +
|
|
270
|
+
"before recording events. Error: ${e.message}"
|
|
271
|
+
)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
207
274
|
|
|
208
275
|
if (debugHelper.isDebuggingEnabled()) {
|
|
209
276
|
val debugData = mutableMapOf<String, Any>()
|
|
210
277
|
debugData["items_count"] = itemsList.size.toString()
|
|
211
278
|
debugData["deeplink"] = deeplink ?: ""
|
|
279
|
+
debugData["all_items"] = itemsDebugData
|
|
280
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
212
281
|
debugHelper.showDebugInfo("Add To Cart Event", debugData)
|
|
213
282
|
}
|
|
214
283
|
}
|
|
@@ -221,7 +290,17 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
221
290
|
val propertiesMap = convertToStringMap(properties.toHashMap())
|
|
222
291
|
val customEvent = CustomEvent.Builder().type(type).properties(propertiesMap).build()
|
|
223
292
|
|
|
224
|
-
|
|
293
|
+
try {
|
|
294
|
+
AttentiveSdk.recordEvent(customEvent)
|
|
295
|
+
} catch (e: Exception) {
|
|
296
|
+
Log.e(
|
|
297
|
+
TAG,
|
|
298
|
+
"[AttentiveSDK] recordCustomEvent failed — SDK may not be initialized. " +
|
|
299
|
+
"On Android, call AttentiveSdk.initialize(config) from Application.onCreate() " +
|
|
300
|
+
"before recording events. Error: ${e.message}"
|
|
301
|
+
)
|
|
302
|
+
return
|
|
303
|
+
}
|
|
225
304
|
|
|
226
305
|
if (debugHelper.isDebuggingEnabled()) {
|
|
227
306
|
val debugData = mutableMapOf<String, Any>()
|
|
@@ -457,14 +536,66 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
457
536
|
* Handle regular/direct app open (not from a push notification).
|
|
458
537
|
*
|
|
459
538
|
* This tracks app open events using the Attentive SDK's event tracking system.
|
|
460
|
-
* This operation is iOS only. Android version is handled directly by native SDK upon
|
|
461
|
-
* TypeScript initialization.
|
|
462
539
|
*
|
|
463
540
|
* @param authorizationStatus Current push authorization status
|
|
464
541
|
*/
|
|
465
542
|
override fun handleRegularOpen(authorizationStatus: String) { // Meant to be NOOP
|
|
466
543
|
Log.i(TAG, "🌉 [AttentiveSDK] handleRegularOpen called (Android)")
|
|
467
544
|
Log.i(TAG, " Authorization status: $authorizationStatus")
|
|
545
|
+
// Log.i(TAG, " Tracking regular app open event...")
|
|
546
|
+
|
|
547
|
+
// try {
|
|
548
|
+
// // Attentive Android SDK 1.0.1 doesn't have a built-in handleRegularOpen method
|
|
549
|
+
// // Track app open as custom event
|
|
550
|
+
|
|
551
|
+
// // Option 1: Track as custom event
|
|
552
|
+
|
|
553
|
+
// Log.i(TAG, " Tracking regular open as custom event 'app_open' with properties")
|
|
554
|
+
|
|
555
|
+
// val properties = mapOf(
|
|
556
|
+
// "event_type" to "app_open",
|
|
557
|
+
// "authorization_status" to authorizationStatus,
|
|
558
|
+
// "platform" to "Android"
|
|
559
|
+
// )
|
|
560
|
+
|
|
561
|
+
// try {
|
|
562
|
+
// Log.i(TAG, " Attempting to track custom event for regular app open")
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
// val customEvent = CustomEvent.Builder()
|
|
566
|
+
// .type("app_open")
|
|
567
|
+
// .properties(properties)
|
|
568
|
+
// .build()
|
|
569
|
+
|
|
570
|
+
// Log.i(TAG, " Custom event built successfully, recording event...")
|
|
571
|
+
|
|
572
|
+
// AttentiveSdk.recordEvent(customEvent)
|
|
573
|
+
|
|
574
|
+
// Log.i(TAG, "✅ [AttentiveSDK] handleRegularOpen completed (tracked as custom event)")
|
|
575
|
+
// Log.i(TAG, " Event sent to Attentive backend")
|
|
576
|
+
// } catch (e: Exception) {
|
|
577
|
+
// Log.w(TAG, "⚠️ [AttentiveSDK] Could not track app open as custom event: ${e.message}")
|
|
578
|
+
// Log.i(TAG, " App open tracking requires manual implementation or SDK upgrade")
|
|
579
|
+
// }
|
|
580
|
+
|
|
581
|
+
// if (debugHelper.isDebuggingEnabled()) {
|
|
582
|
+
// val debugData = mutableMapOf<String, Any>()
|
|
583
|
+
// debugData["authorization_status"] = authorizationStatus
|
|
584
|
+
// debugData["event_type"] = "regular_open"
|
|
585
|
+
// debugData["platform"] = "Android"
|
|
586
|
+
// debugData["sdk_version"] = "2.1.1"
|
|
587
|
+
// debugHelper.showDebugInfo("Regular Open Event", debugData)
|
|
588
|
+
// }
|
|
589
|
+
// } catch (e: Exception) {
|
|
590
|
+
// Log.e(TAG, "❌ [AttentiveSDK] Error in handleRegularOpen: ${e.message}", e)
|
|
591
|
+
|
|
592
|
+
// if (debugHelper.isDebuggingEnabled()) {
|
|
593
|
+
// val debugData = mutableMapOf<String, Any>()
|
|
594
|
+
// debugData["error"] = e.message ?: "Unknown error"
|
|
595
|
+
// debugData["error_type"] = e.javaClass.simpleName
|
|
596
|
+
// debugHelper.showDebugInfo("Regular Open Error", debugData)
|
|
597
|
+
// }
|
|
598
|
+
// }
|
|
468
599
|
}
|
|
469
600
|
|
|
470
601
|
/**
|
|
@@ -668,6 +799,23 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
668
799
|
*/
|
|
669
800
|
override fun getInitialPushNotification(promise: Promise) {
|
|
670
801
|
Log.d(TAG, "getInitialPushNotification called!")
|
|
802
|
+
|
|
803
|
+
// try {
|
|
804
|
+
// val payload = AttentiveNotificationStore.getAndClear()
|
|
805
|
+
// if (payload == null) {
|
|
806
|
+
// Log.d(TAG, "getInitialPushNotification: no pending initial notification")
|
|
807
|
+
// promise.resolve(null)
|
|
808
|
+
// return
|
|
809
|
+
// }
|
|
810
|
+
//
|
|
811
|
+
// Log.i(TAG, "getInitialPushNotification: returning stored notification with keys=${payload.keys}")
|
|
812
|
+
// val result = Arguments.createMap()
|
|
813
|
+
// payload.forEach { (key, value) -> result.putString(key, value) }
|
|
814
|
+
// promise.resolve(result)
|
|
815
|
+
// } catch (e: Exception) {
|
|
816
|
+
// Log.e(TAG, "getInitialPushNotification: error — ${e.message}", e)
|
|
817
|
+
// promise.reject("INITIAL_PUSH_ERROR", "Failed to retrieve initial push notification: ${e.message}", e)
|
|
818
|
+
// }
|
|
671
819
|
}
|
|
672
820
|
|
|
673
821
|
// ==========================================================================
|
|
@@ -686,6 +834,38 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
686
834
|
// No-op: listener bookkeeping is handled by the JS NativeEventEmitter.
|
|
687
835
|
}
|
|
688
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Extracts a human-readable list of item maps from the raw bridge array for debug display.
|
|
839
|
+
*
|
|
840
|
+
* This intentionally reads all fields from the original [ReadableArray] rather than from
|
|
841
|
+
* the built [Item] objects so that every field sent from TypeScript (including optional ones)
|
|
842
|
+
* is visible in the debugger — even fields that may be skipped during SDK item construction.
|
|
843
|
+
*
|
|
844
|
+
* @param rawItems The raw item array as received from the React Native bridge.
|
|
845
|
+
* @return A list of maps, one per item, containing all present fields.
|
|
846
|
+
*/
|
|
847
|
+
private fun extractItemsDebugData(rawItems: ReadableArray): List<Map<String, Any>> {
|
|
848
|
+
val result = mutableListOf<Map<String, Any>>()
|
|
849
|
+
for (i in 0 until rawItems.size()) {
|
|
850
|
+
val rawItem = rawItems.getMap(i) ?: continue
|
|
851
|
+
val itemData = mutableMapOf<String, Any>()
|
|
852
|
+
|
|
853
|
+
rawItem.getString("productId")?.let { itemData["productId"] = it }
|
|
854
|
+
rawItem.getString("productVariantId")?.let { itemData["productVariantId"] = it }
|
|
855
|
+
rawItem.getString("price")?.let { itemData["price"] = it }
|
|
856
|
+
rawItem.getString("currency")?.let { itemData["currency"] = it }
|
|
857
|
+
rawItem.getString("name")?.let { itemData["name"] = it }
|
|
858
|
+
rawItem.getString("productImage")?.let { itemData["productImage"] = it }
|
|
859
|
+
rawItem.getString("category")?.let { itemData["category"] = it }
|
|
860
|
+
if (rawItem.hasKey("quantity")) {
|
|
861
|
+
itemData["quantity"] = rawItem.getDouble("quantity").toInt()
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
result.add(itemData)
|
|
865
|
+
}
|
|
866
|
+
return result
|
|
867
|
+
}
|
|
868
|
+
|
|
689
869
|
private fun convertToStringMap(inputMap: Map<String, Any?>): Map<String, String> {
|
|
690
870
|
val outputMap = mutableMapOf<String, String>()
|
|
691
871
|
for ((key, value) in inputMap) {
|
|
@@ -702,19 +882,37 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
702
882
|
private fun buildItems(rawItems: ReadableArray): List<Item> {
|
|
703
883
|
Log.i(TAG, "buildItems method called with rawItems: $rawItems")
|
|
704
884
|
val items = mutableListOf<Item>()
|
|
885
|
+
|
|
705
886
|
for (i in 0 until rawItems.size()) {
|
|
706
887
|
val rawItem = rawItems.getMap(i) ?: continue
|
|
707
888
|
|
|
708
|
-
//
|
|
889
|
+
// Required scalar fields
|
|
709
890
|
val priceValue = rawItem.getString("price") ?: continue
|
|
710
891
|
val currencyCode = rawItem.getString("currency") ?: continue
|
|
892
|
+
val productId = rawItem.getString("productId") ?: continue
|
|
893
|
+
val productVariantId = rawItem.getString("productVariantId") ?: continue
|
|
894
|
+
|
|
895
|
+
// Parse price amount — skip item on malformed value rather than crash
|
|
896
|
+
val priceDecimal = try {
|
|
897
|
+
BigDecimal(priceValue)
|
|
898
|
+
} catch (e: NumberFormatException) {
|
|
899
|
+
Log.w(TAG, "buildItems: invalid price value '$priceValue' at index $i — skipping item")
|
|
900
|
+
continue
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Parse currency — skip item on unrecognised ISO 4217 code rather than crash
|
|
904
|
+
val currency = try {
|
|
905
|
+
Currency.getInstance(currencyCode)
|
|
906
|
+
} catch (e: IllegalArgumentException) {
|
|
907
|
+
Log.w(TAG, "buildItems: invalid currency code '$currencyCode' at index $i — skipping item")
|
|
908
|
+
continue
|
|
909
|
+
}
|
|
910
|
+
|
|
711
911
|
val price = Price.Builder()
|
|
712
|
-
.price(
|
|
713
|
-
.currency(
|
|
912
|
+
.price(priceDecimal)
|
|
913
|
+
.currency(currency)
|
|
714
914
|
.build()
|
|
715
915
|
|
|
716
|
-
val productId = rawItem.getString("productId") ?: continue
|
|
717
|
-
val productVariantId = rawItem.getString("productVariantId") ?: continue
|
|
718
916
|
val builder = Item.Builder(productId, productVariantId, price)
|
|
719
917
|
|
|
720
918
|
if (rawItem.hasKey("productImage")) {
|
|
@@ -725,16 +923,16 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
725
923
|
builder.name(rawItem.getString("name"))
|
|
726
924
|
}
|
|
727
925
|
|
|
926
|
+
// JS numbers are doubles on the bridge; use getDouble().toInt() to avoid ClassCastException
|
|
728
927
|
if (rawItem.hasKey("quantity")) {
|
|
729
|
-
builder.quantity(rawItem.
|
|
928
|
+
builder.quantity(rawItem.getDouble("quantity").toInt())
|
|
730
929
|
}
|
|
731
930
|
|
|
732
931
|
if (rawItem.hasKey("category")) {
|
|
733
932
|
builder.category(rawItem.getString("category"))
|
|
734
933
|
}
|
|
735
934
|
|
|
736
|
-
|
|
737
|
-
items.add(item)
|
|
935
|
+
items.add(builder.build())
|
|
738
936
|
}
|
|
739
937
|
|
|
740
938
|
return items
|