@multiplayer-app/session-recorder-react-native 0.0.1-beta.3 → 0.0.1-beta.5

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 (121) hide show
  1. package/android/build.gradle +32 -0
  2. package/android/src/main/AndroidManifest.xml +2 -0
  3. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingModule.kt +202 -0
  4. package/android/src/main/java/com/multiplayer/sessionrecorder/ScreenMaskingPackage.kt +16 -0
  5. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderModule.kt +202 -0
  6. package/android/src/main/java/com/multiplayer/sessionrecorder/SessionRecorderPackage.kt +16 -0
  7. package/dist/components/MaskableComponent.d.ts +22 -0
  8. package/dist/components/MaskableComponent.js +1 -0
  9. package/dist/components/MaskableComponent.js.map +1 -0
  10. package/dist/components/MaskableTextInput.d.ts +14 -0
  11. package/dist/components/MaskableTextInput.js +1 -0
  12. package/dist/components/MaskableTextInput.js.map +1 -0
  13. package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
  14. package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
  15. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  16. package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
  17. package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
  18. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  19. package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
  20. package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
  21. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  22. package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
  23. package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
  24. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  25. package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
  26. package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
  27. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  28. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
  29. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
  30. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  31. package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
  32. package/dist/components/SessionRecorderWidget/icons.js +1 -0
  33. package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
  34. package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
  35. package/dist/components/SessionRecorderWidget/index.js +1 -0
  36. package/dist/components/SessionRecorderWidget/index.js.map +1 -0
  37. package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
  38. package/dist/components/SessionRecorderWidget/styles.js +1 -0
  39. package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
  40. package/dist/components/index.d.ts +2 -0
  41. package/dist/components/index.js +1 -1
  42. package/dist/components/index.js.map +1 -1
  43. package/dist/config/defaults.js +1 -1
  44. package/dist/config/defaults.js.map +1 -1
  45. package/dist/config/masking.js +1 -1
  46. package/dist/config/masking.js.map +1 -1
  47. package/dist/context/SessionRecorderContext.d.ts +5 -3
  48. package/dist/context/SessionRecorderContext.js +1 -1
  49. package/dist/context/SessionRecorderContext.js.map +1 -1
  50. package/dist/index.d.ts +0 -1
  51. package/dist/index.js +1 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/native/ScreenMasking.d.ts +21 -0
  54. package/dist/native/ScreenMasking.js +1 -0
  55. package/dist/native/ScreenMasking.js.map +1 -0
  56. package/dist/native/SessionRecorderNative.d.ts +21 -0
  57. package/dist/native/SessionRecorderNative.js +1 -0
  58. package/dist/native/SessionRecorderNative.js.map +1 -0
  59. package/dist/patch/xhr.d.ts +1 -1
  60. package/dist/patch/xhr.js +1 -1
  61. package/dist/patch/xhr.js.map +1 -1
  62. package/dist/recorder/screenRecorder.d.ts +1 -0
  63. package/dist/recorder/screenRecorder.js +1 -1
  64. package/dist/recorder/screenRecorder.js.map +1 -1
  65. package/dist/recorder/screenshotManager.d.ts +10 -0
  66. package/dist/recorder/screenshotManager.js +1 -0
  67. package/dist/recorder/screenshotManager.js.map +1 -0
  68. package/dist/services/screenMaskingService.d.ts +39 -0
  69. package/dist/services/screenMaskingService.js +1 -0
  70. package/dist/services/screenMaskingService.js.map +1 -0
  71. package/dist/services/storage.service.d.ts +18 -2
  72. package/dist/services/storage.service.js +1 -1
  73. package/dist/services/storage.service.js.map +1 -1
  74. package/dist/session-recorder.d.ts +2 -1
  75. package/dist/session-recorder.js +1 -1
  76. package/dist/session-recorder.js.map +1 -1
  77. package/dist/types/session-recorder.d.ts +6 -0
  78. package/dist/types/session-recorder.js.map +1 -1
  79. package/dist/utils/componentRegistry.d.ts +64 -0
  80. package/dist/utils/componentRegistry.js +1 -0
  81. package/dist/utils/componentRegistry.js.map +1 -0
  82. package/dist/utils/platform.d.ts +3 -0
  83. package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
  84. package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
  85. package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
  86. package/dist/utils/screenshotMasker.d.ts +96 -0
  87. package/dist/utils/screenshotMasker.js +1 -0
  88. package/dist/utils/screenshotMasker.js.map +1 -0
  89. package/dist/utils/viewHierarchyTracker.d.ts +89 -0
  90. package/dist/utils/viewHierarchyTracker.js +1 -0
  91. package/dist/utils/viewHierarchyTracker.js.map +1 -0
  92. package/ios/ScreenMasking.m +12 -0
  93. package/ios/ScreenMasking.podspec +21 -0
  94. package/ios/ScreenMasking.swift +205 -0
  95. package/ios/SessionRecorder.m +12 -0
  96. package/ios/SessionRecorder.podspec +21 -0
  97. package/ios/SessionRecorder.swift +205 -0
  98. package/package.json +12 -2
  99. package/react-native.config.js +15 -0
  100. package/src/components/SessionRecorderWidget/FinalPopover.tsx +62 -0
  101. package/src/components/SessionRecorderWidget/FloatingButton.tsx +136 -0
  102. package/src/components/SessionRecorderWidget/InitialPopover.tsx +89 -0
  103. package/src/components/SessionRecorderWidget/ModalContainer.tsx +128 -0
  104. package/src/components/SessionRecorderWidget/ModalHeader.tsx +24 -0
  105. package/src/components/SessionRecorderWidget/SessionRecorderWidget.tsx +109 -0
  106. package/src/components/SessionRecorderWidget/icons.tsx +52 -0
  107. package/src/components/SessionRecorderWidget/index.ts +3 -0
  108. package/src/components/SessionRecorderWidget/styles.ts +150 -0
  109. package/src/components/index.ts +3 -1
  110. package/src/config/defaults.ts +1 -0
  111. package/src/config/masking.ts +1 -0
  112. package/src/context/SessionRecorderContext.tsx +12 -34
  113. package/src/index.ts +1 -9
  114. package/src/native/ScreenMasking.ts +34 -0
  115. package/src/native/SessionRecorderNative.ts +34 -0
  116. package/src/patch/xhr.ts +7 -7
  117. package/src/recorder/screenRecorder.ts +31 -2
  118. package/src/services/screenMaskingService.ts +114 -0
  119. package/src/services/storage.service.ts +45 -4
  120. package/src/session-recorder.ts +9 -3
  121. package/src/types/session-recorder.ts +7 -1
@@ -0,0 +1,109 @@
1
+ import React, { useMemo, useState } from 'react'
2
+ import { Alert } from 'react-native'
3
+ import { SessionState } from '../../types'
4
+ import { SessionType } from '@multiplayer-app/session-recorder-common'
5
+ import { useSessionRecorder } from '../../context/SessionRecorderContext'
6
+ import FloatingButton from './FloatingButton'
7
+ import ModalContainer from './ModalContainer'
8
+ import InitialPopover from './InitialPopover'
9
+ import FinalPopover from './FinalPopover'
10
+
11
+ interface SessionRecorderWidgetProps {}
12
+
13
+ const SessionRecorderWidget: React.FC<SessionRecorderWidgetProps> = () => {
14
+ const { sessionState, instance } = useSessionRecorder()
15
+ const [isModalVisible, setIsModalVisible] = useState(false)
16
+ const [isSubmitting, setIsSubmitting] = useState(false)
17
+
18
+ // Get configuration from instance
19
+ const config = instance.config
20
+ const textOverrides = config.widgetTextOverrides
21
+ const showContinuousRecording = config.showContinuousRecording
22
+
23
+ const openModal = () => {
24
+ setIsModalVisible(true)
25
+ }
26
+
27
+ const closeModal = () => {
28
+ setIsModalVisible(false)
29
+ }
30
+
31
+ const onStartRecording = async (sessionType: SessionType) => {
32
+ try {
33
+ instance.start(sessionType)
34
+ closeModal()
35
+ } catch (error) {
36
+ Alert.alert('Error', 'Failed to start recording')
37
+ }
38
+ }
39
+
40
+ const onStopRecording = async (comment: string) => {
41
+ try {
42
+ setIsSubmitting(true)
43
+ await instance.stop(comment)
44
+ closeModal()
45
+ Alert.alert('Success', 'Session saved successfully')
46
+ } catch (error) {
47
+ Alert.alert('Error', 'Failed to save session')
48
+ } finally {
49
+ setIsSubmitting(false)
50
+ }
51
+ }
52
+
53
+ const onCancelSession = async () => {
54
+ try {
55
+ await instance.cancel()
56
+ closeModal()
57
+ } catch (error) {
58
+ Alert.alert('Error', 'Failed to cancel session')
59
+ }
60
+ }
61
+
62
+ const onSaveContinuousSession = async () => {
63
+ try {
64
+ setIsSubmitting(true)
65
+ await instance.save()
66
+ closeModal()
67
+ Alert.alert('Success', 'Continuous session saved successfully')
68
+ } catch (error) {
69
+ Alert.alert('Error', 'Failed to save continuous session')
70
+ } finally {
71
+ setIsSubmitting(false)
72
+ }
73
+ }
74
+
75
+ const renderModalContent = useMemo(() => {
76
+ if (sessionState === SessionState.started || sessionState === SessionState.paused) {
77
+ return (
78
+ <FinalPopover
79
+ isSubmitting={isSubmitting}
80
+ textOverrides={textOverrides}
81
+ onClose={closeModal}
82
+ onStopRecording={onStopRecording}
83
+ onCancelSession={onCancelSession}
84
+ />
85
+ )
86
+ }
87
+ return (
88
+ <InitialPopover
89
+ isSubmitting={isSubmitting}
90
+ textOverrides={textOverrides}
91
+ showContinuousRecording={showContinuousRecording}
92
+ onClose={closeModal}
93
+ onStartRecording={onStartRecording}
94
+ onSaveContinuousSession={onSaveContinuousSession}
95
+ />
96
+ )
97
+ }, [sessionState, isSubmitting, textOverrides, showContinuousRecording])
98
+
99
+ return (
100
+ <>
101
+ <FloatingButton sessionState={sessionState} onPress={openModal} />
102
+ <ModalContainer isVisible={isModalVisible} onClose={closeModal}>
103
+ {renderModalContent}
104
+ </ModalContainer>
105
+ </>
106
+ )
107
+ }
108
+
109
+ export default SessionRecorderWidget
@@ -0,0 +1,52 @@
1
+ import React from 'react'
2
+ import Svg, { Path, Circle } from 'react-native-svg'
3
+
4
+ interface IconProps {
5
+ size?: number
6
+ color?: string
7
+ }
8
+
9
+ export const RecordIcon: React.FC<IconProps> = ({ size = 19, color = 'white' }) => (
10
+ <Svg width={size} height={size} viewBox='0 0 19 19' fill='none'>
11
+ <Path
12
+ fillRule='evenodd'
13
+ clipRule='evenodd'
14
+ d='M6.68926 5.30356C6.56568 5.38721 6.39976 5.37561 6.29459 5.26937L3.58782 2.53477C3.46424 2.40992 3.47196 2.20492 3.60862 2.09483C5.2319 0.786982 7.28494 0 9.51866 0C11.7535 0 13.8066 0.787042 15.4308 2.09586C15.5674 2.20596 15.5752 2.41091 15.4516 2.53577L12.7468 5.26931C12.6416 5.37558 12.4757 5.38719 12.3521 5.30353C11.5393 4.75345 10.571 4.42281 9.52066 4.42281C8.47036 4.42281 7.50203 4.75346 6.68926 5.30356ZM16.4926 3.4303C16.6163 3.30527 16.8197 3.31303 16.9288 3.45121C18.2224 5.08933 19.0001 7.15932 19.0001 9.4116C19.0001 11.6671 18.2204 13.7392 16.9238 15.3785C16.8147 15.5165 16.6114 15.5242 16.4877 15.3992L13.7872 12.6701C13.682 12.5638 13.6708 12.3962 13.7538 12.2716C14.3006 11.451 14.6291 10.4727 14.6291 9.4116C14.6291 8.35454 14.3016 7.37925 13.756 6.56083C13.6728 6.43616 13.6841 6.26857 13.7893 6.16224L16.4926 3.4303ZM5.21676 12.6712C5.322 12.5649 5.3333 12.3974 5.2502 12.2727C4.70331 11.4522 4.374 10.4737 4.374 9.41184C4.374 8.35469 4.70232 7.37949 5.24808 6.56106C5.33123 6.43637 5.31996 6.26872 5.2147 6.16241L2.50855 3.4293C2.38482 3.30434 2.18146 3.31213 2.07236 3.45028C0.77864 5.08841 0 7.15845 0 9.41184C0 11.6684 0.78066 13.7406 2.07831 15.3799C2.18749 15.5178 2.39066 15.5255 2.51429 15.4006L5.21676 12.6712ZM12.3323 13.707C12.4559 13.6231 12.6221 13.6346 12.7273 13.741L15.4277 16.4691C15.5513 16.594 15.5435 16.7991 15.4068 16.9091C13.7837 18.215 11.7327 19 9.49998 19C7.2693 19 5.21837 18.2159 3.59619 16.9102C3.45943 16.8001 3.45169 16.595 3.57533 16.4702L6.27769 13.7409C6.38296 13.6346 6.54906 13.6231 6.67267 13.707C7.48459 14.2577 8.45278 14.5883 9.50198 14.5883C10.5522 14.5883 11.5204 14.2578 12.3323 13.707Z'
15
+ fill={color}
16
+ />
17
+ </Svg>
18
+ )
19
+
20
+ export const CapturingIcon: React.FC<IconProps> = ({ size = 24, color = 'white' }) => (
21
+ <Svg width={size} height={size} viewBox='0 0 24 24' fill='none'>
22
+ <Circle cx='12' cy='12' r='4' fill={color} />
23
+ <Circle cx='12' cy='12' r='7.5' stroke={color} strokeWidth='1' />
24
+ <Circle cx='12' cy='12' r='11.5' stroke={color} strokeWidth='1' opacity='0.2' />
25
+ </Svg>
26
+ )
27
+
28
+ export const PausedIcon: React.FC<IconProps> = ({ size = 24, color = 'white' }) => (
29
+ <Svg width={size} height={size} viewBox='0 0 24 24' fill='none'>
30
+ <Path d='M8 5V19M16 5V19' stroke={color} strokeWidth='2' strokeLinecap='round' strokeLinejoin='round' />
31
+ </Svg>
32
+ )
33
+
34
+ export const CheckmarkIcon: React.FC<IconProps> = ({ size = 24, color = 'white' }) => (
35
+ <Svg width={size} height={size} viewBox='0 0 24 24' fill='none'>
36
+ <Path
37
+ fillRule='evenodd'
38
+ clipRule='evenodd'
39
+ d='M20.0481 6.35147C20.5168 6.8201 20.5168 7.5799 20.0481 8.04853L10.4481 17.6485C9.97951 18.1172 9.21971 18.1172 8.75108 17.6485L3.95108 12.8485C3.48245 12.3799 3.48245 11.6201 3.95108 11.1515C4.41971 10.6828 5.17951 10.6828 5.64814 11.1515L9.59961 15.1029L18.3511 6.35147C18.8197 5.88284 19.5795 5.88284 20.0481 6.35147Z'
40
+ fill={color}
41
+ />
42
+ </Svg>
43
+ )
44
+
45
+ export const LogoIcon: React.FC<IconProps> = ({ size = 30, color = '#473CFB' }) => (
46
+ <Svg width={size} height={size * 0.8} viewBox='0 0 30 24' fill='none'>
47
+ <Path
48
+ d='M28.8324 8.83643L23.5495 6.85854L21.4856 6.08553L23.6001 0.4375L15 3.65769L6.39963 0.4375L8.51441 6.08585L6.45046 6.85885L1.16757 8.83674L0.625 9.03974L9.10095 12.0981C10.0891 12.4548 10.9201 13.1265 11.4758 13.9952C11.6632 14.2883 11.8194 14.6036 11.9398 14.9369L15 23.4076L18.0602 14.9369C18.1806 14.6036 18.3368 14.288 18.5242 13.9952C19.0802 13.1265 19.9112 12.4545 20.8991 12.0981L29.375 9.03974L28.8324 8.83674V8.83643ZM19.779 10.6434C18.2872 11.1816 17.1126 12.3563 16.5744 13.848L15.014 18.173L11.5182 8.83643L10.7776 6.85854L10.2456 5.43757L9.57367 3.64272L12.3068 4.66612L18.1631 6.85885L20.8233 7.85481L23.4457 8.83674L24.104 9.08308L19.779 10.6438V10.6434Z'
49
+ fill={color}
50
+ />
51
+ </Svg>
52
+ )
@@ -0,0 +1,3 @@
1
+ import SessionRecorderWidget from "./SessionRecorderWidget";
2
+
3
+ export default SessionRecorderWidget;
@@ -0,0 +1,150 @@
1
+ import { StyleSheet } from 'react-native'
2
+
3
+ export const sharedStyles = StyleSheet.create({
4
+ // Popover styles
5
+ popoverContent: {
6
+ flex: 1,
7
+ paddingHorizontal: 0
8
+ },
9
+ popoverHeader: {
10
+ flexDirection: 'column',
11
+ paddingBottom: 8,
12
+ paddingHorizontal: 16,
13
+ borderTopLeftRadius: 20,
14
+ borderTopRightRadius: 20,
15
+ backgroundColor: '#e3ecfd',
16
+ shadowColor: '#e3ecfd',
17
+ shadowOffset: { width: 0, height: 10, },
18
+ shadowOpacity: 1,
19
+ shadowRadius: 5,
20
+ elevation: 3,
21
+ },
22
+ modalHandle: {
23
+ marginTop: 8,
24
+ marginBottom: 16,
25
+ width: 40,
26
+ height: 4,
27
+ backgroundColor: '#D1D5DB',
28
+ borderRadius: 2,
29
+ alignSelf: 'center',
30
+ },
31
+ popoverHeaderContent: {
32
+ flexDirection: 'row',
33
+ justifyContent: 'space-between',
34
+ alignItems: 'flex-start',
35
+ },
36
+
37
+
38
+ cancelButton: {
39
+ paddingHorizontal: 12,
40
+ paddingVertical: 6,
41
+ borderRadius: 6,
42
+ backgroundColor: '#F3F4F6'
43
+ },
44
+ cancelButtonText: {
45
+ color: '#374151',
46
+ fontSize: 14,
47
+ fontWeight: '500'
48
+ },
49
+ popoverBody: {
50
+ flex: 1,
51
+ padding: 16,
52
+ paddingTop: 32,
53
+ },
54
+ title: {
55
+ fontSize: 18,
56
+ fontWeight: '600',
57
+ color: '#2d3748',
58
+ marginBottom: 12
59
+ },
60
+ description: {
61
+ fontSize: 14,
62
+ color: '#718096',
63
+ marginBottom: 24
64
+ },
65
+ popoverFooter: {
66
+ marginTop: 'auto',
67
+ paddingTop: 48
68
+ },
69
+ actionButton: {
70
+ paddingVertical: 16,
71
+ paddingHorizontal: 24,
72
+ borderRadius: 12,
73
+ alignItems: 'center'
74
+ },
75
+ actionButtonText: {
76
+ color: 'white',
77
+ fontSize: 16,
78
+ fontWeight: '500'
79
+ },
80
+
81
+ // Continuous recording styles
82
+ continuousRecordingSection: {
83
+ flexDirection: 'row',
84
+ justifyContent: 'space-between',
85
+ alignItems: 'center',
86
+ marginBottom: 20,
87
+ paddingVertical: 12,
88
+ paddingHorizontal: 16,
89
+ borderWidth: 1,
90
+ borderColor: '#e1e8f1',
91
+ backgroundColor: '#fff',
92
+ borderRadius: 12,
93
+ shadowColor: '#000',
94
+ shadowOffset: { width: 0, height: 2 },
95
+ shadowOpacity: 0.06,
96
+ shadowRadius: 3,
97
+ elevation: 3,
98
+ },
99
+ continuousRecordingLabel: {
100
+ fontSize: 16,
101
+ fontWeight: '500',
102
+ color: '#374151'
103
+ },
104
+ continuousOverlay: {
105
+ marginTop: 20,
106
+ padding: 16,
107
+ backgroundColor: '#FEF3C7',
108
+ borderRadius: 8,
109
+ borderWidth: 1,
110
+ borderColor: '#F59E0B'
111
+ },
112
+ continuousOverlayContent: {
113
+ marginBottom: 16
114
+ },
115
+ continuousOverlayTitle: {
116
+ fontSize: 18,
117
+ fontWeight: 'bold',
118
+ color: '#92400E',
119
+ marginBottom: 8
120
+ },
121
+ continuousOverlayDescription: {
122
+ fontSize: 14,
123
+ color: '#92400E',
124
+ lineHeight: 20
125
+ },
126
+
127
+ // Comment input styles
128
+ commentInput: {
129
+ borderWidth: 1,
130
+ borderColor: '#D1D5DB',
131
+ borderRadius: 8,
132
+ padding: 12,
133
+ fontSize: 16,
134
+ color: '#374151',
135
+ backgroundColor: '#F9FAFB',
136
+ marginBottom: 24,
137
+ minHeight: 80
138
+ },
139
+
140
+ // Button color variants
141
+ startButton: {
142
+ backgroundColor: '#473cfb'
143
+ },
144
+ stopButton: {
145
+ backgroundColor: '#473cfb'
146
+ },
147
+ saveButton: {
148
+ backgroundColor: '#473cfb'
149
+ }
150
+ })
@@ -1 +1,3 @@
1
- export * from './GestureCaptureWrapper'
1
+ export * from './GestureCaptureWrapper'
2
+ export * from './ScreenRecorderView'
3
+ export * from './SessionRecorderWidget'
@@ -23,6 +23,7 @@ export const DEFAULT_MASKING_CONFIG: MaskingConfig = {
23
23
  maskHeadersList: sensitiveHeaders,
24
24
  headersToInclude: [],
25
25
  headersToExclude: [],
26
+ inputMasking: true,
26
27
  }
27
28
 
28
29
  export const DEFAULT_WIDGET_TEXT_CONFIG: WidgetTextOverridesConfig = {
@@ -23,5 +23,6 @@ export const getMaskingConfig = (masking?: MaskingConfig): MaskingConfig => {
23
23
  isContentMaskingEnabled: isValidBoolean(masking.isContentMaskingEnabled, baseMasking.isContentMaskingEnabled ?? true),
24
24
  maskBody: isValidFunction(masking.maskBody, mask(maskBodyFieldsList)),
25
25
  maskHeaders: isValidFunction(masking.maskHeaders, mask(maskHeadersList)),
26
+ inputMasking: isValidBoolean(masking.inputMasking, baseMasking.inputMasking ?? true),
26
27
  }
27
28
  }
@@ -1,12 +1,13 @@
1
1
  import React, { createContext, useContext, PropsWithChildren, useState, useEffect, useRef } from 'react'
2
- import { Pressable, Text, View } from 'react-native'
3
2
  import { SessionRecorderOptions, SessionState } from '../types'
4
- import SessionRecorder from '../session-recorder'
5
3
  import sessionRecorder from '../session-recorder'
6
4
  import { ScreenRecorderView } from '../components/ScreenRecorderView'
5
+ import SessionRecorderWidget from '../components/SessionRecorderWidget'
7
6
 
8
7
  interface SessionRecorderContextType {
9
- instance: typeof SessionRecorder
8
+ instance: typeof sessionRecorder
9
+ isInitialized: boolean
10
+ sessionState: SessionState | null
10
11
  }
11
12
 
12
13
  const SessionRecorderContext = createContext<SessionRecorderContextType | null>(null)
@@ -16,52 +17,29 @@ export interface SessionRecorderProviderProps extends PropsWithChildren {
16
17
  }
17
18
 
18
19
  export const SessionRecorderProvider: React.FC<SessionRecorderProviderProps> = ({ children, options }) => {
19
- const [sessionState, setSessionState] = useState<SessionState | null>(null)
20
+ const [isInitialized, setIsInitialized] = useState(false)
21
+ const [sessionState, setSessionState] = useState<SessionState | null>(SessionState.stopped)
20
22
  const optionsRef = useRef<string>()
21
23
 
22
24
  useEffect(() => {
23
25
  const newOptions = JSON.stringify(options)
24
26
  if (optionsRef.current === JSON.stringify(options)) return
25
27
  optionsRef.current = newOptions
26
- SessionRecorder.init(options)
28
+ sessionRecorder.init(options)
29
+ setIsInitialized(true)
27
30
  }, [options])
28
31
 
29
32
  useEffect(() => {
30
- setSessionState(SessionRecorder.sessionState)
31
- SessionRecorder.on('state-change', (state: SessionState) => {
33
+ setSessionState(sessionRecorder.sessionState)
34
+ sessionRecorder.on('state-change', (state: SessionState) => {
32
35
  setSessionState(state)
33
36
  })
34
37
  }, [])
35
38
 
36
- const onToggleSession = () => {
37
- if (SessionRecorder.sessionState === SessionState.started) {
38
- SessionRecorder.stop()
39
- } else {
40
- SessionRecorder.start()
41
- }
42
- }
43
-
44
39
  return (
45
- <SessionRecorderContext.Provider value={{ instance: sessionRecorder }}>
40
+ <SessionRecorderContext.Provider value={{ instance: sessionRecorder, sessionState, isInitialized }}>
46
41
  <ScreenRecorderView>{children}</ScreenRecorderView>
47
- <Pressable onPress={onToggleSession}>
48
- <View
49
- style={{
50
- position: 'absolute',
51
- right: 0,
52
- bottom: 100,
53
- width: 48,
54
- height: 48,
55
- paddingTop: 16,
56
- paddingLeft: 10,
57
- backgroundColor: 'red',
58
- borderTopLeftRadius: 24,
59
- borderBottomLeftRadius: 24
60
- }}
61
- >
62
- <Text style={{ color: 'white' }}>{sessionState === SessionState.started ? 'Stop' : 'Start'}</Text>
63
- </View>
64
- </Pressable>
42
+ {isInitialized && !!sessionRecorder.config.showWidget && <SessionRecorderWidget />}
65
43
  </SessionRecorderContext.Provider>
66
44
  )
67
45
  }
package/src/index.ts CHANGED
@@ -3,15 +3,7 @@ import SessionRecorder from './session-recorder'
3
3
  export * from '@multiplayer-app/session-recorder-common'
4
4
  export * from './context/SessionRecorderContext'
5
5
 
6
- // Export platform utilities including app metadata configuration
7
- export {
8
- detectPlatform,
9
- isExpoEnvironment,
10
- configureAppMetadata,
11
- getPlatformAttributes,
12
- getConfiguredAppMetadata,
13
- } from './utils/platform'
14
-
6
+ // Export the class for type checking
15
7
  export { SessionRecorder }
16
8
  // Export the instance as default
17
9
  export default SessionRecorder
@@ -0,0 +1,34 @@
1
+ import { NativeModules } from 'react-native'
2
+
3
+ export interface MaskingOptions {
4
+ /** Quality of the captured image (0.1 to 1.0) */
5
+ quality?: number
6
+ /** Whether to mask all input fields automatically */
7
+ inputMasking?: boolean
8
+ }
9
+
10
+
11
+ export interface SessionRecorderNativeModule {
12
+ /**
13
+ * Capture the current screen and apply masking to sensitive elements
14
+ * @returns Promise that resolves to base64 encoded image
15
+ */
16
+ captureAndMask(): Promise<string>
17
+
18
+ /**
19
+ * Capture the current screen and apply masking with custom options
20
+ * @param options Custom masking options
21
+ * @returns Promise that resolves to base64 encoded image
22
+ */
23
+ captureAndMaskWithOptions(options: MaskingOptions): Promise<string>
24
+ }
25
+
26
+ // Get the native module
27
+ const { SessionRecorder } = NativeModules
28
+
29
+ // Validate that the native module is available
30
+ if (!SessionRecorder) {
31
+ console.warn('SessionRecorder native module is not available. Auto-linking may not have completed yet.')
32
+ }
33
+
34
+ export default SessionRecorder as SessionRecorderNativeModule
@@ -0,0 +1,34 @@
1
+ import { NativeModules } from 'react-native'
2
+
3
+ export interface MaskingOptions {
4
+ /** Quality of the captured image (0.1 to 1.0) */
5
+ quality?: number
6
+ /** Whether to mask all input fields automatically */
7
+ inputMasking?: boolean
8
+ }
9
+
10
+
11
+ export interface SessionRecorderNativeModule {
12
+ /**
13
+ * Capture the current screen and apply masking to sensitive elements
14
+ * @returns Promise that resolves to base64 encoded image
15
+ */
16
+ captureAndMask(): Promise<string>
17
+
18
+ /**
19
+ * Capture the current screen and apply masking with custom options
20
+ * @param options Custom masking options
21
+ * @returns Promise that resolves to base64 encoded image
22
+ */
23
+ captureAndMaskWithOptions(options: MaskingOptions): Promise<string>
24
+ }
25
+
26
+ // Get the native module
27
+ const { SessionRecorder } = NativeModules
28
+
29
+ // Validate that the native module is available
30
+ if (!SessionRecorder) {
31
+ console.warn('SessionRecorder native module is not available. Auto-linking may not have completed yet.')
32
+ }
33
+
34
+ export default SessionRecorder as SessionRecorderNativeModule
package/src/patch/xhr.ts CHANGED
@@ -17,10 +17,10 @@ export const setMaxCapturingHttpPayloadSize = (_maxCapturingHttpPayloadSize: num
17
17
  maxCapturingHttpPayloadSize = _maxCapturingHttpPayloadSize
18
18
  }
19
19
 
20
- export const setShouldRecordHttpData = (shouldRecordBodyParam: boolean, shouldRecordHeaders: boolean) => {
20
+ export const setShouldRecordHttpData = (shouldRecordBody: boolean, shouldRecordHeaders: boolean) => {
21
21
  recordRequestHeaders = shouldRecordHeaders
22
22
  recordResponseHeaders = shouldRecordHeaders
23
- shouldRecordBody = shouldRecordBodyParam
23
+ shouldRecordBody = shouldRecordBody
24
24
  }
25
25
 
26
26
  function _tryReadXHRBody({
@@ -30,6 +30,7 @@ function _tryReadXHRBody({
30
30
  body: any | null | undefined
31
31
  url: string | URL | RequestInfo
32
32
  }): string | null {
33
+
33
34
  if (isNullish(body)) {
34
35
  return null
35
36
  }
@@ -38,14 +39,13 @@ function _tryReadXHRBody({
38
39
  return body
39
40
  }
40
41
 
41
-
42
42
  if (isFormData(body)) {
43
43
  return formDataToQuery(body)
44
44
  }
45
45
 
46
46
  if (isObject(body)) {
47
47
  try {
48
- return JSON.stringify(body)
48
+ return JSON.stringify({ ...body })
49
49
  } catch {
50
50
  return '[XHR] Failed to stringify response object'
51
51
  }
@@ -104,11 +104,11 @@ function _tryReadXHRBody({
104
104
  return
105
105
  }
106
106
 
107
-
108
107
  // @ts-ignore
109
108
  const responseHeaders: Record<string, string> = {}
110
- const rawHeaders = xhr.getAllResponseHeaders()
111
- const headers = rawHeaders.trim().split(/[\r\n]+/)
109
+ const rawHeaders = xhr.getAllResponseHeaders() || ''
110
+ const headers = rawHeaders.trim().split(/[\r\n]+/).filter(Boolean)
111
+
112
112
  headers.forEach((line) => {
113
113
  const parts = line.split(': ')
114
114
  const header = parts.shift()
@@ -10,6 +10,7 @@ import {
10
10
  generateScreenHash,
11
11
  logger,
12
12
  } from '../utils'
13
+ import { screenMaskingService, ScreenMaskingConfig } from '../services/screenMaskingService'
13
14
 
14
15
  export class ScreenRecorder implements EventRecorder {
15
16
  private config?: RecorderConfig
@@ -18,7 +19,7 @@ export class ScreenRecorder implements EventRecorder {
18
19
  private captureInterval?: NodeJS.Timeout
19
20
  private captureCount: number = 0
20
21
  private maxCaptures: number = 100 // Limit captures to prevent memory issues
21
- private captureQuality: number = 0.3
22
+ private captureQuality: number = 0.1
22
23
  private captureFormat: 'png' | 'jpg' = 'jpg'
23
24
  private screenDimensions: { width: number; height: number } | null = null
24
25
  private currentScreen: string | null = null
@@ -30,11 +31,24 @@ export class ScreenRecorder implements EventRecorder {
30
31
  private enableChangeDetection: boolean = true
31
32
  private hashSampleSize: number = 100
32
33
  private currentImageNodeId: number | null = null
34
+ private maskingConfig?: ScreenMaskingConfig
33
35
 
34
36
  init(config: RecorderConfig, eventRecorder?: EventRecorder): void {
35
37
  this.config = config
36
38
  this.eventRecorder = eventRecorder
37
39
  this._getScreenDimensions()
40
+
41
+ // Initialize masking configuration
42
+ this.maskingConfig = {
43
+ enabled: true,
44
+ inputMasking: this.config?.masking?.inputMasking ?? true,
45
+ defaultOptions: {
46
+ quality: this.captureQuality,
47
+ },
48
+ }
49
+
50
+ // Update the masking service configuration
51
+ screenMaskingService.updateConfig(this.maskingConfig)
38
52
  }
39
53
 
40
54
  start(): void {
@@ -135,6 +149,21 @@ export class ScreenRecorder implements EventRecorder {
135
149
 
136
150
  private async _captureScreenBase64(): Promise<string | null> {
137
151
  try {
152
+ // Try native masking first if available
153
+ if (screenMaskingService.isScreenMaskingAvailable()) {
154
+ logger.info('ScreenRecorder', 'Using native masking for screen capture')
155
+ const maskedImage = await screenMaskingService.captureMaskedScreen({
156
+ quality: this.captureQuality,
157
+ })
158
+
159
+ if (maskedImage) {
160
+ return maskedImage
161
+ }
162
+
163
+ logger.warn('ScreenRecorder', 'Native masking failed, falling back to view-shot')
164
+ }
165
+
166
+ // Fallback to react-native-view-shot
138
167
  if (!this.viewShotRef) {
139
168
  logger.warn('ScreenRecorder', 'ViewShot ref not available for screen capture')
140
169
  return null
@@ -451,7 +480,7 @@ export class ScreenRecorder implements EventRecorder {
451
480
  // Get current configuration
452
481
  getConfiguration(): Record<string, any> {
453
482
  return {
454
- captureInterval: this.captureInterval ? 5000 : 0, // Default 5 seconds
483
+ captureInterval: this.captureInterval ? 2000 : 0, // Default 5 seconds
455
484
  captureQuality: this.captureQuality,
456
485
  captureFormat: this.captureFormat,
457
486
  maxCaptures: this.maxCaptures,