@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,114 @@
1
+ import SessionRecorderNative, { MaskingOptions } from '../native/SessionRecorderNative'
2
+ import { logger } from '../utils'
3
+
4
+ export interface ScreenMaskingConfig {
5
+ /** Whether screen masking is enabled */
6
+ enabled: boolean
7
+ /** Whether to mask all input fields automatically */
8
+ inputMasking: boolean
9
+ /** Default masking options */
10
+ defaultOptions?: MaskingOptions
11
+ }
12
+
13
+ export class ScreenMaskingService {
14
+ private config: ScreenMaskingConfig
15
+ private isAvailable: boolean = false
16
+
17
+ constructor(config: ScreenMaskingConfig = { enabled: true, inputMasking: true }) {
18
+ this.config = config
19
+ this.checkAvailability()
20
+ }
21
+
22
+ /**
23
+ * Check if the native masking module is available
24
+ */
25
+ private checkAvailability(): void {
26
+ try {
27
+ // Try to access the native module to check if it's available
28
+ if (SessionRecorderNative && typeof SessionRecorderNative.captureAndMask === 'function') {
29
+ this.isAvailable = true
30
+ logger.info('ScreenMaskingService', 'Screen masking native module is available')
31
+ } else {
32
+ this.isAvailable = false
33
+ logger.warn('ScreenMaskingService', 'Screen masking native module is not available - auto-linking may still be in progress')
34
+
35
+ // Retry after a delay for auto-linking
36
+ setTimeout(() => {
37
+ this.checkAvailability()
38
+ }, 2000)
39
+ }
40
+ } catch (error) {
41
+ this.isAvailable = false
42
+ logger.error('ScreenMaskingService', 'Error checking screen masking availability:', error)
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Capture screen with masking applied
48
+ */
49
+ async captureMaskedScreen(options?: MaskingOptions): Promise<string | null> {
50
+ if (!this.isAvailable || !this.config.enabled) {
51
+ logger.warn('ScreenMaskingService', 'Screen masking is not available or disabled')
52
+ return null
53
+ }
54
+
55
+ try {
56
+ const maskingOptions: MaskingOptions = {
57
+ ...this.config.defaultOptions,
58
+ ...options,
59
+ inputMasking: this.config.inputMasking,
60
+ }
61
+
62
+ const maskedImageBase64 = await SessionRecorderNative.captureAndMaskWithOptions(maskingOptions)
63
+ logger.info('ScreenMaskingService', 'Successfully captured masked screen')
64
+ return maskedImageBase64
65
+ } catch (error) {
66
+ logger.error('ScreenMaskingService', 'Failed to capture masked screen:', error)
67
+ return null
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Capture screen with basic masking (no custom options)
73
+ */
74
+ async captureMaskedScreenBasic(): Promise<string | null> {
75
+ if (!this.isAvailable || !this.config.enabled) {
76
+ logger.warn('ScreenMaskingService', 'Screen masking is not available or disabled')
77
+ return null
78
+ }
79
+
80
+ try {
81
+ const maskedImageBase64 = await SessionRecorderNative.captureAndMask()
82
+ logger.info('ScreenMaskingService', 'Successfully captured masked screen (basic)')
83
+ return maskedImageBase64
84
+ } catch (error) {
85
+ logger.error('ScreenMaskingService', 'Failed to capture masked screen (basic):', error)
86
+ return null
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Update the masking configuration
92
+ */
93
+ updateConfig(config: Partial<ScreenMaskingConfig>): void {
94
+ this.config = { ...this.config, ...config }
95
+ logger.info('ScreenMaskingService', 'Screen masking configuration updated')
96
+ }
97
+
98
+ /**
99
+ * Check if screen masking is available
100
+ */
101
+ isScreenMaskingAvailable(): boolean {
102
+ return this.isAvailable && this.config.enabled
103
+ }
104
+
105
+ /**
106
+ * Get the current configuration
107
+ */
108
+ getConfig(): ScreenMaskingConfig {
109
+ return { ...this.config }
110
+ }
111
+ }
112
+
113
+ // Create a singleton instance
114
+ export const screenMaskingService = new ScreenMaskingService()
@@ -1,12 +1,14 @@
1
1
  import AsyncStorage from '@react-native-async-storage/async-storage'
2
2
  import { SessionType } from '@multiplayer-app/session-recorder-common'
3
3
  import { ISession, SessionState } from '../types'
4
+ import { logger } from '../utils'
4
5
 
5
6
  interface CacheData {
6
7
  sessionId: string | null
7
8
  sessionType: SessionType | null
8
9
  sessionState: SessionState | null
9
10
  sessionObject: ISession | null
11
+ floatingButtonPosition: { x: number; y: number } | null
10
12
  }
11
13
 
12
14
  export class StorageService {
@@ -14,29 +16,42 @@ export class StorageService {
14
16
  private static readonly SESSION_TYPE_KEY = 'session_type'
15
17
  private static readonly SESSION_STATE_KEY = 'session_state'
16
18
  private static readonly SESSION_OBJECT_KEY = 'session_object'
19
+ private static readonly FLOATING_BUTTON_POSITION_KEY = 'floating_button_position'
17
20
 
18
21
  private static cache: CacheData = {
19
22
  sessionId: null,
20
23
  sessionType: null,
21
24
  sessionState: null,
22
25
  sessionObject: null,
26
+ floatingButtonPosition: null,
23
27
  }
24
28
 
25
29
  private static cacheInitialized = false
30
+ private static instance: StorageService | null = null
31
+ private static positionSaveTimeout: NodeJS.Timeout | null = null
26
32
 
27
- constructor() {
28
- StorageService.initialize()
33
+ private constructor() {
34
+ // Private constructor for singleton
35
+ }
36
+
37
+ static getInstance(): StorageService {
38
+ if (!StorageService.instance) {
39
+ StorageService.instance = new StorageService()
40
+ StorageService.initialize()
41
+ }
42
+ return StorageService.instance
29
43
  }
30
44
 
31
45
  private static async initializeCache(): Promise<void> {
32
46
  if (StorageService.cacheInitialized) return
33
47
 
34
48
  try {
35
- const [sessionId, sessionType, sessionState, sessionObject] = await Promise.all([
49
+ const [sessionId, sessionType, sessionState, sessionObject, floatingButtonPosition] = await Promise.all([
36
50
  AsyncStorage.getItem(StorageService.SESSION_ID_KEY),
37
51
  AsyncStorage.getItem(StorageService.SESSION_TYPE_KEY),
38
52
  AsyncStorage.getItem(StorageService.SESSION_STATE_KEY),
39
53
  AsyncStorage.getItem(StorageService.SESSION_OBJECT_KEY),
54
+ AsyncStorage.getItem(StorageService.FLOATING_BUTTON_POSITION_KEY),
40
55
  ])
41
56
 
42
57
  StorageService.cache = {
@@ -44,6 +59,7 @@ export class StorageService {
44
59
  sessionType: sessionType as SessionType | null,
45
60
  sessionState: sessionState as SessionState | null,
46
61
  sessionObject: sessionObject ? JSON.parse(sessionObject) : null,
62
+ floatingButtonPosition: floatingButtonPosition ? JSON.parse(floatingButtonPosition) : null,
47
63
  }
48
64
  StorageService.cacheInitialized = true
49
65
  } catch (error) {
@@ -121,6 +137,7 @@ export class StorageService {
121
137
  try {
122
138
  // Clear cache immediately
123
139
  StorageService.cache = {
140
+ ...StorageService.cache,
124
141
  sessionId: null,
125
142
  sessionType: null,
126
143
  sessionState: null,
@@ -142,7 +159,7 @@ export class StorageService {
142
159
  }
143
160
  }
144
161
 
145
- getAllSessionData(): CacheData {
162
+ getAllSessionData(): Omit<CacheData, 'floatingButtonPosition'> {
146
163
  return {
147
164
  sessionId: StorageService.cache.sessionId,
148
165
  sessionType: StorageService.cache.sessionType,
@@ -151,6 +168,30 @@ export class StorageService {
151
168
  }
152
169
  }
153
170
 
171
+ saveFloatingButtonPosition(position: { x: number; y: number }): void {
172
+ try {
173
+ StorageService.cache.floatingButtonPosition = position
174
+
175
+ // Debounce AsyncStorage writes to avoid excessive I/O
176
+ if (StorageService.positionSaveTimeout) {
177
+ clearTimeout(StorageService.positionSaveTimeout)
178
+ }
179
+
180
+ StorageService.positionSaveTimeout = setTimeout(() => {
181
+ AsyncStorage.setItem(StorageService.FLOATING_BUTTON_POSITION_KEY, JSON.stringify(position)).catch(error => {
182
+ logger.error('StorageService', 'Failed to persist floating button position', error)
183
+ })
184
+ }, 100) // 100ms debounce
185
+ } catch (error) {
186
+ logger.error('StorageService', 'Failed to save floating button position', error)
187
+ throw error
188
+ }
189
+ }
190
+
191
+ getFloatingButtonPosition(): { x: number; y: number } | null {
192
+ return StorageService.cache.floatingButtonPosition
193
+ }
194
+
154
195
  // Initialize cache on first use - call this method when the service is first used
155
196
  static async initialize(): Promise<void> {
156
197
  await StorageService.initializeCache()
@@ -16,7 +16,7 @@ import {
16
16
  } from './types'
17
17
  import { getFormattedDate, isSessionActive, getNavigatorInfo } from './utils'
18
18
  import { setMaxCapturingHttpPayloadSize, setShouldRecordHttpData } from './patch/xhr'
19
- import { DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig } from './config'
19
+ import { BASE_CONFIG, DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE, getSessionRecorderConfig } from './config'
20
20
 
21
21
  import { StorageService } from './services/storage.service'
22
22
  import { ApiService, StartSessionRequest, StopSessionRequest } from './services/api.service'
@@ -30,11 +30,11 @@ type SessionRecorderEvents =
30
30
 
31
31
  class SessionRecorder extends Observable<SessionRecorderEvents> implements ISessionRecorder, EventRecorder {
32
32
  private _isInitialized = false
33
- private _configs: SessionRecorderConfigs | null = null
33
+ private _configs: SessionRecorderConfigs
34
34
  private _apiService = new ApiService()
35
35
  private _tracer = new TracerReactNativeSDK()
36
36
  private _recorder = new RecorderReactNativeSDK()
37
- private _storageService = new StorageService()
37
+ private _storageService = StorageService.getInstance()
38
38
  private _startRequestController: AbortController | null = null
39
39
 
40
40
  // Session ID and state are stored in AsyncStorage
@@ -112,11 +112,16 @@ class SessionRecorder extends Observable<SessionRecorderEvents> implements ISess
112
112
  return null
113
113
  }
114
114
 
115
+ public get config(): SessionRecorderConfigs {
116
+ return this._configs
117
+ }
118
+
115
119
  /**
116
120
  * Initialize debugger with default or custom configurations
117
121
  */
118
122
  constructor() {
119
123
  super()
124
+ this._configs = BASE_CONFIG
120
125
  // Initialize with stored session data if available
121
126
  StorageService.initialize()
122
127
  }
@@ -154,6 +159,7 @@ class SessionRecorder extends Observable<SessionRecorderEvents> implements ISess
154
159
  this._isInitialized = true
155
160
  this._checkOperation('init')
156
161
  await this._loadStoredSessionData()
162
+
157
163
  setMaxCapturingHttpPayloadSize(this._configs.maxCapturingHttpPayloadSize || DEFAULT_MAX_HTTP_CAPTURING_PAYLOAD_SIZE)
158
164
  setShouldRecordHttpData(!this._configs.captureBody, this._configs.captureHeaders)
159
165
 
@@ -150,7 +150,6 @@ export interface MaskingConfig {
150
150
  /** Custom function for masking headers in traces */
151
151
  maskHeaders?: (headers: any, span: any) => any;
152
152
 
153
-
154
153
  /** List of body fields to mask in traces */
155
154
  maskBodyFieldsList?: string[]
156
155
  /** List of headers to mask in traces */
@@ -160,6 +159,11 @@ export interface MaskingConfig {
160
159
  headersToInclude?: string[]
161
160
  /** List of headers to exclude from traces */
162
161
  headersToExclude?: string[]
162
+
163
+ /** Whether to mask all input fields during screen recording
164
+ * @default true
165
+ */
166
+ inputMasking?: boolean
163
167
  }
164
168
 
165
169
  /**
@@ -222,6 +226,8 @@ export interface RecorderConfig extends BaseConfig {
222
226
  recordNavigation?: boolean
223
227
  /** Whether to record screen */
224
228
  recordScreen?: boolean
229
+ /** Configuration for masking sensitive data in session recordings */
230
+ masking?: MaskingConfig
225
231
  }
226
232
 
227
233
  /**