@multiplayer-app/session-recorder-react-native 0.0.1-beta.1 → 0.0.1-beta.11
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/SessionRecorderNative.podspec +29 -0
- package/android/build.gradle +32 -0
- package/copy-react-native-dist.sh +4 -10
- package/dist/components/MaskableComponent.d.ts +22 -0
- package/dist/components/MaskableComponent.js +1 -0
- package/dist/components/MaskableComponent.js.map +1 -0
- package/dist/components/MaskableTextInput.d.ts +14 -0
- package/dist/components/MaskableTextInput.js +1 -0
- package/dist/components/MaskableTextInput.js.map +1 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
- package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
- package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
- package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
- package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
- package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
- package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
- package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
- package/dist/components/SessionRecorderWidget/icons.js +1 -0
- package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
- package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
- package/dist/components/SessionRecorderWidget/index.js +1 -0
- package/dist/components/SessionRecorderWidget/index.js.map +1 -0
- package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
- package/dist/components/SessionRecorderWidget/styles.js +1 -0
- package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/masking.js +1 -1
- package/dist/config/masking.js.map +1 -1
- package/dist/context/SessionRecorderContext.d.ts +5 -3
- package/dist/context/SessionRecorderContext.js +1 -1
- package/dist/context/SessionRecorderContext.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/native/ScreenMasking.d.ts +21 -0
- package/dist/native/ScreenMasking.js +1 -0
- package/dist/native/ScreenMasking.js.map +1 -0
- package/dist/native/SessionRecorderNative.d.ts +21 -0
- package/dist/native/SessionRecorderNative.js +1 -0
- package/dist/native/SessionRecorderNative.js.map +1 -0
- package/dist/patch/xhr.js +1 -1
- package/dist/patch/xhr.js.map +1 -1
- package/dist/recorder/screenRecorder.d.ts +1 -0
- package/dist/recorder/screenRecorder.js +1 -1
- package/dist/recorder/screenRecorder.js.map +1 -1
- package/dist/recorder/screenshotManager.d.ts +10 -0
- package/dist/recorder/screenshotManager.js +1 -0
- package/dist/recorder/screenshotManager.js.map +1 -0
- package/dist/services/screenMaskingService.d.ts +39 -0
- package/dist/services/screenMaskingService.js +1 -0
- package/dist/services/screenMaskingService.js.map +1 -0
- package/dist/services/storage.service.d.ts +18 -2
- package/dist/services/storage.service.js +1 -1
- package/dist/services/storage.service.js.map +1 -1
- package/dist/session-recorder.d.ts +2 -1
- package/dist/session-recorder.js +1 -1
- package/dist/session-recorder.js.map +1 -1
- package/dist/types/session-recorder.d.ts +6 -0
- package/dist/types/session-recorder.js.map +1 -1
- package/dist/utils/componentRegistry.d.ts +64 -0
- package/dist/utils/componentRegistry.js +1 -0
- package/dist/utils/componentRegistry.js.map +1 -0
- package/dist/utils/nativeModuleTest.d.ts +8 -0
- package/dist/utils/nativeModuleTest.js +1 -0
- package/dist/utils/nativeModuleTest.js.map +1 -0
- package/dist/utils/platform.d.ts +3 -0
- package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
- package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
- package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
- package/dist/utils/screenshotMasker.d.ts +96 -0
- package/dist/utils/screenshotMasker.js +1 -0
- package/dist/utils/screenshotMasker.js.map +1 -0
- package/dist/utils/viewHierarchyTracker.d.ts +89 -0
- package/dist/utils/viewHierarchyTracker.js +1 -0
- package/dist/utils/viewHierarchyTracker.js.map +1 -0
- package/ios/SessionRecorderNative.m +17 -0
- package/ios/SessionRecorderNative.podspec +26 -0
- package/ios/SessionRecorderNative.swift +205 -0
- package/package.json +22 -7
- package/react-native.config.js +15 -0
- package/RRWEB_INTEGRATION.md +0 -336
- package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
- package/babel.config.js +0 -13
- package/dist/components/GestureCaptureWrapper.d.ts +0 -6
- package/dist/components/GestureCaptureWrapper.js +0 -1
- package/dist/components/GestureCaptureWrapper.js.map +0 -1
- package/dist/expo.d.ts +0 -7
- package/dist/expo.js +0 -1
- package/dist/expo.js.map +0 -1
- package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
- package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
- package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
- package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
- package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
- package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
- package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
- package/dist/recorder/gestureHandlerRecorder.js +0 -1
- package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
- package/dist/types/rrweb.d.ts +0 -118
- package/dist/types/rrweb.js +0 -1
- package/dist/types/rrweb.js.map +0 -1
- 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/index.ts +0 -1
- package/src/config/constants.ts +0 -60
- package/src/config/defaults.ts +0 -82
- package/src/config/index.ts +0 -6
- package/src/config/masking.ts +0 -27
- package/src/config/session-recorder.ts +0 -55
- package/src/config/validators.ts +0 -31
- package/src/context/SessionRecorderContext.tsx +0 -75
- package/src/expo.ts +0 -11
- package/src/index.ts +0 -17
- 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 -142
- 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 -498
- package/src/services/api.service.ts +0 -203
- package/src/services/storage.service.ts +0 -158
- package/src/session-recorder.ts +0 -600
- package/src/types/expo.d.ts +0 -23
- package/src/types/index.ts +0 -28
- package/src/types/session-recorder.ts +0 -423
- 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/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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewHierarchyTracker.js","sourceRoot":"","sources":["../../src/utils/viewHierarchyTracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(SessionRecorderNative, 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
|
+
+ (BOOL)requiresMainQueueSetup
|
|
13
|
+
{
|
|
14
|
+
return YES;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@end
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
|
|
22
|
+
# Ensure proper linking for Expo
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/React-Core/React\""
|
|
25
|
+
}
|
|
26
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import React
|
|
3
|
+
|
|
4
|
+
@objc(SessionRecorderNative)
|
|
5
|
+
class SessionRecorderNative: 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
|
+
}
|
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.
|
|
3
|
+
"version": "0.0.1-beta.11",
|
|
4
4
|
"description": "Multiplayer Fullstack Session Recorder for React Native",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Multiplayer Software, Inc.",
|
|
@@ -21,10 +21,7 @@
|
|
|
21
21
|
"import": "./dist/index.js",
|
|
22
22
|
"require": "./dist/index.js"
|
|
23
23
|
},
|
|
24
|
-
"./
|
|
25
|
-
"import": "./dist/expo.js",
|
|
26
|
-
"require": "./dist/expo.js"
|
|
27
|
-
}
|
|
24
|
+
"./app.plugin.js": "./app.plugin.js"
|
|
28
25
|
},
|
|
29
26
|
"engines": {
|
|
30
27
|
"node": ">=18",
|
|
@@ -86,11 +83,29 @@
|
|
|
86
83
|
},
|
|
87
84
|
"peerDependencies": {
|
|
88
85
|
"@opentelemetry/api": "^1.9.0",
|
|
89
|
-
"expo": ">=49.0.0 <54.0.0",
|
|
90
86
|
"react": ">=18.0.0 <20.0.0",
|
|
91
|
-
"react-native": ">=0.72.0 <0.82.0"
|
|
87
|
+
"react-native": ">=0.72.0 <0.82.0",
|
|
88
|
+
"react-native-svg": "^15.13.0"
|
|
92
89
|
},
|
|
93
90
|
"optionalDependencies": {
|
|
94
91
|
"expo-constants": "^15.0.0"
|
|
92
|
+
},
|
|
93
|
+
"react-native": {
|
|
94
|
+
"ios": {
|
|
95
|
+
"podspecPath": "./SessionRecorderNative.podspec"
|
|
96
|
+
},
|
|
97
|
+
"android": {
|
|
98
|
+
"sourceDir": "./android",
|
|
99
|
+
"packageImportPath": "import com.multiplayer.sessionrecorder.SessionRecorderPackage;"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"expo": {
|
|
103
|
+
"ios": {
|
|
104
|
+
"podspecPath": "./SessionRecorderNative.podspec"
|
|
105
|
+
},
|
|
106
|
+
"android": {
|
|
107
|
+
"sourceDir": "./android",
|
|
108
|
+
"packageImportPath": "import com.multiplayer.sessionrecorder.SessionRecorderPackage;"
|
|
109
|
+
}
|
|
95
110
|
}
|
|
96
111
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
dependencies: {
|
|
3
|
+
'@multiplayer-app/session-recorder-react-native': {
|
|
4
|
+
platforms: {
|
|
5
|
+
android: {
|
|
6
|
+
sourceDir: './android',
|
|
7
|
+
packageImportPath: 'import com.multiplayer.sessionrecorder.SessionRecorderPackage;'
|
|
8
|
+
},
|
|
9
|
+
ios: {
|
|
10
|
+
podspecPath: './SessionRecorderNative.podspec'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
package/RRWEB_INTEGRATION.md
DELETED
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
# RRWeb Integration for React Native Session Recorder
|
|
2
|
-
|
|
3
|
-
This document explains the rrweb-compatible event generation system implemented for React Native session recording.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The React Native session recorder automatically generates rrweb-compatible events that can be replayed using standard rrweb players. **No manual intervention is required** - the system automatically captures:
|
|
8
|
-
|
|
9
|
-
1. **Screen snapshots** as `FullSnapshotEvent` with base64-encoded images (periodic + on interaction)
|
|
10
|
-
2. **Touch interactions** as `IncrementalSnapshotEvent` with `MouseInteraction` data (automatic)
|
|
11
|
-
3. **Navigation events** and other user interactions (automatic)
|
|
12
|
-
|
|
13
|
-
**Recording starts automatically when you call `sessionRecorder.start()` and stops when you call `sessionRecorder.stop()`.**
|
|
14
|
-
|
|
15
|
-
### 🚀 **Smart Change Detection**
|
|
16
|
-
|
|
17
|
-
The system now includes **intelligent change detection** that prevents duplicate events:
|
|
18
|
-
|
|
19
|
-
- **Automatic Comparison**: Each screen capture is compared with the previous one
|
|
20
|
-
- **Hash-Based Detection**: Uses lightweight hashing to detect changes quickly
|
|
21
|
-
- **Skip Unchanged Screens**: Only sends events when the screen actually changes
|
|
22
|
-
- **Touch-Triggered Capture**: Forces capture after touch interactions regardless of change detection
|
|
23
|
-
|
|
24
|
-
## Architecture
|
|
25
|
-
|
|
26
|
-
### Core Components
|
|
27
|
-
|
|
28
|
-
1. **RRWeb Types** (`src/types/rrweb.ts`)
|
|
29
|
-
|
|
30
|
-
- Complete TypeScript definitions for rrweb events
|
|
31
|
-
- React Native specific types for screen and touch data
|
|
32
|
-
|
|
33
|
-
2. **SessionRecorder** (`src/session-recorder.ts`)
|
|
34
|
-
|
|
35
|
-
- Main entry point with `recordEvent()` method for custom events
|
|
36
|
-
- Automatic touch recording (internal methods)
|
|
37
|
-
- Automatic screen capture coordination
|
|
38
|
-
|
|
39
|
-
3. **ScreenRecorder** (`src/recorder/screenRecorder.ts`)
|
|
40
|
-
|
|
41
|
-
- Captures screenshots and converts to `FullSnapshotEvent`
|
|
42
|
-
- Creates virtual DOM with `<img>` elements containing base64 screenshots
|
|
43
|
-
|
|
44
|
-
4. **GestureRecorder** (`src/recorder/gestureRecorder.ts`)
|
|
45
|
-
|
|
46
|
-
- Automatically converts touch events to rrweb `MouseInteraction` events
|
|
47
|
-
- Maps React Native coordinates to rrweb format
|
|
48
|
-
- Sets up automatic touch capture on session start
|
|
49
|
-
|
|
50
|
-
5. **TouchEventCapture** (`src/context/SessionRecorderContext.tsx`)
|
|
51
|
-
- React component that automatically captures touch events
|
|
52
|
-
- No manual setup required - works automatically when session is active
|
|
53
|
-
|
|
54
|
-
## Event Types Generated
|
|
55
|
-
|
|
56
|
-
### FullSnapshotEvent
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
{
|
|
60
|
-
type: EventType.FullSnapshot,
|
|
61
|
-
data: {
|
|
62
|
-
node: {
|
|
63
|
-
type: 1, // Element node
|
|
64
|
-
id: 1,
|
|
65
|
-
tagName: 'div',
|
|
66
|
-
attributes: { style: 'width: 375px; height: 667px; position: relative;' },
|
|
67
|
-
childNodes: [{
|
|
68
|
-
type: 1,
|
|
69
|
-
id: 2,
|
|
70
|
-
tagName: 'img',
|
|
71
|
-
attributes: {
|
|
72
|
-
src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...',
|
|
73
|
-
width: '375',
|
|
74
|
-
height: '667',
|
|
75
|
-
style: 'width: 375px; height: 667px;'
|
|
76
|
-
}
|
|
77
|
-
}]
|
|
78
|
-
},
|
|
79
|
-
initialOffset: { left: 0, top: 0 }
|
|
80
|
-
},
|
|
81
|
-
timestamp: 1640995200000
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### IncrementalSnapshotEvent (Touch Interactions)
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
{
|
|
89
|
-
type: EventType.IncrementalSnapshot,
|
|
90
|
-
data: {
|
|
91
|
-
source: IncrementalSource.MouseInteraction,
|
|
92
|
-
type: MouseInteractionType.TouchStart, // or TouchMove, TouchEnd
|
|
93
|
-
id: 2, // References the image node ID
|
|
94
|
-
x: 150,
|
|
95
|
-
y: 200
|
|
96
|
-
},
|
|
97
|
-
timestamp: 1640995201000
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Usage
|
|
102
|
-
|
|
103
|
-
### Basic Setup (Automatic Recording)
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
import { SessionRecorderProvider } from '@multiplayer-app/session-recorder-react-native'
|
|
107
|
-
|
|
108
|
-
function App() {
|
|
109
|
-
return (
|
|
110
|
-
<SessionRecorderProvider
|
|
111
|
-
options={{
|
|
112
|
-
apiKey: 'your-api-key',
|
|
113
|
-
version: '1.0.0',
|
|
114
|
-
application: 'MyApp',
|
|
115
|
-
environment: 'production',
|
|
116
|
-
recordScreen: true, // Automatic screen capture
|
|
117
|
-
recordGestures: true // Automatic touch recording
|
|
118
|
-
}}
|
|
119
|
-
>
|
|
120
|
-
<YourAppContent />
|
|
121
|
-
</SessionRecorderProvider>
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
**That's it!** Recording is now automatic. When you start a session, the system will:
|
|
127
|
-
|
|
128
|
-
- Automatically capture screen snapshots periodically
|
|
129
|
-
- Automatically record all touch interactions
|
|
130
|
-
- Generate rrweb-compatible events without any manual intervention
|
|
131
|
-
|
|
132
|
-
### Custom Event Recording (Optional)
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
import { useSessionRecorder } from '@multiplayer-app/session-recorder-react-native'
|
|
136
|
-
|
|
137
|
-
function MyComponent() {
|
|
138
|
-
const { client } = useSessionRecorder()
|
|
139
|
-
|
|
140
|
-
const handleCustomEvent = () => {
|
|
141
|
-
// Record a custom rrweb event (optional)
|
|
142
|
-
client.recordEvent({
|
|
143
|
-
type: EventType.Custom,
|
|
144
|
-
data: { customData: 'value' },
|
|
145
|
-
timestamp: Date.now()
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return <Button onPress={handleCustomEvent} title='Record Custom Event' />
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Integration Points
|
|
154
|
-
|
|
155
|
-
### Screen Capture Integration
|
|
156
|
-
|
|
157
|
-
The `ScreenRecorder` class now includes **complete react-native-view-shot integration**:
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
// In screenRecorder.ts - _captureScreenBase64 method
|
|
161
|
-
private async _captureScreenBase64(): Promise<string | null> {
|
|
162
|
-
try {
|
|
163
|
-
if (!this.viewShotRef) {
|
|
164
|
-
console.warn('ViewShot ref not available for screen capture')
|
|
165
|
-
return null
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Capture the screen using react-native-view-shot
|
|
169
|
-
const result = await captureRef(this.viewShotRef, {
|
|
170
|
-
format: this.captureFormat,
|
|
171
|
-
quality: this.captureQuality,
|
|
172
|
-
result: 'base64'
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
return result
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error('Failed to capture screen:', error)
|
|
178
|
-
return null
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**The ViewShot ref is automatically set up** in the `TouchEventCapture` component, so no manual configuration is needed!
|
|
184
|
-
|
|
185
|
-
### Automatic ViewShot Setup
|
|
186
|
-
|
|
187
|
-
The `TouchEventCapture` component automatically sets up the ViewShot ref:
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
// In SessionRecorderContext.tsx
|
|
191
|
-
const TouchEventCapture: React.FC<{ children: ReactNode }> = ({ children }) => {
|
|
192
|
-
const context = useContext(SessionRecorderContext)
|
|
193
|
-
const viewShotRef = useRef<View>(null)
|
|
194
|
-
|
|
195
|
-
// Callback ref to set the viewshot ref immediately when available
|
|
196
|
-
const setViewShotRef = (ref: View | null) => {
|
|
197
|
-
if (ref && context?.client) {
|
|
198
|
-
context.client.setViewShotRef?.(ref)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return (
|
|
203
|
-
<View
|
|
204
|
-
ref={(ref) => {
|
|
205
|
-
viewShotRef.current = ref
|
|
206
|
-
setViewShotRef(ref)
|
|
207
|
-
}}
|
|
208
|
-
style={{ flex: 1 }}
|
|
209
|
-
onTouchStart={handleTouchStart}
|
|
210
|
-
onTouchMove={handleTouchMove}
|
|
211
|
-
onTouchEnd={handleTouchEnd}
|
|
212
|
-
>
|
|
213
|
-
{children}
|
|
214
|
-
</View>
|
|
215
|
-
)
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
This ensures that:
|
|
220
|
-
|
|
221
|
-
- The same View that captures touch events is used for screen capture
|
|
222
|
-
- Perfect synchronization between touch interactions and screen captures
|
|
223
|
-
- No manual ref setup required
|
|
224
|
-
|
|
225
|
-
### Required Dependencies
|
|
226
|
-
|
|
227
|
-
Add these to your `package.json`:
|
|
228
|
-
|
|
229
|
-
```json
|
|
230
|
-
{
|
|
231
|
-
"dependencies": {
|
|
232
|
-
"react-native-view-shot": "^3.8.0"
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
## Automatic Event Flow
|
|
238
|
-
|
|
239
|
-
1. **Session Start**: `SessionRecorder.start()` automatically initializes all recorders
|
|
240
|
-
2. **Automatic Screen Capture**: `ScreenRecorder` automatically captures screenshots periodically + on interactions
|
|
241
|
-
3. **Automatic Touch Events**: `TouchEventCapture` automatically captures all touch interactions
|
|
242
|
-
4. **Automatic Event Generation**: Touch events are automatically converted to rrweb `MouseInteraction` events
|
|
243
|
-
5. **Automatic Event Recording**: All events are automatically stored and can be exported
|
|
244
|
-
|
|
245
|
-
**No manual intervention required!** The entire process is automatic once you start a session.
|
|
246
|
-
|
|
247
|
-
## Customization
|
|
248
|
-
|
|
249
|
-
### Screen Capture Frequency
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
// Adjust capture interval in ScreenRecorder
|
|
253
|
-
screenRecorder.setCaptureInterval(3000) // Capture every 3 seconds
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Change Detection Configuration
|
|
257
|
-
|
|
258
|
-
```typescript
|
|
259
|
-
// Enable/disable change detection
|
|
260
|
-
screenRecorder.setChangeDetection(true) // Default: true
|
|
261
|
-
|
|
262
|
-
// Adjust hash sample size for change detection
|
|
263
|
-
screenRecorder.setHashSampleSize(200) // Default: 100 characters
|
|
264
|
-
|
|
265
|
-
// Force capture (bypasses change detection)
|
|
266
|
-
screenRecorder.forceCapture()
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### Touch Event Throttling
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
// Adjust gesture throttling in GestureRecorder
|
|
273
|
-
gestureRecorder.setGestureThrottle(100) // Throttle to 100ms
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Event Filtering
|
|
277
|
-
|
|
278
|
-
You can filter events by modifying the `recordEvent` method in `SessionRecorder`:
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
recordEvent(event: RRWebEvent): void {
|
|
282
|
-
if (!this._isInitialized || this.sessionState !== SessionState.started) {
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Add custom filtering logic here
|
|
287
|
-
if (event.type === EventType.IncrementalSnapshot) {
|
|
288
|
-
// Filter out certain touch events if needed
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
this._recorder.recordEvent(event)
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
## Exporting Events
|
|
296
|
-
|
|
297
|
-
To export recorded events for rrweb playback:
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
const events = sessionRecorder.getRecordedEvents()
|
|
301
|
-
// Save events to file or send to server
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
## Troubleshooting
|
|
305
|
-
|
|
306
|
-
### Common Issues
|
|
307
|
-
|
|
308
|
-
1. **Screen capture not working**: Ensure `react-native-view-shot` is properly installed and configured
|
|
309
|
-
2. **Touch events not recorded**: Check that `TouchEventCapture` wraps your app content
|
|
310
|
-
3. **Events not generated**: Verify session is in `started` state
|
|
311
|
-
|
|
312
|
-
### Debug Mode
|
|
313
|
-
|
|
314
|
-
Enable debug logging by setting:
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
// In your app configuration
|
|
318
|
-
console.log('Recording stats:', sessionRecorder.getRecordingStats())
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
## Future Enhancements
|
|
322
|
-
|
|
323
|
-
- [ ] Support for multiple screen orientations
|
|
324
|
-
- [ ] Gesture recognition and classification
|
|
325
|
-
- [ ] Performance optimization for large sessions
|
|
326
|
-
- [ ] Integration with additional React Native libraries
|
|
327
|
-
- [ ] Custom event types for React Native specific interactions
|
|
328
|
-
|
|
329
|
-
## Compatibility
|
|
330
|
-
|
|
331
|
-
This implementation is compatible with:
|
|
332
|
-
|
|
333
|
-
- rrweb 1.x and 2.x
|
|
334
|
-
- React Native 0.60+
|
|
335
|
-
- iOS and Android platforms
|
|
336
|
-
- Standard rrweb players and replay tools
|