@attentive-mobile/attentive-react-native-sdk 1.0.5 → 2.0.0-beta.1
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 +126 -0
- package/android/build.gradle +1 -0
- package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.kt +384 -0
- package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkPackage.kt +36 -0
- package/android/src/main/kotlin/com/attentivereactnativesdk/debug/AttentiveDebugHelper.kt +22 -6
- package/attentive-react-native-sdk.podspec +3 -3
- package/ios/AttentiveReactNativeSdk.h +1 -1
- package/ios/AttentiveReactNativeSdk.mm +317 -37
- package/ios/Bridging/ATTNNativeSDK.swift +392 -45
- package/ios/Bridging/AttentiveReactNativeSdk-Bridging-Header.h +3 -0
- package/ios/Bridging/AttentiveSDKManager.swift +83 -0
- package/ios/Podfile +3 -16
- package/lib/commonjs/NativeAttentiveReactNativeSdk.js +14 -0
- package/lib/commonjs/NativeAttentiveReactNativeSdk.js.map +1 -0
- package/lib/commonjs/eventTypes.js.map +1 -1
- package/lib/commonjs/index.js +362 -52
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeAttentiveReactNativeSdk.js +7 -0
- package/lib/module/NativeAttentiveReactNativeSdk.js.map +1 -0
- package/lib/module/eventTypes.js.map +1 -1
- package/lib/module/index.js +345 -50
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts +103 -0
- package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts.map +1 -0
- package/lib/typescript/eventTypes.d.ts +44 -17
- package/lib/typescript/eventTypes.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +276 -41
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +21 -7
- package/src/NativeAttentiveReactNativeSdk.ts +152 -0
- package/src/eventTypes.tsx +57 -20
- package/src/index.tsx +472 -96
- package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.java +0 -310
- package/android/src/main/java/com/attentivereactnativesdk/AttentiveReactNativeSdkPackage.java +0 -28
- 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/README.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
# Attentive React Native SDK
|
|
4
4
|
The Attentive React Native SDK provides the functionality to render Attentive creative units and collect Attentive events in React Native mobile applications.
|
|
5
5
|
|
|
6
|
+
## Package Manager
|
|
7
|
+
|
|
8
|
+
This project uses **npm** as the preferred package manager for consistency and alignment with modern React Native best practices. While this library will work with any package manager (npm, yarn, or pnpm), the development scripts are configured to use npm.
|
|
9
|
+
|
|
10
|
+
**Note on package managers:** Modern npm (v7+) has significantly improved performance and features, making it the recommended choice for React Native projects. Both npm and yarn work well with React Native, but this project standardizes on npm for development workflows.
|
|
11
|
+
|
|
6
12
|
## Installation
|
|
7
13
|
|
|
8
14
|
Run `npm install @attentive-mobile/attentive-react-native-sdk` from your app's root directory.
|
|
@@ -162,3 +168,123 @@ Attentive.identify({phone: '+15556667777'};)
|
|
|
162
168
|
// email: 'theusersemail@gmail.com'
|
|
163
169
|
// phone: '+15556667777'
|
|
164
170
|
```
|
|
171
|
+
|
|
172
|
+
### Push Notifications (iOS Only)
|
|
173
|
+
|
|
174
|
+
The SDK supports push notification integration for iOS. Android support is planned for a future release.
|
|
175
|
+
|
|
176
|
+
#### Request Push Permission
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { registerForPushNotifications } from 'attentive-react-native-sdk';
|
|
180
|
+
|
|
181
|
+
// Request permission to send push notifications
|
|
182
|
+
// This will show the iOS system permission dialog
|
|
183
|
+
registerForPushNotifications();
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Register Device Token
|
|
187
|
+
|
|
188
|
+
When your app receives a device token from APNs, register it with the Attentive backend:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { registerDeviceToken } from 'attentive-react-native-sdk';
|
|
192
|
+
|
|
193
|
+
// In your AppDelegate or push notification handler:
|
|
194
|
+
// Convert the device token Data to a hex string and pass the authorization status
|
|
195
|
+
registerDeviceToken(hexEncodedToken, 'authorized');
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The `authorizationStatus` parameter should be one of:
|
|
199
|
+
- `'authorized'` - User has granted permission
|
|
200
|
+
- `'denied'` - User has denied permission
|
|
201
|
+
- `'notDetermined'` - User hasn't been asked yet
|
|
202
|
+
- `'provisional'` - Provisional authorization (quiet notifications)
|
|
203
|
+
- `'ephemeral'` - App Clip notifications
|
|
204
|
+
|
|
205
|
+
#### Handle Push Notification Opens
|
|
206
|
+
|
|
207
|
+
When a user taps on a push notification, track the event:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { handlePushOpened } from 'attentive-react-native-sdk';
|
|
211
|
+
import type { ApplicationState, PushAuthorizationStatus } from 'attentive-react-native-sdk';
|
|
212
|
+
|
|
213
|
+
// In your notification handler:
|
|
214
|
+
handlePushOpened(
|
|
215
|
+
notificationPayload, // The notification's userInfo/data
|
|
216
|
+
'background', // App state: 'active', 'inactive', or 'background'
|
|
217
|
+
'authorized' // Current authorization status
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
#### Handle Foreground Notifications
|
|
222
|
+
|
|
223
|
+
When a notification arrives while the app is in the foreground:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { handleForegroundNotification } from 'attentive-react-native-sdk';
|
|
227
|
+
|
|
228
|
+
// In your foreground notification handler:
|
|
229
|
+
handleForegroundNotification(notificationPayload);
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
#### iOS AppDelegate Integration
|
|
233
|
+
|
|
234
|
+
For proper push notification integration, your iOS AppDelegate needs to:
|
|
235
|
+
|
|
236
|
+
1. Request notification permissions via the SDK
|
|
237
|
+
2. Implement `application:didRegisterForRemoteNotificationsWithDeviceToken:` to register the token
|
|
238
|
+
3. Implement `UNUserNotificationCenterDelegate` methods to handle notification events
|
|
239
|
+
|
|
240
|
+
##### Callback-Based Registration (Recommended)
|
|
241
|
+
|
|
242
|
+
For more control over the registration flow, you can use the callback-based registration directly in your AppDelegate:
|
|
243
|
+
|
|
244
|
+
```swift
|
|
245
|
+
// In AppDelegate.swift
|
|
246
|
+
import UserNotifications
|
|
247
|
+
import attentive_react_native_sdk
|
|
248
|
+
|
|
249
|
+
func application(
|
|
250
|
+
_ application: UIApplication,
|
|
251
|
+
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
|
|
252
|
+
) {
|
|
253
|
+
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
254
|
+
let authStatus = settings.authorizationStatus
|
|
255
|
+
|
|
256
|
+
// Get SDK instance with proper type
|
|
257
|
+
guard let attentiveSdk = AttentiveSDKManager.shared.sdk as? ATTNNativeSDK else {
|
|
258
|
+
print("[Attentive] SDK not initialized")
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Register device token with callback
|
|
263
|
+
attentiveSdk.registerDeviceToken(
|
|
264
|
+
deviceToken,
|
|
265
|
+
authorizationStatus: authStatus,
|
|
266
|
+
callback: { data, url, response, error in
|
|
267
|
+
DispatchQueue.main.async {
|
|
268
|
+
// Handle registration result
|
|
269
|
+
if let error = error {
|
|
270
|
+
print("[Attentive] Registration failed: \(error.localizedDescription)")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Trigger regular open event after registration
|
|
274
|
+
attentiveSdk.handleRegularOpen(authorizationStatus: authStatus)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Documentation:**
|
|
283
|
+
- [Push Token Registration Guide](./PUSH_TOKEN_REGISTRATION_GUIDE.md) - Detailed guide for callback-based registration
|
|
284
|
+
- [AppDelegate Callback Example](./APPDELEGATE_CALLBACK_EXAMPLE.md) - Complete AppDelegate implementation
|
|
285
|
+
- [Push Notifications Setup](./PUSH_NOTIFICATIONS_SETUP.md) - General push notification setup
|
|
286
|
+
- [iOS Native SDK documentation](https://github.com/attentive-mobile/attentive-ios-sdk) - Native SDK reference
|
|
287
|
+
|
|
288
|
+
#### Android Support
|
|
289
|
+
|
|
290
|
+
Android push notification support is not yet implemented. The push notification methods will be no-ops on Android. FCM (Firebase Cloud Messaging) integration is planned for a future release.
|
package/android/build.gradle
CHANGED
|
@@ -84,6 +84,7 @@ dependencies {
|
|
|
84
84
|
implementation "com.facebook.react:react-native:+"
|
|
85
85
|
implementation 'com.attentive:attentive-android-sdk:1.0.1'
|
|
86
86
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.10"
|
|
87
|
+
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.10"))
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
if (isNewArchitectureEnabled()) {
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
package com.attentivereactnativesdk
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import androidx.annotation.NonNull
|
|
7
|
+
import androidx.annotation.Nullable
|
|
8
|
+
import com.attentive.androidsdk.AttentiveConfig
|
|
9
|
+
import com.attentive.androidsdk.AttentiveEventTracker
|
|
10
|
+
import com.attentive.androidsdk.UserIdentifiers
|
|
11
|
+
import com.attentive.androidsdk.creatives.Creative
|
|
12
|
+
import com.attentive.androidsdk.events.AddToCartEvent
|
|
13
|
+
import com.attentive.androidsdk.events.CustomEvent
|
|
14
|
+
import com.attentive.androidsdk.events.Item
|
|
15
|
+
import com.attentive.androidsdk.events.Order
|
|
16
|
+
import com.attentive.androidsdk.events.Price
|
|
17
|
+
import com.attentive.androidsdk.events.ProductViewEvent
|
|
18
|
+
import com.attentive.androidsdk.events.PurchaseEvent
|
|
19
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
20
|
+
import com.facebook.react.bridge.ReactMethod
|
|
21
|
+
import com.facebook.react.bridge.ReadableArray
|
|
22
|
+
import com.facebook.react.bridge.ReadableMap
|
|
23
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
24
|
+
import com.facebook.react.bridge.Promise
|
|
25
|
+
import com.attentivereactnativesdk.debug.AttentiveDebugHelper
|
|
26
|
+
import java.math.BigDecimal
|
|
27
|
+
import java.security.InvalidParameterException
|
|
28
|
+
import java.util.Currency
|
|
29
|
+
import java.util.Locale
|
|
30
|
+
|
|
31
|
+
class AttentiveReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
32
|
+
NativeAttentiveReactNativeSdkSpec(reactContext) {
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val NAME = "AttentiveReactNativeSdk"
|
|
36
|
+
private const val TAG = NAME
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private var attentiveConfig: AttentiveConfig? = null
|
|
40
|
+
private var creative: Creative? = null
|
|
41
|
+
private val debugHelper: AttentiveDebugHelper
|
|
42
|
+
|
|
43
|
+
init {
|
|
44
|
+
debugHelper = AttentiveDebugHelper(reactContext)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override fun getName(): String {
|
|
48
|
+
return NAME
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override fun initialize(
|
|
52
|
+
attentiveDomain: String,
|
|
53
|
+
mode: String,
|
|
54
|
+
skipFatigueOnCreatives: Boolean,
|
|
55
|
+
enableDebugger: Boolean
|
|
56
|
+
) {
|
|
57
|
+
// Initialize debug helper
|
|
58
|
+
debugHelper.initialize(enableDebugger)
|
|
59
|
+
|
|
60
|
+
attentiveConfig = AttentiveConfig.Builder()
|
|
61
|
+
.context(reactApplicationContext)
|
|
62
|
+
.domain(attentiveDomain)
|
|
63
|
+
.mode(AttentiveConfig.Mode.valueOf(mode.uppercase(Locale.ROOT)))
|
|
64
|
+
.skipFatigueOnCreatives(skipFatigueOnCreatives)
|
|
65
|
+
.build()
|
|
66
|
+
AttentiveEventTracker.getInstance().initialize(attentiveConfig)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
override fun triggerCreative(creativeId: String?) {
|
|
70
|
+
Log.i(TAG, "Native Attentive module was called to trigger the creative.")
|
|
71
|
+
try {
|
|
72
|
+
val currentActivity: Activity? = reactApplicationContext.currentActivity
|
|
73
|
+
if (currentActivity != null) {
|
|
74
|
+
val rootView =
|
|
75
|
+
currentActivity.window.decorView.rootView as ViewGroup
|
|
76
|
+
// The following calls edit the view hierarchy so they must run on the UI thread
|
|
77
|
+
UiThreadUtil.runOnUiThread {
|
|
78
|
+
creative = Creative(attentiveConfig, rootView)
|
|
79
|
+
creative?.trigger(null, creativeId)
|
|
80
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
81
|
+
val debugData = mutableMapOf<String, Any>()
|
|
82
|
+
debugData["type"] = "trigger"
|
|
83
|
+
debugData["creativeId"] = creativeId ?: "default"
|
|
84
|
+
debugHelper.showDebugInfo("Creative Triggered", debugData)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
Log.w(TAG, "Could not trigger the Attentive Creative because the current Activity was null")
|
|
89
|
+
}
|
|
90
|
+
} catch (e: Exception) {
|
|
91
|
+
Log.e(TAG, "Exception when triggering the creative: $e")
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
override fun destroyCreative() {
|
|
96
|
+
val creativeToDestroy = creative
|
|
97
|
+
if (creativeToDestroy != null) {
|
|
98
|
+
UiThreadUtil.runOnUiThread {
|
|
99
|
+
creativeToDestroy.destroy()
|
|
100
|
+
creative = null
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override fun updateDomain(domain: String) {
|
|
106
|
+
attentiveConfig?.changeDomain(domain)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
override fun clearUser() {
|
|
110
|
+
attentiveConfig?.clearUser()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override fun identify(
|
|
114
|
+
phone: String?,
|
|
115
|
+
email: String?,
|
|
116
|
+
klaviyoId: String?,
|
|
117
|
+
shopifyId: String?,
|
|
118
|
+
clientUserId: String?,
|
|
119
|
+
customIdentifiers: ReadableMap?
|
|
120
|
+
) {
|
|
121
|
+
val idsBuilder = UserIdentifiers.Builder()
|
|
122
|
+
if (!phone.isNullOrEmpty()) {
|
|
123
|
+
idsBuilder.withPhone(phone)
|
|
124
|
+
}
|
|
125
|
+
if (!email.isNullOrEmpty()) {
|
|
126
|
+
idsBuilder.withEmail(email)
|
|
127
|
+
}
|
|
128
|
+
if (!klaviyoId.isNullOrEmpty()) {
|
|
129
|
+
idsBuilder.withKlaviyoId(klaviyoId)
|
|
130
|
+
}
|
|
131
|
+
if (!shopifyId.isNullOrEmpty()) {
|
|
132
|
+
idsBuilder.withShopifyId(shopifyId)
|
|
133
|
+
}
|
|
134
|
+
if (!clientUserId.isNullOrEmpty()) {
|
|
135
|
+
idsBuilder.withClientUserId(clientUserId)
|
|
136
|
+
}
|
|
137
|
+
if (customIdentifiers != null) {
|
|
138
|
+
val customIds = mutableMapOf<String, String>()
|
|
139
|
+
val rawCustomIds = customIdentifiers.toHashMap()
|
|
140
|
+
for ((key, value) in rawCustomIds) {
|
|
141
|
+
if (value is String) {
|
|
142
|
+
customIds[key] = value
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
idsBuilder.withCustomIdentifiers(customIds)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
attentiveConfig?.identify(idsBuilder.build())
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
override fun recordProductViewEvent(items: ReadableArray, deeplink: String?) {
|
|
152
|
+
Log.i(TAG, "Sending product viewed event")
|
|
153
|
+
|
|
154
|
+
val itemsList = buildItems(items)
|
|
155
|
+
val productViewEvent = ProductViewEvent.Builder(itemsList).deeplink(deeplink).build()
|
|
156
|
+
|
|
157
|
+
AttentiveEventTracker.getInstance().recordEvent(productViewEvent)
|
|
158
|
+
|
|
159
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
160
|
+
val debugData = mutableMapOf<String, Any>()
|
|
161
|
+
debugData["items_count"] = itemsList.size.toString()
|
|
162
|
+
debugData["deeplink"] = deeplink ?: ""
|
|
163
|
+
debugHelper.showDebugInfo("Product View Event", debugData)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
override fun recordPurchaseEvent(
|
|
168
|
+
items: ReadableArray,
|
|
169
|
+
orderId: String,
|
|
170
|
+
cartId: String?,
|
|
171
|
+
cartCoupon: String?
|
|
172
|
+
) {
|
|
173
|
+
Log.i(TAG, "Sending purchase event")
|
|
174
|
+
val order = Order.Builder(orderId).build()
|
|
175
|
+
|
|
176
|
+
val itemsList = buildItems(items)
|
|
177
|
+
val purchaseEvent = PurchaseEvent.Builder(itemsList, order).build()
|
|
178
|
+
|
|
179
|
+
AttentiveEventTracker.getInstance().recordEvent(purchaseEvent)
|
|
180
|
+
|
|
181
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
182
|
+
val debugData = mutableMapOf<String, Any>()
|
|
183
|
+
debugData["items_count"] = itemsList.size.toString()
|
|
184
|
+
debugData["order_id"] = orderId
|
|
185
|
+
if (cartId != null) debugData["cart_id"] = cartId
|
|
186
|
+
if (cartCoupon != null) debugData["cart_coupon"] = cartCoupon
|
|
187
|
+
debugHelper.showDebugInfo("Purchase Event", debugData)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
override fun recordAddToCartEvent(items: ReadableArray, deeplink: String?) {
|
|
192
|
+
Log.i(TAG, "Sending add to cart event")
|
|
193
|
+
|
|
194
|
+
val itemsList = buildItems(items)
|
|
195
|
+
val addToCartEvent = AddToCartEvent.Builder(itemsList).deeplink(deeplink).build()
|
|
196
|
+
|
|
197
|
+
AttentiveEventTracker.getInstance().recordEvent(addToCartEvent)
|
|
198
|
+
|
|
199
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
200
|
+
val debugData = mutableMapOf<String, Any>()
|
|
201
|
+
debugData["items_count"] = itemsList.size.toString()
|
|
202
|
+
debugData["deeplink"] = deeplink ?: ""
|
|
203
|
+
debugHelper.showDebugInfo("Add To Cart Event", debugData)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
override fun recordCustomEvent(type: String, properties: ReadableMap?) {
|
|
208
|
+
Log.i(TAG, "Sending custom event")
|
|
209
|
+
if (properties == null) {
|
|
210
|
+
throw IllegalArgumentException("The CustomEvent 'properties' field cannot be null.")
|
|
211
|
+
}
|
|
212
|
+
val propertiesMap = convertToStringMap(properties.toHashMap())
|
|
213
|
+
val customEvent = CustomEvent.Builder(type, propertiesMap).build()
|
|
214
|
+
|
|
215
|
+
AttentiveEventTracker.getInstance().recordEvent(customEvent)
|
|
216
|
+
|
|
217
|
+
if (debugHelper.isDebuggingEnabled()) {
|
|
218
|
+
val debugData = mutableMapOf<String, Any>()
|
|
219
|
+
debugData["event_type"] = type
|
|
220
|
+
debugData["properties_count"] = propertiesMap.size.toString()
|
|
221
|
+
debugHelper.showDebugInfo("Custom Event", debugData)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
override fun invokeAttentiveDebugHelper() {
|
|
226
|
+
debugHelper.invokeDebugHelper()
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
override fun exportDebugLogs(promise: Promise) {
|
|
230
|
+
try {
|
|
231
|
+
val exportContent = debugHelper.exportDebugLogs()
|
|
232
|
+
promise.resolve(exportContent)
|
|
233
|
+
} catch (e: Exception) {
|
|
234
|
+
promise.reject("EXPORT_ERROR", "Failed to export debug logs: " + e.message, e)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ==========================================================================
|
|
239
|
+
// MARK: - Push Notification Methods (Android Implementation - TODO)
|
|
240
|
+
// ==========================================================================
|
|
241
|
+
//
|
|
242
|
+
// These methods are stubs for Android push notification support.
|
|
243
|
+
// Android push notifications typically use Firebase Cloud Messaging (FCM)
|
|
244
|
+
// and require different handling than iOS APNs.
|
|
245
|
+
//
|
|
246
|
+
// TODO: Implement Android push notification support
|
|
247
|
+
// - Integrate with Firebase Cloud Messaging (FCM)
|
|
248
|
+
// - Register FCM token with Attentive backend
|
|
249
|
+
// - Handle push notification opens and foreground notifications
|
|
250
|
+
// - Consider using the attentive-android-sdk's push notification features if available
|
|
251
|
+
//
|
|
252
|
+
// Reference: The iOS implementation uses:
|
|
253
|
+
// - registerForPushNotifications() - Request permission
|
|
254
|
+
// - registerDeviceToken() - Send token to backend
|
|
255
|
+
// - handlePushOpened() - Track push open events
|
|
256
|
+
// - handleForegroundNotification() - Handle foreground push display
|
|
257
|
+
// ==========================================================================
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Request push notification permission from the user.
|
|
261
|
+
*
|
|
262
|
+
* TODO: Implement for Android
|
|
263
|
+
* - For Android 13+ (API 33+), request POST_NOTIFICATIONS permission
|
|
264
|
+
* - For older versions, permissions are granted at install time
|
|
265
|
+
* - Initialize FCM and get the registration token
|
|
266
|
+
*/
|
|
267
|
+
override fun registerForPushNotifications() {
|
|
268
|
+
Log.i(TAG, "[TODO] registerForPushNotifications called - Android implementation pending")
|
|
269
|
+
// TODO: Implement Android push notification registration
|
|
270
|
+
// 1. Check/request POST_NOTIFICATIONS permission (Android 13+)
|
|
271
|
+
// 2. Initialize Firebase Cloud Messaging
|
|
272
|
+
// 3. Get FCM registration token
|
|
273
|
+
// 4. Register token with Attentive backend
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Register the device token with the Attentive backend.
|
|
278
|
+
*
|
|
279
|
+
* TODO: Implement for Android
|
|
280
|
+
* - Android uses FCM tokens instead of APNs tokens
|
|
281
|
+
* - Token format and registration endpoint may differ
|
|
282
|
+
*
|
|
283
|
+
* @param token The FCM registration token
|
|
284
|
+
* @param authorizationStatus Push authorization status (may not apply to Android)
|
|
285
|
+
*/
|
|
286
|
+
override fun registerDeviceToken(token: String, authorizationStatus: String) {
|
|
287
|
+
Log.i(TAG, "[TODO] registerDeviceToken called - Android implementation pending")
|
|
288
|
+
Log.d(TAG, "Token: ${token.take(16)}..., Status: $authorizationStatus")
|
|
289
|
+
// TODO: Implement Android device token registration
|
|
290
|
+
// 1. Send FCM token to Attentive backend
|
|
291
|
+
// 2. Handle token refresh via FirebaseMessagingService.onNewToken()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Handle when a push notification is opened by the user.
|
|
296
|
+
*
|
|
297
|
+
* TODO: Implement for Android
|
|
298
|
+
* - Track push open events with Attentive
|
|
299
|
+
* - Handle deep linking if present in payload
|
|
300
|
+
*
|
|
301
|
+
* @param userInfo The notification payload (from FCM RemoteMessage data)
|
|
302
|
+
* @param applicationState App state when notification was opened
|
|
303
|
+
* @param authorizationStatus Push authorization status
|
|
304
|
+
*/
|
|
305
|
+
override fun handlePushOpened(
|
|
306
|
+
userInfo: ReadableMap,
|
|
307
|
+
applicationState: String,
|
|
308
|
+
authorizationStatus: String
|
|
309
|
+
) {
|
|
310
|
+
Log.i(TAG, "[TODO] handlePushOpened called - Android implementation pending")
|
|
311
|
+
Log.d(TAG, "App state: $applicationState, Auth status: $authorizationStatus")
|
|
312
|
+
// TODO: Implement Android push open tracking
|
|
313
|
+
// 1. Parse notification payload
|
|
314
|
+
// 2. Send push open event to Attentive backend
|
|
315
|
+
// 3. Handle any deep links in the payload
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Handle when a push notification arrives while the app is in foreground.
|
|
320
|
+
*
|
|
321
|
+
* TODO: Implement for Android
|
|
322
|
+
* - Android handles foreground notifications differently than iOS
|
|
323
|
+
* - By default, FCM data messages don't show UI in foreground
|
|
324
|
+
* - Need to create NotificationCompat.Builder to show notification
|
|
325
|
+
*
|
|
326
|
+
* @param userInfo The notification payload
|
|
327
|
+
*/
|
|
328
|
+
override fun handleForegroundNotification(userInfo: ReadableMap) {
|
|
329
|
+
Log.i(TAG, "[TODO] handleForegroundNotification called - Android implementation pending")
|
|
330
|
+
// TODO: Implement Android foreground notification handling
|
|
331
|
+
// 1. Create notification channel (required for Android 8+)
|
|
332
|
+
// 2. Build and display notification using NotificationCompat
|
|
333
|
+
// 3. Track foreground notification event with Attentive
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private fun convertToStringMap(inputMap: Map<String, Any?>): Map<String, String> {
|
|
337
|
+
val outputMap = mutableMapOf<String, String>()
|
|
338
|
+
for ((key, value) in inputMap) {
|
|
339
|
+
if (value == null) {
|
|
340
|
+
throw InvalidParameterException("The key '$key' has a null value.")
|
|
341
|
+
}
|
|
342
|
+
if (value is String) {
|
|
343
|
+
outputMap[key] = value
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return outputMap
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private fun buildItems(rawItems: ReadableArray): List<Item> {
|
|
350
|
+
Log.i(TAG, "buildItems method called with rawItems: $rawItems")
|
|
351
|
+
val items = mutableListOf<Item>()
|
|
352
|
+
for (i in 0 until rawItems.size()) {
|
|
353
|
+
val rawItem = rawItems.getMap(i) ?: continue
|
|
354
|
+
|
|
355
|
+
// Price and currency are now flattened, not nested
|
|
356
|
+
val priceValue = rawItem.getString("price")
|
|
357
|
+
val currencyCode = rawItem.getString("currency")
|
|
358
|
+
val price = Price.Builder(BigDecimal(priceValue), Currency.getInstance(currencyCode)).build()
|
|
359
|
+
|
|
360
|
+
val builder = Item.Builder(rawItem.getString("productId"), rawItem.getString("productVariantId"), price)
|
|
361
|
+
|
|
362
|
+
if (rawItem.hasKey("productImage")) {
|
|
363
|
+
builder.productImage(rawItem.getString("productImage"))
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (rawItem.hasKey("name")) {
|
|
367
|
+
builder.name(rawItem.getString("name"))
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (rawItem.hasKey("quantity")) {
|
|
371
|
+
builder.quantity(rawItem.getInt("quantity"))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (rawItem.hasKey("category")) {
|
|
375
|
+
builder.category(rawItem.getString("category"))
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
val item = builder.build()
|
|
379
|
+
items.add(item)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return items
|
|
383
|
+
}
|
|
384
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.attentivereactnativesdk
|
|
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
|
+
|
|
9
|
+
class AttentiveReactNativeSdkPackage : TurboReactPackage() {
|
|
10
|
+
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == AttentiveReactNativeSdkModule.NAME) {
|
|
13
|
+
AttentiveReactNativeSdkModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
val moduleInfos = mutableMapOf<String, ReactModuleInfo>()
|
|
22
|
+
val isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
23
|
+
moduleInfos[AttentiveReactNativeSdkModule.NAME] = ReactModuleInfo(
|
|
24
|
+
AttentiveReactNativeSdkModule.NAME,
|
|
25
|
+
AttentiveReactNativeSdkModule.NAME,
|
|
26
|
+
false, // canOverrideExistingModule
|
|
27
|
+
false, // needsEagerInit
|
|
28
|
+
true, // hasConstants
|
|
29
|
+
false, // isCxxModule
|
|
30
|
+
isTurboModule // isTurboModule
|
|
31
|
+
)
|
|
32
|
+
moduleInfos
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
@@ -38,8 +38,13 @@ class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
private val debugHistory = mutableListOf<DebugEvent>()
|
|
41
|
-
var isDebuggingEnabled = false
|
|
42
|
-
|
|
41
|
+
private var isDebuggingEnabled = false
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns whether debugging is currently enabled.
|
|
45
|
+
* Provides explicit getter method for Java interop.
|
|
46
|
+
*/
|
|
47
|
+
fun isDebuggingEnabled(): Boolean = isDebuggingEnabled
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
50
|
* Initializes debugging based on configuration and build type.
|
|
@@ -52,7 +57,7 @@ class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
|
52
57
|
isDebuggingEnabled = enableDebuggerFromConfig && isDebugBuild
|
|
53
58
|
|
|
54
59
|
Log.i(TAG, "Debug initialization - enableDebuggerFromConfig: $enableDebuggerFromConfig, " +
|
|
55
|
-
"isDebugBuild: $isDebugBuild, debuggingEnabled: $isDebuggingEnabled")
|
|
60
|
+
"isDebugBuild: $isDebugBuild, debuggingEnabled: ${isDebuggingEnabled()}")
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
@@ -62,7 +67,7 @@ class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
|
62
67
|
* @param data The event data
|
|
63
68
|
*/
|
|
64
69
|
fun showDebugInfo(event: String, data: Map<String, Any?>) {
|
|
65
|
-
if (!isDebuggingEnabled) return
|
|
70
|
+
if (!isDebuggingEnabled()) return
|
|
66
71
|
|
|
67
72
|
Log.i(TAG, "showDebugInfo called for event: $event, data: $data")
|
|
68
73
|
|
|
@@ -86,17 +91,28 @@ class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
|
86
91
|
* Shows existing debug session data without adding to history.
|
|
87
92
|
*/
|
|
88
93
|
fun invokeDebugHelper() {
|
|
89
|
-
|
|
94
|
+
Log.i(TAG, "invokeDebugHelper called - isDebuggingEnabled: ${isDebuggingEnabled()}")
|
|
95
|
+
|
|
96
|
+
if (!isDebuggingEnabled()) {
|
|
97
|
+
Log.w(TAG, "Debug helper not invoked because debugging is not enabled")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
90
100
|
|
|
91
101
|
val currentActivity = reactContext.currentActivity
|
|
102
|
+
Log.i(TAG, "Current activity: $currentActivity")
|
|
103
|
+
|
|
92
104
|
if (currentActivity != null) {
|
|
105
|
+
Log.i(TAG, "Activity is available, running on UI thread")
|
|
93
106
|
UiThreadUtil.runOnUiThread {
|
|
94
107
|
val debugData = mapOf(
|
|
95
108
|
"action" to "manual_debug_call",
|
|
96
109
|
"session_events" to debugHistory.size.toString()
|
|
97
110
|
)
|
|
111
|
+
Log.i(TAG, "About to show debug dialog")
|
|
98
112
|
showDebugDialog(currentActivity, "Manual Debug View", debugData)
|
|
99
113
|
}
|
|
114
|
+
} else {
|
|
115
|
+
Log.w(TAG, "Current activity is null, cannot show debug dialog")
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
|
|
@@ -105,7 +121,7 @@ class AttentiveDebugHelper(private val reactContext: ReactApplicationContext) {
|
|
|
105
121
|
* @return A comprehensive formatted string containing all debug events in the current session
|
|
106
122
|
*/
|
|
107
123
|
fun exportDebugLogs(): String {
|
|
108
|
-
if (!isDebuggingEnabled) {
|
|
124
|
+
if (!isDebuggingEnabled()) {
|
|
109
125
|
return "Debug logging is not enabled. Please enable debugging to export logs."
|
|
110
126
|
}
|
|
111
127
|
|
|
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
|
|
|
16
16
|
|
|
17
17
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
18
18
|
|
|
19
|
-
s.dependency 'attentive-ios-sdk', '
|
|
19
|
+
s.dependency 'attentive-ios-sdk', '2.0.8'
|
|
20
20
|
s.swift_versions = ['5']
|
|
21
21
|
s.dependency "React-Core"
|
|
22
22
|
|
|
@@ -24,9 +24,9 @@ Pod::Spec.new do |s|
|
|
|
24
24
|
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
25
25
|
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
26
26
|
s.pod_target_xcconfig = {
|
|
27
|
-
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
27
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\" \"$(PODS_ROOT)/Headers/Public/React-NativeModulesApple\" \"$(PODS_ROOT)/Headers/Private/React-NativeModulesApple\" \"$(PODS_ROOT)/Headers/Private/React-Codegen/react/renderer/components\" \"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen/React_Codegen.framework/Headers\"",
|
|
28
28
|
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
29
|
-
"CLANG_CXX_LANGUAGE_STANDARD" => "c++
|
|
29
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++20"
|
|
30
30
|
}
|
|
31
31
|
s.dependency "React-Codegen"
|
|
32
32
|
s.dependency "RCT-Folly"
|