@attentive-mobile/attentive-react-native-sdk 2.0.0-beta.5 → 2.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +117 -11
  2. package/android/build.gradle +4 -1
  3. package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveNotificationStore.kt +60 -0
  4. package/android/src/main/kotlin/com/attentivereactnativesdk/AttentivePushHelper.kt +15 -7
  5. package/android/src/main/kotlin/com/attentivereactnativesdk/AttentiveReactNativeSdkModule.kt +370 -140
  6. package/android/src/test/kotlin/com/attentivereactnativesdk/AttentiveNotificationStoreTest.kt +103 -0
  7. package/attentive-react-native-sdk.podspec +1 -1
  8. package/ios/AttentiveReactNativeSdk.mm +17 -2
  9. package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcuserdata/zheref.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  10. package/ios/Bridging/ATTNNativeSDK.swift +116 -46
  11. package/ios/Bridging/AttentiveSDKManager.swift +196 -27
  12. package/ios/Podfile +1 -1
  13. package/lib/commonjs/NativeAttentiveReactNativeSdk.js +1 -1
  14. package/lib/commonjs/NativeAttentiveReactNativeSdk.js.map +1 -1
  15. package/lib/commonjs/eventTypes.js.map +1 -1
  16. package/lib/commonjs/index.js +50 -17
  17. package/lib/commonjs/index.js.map +1 -1
  18. package/lib/module/NativeAttentiveReactNativeSdk.js +2 -2
  19. package/lib/module/NativeAttentiveReactNativeSdk.js.map +1 -1
  20. package/lib/module/eventTypes.js.map +1 -1
  21. package/lib/module/index.js +50 -18
  22. package/lib/module/index.js.map +1 -1
  23. package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts +12 -1
  24. package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts.map +1 -1
  25. package/lib/typescript/index.d.ts +46 -18
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/package.json +3 -2
  28. package/src/NativeAttentiveReactNativeSdk.ts +69 -52
  29. package/src/index.tsx +53 -17
@@ -1,5 +1,5 @@
1
- import type { TurboModule } from "react-native/Libraries/TurboModule/RCTExport";
2
- import { TurboModuleRegistry, NativeModules } from "react-native";
1
+ import type { TurboModule } from 'react-native/Libraries/TurboModule/RCTExport'
2
+ import { TurboModuleRegistry, NativeModules } from 'react-native'
3
3
 
4
4
  export interface Spec extends TurboModule {
5
5
  initialize: (
@@ -7,10 +7,10 @@ export interface Spec extends TurboModule {
7
7
  mode: string,
8
8
  skipFatigueOnCreatives: boolean,
9
9
  enableDebugger: boolean
10
- ) => void;
11
- triggerCreative: (creativeId?: string) => void;
12
- destroyCreative: () => void;
13
- updateDomain: (domain: string) => void;
10
+ ) => void
11
+ triggerCreative: (creativeId?: string) => void
12
+ destroyCreative: () => void
13
+ updateDomain: (domain: string) => void
14
14
  identify: (
15
15
  phone?: string,
16
16
  email?: string,
@@ -18,52 +18,52 @@ export interface Spec extends TurboModule {
18
18
  shopifyId?: string,
19
19
  clientUserId?: string,
20
20
  customIdentifiers?: Object
21
- ) => void;
22
- clearUser: () => void;
21
+ ) => void
22
+ clearUser: () => void
23
23
  recordAddToCartEvent: (
24
24
  items: Array<{
25
- productId: string;
26
- productVariantId: string;
27
- price: string;
28
- currency: string;
29
- productImage?: string;
30
- name?: string;
31
- quantity?: number;
32
- category?: string;
25
+ productId: string
26
+ productVariantId: string
27
+ price: string
28
+ currency: string
29
+ productImage?: string
30
+ name?: string
31
+ quantity?: number
32
+ category?: string
33
33
  }>,
34
34
  deeplink?: string
35
- ) => void;
35
+ ) => void
36
36
  recordProductViewEvent: (
37
37
  items: Array<{
38
- productId: string;
39
- productVariantId: string;
40
- price: string;
41
- currency: string;
42
- productImage?: string;
43
- name?: string;
44
- quantity?: number;
45
- category?: string;
38
+ productId: string
39
+ productVariantId: string
40
+ price: string
41
+ currency: string
42
+ productImage?: string
43
+ name?: string
44
+ quantity?: number
45
+ category?: string
46
46
  }>,
47
47
  deeplink?: string
48
- ) => void;
48
+ ) => void
49
49
  recordPurchaseEvent: (
50
50
  items: Array<{
51
- productId: string;
52
- productVariantId: string;
53
- price: string;
54
- currency: string;
55
- productImage?: string;
56
- name?: string;
57
- quantity?: number;
58
- category?: string;
51
+ productId: string
52
+ productVariantId: string
53
+ price: string
54
+ currency: string
55
+ productImage?: string
56
+ name?: string
57
+ quantity?: number
58
+ category?: string
59
59
  }>,
60
60
  orderId: string,
61
61
  cartId?: string,
62
62
  cartCoupon?: string
63
- ) => void;
64
- recordCustomEvent: (type: string, properties: Object) => void;
65
- invokeAttentiveDebugHelper: () => void;
66
- exportDebugLogs: () => Promise<string>;
63
+ ) => void
64
+ recordCustomEvent: (type: string, properties: Object) => void
65
+ invokeAttentiveDebugHelper: () => void
66
+ exportDebugLogs: () => Promise<string>
67
67
 
68
68
  // Push Notification Methods
69
69
  /**
@@ -71,7 +71,7 @@ export interface Spec extends TurboModule {
71
71
  * On iOS, triggers the system permission dialog.
72
72
  * On Android 13+, requests POST_NOTIFICATIONS; on older versions, no-op.
73
73
  */
74
- registerForPushNotifications: () => void;
74
+ registerForPushNotifications: () => void
75
75
 
76
76
  /**
77
77
  * Get the current push notification authorization status.
@@ -79,7 +79,7 @@ export interface Spec extends TurboModule {
79
79
  * On iOS, use PushNotificationIOS.checkPermissions instead; this method is for Android parity.
80
80
  * @returns Promise resolving to 'authorized' | 'denied' | 'notDetermined'
81
81
  */
82
- getPushAuthorizationStatus: () => Promise<string>;
82
+ getPushAuthorizationStatus: () => Promise<string>
83
83
 
84
84
  /**
85
85
  * Register the device token received from APNs (simple version without callback).
@@ -87,7 +87,7 @@ export interface Spec extends TurboModule {
87
87
  * @param token - The hex-encoded device token string
88
88
  * @param authorizationStatus - Current push authorization status
89
89
  */
90
- registerDeviceToken: (token: string, authorizationStatus: string) => void;
90
+ registerDeviceToken: (token: string, authorizationStatus: string) => void
91
91
 
92
92
  /**
93
93
  * Register the device token received from APNs with a callback.
@@ -100,8 +100,13 @@ export interface Spec extends TurboModule {
100
100
  registerDeviceTokenWithCallback: (
101
101
  token: string,
102
102
  authorizationStatus: string,
103
- callback: (data?: Object, url?: string, response?: Object, error?: Object) => void
104
- ) => void;
103
+ callback: (
104
+ data?: Object,
105
+ url?: string,
106
+ response?: Object,
107
+ error?: Object
108
+ ) => void
109
+ ) => void
105
110
 
106
111
  /**
107
112
  * Handle regular/direct app open (not from a push notification).
@@ -109,7 +114,7 @@ export interface Spec extends TurboModule {
109
114
  * This should be called after device token registration to track app opens.
110
115
  * @param authorizationStatus - Current push authorization status
111
116
  */
112
- handleRegularOpen: (authorizationStatus: string) => void;
117
+ handleRegularOpen: (authorizationStatus: string) => void
113
118
 
114
119
  /**
115
120
  * Handle when a push notification is opened by the user.
@@ -122,14 +127,14 @@ export interface Spec extends TurboModule {
122
127
  userInfo: Object,
123
128
  applicationState: string,
124
129
  authorizationStatus: string
125
- ) => void;
130
+ ) => void
126
131
 
127
132
  /**
128
133
  * Handle when a push notification arrives while the app is in foreground.
129
134
  * iOS only - Android is a no-op.
130
135
  * @param userInfo - The notification payload
131
136
  */
132
- handleForegroundNotification: (userInfo: Object) => void;
137
+ handleForegroundNotification: (userInfo: Object) => void
133
138
 
134
139
  /**
135
140
  * Handle a push notification when the app is in the foreground (active state).
@@ -138,7 +143,7 @@ export interface Spec extends TurboModule {
138
143
  * @param userInfo - The notification payload
139
144
  * @param authorizationStatus - Current push authorization status
140
145
  */
141
- handleForegroundPush: (userInfo: Object, authorizationStatus: string) => void;
146
+ handleForegroundPush: (userInfo: Object, authorizationStatus: string) => void
142
147
 
143
148
  /**
144
149
  * Handle when a push notification is opened by the user (app in background/inactive state).
@@ -147,15 +152,27 @@ export interface Spec extends TurboModule {
147
152
  * @param userInfo - The notification payload
148
153
  * @param authorizationStatus - Current push authorization status
149
154
  */
150
- handlePushOpen: (userInfo: Object, authorizationStatus: string) => void;
155
+ handlePushOpen: (userInfo: Object, authorizationStatus: string) => void
156
+
157
+ /**
158
+ * Returns the push notification payload that launched the app from a killed state
159
+ * (i.e. the user tapped a notification when the app was not running), then clears it
160
+ * so it is only delivered once.
161
+ *
162
+ * Android only — on iOS use `PushNotificationIOS.getInitialNotification()` instead.
163
+ *
164
+ * @returns Promise resolving to a notification data map, or null if the app was not
165
+ * launched from a push notification tap.
166
+ */
167
+ getInitialPushNotification: () => Promise<Object | null>
151
168
  }
152
169
 
153
170
  // Try to load via TurboModule first (new architecture)
154
171
  // Fall back to NativeModules for old architecture
155
- const isTurboModuleEnabled = (global as any).__turboModuleProxy != null;
172
+ const isTurboModuleEnabled = (global as any).__turboModuleProxy != null
156
173
 
157
174
  const AttentiveReactNativeSdkModule = isTurboModuleEnabled
158
- ? TurboModuleRegistry.get<Spec>("AttentiveReactNativeSdk")
159
- : NativeModules.AttentiveReactNativeSdk;
175
+ ? TurboModuleRegistry.get<Spec>('AttentiveReactNativeSdk')
176
+ : NativeModules.AttentiveReactNativeSdk
160
177
 
161
- export default AttentiveReactNativeSdkModule as Spec | null;
178
+ export default AttentiveReactNativeSdkModule as Spec | null
package/src/index.tsx CHANGED
@@ -39,7 +39,12 @@ const AttentiveReactNativeSdk = (
39
39
  ) as Spec
40
40
 
41
41
  /**
42
- * Initialize the Attentive SDK with the provided configuration
42
+ * Initialize the Attentive SDK with the provided configuration.
43
+ * This is the only supported entry point: the app (e.g. Bonni) must call this from TypeScript
44
+ * once at startup; the call is forwarded to the native module on each platform (iOS/Android),
45
+ * which then initializes the platform Attentive SDK. Native code must not initialize the SDK
46
+ * on its own (e.g. in Application onCreate or AppDelegate).
47
+ *
43
48
  * @param configuration - Configuration object for the Attentive SDK
44
49
  */
45
50
  function initialize(configuration: AttentiveSdkConfiguration) {
@@ -380,17 +385,16 @@ function handleForegroundNotification(
380
385
 
381
386
  /**
382
387
  * Handle a push notification when the app is in the foreground (active state).
383
- * This is the React Native equivalent of the native iOS handleForegroundPush method.
384
388
  *
385
389
  * Call this when you receive a notification response and the app state is 'active'.
386
- * This is part of implementing the native iOS pattern:
387
- * ```swift
388
- * case .active:
389
- * self.attentiveSdk?.handleForegroundPush(response: response, authorizationStatus: authStatus)
390
- * ```
391
390
  *
392
- * On iOS, this properly tracks foreground push notifications.
393
- * On Android, registers the FCM token when provided by the host app.
391
+ * **iOS prerequisite:** Your AppDelegate's
392
+ * `userNotificationCenter(_:didReceive:withCompletionHandler:)` must call
393
+ * `AttentiveSDKManager.shared.handleNotificationResponse(response)` so that
394
+ * the SDK can cache the `UNNotificationResponse` required by the native iOS SDK.
395
+ * Without that one line of native code, this function cannot track the event.
396
+ *
397
+ * On Android, this tracks the foreground push as a custom event.
394
398
  *
395
399
  * @param userInfo - The notification payload from the push notification
396
400
  * @param authorizationStatus - Current push authorization status
@@ -419,17 +423,16 @@ function handleForegroundPush(
419
423
 
420
424
  /**
421
425
  * Handle when a push notification is opened by the user (app in background/inactive state).
422
- * This is the React Native equivalent of the native iOS handlePushOpen method.
423
426
  *
424
427
  * Call this when you receive a notification response and the app state is 'background' or 'inactive'.
425
- * This is part of implementing the native iOS pattern:
426
- * ```swift
427
- * case .background, .inactive:
428
- * self.attentiveSdk?.handlePushOpen(response: response, authorizationStatus: authStatus)
429
- * ```
430
428
  *
431
- * On iOS, this properly tracks push notification opens.
432
- * On Android, registers the FCM token when provided by the host app.
429
+ * **iOS prerequisite:** Your AppDelegate's
430
+ * `userNotificationCenter(_:didReceive:withCompletionHandler:)` must call
431
+ * `AttentiveSDKManager.shared.handleNotificationResponse(response)` so that
432
+ * the SDK can cache the `UNNotificationResponse` required by the native iOS SDK.
433
+ * Without that one line of native code, this function cannot track the event.
434
+ *
435
+ * On Android, this tracks the push open as a custom event.
433
436
  *
434
437
  * @param userInfo - The notification payload from the push notification
435
438
  * @param authorizationStatus - Current push authorization status
@@ -456,6 +459,38 @@ function handlePushOpen(
456
459
  )
457
460
  }
458
461
 
462
+ /**
463
+ * Returns the push notification payload that launched the app from a killed state
464
+ * (i.e. the user tapped an FCM notification while the app was not running) and clears
465
+ * it so it is only delivered once.
466
+ *
467
+ * **Android only.** On iOS, use `PushNotificationIOS.getInitialNotification()` to
468
+ * achieve the same result — the Attentive iOS SDK event is tracked natively in
469
+ * `AppDelegate.userNotificationCenter(_:didReceive:withCompletionHandler:)` via
470
+ * `AttentiveSDKManager.shared`.
471
+ *
472
+ * Call this once at app startup (after `initialize()`) to detect and handle the
473
+ * killed-state push-open scenario:
474
+ *
475
+ * ```typescript
476
+ * const initial = await getInitialPushNotification()
477
+ * if (initial) {
478
+ * const authStatus = await getPushAuthorizationStatus()
479
+ * handlePushOpen(initial as PushNotificationUserInfo, authStatus)
480
+ * }
481
+ * ```
482
+ *
483
+ * @returns A promise that resolves to the notification data object, or `null` if the
484
+ * app was not launched via a push notification tap.
485
+ */
486
+ async function getInitialPushNotification(): Promise<Record<
487
+ string,
488
+ string
489
+ > | null> {
490
+ const result = await AttentiveReactNativeSdk.getInitialPushNotification()
491
+ return result as Record<string, string> | null
492
+ }
493
+
459
494
  export {
460
495
  initialize,
461
496
  triggerCreative,
@@ -479,6 +514,7 @@ export {
479
514
  handleForegroundNotification,
480
515
  handleForegroundPush,
481
516
  handlePushOpen,
517
+ getInitialPushNotification,
482
518
  }
483
519
 
484
520
  export type {