react-native-bug-reporter 1.0.2 → 1.0.4

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.
@@ -3,9 +3,14 @@
3
3
  <!-- Official screenshot detection on Android 14+ (no runtime prompt). -->
4
4
  <uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />
5
5
 
6
- <!-- MediaStore fallback for screenshot detection on Android 13 and below. -->
6
+ <!-- MediaStore fallback for screenshot detection on Android 13. -->
7
7
  <uses-permission
8
8
  android:name="android.permission.READ_MEDIA_IMAGES"
9
9
  android:maxSdkVersion="33" />
10
10
 
11
+ <!-- MediaStore fallback for Android 12L and below (older devices). -->
12
+ <uses-permission
13
+ android:name="android.permission.READ_EXTERNAL_STORAGE"
14
+ android:maxSdkVersion="32" />
15
+
11
16
  </manifest>
@@ -0,0 +1,34 @@
1
+ package com.bugreporter
2
+
3
+ import android.app.Activity
4
+ import android.os.Handler
5
+ import java.util.concurrent.Executor
6
+
7
+ /**
8
+ * Isolated wrapper around the API 34+ `Activity.ScreenCaptureCallback`.
9
+ *
10
+ * IMPORTANT: this class is the ONLY place that references
11
+ * `Activity.ScreenCaptureCallback`. The main module must never reference that
12
+ * type directly — otherwise, on Android 13 and below where the class does not
13
+ * exist, the runtime's reflection over the module's methods throws
14
+ * `NoClassDefFoundError` and the whole native module fails to load.
15
+ *
16
+ * This class is only ever instantiated inside an `SDK_INT >= 34` guard, so it
17
+ * (and the missing framework class) is never loaded on older devices.
18
+ */
19
+ class ScreenCaptureCallbackApi34(
20
+ private val activity: Activity,
21
+ private val handler: Handler,
22
+ private val onCapture: () -> Unit,
23
+ ) {
24
+ private val callback = Activity.ScreenCaptureCallback { onCapture() }
25
+
26
+ fun register() {
27
+ val executor = Executor { command -> handler.post(command) }
28
+ activity.registerScreenCaptureCallback(executor, callback)
29
+ }
30
+
31
+ fun unregister() {
32
+ activity.unregisterScreenCaptureCallback(callback)
33
+ }
34
+ }
@@ -1,6 +1,8 @@
1
1
  package com.bugreporter
2
2
 
3
+ import android.Manifest
3
4
  import android.app.Activity
5
+ import android.content.pm.PackageManager
4
6
  import android.database.ContentObserver
5
7
  import android.graphics.Bitmap
6
8
  import android.net.Uri
@@ -18,7 +20,6 @@ import com.facebook.react.bridge.WritableMap
18
20
  import com.facebook.react.modules.core.DeviceEventManagerModule
19
21
  import java.io.File
20
22
  import java.io.FileOutputStream
21
- import java.util.concurrent.Executor
22
23
 
23
24
  /**
24
25
  * Native screenshot detector for Android.
@@ -38,7 +39,8 @@ class ScreenshotDetectorModule(private val reactContext: ReactApplicationContext
38
39
 
39
40
  private val mainHandler = Handler(Looper.getMainLooper())
40
41
  private var contentObserver: ContentObserver? = null
41
- private var screenCaptureCallback: Any? = null
42
+ // Typed as Any? so this class never references the API-34 ScreenCaptureCallback.
43
+ private var screenCapture: ScreenCaptureCallbackApi34? = null
42
44
  private var isListening = false
43
45
 
44
46
  override fun getName(): String = NAME
@@ -50,10 +52,32 @@ class ScreenshotDetectorModule(private val reactContext: ReactApplicationContext
50
52
  if (Build.VERSION.SDK_INT >= 34) {
51
53
  registerScreenCaptureCallback()
52
54
  } else {
55
+ // Older devices detect screenshots via MediaStore, which needs the media
56
+ // read permission granted at runtime.
57
+ requestStoragePermissionIfNeeded()
53
58
  registerContentObserver()
54
59
  }
55
60
  }
56
61
 
62
+ /**
63
+ * Requests the media-read permission on Android 13- so the MediaStore
64
+ * ContentObserver reliably receives screenshot notifications. No-op on
65
+ * Android 14+ (the official ScreenCaptureCallback needs no permission).
66
+ */
67
+ private fun requestStoragePermissionIfNeeded() {
68
+ val activity = reactContext.currentActivity ?: return
69
+ val perm =
70
+ if (Build.VERSION.SDK_INT >= 33) Manifest.permission.READ_MEDIA_IMAGES
71
+ else Manifest.permission.READ_EXTERNAL_STORAGE
72
+ try {
73
+ if (activity.checkSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
74
+ activity.requestPermissions(arrayOf(perm), PERMISSION_REQUEST_CODE)
75
+ }
76
+ } catch (_: Exception) {
77
+ // Ignore — observer is still registered; some OEMs notify without it.
78
+ }
79
+ }
80
+
57
81
  @ReactMethod
58
82
  fun stop() {
59
83
  isListening = false
@@ -78,13 +102,14 @@ class ScreenshotDetectorModule(private val reactContext: ReactApplicationContext
78
102
  private fun registerScreenCaptureCallback() {
79
103
  val activity = reactContext.currentActivity ?: return
80
104
  mainHandler.post {
81
- val executor = Executor { command -> mainHandler.post(command) }
82
- val callback = Activity.ScreenCaptureCallback {
83
- onScreenshotDetected()
84
- }
85
- screenCaptureCallback = callback
86
105
  try {
87
- activity.registerScreenCaptureCallback(executor, callback)
106
+ // All ScreenCaptureCallback usage is isolated in this helper so the
107
+ // missing class never loads on older Android.
108
+ val helper = ScreenCaptureCallbackApi34(activity, mainHandler) {
109
+ onScreenshotDetected()
110
+ }
111
+ helper.register()
112
+ screenCapture = helper
88
113
  } catch (e: Exception) {
89
114
  // Fall back to the content observer if the callback can't be registered.
90
115
  registerContentObserver()
@@ -93,14 +118,13 @@ class ScreenshotDetectorModule(private val reactContext: ReactApplicationContext
93
118
  }
94
119
 
95
120
  private fun unregisterScreenCaptureCallback() {
96
- val activity = reactContext.currentActivity ?: return
97
- val callback = screenCaptureCallback as? Activity.ScreenCaptureCallback ?: return
121
+ val helper = screenCapture ?: return
98
122
  mainHandler.post {
99
123
  try {
100
- activity.unregisterScreenCaptureCallback(callback)
124
+ helper.unregister()
101
125
  } catch (_: Exception) {
102
126
  }
103
- screenCaptureCallback = null
127
+ screenCapture = null
104
128
  }
105
129
  }
106
130
 
@@ -223,5 +247,6 @@ class ScreenshotDetectorModule(private val reactContext: ReactApplicationContext
223
247
 
224
248
  companion object {
225
249
  const val NAME = "RNBugReporterScreenshot"
250
+ private const val PERMISSION_REQUEST_CODE = 7322
226
251
  }
227
252
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-bug-reporter",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Universal React Native bug reporter: auto-detect screenshots, annotate, collect device/app/user context, and ship to Supabase (Postgres + Storage) with Edge Function email.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",