@rejourneyco/react-native 1.0.0

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 (152) hide show
  1. package/android/build.gradle.kts +135 -0
  2. package/android/consumer-rules.pro +10 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/AndroidManifest.xml +15 -0
  5. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
  6. package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
  7. package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
  8. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
  9. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
  10. package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
  11. package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
  12. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
  13. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
  14. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
  15. package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
  16. package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
  17. package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
  18. package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
  19. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
  20. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
  21. package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
  22. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
  23. package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
  24. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
  25. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
  26. package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
  27. package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
  28. package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
  29. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
  30. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
  31. package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
  32. package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
  33. package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
  34. package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
  35. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
  36. package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
  37. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
  38. package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
  39. package/ios/Capture/RJANRHandler.h +42 -0
  40. package/ios/Capture/RJANRHandler.m +328 -0
  41. package/ios/Capture/RJCaptureEngine.h +275 -0
  42. package/ios/Capture/RJCaptureEngine.m +2062 -0
  43. package/ios/Capture/RJCaptureHeuristics.h +80 -0
  44. package/ios/Capture/RJCaptureHeuristics.m +903 -0
  45. package/ios/Capture/RJCrashHandler.h +46 -0
  46. package/ios/Capture/RJCrashHandler.m +313 -0
  47. package/ios/Capture/RJMotionEvent.h +183 -0
  48. package/ios/Capture/RJMotionEvent.m +183 -0
  49. package/ios/Capture/RJPerformanceManager.h +100 -0
  50. package/ios/Capture/RJPerformanceManager.m +373 -0
  51. package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
  52. package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
  53. package/ios/Capture/RJSegmentUploader.h +146 -0
  54. package/ios/Capture/RJSegmentUploader.m +778 -0
  55. package/ios/Capture/RJVideoEncoder.h +247 -0
  56. package/ios/Capture/RJVideoEncoder.m +1036 -0
  57. package/ios/Capture/RJViewControllerTracker.h +73 -0
  58. package/ios/Capture/RJViewControllerTracker.m +508 -0
  59. package/ios/Capture/RJViewHierarchyScanner.h +215 -0
  60. package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
  61. package/ios/Capture/RJViewSerializer.h +119 -0
  62. package/ios/Capture/RJViewSerializer.m +498 -0
  63. package/ios/Core/RJConstants.h +124 -0
  64. package/ios/Core/RJConstants.m +88 -0
  65. package/ios/Core/RJLifecycleManager.h +85 -0
  66. package/ios/Core/RJLifecycleManager.m +308 -0
  67. package/ios/Core/RJLogger.h +61 -0
  68. package/ios/Core/RJLogger.m +211 -0
  69. package/ios/Core/RJTypes.h +176 -0
  70. package/ios/Core/RJTypes.m +66 -0
  71. package/ios/Core/Rejourney.h +64 -0
  72. package/ios/Core/Rejourney.mm +2495 -0
  73. package/ios/Network/RJDeviceAuthManager.h +94 -0
  74. package/ios/Network/RJDeviceAuthManager.m +967 -0
  75. package/ios/Network/RJNetworkMonitor.h +68 -0
  76. package/ios/Network/RJNetworkMonitor.m +267 -0
  77. package/ios/Network/RJRetryManager.h +73 -0
  78. package/ios/Network/RJRetryManager.m +325 -0
  79. package/ios/Network/RJUploadManager.h +267 -0
  80. package/ios/Network/RJUploadManager.m +2296 -0
  81. package/ios/Privacy/RJPrivacyMask.h +163 -0
  82. package/ios/Privacy/RJPrivacyMask.m +922 -0
  83. package/ios/Rejourney.h +63 -0
  84. package/ios/Touch/RJGestureClassifier.h +130 -0
  85. package/ios/Touch/RJGestureClassifier.m +333 -0
  86. package/ios/Touch/RJTouchInterceptor.h +169 -0
  87. package/ios/Touch/RJTouchInterceptor.m +772 -0
  88. package/ios/Utils/RJEventBuffer.h +112 -0
  89. package/ios/Utils/RJEventBuffer.m +358 -0
  90. package/ios/Utils/RJGzipUtils.h +33 -0
  91. package/ios/Utils/RJGzipUtils.m +89 -0
  92. package/ios/Utils/RJKeychainManager.h +48 -0
  93. package/ios/Utils/RJKeychainManager.m +111 -0
  94. package/ios/Utils/RJPerfTiming.h +209 -0
  95. package/ios/Utils/RJPerfTiming.m +264 -0
  96. package/ios/Utils/RJTelemetry.h +92 -0
  97. package/ios/Utils/RJTelemetry.m +320 -0
  98. package/ios/Utils/RJWindowUtils.h +66 -0
  99. package/ios/Utils/RJWindowUtils.m +133 -0
  100. package/lib/commonjs/NativeRejourney.js +40 -0
  101. package/lib/commonjs/components/Mask.js +79 -0
  102. package/lib/commonjs/index.js +1381 -0
  103. package/lib/commonjs/sdk/autoTracking.js +1259 -0
  104. package/lib/commonjs/sdk/constants.js +151 -0
  105. package/lib/commonjs/sdk/errorTracking.js +199 -0
  106. package/lib/commonjs/sdk/index.js +50 -0
  107. package/lib/commonjs/sdk/metricsTracking.js +204 -0
  108. package/lib/commonjs/sdk/navigation.js +151 -0
  109. package/lib/commonjs/sdk/networkInterceptor.js +412 -0
  110. package/lib/commonjs/sdk/utils.js +363 -0
  111. package/lib/commonjs/types/expo-router.d.js +2 -0
  112. package/lib/commonjs/types/index.js +2 -0
  113. package/lib/module/NativeRejourney.js +38 -0
  114. package/lib/module/components/Mask.js +72 -0
  115. package/lib/module/index.js +1284 -0
  116. package/lib/module/sdk/autoTracking.js +1233 -0
  117. package/lib/module/sdk/constants.js +145 -0
  118. package/lib/module/sdk/errorTracking.js +189 -0
  119. package/lib/module/sdk/index.js +12 -0
  120. package/lib/module/sdk/metricsTracking.js +187 -0
  121. package/lib/module/sdk/navigation.js +143 -0
  122. package/lib/module/sdk/networkInterceptor.js +401 -0
  123. package/lib/module/sdk/utils.js +342 -0
  124. package/lib/module/types/expo-router.d.js +2 -0
  125. package/lib/module/types/index.js +2 -0
  126. package/lib/typescript/NativeRejourney.d.ts +147 -0
  127. package/lib/typescript/components/Mask.d.ts +39 -0
  128. package/lib/typescript/index.d.ts +117 -0
  129. package/lib/typescript/sdk/autoTracking.d.ts +204 -0
  130. package/lib/typescript/sdk/constants.d.ts +120 -0
  131. package/lib/typescript/sdk/errorTracking.d.ts +32 -0
  132. package/lib/typescript/sdk/index.d.ts +9 -0
  133. package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
  134. package/lib/typescript/sdk/navigation.d.ts +33 -0
  135. package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
  136. package/lib/typescript/sdk/utils.d.ts +148 -0
  137. package/lib/typescript/types/index.d.ts +624 -0
  138. package/package.json +102 -0
  139. package/rejourney.podspec +21 -0
  140. package/src/NativeRejourney.ts +165 -0
  141. package/src/components/Mask.tsx +80 -0
  142. package/src/index.ts +1459 -0
  143. package/src/sdk/autoTracking.ts +1373 -0
  144. package/src/sdk/constants.ts +134 -0
  145. package/src/sdk/errorTracking.ts +231 -0
  146. package/src/sdk/index.ts +11 -0
  147. package/src/sdk/metricsTracking.ts +232 -0
  148. package/src/sdk/navigation.ts +157 -0
  149. package/src/sdk/networkInterceptor.ts +440 -0
  150. package/src/sdk/utils.ts +369 -0
  151. package/src/types/expo-router.d.ts +7 -0
  152. package/src/types/index.ts +739 -0
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Rejourney Navigation Utilities
3
+ *
4
+ * Helper functions for extracting human-readable screen names from
5
+ * React Navigation / Expo Router state.
6
+ *
7
+ * These functions are used internally by the SDK's automatic navigation
8
+ * detection. You don't need to import or use these directly.
9
+ */
10
+
11
+ /**
12
+ * Normalize a screen name to be human-readable
13
+ * Handles common patterns from React Native / Expo Router
14
+ *
15
+ * @param raw - Raw screen name to normalize
16
+ * @returns Cleaned, human-readable screen name
17
+ */
18
+ export function normalizeScreenName(raw: string): string {
19
+ if (!raw) return 'Unknown';
20
+
21
+ let name = raw;
22
+
23
+ // 1. Remove non-printable characters and weird symbols (often from icons)
24
+ name = name.replace(/[^\x20-\x7E\s]/g, '');
25
+
26
+ // 2. Handle hyphens: my-recipes -> My Recipes
27
+ name = name.split(/[-_]/).map(word =>
28
+ word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
29
+ ).join(' ');
30
+
31
+ // 3. Remove common suffixes
32
+ const suffixes = ['Screen', 'Page', 'View', 'Controller', 'ViewController', 'VC'];
33
+ for (const suffix of suffixes) {
34
+ if (name.endsWith(suffix) && name.length > suffix.length) {
35
+ name = name.slice(0, -suffix.length).trim();
36
+ }
37
+ }
38
+
39
+ // 4. Remove common prefixes (React Native internals)
40
+ const prefixes = ['RNS', 'RCT', 'RN', 'UI'];
41
+ for (const prefix of prefixes) {
42
+ if (name.startsWith(prefix) && name.length > prefix.length + 2) {
43
+ name = name.slice(prefix.length).trim();
44
+ }
45
+ }
46
+
47
+ // 5. Handle dynamic route params: [id] -> (omit), [userId] -> "User"
48
+ name = name.replace(/\[([a-zA-Z]+)(?:Id)?\]/g, (_, param) => {
49
+ const clean = param.replace(/Id$/i, '');
50
+ if (clean.length < 2) return '';
51
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
52
+ });
53
+
54
+ // 6. Remove leftover brackets
55
+ name = name.replace(/\[\]/g, '');
56
+
57
+ // 7. Convert camelCase/PascalCase to Title Case with spaces
58
+ name = name.replace(/([a-z])([A-Z])/g, '$1 $2');
59
+
60
+ // 8. Clean up multiple spaces and trim
61
+ name = name.replace(/\s+/g, ' ').trim();
62
+
63
+ // 9. Capitalize first letter
64
+ if (name.length > 0) {
65
+ name = name.charAt(0).toUpperCase() + name.slice(1);
66
+ }
67
+
68
+ return name || 'Unknown';
69
+ }
70
+
71
+ /**
72
+ * Get a human-readable screen name from Expo Router path and segments
73
+ *
74
+ * @param pathname - The current route pathname
75
+ * @param segments - Route segments from useSegments()
76
+ * @returns Human-readable screen name
77
+ */
78
+ export function getScreenNameFromPath(pathname: string, segments: string[]): string {
79
+ // Use segments for cleaner names if available
80
+ if (segments.length > 0) {
81
+ // Filter out group markers like (tabs), (auth), etc.
82
+ const cleanSegments = segments.filter(s => !s.startsWith('(') && !s.endsWith(')'));
83
+
84
+ if (cleanSegments.length > 0) {
85
+ // Process each segment
86
+ const processedSegments = cleanSegments.map(s => {
87
+ // Handle dynamic params like [id]
88
+ if (s.startsWith('[') && s.endsWith(']')) {
89
+ const param = s.slice(1, -1);
90
+ // Skip pure ID params, keep meaningful ones
91
+ if (param === 'id' || param === 'slug') return null;
92
+ // Extract meaningful part: userId -> User
93
+ const clean = param.replace(/Id$/i, '');
94
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
95
+ }
96
+ // Regular segment - capitalize
97
+ return s.charAt(0).toUpperCase() + s.slice(1);
98
+ }).filter(Boolean);
99
+
100
+ if (processedSegments.length > 0) {
101
+ return processedSegments.join(' > ');
102
+ }
103
+ }
104
+ }
105
+
106
+ // Fall back to pathname
107
+ if (!pathname || pathname === '/') {
108
+ return 'Home';
109
+ }
110
+
111
+ // Clean up the path
112
+ let cleanPath = pathname
113
+ .replace(/^\/(tabs)?/, '') // Remove leading slash and (tabs)
114
+ .replace(/\([^)]+\)/g, '') // Remove all group markers like (settings)
115
+ .replace(/\[([^\]]+)\]/g, (_, param) => {
116
+ // Handle dynamic params in path
117
+ if (param === 'id' || param === 'slug') return '';
118
+ const clean = param.replace(/Id$/i, '');
119
+ return clean.charAt(0).toUpperCase() + clean.slice(1);
120
+ })
121
+ .replace(/\/+/g, '/') // Collapse multiple slashes
122
+ .replace(/^\//, '') // Remove leading slash
123
+ .replace(/\/$/, '') // Remove trailing slash
124
+ .replace(/\//g, ' > ') // Replace slashes with arrows
125
+ .trim();
126
+
127
+ if (!cleanPath) {
128
+ return 'Home';
129
+ }
130
+
131
+ // Capitalize first letter of each word
132
+ return cleanPath
133
+ .split(' > ')
134
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
135
+ .filter(s => s.length > 0)
136
+ .join(' > ') || 'Home';
137
+ }
138
+
139
+ /**
140
+ * Get the current route name from a navigation state object
141
+ *
142
+ * @param state - React Navigation state object
143
+ * @returns Current route name or null
144
+ */
145
+ export function getCurrentRouteFromState(state: any): string | null {
146
+ if (!state || !state.routes) return null;
147
+
148
+ const route = state.routes[state.index ?? state.routes.length - 1];
149
+ if (!route) return null;
150
+
151
+ // If nested state, recurse
152
+ if (route.state) {
153
+ return getCurrentRouteFromState(route.state);
154
+ }
155
+
156
+ return route.name || null;
157
+ }
@@ -0,0 +1,440 @@
1
+ /**
2
+ * Network Interceptor for Rejourney - Optimized Version
3
+ *
4
+ * Automatically intercepts fetch() and XMLHttpRequest to log API calls.
5
+ *
6
+ * PERFORMANCE OPTIMIZATIONS:
7
+ * - Minimal synchronous overhead (just captures timing, no processing)
8
+ * - Batched async logging (doesn't block requests)
9
+ * - Circular buffer with max size limit
10
+ * - Sampling for high-frequency endpoints
11
+ * - No string allocations in hot path
12
+ * - Lazy URL parsing
13
+ * - PII Scrubbing for query parameters
14
+ */
15
+
16
+ import type { NetworkRequestParams } from '../types';
17
+
18
+ // Store original implementations
19
+ let originalFetch: typeof fetch | null = null;
20
+ let originalXHROpen: typeof XMLHttpRequest.prototype.open | null = null;
21
+ let originalXHRSend: typeof XMLHttpRequest.prototype.send | null = null;
22
+
23
+ // Callback to log network requests (called asynchronously)
24
+ let logCallback: ((request: NetworkRequestParams) => void) | null = null;
25
+
26
+ // Pending requests buffer (circular buffer for memory efficiency)
27
+ const MAX_PENDING_REQUESTS = 100;
28
+ const pendingRequests: (NetworkRequestParams | null)[] = new Array(MAX_PENDING_REQUESTS).fill(null);
29
+ let pendingHead = 0;
30
+ let pendingTail = 0;
31
+ let pendingCount = 0;
32
+
33
+ // Flush timer
34
+ let flushTimer: ReturnType<typeof setTimeout> | null = null;
35
+ const FLUSH_INTERVAL = 500; // Flush every 500ms
36
+
37
+ // Sampling for high-frequency endpoints
38
+ const endpointCounts = new Map<string, { count: number; lastReset: number }>();
39
+ const SAMPLE_WINDOW = 10000; // 10 second window
40
+ const MAX_PER_ENDPOINT = 20; // Max 20 requests per endpoint per window
41
+
42
+ // Configuration
43
+ const config = {
44
+ enabled: true,
45
+ ignorePatterns: [] as string[], // Simple string patterns for fast matching
46
+ maxUrlLength: 300, // Shorter for efficiency
47
+ captureSizes: false, // Disabled by default for performance
48
+ };
49
+
50
+ // PII Scrubbing - Sensitive keys to look for in query params
51
+ const SENSITIVE_KEYS = ['token', 'key', 'secret', 'password', 'auth', 'access_token', 'api_key'];
52
+
53
+ /**
54
+ * Scrub sensitive data from URL
55
+ */
56
+ function scrubUrl(url: string): string {
57
+ try {
58
+ // Fast check if URL might contain params
59
+ if (url.indexOf('?') === -1) return url;
60
+
61
+ const urlObj = new URL(url);
62
+ let modified = false;
63
+
64
+ SENSITIVE_KEYS.forEach(key => {
65
+ if (urlObj.searchParams.has(key)) {
66
+ urlObj.searchParams.set(key, '[REDACTED]');
67
+ modified = true;
68
+ }
69
+ });
70
+
71
+ // Also scan for partial matches (case-insensitive) if strict scrubbing needed
72
+ // But for performance, we stick to exact keys or common variations
73
+
74
+ return modified ? urlObj.toString() : url;
75
+ } catch {
76
+ // If URL parsing fails (relative URL?), try primitive replacement
77
+ let scrubbed = url;
78
+ SENSITIVE_KEYS.forEach(key => {
79
+ const regex = new RegExp(`([?&])${key}=[^&]*`, 'gi');
80
+ scrubbed = scrubbed.replace(regex, `$1${key}=[REDACTED]`);
81
+ });
82
+ return scrubbed;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Fast check if URL should be ignored (no regex for speed)
88
+ */
89
+ function shouldIgnoreUrl(url: string): boolean {
90
+ const patterns = config.ignorePatterns;
91
+ for (let i = 0; i < patterns.length; i++) {
92
+ const pattern = patterns[i];
93
+ if (pattern && url.indexOf(pattern) !== -1) return true;
94
+ }
95
+ return false;
96
+ }
97
+
98
+ /**
99
+ * Check if we should sample this request (rate limiting per endpoint)
100
+ */
101
+ function shouldSampleRequest(urlPath: string): boolean {
102
+ const now = Date.now();
103
+ let entry = endpointCounts.get(urlPath);
104
+
105
+ if (!entry || now - entry.lastReset > SAMPLE_WINDOW) {
106
+ entry = { count: 0, lastReset: now };
107
+ endpointCounts.set(urlPath, entry);
108
+ }
109
+
110
+ entry.count++;
111
+ return entry.count <= MAX_PER_ENDPOINT;
112
+ }
113
+
114
+ /**
115
+ * Add request to pending buffer (non-blocking)
116
+ */
117
+ function queueRequest(request: NetworkRequestParams): void {
118
+ if (pendingCount >= MAX_PENDING_REQUESTS) {
119
+ // Buffer full, drop oldest
120
+ pendingHead = (pendingHead + 1) % MAX_PENDING_REQUESTS;
121
+ pendingCount--;
122
+ }
123
+
124
+ // Scrub URL before queuing
125
+ request.url = scrubUrl(request.url);
126
+
127
+ pendingRequests[pendingTail] = request;
128
+ pendingTail = (pendingTail + 1) % MAX_PENDING_REQUESTS;
129
+ pendingCount++;
130
+
131
+ // Schedule flush if not already scheduled
132
+ if (!flushTimer) {
133
+ flushTimer = setTimeout(flushPendingRequests, FLUSH_INTERVAL);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Flush pending requests to callback
139
+ */
140
+ function flushPendingRequests(): void {
141
+ flushTimer = null;
142
+
143
+ if (!logCallback || pendingCount === 0) return;
144
+
145
+ // Process all pending requests
146
+ while (pendingCount > 0) {
147
+ const request = pendingRequests[pendingHead];
148
+ pendingRequests[pendingHead] = null; // Allow GC
149
+ pendingHead = (pendingHead + 1) % MAX_PENDING_REQUESTS;
150
+ pendingCount--;
151
+
152
+ if (request) {
153
+ try {
154
+ logCallback(request);
155
+ } catch {
156
+ // Ignore logging errors
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Parse URL efficiently (only extract what we need)
164
+ */
165
+ function parseUrlFast(url: string): { host: string; path: string } {
166
+ // Fast path for common patterns
167
+ let hostEnd = -1;
168
+ let pathStart = -1;
169
+
170
+ // Find ://
171
+ const protoEnd = url.indexOf('://');
172
+ if (protoEnd !== -1) {
173
+ const afterProto = protoEnd + 3;
174
+ // Find end of host (first / after ://)
175
+ const slashPos = url.indexOf('/', afterProto);
176
+ if (slashPos !== -1) {
177
+ hostEnd = slashPos;
178
+ pathStart = slashPos;
179
+ } else {
180
+ hostEnd = url.length;
181
+ pathStart = url.length;
182
+ }
183
+
184
+ return {
185
+ host: url.substring(afterProto, hostEnd),
186
+ path: pathStart < url.length ? url.substring(pathStart) : '/',
187
+ };
188
+ }
189
+
190
+ // Relative URL
191
+ return { host: '', path: url };
192
+ }
193
+
194
+ /**
195
+ * Intercept fetch - minimal overhead version
196
+ */
197
+ function interceptFetch(): void {
198
+ if (typeof globalThis.fetch === 'undefined') return;
199
+ if (originalFetch) return;
200
+
201
+ originalFetch = globalThis.fetch;
202
+
203
+ globalThis.fetch = function optimizedFetch(
204
+ input: RequestInfo | URL,
205
+ init?: RequestInit
206
+ ): Promise<Response> {
207
+ // Fast path: if disabled or no callback, skip entirely
208
+ if (!config.enabled || !logCallback) {
209
+ return originalFetch!(input, init);
210
+ }
211
+
212
+ // Extract URL string (minimal work)
213
+ const url = typeof input === 'string'
214
+ ? input
215
+ : input instanceof URL
216
+ ? input.href
217
+ : (input as Request).url;
218
+
219
+ // Fast ignore check
220
+ if (shouldIgnoreUrl(url)) {
221
+ return originalFetch!(input, init);
222
+ }
223
+
224
+ // Parse URL and check sampling
225
+ const { path } = parseUrlFast(url);
226
+ if (!shouldSampleRequest(path)) {
227
+ return originalFetch!(input, init);
228
+ }
229
+
230
+ // Capture start time (only synchronous work)
231
+ const startTime = Date.now();
232
+ const method = ((init?.method || 'GET').toUpperCase()) as NetworkRequestParams['method'];
233
+
234
+ // Call original fetch
235
+ return originalFetch!(input, init).then(
236
+ (response) => {
237
+ // Success - queue the log asynchronously
238
+ queueRequest({
239
+ requestId: `f${startTime}`,
240
+ method,
241
+ url: url.length > config.maxUrlLength ? url.substring(0, config.maxUrlLength) : url,
242
+ statusCode: response.status,
243
+ duration: Date.now() - startTime,
244
+ startTimestamp: startTime,
245
+ endTimestamp: Date.now(),
246
+ success: response.ok,
247
+ });
248
+ return response;
249
+ },
250
+ (error) => {
251
+ // Error - queue the log asynchronously
252
+ queueRequest({
253
+ requestId: `f${startTime}`,
254
+ method,
255
+ url: url.length > config.maxUrlLength ? url.substring(0, config.maxUrlLength) : url,
256
+ statusCode: 0,
257
+ duration: Date.now() - startTime,
258
+ startTimestamp: startTime,
259
+ endTimestamp: Date.now(),
260
+ success: false,
261
+ errorMessage: error?.message || 'Network error',
262
+ });
263
+ throw error;
264
+ }
265
+ );
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Intercept XMLHttpRequest - minimal overhead version
271
+ */
272
+ function interceptXHR(): void {
273
+ if (typeof XMLHttpRequest === 'undefined') return;
274
+ if (originalXHROpen) return;
275
+
276
+ originalXHROpen = XMLHttpRequest.prototype.open;
277
+ originalXHRSend = XMLHttpRequest.prototype.send;
278
+
279
+ XMLHttpRequest.prototype.open = function (
280
+ method: string,
281
+ url: string | URL,
282
+ async: boolean = true,
283
+ username?: string | null,
284
+ password?: string | null
285
+ ): void {
286
+ const urlString = typeof url === 'string' ? url : url.toString();
287
+
288
+ // Store minimal info
289
+ (this as any).__rj = {
290
+ m: method.toUpperCase(),
291
+ u: urlString,
292
+ t: 0,
293
+ };
294
+
295
+ return originalXHROpen!.call(this, method, urlString, async, username, password);
296
+ };
297
+
298
+ XMLHttpRequest.prototype.send = function (body?: any): void {
299
+ const data = (this as any).__rj;
300
+
301
+ if (!config.enabled || !logCallback || !data || shouldIgnoreUrl(data.u)) {
302
+ return originalXHRSend!.call(this, body);
303
+ }
304
+
305
+ // Check sampling
306
+ const { path } = parseUrlFast(data.u);
307
+ if (!shouldSampleRequest(path)) {
308
+ return originalXHRSend!.call(this, body);
309
+ }
310
+
311
+ data.t = Date.now();
312
+
313
+ const onComplete = () => {
314
+ const endTime = Date.now();
315
+ queueRequest({
316
+ requestId: `x${data.t}`,
317
+ method: data.m as NetworkRequestParams['method'],
318
+ url: data.u.length > config.maxUrlLength ? data.u.substring(0, config.maxUrlLength) : data.u,
319
+ statusCode: this.status,
320
+ duration: endTime - data.t,
321
+ startTimestamp: data.t,
322
+ endTimestamp: endTime,
323
+ success: this.status >= 200 && this.status < 400,
324
+ errorMessage: this.status === 0 ? 'Network error' : undefined,
325
+ });
326
+ };
327
+
328
+ // Use load/error events (more efficient than readystatechange)
329
+ // Note: We use basic addEventListener without options for RN compatibility
330
+ this.addEventListener('load', onComplete);
331
+ this.addEventListener('error', onComplete);
332
+ this.addEventListener('abort', onComplete);
333
+
334
+ return originalXHRSend!.call(this, body);
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Initialize network interception
340
+ */
341
+ export function initNetworkInterceptor(
342
+ callback: (request: NetworkRequestParams) => void,
343
+ options?: {
344
+ ignoreUrls?: (string | RegExp)[];
345
+ captureSizes?: boolean;
346
+ }
347
+ ): void {
348
+ logCallback = callback;
349
+
350
+ // Convert patterns to simple strings for fast matching
351
+ if (options?.ignoreUrls) {
352
+ config.ignorePatterns = options.ignoreUrls
353
+ .filter((p): p is string => typeof p === 'string');
354
+ // Note: RegExp patterns are not supported in optimized version for performance
355
+ }
356
+
357
+ if (options?.captureSizes !== undefined) {
358
+ config.captureSizes = options.captureSizes;
359
+ }
360
+
361
+ interceptFetch();
362
+ interceptXHR();
363
+ }
364
+
365
+ /**
366
+ * Disable network interception
367
+ */
368
+ export function disableNetworkInterceptor(): void {
369
+ config.enabled = false;
370
+
371
+ // Flush any pending requests
372
+ if (flushTimer) {
373
+ clearTimeout(flushTimer);
374
+ flushTimer = null;
375
+ }
376
+ flushPendingRequests();
377
+ }
378
+
379
+ /**
380
+ * Re-enable network interception
381
+ */
382
+ export function enableNetworkInterceptor(): void {
383
+ config.enabled = true;
384
+ }
385
+
386
+ /**
387
+ * Force flush pending requests (call before app termination)
388
+ */
389
+ export function flushNetworkRequests(): void {
390
+ if (flushTimer) {
391
+ clearTimeout(flushTimer);
392
+ flushTimer = null;
393
+ }
394
+ flushPendingRequests();
395
+ }
396
+
397
+ /**
398
+ * Restore original fetch and XHR
399
+ */
400
+ export function restoreNetworkInterceptor(): void {
401
+ if (originalFetch) {
402
+ globalThis.fetch = originalFetch;
403
+ originalFetch = null;
404
+ }
405
+
406
+ if (originalXHROpen && originalXHRSend) {
407
+ XMLHttpRequest.prototype.open = originalXHROpen;
408
+ XMLHttpRequest.prototype.send = originalXHRSend;
409
+ originalXHROpen = null;
410
+ originalXHRSend = null;
411
+ }
412
+
413
+ logCallback = null;
414
+
415
+ // Clear state
416
+ pendingHead = 0;
417
+ pendingTail = 0;
418
+ pendingCount = 0;
419
+ endpointCounts.clear();
420
+
421
+ if (flushTimer) {
422
+ clearTimeout(flushTimer);
423
+ flushTimer = null;
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Get stats for debugging
429
+ */
430
+ export function getNetworkInterceptorStats(): {
431
+ pendingCount: number;
432
+ endpointCount: number;
433
+ enabled: boolean;
434
+ } {
435
+ return {
436
+ pendingCount,
437
+ endpointCount: endpointCounts.size,
438
+ enabled: config.enabled,
439
+ };
440
+ }