@attentive-mobile/attentive-react-native-sdk 1.0.5 → 2.0.0-beta.2
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 +506 -58
- 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
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import Foundation
|
|
10
10
|
import attentive_ios_sdk
|
|
11
11
|
import UIKit
|
|
12
|
+
import UserNotifications
|
|
12
13
|
|
|
13
14
|
// Debug Event structure for session history
|
|
14
15
|
struct DebugEvent {
|
|
@@ -23,7 +24,7 @@ struct DebugEvent {
|
|
|
23
24
|
self.eventType = eventType
|
|
24
25
|
self.data = data
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
/**
|
|
28
29
|
* Formats the debug event as a human-readable string for export
|
|
29
30
|
* @return A formatted string containing timestamp, event type, and data
|
|
@@ -32,17 +33,17 @@ struct DebugEvent {
|
|
|
32
33
|
let formatter = DateFormatter()
|
|
33
34
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
|
|
34
35
|
let timeString = formatter.string(from: timestamp)
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
var output = "[\(timeString)] \(eventType)\n"
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
// Add summary information if available
|
|
39
40
|
let summary = getSummary()
|
|
40
41
|
if !summary.isEmpty {
|
|
41
42
|
output += "Summary: \(summary)\n"
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
output += "Data:\n"
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
// Format data as JSON for better readability
|
|
47
48
|
do {
|
|
48
49
|
let jsonData = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
|
|
@@ -54,17 +55,17 @@ struct DebugEvent {
|
|
|
54
55
|
} catch {
|
|
55
56
|
output += "\(data)"
|
|
56
57
|
}
|
|
57
|
-
|
|
58
|
+
|
|
58
59
|
return output + "\n" + String(repeating: "=", count: 50) + "\n"
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
/**
|
|
62
63
|
* Generates a summary of the debug event for quick overview
|
|
63
64
|
* @return A brief summary string highlighting key information
|
|
64
65
|
*/
|
|
65
66
|
private func getSummary() -> String {
|
|
66
67
|
var summaryParts: [String] = []
|
|
67
|
-
|
|
68
|
+
|
|
68
69
|
if let itemsCount = data["items_count"] as? String {
|
|
69
70
|
summaryParts.append("Items: \(itemsCount)")
|
|
70
71
|
}
|
|
@@ -77,10 +78,10 @@ struct DebugEvent {
|
|
|
77
78
|
if let eventType = data["event_type"] as? String {
|
|
78
79
|
summaryParts.append("Type: \(eventType)")
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
|
|
81
82
|
// Always show payload size info
|
|
82
83
|
summaryParts.append("Payload: \(data.count) fields")
|
|
83
|
-
|
|
84
|
+
|
|
84
85
|
return summaryParts.joined(separator: " • ")
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -94,17 +95,16 @@ struct DebugEvent {
|
|
|
94
95
|
@objc(initWithDomain:mode:skipFatigueOnCreatives:enableDebugger:)
|
|
95
96
|
public init(domain: String, mode: String, skipFatigueOnCreatives: Bool, enableDebugger: Bool) {
|
|
96
97
|
self.sdk = ATTNSDK(domain: domain, mode: ATTNSDKMode(rawValue: mode) ?? .production)
|
|
97
|
-
self.sdk.skipFatigueOnCreative = skipFatigueOnCreatives
|
|
98
|
-
|
|
98
|
+
self.sdk.skipFatigueOnCreative = skipFatigueOnCreatives
|
|
99
|
+
|
|
99
100
|
// Only enable debugging if both enableDebugger is true AND the app is running in debug mode
|
|
100
|
-
let enableDebuggerFromConfig = enableDebugger ?? false
|
|
101
101
|
#if DEBUG
|
|
102
102
|
let isDebugBuild = true
|
|
103
103
|
#else
|
|
104
104
|
let isDebugBuild = false
|
|
105
105
|
#endif
|
|
106
|
-
self.debuggingEnabled =
|
|
107
|
-
|
|
106
|
+
self.debuggingEnabled = enableDebugger && isDebugBuild
|
|
107
|
+
|
|
108
108
|
ATTNEventTracker.setup(with: sdk)
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -131,7 +131,14 @@ struct DebugEvent {
|
|
|
131
131
|
|
|
132
132
|
@objc(identify:)
|
|
133
133
|
public func identify(_ identifiers: [String: Any]) {
|
|
134
|
+
print("👤 [AttentiveSDK] identify called from React Native")
|
|
135
|
+
print(" Identifiers: \(identifiers)")
|
|
136
|
+
|
|
134
137
|
sdk.identify(identifiers)
|
|
138
|
+
|
|
139
|
+
print("✅ [AttentiveSDK] identify completed")
|
|
140
|
+
print(" User is now identified with the SDK")
|
|
141
|
+
print(" SDK can now make network calls")
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
@objc
|
|
@@ -139,6 +146,346 @@ struct DebugEvent {
|
|
|
139
146
|
sdk.clearUser()
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
// MARK: - Push Notification Methods
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Request push notification permission from the user.
|
|
153
|
+
* This will trigger the iOS permission dialog.
|
|
154
|
+
*/
|
|
155
|
+
@objc
|
|
156
|
+
public func registerForPushNotifications() {
|
|
157
|
+
sdk.registerForPushNotifications()
|
|
158
|
+
if debuggingEnabled {
|
|
159
|
+
showDebugInfo(event: "Push Registration Requested", data: ["action": "registerForPushNotifications"])
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Register the device token received from APNs with the Attentive backend.
|
|
165
|
+
* @param token The hex-encoded device token string
|
|
166
|
+
* @param authorizationStatus Current push authorization status string
|
|
167
|
+
*/
|
|
168
|
+
@objc(registerDeviceToken:authorizationStatus:)
|
|
169
|
+
public func registerDeviceToken(_ token: String, authorizationStatus: String) {
|
|
170
|
+
// Convert hex string back to Data
|
|
171
|
+
guard let tokenData = hexStringToData(token) else {
|
|
172
|
+
print("[AttentiveSDK] Invalid device token format")
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Map string to UNAuthorizationStatus
|
|
177
|
+
let status = mapAuthorizationStatus(authorizationStatus)
|
|
178
|
+
|
|
179
|
+
sdk.registerDeviceToken(tokenData, authorizationStatus: status)
|
|
180
|
+
|
|
181
|
+
if debuggingEnabled {
|
|
182
|
+
showDebugInfo(event: "Device Token Registered", data: [
|
|
183
|
+
"token": String(token.prefix(16)) + "...",
|
|
184
|
+
"authorizationStatus": authorizationStatus
|
|
185
|
+
])
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Register the device token with callback for use in AppDelegate.
|
|
191
|
+
* This method is intended to be called directly from the host app's AppDelegate,
|
|
192
|
+
* not from React Native JavaScript.
|
|
193
|
+
*
|
|
194
|
+
* @param token The device token Data from APNs
|
|
195
|
+
* @param authorizationStatus Current push authorization status
|
|
196
|
+
* @param callback Completion handler called after registration attempt
|
|
197
|
+
*/
|
|
198
|
+
@objc(registerDeviceTokenWithCallback:authorizationStatus:callback:)
|
|
199
|
+
public func registerDeviceToken(
|
|
200
|
+
_ token: Data,
|
|
201
|
+
authorizationStatus: UNAuthorizationStatus,
|
|
202
|
+
callback: @escaping (_ data: Data?, _ url: URL?, _ response: URLResponse?, _ error: Error?) -> Void
|
|
203
|
+
) {
|
|
204
|
+
sdk.registerDeviceToken(token, authorizationStatus: authorizationStatus, callback: callback)
|
|
205
|
+
|
|
206
|
+
if debuggingEnabled {
|
|
207
|
+
let tokenString = token.map { String(format: "%02.2hhx", $0) }.joined()
|
|
208
|
+
showDebugInfo(event: "Device Token Registered (with callback)", data: [
|
|
209
|
+
"token": String(tokenString.prefix(16)) + "...",
|
|
210
|
+
"authorizationStatus": authorizationStatusToString(authorizationStatus)
|
|
211
|
+
])
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handle a regular/direct app open event.
|
|
217
|
+
* This should be called after device token registration completes (success or failure).
|
|
218
|
+
*
|
|
219
|
+
* This is the TypeScript equivalent of the native iOS AppDelegate method:
|
|
220
|
+
* ```swift
|
|
221
|
+
* func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
222
|
+
* UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
|
|
223
|
+
* guard let self = self else { return }
|
|
224
|
+
* let authStatus = settings.authorizationStatus
|
|
225
|
+
* attentiveSdk?.registerDeviceToken(deviceToken, authorizationStatus: authStatus, callback: { data, url, response, error in
|
|
226
|
+
* DispatchQueue.main.async {
|
|
227
|
+
* self.attentiveSdk?.handleRegularOpen(authorizationStatus: authStatus)
|
|
228
|
+
* }
|
|
229
|
+
* })
|
|
230
|
+
* }
|
|
231
|
+
* }
|
|
232
|
+
* ```
|
|
233
|
+
*
|
|
234
|
+
* @param authorizationStatus Current push authorization status
|
|
235
|
+
*/
|
|
236
|
+
@objc(handleRegularOpen:)
|
|
237
|
+
public func handleRegularOpen(authorizationStatus: UNAuthorizationStatus) {
|
|
238
|
+
print("🌉 [AttentiveSDK] handleRegularOpen called from React Native")
|
|
239
|
+
print(" Authorization Status: \(authorizationStatusToString(authorizationStatus))")
|
|
240
|
+
print(" Calling underlying iOS SDK handleRegularOpen...")
|
|
241
|
+
|
|
242
|
+
// Call the underlying Attentive iOS SDK
|
|
243
|
+
sdk.handleRegularOpen(authorizationStatus: authorizationStatus)
|
|
244
|
+
|
|
245
|
+
print("✅ [AttentiveSDK] handleRegularOpen completed")
|
|
246
|
+
print(" This should trigger a network call to: https://mobile.attentivemobile.com/mtctrl")
|
|
247
|
+
print(" If you don't see the network call:")
|
|
248
|
+
print(" 1. Check that user is identified (call identify() before handleRegularOpen)")
|
|
249
|
+
print(" 2. Check your proxy debugger is configured for mobile.attentivemobile.com")
|
|
250
|
+
print(" 3. Verify SSL proxying is enabled")
|
|
251
|
+
print(" 4. Check device has internet connection")
|
|
252
|
+
|
|
253
|
+
if debuggingEnabled {
|
|
254
|
+
showDebugInfo(event: "Regular Open Event", data: [
|
|
255
|
+
"authorizationStatus": authorizationStatusToString(authorizationStatus),
|
|
256
|
+
"expectedEndpoint": "https://mobile.attentivemobile.com/mtctrl",
|
|
257
|
+
"note": "Check network logs to verify endpoint was called"
|
|
258
|
+
])
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Handle when a push notification is opened by the user.
|
|
265
|
+
* Note: SDK 2.0.8 changed the API to require UNNotificationResponse instead of userInfo dictionary.
|
|
266
|
+
* This method is kept for backward compatibility but has limited functionality.
|
|
267
|
+
* For full functionality, handle push notifications natively in AppDelegate.
|
|
268
|
+
*
|
|
269
|
+
* @param userInfo The notification payload
|
|
270
|
+
* @param applicationState The app state when notification was opened
|
|
271
|
+
* @param authorizationStatus Current push authorization status
|
|
272
|
+
*/
|
|
273
|
+
@objc(handlePushOpened:applicationState:authorizationStatus:)
|
|
274
|
+
public func handlePushOpened(_ userInfo: [String: Any], applicationState: String, authorizationStatus: String) {
|
|
275
|
+
// Note: SDK 2.0.8 changed the API to require UNNotificationResponse
|
|
276
|
+
// Since React Native doesn't provide direct access to UNNotificationResponse,
|
|
277
|
+
// apps should handle push notifications natively in AppDelegate for full functionality
|
|
278
|
+
print("[AttentiveSDK] Warning: Push notification handling from React Native is limited in SDK 2.0.8")
|
|
279
|
+
print("[AttentiveSDK] The native SDK now requires UNNotificationResponse for push tracking")
|
|
280
|
+
print("[AttentiveSDK] Please implement push handling in AppDelegate for full functionality")
|
|
281
|
+
|
|
282
|
+
if debuggingEnabled {
|
|
283
|
+
showDebugInfo(event: "Push Opened (Limited)", data: [
|
|
284
|
+
"applicationState": applicationState,
|
|
285
|
+
"authorizationStatus": authorizationStatus,
|
|
286
|
+
"userInfo": userInfo,
|
|
287
|
+
"warning": "SDK 2.0.8 requires native UNNotificationResponse handling in AppDelegate"
|
|
288
|
+
])
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Handle when a push notification arrives while the app is in foreground.
|
|
294
|
+
* @param userInfo The notification payload
|
|
295
|
+
*/
|
|
296
|
+
@objc(handleForegroundNotification:)
|
|
297
|
+
public func handleForegroundNotification(_ userInfo: [String: Any]) {
|
|
298
|
+
// Note: SDK 2.0.8 changed the API to require UNNotificationResponse
|
|
299
|
+
// Since React Native doesn't provide this, we'll log a warning
|
|
300
|
+
print("[AttentiveSDK] Warning: Foreground notification handling from React Native is limited in SDK 2.0.8")
|
|
301
|
+
print("[AttentiveSDK] Please handle foreground notifications natively in AppDelegate for full functionality")
|
|
302
|
+
|
|
303
|
+
if debuggingEnabled {
|
|
304
|
+
showDebugInfo(event: "Foreground Notification", data: [
|
|
305
|
+
"userInfo": userInfo,
|
|
306
|
+
"warning": "SDK 2.0.8 requires native UNNotificationResponse handling"
|
|
307
|
+
])
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Handle a push notification when the app is in the foreground (active state).
|
|
313
|
+
* This is the native equivalent that should be called from AppDelegate when the app is active.
|
|
314
|
+
*
|
|
315
|
+
* Equivalent to Swift AppDelegate code:
|
|
316
|
+
* ```swift
|
|
317
|
+
* case .active:
|
|
318
|
+
* self.attentiveSdk?.handleForegroundPush(response: response, authorizationStatus: authStatus)
|
|
319
|
+
* ```
|
|
320
|
+
*
|
|
321
|
+
* @param response The UNNotificationResponse from the notification center delegate
|
|
322
|
+
* @param authorizationStatus Current push authorization status
|
|
323
|
+
*/
|
|
324
|
+
@objc(handleForegroundPush:authorizationStatus:)
|
|
325
|
+
public func handleForegroundPush(response: UNNotificationResponse, authorizationStatus: UNAuthorizationStatus) {
|
|
326
|
+
sdk.handleForegroundPush(response: response, authorizationStatus: authorizationStatus)
|
|
327
|
+
|
|
328
|
+
if debuggingEnabled {
|
|
329
|
+
let userInfo = response.notification.request.content.userInfo
|
|
330
|
+
showDebugInfo(event: "Foreground Push", data: [
|
|
331
|
+
"authorizationStatus": authorizationStatusToString(authorizationStatus),
|
|
332
|
+
"userInfo": userInfo as? [String: Any] ?? [:],
|
|
333
|
+
"actionIdentifier": response.actionIdentifier
|
|
334
|
+
])
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Handle when a push notification is opened by the user (app in background/inactive state).
|
|
340
|
+
* This is the native equivalent that should be called from AppDelegate when the app is background or inactive.
|
|
341
|
+
*
|
|
342
|
+
* Equivalent to Swift AppDelegate code:
|
|
343
|
+
* ```swift
|
|
344
|
+
* case .background, .inactive:
|
|
345
|
+
* self.attentiveSdk?.handlePushOpen(response: response, authorizationStatus: authStatus)
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* @param response The UNNotificationResponse from the notification center delegate
|
|
349
|
+
* @param authorizationStatus Current push authorization status
|
|
350
|
+
*/
|
|
351
|
+
@objc(handlePushOpen:authorizationStatus:)
|
|
352
|
+
public func handlePushOpen(response: UNNotificationResponse, authorizationStatus: UNAuthorizationStatus) {
|
|
353
|
+
sdk.handlePushOpen(response: response, authorizationStatus: authorizationStatus)
|
|
354
|
+
|
|
355
|
+
if debuggingEnabled {
|
|
356
|
+
let userInfo = response.notification.request.content.userInfo
|
|
357
|
+
showDebugInfo(event: "Push Open", data: [
|
|
358
|
+
"authorizationStatus": authorizationStatusToString(authorizationStatus),
|
|
359
|
+
"userInfo": userInfo as? [String: Any] ?? [:],
|
|
360
|
+
"actionIdentifier": response.actionIdentifier
|
|
361
|
+
])
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Handle a push notification when the app is in the foreground (active state) - React Native version.
|
|
367
|
+
* This method accepts userInfo dictionary instead of UNNotificationResponse, making it callable from React Native.
|
|
368
|
+
*
|
|
369
|
+
* Note: This is a limited version since we don't have access to the full UNNotificationResponse.
|
|
370
|
+
* For full functionality, use the native AppDelegate implementation.
|
|
371
|
+
*
|
|
372
|
+
* @param userInfo The notification payload dictionary
|
|
373
|
+
* @param authorizationStatus Current push authorization status string
|
|
374
|
+
*/
|
|
375
|
+
@objc(handleForegroundPushFromRN:authorizationStatus:)
|
|
376
|
+
public func handleForegroundPushFromRN(_ userInfo: [String: Any], authorizationStatus: String) {
|
|
377
|
+
_ = mapAuthorizationStatus(authorizationStatus)
|
|
378
|
+
|
|
379
|
+
// Note: SDK 2.0.8 requires UNNotificationResponse, but we only have userInfo from React Native
|
|
380
|
+
// This is a workaround that logs the limitation
|
|
381
|
+
print("[AttentiveSDK] handleForegroundPush called from React Native (limited functionality)")
|
|
382
|
+
print("[AttentiveSDK] For full functionality, implement in native AppDelegate")
|
|
383
|
+
|
|
384
|
+
if debuggingEnabled {
|
|
385
|
+
showDebugInfo(event: "Foreground Push (React Native)", data: [
|
|
386
|
+
"authorizationStatus": authorizationStatus,
|
|
387
|
+
"userInfo": userInfo,
|
|
388
|
+
"note": "Limited functionality - UNNotificationResponse not available from React Native"
|
|
389
|
+
])
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Handle when a push notification is opened by the user (app in background/inactive state) - React Native version.
|
|
395
|
+
* This method accepts userInfo dictionary instead of UNNotificationResponse, making it callable from React Native.
|
|
396
|
+
*
|
|
397
|
+
* Note: This is a limited version since we don't have access to the full UNNotificationResponse.
|
|
398
|
+
* For full functionality, use the native AppDelegate implementation.
|
|
399
|
+
*
|
|
400
|
+
* @param userInfo The notification payload dictionary
|
|
401
|
+
* @param authorizationStatus Current push authorization status string
|
|
402
|
+
*/
|
|
403
|
+
@objc(handlePushOpenFromRN:authorizationStatus:)
|
|
404
|
+
public func handlePushOpenFromRN(_ userInfo: [String: Any], authorizationStatus: String) {
|
|
405
|
+
_ = mapAuthorizationStatus(authorizationStatus)
|
|
406
|
+
|
|
407
|
+
// Note: SDK 2.0.8 requires UNNotificationResponse, but we only have userInfo from React Native
|
|
408
|
+
// This is a workaround that logs the limitation
|
|
409
|
+
print("[AttentiveSDK] handlePushOpen called from React Native (limited functionality)")
|
|
410
|
+
print("[AttentiveSDK] For full functionality, implement in native AppDelegate")
|
|
411
|
+
|
|
412
|
+
if debuggingEnabled {
|
|
413
|
+
showDebugInfo(event: "Push Open (React Native)", data: [
|
|
414
|
+
"authorizationStatus": authorizationStatus,
|
|
415
|
+
"userInfo": userInfo,
|
|
416
|
+
"note": "Limited functionality - UNNotificationResponse not available from React Native"
|
|
417
|
+
])
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// MARK: - Push Notification Helpers
|
|
422
|
+
|
|
423
|
+
private func hexStringToData(_ hexString: String) -> Data? {
|
|
424
|
+
var data = Data()
|
|
425
|
+
var hex = hexString
|
|
426
|
+
|
|
427
|
+
// Remove any non-hex characters
|
|
428
|
+
hex = hex.replacingOccurrences(of: " ", with: "")
|
|
429
|
+
hex = hex.replacingOccurrences(of: "<", with: "")
|
|
430
|
+
hex = hex.replacingOccurrences(of: ">", with: "")
|
|
431
|
+
|
|
432
|
+
var index = hex.startIndex
|
|
433
|
+
while index < hex.endIndex {
|
|
434
|
+
let nextIndex = hex.index(index, offsetBy: 2, limitedBy: hex.endIndex) ?? hex.endIndex
|
|
435
|
+
if nextIndex > hex.endIndex { break }
|
|
436
|
+
|
|
437
|
+
let byteString = String(hex[index..<nextIndex])
|
|
438
|
+
if let byte = UInt8(byteString, radix: 16) {
|
|
439
|
+
data.append(byte)
|
|
440
|
+
} else {
|
|
441
|
+
return nil
|
|
442
|
+
}
|
|
443
|
+
index = nextIndex
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return data.isEmpty ? nil : data
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private func mapAuthorizationStatus(_ status: String) -> UNAuthorizationStatus {
|
|
450
|
+
switch status.lowercased() {
|
|
451
|
+
case "authorized":
|
|
452
|
+
return .authorized
|
|
453
|
+
case "denied":
|
|
454
|
+
return .denied
|
|
455
|
+
case "notdetermined":
|
|
456
|
+
return .notDetermined
|
|
457
|
+
case "provisional":
|
|
458
|
+
return .provisional
|
|
459
|
+
case "ephemeral":
|
|
460
|
+
if #available(iOS 14.0, *) {
|
|
461
|
+
return .ephemeral
|
|
462
|
+
}
|
|
463
|
+
return .authorized
|
|
464
|
+
default:
|
|
465
|
+
return .notDetermined
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private func authorizationStatusToString(_ status: UNAuthorizationStatus) -> String {
|
|
470
|
+
switch status {
|
|
471
|
+
case .authorized:
|
|
472
|
+
return "authorized"
|
|
473
|
+
case .denied:
|
|
474
|
+
return "denied"
|
|
475
|
+
case .notDetermined:
|
|
476
|
+
return "notDetermined"
|
|
477
|
+
case .provisional:
|
|
478
|
+
return "provisional"
|
|
479
|
+
case .ephemeral:
|
|
480
|
+
if #available(iOS 14.0, *) {
|
|
481
|
+
return "ephemeral"
|
|
482
|
+
}
|
|
483
|
+
return "authorized"
|
|
484
|
+
@unknown default:
|
|
485
|
+
return "notDetermined"
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
142
489
|
@objc
|
|
143
490
|
public func invokeAttentiveDebugHelper() {
|
|
144
491
|
if debuggingEnabled {
|
|
@@ -157,7 +504,7 @@ struct DebugEvent {
|
|
|
157
504
|
}
|
|
158
505
|
}
|
|
159
506
|
}
|
|
160
|
-
|
|
507
|
+
|
|
161
508
|
}
|
|
162
509
|
|
|
163
510
|
public extension ATTNNativeSDK {
|
|
@@ -170,36 +517,36 @@ public extension ATTNNativeSDK {
|
|
|
170
517
|
guard debuggingEnabled else {
|
|
171
518
|
return "Debug logging is not enabled. Please enable debugging to export logs."
|
|
172
519
|
}
|
|
173
|
-
|
|
520
|
+
|
|
174
521
|
if debugHistory.isEmpty {
|
|
175
522
|
return "No debug events recorded in this session."
|
|
176
523
|
}
|
|
177
|
-
|
|
524
|
+
|
|
178
525
|
let formatter = DateFormatter()
|
|
179
526
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
180
527
|
let exportDate = formatter.string(from: Date())
|
|
181
|
-
|
|
528
|
+
|
|
182
529
|
var exportContent = """
|
|
183
530
|
Attentive React Native SDK - Debug Session Export
|
|
184
531
|
Generated: \(exportDate)
|
|
185
532
|
Total Events: \(debugHistory.count)
|
|
186
|
-
|
|
533
|
+
|
|
187
534
|
\(String(repeating: "=", count: 60))
|
|
188
|
-
|
|
535
|
+
|
|
189
536
|
"""
|
|
190
|
-
|
|
537
|
+
|
|
191
538
|
// Add all events in chronological order (oldest first for better readability)
|
|
192
539
|
for (index, event) in debugHistory.enumerated() {
|
|
193
540
|
exportContent += "Event #\(index + 1)\n"
|
|
194
541
|
exportContent += event.formatForExport()
|
|
195
542
|
exportContent += "\n"
|
|
196
543
|
}
|
|
197
|
-
|
|
544
|
+
|
|
198
545
|
exportContent += """
|
|
199
546
|
\(String(repeating: "=", count: 60))
|
|
200
547
|
End of Debug Session Export
|
|
201
548
|
"""
|
|
202
|
-
|
|
549
|
+
|
|
203
550
|
return exportContent
|
|
204
551
|
}
|
|
205
552
|
|
|
@@ -211,7 +558,35 @@ public extension ATTNNativeSDK {
|
|
|
211
558
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
212
559
|
|
|
213
560
|
if debuggingEnabled {
|
|
214
|
-
|
|
561
|
+
// Enhanced debug data to show parsed item details
|
|
562
|
+
var debugData: [String: Any] = [
|
|
563
|
+
"items_count": "\(items.count)",
|
|
564
|
+
"deeplink": deeplink,
|
|
565
|
+
"payload": attributes
|
|
566
|
+
]
|
|
567
|
+
|
|
568
|
+
// Add item details to debug output
|
|
569
|
+
if let firstItem = items.first {
|
|
570
|
+
var itemDetails: [String: Any] = [
|
|
571
|
+
"productId": firstItem.productId,
|
|
572
|
+
"productVariantId": firstItem.productVariantId
|
|
573
|
+
]
|
|
574
|
+
if let name = firstItem.name {
|
|
575
|
+
itemDetails["name"] = name
|
|
576
|
+
}
|
|
577
|
+
if let productImage = firstItem.productImage {
|
|
578
|
+
itemDetails["productImage"] = productImage
|
|
579
|
+
}
|
|
580
|
+
if let category = firstItem.category {
|
|
581
|
+
itemDetails["category"] = category
|
|
582
|
+
}
|
|
583
|
+
if let quantity = firstItem.quantity {
|
|
584
|
+
itemDetails["quantity"] = quantity
|
|
585
|
+
}
|
|
586
|
+
debugData["first_item"] = itemDetails
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
showDebugInfo(event: "Add To Cart Event", data: debugData)
|
|
215
590
|
}
|
|
216
591
|
}
|
|
217
592
|
|
|
@@ -223,7 +598,35 @@ public extension ATTNNativeSDK {
|
|
|
223
598
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
224
599
|
|
|
225
600
|
if debuggingEnabled {
|
|
226
|
-
|
|
601
|
+
// Enhanced debug data to show parsed item details
|
|
602
|
+
var debugData: [String: Any] = [
|
|
603
|
+
"items_count": "\(items.count)",
|
|
604
|
+
"deeplink": deeplink,
|
|
605
|
+
"payload": attributes
|
|
606
|
+
]
|
|
607
|
+
|
|
608
|
+
// Add item details to debug output
|
|
609
|
+
if let firstItem = items.first {
|
|
610
|
+
var itemDetails: [String: Any] = [
|
|
611
|
+
"productId": firstItem.productId,
|
|
612
|
+
"productVariantId": firstItem.productVariantId
|
|
613
|
+
]
|
|
614
|
+
if let name = firstItem.name {
|
|
615
|
+
itemDetails["name"] = name
|
|
616
|
+
}
|
|
617
|
+
if let productImage = firstItem.productImage {
|
|
618
|
+
itemDetails["productImage"] = productImage
|
|
619
|
+
}
|
|
620
|
+
if let category = firstItem.category {
|
|
621
|
+
itemDetails["category"] = category
|
|
622
|
+
}
|
|
623
|
+
if let quantity = firstItem.quantity {
|
|
624
|
+
itemDetails["quantity"] = quantity
|
|
625
|
+
}
|
|
626
|
+
debugData["first_item"] = itemDetails
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
showDebugInfo(event: "Product View Event", data: debugData)
|
|
227
630
|
}
|
|
228
631
|
}
|
|
229
632
|
|
|
@@ -237,7 +640,35 @@ public extension ATTNNativeSDK {
|
|
|
237
640
|
ATTNEventTracker.sharedInstance()?.record(event: event)
|
|
238
641
|
|
|
239
642
|
if debuggingEnabled {
|
|
240
|
-
|
|
643
|
+
// Enhanced debug data to show parsed item details
|
|
644
|
+
var debugData: [String: Any] = [
|
|
645
|
+
"items_count": "\(items.count)",
|
|
646
|
+
"order_id": orderId,
|
|
647
|
+
"payload": attributes
|
|
648
|
+
]
|
|
649
|
+
|
|
650
|
+
// Add item details to debug output
|
|
651
|
+
if let firstItem = items.first {
|
|
652
|
+
var itemDetails: [String: Any] = [
|
|
653
|
+
"productId": firstItem.productId,
|
|
654
|
+
"productVariantId": firstItem.productVariantId
|
|
655
|
+
]
|
|
656
|
+
if let name = firstItem.name {
|
|
657
|
+
itemDetails["name"] = name
|
|
658
|
+
}
|
|
659
|
+
if let productImage = firstItem.productImage {
|
|
660
|
+
itemDetails["productImage"] = productImage
|
|
661
|
+
}
|
|
662
|
+
if let category = firstItem.category {
|
|
663
|
+
itemDetails["category"] = category
|
|
664
|
+
}
|
|
665
|
+
if let quantity = firstItem.quantity {
|
|
666
|
+
itemDetails["quantity"] = quantity
|
|
667
|
+
}
|
|
668
|
+
debugData["first_item"] = itemDetails
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
showDebugInfo(event: "Purchase Event", data: debugData)
|
|
241
672
|
}
|
|
242
673
|
}
|
|
243
674
|
|
|
@@ -259,21 +690,38 @@ private extension ATTNNativeSDK {
|
|
|
259
690
|
var itemsToReturn: [ATTNItem] = []
|
|
260
691
|
|
|
261
692
|
for rawItem in rawItems {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
693
|
+
// Parse price - flattened structure (not nested)
|
|
694
|
+
guard let priceString = rawItem["price"] as? String,
|
|
695
|
+
let currency = rawItem["currency"] as? String,
|
|
696
|
+
let productId = rawItem["productId"] as? String,
|
|
697
|
+
let productVariantId = rawItem["productVariantId"] as? String else {
|
|
698
|
+
continue
|
|
699
|
+
}
|
|
265
700
|
|
|
266
|
-
|
|
701
|
+
let price = NSDecimalNumber(string: priceString)
|
|
702
|
+
let attnPrice = ATTNPrice(price: price, currency: currency)
|
|
267
703
|
|
|
268
|
-
|
|
704
|
+
let item = ATTNItem(productId: productId, productVariantId: productVariantId, price: attnPrice)
|
|
269
705
|
|
|
270
|
-
|
|
271
|
-
|
|
706
|
+
// Parse optional fields to match Android implementation
|
|
707
|
+
if let productImage = rawItem["productImage"] as? String {
|
|
708
|
+
item.productImage = productImage
|
|
709
|
+
}
|
|
272
710
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
711
|
+
if let name = rawItem["name"] as? String {
|
|
712
|
+
item.name = name
|
|
276
713
|
}
|
|
714
|
+
|
|
715
|
+
// React Native bridges JS numbers as NSNumber, so accept NSNumber directly
|
|
716
|
+
if let quantity = rawItem["quantity"] as? NSNumber {
|
|
717
|
+
item.quantity = quantity
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if let category = rawItem["category"] as? String {
|
|
721
|
+
item.category = category
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
itemsToReturn.append(item)
|
|
277
725
|
}
|
|
278
726
|
|
|
279
727
|
return itemsToReturn
|
|
@@ -380,7 +828,7 @@ class DebugOverlayViewController: UIViewController {
|
|
|
380
828
|
shareButton.addTarget(self, action: #selector(shareButtonTapped), for: .touchUpInside)
|
|
381
829
|
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
382
830
|
containerView.addSubview(shareButton)
|
|
383
|
-
|
|
831
|
+
|
|
384
832
|
// X Close button in top-right corner
|
|
385
833
|
let closeButton = UIButton(type: .system)
|
|
386
834
|
closeButton.setTitle("✕", for: .normal)
|
|
@@ -538,10 +986,10 @@ class DebugOverlayViewController: UIViewController {
|
|
|
538
986
|
@objc private func shareButtonTapped() {
|
|
539
987
|
// Generate export content for the current history
|
|
540
988
|
let exportContent = generateExportContent()
|
|
541
|
-
|
|
989
|
+
|
|
542
990
|
// Create activity view controller for sharing
|
|
543
991
|
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
544
|
-
|
|
992
|
+
|
|
545
993
|
// For iPad - prevent crash by setting popover presentation controller
|
|
546
994
|
if let popover = activityVC.popoverPresentationController {
|
|
547
995
|
// Find the share button view to anchor the popover
|
|
@@ -553,10 +1001,10 @@ class DebugOverlayViewController: UIViewController {
|
|
|
553
1001
|
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
554
1002
|
}
|
|
555
1003
|
}
|
|
556
|
-
|
|
1004
|
+
|
|
557
1005
|
present(activityVC, animated: true)
|
|
558
1006
|
}
|
|
559
|
-
|
|
1007
|
+
|
|
560
1008
|
/**
|
|
561
1009
|
* Generates formatted export content for sharing
|
|
562
1010
|
* @return Formatted string containing all debug events
|
|
@@ -565,32 +1013,32 @@ class DebugOverlayViewController: UIViewController {
|
|
|
565
1013
|
if history.isEmpty {
|
|
566
1014
|
return "No debug events recorded in this session."
|
|
567
1015
|
}
|
|
568
|
-
|
|
1016
|
+
|
|
569
1017
|
let formatter = DateFormatter()
|
|
570
1018
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
571
1019
|
let exportDate = formatter.string(from: Date())
|
|
572
|
-
|
|
1020
|
+
|
|
573
1021
|
var exportContent = """
|
|
574
1022
|
Attentive React Native SDK - Debug Session Export
|
|
575
1023
|
Generated: \(exportDate)
|
|
576
1024
|
Total Events: \(history.count)
|
|
577
|
-
|
|
1025
|
+
|
|
578
1026
|
\(String(repeating: "=", count: 60))
|
|
579
|
-
|
|
1027
|
+
|
|
580
1028
|
"""
|
|
581
|
-
|
|
1029
|
+
|
|
582
1030
|
// Add all events in chronological order (oldest first for better readability)
|
|
583
1031
|
for (index, event) in history.enumerated() {
|
|
584
1032
|
exportContent += "Event #\(index + 1)\n"
|
|
585
1033
|
exportContent += event.formatForExport()
|
|
586
1034
|
exportContent += "\n"
|
|
587
1035
|
}
|
|
588
|
-
|
|
1036
|
+
|
|
589
1037
|
exportContent += """
|
|
590
1038
|
\(String(repeating: "=", count: 60))
|
|
591
1039
|
End of Debug Session Export
|
|
592
1040
|
"""
|
|
593
|
-
|
|
1041
|
+
|
|
594
1042
|
return exportContent
|
|
595
1043
|
}
|
|
596
1044
|
|
|
@@ -772,7 +1220,7 @@ class EventDetailViewController: UIViewController {
|
|
|
772
1220
|
shareButton.addTarget(self, action: #selector(shareEventButtonTapped), for: .touchUpInside)
|
|
773
1221
|
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
774
1222
|
containerView.addSubview(shareButton)
|
|
775
|
-
|
|
1223
|
+
|
|
776
1224
|
let closeButton = UIButton(type: .system)
|
|
777
1225
|
closeButton.setTitle("✕", for: .normal)
|
|
778
1226
|
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
@@ -834,19 +1282,19 @@ class EventDetailViewController: UIViewController {
|
|
|
834
1282
|
*/
|
|
835
1283
|
@objc private func shareEventButtonTapped() {
|
|
836
1284
|
let exportContent = generateSingleEventExport()
|
|
837
|
-
|
|
1285
|
+
|
|
838
1286
|
// Create activity view controller for sharing
|
|
839
1287
|
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
840
|
-
|
|
1288
|
+
|
|
841
1289
|
// For iPad - prevent crash by setting popover presentation controller
|
|
842
1290
|
if let popover = activityVC.popoverPresentationController {
|
|
843
1291
|
popover.sourceView = view
|
|
844
1292
|
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
845
1293
|
}
|
|
846
|
-
|
|
1294
|
+
|
|
847
1295
|
present(activityVC, animated: true)
|
|
848
1296
|
}
|
|
849
|
-
|
|
1297
|
+
|
|
850
1298
|
/**
|
|
851
1299
|
* Generates formatted export content for a single event
|
|
852
1300
|
* @return Formatted string containing the single debug event
|
|
@@ -855,18 +1303,18 @@ class EventDetailViewController: UIViewController {
|
|
|
855
1303
|
let formatter = DateFormatter()
|
|
856
1304
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
857
1305
|
let exportDate = formatter.string(from: Date())
|
|
858
|
-
|
|
1306
|
+
|
|
859
1307
|
let eventContent = """
|
|
860
1308
|
Attentive React Native SDK - Single Event Export
|
|
861
1309
|
Generated: \(exportDate)
|
|
862
|
-
|
|
1310
|
+
|
|
863
1311
|
\(String(repeating: "=", count: 60))
|
|
864
|
-
|
|
1312
|
+
|
|
865
1313
|
\(event.formatForExport())
|
|
866
1314
|
\(String(repeating: "=", count: 60))
|
|
867
1315
|
End of Single Event Export
|
|
868
1316
|
"""
|
|
869
|
-
|
|
1317
|
+
|
|
870
1318
|
return eventContent
|
|
871
1319
|
}
|
|
872
1320
|
|