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

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 (26) hide show
  1. package/README.md +3 -2
  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 +168 -136
  6. package/android/src/test/kotlin/com/attentivereactnativesdk/AttentiveNotificationStoreTest.kt +103 -0
  7. package/ios/AttentiveReactNativeSdk.mm +17 -2
  8. package/ios/AttentiveReactNativeSdk.xcodeproj/project.xcworkspace/xcuserdata/zheref.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  9. package/ios/Bridging/ATTNNativeSDK.swift +35 -28
  10. package/lib/commonjs/NativeAttentiveReactNativeSdk.js +1 -1
  11. package/lib/commonjs/NativeAttentiveReactNativeSdk.js.map +1 -1
  12. package/lib/commonjs/eventTypes.js.map +1 -1
  13. package/lib/commonjs/index.js +36 -1
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/module/NativeAttentiveReactNativeSdk.js +2 -2
  16. package/lib/module/NativeAttentiveReactNativeSdk.js.map +1 -1
  17. package/lib/module/eventTypes.js.map +1 -1
  18. package/lib/module/index.js +36 -2
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts +12 -1
  21. package/lib/typescript/NativeAttentiveReactNativeSdk.d.ts.map +1 -1
  22. package/lib/typescript/index.d.ts +32 -2
  23. package/lib/typescript/index.d.ts.map +1 -1
  24. package/package.json +3 -2
  25. package/src/NativeAttentiveReactNativeSdk.ts +69 -52
  26. package/src/index.tsx +36 -1
package/README.md CHANGED
@@ -270,12 +270,13 @@ return () => subscription.remove();
270
270
 
271
271
  #### 3. Optional: Register FCM token (Android)
272
272
 
273
- If your app uses Firebase Cloud Messaging and you have an FCM token, register it with the Attentive backend and then call `handleRegularOpen` in the callback (same pattern as iOS):
273
+ **Recommended:** This React Native SDK’s Android native module depends on Attentive Android SDK **2.1.1**, which exposes `AttentiveSdk.getPushTokenWithCallback`. Calling `registerForPushNotifications()` from JS triggers that API: the SDK requests permission (when needed), fetches the FCM token, and registers it with Attentive. No separate native code is required.
274
+
275
+ **Alternative (token from JS):** If you obtain the FCM token elsewhere (e.g. Firebase Messaging), use `registerDeviceTokenWithCallback` and then call `handleRegularOpen` in the callback:
274
276
 
275
277
  ```typescript
276
278
  import { registerDeviceTokenWithCallback, handleRegularOpen } from 'attentive-react-native-sdk';
277
279
 
278
- // When you receive the FCM token (e.g. from Firebase Messaging):
279
280
  getPushAuthorizationStatus().then((authStatus) => {
280
281
  registerDeviceTokenWithCallback(
281
282
  fcmToken,
@@ -82,7 +82,10 @@ dependencies {
82
82
  // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
83
83
  //noinspection GradleDynamicVersion
84
84
  implementation "com.facebook.react:react-native:+"
85
- implementation 'com.attentive:attentive-android-sdk:1.0.1'
85
+ // Use `api` so that the Attentive Android SDK types (AttentiveSdk, CustomEvent, etc.)
86
+ // are visible to app-level code (e.g. Bonni's AttentiveFirebaseMessagingService) that
87
+ // depends on this library and needs to call the SDK directly from native components.
88
+ api 'com.attentive:attentive-android-sdk:2.1.3-beta.1'
86
89
  implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.10"
87
90
  implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.9.10"))
88
91
 
@@ -0,0 +1,60 @@
1
+ package com.attentivereactnativesdk
2
+
3
+ /**
4
+ * In-process store for a pending initial push notification payload.
5
+ *
6
+ * When the user taps an FCM notification while the app is in the killed state,
7
+ * Android launches the app's main activity before the React Native bridge is
8
+ * initialised. The notification payload is written here by the host app's
9
+ * `MainActivity` and consumed exactly once by [AttentiveReactNativeSdkModule.getInitialPushNotification]
10
+ * after the JS layer is ready.
11
+ *
12
+ * ## Usage pattern
13
+ *
14
+ * ```kotlin
15
+ * // In host app's MainActivity.onCreate:
16
+ * intent?.extras?.let { extras ->
17
+ * val payload = // … extract FCM data …
18
+ * AttentiveNotificationStore.setPendingInitialNotification(payload)
19
+ * }
20
+ * ```
21
+ *
22
+ * ```typescript
23
+ * // In JS (App.tsx), after initialize():
24
+ * const initial = await getInitialPushNotification()
25
+ * if (initial) handlePushOpen(initial, authStatus)
26
+ * ```
27
+ *
28
+ * Thread-safety: the two public methods are `@Synchronized` so concurrent access
29
+ * from the main UI thread (writer) and the JS bridge thread (reader) is safe.
30
+ */
31
+ object AttentiveNotificationStore {
32
+
33
+ @Volatile
34
+ private var pendingInitialNotification: Map<String, String>? = null
35
+
36
+ /**
37
+ * Stores [payload] as the pending initial push notification.
38
+ *
39
+ * Replaces any previously stored value (only one initial notification is tracked at a time).
40
+ *
41
+ * @param payload A map of string key-value pairs representing the notification data.
42
+ */
43
+ @Synchronized
44
+ fun setPendingInitialNotification(payload: Map<String, String>) {
45
+ pendingInitialNotification = payload
46
+ }
47
+
48
+ /**
49
+ * Returns the stored initial push notification payload and clears it atomically,
50
+ * ensuring the payload is delivered to the JS layer exactly once.
51
+ *
52
+ * @return The stored payload map, or `null` if no initial notification is pending.
53
+ */
54
+ @Synchronized
55
+ fun getAndClear(): Map<String, String>? {
56
+ val pending = pendingInitialNotification
57
+ pendingInitialNotification = null
58
+ return pending
59
+ }
60
+ }
@@ -26,8 +26,8 @@ object AttentivePushHelper {
26
26
  /**
27
27
  * Authorization status values aligned with iOS push authorization for use in handleRegularOpen etc.
28
28
  * - "authorized" – user has granted notification permission (or API < 33)
29
- * - "denied" – user denied or permission not granted
30
- * - "notDetermined" – not yet requested (API 33+ only)
29
+ * - "denied" – user was asked and denied (API 33+, when determinable via activity)
30
+ * - "notDetermined" – not yet requested, or unable to distinguish (API 33+ only)
31
31
  */
32
32
  const val STATUS_AUTHORIZED = "authorized"
33
33
  const val STATUS_DENIED = "denied"
@@ -36,14 +36,18 @@ object AttentivePushHelper {
36
36
  /**
37
37
  * Returns the current push notification authorization status.
38
38
  *
39
- * On API 33+: uses [android.permission.POST_NOTIFICATIONS].
39
+ * On API 33+: uses [android.permission.POST_NOTIFICATIONS]. When permission is not granted,
40
+ * uses [activity] (when provided) and [ActivityCompat.shouldShowRequestPermissionRationale]
41
+ * to distinguish "denied" (user was asked and declined) from "notDetermined" (not yet asked).
40
42
  * On API < 33: returns [STATUS_AUTHORIZED] (no runtime permission required).
41
43
  *
42
44
  * @param context Application or Activity context
45
+ * @param activity Current activity, or null. When non-null on API 33+, used to detect
46
+ * "denied" vs "notDetermined" so downstream logic and analytics are correct for denied users.
43
47
  * @return One of [STATUS_AUTHORIZED], [STATUS_DENIED], or [STATUS_NOT_DETERMINED]
44
48
  */
45
49
  @JvmStatic
46
- fun getAuthorizationStatus(context: Context): String {
50
+ fun getAuthorizationStatus(context: Context, activity: Activity? = null): String {
47
51
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
48
52
  // API 32 and below: notification permission not required at runtime
49
53
  return STATUS_AUTHORIZED
@@ -51,9 +55,13 @@ object AttentivePushHelper {
51
55
  return when (ContextCompat.checkSelfPermission(context, android.Manifest.permission.POST_NOTIFICATIONS)) {
52
56
  PackageManager.PERMISSION_GRANTED -> STATUS_AUTHORIZED
53
57
  else -> {
54
- // Not granted. Without an Activity we cannot distinguish "denied" vs "not yet asked".
55
- // Return notDetermined so the app can call registerForPushNotifications to request.
56
- STATUS_NOT_DETERMINED
58
+ // Not granted. Use shouldShowRequestPermissionRationale when we have an Activity
59
+ // so we do not report "denied" users as "notDetermined" (fixes prompt-gating and analytics).
60
+ if (activity != null && ActivityCompat.shouldShowRequestPermissionRationale(activity, android.Manifest.permission.POST_NOTIFICATIONS)) {
61
+ STATUS_DENIED
62
+ } else {
63
+ STATUS_NOT_DETERMINED
64
+ }
57
65
  }
58
66
  }
59
67
  }