@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.
Files changed (162) hide show
  1. package/SessionRecorderNative.podspec +29 -0
  2. package/android/build.gradle +32 -0
  3. package/copy-react-native-dist.sh +4 -10
  4. package/dist/components/MaskableComponent.d.ts +22 -0
  5. package/dist/components/MaskableComponent.js +1 -0
  6. package/dist/components/MaskableComponent.js.map +1 -0
  7. package/dist/components/MaskableTextInput.d.ts +14 -0
  8. package/dist/components/MaskableTextInput.js +1 -0
  9. package/dist/components/MaskableTextInput.js.map +1 -0
  10. package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
  11. package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
  12. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  13. package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
  14. package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
  15. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  16. package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
  17. package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
  18. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  19. package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
  20. package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
  21. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  22. package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
  23. package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
  24. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  25. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
  26. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
  27. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  28. package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
  29. package/dist/components/SessionRecorderWidget/icons.js +1 -0
  30. package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
  31. package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
  32. package/dist/components/SessionRecorderWidget/index.js +1 -0
  33. package/dist/components/SessionRecorderWidget/index.js.map +1 -0
  34. package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
  35. package/dist/components/SessionRecorderWidget/styles.js +1 -0
  36. package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
  37. package/dist/components/index.d.ts +2 -0
  38. package/dist/components/index.js +1 -1
  39. package/dist/components/index.js.map +1 -1
  40. package/dist/config/defaults.js +1 -1
  41. package/dist/config/defaults.js.map +1 -1
  42. package/dist/config/masking.js +1 -1
  43. package/dist/config/masking.js.map +1 -1
  44. package/dist/context/SessionRecorderContext.d.ts +5 -3
  45. package/dist/context/SessionRecorderContext.js +1 -1
  46. package/dist/context/SessionRecorderContext.js.map +1 -1
  47. package/dist/index.d.ts +0 -1
  48. package/dist/index.js +1 -1
  49. package/dist/index.js.map +1 -1
  50. package/dist/native/ScreenMasking.d.ts +21 -0
  51. package/dist/native/ScreenMasking.js +1 -0
  52. package/dist/native/ScreenMasking.js.map +1 -0
  53. package/dist/native/SessionRecorderNative.d.ts +21 -0
  54. package/dist/native/SessionRecorderNative.js +1 -0
  55. package/dist/native/SessionRecorderNative.js.map +1 -0
  56. package/dist/patch/xhr.js +1 -1
  57. package/dist/patch/xhr.js.map +1 -1
  58. package/dist/recorder/screenRecorder.d.ts +1 -0
  59. package/dist/recorder/screenRecorder.js +1 -1
  60. package/dist/recorder/screenRecorder.js.map +1 -1
  61. package/dist/recorder/screenshotManager.d.ts +10 -0
  62. package/dist/recorder/screenshotManager.js +1 -0
  63. package/dist/recorder/screenshotManager.js.map +1 -0
  64. package/dist/services/screenMaskingService.d.ts +39 -0
  65. package/dist/services/screenMaskingService.js +1 -0
  66. package/dist/services/screenMaskingService.js.map +1 -0
  67. package/dist/services/storage.service.d.ts +18 -2
  68. package/dist/services/storage.service.js +1 -1
  69. package/dist/services/storage.service.js.map +1 -1
  70. package/dist/session-recorder.d.ts +2 -1
  71. package/dist/session-recorder.js +1 -1
  72. package/dist/session-recorder.js.map +1 -1
  73. package/dist/types/session-recorder.d.ts +6 -0
  74. package/dist/types/session-recorder.js.map +1 -1
  75. package/dist/utils/componentRegistry.d.ts +64 -0
  76. package/dist/utils/componentRegistry.js +1 -0
  77. package/dist/utils/componentRegistry.js.map +1 -0
  78. package/dist/utils/nativeModuleTest.d.ts +8 -0
  79. package/dist/utils/nativeModuleTest.js +1 -0
  80. package/dist/utils/nativeModuleTest.js.map +1 -0
  81. package/dist/utils/platform.d.ts +3 -0
  82. package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
  83. package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
  84. package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
  85. package/dist/utils/screenshotMasker.d.ts +96 -0
  86. package/dist/utils/screenshotMasker.js +1 -0
  87. package/dist/utils/screenshotMasker.js.map +1 -0
  88. package/dist/utils/viewHierarchyTracker.d.ts +89 -0
  89. package/dist/utils/viewHierarchyTracker.js +1 -0
  90. package/dist/utils/viewHierarchyTracker.js.map +1 -0
  91. package/ios/SessionRecorderNative.m +17 -0
  92. package/ios/SessionRecorderNative.podspec +26 -0
  93. package/ios/SessionRecorderNative.swift +205 -0
  94. package/package.json +22 -7
  95. package/react-native.config.js +15 -0
  96. package/RRWEB_INTEGRATION.md +0 -336
  97. package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
  98. package/babel.config.js +0 -13
  99. package/dist/components/GestureCaptureWrapper.d.ts +0 -6
  100. package/dist/components/GestureCaptureWrapper.js +0 -1
  101. package/dist/components/GestureCaptureWrapper.js.map +0 -1
  102. package/dist/expo.d.ts +0 -7
  103. package/dist/expo.js +0 -1
  104. package/dist/expo.js.map +0 -1
  105. package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
  106. package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
  107. package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
  108. package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
  109. package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
  110. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
  111. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
  112. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
  113. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
  114. package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
  115. package/dist/recorder/gestureHandlerRecorder.js +0 -1
  116. package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
  117. package/dist/types/rrweb.d.ts +0 -118
  118. package/dist/types/rrweb.js +0 -1
  119. package/dist/types/rrweb.js.map +0 -1
  120. package/scripts/generate-app-metadata.js +0 -173
  121. package/src/components/GestureCaptureWrapper/GestureCaptureWrapper.tsx +0 -86
  122. package/src/components/GestureCaptureWrapper/index.ts +0 -1
  123. package/src/components/ScreenRecorderView/ScreenRecorderView.tsx +0 -72
  124. package/src/components/ScreenRecorderView/index.ts +0 -1
  125. package/src/components/index.ts +0 -1
  126. package/src/config/constants.ts +0 -60
  127. package/src/config/defaults.ts +0 -82
  128. package/src/config/index.ts +0 -6
  129. package/src/config/masking.ts +0 -27
  130. package/src/config/session-recorder.ts +0 -55
  131. package/src/config/validators.ts +0 -31
  132. package/src/context/SessionRecorderContext.tsx +0 -75
  133. package/src/expo.ts +0 -11
  134. package/src/index.ts +0 -17
  135. package/src/otel/helpers.ts +0 -275
  136. package/src/otel/index.ts +0 -138
  137. package/src/otel/instrumentations/index.ts +0 -115
  138. package/src/patch/index.ts +0 -1
  139. package/src/patch/xhr.ts +0 -142
  140. package/src/recorder/eventExporter.ts +0 -141
  141. package/src/recorder/gestureRecorder.ts +0 -498
  142. package/src/recorder/index.ts +0 -179
  143. package/src/recorder/navigationTracker.ts +0 -449
  144. package/src/recorder/screenRecorder.ts +0 -498
  145. package/src/services/api.service.ts +0 -203
  146. package/src/services/storage.service.ts +0 -158
  147. package/src/session-recorder.ts +0 -600
  148. package/src/types/expo.d.ts +0 -23
  149. package/src/types/index.ts +0 -28
  150. package/src/types/session-recorder.ts +0 -423
  151. package/src/types/session.ts +0 -65
  152. package/src/utils/app-metadata.ts +0 -31
  153. package/src/utils/index.ts +0 -8
  154. package/src/utils/logger.ts +0 -225
  155. package/src/utils/platform.ts +0 -384
  156. package/src/utils/request-utils.ts +0 -61
  157. package/src/utils/rrweb-events.ts +0 -309
  158. package/src/utils/session.ts +0 -18
  159. package/src/utils/time.ts +0 -17
  160. package/src/utils/type-utils.ts +0 -75
  161. package/src/version.ts +0 -1
  162. 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.1",
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
- "./expo": {
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
+ }
@@ -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