@attentive-mobile/attentive-react-native-sdk 2.0.0-beta.6 → 2.0.0-beta.8
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 +165 -108
- package/android/build.gradle +1 -1
- package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.kt +209 -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/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcuserdata/zheref.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/AttentiveReactNativeSdk.xcodeproj/xcuserdata/zheref.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
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.d(
|
|
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,19 @@ 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
|
-
AttentiveSdk.recordEvent(productViewEvent)
|
|
190
|
+
if (!recordEventSafely("recordProductViewEvent") { AttentiveSdk.recordEvent(productViewEvent) }) return
|
|
164
191
|
|
|
165
192
|
if (debugHelper.isDebuggingEnabled()) {
|
|
166
193
|
val debugData = mutableMapOf<String, Any>()
|
|
167
194
|
debugData["items_count"] = itemsList.size.toString()
|
|
168
195
|
debugData["deeplink"] = deeplink ?: ""
|
|
196
|
+
debugData["all_items"] = itemsDebugData
|
|
197
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
169
198
|
debugHelper.showDebugInfo("Product View Event", debugData)
|
|
170
199
|
}
|
|
171
200
|
}
|
|
@@ -177,6 +206,9 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
177
206
|
cartCoupon: String?
|
|
178
207
|
) {
|
|
179
208
|
Log.i(TAG, "Sending purchase event")
|
|
209
|
+
|
|
210
|
+
val itemsDebugData = if (debugHelper.isDebuggingEnabled()) extractItemsDebugData(items) else emptyList()
|
|
211
|
+
|
|
180
212
|
val order = Order.Builder().orderId(orderId).build()
|
|
181
213
|
val itemsList = buildItems(items)
|
|
182
214
|
val purchaseBuilder = PurchaseEvent.Builder(itemsList, order)
|
|
@@ -185,14 +217,16 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
185
217
|
}
|
|
186
218
|
val purchaseEvent = purchaseBuilder.build()
|
|
187
219
|
|
|
188
|
-
AttentiveSdk.recordEvent(purchaseEvent)
|
|
220
|
+
if (!recordEventSafely("recordPurchaseEvent") { AttentiveSdk.recordEvent(purchaseEvent) }) return
|
|
189
221
|
|
|
190
222
|
if (debugHelper.isDebuggingEnabled()) {
|
|
191
223
|
val debugData = mutableMapOf<String, Any>()
|
|
192
224
|
debugData["items_count"] = itemsList.size.toString()
|
|
193
225
|
debugData["order_id"] = orderId
|
|
194
|
-
if (cartId
|
|
195
|
-
if (cartCoupon
|
|
226
|
+
if (!cartId.isNullOrEmpty()) debugData["cart_id"] = cartId!!
|
|
227
|
+
if (!cartCoupon.isNullOrEmpty()) debugData["cart_coupon"] = cartCoupon!!
|
|
228
|
+
debugData["all_items"] = itemsDebugData
|
|
229
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
196
230
|
debugHelper.showDebugInfo("Purchase Event", debugData)
|
|
197
231
|
}
|
|
198
232
|
}
|
|
@@ -200,15 +234,20 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
200
234
|
override fun recordAddToCartEvent(items: ReadableArray, deeplink: String?) {
|
|
201
235
|
Log.i(TAG, "Sending add to cart event")
|
|
202
236
|
|
|
237
|
+
// Extract raw debug data before building items so all bridge fields are preserved
|
|
238
|
+
val itemsDebugData = if (debugHelper.isDebuggingEnabled()) extractItemsDebugData(items) else emptyList()
|
|
239
|
+
|
|
203
240
|
val itemsList = buildItems(items)
|
|
204
241
|
val addToCartEvent = AddToCartEvent.Builder().items(itemsList).deeplink(deeplink).build()
|
|
205
242
|
|
|
206
|
-
AttentiveSdk.recordEvent(addToCartEvent)
|
|
243
|
+
if (!recordEventSafely("recordAddToCartEvent") { AttentiveSdk.recordEvent(addToCartEvent) }) return
|
|
207
244
|
|
|
208
245
|
if (debugHelper.isDebuggingEnabled()) {
|
|
209
246
|
val debugData = mutableMapOf<String, Any>()
|
|
210
247
|
debugData["items_count"] = itemsList.size.toString()
|
|
211
248
|
debugData["deeplink"] = deeplink ?: ""
|
|
249
|
+
debugData["all_items"] = itemsDebugData
|
|
250
|
+
itemsDebugData.firstOrNull()?.let { debugData["first_item"] = it }
|
|
212
251
|
debugHelper.showDebugInfo("Add To Cart Event", debugData)
|
|
213
252
|
}
|
|
214
253
|
}
|
|
@@ -221,7 +260,7 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
221
260
|
val propertiesMap = convertToStringMap(properties.toHashMap())
|
|
222
261
|
val customEvent = CustomEvent.Builder().type(type).properties(propertiesMap).build()
|
|
223
262
|
|
|
224
|
-
AttentiveSdk.recordEvent(customEvent)
|
|
263
|
+
if (!recordEventSafely("recordCustomEvent") { AttentiveSdk.recordEvent(customEvent) }) return
|
|
225
264
|
|
|
226
265
|
if (debugHelper.isDebuggingEnabled()) {
|
|
227
266
|
val debugData = mutableMapOf<String, Any>()
|
|
@@ -457,14 +496,66 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
457
496
|
* Handle regular/direct app open (not from a push notification).
|
|
458
497
|
*
|
|
459
498
|
* 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
499
|
*
|
|
463
500
|
* @param authorizationStatus Current push authorization status
|
|
464
501
|
*/
|
|
465
502
|
override fun handleRegularOpen(authorizationStatus: String) { // Meant to be NOOP
|
|
466
503
|
Log.i(TAG, "🌉 [AttentiveSDK] handleRegularOpen called (Android)")
|
|
467
504
|
Log.i(TAG, " Authorization status: $authorizationStatus")
|
|
505
|
+
// Log.i(TAG, " Tracking regular app open event...")
|
|
506
|
+
|
|
507
|
+
// try {
|
|
508
|
+
// // Attentive Android SDK 1.0.1 doesn't have a built-in handleRegularOpen method
|
|
509
|
+
// // Track app open as custom event
|
|
510
|
+
|
|
511
|
+
// // Option 1: Track as custom event
|
|
512
|
+
|
|
513
|
+
// Log.i(TAG, " Tracking regular open as custom event 'app_open' with properties")
|
|
514
|
+
|
|
515
|
+
// val properties = mapOf(
|
|
516
|
+
// "event_type" to "app_open",
|
|
517
|
+
// "authorization_status" to authorizationStatus,
|
|
518
|
+
// "platform" to "Android"
|
|
519
|
+
// )
|
|
520
|
+
|
|
521
|
+
// try {
|
|
522
|
+
// Log.i(TAG, " Attempting to track custom event for regular app open")
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
// val customEvent = CustomEvent.Builder()
|
|
526
|
+
// .type("app_open")
|
|
527
|
+
// .properties(properties)
|
|
528
|
+
// .build()
|
|
529
|
+
|
|
530
|
+
// Log.i(TAG, " Custom event built successfully, recording event...")
|
|
531
|
+
|
|
532
|
+
// AttentiveSdk.recordEvent(customEvent)
|
|
533
|
+
|
|
534
|
+
// Log.i(TAG, "✅ [AttentiveSDK] handleRegularOpen completed (tracked as custom event)")
|
|
535
|
+
// Log.i(TAG, " Event sent to Attentive backend")
|
|
536
|
+
// } catch (e: Exception) {
|
|
537
|
+
// Log.w(TAG, "⚠️ [AttentiveSDK] Could not track app open as custom event: ${e.message}")
|
|
538
|
+
// Log.i(TAG, " App open tracking requires manual implementation or SDK upgrade")
|
|
539
|
+
// }
|
|
540
|
+
|
|
541
|
+
// if (debugHelper.isDebuggingEnabled()) {
|
|
542
|
+
// val debugData = mutableMapOf<String, Any>()
|
|
543
|
+
// debugData["authorization_status"] = authorizationStatus
|
|
544
|
+
// debugData["event_type"] = "regular_open"
|
|
545
|
+
// debugData["platform"] = "Android"
|
|
546
|
+
// debugData["sdk_version"] = "2.1.1"
|
|
547
|
+
// debugHelper.showDebugInfo("Regular Open Event", debugData)
|
|
548
|
+
// }
|
|
549
|
+
// } catch (e: Exception) {
|
|
550
|
+
// Log.e(TAG, "❌ [AttentiveSDK] Error in handleRegularOpen: ${e.message}", e)
|
|
551
|
+
|
|
552
|
+
// if (debugHelper.isDebuggingEnabled()) {
|
|
553
|
+
// val debugData = mutableMapOf<String, Any>()
|
|
554
|
+
// debugData["error"] = e.message ?: "Unknown error"
|
|
555
|
+
// debugData["error_type"] = e.javaClass.simpleName
|
|
556
|
+
// debugHelper.showDebugInfo("Regular Open Error", debugData)
|
|
557
|
+
// }
|
|
558
|
+
// }
|
|
468
559
|
}
|
|
469
560
|
|
|
470
561
|
/**
|
|
@@ -668,6 +759,23 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
668
759
|
*/
|
|
669
760
|
override fun getInitialPushNotification(promise: Promise) {
|
|
670
761
|
Log.d(TAG, "getInitialPushNotification called!")
|
|
762
|
+
|
|
763
|
+
// try {
|
|
764
|
+
// val payload = AttentiveNotificationStore.getAndClear()
|
|
765
|
+
// if (payload == null) {
|
|
766
|
+
// Log.d(TAG, "getInitialPushNotification: no pending initial notification")
|
|
767
|
+
// promise.resolve(null)
|
|
768
|
+
// return
|
|
769
|
+
// }
|
|
770
|
+
//
|
|
771
|
+
// Log.i(TAG, "getInitialPushNotification: returning stored notification with keys=${payload.keys}")
|
|
772
|
+
// val result = Arguments.createMap()
|
|
773
|
+
// payload.forEach { (key, value) -> result.putString(key, value) }
|
|
774
|
+
// promise.resolve(result)
|
|
775
|
+
// } catch (e: Exception) {
|
|
776
|
+
// Log.e(TAG, "getInitialPushNotification: error — ${e.message}", e)
|
|
777
|
+
// promise.reject("INITIAL_PUSH_ERROR", "Failed to retrieve initial push notification: ${e.message}", e)
|
|
778
|
+
// }
|
|
671
779
|
}
|
|
672
780
|
|
|
673
781
|
// ==========================================================================
|
|
@@ -686,6 +794,68 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
686
794
|
// No-op: listener bookkeeping is handled by the JS NativeEventEmitter.
|
|
687
795
|
}
|
|
688
796
|
|
|
797
|
+
/**
|
|
798
|
+
* Extracts a human-readable list of item maps from the raw bridge array for debug display.
|
|
799
|
+
*
|
|
800
|
+
* This intentionally reads all fields from the original [ReadableArray] rather than from
|
|
801
|
+
* the built [Item] objects so that every field sent from TypeScript (including optional ones)
|
|
802
|
+
* is visible in the debugger — even fields that may be skipped during SDK item construction.
|
|
803
|
+
*
|
|
804
|
+
* @param rawItems The raw item array as received from the React Native bridge.
|
|
805
|
+
* @return A list of maps, one per item, containing all present fields.
|
|
806
|
+
*/
|
|
807
|
+
/**
|
|
808
|
+
* Executes [block] (which calls [AttentiveSdk.recordEvent]) and catches any [Exception].
|
|
809
|
+
*
|
|
810
|
+
* When an exception is caught the log always includes the exception's simple class name so
|
|
811
|
+
* callers can distinguish an uninitialized-SDK error (typically [IllegalStateException]) from
|
|
812
|
+
* a programming mistake such as [NullPointerException] or [IllegalArgumentException] in event
|
|
813
|
+
* construction — both of which would have been silently misattributed to initialization
|
|
814
|
+
* failure under the previous broad catch-and-blame pattern.
|
|
815
|
+
*
|
|
816
|
+
* @param callerName Method name to include in the log for quick triage (e.g. "recordPurchaseEvent").
|
|
817
|
+
* @param block Lambda that performs the [AttentiveSdk.recordEvent] call.
|
|
818
|
+
* @return `true` if [block] completed without throwing, `false` if an exception was caught.
|
|
819
|
+
*/
|
|
820
|
+
private fun recordEventSafely(callerName: String, block: () -> Unit): Boolean {
|
|
821
|
+
return try {
|
|
822
|
+
block()
|
|
823
|
+
true
|
|
824
|
+
} catch (e: Exception) {
|
|
825
|
+
// Include the exception class so the developer can tell apart an uninitialized SDK
|
|
826
|
+
// (IllegalStateException) from a malformed-event bug (NullPointerException, etc.)
|
|
827
|
+
Log.e(
|
|
828
|
+
TAG,
|
|
829
|
+
"[AttentiveSDK] $callerName failed with ${e.javaClass.simpleName}: ${e.message}. " +
|
|
830
|
+
"If the SDK was not initialized via AttentiveSdk.initialize() in Application.onCreate(), " +
|
|
831
|
+
"that is the most likely cause. Otherwise, inspect the exception type above."
|
|
832
|
+
)
|
|
833
|
+
false
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private fun extractItemsDebugData(rawItems: ReadableArray): List<Map<String, Any>> {
|
|
838
|
+
val result = mutableListOf<Map<String, Any>>()
|
|
839
|
+
for (i in 0 until rawItems.size()) {
|
|
840
|
+
val rawItem = rawItems.getMap(i) ?: continue
|
|
841
|
+
val itemData = mutableMapOf<String, Any>()
|
|
842
|
+
|
|
843
|
+
rawItem.getString("productId")?.let { itemData["productId"] = it }
|
|
844
|
+
rawItem.getString("productVariantId")?.let { itemData["productVariantId"] = it }
|
|
845
|
+
rawItem.getString("price")?.let { itemData["price"] = it }
|
|
846
|
+
rawItem.getString("currency")?.let { itemData["currency"] = it }
|
|
847
|
+
rawItem.getString("name")?.let { itemData["name"] = it }
|
|
848
|
+
rawItem.getString("productImage")?.let { itemData["productImage"] = it }
|
|
849
|
+
rawItem.getString("category")?.let { itemData["category"] = it }
|
|
850
|
+
if (rawItem.hasKey("quantity")) {
|
|
851
|
+
itemData["quantity"] = rawItem.getDouble("quantity").toInt()
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
result.add(itemData)
|
|
855
|
+
}
|
|
856
|
+
return result
|
|
857
|
+
}
|
|
858
|
+
|
|
689
859
|
private fun convertToStringMap(inputMap: Map<String, Any?>): Map<String, String> {
|
|
690
860
|
val outputMap = mutableMapOf<String, String>()
|
|
691
861
|
for ((key, value) in inputMap) {
|
|
@@ -702,19 +872,37 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
702
872
|
private fun buildItems(rawItems: ReadableArray): List<Item> {
|
|
703
873
|
Log.i(TAG, "buildItems method called with rawItems: $rawItems")
|
|
704
874
|
val items = mutableListOf<Item>()
|
|
875
|
+
|
|
705
876
|
for (i in 0 until rawItems.size()) {
|
|
706
877
|
val rawItem = rawItems.getMap(i) ?: continue
|
|
707
878
|
|
|
708
|
-
//
|
|
879
|
+
// Required scalar fields
|
|
709
880
|
val priceValue = rawItem.getString("price") ?: continue
|
|
710
881
|
val currencyCode = rawItem.getString("currency") ?: continue
|
|
882
|
+
val productId = rawItem.getString("productId") ?: continue
|
|
883
|
+
val productVariantId = rawItem.getString("productVariantId") ?: continue
|
|
884
|
+
|
|
885
|
+
// Parse price amount — skip item on malformed value rather than crash
|
|
886
|
+
val priceDecimal = try {
|
|
887
|
+
BigDecimal(priceValue)
|
|
888
|
+
} catch (e: NumberFormatException) {
|
|
889
|
+
Log.w(TAG, "buildItems: invalid price value '$priceValue' at index $i — skipping item")
|
|
890
|
+
continue
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Parse currency — skip item on unrecognised ISO 4217 code rather than crash
|
|
894
|
+
val currency = try {
|
|
895
|
+
Currency.getInstance(currencyCode)
|
|
896
|
+
} catch (e: IllegalArgumentException) {
|
|
897
|
+
Log.w(TAG, "buildItems: invalid currency code '$currencyCode' at index $i — skipping item")
|
|
898
|
+
continue
|
|
899
|
+
}
|
|
900
|
+
|
|
711
901
|
val price = Price.Builder()
|
|
712
|
-
.price(
|
|
713
|
-
.currency(
|
|
902
|
+
.price(priceDecimal)
|
|
903
|
+
.currency(currency)
|
|
714
904
|
.build()
|
|
715
905
|
|
|
716
|
-
val productId = rawItem.getString("productId") ?: continue
|
|
717
|
-
val productVariantId = rawItem.getString("productVariantId") ?: continue
|
|
718
906
|
val builder = Item.Builder(productId, productVariantId, price)
|
|
719
907
|
|
|
720
908
|
if (rawItem.hasKey("productImage")) {
|
|
@@ -725,16 +913,16 @@ class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
|
725
913
|
builder.name(rawItem.getString("name"))
|
|
726
914
|
}
|
|
727
915
|
|
|
916
|
+
// JS numbers are doubles on the bridge; use getDouble().toInt() to avoid ClassCastException
|
|
728
917
|
if (rawItem.hasKey("quantity")) {
|
|
729
|
-
builder.quantity(rawItem.
|
|
918
|
+
builder.quantity(rawItem.getDouble("quantity").toInt())
|
|
730
919
|
}
|
|
731
920
|
|
|
732
921
|
if (rawItem.hasKey("category")) {
|
|
733
922
|
builder.category(rawItem.getString("category"))
|
|
734
923
|
}
|
|
735
924
|
|
|
736
|
-
|
|
737
|
-
items.add(item)
|
|
925
|
+
items.add(builder.build())
|
|
738
926
|
}
|
|
739
927
|
|
|
740
928
|
return items
|
|
@@ -280,7 +280,7 @@ struct DebugEvent {
|
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
282
|
* Handle when a push notification is opened by the user.
|
|
283
|
-
* Note: SDK
|
|
283
|
+
* Note: The SDK requires UNNotificationResponse instead of a plain userInfo dictionary.
|
|
284
284
|
* This method is kept for backward compatibility but has limited functionality.
|
|
285
285
|
* For full functionality, handle push notifications natively in AppDelegate.
|
|
286
286
|
*
|
|
@@ -290,11 +290,11 @@ struct DebugEvent {
|
|
|
290
290
|
*/
|
|
291
291
|
@objc(handlePushOpened:applicationState:authorizationStatus:)
|
|
292
292
|
public func handlePushOpened(_ userInfo: [String: Any], applicationState: String, authorizationStatus: String) {
|
|
293
|
-
// Note:
|
|
293
|
+
// Note: The native SDK requires UNNotificationResponse for push tracking.
|
|
294
294
|
// Since React Native doesn't provide direct access to UNNotificationResponse,
|
|
295
|
-
// apps should handle push notifications natively in AppDelegate for full functionality
|
|
296
|
-
print("[AttentiveSDK] Warning: Push notification handling from React Native is limited
|
|
297
|
-
print("[AttentiveSDK] The native SDK
|
|
295
|
+
// apps should handle push notifications natively in AppDelegate for full functionality.
|
|
296
|
+
print("[AttentiveSDK] Warning: Push notification handling from React Native is limited")
|
|
297
|
+
print("[AttentiveSDK] The native SDK requires UNNotificationResponse for push tracking")
|
|
298
298
|
print("[AttentiveSDK] Please implement push handling in AppDelegate for full functionality")
|
|
299
299
|
|
|
300
300
|
if debuggingEnabled {
|
|
@@ -302,7 +302,7 @@ struct DebugEvent {
|
|
|
302
302
|
"applicationState": applicationState,
|
|
303
303
|
"authorizationStatus": authorizationStatus,
|
|
304
304
|
"userInfo": userInfo,
|
|
305
|
-
"warning": "SDK
|
|
305
|
+
"warning": "Native SDK requires UNNotificationResponse handling in AppDelegate"
|
|
306
306
|
])
|
|
307
307
|
}
|
|
308
308
|
}
|
|
@@ -313,15 +313,15 @@ struct DebugEvent {
|
|
|
313
313
|
*/
|
|
314
314
|
@objc(handleForegroundNotification:)
|
|
315
315
|
public func handleForegroundNotification(_ userInfo: [String: Any]) {
|
|
316
|
-
// Note:
|
|
317
|
-
// Since React Native doesn't provide this, we'll log a warning
|
|
318
|
-
print("[AttentiveSDK] Warning: Foreground notification handling from React Native is limited
|
|
316
|
+
// Note: The native SDK requires UNNotificationResponse for push tracking.
|
|
317
|
+
// Since React Native doesn't provide this, we'll log a warning.
|
|
318
|
+
print("[AttentiveSDK] Warning: Foreground notification handling from React Native is limited")
|
|
319
319
|
print("[AttentiveSDK] Please handle foreground notifications natively in AppDelegate for full functionality")
|
|
320
320
|
|
|
321
321
|
if debuggingEnabled {
|
|
322
322
|
showDebugInfo(event: "Foreground Notification", data: [
|
|
323
323
|
"userInfo": userInfo,
|
|
324
|
-
"warning": "SDK
|
|
324
|
+
"warning": "Native SDK requires UNNotificationResponse handling"
|
|
325
325
|
])
|
|
326
326
|
}
|
|
327
327
|
}
|
|
@@ -383,63 +383,126 @@ struct DebugEvent {
|
|
|
383
383
|
/**
|
|
384
384
|
* React Native bridge entry point for foreground push tracking.
|
|
385
385
|
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
386
|
+
* The Attentive iOS SDK requires a `UNNotificationResponse` which cannot be
|
|
387
|
+
* serialised across the React Native bridge. To work around this, the method
|
|
388
|
+
* checks `AttentiveSDKManager.shared` for a cached response (set by the
|
|
389
|
+
* recommended `handleNotificationResponse(_:)` call in AppDelegate).
|
|
390
390
|
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
391
|
+
* - If a cached response exists **and was already tracked**, this is a no-op
|
|
392
|
+
* (prevents double-tracking).
|
|
393
|
+
* - If a cached response exists **but was not yet tracked**, it is used to
|
|
394
|
+
* call the real native SDK method.
|
|
395
|
+
* - If no cached response is available, a warning is logged instructing the
|
|
396
|
+
* developer to add `AttentiveSDKManager.shared.handleNotificationResponse(response)`
|
|
397
|
+
* to their AppDelegate.
|
|
397
398
|
*
|
|
398
|
-
* @param userInfo The notification payload dictionary
|
|
399
|
-
* @param authorizationStatus Current push authorization status string
|
|
399
|
+
* @param userInfo The notification payload dictionary.
|
|
400
|
+
* @param authorizationStatus Current push authorization status string.
|
|
400
401
|
*/
|
|
401
402
|
@objc(handleForegroundPushFromRN:authorizationStatus:)
|
|
402
403
|
public func handleForegroundPushFromRN(_ userInfo: [String: Any], authorizationStatus: String) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
404
|
+
if let pending = AttentiveSDKManager.shared.consumePendingResponse() {
|
|
405
|
+
if pending.alreadyTracked {
|
|
406
|
+
// Already tracked by handleNotificationResponse — nothing to do.
|
|
407
|
+
if debuggingEnabled {
|
|
408
|
+
showDebugInfo(event: "Foreground Push (already tracked natively)", data: [
|
|
409
|
+
"authorizationStatus": authorizationStatus,
|
|
410
|
+
"note": "Push was already tracked via AttentiveSDKManager.handleNotificationResponse."
|
|
411
|
+
])
|
|
412
|
+
}
|
|
413
|
+
return
|
|
414
|
+
}
|
|
406
415
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
416
|
+
// Cached response exists but wasn't tracked yet — track it now.
|
|
417
|
+
let authStatus = mapAuthorizationStatus(authorizationStatus)
|
|
418
|
+
sdk.handleForegroundPush(response: pending.response, authorizationStatus: authStatus)
|
|
419
|
+
|
|
420
|
+
if debuggingEnabled {
|
|
421
|
+
let cachedUserInfo = pending.response.notification.request.content.userInfo
|
|
422
|
+
showDebugInfo(event: "Foreground Push (tracked via cached response)", data: [
|
|
423
|
+
"authorizationStatus": authorizationStatus,
|
|
424
|
+
"userInfo": cachedUserInfo as? [String: Any] ?? [:],
|
|
425
|
+
"actionIdentifier": pending.response.actionIdentifier
|
|
426
|
+
])
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
// No cached response. Either:
|
|
430
|
+
// (a) The client's AppDelegate is missing handleNotificationResponse, or
|
|
431
|
+
// (b) A prior bridge call (handlePushOpen / handleForegroundPush) already consumed it.
|
|
432
|
+
print("[AttentiveSDK] handleForegroundPushFromRN: No cached UNNotificationResponse available.")
|
|
433
|
+
print("[AttentiveSDK] If push tracking is not working, add this line to your AppDelegate's didReceive handler:")
|
|
434
|
+
print("[AttentiveSDK] AttentiveSDKManager.shared.handleNotificationResponse(response)")
|
|
435
|
+
|
|
436
|
+
if debuggingEnabled {
|
|
437
|
+
showDebugInfo(event: "Foreground Push (no cached response)", data: [
|
|
438
|
+
"authorizationStatus": authorizationStatus,
|
|
439
|
+
"userInfo": userInfo,
|
|
440
|
+
"note": "No cached response — either already consumed by a prior call or AppDelegate setup missing."
|
|
441
|
+
])
|
|
442
|
+
}
|
|
413
443
|
}
|
|
414
444
|
}
|
|
415
445
|
|
|
416
446
|
/**
|
|
417
447
|
* React Native bridge entry point for push-open tracking (background/inactive tap).
|
|
418
448
|
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
449
|
+
* The Attentive iOS SDK requires a `UNNotificationResponse` which cannot be
|
|
450
|
+
* serialised across the React Native bridge. To work around this, the method
|
|
451
|
+
* checks `AttentiveSDKManager.shared` for a cached response (set by the
|
|
452
|
+
* recommended `handleNotificationResponse(_:)` call in AppDelegate).
|
|
422
453
|
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
454
|
+
* - If a cached response exists **and was already tracked**, this is a no-op
|
|
455
|
+
* (prevents double-tracking).
|
|
456
|
+
* - If a cached response exists **but was not yet tracked**, it is used to
|
|
457
|
+
* call the real native SDK method.
|
|
458
|
+
* - If no cached response is available, a warning is logged instructing the
|
|
459
|
+
* developer to add `AttentiveSDKManager.shared.handleNotificationResponse(response)`
|
|
460
|
+
* to their AppDelegate.
|
|
427
461
|
*
|
|
428
|
-
* @param userInfo The notification payload dictionary
|
|
429
|
-
* @param authorizationStatus Current push authorization status string
|
|
462
|
+
* @param userInfo The notification payload dictionary.
|
|
463
|
+
* @param authorizationStatus Current push authorization status string.
|
|
430
464
|
*/
|
|
431
465
|
@objc(handlePushOpenFromRN:authorizationStatus:)
|
|
432
466
|
public func handlePushOpenFromRN(_ userInfo: [String: Any], authorizationStatus: String) {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
467
|
+
if let pending = AttentiveSDKManager.shared.consumePendingResponse() {
|
|
468
|
+
if pending.alreadyTracked {
|
|
469
|
+
// Already tracked by handleNotificationResponse — nothing to do.
|
|
470
|
+
if debuggingEnabled {
|
|
471
|
+
showDebugInfo(event: "Push Open (already tracked natively)", data: [
|
|
472
|
+
"authorizationStatus": authorizationStatus,
|
|
473
|
+
"note": "Push was already tracked via AttentiveSDKManager.handleNotificationResponse."
|
|
474
|
+
])
|
|
475
|
+
}
|
|
476
|
+
return
|
|
477
|
+
}
|
|
436
478
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
479
|
+
// Cached response exists but wasn't tracked yet — track it now.
|
|
480
|
+
let authStatus = mapAuthorizationStatus(authorizationStatus)
|
|
481
|
+
sdk.handlePushOpen(response: pending.response, authorizationStatus: authStatus)
|
|
482
|
+
|
|
483
|
+
if debuggingEnabled {
|
|
484
|
+
let cachedUserInfo = pending.response.notification.request.content.userInfo
|
|
485
|
+
showDebugInfo(event: "Push Open (tracked via cached response)", data: [
|
|
486
|
+
"authorizationStatus": authorizationStatus,
|
|
487
|
+
"userInfo": cachedUserInfo as? [String: Any] ?? [:],
|
|
488
|
+
"actionIdentifier": pending.response.actionIdentifier
|
|
489
|
+
])
|
|
490
|
+
}
|
|
491
|
+
} else {
|
|
492
|
+
// No cached response. Either:
|
|
493
|
+
// (a) The client's AppDelegate is missing handleNotificationResponse, or
|
|
494
|
+
// (b) A prior bridge call (handlePushOpen / handleForegroundPush) already consumed it.
|
|
495
|
+
print("[AttentiveSDK] handlePushOpenFromRN: No cached UNNotificationResponse available.")
|
|
496
|
+
print("[AttentiveSDK] If push tracking is not working, add this line to your AppDelegate's didReceive handler:")
|
|
497
|
+
print("[AttentiveSDK] AttentiveSDKManager.shared.handleNotificationResponse(response)")
|
|
498
|
+
|
|
499
|
+
if debuggingEnabled {
|
|
500
|
+
showDebugInfo(event: "Push Open (no cached response)", data: [
|
|
501
|
+
"authorizationStatus": authorizationStatus,
|
|
502
|
+
"userInfo": userInfo,
|
|
503
|
+
"note": "No cached response — either already consumed by a prior call or AppDelegate setup missing."
|
|
504
|
+
])
|
|
505
|
+
}
|
|
443
506
|
}
|
|
444
507
|
}
|
|
445
508
|
|