@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.
- package/docs/NATIVE_MODULE_SETUP.md +175 -0
- package/ios/SessionRecorderNative.podspec +5 -0
- package/package.json +11 -1
- package/plugin/package.json +20 -0
- package/plugin/src/index.js +42 -0
- package/android/src/main/AndroidManifest.xml +0 -2
- package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingModule.kt +0 -202
- package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingPackage.kt +0 -16
- package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderModule.kt +0 -202
- package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderPackage.kt +0 -16
- package/babel.config.js +0 -13
- package/docs/AUTO_METADATA_DETECTION.md +0 -108
- package/docs/TROUBLESHOOTING.md +0 -168
- package/ios/ScreenMasking.m +0 -12
- package/ios/ScreenMasking.podspec +0 -21
- package/ios/ScreenMasking.swift +0 -205
- package/ios/SessionRecorder.podspec +0 -21
- package/scripts/generate-app-metadata.js +0 -173
- package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
- package/src/components/GestureCaptureWrapper/index.ts +0 -1
- package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
- package/src/components/ScreenRecorderView/index.ts +0 -1
- package/src/components/SessionRecorderWidget/FinalPopover.tsx +0 -62
- package/src/components/SessionRecorderWidget/FloatingButton.tsx +0 -136
- package/src/components/SessionRecorderWidget/InitialPopover.tsx +0 -89
- package/src/components/SessionRecorderWidget/ModalContainer.tsx +0 -128
- package/src/components/SessionRecorderWidget/ModalHeader.tsx +0 -24
- package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +0 -109
- package/src/components/SessionRecorderWidget/icons.tsx +0 -52
- package/src/components/SessionRecorderWidget/index.ts +0 -3
- package/src/components/SessionRecorderWidget/styles.ts +0 -150
- package/src/components/index.ts +0 -3
- package/src/config/constants.ts +0 -60
- package/src/config/defaults.ts +0 -83
- package/src/config/index.ts +0 -6
- package/src/config/masking.ts +0 -28
- package/src/config/session-recorder.ts +0 -55
- package/src/config/validators.ts +0 -31
- package/src/context/SessionRecorderContext.tsx +0 -53
- package/src/index.ts +0 -9
- package/src/native/ScreenMasking.ts +0 -34
- package/src/native/SessionRecorderNative.ts +0 -34
- package/src/otel/helpers.ts +0 -275
- package/src/otel/index.ts +0 -138
- package/src/otel/instrumentations/index.ts +0 -115
- package/src/patch/index.ts +0 -1
- package/src/patch/xhr.ts +0 -141
- package/src/recorder/eventExporter.ts +0 -141
- package/src/recorder/gestureRecorder.ts +0 -498
- package/src/recorder/index.ts +0 -179
- package/src/recorder/navigationTracker.ts +0 -449
- package/src/recorder/screenRecorder.ts +0 -527
- package/src/services/api.service.ts +0 -203
- package/src/services/screenMaskingService.ts +0 -118
- package/src/services/storage.service.ts +0 -199
- package/src/session-recorder.ts +0 -606
- package/src/types/expo.d.ts +0 -23
- package/src/types/index.ts +0 -28
- package/src/types/session-recorder.ts +0 -429
- package/src/types/session.ts +0 -65
- package/src/utils/app-metadata.ts +0 -31
- package/src/utils/index.ts +0 -8
- package/src/utils/logger.ts +0 -225
- package/src/utils/nativeModuleTest.ts +0 -60
- package/src/utils/platform.ts +0 -384
- package/src/utils/request-utils.ts +0 -61
- package/src/utils/rrweb-events.ts +0 -309
- package/src/utils/session.ts +0 -18
- package/src/utils/time.ts +0 -17
- package/src/utils/type-utils.ts +0 -75
- package/src/version.ts +0 -1
- package/tsconfig.json +0 -24
- /package/ios/{SessionRecorder.m → SessionRecorderNative.m} +0 -0
- /package/ios/{SessionRecorder.swift → SessionRecorderNative.swift} +0 -0
|
@@ -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
|
-
}
|
package/babel.config.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
presets: [
|
|
3
|
-
'@react-native/babel-preset',
|
|
4
|
-
'@babel/preset-env',
|
|
5
|
-
'@babel/preset-typescript',
|
|
6
|
-
['@babel/preset-react', { runtime: 'automatic' }],
|
|
7
|
-
],
|
|
8
|
-
plugins: [
|
|
9
|
-
['@babel/plugin-transform-private-property-in-object', { loose: true }],
|
|
10
|
-
['@babel/plugin-transform-private-methods', { loose: true }],
|
|
11
|
-
['@babel/plugin-transform-class-properties', { loose: true }],
|
|
12
|
-
],
|
|
13
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Automatic App Metadata Detection
|
|
2
|
-
|
|
3
|
-
The session recorder automatically detects app metadata from your project configuration files **without requiring any developer intervention**.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
The library automatically extracts app information from common configuration files in this priority order:
|
|
8
|
-
|
|
9
|
-
1. **`app.json`** - Expo/React Native app configuration
|
|
10
|
-
2. **`app.config.js`** - Dynamic Expo configuration
|
|
11
|
-
3. **`package.json`** - Node.js package configuration (fallback)
|
|
12
|
-
|
|
13
|
-
## Supported Configuration Files
|
|
14
|
-
|
|
15
|
-
### app.json
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{
|
|
19
|
-
"name": "My Awesome App",
|
|
20
|
-
"version": "1.2.3",
|
|
21
|
-
"displayName": "My App",
|
|
22
|
-
"ios": {
|
|
23
|
-
"bundleIdentifier": "com.mycompany.myapp",
|
|
24
|
-
"buildNumber": "123"
|
|
25
|
-
},
|
|
26
|
-
"android": {
|
|
27
|
-
"package": "com.mycompany.myapp",
|
|
28
|
-
"versionCode": 123
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### app.config.js
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
export default {
|
|
37
|
-
name: 'My Awesome App',
|
|
38
|
-
version: '1.2.3',
|
|
39
|
-
displayName: 'My App',
|
|
40
|
-
ios: {
|
|
41
|
-
bundleIdentifier: 'com.mycompany.myapp',
|
|
42
|
-
buildNumber: '123'
|
|
43
|
-
},
|
|
44
|
-
android: {
|
|
45
|
-
package: 'com.mycompany.myapp',
|
|
46
|
-
versionCode: 123
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### package.json (fallback)
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"name": "my-awesome-app",
|
|
56
|
-
"version": "1.2.3"
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Detected Metadata
|
|
61
|
-
|
|
62
|
-
The following information is automatically extracted:
|
|
63
|
-
|
|
64
|
-
- **App Name** - From `name` or `displayName`
|
|
65
|
-
- **App Version** - From `version`
|
|
66
|
-
- **Bundle ID** - From `ios.bundleIdentifier` or `android.package`
|
|
67
|
-
- **Build Number** - From `ios.buildNumber` or `android.versionCode`
|
|
68
|
-
|
|
69
|
-
## Build Process
|
|
70
|
-
|
|
71
|
-
The metadata detection happens automatically during the build process:
|
|
72
|
-
|
|
73
|
-
1. The build script scans your project root for configuration files
|
|
74
|
-
2. Extracts relevant metadata from the first found configuration
|
|
75
|
-
3. Generates a TypeScript file with the detected metadata
|
|
76
|
-
4. The session recorder uses this metadata in all recordings
|
|
77
|
-
|
|
78
|
-
## No Developer Action Required
|
|
79
|
-
|
|
80
|
-
✅ **Zero configuration** - Works out of the box
|
|
81
|
-
✅ **Automatic detection** - Scans common config files
|
|
82
|
-
✅ **Build-time generation** - No runtime file reading
|
|
83
|
-
✅ **Fallback support** - Graceful degradation
|
|
84
|
-
|
|
85
|
-
## Manual Override (Optional)
|
|
86
|
-
|
|
87
|
-
If you need to override the auto-detected metadata, you can still use the manual configuration:
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import { configureAppMetadata } from '@multiplayer-app/session-recorder-react-native'
|
|
91
|
-
|
|
92
|
-
configureAppMetadata({
|
|
93
|
-
name: 'Custom App Name',
|
|
94
|
-
version: '2.0.0',
|
|
95
|
-
bundleId: 'com.custom.app'
|
|
96
|
-
})
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Priority Order
|
|
100
|
-
|
|
101
|
-
The metadata detection follows this priority:
|
|
102
|
-
|
|
103
|
-
1. **Expo Config** (if using Expo)
|
|
104
|
-
2. **Manual Configuration** (if `configureAppMetadata` was called)
|
|
105
|
-
3. **Auto-detected Metadata** (from config files)
|
|
106
|
-
4. **Fallback Values** (defaults)
|
|
107
|
-
|
|
108
|
-
This ensures maximum compatibility across different React Native setups while providing rich metadata for debugging sessions.
|
package/docs/TROUBLESHOOTING.md
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Troubleshooting Native Module Issues
|
|
2
|
-
|
|
3
|
-
If you're seeing the warning "Screen masking native module is not available - auto-linking may still be in progress", follow these steps:
|
|
4
|
-
|
|
5
|
-
## Quick Fix
|
|
6
|
-
|
|
7
|
-
1. **Clean and Rebuild**:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
# Clean React Native cache
|
|
11
|
-
npx react-native start --reset-cache
|
|
12
|
-
|
|
13
|
-
# For iOS
|
|
14
|
-
cd ios && pod install && cd ..
|
|
15
|
-
npx react-native run-ios
|
|
16
|
-
|
|
17
|
-
# For Android
|
|
18
|
-
npx react-native run-android
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
2. **Check Auto-Linking**:
|
|
22
|
-
```bash
|
|
23
|
-
# Check if auto-linking detected the module
|
|
24
|
-
npx react-native config
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Detailed Troubleshooting
|
|
28
|
-
|
|
29
|
-
### 1. Verify Package Installation
|
|
30
|
-
|
|
31
|
-
Make sure the package is properly installed:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npm list @multiplayer-app/session-recorder-react-native
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### 2. Check Native Module Registration
|
|
38
|
-
|
|
39
|
-
The native module should be named `SessionRecorderNative`. You can verify this by checking the logs for:
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
Available native modules: [..., SessionRecorderNative, ...]
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 3. iOS Specific Issues
|
|
46
|
-
|
|
47
|
-
**Check Podfile**:
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
# In your ios/Podfile, you should see:
|
|
51
|
-
pod 'SessionRecorderNative', :path => '../node_modules/@multiplayer-app/session-recorder-react-native/ios'
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Clean iOS Build**:
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
cd ios
|
|
58
|
-
rm -rf build
|
|
59
|
-
rm -rf Pods
|
|
60
|
-
rm Podfile.lock
|
|
61
|
-
pod install
|
|
62
|
-
cd ..
|
|
63
|
-
npx react-native run-ios
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 4. Android Specific Issues
|
|
67
|
-
|
|
68
|
-
**Check MainApplication.java**:
|
|
69
|
-
|
|
70
|
-
```java
|
|
71
|
-
// Should be automatically added by auto-linking:
|
|
72
|
-
import com.multiplayer.sessionrecorder.SessionRecorderPackage;
|
|
73
|
-
|
|
74
|
-
// In getPackages():
|
|
75
|
-
new SessionRecorderPackage()
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Clean Android Build**:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
cd android
|
|
82
|
-
./gradlew clean
|
|
83
|
-
cd ..
|
|
84
|
-
npx react-native run-android
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### 5. Manual Linking (If Auto-Linking Fails)
|
|
88
|
-
|
|
89
|
-
If auto-linking doesn't work, you can manually link:
|
|
90
|
-
|
|
91
|
-
**iOS**:
|
|
92
|
-
|
|
93
|
-
1. Add to `ios/Podfile`:
|
|
94
|
-
```ruby
|
|
95
|
-
pod 'SessionRecorderNative', :path => '../node_modules/@multiplayer-app/session-recorder-react-native/ios'
|
|
96
|
-
```
|
|
97
|
-
2. Run `pod install`
|
|
98
|
-
|
|
99
|
-
**Android**:
|
|
100
|
-
|
|
101
|
-
1. Add to `android/settings.gradle`:
|
|
102
|
-
```gradle
|
|
103
|
-
include ':react-native-session-recorder'
|
|
104
|
-
project(':react-native-session-recorder').projectDir = new File(rootProject.projectDir, '../node_modules/@multiplayer-app/session-recorder-react-native/android')
|
|
105
|
-
```
|
|
106
|
-
2. Add to `android/app/build.gradle`:
|
|
107
|
-
```gradle
|
|
108
|
-
dependencies {
|
|
109
|
-
implementation project(':react-native-session-recorder')
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
3. Add to `MainApplication.java`:
|
|
113
|
-
|
|
114
|
-
```java
|
|
115
|
-
import com.multiplayer.sessionrecorder.SessionRecorderPackage;
|
|
116
|
-
|
|
117
|
-
@Override
|
|
118
|
-
protected List<ReactPackage> getPackages() {
|
|
119
|
-
return Arrays.<ReactPackage>asList(
|
|
120
|
-
new MainReactPackage(),
|
|
121
|
-
new SessionRecorderPackage()
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 6. Debug Native Module Availability
|
|
127
|
-
|
|
128
|
-
Add this to your app to debug:
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
import { testNativeModuleAvailability } from '@multiplayer-app/session-recorder-react-native/utils'
|
|
132
|
-
|
|
133
|
-
// Call this in your app
|
|
134
|
-
testNativeModuleAvailability()
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
This will log detailed information about native module availability.
|
|
138
|
-
|
|
139
|
-
### 7. Common Issues
|
|
140
|
-
|
|
141
|
-
**Issue**: "Module not found" error
|
|
142
|
-
**Solution**: Ensure the package is installed and auto-linking is working
|
|
143
|
-
|
|
144
|
-
**Issue**: "Method not available" error
|
|
145
|
-
**Solution**: The native module is linked but methods aren't exposed. Check the native code.
|
|
146
|
-
|
|
147
|
-
**Issue**: Auto-linking not working
|
|
148
|
-
**Solution**: Check `react-native.config.js` and ensure React Native version is 0.60+
|
|
149
|
-
|
|
150
|
-
**Issue**: Pod install fails
|
|
151
|
-
**Solution**: Update CocoaPods: `sudo gem install cocoapods`
|
|
152
|
-
|
|
153
|
-
### 8. Verification
|
|
154
|
-
|
|
155
|
-
Once fixed, you should see:
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
✅ SessionRecorderNative module is available!
|
|
159
|
-
✅ captureAndMask method is available!
|
|
160
|
-
✅ captureAndMaskWithOptions method is available!
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## Still Having Issues?
|
|
164
|
-
|
|
165
|
-
1. Check the [React Native Auto-Linking documentation](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md)
|
|
166
|
-
2. Verify your React Native version (0.60+ required)
|
|
167
|
-
3. Check for conflicting native modules
|
|
168
|
-
4. Try creating a fresh React Native project and testing the module there
|
package/ios/ScreenMasking.m
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#import <React/RCTBridgeModule.h>
|
|
2
|
-
|
|
3
|
-
@interface RCT_EXTERN_MODULE(SessionRecorder, NSObject)
|
|
4
|
-
|
|
5
|
-
RCT_EXTERN_METHOD(captureAndMask:(RCTPromiseResolveBlock)resolve
|
|
6
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
7
|
-
|
|
8
|
-
RCT_EXTERN_METHOD(captureAndMaskWithOptions:(NSDictionary *)options
|
|
9
|
-
resolve:(RCTPromiseResolveBlock)resolve
|
|
10
|
-
reject:(RCTPromiseRejectBlock)reject)
|
|
11
|
-
|
|
12
|
-
@end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
|
|
3
|
-
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
|
|
4
|
-
|
|
5
|
-
Pod::Spec.new do |s|
|
|
6
|
-
s.name = "SessionRecorder"
|
|
7
|
-
s.version = package["version"]
|
|
8
|
-
s.summary = "Native session recorder module for React Native"
|
|
9
|
-
s.description = "A native module that provides session recording with automatic masking of sensitive UI elements"
|
|
10
|
-
s.homepage = "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript"
|
|
11
|
-
s.license = "MIT"
|
|
12
|
-
s.authors = { "Multiplayer Software, Inc." => "https://www.multiplayer.app" }
|
|
13
|
-
s.platforms = { :ios => "12.0" }
|
|
14
|
-
s.source = { :git => "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript.git", :tag => "#{s.version}" }
|
|
15
|
-
|
|
16
|
-
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
-
s.requires_arc = true
|
|
18
|
-
|
|
19
|
-
s.dependency "React-Core"
|
|
20
|
-
s.dependency "React"
|
|
21
|
-
end
|
package/ios/ScreenMasking.swift
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import UIKit
|
|
2
|
-
import React
|
|
3
|
-
|
|
4
|
-
@objc(SessionRecorder)
|
|
5
|
-
class SessionRecorder: NSObject {
|
|
6
|
-
|
|
7
|
-
@objc func captureAndMask(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
8
|
-
DispatchQueue.main.async {
|
|
9
|
-
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
|
|
10
|
-
reject("NO_WINDOW", "Unable to get key window", nil)
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, UIScreen.main.scale)
|
|
15
|
-
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
|
|
16
|
-
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
|
|
17
|
-
UIGraphicsEndImageContext()
|
|
18
|
-
|
|
19
|
-
guard let image = screenshot else {
|
|
20
|
-
reject("CAPTURE_FAILED", "Failed to capture screen", nil)
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Apply masking to sensitive elements
|
|
25
|
-
let maskedImage = self.applyMasking(to: image, in: window)
|
|
26
|
-
|
|
27
|
-
if let data = maskedImage.jpegData(compressionQuality: 0.5) {
|
|
28
|
-
let base64 = data.base64EncodedString()
|
|
29
|
-
resolve(base64)
|
|
30
|
-
} else {
|
|
31
|
-
reject("ENCODING_FAILED", "Failed to encode image", nil)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@objc func captureAndMaskWithOptions(_ options: NSDictionary, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
37
|
-
DispatchQueue.main.async {
|
|
38
|
-
guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }) else {
|
|
39
|
-
reject("NO_WINDOW", "Unable to get key window", nil)
|
|
40
|
-
return
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
UIGraphicsBeginImageContextWithOptions(window.bounds.size, false, UIScreen.main.scale)
|
|
44
|
-
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
|
|
45
|
-
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
|
|
46
|
-
UIGraphicsEndImageContext()
|
|
47
|
-
|
|
48
|
-
guard let image = screenshot else {
|
|
49
|
-
reject("CAPTURE_FAILED", "Failed to capture screen", nil)
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Apply masking with custom options
|
|
54
|
-
let maskedImage = self.applyMaskingWithOptions(to: image, in: window, options: options)
|
|
55
|
-
|
|
56
|
-
if let data = maskedImage.jpegData(compressionQuality: 0.5) {
|
|
57
|
-
let base64 = data.base64EncodedString()
|
|
58
|
-
resolve(base64)
|
|
59
|
-
} else {
|
|
60
|
-
reject("ENCODING_FAILED", "Failed to encode image", nil)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private func applyMasking(to image: UIImage, in window: UIWindow) -> UIImage {
|
|
66
|
-
return applyMaskingWithOptions(to: image, in: window, options: [:])
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private func applyMaskingWithOptions(to image: UIImage, in window: UIWindow, options: NSDictionary) -> UIImage {
|
|
70
|
-
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
|
|
71
|
-
guard let context = UIGraphicsGetCurrentContext() else { return image }
|
|
72
|
-
|
|
73
|
-
// Draw the original image
|
|
74
|
-
image.draw(in: CGRect(origin: .zero, size: image.size))
|
|
75
|
-
|
|
76
|
-
// Find and mask sensitive elements
|
|
77
|
-
let sensitiveElements = findSensitiveElements(in: window)
|
|
78
|
-
|
|
79
|
-
for element in sensitiveElements {
|
|
80
|
-
let frame = element.frame
|
|
81
|
-
let maskingType = getMaskingType(for: element)
|
|
82
|
-
|
|
83
|
-
switch maskingType {
|
|
84
|
-
case .blur:
|
|
85
|
-
applyBlurMask(in: context, frame: frame)
|
|
86
|
-
case .rectangle:
|
|
87
|
-
applyRectangleMask(in: context, frame: frame)
|
|
88
|
-
case .pixelate:
|
|
89
|
-
applyPixelateMask(in: context, frame: frame, image: image)
|
|
90
|
-
case .none:
|
|
91
|
-
break
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
let maskedImage = UIGraphicsGetImageFromCurrentImageContext() ?? image
|
|
96
|
-
UIGraphicsEndImageContext()
|
|
97
|
-
|
|
98
|
-
return maskedImage
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private func findSensitiveElements(in view: UIView) -> [UIView] {
|
|
102
|
-
var sensitiveElements: [UIView] = []
|
|
103
|
-
|
|
104
|
-
func traverseView(_ view: UIView) {
|
|
105
|
-
// Check if this view should be masked
|
|
106
|
-
if shouldMaskView(view) {
|
|
107
|
-
sensitiveElements.append(view)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Recursively check subviews
|
|
111
|
-
for subview in view.subviews {
|
|
112
|
-
traverseView(subview)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
traverseView(view)
|
|
117
|
-
return sensitiveElements
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
private func shouldMaskView(_ view: UIView) -> Bool {
|
|
121
|
-
// Check for UITextField - mask all text fields when inputMasking is enabled
|
|
122
|
-
if view is UITextField {
|
|
123
|
-
return true
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check for UITextView - mask all text views when inputMasking is enabled
|
|
127
|
-
if view is UITextView {
|
|
128
|
-
return true
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return false
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private func getMaskingType(for view: UIView) -> MaskingType {
|
|
135
|
-
// Default masking type for all text inputs
|
|
136
|
-
return .rectangle
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private func applyBlurMask(in context: CGContext, frame: CGRect) {
|
|
140
|
-
// Create a blur effect
|
|
141
|
-
context.setFillColor(UIColor.black.withAlphaComponent(0.8).cgColor)
|
|
142
|
-
context.fill(frame)
|
|
143
|
-
|
|
144
|
-
// Add some noise to make it look blurred
|
|
145
|
-
context.setFillColor(UIColor.white.withAlphaComponent(0.3).cgColor)
|
|
146
|
-
for _ in 0..<20 {
|
|
147
|
-
let randomX = frame.origin.x + CGFloat.random(in: 0...frame.width)
|
|
148
|
-
let randomY = frame.origin.y + CGFloat.random(in: 0...frame.height)
|
|
149
|
-
let randomSize = CGFloat.random(in: 2...8)
|
|
150
|
-
context.fillEllipse(in: CGRect(x: randomX, y: randomY, width: randomSize, height: randomSize))
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private func applyRectangleMask(in context: CGContext, frame: CGRect) {
|
|
155
|
-
// Simple rectangle fill
|
|
156
|
-
context.setFillColor(UIColor.gray.cgColor)
|
|
157
|
-
context.fill(frame)
|
|
158
|
-
|
|
159
|
-
// Add some text-like pattern
|
|
160
|
-
context.setFillColor(UIColor.darkGray.cgColor)
|
|
161
|
-
let lineHeight: CGFloat = 4
|
|
162
|
-
let spacing: CGFloat = 8
|
|
163
|
-
|
|
164
|
-
for i in stride(from: frame.origin.y + spacing, to: frame.origin.y + frame.height - spacing, by: lineHeight + spacing) {
|
|
165
|
-
let lineWidth = CGFloat.random(in: frame.width * 0.3...frame.width * 0.8)
|
|
166
|
-
let lineX = frame.origin.x + CGFloat.random(in: 0...(frame.width - lineWidth))
|
|
167
|
-
context.fill(CGRect(x: lineX, y: i, width: lineWidth, height: lineHeight))
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private func applyPixelateMask(in context: CGContext, frame: CGRect, image: UIImage) {
|
|
172
|
-
// Create a pixelated effect
|
|
173
|
-
let pixelSize: CGFloat = 8
|
|
174
|
-
let pixelCountX = Int(frame.width / pixelSize)
|
|
175
|
-
let pixelCountY = Int(frame.height / pixelSize)
|
|
176
|
-
|
|
177
|
-
for x in 0..<pixelCountX {
|
|
178
|
-
for y in 0..<pixelCountY {
|
|
179
|
-
let pixelFrame = CGRect(
|
|
180
|
-
x: frame.origin.x + CGFloat(x) * pixelSize,
|
|
181
|
-
y: frame.origin.y + CGFloat(y) * pixelSize,
|
|
182
|
-
width: pixelSize,
|
|
183
|
-
height: pixelSize
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
// Use a random color for each pixel
|
|
187
|
-
let randomColor = UIColor(
|
|
188
|
-
red: CGFloat.random(in: 0...1),
|
|
189
|
-
green: CGFloat.random(in: 0...1),
|
|
190
|
-
blue: CGFloat.random(in: 0...1),
|
|
191
|
-
alpha: 1.0
|
|
192
|
-
)
|
|
193
|
-
context.setFillColor(randomColor.cgColor)
|
|
194
|
-
context.fill(pixelFrame)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private enum MaskingType {
|
|
201
|
-
case blur
|
|
202
|
-
case rectangle
|
|
203
|
-
case pixelate
|
|
204
|
-
case none
|
|
205
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
require "json"
|
|
2
|
-
|
|
3
|
-
package = JSON.parse(File.read(File.join(__dir__, "..", "package.json")))
|
|
4
|
-
|
|
5
|
-
Pod::Spec.new do |s|
|
|
6
|
-
s.name = "SessionRecorderNative"
|
|
7
|
-
s.version = package["version"]
|
|
8
|
-
s.summary = "Native session recorder module for React Native"
|
|
9
|
-
s.description = "A native module that provides session recording with automatic masking of sensitive UI elements"
|
|
10
|
-
s.homepage = "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript"
|
|
11
|
-
s.license = "MIT"
|
|
12
|
-
s.authors = { "Multiplayer Software, Inc." => "https://www.multiplayer.app" }
|
|
13
|
-
s.platforms = { :ios => "12.0" }
|
|
14
|
-
s.source = { :git => "https://github.com/multiplayer-app/multiplayer-session-recorder-javascript.git", :tag => "#{s.version}" }
|
|
15
|
-
|
|
16
|
-
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
|
-
s.requires_arc = true
|
|
18
|
-
|
|
19
|
-
s.dependency "React-Core"
|
|
20
|
-
s.dependency "React"
|
|
21
|
-
end
|