@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
|
@@ -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
|
|
|
@@ -380,7 +727,7 @@ class DebugOverlayViewController: UIViewController {
|
|
|
380
727
|
shareButton.addTarget(self, action: #selector(shareButtonTapped), for: .touchUpInside)
|
|
381
728
|
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
382
729
|
containerView.addSubview(shareButton)
|
|
383
|
-
|
|
730
|
+
|
|
384
731
|
// X Close button in top-right corner
|
|
385
732
|
let closeButton = UIButton(type: .system)
|
|
386
733
|
closeButton.setTitle("✕", for: .normal)
|
|
@@ -538,10 +885,10 @@ class DebugOverlayViewController: UIViewController {
|
|
|
538
885
|
@objc private func shareButtonTapped() {
|
|
539
886
|
// Generate export content for the current history
|
|
540
887
|
let exportContent = generateExportContent()
|
|
541
|
-
|
|
888
|
+
|
|
542
889
|
// Create activity view controller for sharing
|
|
543
890
|
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
544
|
-
|
|
891
|
+
|
|
545
892
|
// For iPad - prevent crash by setting popover presentation controller
|
|
546
893
|
if let popover = activityVC.popoverPresentationController {
|
|
547
894
|
// Find the share button view to anchor the popover
|
|
@@ -553,10 +900,10 @@ class DebugOverlayViewController: UIViewController {
|
|
|
553
900
|
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
554
901
|
}
|
|
555
902
|
}
|
|
556
|
-
|
|
903
|
+
|
|
557
904
|
present(activityVC, animated: true)
|
|
558
905
|
}
|
|
559
|
-
|
|
906
|
+
|
|
560
907
|
/**
|
|
561
908
|
* Generates formatted export content for sharing
|
|
562
909
|
* @return Formatted string containing all debug events
|
|
@@ -565,32 +912,32 @@ class DebugOverlayViewController: UIViewController {
|
|
|
565
912
|
if history.isEmpty {
|
|
566
913
|
return "No debug events recorded in this session."
|
|
567
914
|
}
|
|
568
|
-
|
|
915
|
+
|
|
569
916
|
let formatter = DateFormatter()
|
|
570
917
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
571
918
|
let exportDate = formatter.string(from: Date())
|
|
572
|
-
|
|
919
|
+
|
|
573
920
|
var exportContent = """
|
|
574
921
|
Attentive React Native SDK - Debug Session Export
|
|
575
922
|
Generated: \(exportDate)
|
|
576
923
|
Total Events: \(history.count)
|
|
577
|
-
|
|
924
|
+
|
|
578
925
|
\(String(repeating: "=", count: 60))
|
|
579
|
-
|
|
926
|
+
|
|
580
927
|
"""
|
|
581
|
-
|
|
928
|
+
|
|
582
929
|
// Add all events in chronological order (oldest first for better readability)
|
|
583
930
|
for (index, event) in history.enumerated() {
|
|
584
931
|
exportContent += "Event #\(index + 1)\n"
|
|
585
932
|
exportContent += event.formatForExport()
|
|
586
933
|
exportContent += "\n"
|
|
587
934
|
}
|
|
588
|
-
|
|
935
|
+
|
|
589
936
|
exportContent += """
|
|
590
937
|
\(String(repeating: "=", count: 60))
|
|
591
938
|
End of Debug Session Export
|
|
592
939
|
"""
|
|
593
|
-
|
|
940
|
+
|
|
594
941
|
return exportContent
|
|
595
942
|
}
|
|
596
943
|
|
|
@@ -772,7 +1119,7 @@ class EventDetailViewController: UIViewController {
|
|
|
772
1119
|
shareButton.addTarget(self, action: #selector(shareEventButtonTapped), for: .touchUpInside)
|
|
773
1120
|
shareButton.translatesAutoresizingMaskIntoConstraints = false
|
|
774
1121
|
containerView.addSubview(shareButton)
|
|
775
|
-
|
|
1122
|
+
|
|
776
1123
|
let closeButton = UIButton(type: .system)
|
|
777
1124
|
closeButton.setTitle("✕", for: .normal)
|
|
778
1125
|
closeButton.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
|
@@ -834,19 +1181,19 @@ class EventDetailViewController: UIViewController {
|
|
|
834
1181
|
*/
|
|
835
1182
|
@objc private func shareEventButtonTapped() {
|
|
836
1183
|
let exportContent = generateSingleEventExport()
|
|
837
|
-
|
|
1184
|
+
|
|
838
1185
|
// Create activity view controller for sharing
|
|
839
1186
|
let activityVC = UIActivityViewController(activityItems: [exportContent], applicationActivities: nil)
|
|
840
|
-
|
|
1187
|
+
|
|
841
1188
|
// For iPad - prevent crash by setting popover presentation controller
|
|
842
1189
|
if let popover = activityVC.popoverPresentationController {
|
|
843
1190
|
popover.sourceView = view
|
|
844
1191
|
popover.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
|
|
845
1192
|
}
|
|
846
|
-
|
|
1193
|
+
|
|
847
1194
|
present(activityVC, animated: true)
|
|
848
1195
|
}
|
|
849
|
-
|
|
1196
|
+
|
|
850
1197
|
/**
|
|
851
1198
|
* Generates formatted export content for a single event
|
|
852
1199
|
* @return Formatted string containing the single debug event
|
|
@@ -855,18 +1202,18 @@ class EventDetailViewController: UIViewController {
|
|
|
855
1202
|
let formatter = DateFormatter()
|
|
856
1203
|
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
857
1204
|
let exportDate = formatter.string(from: Date())
|
|
858
|
-
|
|
1205
|
+
|
|
859
1206
|
let eventContent = """
|
|
860
1207
|
Attentive React Native SDK - Single Event Export
|
|
861
1208
|
Generated: \(exportDate)
|
|
862
|
-
|
|
1209
|
+
|
|
863
1210
|
\(String(repeating: "=", count: 60))
|
|
864
|
-
|
|
1211
|
+
|
|
865
1212
|
\(event.formatForExport())
|
|
866
1213
|
\(String(repeating: "=", count: 60))
|
|
867
1214
|
End of Single Event Export
|
|
868
1215
|
"""
|
|
869
|
-
|
|
1216
|
+
|
|
870
1217
|
return eventContent
|
|
871
1218
|
}
|
|
872
1219
|
|