@multiplayer-app/session-recorder-react-native 0.0.1-alpha.9 → 0.0.1-beta.10

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 (197) hide show
  1. package/SessionRecorderNative.podspec +29 -0
  2. package/android/build.gradle +32 -0
  3. package/app.plugin.js +42 -0
  4. package/copy-react-native-dist.sh +4 -10
  5. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js +1 -0
  6. package/dist/components/GestureCaptureWrapper/GestureCaptureWrapper.js.map +1 -0
  7. package/dist/components/GestureCaptureWrapper/index.d.ts +1 -0
  8. package/dist/components/GestureCaptureWrapper/index.js +1 -0
  9. package/dist/components/GestureCaptureWrapper/index.js.map +1 -0
  10. package/dist/components/MaskableComponent.d.ts +22 -0
  11. package/dist/components/MaskableComponent.js +1 -0
  12. package/dist/components/MaskableComponent.js.map +1 -0
  13. package/dist/components/MaskableTextInput.d.ts +14 -0
  14. package/dist/components/MaskableTextInput.js +1 -0
  15. package/dist/components/MaskableTextInput.js.map +1 -0
  16. package/dist/components/ScreenRecorderView/ScreenRecorderView.d.ts +5 -0
  17. package/dist/components/ScreenRecorderView/ScreenRecorderView.js +1 -0
  18. package/dist/components/ScreenRecorderView/ScreenRecorderView.js.map +1 -0
  19. package/dist/components/ScreenRecorderView/index.d.ts +1 -0
  20. package/dist/components/ScreenRecorderView/index.js +1 -0
  21. package/dist/components/ScreenRecorderView/index.js.map +1 -0
  22. package/dist/components/SessionRecorderWidget/FinalPopover.d.ts +11 -0
  23. package/dist/components/SessionRecorderWidget/FinalPopover.js +1 -0
  24. package/dist/components/SessionRecorderWidget/FinalPopover.js.map +1 -0
  25. package/dist/components/SessionRecorderWidget/FloatingButton.d.ts +8 -0
  26. package/dist/components/SessionRecorderWidget/FloatingButton.js +1 -0
  27. package/dist/components/SessionRecorderWidget/FloatingButton.js.map +1 -0
  28. package/dist/components/SessionRecorderWidget/InitialPopover.d.ts +13 -0
  29. package/dist/components/SessionRecorderWidget/InitialPopover.js +1 -0
  30. package/dist/components/SessionRecorderWidget/InitialPopover.js.map +1 -0
  31. package/dist/components/SessionRecorderWidget/ModalContainer.d.ts +8 -0
  32. package/dist/components/SessionRecorderWidget/ModalContainer.js +1 -0
  33. package/dist/components/SessionRecorderWidget/ModalContainer.js.map +1 -0
  34. package/dist/components/SessionRecorderWidget/ModalHeader.d.ts +6 -0
  35. package/dist/components/SessionRecorderWidget/ModalHeader.js +1 -0
  36. package/dist/components/SessionRecorderWidget/ModalHeader.js.map +1 -0
  37. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.d.ts +5 -0
  38. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js +1 -0
  39. package/dist/components/SessionRecorderWidget/SessionRecorderWidget.js.map +1 -0
  40. package/dist/components/SessionRecorderWidget/icons.d.ts +11 -0
  41. package/dist/components/SessionRecorderWidget/icons.js +1 -0
  42. package/dist/components/SessionRecorderWidget/icons.js.map +1 -0
  43. package/dist/components/SessionRecorderWidget/index.d.ts +2 -0
  44. package/dist/components/SessionRecorderWidget/index.js +1 -0
  45. package/dist/components/SessionRecorderWidget/index.js.map +1 -0
  46. package/dist/components/SessionRecorderWidget/styles.d.ts +145 -0
  47. package/dist/components/SessionRecorderWidget/styles.js +1 -0
  48. package/dist/components/SessionRecorderWidget/styles.js.map +1 -0
  49. package/dist/components/index.d.ts +3 -0
  50. package/dist/components/index.js +1 -0
  51. package/dist/components/index.js.map +1 -0
  52. package/dist/config/defaults.js +1 -1
  53. package/dist/config/defaults.js.map +1 -1
  54. package/dist/config/masking.js +1 -1
  55. package/dist/config/masking.js.map +1 -1
  56. package/dist/context/SessionRecorderContext.d.ts +5 -3
  57. package/dist/context/SessionRecorderContext.js +1 -1
  58. package/dist/context/SessionRecorderContext.js.map +1 -1
  59. package/dist/index.js.map +1 -1
  60. package/dist/native/ScreenMasking.d.ts +21 -0
  61. package/dist/native/ScreenMasking.js +1 -0
  62. package/dist/native/ScreenMasking.js.map +1 -0
  63. package/dist/native/SessionRecorderNative.d.ts +21 -0
  64. package/dist/native/SessionRecorderNative.js +1 -0
  65. package/dist/native/SessionRecorderNative.js.map +1 -0
  66. package/dist/otel/index.d.ts +0 -2
  67. package/dist/otel/index.js.map +1 -1
  68. package/dist/otel/instrumentations/index.d.ts +0 -3
  69. package/dist/otel/instrumentations/index.js +1 -1
  70. package/dist/otel/instrumentations/index.js.map +1 -1
  71. package/dist/patch/xhr.js +1 -1
  72. package/dist/patch/xhr.js.map +1 -1
  73. package/dist/recorder/gestureRecorder.d.ts +0 -9
  74. package/dist/recorder/gestureRecorder.js +1 -1
  75. package/dist/recorder/gestureRecorder.js.map +1 -1
  76. package/dist/recorder/index.d.ts +4 -3
  77. package/dist/recorder/index.js.map +1 -1
  78. package/dist/recorder/screenRecorder.d.ts +2 -6
  79. package/dist/recorder/screenRecorder.js +1 -1
  80. package/dist/recorder/screenRecorder.js.map +1 -1
  81. package/dist/recorder/screenshotManager.d.ts +10 -0
  82. package/dist/recorder/screenshotManager.js +1 -0
  83. package/dist/recorder/screenshotManager.js.map +1 -0
  84. package/dist/services/screenMaskingService.d.ts +39 -0
  85. package/dist/services/screenMaskingService.js +1 -0
  86. package/dist/services/screenMaskingService.js.map +1 -0
  87. package/dist/services/storage.service.d.ts +18 -2
  88. package/dist/services/storage.service.js +1 -1
  89. package/dist/services/storage.service.js.map +1 -1
  90. package/dist/session-recorder.d.ts +4 -2
  91. package/dist/session-recorder.js +1 -1
  92. package/dist/session-recorder.js.map +1 -1
  93. package/dist/types/index.d.ts +2 -16
  94. package/dist/types/index.js +1 -1
  95. package/dist/types/index.js.map +1 -1
  96. package/dist/types/session-recorder.d.ts +6 -0
  97. package/dist/types/session-recorder.js.map +1 -1
  98. package/dist/utils/app-metadata.d.ts +16 -0
  99. package/dist/utils/app-metadata.js +1 -0
  100. package/dist/utils/app-metadata.js.map +1 -0
  101. package/dist/utils/componentRegistry.d.ts +64 -0
  102. package/dist/utils/componentRegistry.js +1 -0
  103. package/dist/utils/componentRegistry.js.map +1 -0
  104. package/dist/utils/nativeModuleTest.d.ts +8 -0
  105. package/dist/utils/nativeModuleTest.js +1 -0
  106. package/dist/utils/nativeModuleTest.js.map +1 -0
  107. package/dist/utils/platform.d.ts +35 -0
  108. package/dist/utils/platform.js +1 -1
  109. package/dist/utils/platform.js.map +1 -1
  110. package/dist/utils/reactNativeHierarchyExtractor.d.ts +38 -0
  111. package/dist/utils/reactNativeHierarchyExtractor.js +1 -0
  112. package/dist/utils/reactNativeHierarchyExtractor.js.map +1 -0
  113. package/dist/utils/rrweb-events.d.ts +1 -1
  114. package/dist/utils/rrweb-events.js +1 -1
  115. package/dist/utils/rrweb-events.js.map +1 -1
  116. package/dist/utils/screenshotMasker.d.ts +96 -0
  117. package/dist/utils/screenshotMasker.js +1 -0
  118. package/dist/utils/screenshotMasker.js.map +1 -0
  119. package/dist/utils/viewHierarchyTracker.d.ts +89 -0
  120. package/dist/utils/viewHierarchyTracker.js +1 -0
  121. package/dist/utils/viewHierarchyTracker.js.map +1 -0
  122. package/dist/version.d.ts +1 -1
  123. package/dist/version.js +1 -1
  124. package/dist/version.js.map +1 -1
  125. package/docs/NATIVE_MODULE_SETUP.md +177 -0
  126. package/ios/SessionRecorderNative.m +17 -0
  127. package/ios/SessionRecorderNative.podspec +26 -0
  128. package/ios/SessionRecorderNative.swift +205 -0
  129. package/package.json +25 -14
  130. package/plugin/package.json +20 -0
  131. package/plugin/src/index.js +42 -0
  132. package/react-native.config.js +15 -0
  133. package/RRWEB_INTEGRATION.md +0 -336
  134. package/VIEWSHOT_INTEGRATION_TEST.md +0 -123
  135. package/babel.config.js +0 -13
  136. package/dist/components/GestureCaptureWrapper.js +0 -1
  137. package/dist/components/GestureCaptureWrapper.js.map +0 -1
  138. package/dist/expo.d.ts +0 -7
  139. package/dist/expo.js +0 -1
  140. package/dist/expo.js.map +0 -1
  141. package/dist/otel/instrumentations/gestureInstrumentation.d.ts +0 -15
  142. package/dist/otel/instrumentations/gestureInstrumentation.js +0 -1
  143. package/dist/otel/instrumentations/gestureInstrumentation.js.map +0 -1
  144. package/dist/otel/instrumentations/reactNativeInstrumentation.d.ts +0 -8
  145. package/dist/otel/instrumentations/reactNativeInstrumentation.js +0 -1
  146. package/dist/otel/instrumentations/reactNativeInstrumentation.js.map +0 -1
  147. package/dist/otel/instrumentations/reactNavigationInstrumentation.d.ts +0 -13
  148. package/dist/otel/instrumentations/reactNavigationInstrumentation.js +0 -1
  149. package/dist/otel/instrumentations/reactNavigationInstrumentation.js.map +0 -1
  150. package/dist/recorder/gestureHandlerRecorder.d.ts +0 -19
  151. package/dist/recorder/gestureHandlerRecorder.js +0 -1
  152. package/dist/recorder/gestureHandlerRecorder.js.map +0 -1
  153. package/dist/types/rrweb.d.ts +0 -118
  154. package/dist/types/rrweb.js +0 -1
  155. package/dist/types/rrweb.js.map +0 -1
  156. package/src/components/GestureCaptureWrapper.tsx +0 -110
  157. package/src/config/constants.ts +0 -60
  158. package/src/config/defaults.ts +0 -82
  159. package/src/config/index.ts +0 -6
  160. package/src/config/masking.ts +0 -27
  161. package/src/config/session-recorder.ts +0 -55
  162. package/src/config/validators.ts +0 -31
  163. package/src/context/SessionRecorderContext.tsx +0 -143
  164. package/src/expo.ts +0 -11
  165. package/src/index.ts +0 -9
  166. package/src/otel/helpers.ts +0 -275
  167. package/src/otel/index.ts +0 -149
  168. package/src/otel/instrumentations/gestureInstrumentation.ts +0 -141
  169. package/src/otel/instrumentations/index.ts +0 -120
  170. package/src/otel/instrumentations/reactNativeInstrumentation.ts +0 -77
  171. package/src/otel/instrumentations/reactNavigationInstrumentation.ts +0 -119
  172. package/src/patch/index.ts +0 -1
  173. package/src/patch/xhr.ts +0 -142
  174. package/src/recorder/eventExporter.ts +0 -141
  175. package/src/recorder/gestureHandlerRecorder.ts +0 -157
  176. package/src/recorder/gestureRecorder.ts +0 -622
  177. package/src/recorder/index.ts +0 -178
  178. package/src/recorder/navigationTracker.ts +0 -449
  179. package/src/recorder/screenRecorder.ts +0 -506
  180. package/src/services/api.service.ts +0 -203
  181. package/src/services/storage.service.ts +0 -158
  182. package/src/session-recorder.ts +0 -601
  183. package/src/types/expo.d.ts +0 -23
  184. package/src/types/index.ts +0 -46
  185. package/src/types/session-recorder.ts +0 -423
  186. package/src/types/session.ts +0 -65
  187. package/src/utils/index.ts +0 -8
  188. package/src/utils/logger.ts +0 -225
  189. package/src/utils/platform.ts +0 -87
  190. package/src/utils/request-utils.ts +0 -61
  191. package/src/utils/rrweb-events.ts +0 -311
  192. package/src/utils/session.ts +0 -18
  193. package/src/utils/time.ts +0 -17
  194. package/src/utils/type-utils.ts +0 -75
  195. package/src/version.ts +0 -1
  196. package/tsconfig.json +0 -24
  197. /package/dist/components/{GestureCaptureWrapper.d.ts → GestureCaptureWrapper/GestureCaptureWrapper.d.ts} +0 -0
@@ -1,178 +0,0 @@
1
- import { SessionType } from '@multiplayer-app/session-recorder-common'
2
- // import { pack } from '@rrweb/packer' // Removed to avoid blob creation issues in Hermes
3
- import { EventExporter } from './eventExporter'
4
- import { logger } from '../utils'
5
- import { ScreenRecorder } from './screenRecorder'
6
- import { GestureRecorder } from './gestureRecorder'
7
- import { NavigationTracker } from './navigationTracker'
8
- import { RecorderConfig, EventRecorder, RRWebEvent } from '../types'
9
- export class RecorderReactNativeSDK implements EventRecorder {
10
- private isRecording = false
11
- private config?: RecorderConfig
12
- private screenRecorder: ScreenRecorder
13
- private gestureRecorder: GestureRecorder
14
- private navigationTracker: NavigationTracker
15
- private recordedEvents: RRWebEvent[] = []
16
- private exporter: EventExporter | undefined
17
- private sessionId: string | null = null
18
- private sessionType: SessionType = SessionType.PLAIN
19
-
20
-
21
- constructor() {
22
- this.screenRecorder = new ScreenRecorder()
23
- this.gestureRecorder = new GestureRecorder()
24
- this.navigationTracker = new NavigationTracker()
25
- }
26
-
27
- init(config: RecorderConfig): void {
28
- this.config = config
29
- this.gestureRecorder.init(config, this, this.screenRecorder)
30
- this.navigationTracker.init(config)
31
- this.screenRecorder.init(config, this)
32
- this.exporter = new EventExporter({
33
- socketUrl: config.apiBaseUrl || '',
34
- apiKey: config.apiKey,
35
- })
36
- }
37
-
38
- start(sessionId: string | null, sessionType: SessionType): void {
39
- if (!this.config) {
40
- throw new Error('Configuration not initialized. Call init() before start().')
41
- }
42
-
43
- this.sessionId = sessionId
44
- this.sessionType = sessionType
45
- this.isRecording = true
46
-
47
- // Emit recording started meta event
48
-
49
- if (this.config.recordScreen) {
50
- this.screenRecorder.start()
51
- }
52
-
53
- if (this.config.recordGestures) {
54
- this.gestureRecorder.start()
55
- }
56
-
57
- if (this.config.recordNavigation) {
58
- this.navigationTracker.start()
59
- }
60
-
61
-
62
- }
63
-
64
- stop(): void {
65
- this.isRecording = false
66
- this.gestureRecorder.stop()
67
- this.navigationTracker.stop()
68
- this.screenRecorder.stop()
69
- this.exporter?.close()
70
- }
71
-
72
-
73
- setNavigationRef(ref: any): void {
74
- this.navigationTracker.setNavigationRef(ref)
75
- }
76
-
77
- /**
78
- * Set the viewshot ref for screen capture
79
- * @param ref - React Native View ref for screen capture
80
- */
81
- setViewShotRef(ref: any): void {
82
- this.screenRecorder.setViewShotRef(ref)
83
- }
84
-
85
- /**
86
- * Record an rrweb event
87
- * @param event - The rrweb event to record
88
- */
89
- recordEvent(event: RRWebEvent): void {
90
- if (!this.isRecording) {
91
- return
92
- }
93
-
94
- if (this.exporter) {
95
- logger.debug('RecorderReactNativeSDK', 'Sending to exporter', event)
96
- // Skip packing to avoid blob creation issues in Hermes
97
- // const packedEvent = pack(event)
98
- this.exporter.send({
99
- event: event, // Send raw event instead of packed
100
- eventType: event.type,
101
- timestamp: event.timestamp,
102
- debugSessionId: this.sessionId,
103
- debugSessionType: this.sessionType,
104
- })
105
- }
106
- }
107
-
108
- /**
109
- * Record touch start event
110
- * @param x - X coordinate
111
- * @param y - Y coordinate
112
- * @param target - Target element identifier
113
- * @param pressure - Touch pressure
114
- */
115
- recordTouchStart(x: number, y: number, target?: string, pressure?: number): void {
116
- if (!this.isRecording) {
117
- return
118
- }
119
-
120
- this.gestureRecorder.recordTouchStart(x, y, target, pressure)
121
- }
122
-
123
- /**
124
- * Record touch move event
125
- * @param x - X coordinate
126
- * @param y - Y coordinate
127
- * @param target - Target element identifier
128
- * @param pressure - Touch pressure
129
- */
130
- recordTouchMove(x: number, y: number, target?: string, pressure?: number): void {
131
- if (!this.isRecording) {
132
- return
133
- }
134
-
135
- this.gestureRecorder.recordTouchMove(x, y, target, pressure)
136
- }
137
-
138
- /**
139
- * Record touch end event
140
- * @param x - X coordinate
141
- * @param y - Y coordinate
142
- * @param target - Target element identifier
143
- * @param pressure - Touch pressure
144
- */
145
- recordTouchEnd(x: number, y: number, target?: string, pressure?: number): void {
146
- if (!this.isRecording) {
147
- return
148
- }
149
-
150
- this.gestureRecorder.recordTouchEnd(x, y, target, pressure)
151
- }
152
-
153
- /**
154
- * Get all recorded events
155
- * @returns Array of recorded rrweb events
156
- */
157
- getRecordedEvents(): RRWebEvent[] {
158
- return [...this.recordedEvents]
159
- }
160
-
161
- /**
162
- * Clear all recorded events
163
- */
164
- clearRecordedEvents(): void {
165
- this.recordedEvents = []
166
- }
167
-
168
- /**
169
- * Get recording statistics
170
- * @returns Recording statistics
171
- */
172
- getRecordingStats(): { totalEvents: number; isRecording: boolean } {
173
- return {
174
- totalEvents: this.recordedEvents.length,
175
- isRecording: this.isRecording,
176
- }
177
- }
178
- }
@@ -1,449 +0,0 @@
1
- import { NavigationEvent, RecorderConfig } from '../types'
2
- import { trace, SpanStatusCode } from '@opentelemetry/api'
3
- import { logger } from '../utils'
4
-
5
- export class NavigationTracker {
6
- private config?: RecorderConfig
7
- private isRecording = false
8
- private navigationRef: any = null
9
- private events: NavigationEvent[] = []
10
- private navigationListeners: Map<string, any> = new Map()
11
- private currentRoute: string | null = null
12
- private navigationStack: string[] = []
13
- private navigationStartTime: number = 0
14
-
15
- init(config: RecorderConfig): void {
16
- this.config = config
17
- }
18
-
19
- setNavigationRef(ref: any): void {
20
- this.navigationRef = ref
21
- if (this.isRecording) {
22
- this._setupNavigationListener()
23
- }
24
- }
25
-
26
- start(): void {
27
- logger.info('NavigationTracker', 'Navigation tracking started')
28
- this.isRecording = true
29
- this.events = []
30
- this.navigationStack = []
31
- this.navigationStartTime = Date.now()
32
- this._setupNavigationListener()
33
- // Navigation tracking started
34
- }
35
-
36
- stop(): void {
37
- this.isRecording = false
38
- this._removeNavigationListener()
39
- // Navigation tracking stopped
40
- }
41
-
42
- pause(): void {
43
- this.isRecording = false
44
- }
45
-
46
- resume(): void {
47
- this.isRecording = true
48
- this._setupNavigationListener()
49
- }
50
-
51
- private _setupNavigationListener(): void {
52
- if (!this.navigationRef) {
53
- // Navigation ref not set - silently continue
54
- return
55
- }
56
-
57
- try {
58
- // Listen to navigation state changes
59
- const stateListener = this.navigationRef.addListener('state', (e: any) => {
60
- this._recordNavigationEvent('state_change', e.data)
61
- })
62
-
63
- // Listen to focus events
64
- const focusListener = this.navigationRef.addListener('focus', (e: any) => {
65
- this._recordNavigationEvent('focus', e.data)
66
- })
67
-
68
- // Listen to blur events
69
- const blurListener = this.navigationRef.addListener('blur', (e: any) => {
70
- this._recordNavigationEvent('blur', e.data)
71
- })
72
-
73
- // Listen to beforeRemove events
74
- const beforeRemoveListener = this.navigationRef.addListener('beforeRemove', (e: any) => {
75
- this._recordNavigationEvent('beforeRemove', e.data)
76
- })
77
-
78
- // Store listeners for cleanup
79
- this.navigationListeners.set('state', stateListener)
80
- this.navigationListeners.set('focus', focusListener)
81
- this.navigationListeners.set('blur', blurListener)
82
- this.navigationListeners.set('beforeRemove', beforeRemoveListener)
83
-
84
- // Navigation listeners setup complete
85
- } catch (error) {
86
- // Failed to setup navigation listeners - silently continue
87
- }
88
- }
89
-
90
- private _removeNavigationListener(): void {
91
- try {
92
- // Remove all listeners
93
- this.navigationListeners.forEach((listener, key) => {
94
- if (listener && typeof listener.remove === 'function') {
95
- listener.remove()
96
- }
97
- })
98
- this.navigationListeners.clear()
99
- // Navigation listeners removed
100
- } catch (error) {
101
- // Failed to remove navigation listeners - silently continue
102
- }
103
- }
104
-
105
- private _recordNavigationEvent(eventType: string, data: any): void {
106
- if (!this.isRecording) return
107
-
108
- const event: NavigationEvent = {
109
- type: 'navigate', // Default type
110
- timestamp: Date.now(),
111
- metadata: {
112
- eventType,
113
- navigationDuration: Date.now() - this.navigationStartTime,
114
- stackDepth: this.navigationStack.length,
115
- },
116
- }
117
-
118
- if (data) {
119
- if (data.routeName) {
120
- event.routeName = data.routeName
121
- this._updateNavigationStack(data.routeName, eventType)
122
- }
123
- if (data.params) {
124
- event.params = data.params
125
- }
126
- if (data.key) {
127
- event.metadata!.routeKey = data.key
128
- }
129
- }
130
-
131
- this.events.push(event)
132
- this._sendEvent(event)
133
- this._recordOpenTelemetrySpan(event)
134
- }
135
-
136
-
137
-
138
- private _updateNavigationStack(routeName: string, eventType: string): void {
139
- if (eventType === 'focus' || eventType === 'state_change') {
140
- if (this.currentRoute !== routeName) {
141
- this.currentRoute = routeName
142
- this.navigationStack.push(routeName)
143
- }
144
- } else if (eventType === 'blur' || eventType === 'beforeRemove') {
145
- const index = this.navigationStack.indexOf(routeName)
146
- if (index > -1) {
147
- this.navigationStack.splice(index, 1)
148
- }
149
- }
150
- }
151
-
152
- private _sendEvent(event: NavigationEvent): void {
153
- // Navigation event recorded
154
- }
155
-
156
- private _recordOpenTelemetrySpan(event: NavigationEvent): void {
157
- try {
158
- const span = trace.getTracer('navigation').startSpan(`Navigation.${event.type}`, {
159
- attributes: {
160
- 'navigation.system': 'ReactNavigation',
161
- 'navigation.operation': event.type,
162
- 'navigation.type': event.type,
163
- 'navigation.timestamp': event.timestamp,
164
- 'navigation.platform': 'react-native',
165
- },
166
- })
167
-
168
- if (event.routeName) {
169
- span.setAttribute('navigation.route_name', event.routeName)
170
- }
171
- if (event.params) {
172
- span.setAttribute('navigation.params', JSON.stringify(event.params))
173
- }
174
- if (event.metadata) {
175
- Object.entries(event.metadata).forEach(([key, value]) => {
176
- span.setAttribute(`navigation.metadata.${key}`, String(value))
177
- })
178
- }
179
-
180
- span.setStatus({ code: SpanStatusCode.OK })
181
- span.end()
182
- } catch (error) {
183
- // Failed to record OpenTelemetry span for navigation - silently continue
184
- }
185
- }
186
-
187
- // Public methods for manual event recording
188
- recordNavigate(routeName: string, params?: Record<string, any>): void {
189
- const event: NavigationEvent = {
190
- type: 'navigate',
191
- timestamp: Date.now(),
192
- routeName,
193
- params,
194
- metadata: {
195
- navigationDuration: Date.now() - this.navigationStartTime,
196
- stackDepth: this.navigationStack.length,
197
- manual: true,
198
- },
199
- }
200
-
201
- this._updateNavigationStack(routeName, 'focus')
202
- this._recordEvent(event)
203
- }
204
-
205
- recordGoBack(): void {
206
- const event: NavigationEvent = {
207
- type: 'goBack',
208
- timestamp: Date.now(),
209
- metadata: {
210
- navigationDuration: Date.now() - this.navigationStartTime,
211
- stackDepth: this.navigationStack.length,
212
- manual: true,
213
- },
214
- }
215
-
216
- this._recordEvent(event)
217
- }
218
-
219
- recordReset(routes: any[]): void {
220
- const event: NavigationEvent = {
221
- type: 'reset',
222
- timestamp: Date.now(),
223
- metadata: {
224
- navigationDuration: Date.now() - this.navigationStartTime,
225
- routesCount: routes.length,
226
- manual: true,
227
- },
228
- }
229
-
230
- // Update navigation stack
231
- this.navigationStack = routes.map(route => route.name || route.routeName)
232
- if (routes.length > 0) {
233
- this.currentRoute = routes[0].name || routes[0].routeName
234
- }
235
-
236
- this._recordEvent(event)
237
- this._recordEvent(event)
238
- }
239
-
240
- private _recordEvent(event: NavigationEvent): void {
241
- if (!this.isRecording) return
242
-
243
- this.events.push(event)
244
- this._sendEvent(event)
245
- this._recordOpenTelemetrySpan(event)
246
- }
247
-
248
- // Advanced navigation tracking methods
249
- recordDeepLink(url: string, params?: Record<string, any>): void {
250
- const event: NavigationEvent = {
251
- type: 'navigate',
252
- timestamp: Date.now(),
253
- routeName: 'deepLink',
254
- params: { url, ...params },
255
- metadata: {
256
- navigationDuration: Date.now() - this.navigationStartTime,
257
- stackDepth: this.navigationStack.length,
258
- deepLink: true,
259
- },
260
- }
261
-
262
- this._recordEvent(event)
263
- this._recordEvent(event)
264
- }
265
-
266
- recordTabChange(tabName: string, tabIndex: number): void {
267
- const event: NavigationEvent = {
268
- type: 'navigate',
269
- timestamp: Date.now(),
270
- routeName: tabName,
271
- params: { tabIndex },
272
- metadata: {
273
- navigationDuration: Date.now() - this.navigationStartTime,
274
- stackDepth: this.navigationStack.length,
275
- tabChange: true,
276
- tabIndex,
277
- },
278
- }
279
-
280
- this._recordEvent(event)
281
- this._recordEvent(event)
282
- }
283
-
284
- recordModalOpen(modalName: string, params?: Record<string, any>): void {
285
- const event: NavigationEvent = {
286
- type: 'navigate',
287
- timestamp: Date.now(),
288
- routeName: modalName,
289
- params,
290
- metadata: {
291
- navigationDuration: Date.now() - this.navigationStartTime,
292
- stackDepth: this.navigationStack.length,
293
- modal: true,
294
- },
295
- }
296
-
297
- this._recordEvent(event)
298
- this._recordEvent(event)
299
- }
300
-
301
- recordModalClose(modalName: string): void {
302
- const event: NavigationEvent = {
303
- type: 'goBack',
304
- timestamp: Date.now(),
305
- routeName: modalName,
306
- metadata: {
307
- navigationDuration: Date.now() - this.navigationStartTime,
308
- stackDepth: this.navigationStack.length,
309
- modal: true,
310
- modalClose: true,
311
- },
312
- }
313
-
314
- this._recordEvent(event)
315
- this._recordEvent(event)
316
- }
317
-
318
- recordStackPush(routeName: string, params?: Record<string, any>): void {
319
- const event: NavigationEvent = {
320
- type: 'navigate',
321
- timestamp: Date.now(),
322
- routeName,
323
- params,
324
- metadata: {
325
- navigationDuration: Date.now() - this.navigationStartTime,
326
- stackDepth: this.navigationStack.length,
327
- stackOperation: 'push',
328
- },
329
- }
330
-
331
- this._recordEvent(event)
332
- this._recordEvent(event)
333
- }
334
-
335
- recordStackPop(routeName?: string): void {
336
- const event: NavigationEvent = {
337
- type: 'goBack',
338
- timestamp: Date.now(),
339
- routeName,
340
- metadata: {
341
- navigationDuration: Date.now() - this.navigationStartTime,
342
- stackDepth: this.navigationStack.length,
343
- stackOperation: 'pop',
344
- },
345
- }
346
-
347
- this._recordEvent(event)
348
- this._recordEvent(event)
349
- }
350
-
351
- // Performance monitoring
352
- recordNavigationPerformance(routeName: string, loadTime: number): void {
353
- const event: NavigationEvent = {
354
- type: 'navigate',
355
- timestamp: Date.now(),
356
- routeName,
357
- metadata: {
358
- navigationDuration: Date.now() - this.navigationStartTime,
359
- stackDepth: this.navigationStack.length,
360
- performance: 'monitoring',
361
- loadTime,
362
- },
363
- }
364
-
365
- this._recordEvent(event)
366
- this._recordEvent(event)
367
- }
368
-
369
- // Error tracking
370
- recordNavigationError(error: Error, routeName?: string): void {
371
- const event: NavigationEvent = {
372
- type: 'navigate',
373
- timestamp: Date.now(),
374
- routeName,
375
- metadata: {
376
- navigationDuration: Date.now() - this.navigationStartTime,
377
- stackDepth: this.navigationStack.length,
378
- error: true,
379
- errorType: error.name,
380
- errorMessage: error.message,
381
- },
382
- }
383
-
384
- this._recordEvent(event)
385
- this._recordEvent(event)
386
-
387
- // Also record as OpenTelemetry error span
388
- try {
389
- const span = trace.getTracer('navigation').startSpan('Navigation.error', {
390
- attributes: {
391
- 'navigation.system': 'ReactNavigation',
392
- 'navigation.error': true,
393
- 'navigation.error.type': error.name,
394
- 'navigation.error.message': error.message,
395
- 'navigation.route_name': routeName || 'unknown',
396
- 'navigation.timestamp': Date.now(),
397
- },
398
- })
399
-
400
- span.setStatus({ code: SpanStatusCode.ERROR, message: error.message })
401
- span.recordException(error)
402
- span.end()
403
- } catch (spanError) {
404
- // Failed to record error span - silently continue
405
- }
406
- }
407
-
408
- // Get current navigation state
409
- getCurrentRoute(): string | null {
410
- return this.currentRoute
411
- }
412
-
413
- getNavigationStack(): string[] {
414
- return [...this.navigationStack]
415
- }
416
-
417
- getNavigationDepth(): number {
418
- return this.navigationStack.length
419
- }
420
-
421
- // Get recorded events
422
- getEvents(): NavigationEvent[] {
423
- return [...this.events]
424
- }
425
-
426
- // Clear events
427
- clearEvents(): void {
428
- this.events = []
429
- }
430
-
431
- // Get navigation statistics
432
- getNavigationStats(): Record<string, number> {
433
- const stats: Record<string, number> = {}
434
- this.events.forEach(event => {
435
- stats[event.type] = (stats[event.type] || 0) + 1
436
- })
437
- return stats
438
- }
439
-
440
- // Get recording status
441
- isRecordingEnabled(): boolean {
442
- return this.isRecording
443
- }
444
-
445
- // Get navigation duration
446
- getNavigationDuration(): number {
447
- return Date.now() - this.navigationStartTime
448
- }
449
- }