@multiplayer-app/session-recorder-react-native 0.0.1-beta.7 → 0.0.1-beta.8

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 (74) hide show
  1. package/docs/NATIVE_MODULE_SETUP.md +175 -0
  2. package/ios/SessionRecorderNative.podspec +5 -0
  3. package/package.json +11 -1
  4. package/plugin/package.json +20 -0
  5. package/plugin/src/index.js +42 -0
  6. package/android/src/main/AndroidManifest.xml +0 -2
  7. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingModule.kt +0 -202
  8. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingPackage.kt +0 -16
  9. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderModule.kt +0 -202
  10. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderPackage.kt +0 -16
  11. package/babel.config.js +0 -13
  12. package/docs/AUTO_METADATA_DETECTION.md +0 -108
  13. package/docs/TROUBLESHOOTING.md +0 -168
  14. package/ios/ScreenMasking.m +0 -12
  15. package/ios/ScreenMasking.podspec +0 -21
  16. package/ios/ScreenMasking.swift +0 -205
  17. package/ios/SessionRecorder.podspec +0 -21
  18. package/scripts/generate-app-metadata.js +0 -173
  19. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
  20. package/src/components/GestureCaptureWrapper/index.ts +0 -1
  21. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
  22. package/src/components/ScreenRecorderView/index.ts +0 -1
  23. package/src/components/SessionRecorderWidget/FinalPopover.tsx +0 -62
  24. package/src/components/SessionRecorderWidget/FloatingButton.tsx +0 -136
  25. package/src/components/SessionRecorderWidget/InitialPopover.tsx +0 -89
  26. package/src/components/SessionRecorderWidget/ModalContainer.tsx +0 -128
  27. package/src/components/SessionRecorderWidget/ModalHeader.tsx +0 -24
  28. package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +0 -109
  29. package/src/components/SessionRecorderWidget/icons.tsx +0 -52
  30. package/src/components/SessionRecorderWidget/index.ts +0 -3
  31. package/src/components/SessionRecorderWidget/styles.ts +0 -150
  32. package/src/components/index.ts +0 -3
  33. package/src/config/constants.ts +0 -60
  34. package/src/config/defaults.ts +0 -83
  35. package/src/config/index.ts +0 -6
  36. package/src/config/masking.ts +0 -28
  37. package/src/config/session-recorder.ts +0 -55
  38. package/src/config/validators.ts +0 -31
  39. package/src/context/SessionRecorderContext.tsx +0 -53
  40. package/src/index.ts +0 -9
  41. package/src/native/ScreenMasking.ts +0 -34
  42. package/src/native/SessionRecorderNative.ts +0 -34
  43. package/src/otel/helpers.ts +0 -275
  44. package/src/otel/index.ts +0 -138
  45. package/src/otel/instrumentations/index.ts +0 -115
  46. package/src/patch/index.ts +0 -1
  47. package/src/patch/xhr.ts +0 -141
  48. package/src/recorder/eventExporter.ts +0 -141
  49. package/src/recorder/gestureRecorder.ts +0 -498
  50. package/src/recorder/index.ts +0 -179
  51. package/src/recorder/navigationTracker.ts +0 -449
  52. package/src/recorder/screenRecorder.ts +0 -527
  53. package/src/services/api.service.ts +0 -203
  54. package/src/services/screenMaskingService.ts +0 -118
  55. package/src/services/storage.service.ts +0 -199
  56. package/src/session-recorder.ts +0 -606
  57. package/src/types/expo.d.ts +0 -23
  58. package/src/types/index.ts +0 -28
  59. package/src/types/session-recorder.ts +0 -429
  60. package/src/types/session.ts +0 -65
  61. package/src/utils/app-metadata.ts +0 -31
  62. package/src/utils/index.ts +0 -8
  63. package/src/utils/logger.ts +0 -225
  64. package/src/utils/nativeModuleTest.ts +0 -60
  65. package/src/utils/platform.ts +0 -384
  66. package/src/utils/request-utils.ts +0 -61
  67. package/src/utils/rrweb-events.ts +0 -309
  68. package/src/utils/session.ts +0 -18
  69. package/src/utils/time.ts +0 -17
  70. package/src/utils/type-utils.ts +0 -75
  71. package/src/version.ts +0 -1
  72. package/tsconfig.json +0 -24
  73. /package/ios/{SessionRecorder.m → SessionRecorderNative.m} +0 -0
  74. /package/ios/{SessionRecorder.swift → SessionRecorderNative.swift} +0 -0
@@ -0,0 +1,175 @@
1
+ # Native Module Setup Guide
2
+
3
+ This guide explains how to properly set up the Session Recorder React Native library with native modules in both React Native and Expo projects.
4
+
5
+ ## Issues Fixed
6
+
7
+ ### 1. Missing iOS Native Files in NPM Package
8
+
9
+ **Problem**: The iOS native implementation files (`SessionRecorderNative.m` and `SessionRecorderNative.swift`) were not being included in the published npm package.
10
+
11
+ **Solution**: Updated `.npmignore` to exclude source files but include the compiled native modules.
12
+
13
+ ### 2. Auto-linking Configuration
14
+
15
+ **Problem**: The library wasn't properly configured for auto-linking in Expo projects.
16
+
17
+ **Solution**:
18
+
19
+ - Added `expo` configuration section to `package.json`
20
+ - Created an Expo plugin for automatic Podfile configuration
21
+ - Updated podspec with proper Expo compatibility settings
22
+
23
+ ### 3. Podspec Configuration
24
+
25
+ **Problem**: The podspec wasn't optimized for Expo compatibility.
26
+
27
+ **Solution**: Added proper header search paths and Expo-specific configurations.
28
+
29
+ ## Setup Instructions
30
+
31
+ ### For Expo Projects
32
+
33
+ 1. **Install the package**:
34
+
35
+ ```bash
36
+ npm install @multiplayer-app/session-recorder-react-native
37
+ ```
38
+
39
+ 2. **Add to app.json/app.config.js**:
40
+
41
+ ```json
42
+ {
43
+ "expo": {
44
+ "plugins": ["@multiplayer-app/session-recorder-react-native"]
45
+ }
46
+ }
47
+ ```
48
+
49
+ 3. **Run prebuild** (if using development build):
50
+
51
+ ```bash
52
+ npx expo prebuild --clean
53
+ ```
54
+
55
+ 4. **Install iOS dependencies**:
56
+ ```bash
57
+ cd ios && pod install && cd ..
58
+ ```
59
+
60
+ ### For React Native CLI Projects
61
+
62
+ 1. **Install the package**:
63
+
64
+ ```bash
65
+ npm install @multiplayer-app/session-recorder-react-native
66
+ ```
67
+
68
+ 2. **iOS Setup**:
69
+
70
+ ```bash
71
+ cd ios && pod install && cd ..
72
+ ```
73
+
74
+ 3. **Android Setup**: No additional steps required (auto-linking handles it)
75
+
76
+ ## Troubleshooting
77
+
78
+ ### "Pod SessionRecorderNative files are missing"
79
+
80
+ This error occurs when the iOS native files aren't properly included in the package or auto-linking fails.
81
+
82
+ **Solutions**:
83
+
84
+ 1. **Check package installation**:
85
+
86
+ ```bash
87
+ ls node_modules/@multiplayer-app/session-recorder-react-native/ios/
88
+ ```
89
+
90
+ Should show: `SessionRecorderNative.m`, `SessionRecorderNative.podspec`, `SessionRecorderNative.swift`
91
+
92
+ 2. **Clear and reinstall**:
93
+
94
+ ```bash
95
+ rm -rf node_modules
96
+ npm install
97
+ cd ios && pod install --repo-update && cd ..
98
+ ```
99
+
100
+ 3. **For Expo projects, ensure plugin is configured**:
101
+ ```json
102
+ {
103
+ "expo": {
104
+ "plugins": ["@multiplayer-app/session-recorder-react-native"]
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Auto-linking Issues
110
+
111
+ If auto-linking doesn't work:
112
+
113
+ 1. **Check react-native.config.js** (should be automatically created):
114
+
115
+ ```javascript
116
+ module.exports = {
117
+ dependencies: {
118
+ '@multiplayer-app/session-recorder-react-native': {
119
+ platforms: {
120
+ android: {
121
+ sourceDir: '../android',
122
+ packageImportPath: 'import com.multiplayer.sessionrecorder.SessionRecorderPackage;'
123
+ },
124
+ ios: {
125
+ podspecPath: '../ios/SessionRecorderNative.podspec'
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ 2. **Manual linking** (if auto-linking fails):
134
+ - iOS: Add to Podfile manually
135
+ - Android: Add to MainApplication.java manually
136
+
137
+ ## File Structure
138
+
139
+ The package includes these native files:
140
+
141
+ ```
142
+ ios/
143
+ ├── SessionRecorderNative.m # Objective-C bridge
144
+ ├── SessionRecorderNative.podspec # CocoaPods specification
145
+ └── SessionRecorderNative.swift # Swift implementation
146
+
147
+ android/
148
+ ├── build.gradle # Android build configuration
149
+ └── src/main/java/com/multiplayer/sessionrecorder/
150
+ ├── SessionRecorderModule.kt # Main Android module
151
+ ├── SessionRecorderPackage.kt # Package registration
152
+ ├── ScreenMaskingModule.kt # Screen masking module
153
+ └── ScreenMaskingPackage.kt # Screen masking package
154
+ ```
155
+
156
+ ## Verification
157
+
158
+ To verify the setup is working:
159
+
160
+ 1. **Check iOS**:
161
+
162
+ ```bash
163
+ cd ios && pod list | grep SessionRecorderNative
164
+ ```
165
+
166
+ 2. **Check Android**: Look for the package in `android/app/src/main/java/`
167
+
168
+ 3. **Test in code**:
169
+
170
+ ```javascript
171
+ import { SessionRecorderNative } from '@multiplayer-app/session-recorder-react-native'
172
+
173
+ // This should not throw an error
174
+ console.log('Native module loaded:', SessionRecorderNative)
175
+ ```
@@ -18,4 +18,9 @@ Pod::Spec.new do |s|
18
18
 
19
19
  s.dependency "React-Core"
20
20
  s.dependency "React"
21
+
22
+ # Ensure proper linking for Expo
23
+ s.pod_target_xcconfig = {
24
+ "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/React-Core/React\""
25
+ }
21
26
  end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/session-recorder-react-native",
3
- "version": "0.0.1-beta.7",
3
+ "version": "0.0.1-beta.8",
4
4
  "description": "Multiplayer Fullstack Session Recorder for React Native",
5
5
  "author": {
6
6
  "name": "Multiplayer Software, Inc.",
@@ -97,5 +97,15 @@
97
97
  "sourceDir": "./android",
98
98
  "packageImportPath": "import com.multiplayer.sessionrecorder.SessionRecorderPackage;"
99
99
  }
100
+ },
101
+ "expo": {
102
+ "ios": {
103
+ "podspecPath": "./ios/SessionRecorderNative.podspec"
104
+ },
105
+ "android": {
106
+ "sourceDir": "./android",
107
+ "packageImportPath": "import com.multiplayer.sessionrecorder.SessionRecorderPackage;"
108
+ },
109
+ "plugin": "./plugin/src/index.js"
100
110
  }
101
111
  }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@multiplayer-app/session-recorder-react-native-plugin",
3
+ "version": "0.0.1-beta.8",
4
+ "description": "Expo plugin for Session Recorder React Native",
5
+ "main": "src/index.js",
6
+ "keywords": [
7
+ "expo",
8
+ "plugin",
9
+ "session-recorder",
10
+ "react-native"
11
+ ],
12
+ "author": "Multiplayer Software, Inc.",
13
+ "license": "MIT",
14
+ "dependencies": {
15
+ "@expo/config-plugins": "^7.0.0"
16
+ },
17
+ "peerDependencies": {
18
+ "expo": ">=49.0.0"
19
+ }
20
+ }
@@ -0,0 +1,42 @@
1
+ const { withDangerousMod } = require('@expo/config-plugins')
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+
5
+ const withSessionRecorder = (config) => {
6
+ return withDangerousMod(config, [
7
+ 'ios',
8
+ async (config) => {
9
+ const projectRoot = config.modRequest.projectRoot
10
+ const platformRoot = config.modRequest.platformProjectRoot
11
+
12
+ // Ensure the Podfile includes our native module
13
+ const podfilePath = path.join(platformRoot, 'Podfile')
14
+
15
+ if (fs.existsSync(podfilePath)) {
16
+ let podfileContent = fs.readFileSync(podfilePath, 'utf8')
17
+
18
+ // Check if our pod is already included
19
+ if (!podfileContent.includes('SessionRecorderNative')) {
20
+ // Add our pod to the Podfile
21
+ const podEntry = ` pod 'SessionRecorderNative', :path => '../node_modules/@multiplayer-app/session-recorder-react-native/ios'\n`
22
+
23
+ // Find the target section and add our pod
24
+ const targetRegex = /(target ['"][^'"]+['"] do)/
25
+ if (targetRegex.test(podfileContent)) {
26
+ podfileContent = podfileContent.replace(targetRegex, `$1\n${podEntry}`)
27
+ } else {
28
+ // If no target found, add at the end before the end statement
29
+ podfileContent = podfileContent.replace(/(end\s*$)/, `${podEntry}$1`)
30
+ }
31
+
32
+ fs.writeFileSync(podfilePath, podfileContent)
33
+ console.log('✅ Added SessionRecorderNative to Podfile')
34
+ }
35
+ }
36
+
37
+ return config
38
+ }
39
+ ])
40
+ }
41
+
42
+ module.exports = withSessionRecorder
@@ -1,2 +0,0 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
- </manifest>
@@ -1,202 +0,0 @@
1
- package com.multiplayer.sessionrecorder
2
-
3
- import android.app.Activity
4
- import android.graphics.*
5
- import android.util.Base64
6
- import android.view.View
7
- import android.widget.EditText
8
- import android.widget.TextView
9
- import com.facebook.react.bridge.*
10
- import java.io.ByteArrayOutputStream
11
-
12
- class SessionRecorderModule(reactContext: ReactApplicationContext) :
13
- ReactContextBaseJavaModule(reactContext) {
14
-
15
- override fun getName() = "SessionRecorder"
16
-
17
- @ReactMethod
18
- fun captureAndMask(promise: Promise) {
19
- val activity = currentActivity ?: return promise.reject("NO_ACTIVITY", "No activity found")
20
-
21
- try {
22
- val maskedImage = captureAndMaskScreen(activity, null)
23
- promise.resolve(maskedImage)
24
- } catch (e: Exception) {
25
- promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
26
- }
27
- }
28
-
29
- @ReactMethod
30
- fun captureAndMaskWithOptions(options: ReadableMap, promise: Promise) {
31
- val activity = currentActivity ?: return promise.reject("NO_ACTIVITY", "No activity found")
32
-
33
- try {
34
- val maskedImage = captureAndMaskScreen(activity, options)
35
- promise.resolve(maskedImage)
36
- } catch (e: Exception) {
37
- promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
38
- }
39
- }
40
-
41
- private fun captureAndMaskScreen(activity: Activity, options: ReadableMap?): String {
42
- val rootView = activity.window.decorView.rootView
43
-
44
- // Enable drawing cache
45
- rootView.isDrawingCacheEnabled = true
46
- rootView.buildDrawingCache()
47
-
48
- val bitmap = Bitmap.createBitmap(rootView.drawingCache)
49
- rootView.isDrawingCacheEnabled = false
50
-
51
- // Apply masking
52
- val maskedBitmap = applyMasking(bitmap, rootView, options)
53
-
54
- // Convert to base64
55
- val output = ByteArrayOutputStream()
56
- maskedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, output)
57
- val base64 = Base64.encodeToString(output.toByteArray(), Base64.DEFAULT)
58
-
59
- return base64
60
- }
61
-
62
- private fun applyMasking(bitmap: Bitmap, rootView: View, options: ReadableMap?): Bitmap {
63
- val maskedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
64
- val canvas = Canvas(maskedBitmap)
65
-
66
- // Find sensitive elements
67
- val sensitiveElements = findSensitiveElements(rootView)
68
-
69
- for (element in sensitiveElements) {
70
- val location = IntArray(2)
71
- element.getLocationOnScreen(location)
72
-
73
- val frame = Rect(
74
- location[0],
75
- location[1],
76
- location[0] + element.width,
77
- location[1] + element.height
78
- )
79
-
80
- val maskingType = getMaskingType(element)
81
-
82
- when (maskingType) {
83
- MaskingType.BLUR -> applyBlurMask(canvas, frame)
84
- MaskingType.RECTANGLE -> applyRectangleMask(canvas, frame)
85
- MaskingType.PIXELATE -> applyPixelateMask(canvas, frame)
86
- MaskingType.NONE -> { /* No masking */ }
87
- }
88
- }
89
-
90
- return maskedBitmap
91
- }
92
-
93
- private fun findSensitiveElements(view: View): List<View> {
94
- val sensitiveElements = mutableListOf<View>()
95
-
96
- fun traverseView(currentView: View) {
97
- if (shouldMaskView(currentView)) {
98
- sensitiveElements.add(currentView)
99
- }
100
-
101
- if (currentView is ViewGroup) {
102
- for (i in 0 until currentView.childCount) {
103
- traverseView(currentView.getChildAt(i))
104
- }
105
- }
106
- }
107
-
108
- traverseView(view)
109
- return sensitiveElements
110
- }
111
-
112
- private fun shouldMaskView(view: View): Boolean {
113
- // Check for EditText - mask all text fields when inputMasking is enabled
114
- if (view is EditText) {
115
- return true
116
- }
117
-
118
- // Check for TextView - mask all text views when inputMasking is enabled
119
- if (view is TextView) {
120
- return true
121
- }
122
-
123
- return false
124
- }
125
-
126
- private fun getMaskingType(view: View): MaskingType {
127
- // Default masking type for all text inputs
128
- return MaskingType.RECTANGLE
129
- }
130
-
131
- private fun applyBlurMask(canvas: Canvas, frame: Rect) {
132
- val paint = Paint().apply {
133
- color = Color.BLACK
134
- alpha = 200 // Semi-transparent
135
- }
136
-
137
- canvas.drawRect(frame, paint)
138
-
139
- // Add some noise to make it look blurred
140
- paint.color = Color.WHITE
141
- paint.alpha = 80
142
-
143
- for (i in 0..20) {
144
- val randomX = frame.left + (Math.random() * frame.width()).toFloat()
145
- val randomY = frame.top + (Math.random() * frame.height()).toFloat()
146
- val randomSize = (Math.random() * 6 + 2).toFloat()
147
- canvas.drawCircle(randomX, randomY, randomSize, paint)
148
- }
149
- }
150
-
151
- private fun applyRectangleMask(canvas: Canvas, frame: Rect) {
152
- val paint = Paint().apply {
153
- color = Color.GRAY
154
- }
155
-
156
- canvas.drawRect(frame, paint)
157
-
158
- // Add some text-like pattern
159
- paint.color = Color.DKGRAY
160
- val lineHeight = 4f
161
- val spacing = 8f
162
-
163
- var y = frame.top + spacing
164
- while (y < frame.bottom - spacing) {
165
- val lineWidth = (Math.random() * frame.width() * 0.5 + frame.width() * 0.3).toFloat()
166
- val x = frame.left + (Math.random() * (frame.width() - lineWidth)).toFloat()
167
- canvas.drawRect(x, y, x + lineWidth, y + lineHeight, paint)
168
- y += lineHeight + spacing
169
- }
170
- }
171
-
172
- private fun applyPixelateMask(canvas: Canvas, frame: Rect) {
173
- val pixelSize = 8f
174
- val pixelCountX = (frame.width() / pixelSize).toInt()
175
- val pixelCountY = (frame.height() / pixelSize).toInt()
176
-
177
- val paint = Paint()
178
-
179
- for (x in 0 until pixelCountX) {
180
- for (y in 0 until pixelCountY) {
181
- val pixelFrame = RectF(
182
- frame.left + x * pixelSize,
183
- frame.top + y * pixelSize,
184
- frame.left + (x + 1) * pixelSize,
185
- frame.top + (y + 1) * pixelSize
186
- )
187
-
188
- // Use a random color for each pixel
189
- paint.color = Color.rgb(
190
- (Math.random() * 255).toInt(),
191
- (Math.random() * 255).toInt(),
192
- (Math.random() * 255).toInt()
193
- )
194
- canvas.drawRect(pixelFrame, paint)
195
- }
196
- }
197
- }
198
-
199
- private enum class MaskingType {
200
- BLUR, RECTANGLE, PIXELATE, NONE
201
- }
202
- }
@@ -1,16 +0,0 @@
1
- package com.multiplayer.sessionrecorder
2
-
3
- import com.facebook.react.ReactPackage
4
- import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.uimanager.ViewManager
7
-
8
- class SessionRecorderPackage : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
- return listOf(SessionRecorderModule(reactContext))
11
- }
12
-
13
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
- return emptyList()
15
- }
16
- }
@@ -1,202 +0,0 @@
1
- package com.multiplayer.sessionrecorder
2
-
3
- import android.app.Activity
4
- import android.graphics.*
5
- import android.util.Base64
6
- import android.view.View
7
- import android.widget.EditText
8
- import android.widget.TextView
9
- import com.facebook.react.bridge.*
10
- import java.io.ByteArrayOutputStream
11
-
12
- class SessionRecorderModule(reactContext: ReactApplicationContext) :
13
- ReactContextBaseJavaModule(reactContext) {
14
-
15
- override fun getName() = "SessionRecorderNative"
16
-
17
- @ReactMethod
18
- fun captureAndMask(promise: Promise) {
19
- val activity = currentActivity ?: return promise.reject("NO_ACTIVITY", "No activity found")
20
-
21
- try {
22
- val maskedImage = captureAndMaskScreen(activity, null)
23
- promise.resolve(maskedImage)
24
- } catch (e: Exception) {
25
- promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
26
- }
27
- }
28
-
29
- @ReactMethod
30
- fun captureAndMaskWithOptions(options: ReadableMap, promise: Promise) {
31
- val activity = currentActivity ?: return promise.reject("NO_ACTIVITY", "No activity found")
32
-
33
- try {
34
- val maskedImage = captureAndMaskScreen(activity, options)
35
- promise.resolve(maskedImage)
36
- } catch (e: Exception) {
37
- promise.reject("CAPTURE_FAILED", "Failed to capture and mask screen", e)
38
- }
39
- }
40
-
41
- private fun captureAndMaskScreen(activity: Activity, options: ReadableMap?): String {
42
- val rootView = activity.window.decorView.rootView
43
-
44
- // Enable drawing cache
45
- rootView.isDrawingCacheEnabled = true
46
- rootView.buildDrawingCache()
47
-
48
- val bitmap = Bitmap.createBitmap(rootView.drawingCache)
49
- rootView.isDrawingCacheEnabled = false
50
-
51
- // Apply masking
52
- val maskedBitmap = applyMasking(bitmap, rootView, options)
53
-
54
- // Convert to base64
55
- val output = ByteArrayOutputStream()
56
- maskedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, output)
57
- val base64 = Base64.encodeToString(output.toByteArray(), Base64.DEFAULT)
58
-
59
- return base64
60
- }
61
-
62
- private fun applyMasking(bitmap: Bitmap, rootView: View, options: ReadableMap?): Bitmap {
63
- val maskedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
64
- val canvas = Canvas(maskedBitmap)
65
-
66
- // Find sensitive elements
67
- val sensitiveElements = findSensitiveElements(rootView)
68
-
69
- for (element in sensitiveElements) {
70
- val location = IntArray(2)
71
- element.getLocationOnScreen(location)
72
-
73
- val frame = Rect(
74
- location[0],
75
- location[1],
76
- location[0] + element.width,
77
- location[1] + element.height
78
- )
79
-
80
- val maskingType = getMaskingType(element)
81
-
82
- when (maskingType) {
83
- MaskingType.BLUR -> applyBlurMask(canvas, frame)
84
- MaskingType.RECTANGLE -> applyRectangleMask(canvas, frame)
85
- MaskingType.PIXELATE -> applyPixelateMask(canvas, frame)
86
- MaskingType.NONE -> { /* No masking */ }
87
- }
88
- }
89
-
90
- return maskedBitmap
91
- }
92
-
93
- private fun findSensitiveElements(view: View): List<View> {
94
- val sensitiveElements = mutableListOf<View>()
95
-
96
- fun traverseView(currentView: View) {
97
- if (shouldMaskView(currentView)) {
98
- sensitiveElements.add(currentView)
99
- }
100
-
101
- if (currentView is ViewGroup) {
102
- for (i in 0 until currentView.childCount) {
103
- traverseView(currentView.getChildAt(i))
104
- }
105
- }
106
- }
107
-
108
- traverseView(view)
109
- return sensitiveElements
110
- }
111
-
112
- private fun shouldMaskView(view: View): Boolean {
113
- // Check for EditText - mask all text fields when inputMasking is enabled
114
- if (view is EditText) {
115
- return true
116
- }
117
-
118
- // Check for TextView - mask all text views when inputMasking is enabled
119
- if (view is TextView) {
120
- return true
121
- }
122
-
123
- return false
124
- }
125
-
126
- private fun getMaskingType(view: View): MaskingType {
127
- // Default masking type for all text inputs
128
- return MaskingType.RECTANGLE
129
- }
130
-
131
- private fun applyBlurMask(canvas: Canvas, frame: Rect) {
132
- val paint = Paint().apply {
133
- color = Color.BLACK
134
- alpha = 200 // Semi-transparent
135
- }
136
-
137
- canvas.drawRect(frame, paint)
138
-
139
- // Add some noise to make it look blurred
140
- paint.color = Color.WHITE
141
- paint.alpha = 80
142
-
143
- for (i in 0..20) {
144
- val randomX = frame.left + (Math.random() * frame.width()).toFloat()
145
- val randomY = frame.top + (Math.random() * frame.height()).toFloat()
146
- val randomSize = (Math.random() * 6 + 2).toFloat()
147
- canvas.drawCircle(randomX, randomY, randomSize, paint)
148
- }
149
- }
150
-
151
- private fun applyRectangleMask(canvas: Canvas, frame: Rect) {
152
- val paint = Paint().apply {
153
- color = Color.GRAY
154
- }
155
-
156
- canvas.drawRect(frame, paint)
157
-
158
- // Add some text-like pattern
159
- paint.color = Color.DKGRAY
160
- val lineHeight = 4f
161
- val spacing = 8f
162
-
163
- var y = frame.top + spacing
164
- while (y < frame.bottom - spacing) {
165
- val lineWidth = (Math.random() * frame.width() * 0.5 + frame.width() * 0.3).toFloat()
166
- val x = frame.left + (Math.random() * (frame.width() - lineWidth)).toFloat()
167
- canvas.drawRect(x, y, x + lineWidth, y + lineHeight, paint)
168
- y += lineHeight + spacing
169
- }
170
- }
171
-
172
- private fun applyPixelateMask(canvas: Canvas, frame: Rect) {
173
- val pixelSize = 8f
174
- val pixelCountX = (frame.width() / pixelSize).toInt()
175
- val pixelCountY = (frame.height() / pixelSize).toInt()
176
-
177
- val paint = Paint()
178
-
179
- for (x in 0 until pixelCountX) {
180
- for (y in 0 until pixelCountY) {
181
- val pixelFrame = RectF(
182
- frame.left + x * pixelSize,
183
- frame.top + y * pixelSize,
184
- frame.left + (x + 1) * pixelSize,
185
- frame.top + (y + 1) * pixelSize
186
- )
187
-
188
- // Use a random color for each pixel
189
- paint.color = Color.rgb(
190
- (Math.random() * 255).toInt(),
191
- (Math.random() * 255).toInt(),
192
- (Math.random() * 255).toInt()
193
- )
194
- canvas.drawRect(pixelFrame, paint)
195
- }
196
- }
197
- }
198
-
199
- private enum class MaskingType {
200
- BLUR, RECTANGLE, PIXELATE, NONE
201
- }
202
- }