@multiplayer-app/session-recorder-react-native 0.0.1-alpha.1

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