react-native-privacy-guard-kit 0.1.0

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 (60) hide show
  1. package/LICENSE +20 -0
  2. package/PrivacyGuardKit.podspec +27 -0
  3. package/README.md +320 -0
  4. package/android/CMakeLists.txt +47 -0
  5. package/android/build.gradle +64 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/privacyguardkit/PrivacyGuardKitPackage.kt +17 -0
  8. package/android/src/main/java/com/privacyguardkit/Privacyguardkitmodule .kt +208 -0
  9. package/android/src/main/java/com/privacyguardkit/Screenshotobserver.kt +66 -0
  10. package/android/src/main/java/com/privacyguardkit/Secureview.kt +104 -0
  11. package/android/src/main/java/com/privacyguardkit/Secureviewmanager.kt +27 -0
  12. package/android/src/main/jni/react/renderer/components/PrivacyGuardKitViewSpec/RNSecureViewComponentDescriptor.h +25 -0
  13. package/ios/PrivacyGuardKit-Umbrella.h +14 -0
  14. package/ios/PrivacyGuardKit.m +38 -0
  15. package/ios/PrivacyGuardKit.swift +221 -0
  16. package/ios/RNSecureViewComponentView.h +16 -0
  17. package/ios/RNSecureViewComponentView.mm +84 -0
  18. package/ios/RNSecureViewManager.mm +48 -0
  19. package/lib/module/Hooks.js +119 -0
  20. package/lib/module/Hooks.js.map +1 -0
  21. package/lib/module/NativePrivacyGuardKit.js +12 -0
  22. package/lib/module/NativePrivacyGuardKit.js.map +1 -0
  23. package/lib/module/PrivacyGuardProvider.js +99 -0
  24. package/lib/module/PrivacyGuardProvider.js.map +1 -0
  25. package/lib/module/PrivacyGuardkitApi.js +104 -0
  26. package/lib/module/PrivacyGuardkitApi.js.map +1 -0
  27. package/lib/module/SecureView.js +24 -0
  28. package/lib/module/SecureView.js.map +1 -0
  29. package/lib/module/index.js +16 -0
  30. package/lib/module/index.js.map +1 -0
  31. package/lib/module/package.json +1 -0
  32. package/lib/module/specs/RNSecureViewNativeComponent.ts +19 -0
  33. package/lib/module/types.js +2 -0
  34. package/lib/module/types.js.map +1 -0
  35. package/lib/typescript/package.json +1 -0
  36. package/lib/typescript/src/Hooks.d.ts +29 -0
  37. package/lib/typescript/src/Hooks.d.ts.map +1 -0
  38. package/lib/typescript/src/NativePrivacyGuardKit.d.ts +4 -0
  39. package/lib/typescript/src/NativePrivacyGuardKit.d.ts.map +1 -0
  40. package/lib/typescript/src/PrivacyGuardProvider.d.ts +30 -0
  41. package/lib/typescript/src/PrivacyGuardProvider.d.ts.map +1 -0
  42. package/lib/typescript/src/PrivacyGuardkitApi.d.ts +45 -0
  43. package/lib/typescript/src/PrivacyGuardkitApi.d.ts.map +1 -0
  44. package/lib/typescript/src/SecureView.d.ts +10 -0
  45. package/lib/typescript/src/SecureView.d.ts.map +1 -0
  46. package/lib/typescript/src/index.d.ts +6 -0
  47. package/lib/typescript/src/index.d.ts.map +1 -0
  48. package/lib/typescript/src/specs/RNSecureViewNativeComponent.d.ts +11 -0
  49. package/lib/typescript/src/specs/RNSecureViewNativeComponent.d.ts.map +1 -0
  50. package/lib/typescript/src/types.d.ts +29 -0
  51. package/lib/typescript/src/types.d.ts.map +1 -0
  52. package/package.json +174 -0
  53. package/src/Hooks.ts +138 -0
  54. package/src/NativePrivacyGuardKit.ts +13 -0
  55. package/src/PrivacyGuardProvider.tsx +134 -0
  56. package/src/PrivacyGuardkitApi.ts +123 -0
  57. package/src/SecureView.tsx +29 -0
  58. package/src/index.tsx +37 -0
  59. package/src/specs/RNSecureViewNativeComponent.ts +19 -0
  60. package/src/types.ts +34 -0
@@ -0,0 +1,208 @@
1
+ package com.privacyguardkit
2
+
3
+ import android.app.Activity
4
+ import android.content.ClipboardManager
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import android.view.WindowManager
8
+ import com.facebook.react.bridge.Promise
9
+ import com.facebook.react.bridge.ReactApplicationContext
10
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
11
+ import com.facebook.react.bridge.ReactMethod
12
+ import com.facebook.react.bridge.WritableMap
13
+ import com.facebook.react.modules.core.DeviceEventManagerModule
14
+
15
+ class PrivacyGuardKitModule(
16
+ private val reactContext: ReactApplicationContext
17
+ ) : ReactContextBaseJavaModule(reactContext) {
18
+
19
+ companion object {
20
+ const val MODULE_NAME = "PrivacyGuardKit"
21
+ const val EVENT_SCREENSHOT_TAKEN = "onScreenshotTaken"
22
+ const val EVENT_SCREEN_RECORDING_STARTED = "onScreenRecordingStarted"
23
+ const val EVENT_SCREEN_RECORDING_STOPPED = "onScreenRecordingStopped"
24
+ }
25
+
26
+ private var isScreenCaptureDisabled = false
27
+ private var isAppSwitcherProtected = false
28
+ private var screenshotObserver: ScreenshotObserver? = null
29
+
30
+ // Helper — safely get the current Activity from the stored context
31
+ private val activity: Activity?
32
+ get() = reactContext.currentActivity
33
+
34
+ override fun getName(): String = MODULE_NAME
35
+
36
+ // ─────────────────────────────────────────────
37
+ // SCREEN CAPTURE
38
+ // ─────────────────────────────────────────────
39
+
40
+ @ReactMethod
41
+ fun disableScreenCapture(promise: Promise) {
42
+ val act = activity
43
+ if (act == null) {
44
+ promise.reject("NO_ACTIVITY", "No current activity available")
45
+ return
46
+ }
47
+ try {
48
+ act.runOnUiThread {
49
+ act.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
50
+ isScreenCaptureDisabled = true
51
+ }
52
+ promise.resolve(true)
53
+ } catch (e: Exception) {
54
+ promise.reject("DISABLE_CAPTURE_ERROR", e.message, e)
55
+ }
56
+ }
57
+
58
+ @ReactMethod
59
+ fun enableScreenCapture(promise: Promise) {
60
+ val act = activity
61
+ if (act == null) {
62
+ promise.reject("NO_ACTIVITY", "No current activity available")
63
+ return
64
+ }
65
+ try {
66
+ act.runOnUiThread {
67
+ act.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
68
+ isScreenCaptureDisabled = false
69
+ }
70
+ promise.resolve(true)
71
+ } catch (e: Exception) {
72
+ promise.reject("ENABLE_CAPTURE_ERROR", e.message, e)
73
+ }
74
+ }
75
+
76
+ @ReactMethod
77
+ fun isScreenCaptureDisabled(promise: Promise) {
78
+ promise.resolve(isScreenCaptureDisabled)
79
+ }
80
+
81
+ // ─────────────────────────────────────────────
82
+ // SCREEN RECORDING DETECTION
83
+ // ─────────────────────────────────────────────
84
+
85
+ @ReactMethod
86
+ fun isScreenBeingRecorded(promise: Promise) {
87
+ // Android does not expose a direct public API for this.
88
+ // FLAG_SECURE prevents recording content from being visible.
89
+ // Full detection requires a foreground service with MediaProjection.
90
+ promise.resolve(false)
91
+ }
92
+
93
+ // ─────────────────────────────────────────────
94
+ // APP SWITCHER PROTECTION
95
+ // FLAG_SECURE covers app-switcher thumbnails on Android
96
+ // ─────────────────────────────────────────────
97
+
98
+ @ReactMethod
99
+ fun enableAppSwitcherProtection(promise: Promise) {
100
+ val act = activity
101
+ if (act == null) {
102
+ promise.reject("NO_ACTIVITY", "No current activity available")
103
+ return
104
+ }
105
+ try {
106
+ act.runOnUiThread {
107
+ act.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
108
+ isAppSwitcherProtected = true
109
+ }
110
+ promise.resolve(true)
111
+ } catch (e: Exception) {
112
+ promise.reject("APP_SWITCHER_ERROR", e.message, e)
113
+ }
114
+ }
115
+
116
+ @ReactMethod
117
+ fun disableAppSwitcherProtection(promise: Promise) {
118
+ val act = activity
119
+ if (act == null) {
120
+ promise.reject("NO_ACTIVITY", "No current activity available")
121
+ return
122
+ }
123
+ try {
124
+ act.runOnUiThread {
125
+ // Only clear the flag if screen capture protection is also off
126
+ if (!isScreenCaptureDisabled) {
127
+ act.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
128
+ }
129
+ isAppSwitcherProtected = false
130
+ }
131
+ promise.resolve(true)
132
+ } catch (e: Exception) {
133
+ promise.reject("APP_SWITCHER_DISABLE_ERROR", e.message, e)
134
+ }
135
+ }
136
+
137
+ // ─────────────────────────────────────────────
138
+ // SCREENSHOT EVENT LISTENER
139
+ // ─────────────────────────────────────────────
140
+
141
+ @ReactMethod
142
+ fun startScreenshotListener(promise: Promise) {
143
+ try {
144
+ screenshotObserver = ScreenshotObserver(reactContext) {
145
+ sendEvent(EVENT_SCREENSHOT_TAKEN, null)
146
+ }
147
+ screenshotObserver?.start()
148
+ promise.resolve(true)
149
+ } catch (e: Exception) {
150
+ promise.reject("SCREENSHOT_LISTENER_ERROR", e.message, e)
151
+ }
152
+ }
153
+
154
+ @ReactMethod
155
+ fun stopScreenshotListener(promise: Promise) {
156
+ try {
157
+ screenshotObserver?.stop()
158
+ screenshotObserver = null
159
+ promise.resolve(true)
160
+ } catch (e: Exception) {
161
+ promise.reject("SCREENSHOT_LISTENER_STOP_ERROR", e.message, e)
162
+ }
163
+ }
164
+
165
+ // ─────────────────────────────────────────────
166
+ // CLIPBOARD PROTECTION
167
+ // ─────────────────────────────────────────────
168
+
169
+ @ReactMethod
170
+ fun clearClipboard(promise: Promise) {
171
+ try {
172
+ val clipboard = reactContext
173
+ .getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
174
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
175
+ clipboard.clearPrimaryClip()
176
+ } else {
177
+ @Suppress("DEPRECATION")
178
+ clipboard.text = ""
179
+ }
180
+ promise.resolve(true)
181
+ } catch (e: Exception) {
182
+ promise.reject("CLIPBOARD_CLEAR_ERROR", e.message, e)
183
+ }
184
+ }
185
+
186
+ // ─────────────────────────────────────────────
187
+ // EVENT EMITTER SUPPORT
188
+ // Required stubs for RN's NativeEventEmitter
189
+ // ─────────────────────────────────────────────
190
+
191
+ @ReactMethod
192
+ fun addListener(eventName: String) { /* Required by RN */ }
193
+
194
+ @ReactMethod
195
+ fun removeListeners(count: Int) { /* Required by RN */ }
196
+
197
+ private fun sendEvent(eventName: String, params: WritableMap?) {
198
+ reactContext
199
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
200
+ .emit(eventName, params)
201
+ }
202
+
203
+ override fun invalidate() {
204
+ super.invalidate()
205
+ screenshotObserver?.stop()
206
+ screenshotObserver = null
207
+ }
208
+ }
@@ -0,0 +1,66 @@
1
+ package com.privacyguardkit
2
+
3
+ import android.content.Context
4
+ import android.database.ContentObserver
5
+ import android.net.Uri
6
+ import android.os.Handler
7
+ import android.os.Looper
8
+ import android.provider.MediaStore
9
+
10
+ /**
11
+ * Watches the MediaStore for new image entries in the Screenshots folder.
12
+ * Fires [onScreenshotTaken] when a new screenshot is detected.
13
+ */
14
+ class ScreenshotObserver(
15
+ private val context: Context,
16
+ private val onScreenshotTaken: () -> Unit
17
+ ) {
18
+ private var contentObserver: ContentObserver? = null
19
+
20
+ fun start() {
21
+ val handler = Handler(Looper.getMainLooper())
22
+
23
+ contentObserver = object : ContentObserver(handler) {
24
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
25
+ super.onChange(selfChange, uri)
26
+ uri ?: return
27
+ if (isScreenshot(uri)) {
28
+ onScreenshotTaken()
29
+ }
30
+ }
31
+ }
32
+
33
+ context.contentResolver.registerContentObserver(
34
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
35
+ true,
36
+ contentObserver!!
37
+ )
38
+ }
39
+
40
+ fun stop() {
41
+ contentObserver?.let {
42
+ context.contentResolver.unregisterContentObserver(it)
43
+ }
44
+ contentObserver = null
45
+ }
46
+
47
+ private fun isScreenshot(uri: Uri): Boolean {
48
+ return try {
49
+ val cursor = context.contentResolver.query(
50
+ uri,
51
+ arrayOf(MediaStore.Images.Media.DATA),
52
+ null, null, null
53
+ )
54
+ cursor?.use {
55
+ if (it.moveToFirst()) {
56
+ val path = it.getString(
57
+ it.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
58
+ )
59
+ path?.lowercase()?.contains("screenshot") == true
60
+ } else false
61
+ } ?: false
62
+ } catch (e: Exception) {
63
+ false
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,104 @@
1
+ package com.privacyguardkit
2
+
3
+ import android.content.Context
4
+ import android.view.ActionMode
5
+ import android.view.Menu
6
+ import android.view.MenuItem
7
+ import android.view.View
8
+ import android.view.ViewGroup
9
+ import android.widget.EditText
10
+ import android.widget.TextView
11
+ import com.facebook.react.views.view.ReactViewGroup
12
+
13
+ /**
14
+ * A Fabric-compatible ReactViewGroup that disables copy/paste
15
+ * for all TextView and EditText children recursively.
16
+ *
17
+ * Extends ReactViewGroup (not plain ViewGroup) so Fabric's
18
+ * SurfaceMountingManager can correctly cast it via IViewGroupManager.
19
+ */
20
+ class SecureView(context: Context) : ReactViewGroup(context) {
21
+
22
+ private var isCopyPasteDisabled = false
23
+
24
+ fun disableCopyPaste() {
25
+ isCopyPasteDisabled = true
26
+ applyToChildren(this)
27
+ }
28
+
29
+ fun enableCopyPaste() {
30
+ isCopyPasteDisabled = false
31
+ restoreChildren(this)
32
+ }
33
+
34
+ /**
35
+ * Called by Fabric's SurfaceMountingManager when a child is mounted.
36
+ * Signature must be (child: View) — non-nullable — to match ViewGroup.
37
+ */
38
+ override fun onViewAdded(child: View) {
39
+ super.onViewAdded(child)
40
+ if (isCopyPasteDisabled) {
41
+ applyToView(child)
42
+ // Also recurse into the child if it's already a group
43
+ if (child is ViewGroup) applyToChildren(child)
44
+ }
45
+ }
46
+
47
+ // ── Recursive helpers ─────────────────────────────────────
48
+
49
+ private fun applyToChildren(group: ViewGroup) {
50
+ for (i in 0 until group.childCount) {
51
+ val child = group.getChildAt(i)
52
+ applyToView(child)
53
+ if (child is ViewGroup) applyToChildren(child)
54
+ }
55
+ }
56
+
57
+ private fun restoreChildren(group: ViewGroup) {
58
+ for (i in 0 until group.childCount) {
59
+ val child = group.getChildAt(i)
60
+ restoreView(child)
61
+ if (child is ViewGroup) restoreChildren(child)
62
+ }
63
+ }
64
+
65
+ private fun applyToView(view: View) {
66
+ when (view) {
67
+ is EditText -> {
68
+ view.isLongClickable = false
69
+ view.setTextIsSelectable(false)
70
+ view.customSelectionActionModeCallback = BlockActionMode()
71
+ view.customInsertionActionModeCallback = BlockActionMode()
72
+ }
73
+ is TextView -> {
74
+ view.isLongClickable = false
75
+ view.setTextIsSelectable(false)
76
+ view.customSelectionActionModeCallback = BlockActionMode()
77
+ }
78
+ }
79
+ }
80
+
81
+ private fun restoreView(view: View) {
82
+ when (view) {
83
+ is EditText -> {
84
+ view.isLongClickable = true
85
+ view.setTextIsSelectable(true)
86
+ view.customSelectionActionModeCallback = null
87
+ view.customInsertionActionModeCallback = null
88
+ }
89
+ is TextView -> {
90
+ view.isLongClickable = true
91
+ view.setTextIsSelectable(true)
92
+ view.customSelectionActionModeCallback = null
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ // Blocks the copy/paste/cut context menu from appearing
99
+ private class BlockActionMode : ActionMode.Callback {
100
+ override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean = false
101
+ override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean = false
102
+ override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean = false
103
+ override fun onDestroyActionMode(mode: ActionMode?) = Unit
104
+ }
@@ -0,0 +1,27 @@
1
+ package com.privacyguardkit
2
+
3
+ import com.facebook.react.uimanager.ThemedReactContext
4
+ import com.facebook.react.uimanager.ViewGroupManager
5
+ import com.facebook.react.uimanager.annotations.ReactProp
6
+
7
+ /**
8
+ * ViewGroupManager (not SimpleViewManager) is required for:
9
+ * 1. New Architecture (Fabric) — Fabric casts view managers to
10
+ * IViewGroupManager when the view has children. SimpleViewManager
11
+ * does NOT implement IViewGroupManager, causing the cast crash.
12
+ * 2. SecureView extends ReactViewGroup (a ViewGroup), so it must
13
+ * be managed by a ViewGroupManager anyway.
14
+ */
15
+ class SecureViewManager : ViewGroupManager<SecureView>() {
16
+
17
+ override fun getName(): String = "RNSecureView"
18
+
19
+ override fun createViewInstance(reactContext: ThemedReactContext): SecureView {
20
+ return SecureView(reactContext)
21
+ }
22
+
23
+ @ReactProp(name = "disableCopyPaste", defaultBoolean = false)
24
+ fun setDisableCopyPaste(view: SecureView, disable: Boolean) {
25
+ if (disable) view.disableCopyPaste() else view.enableCopyPaste()
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ // android/src/main/jni/react/renderer/components/PrivacyGuardKitViewSpec/
2
+ // RNSecureViewComponentDescriptor.h
3
+ //
4
+ // This file is AUTO-GENERATED by React Native codegen.
5
+ // You should NOT edit it manually — re-run codegen to regenerate.
6
+ // It is provided here for reference only.
7
+ //
8
+ // If autolinking.cpp still cannot find RNSecureViewComponentDescriptor,
9
+ // ensure your build.gradle has:
10
+ // react {
11
+ // libraryName = "PrivacyGuardKitViewSpec"
12
+ // jsRootDir = file("../src/specs")
13
+ // }
14
+ // and run: cd android && ./gradlew generateCodegenArtifactsFromSchema
15
+
16
+ #pragma once
17
+ #include <react/renderer/components/PrivacyGuardKitViewSpec/ShadowNodes.h>
18
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
19
+
20
+ namespace facebook::react {
21
+
22
+ using RNSecureViewComponentDescriptor =
23
+ ConcreteComponentDescriptor<RNSecureViewShadowNode>;
24
+
25
+ } // namespace facebook::react
@@ -0,0 +1,14 @@
1
+ // PrivacyGuardKit-Umbrella.h
2
+ // Public umbrella header for the PrivacyGuardKit framework.
3
+ // CocoaPods generates a module map that imports this file,
4
+ // which is how Swift sees ObjC symbols — no bridging header needed.
5
+ //
6
+ // RULES:
7
+ // - Only list headers that are safe for Swift to import
8
+ // - Do NOT include C++ or Fabric headers here (they break Swift)
9
+ // - The file name must be: <PodName>-Umbrella.h OR listed in s.public_header_files
10
+
11
+ #import <React/RCTBridgeModule.h>
12
+ #import <React/RCTEventEmitter.h>
13
+ #import <React/RCTViewManager.h>
14
+
@@ -0,0 +1,38 @@
1
+ // PrivacyGuardKit.m
2
+ // RCT_EXTERN_MODULE and RCT_EXTERN_METHOD must be inside
3
+ // an @interface/@implementation block — the macros expand to
4
+ // ObjC method declarations which require a class context.
5
+
6
+ #import <React/RCTBridgeModule.h>
7
+ #import <React/RCTEventEmitter.h>
8
+
9
+ @interface RCT_EXTERN_MODULE(PrivacyGuardKit, RCTEventEmitter)
10
+
11
+ RCT_EXTERN_METHOD(disableScreenCapture:(RCTPromiseResolveBlock)resolve
12
+ reject:(RCTPromiseRejectBlock)reject)
13
+
14
+ RCT_EXTERN_METHOD(enableScreenCapture:(RCTPromiseResolveBlock)resolve
15
+ reject:(RCTPromiseRejectBlock)reject)
16
+
17
+ RCT_EXTERN_METHOD(isScreenCaptureDisabled:(RCTPromiseResolveBlock)resolve
18
+ reject:(RCTPromiseRejectBlock)reject)
19
+
20
+ RCT_EXTERN_METHOD(isScreenBeingRecorded:(RCTPromiseResolveBlock)resolve
21
+ reject:(RCTPromiseRejectBlock)reject)
22
+
23
+ RCT_EXTERN_METHOD(startScreenshotListener:(RCTPromiseResolveBlock)resolve
24
+ reject:(RCTPromiseRejectBlock)reject)
25
+
26
+ RCT_EXTERN_METHOD(stopScreenshotListener:(RCTPromiseResolveBlock)resolve
27
+ reject:(RCTPromiseRejectBlock)reject)
28
+
29
+ RCT_EXTERN_METHOD(enableAppSwitcherProtection:(RCTPromiseResolveBlock)resolve
30
+ reject:(RCTPromiseRejectBlock)reject)
31
+
32
+ RCT_EXTERN_METHOD(disableAppSwitcherProtection:(RCTPromiseResolveBlock)resolve
33
+ reject:(RCTPromiseRejectBlock)reject)
34
+
35
+ RCT_EXTERN_METHOD(clearClipboard:(RCTPromiseResolveBlock)resolve
36
+ reject:(RCTPromiseRejectBlock)reject)
37
+
38
+ @end