react-native-prod-debugger 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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +350 -0
  3. package/lib/commonjs/core/DebugBubble.js +139 -0
  4. package/lib/commonjs/core/DebugBubble.js.map +1 -0
  5. package/lib/commonjs/core/DebugOverlay.js +184 -0
  6. package/lib/commonjs/core/DebugOverlay.js.map +1 -0
  7. package/lib/commonjs/core/DebuggerProvider.js +174 -0
  8. package/lib/commonjs/core/DebuggerProvider.js.map +1 -0
  9. package/lib/commonjs/core/GestureDetector.js +83 -0
  10. package/lib/commonjs/core/GestureDetector.js.map +1 -0
  11. package/lib/commonjs/core/PluginRegistry.js +124 -0
  12. package/lib/commonjs/core/PluginRegistry.js.map +1 -0
  13. package/lib/commonjs/core/theme.js +50 -0
  14. package/lib/commonjs/core/theme.js.map +1 -0
  15. package/lib/commonjs/core/types.js +6 -0
  16. package/lib/commonjs/core/types.js.map +1 -0
  17. package/lib/commonjs/core/useDebugger.js +27 -0
  18. package/lib/commonjs/core/useDebugger.js.map +1 -0
  19. package/lib/commonjs/core/utils.js +239 -0
  20. package/lib/commonjs/core/utils.js.map +1 -0
  21. package/lib/commonjs/index.js +132 -0
  22. package/lib/commonjs/index.js.map +1 -0
  23. package/lib/commonjs/plugins/console/ConsoleInterceptor.js +103 -0
  24. package/lib/commonjs/plugins/console/ConsoleInterceptor.js.map +1 -0
  25. package/lib/commonjs/plugins/console/ConsoleViewerPlugin.js +269 -0
  26. package/lib/commonjs/plugins/console/ConsoleViewerPlugin.js.map +1 -0
  27. package/lib/commonjs/plugins/crashReporter/CrashReporterPlugin.js +363 -0
  28. package/lib/commonjs/plugins/crashReporter/CrashReporterPlugin.js.map +1 -0
  29. package/lib/commonjs/plugins/customActions/CustomActionsPlugin.js +218 -0
  30. package/lib/commonjs/plugins/customActions/CustomActionsPlugin.js.map +1 -0
  31. package/lib/commonjs/plugins/customActions/actionStore.js +46 -0
  32. package/lib/commonjs/plugins/customActions/actionStore.js.map +1 -0
  33. package/lib/commonjs/plugins/deepLinkTester/DeepLinkTesterPlugin.js +268 -0
  34. package/lib/commonjs/plugins/deepLinkTester/DeepLinkTesterPlugin.js.map +1 -0
  35. package/lib/commonjs/plugins/deviceInfo/DeviceInfoPlugin.js +184 -0
  36. package/lib/commonjs/plugins/deviceInfo/DeviceInfoPlugin.js.map +1 -0
  37. package/lib/commonjs/plugins/featureFlags/FeatureFlagsPlugin.js +381 -0
  38. package/lib/commonjs/plugins/featureFlags/FeatureFlagsPlugin.js.map +1 -0
  39. package/lib/commonjs/plugins/featureFlags/flagStore.js +125 -0
  40. package/lib/commonjs/plugins/featureFlags/flagStore.js.map +1 -0
  41. package/lib/commonjs/plugins/navigationInspector/NavigationInspectorPlugin.js +250 -0
  42. package/lib/commonjs/plugins/navigationInspector/NavigationInspectorPlugin.js.map +1 -0
  43. package/lib/commonjs/plugins/navigationInspector/navigationStore.js +65 -0
  44. package/lib/commonjs/plugins/navigationInspector/navigationStore.js.map +1 -0
  45. package/lib/commonjs/plugins/network/NetworkInspectorPlugin.js +709 -0
  46. package/lib/commonjs/plugins/network/NetworkInspectorPlugin.js.map +1 -0
  47. package/lib/commonjs/plugins/network/NetworkInterceptor.js +305 -0
  48. package/lib/commonjs/plugins/network/NetworkInterceptor.js.map +1 -0
  49. package/lib/commonjs/plugins/performance/PerformanceMonitorPlugin.js +261 -0
  50. package/lib/commonjs/plugins/performance/PerformanceMonitorPlugin.js.map +1 -0
  51. package/lib/commonjs/plugins/remoteConfig/RemoteConfigPlugin.js +265 -0
  52. package/lib/commonjs/plugins/remoteConfig/RemoteConfigPlugin.js.map +1 -0
  53. package/lib/commonjs/plugins/remoteConfig/remoteConfigStore.js +50 -0
  54. package/lib/commonjs/plugins/remoteConfig/remoteConfigStore.js.map +1 -0
  55. package/lib/commonjs/plugins/stateInspector/StateInspectorPlugin.js +378 -0
  56. package/lib/commonjs/plugins/stateInspector/StateInspectorPlugin.js.map +1 -0
  57. package/lib/commonjs/plugins/stateInspector/stateAdapterRegistry.js +62 -0
  58. package/lib/commonjs/plugins/stateInspector/stateAdapterRegistry.js.map +1 -0
  59. package/lib/commonjs/plugins/storageBrowser/StorageBrowserPlugin.js +496 -0
  60. package/lib/commonjs/plugins/storageBrowser/StorageBrowserPlugin.js.map +1 -0
  61. package/lib/commonjs/plugins/storageBrowser/storageAdapterRegistry.js +44 -0
  62. package/lib/commonjs/plugins/storageBrowser/storageAdapterRegistry.js.map +1 -0
  63. package/lib/commonjs/plugins/timeline/TimelinePlugin.js +294 -0
  64. package/lib/commonjs/plugins/timeline/TimelinePlugin.js.map +1 -0
  65. package/lib/commonjs/plugins/timeline/timelineStore.js +61 -0
  66. package/lib/commonjs/plugins/timeline/timelineStore.js.map +1 -0
  67. package/lib/module/core/DebugBubble.js +131 -0
  68. package/lib/module/core/DebugBubble.js.map +1 -0
  69. package/lib/module/core/DebugOverlay.js +176 -0
  70. package/lib/module/core/DebugOverlay.js.map +1 -0
  71. package/lib/module/core/DebuggerProvider.js +167 -0
  72. package/lib/module/core/DebuggerProvider.js.map +1 -0
  73. package/lib/module/core/GestureDetector.js +75 -0
  74. package/lib/module/core/GestureDetector.js.map +1 -0
  75. package/lib/module/core/PluginRegistry.js +116 -0
  76. package/lib/module/core/PluginRegistry.js.map +1 -0
  77. package/lib/module/core/theme.js +43 -0
  78. package/lib/module/core/theme.js.map +1 -0
  79. package/lib/module/core/types.js +2 -0
  80. package/lib/module/core/types.js.map +1 -0
  81. package/lib/module/core/useDebugger.js +21 -0
  82. package/lib/module/core/useDebugger.js.map +1 -0
  83. package/lib/module/core/utils.js +219 -0
  84. package/lib/module/core/utils.js.map +1 -0
  85. package/lib/module/index.js +44 -0
  86. package/lib/module/index.js.map +1 -0
  87. package/lib/module/plugins/console/ConsoleInterceptor.js +97 -0
  88. package/lib/module/plugins/console/ConsoleInterceptor.js.map +1 -0
  89. package/lib/module/plugins/console/ConsoleViewerPlugin.js +262 -0
  90. package/lib/module/plugins/console/ConsoleViewerPlugin.js.map +1 -0
  91. package/lib/module/plugins/crashReporter/CrashReporterPlugin.js +357 -0
  92. package/lib/module/plugins/crashReporter/CrashReporterPlugin.js.map +1 -0
  93. package/lib/module/plugins/customActions/CustomActionsPlugin.js +211 -0
  94. package/lib/module/plugins/customActions/CustomActionsPlugin.js.map +1 -0
  95. package/lib/module/plugins/customActions/actionStore.js +38 -0
  96. package/lib/module/plugins/customActions/actionStore.js.map +1 -0
  97. package/lib/module/plugins/deepLinkTester/DeepLinkTesterPlugin.js +261 -0
  98. package/lib/module/plugins/deepLinkTester/DeepLinkTesterPlugin.js.map +1 -0
  99. package/lib/module/plugins/deviceInfo/DeviceInfoPlugin.js +177 -0
  100. package/lib/module/plugins/deviceInfo/DeviceInfoPlugin.js.map +1 -0
  101. package/lib/module/plugins/featureFlags/FeatureFlagsPlugin.js +374 -0
  102. package/lib/module/plugins/featureFlags/FeatureFlagsPlugin.js.map +1 -0
  103. package/lib/module/plugins/featureFlags/flagStore.js +117 -0
  104. package/lib/module/plugins/featureFlags/flagStore.js.map +1 -0
  105. package/lib/module/plugins/navigationInspector/NavigationInspectorPlugin.js +243 -0
  106. package/lib/module/plugins/navigationInspector/NavigationInspectorPlugin.js.map +1 -0
  107. package/lib/module/plugins/navigationInspector/navigationStore.js +58 -0
  108. package/lib/module/plugins/navigationInspector/navigationStore.js.map +1 -0
  109. package/lib/module/plugins/network/NetworkInspectorPlugin.js +703 -0
  110. package/lib/module/plugins/network/NetworkInspectorPlugin.js.map +1 -0
  111. package/lib/module/plugins/network/NetworkInterceptor.js +299 -0
  112. package/lib/module/plugins/network/NetworkInterceptor.js.map +1 -0
  113. package/lib/module/plugins/performance/PerformanceMonitorPlugin.js +254 -0
  114. package/lib/module/plugins/performance/PerformanceMonitorPlugin.js.map +1 -0
  115. package/lib/module/plugins/remoteConfig/RemoteConfigPlugin.js +258 -0
  116. package/lib/module/plugins/remoteConfig/RemoteConfigPlugin.js.map +1 -0
  117. package/lib/module/plugins/remoteConfig/remoteConfigStore.js +43 -0
  118. package/lib/module/plugins/remoteConfig/remoteConfigStore.js.map +1 -0
  119. package/lib/module/plugins/stateInspector/StateInspectorPlugin.js +372 -0
  120. package/lib/module/plugins/stateInspector/StateInspectorPlugin.js.map +1 -0
  121. package/lib/module/plugins/stateInspector/stateAdapterRegistry.js +54 -0
  122. package/lib/module/plugins/stateInspector/stateAdapterRegistry.js.map +1 -0
  123. package/lib/module/plugins/storageBrowser/StorageBrowserPlugin.js +489 -0
  124. package/lib/module/plugins/storageBrowser/StorageBrowserPlugin.js.map +1 -0
  125. package/lib/module/plugins/storageBrowser/storageAdapterRegistry.js +36 -0
  126. package/lib/module/plugins/storageBrowser/storageAdapterRegistry.js.map +1 -0
  127. package/lib/module/plugins/timeline/TimelinePlugin.js +287 -0
  128. package/lib/module/plugins/timeline/TimelinePlugin.js.map +1 -0
  129. package/lib/module/plugins/timeline/timelineStore.js +54 -0
  130. package/lib/module/plugins/timeline/timelineStore.js.map +1 -0
  131. package/lib/typescript/core/DebugBubble.d.ts +22 -0
  132. package/lib/typescript/core/DebugBubble.d.ts.map +1 -0
  133. package/lib/typescript/core/DebugOverlay.d.ts +18 -0
  134. package/lib/typescript/core/DebugOverlay.d.ts.map +1 -0
  135. package/lib/typescript/core/DebuggerProvider.d.ts +25 -0
  136. package/lib/typescript/core/DebuggerProvider.d.ts.map +1 -0
  137. package/lib/typescript/core/GestureDetector.d.ts +20 -0
  138. package/lib/typescript/core/GestureDetector.d.ts.map +1 -0
  139. package/lib/typescript/core/PluginRegistry.d.ts +41 -0
  140. package/lib/typescript/core/PluginRegistry.d.ts.map +1 -0
  141. package/lib/typescript/core/theme.d.ts +11 -0
  142. package/lib/typescript/core/theme.d.ts.map +1 -0
  143. package/lib/typescript/core/types.d.ts +269 -0
  144. package/lib/typescript/core/types.d.ts.map +1 -0
  145. package/lib/typescript/core/useDebugger.d.ts +14 -0
  146. package/lib/typescript/core/useDebugger.d.ts.map +1 -0
  147. package/lib/typescript/core/utils.d.ts +46 -0
  148. package/lib/typescript/core/utils.d.ts.map +1 -0
  149. package/lib/typescript/index.d.ts +23 -0
  150. package/lib/typescript/index.d.ts.map +1 -0
  151. package/lib/typescript/plugins/console/ConsoleInterceptor.d.ts +32 -0
  152. package/lib/typescript/plugins/console/ConsoleInterceptor.d.ts.map +1 -0
  153. package/lib/typescript/plugins/console/ConsoleViewerPlugin.d.ts +3 -0
  154. package/lib/typescript/plugins/console/ConsoleViewerPlugin.d.ts.map +1 -0
  155. package/lib/typescript/plugins/crashReporter/CrashReporterPlugin.d.ts +3 -0
  156. package/lib/typescript/plugins/crashReporter/CrashReporterPlugin.d.ts.map +1 -0
  157. package/lib/typescript/plugins/customActions/CustomActionsPlugin.d.ts +3 -0
  158. package/lib/typescript/plugins/customActions/CustomActionsPlugin.d.ts.map +1 -0
  159. package/lib/typescript/plugins/customActions/actionStore.d.ts +16 -0
  160. package/lib/typescript/plugins/customActions/actionStore.d.ts.map +1 -0
  161. package/lib/typescript/plugins/deepLinkTester/DeepLinkTesterPlugin.d.ts +3 -0
  162. package/lib/typescript/plugins/deepLinkTester/DeepLinkTesterPlugin.d.ts.map +1 -0
  163. package/lib/typescript/plugins/deviceInfo/DeviceInfoPlugin.d.ts +3 -0
  164. package/lib/typescript/plugins/deviceInfo/DeviceInfoPlugin.d.ts.map +1 -0
  165. package/lib/typescript/plugins/featureFlags/FeatureFlagsPlugin.d.ts +3 -0
  166. package/lib/typescript/plugins/featureFlags/FeatureFlagsPlugin.d.ts.map +1 -0
  167. package/lib/typescript/plugins/featureFlags/flagStore.d.ts +45 -0
  168. package/lib/typescript/plugins/featureFlags/flagStore.d.ts.map +1 -0
  169. package/lib/typescript/plugins/navigationInspector/NavigationInspectorPlugin.d.ts +3 -0
  170. package/lib/typescript/plugins/navigationInspector/NavigationInspectorPlugin.d.ts.map +1 -0
  171. package/lib/typescript/plugins/navigationInspector/navigationStore.d.ts +30 -0
  172. package/lib/typescript/plugins/navigationInspector/navigationStore.d.ts.map +1 -0
  173. package/lib/typescript/plugins/network/NetworkInspectorPlugin.d.ts +3 -0
  174. package/lib/typescript/plugins/network/NetworkInspectorPlugin.d.ts.map +1 -0
  175. package/lib/typescript/plugins/network/NetworkInterceptor.d.ts +42 -0
  176. package/lib/typescript/plugins/network/NetworkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/plugins/performance/PerformanceMonitorPlugin.d.ts +3 -0
  178. package/lib/typescript/plugins/performance/PerformanceMonitorPlugin.d.ts.map +1 -0
  179. package/lib/typescript/plugins/remoteConfig/RemoteConfigPlugin.d.ts +3 -0
  180. package/lib/typescript/plugins/remoteConfig/RemoteConfigPlugin.d.ts.map +1 -0
  181. package/lib/typescript/plugins/remoteConfig/remoteConfigStore.d.ts +20 -0
  182. package/lib/typescript/plugins/remoteConfig/remoteConfigStore.d.ts.map +1 -0
  183. package/lib/typescript/plugins/stateInspector/StateInspectorPlugin.d.ts +3 -0
  184. package/lib/typescript/plugins/stateInspector/StateInspectorPlugin.d.ts.map +1 -0
  185. package/lib/typescript/plugins/stateInspector/stateAdapterRegistry.d.ts +34 -0
  186. package/lib/typescript/plugins/stateInspector/stateAdapterRegistry.d.ts.map +1 -0
  187. package/lib/typescript/plugins/storageBrowser/StorageBrowserPlugin.d.ts +3 -0
  188. package/lib/typescript/plugins/storageBrowser/StorageBrowserPlugin.d.ts.map +1 -0
  189. package/lib/typescript/plugins/storageBrowser/storageAdapterRegistry.d.ts +16 -0
  190. package/lib/typescript/plugins/storageBrowser/storageAdapterRegistry.d.ts.map +1 -0
  191. package/lib/typescript/plugins/timeline/TimelinePlugin.d.ts +3 -0
  192. package/lib/typescript/plugins/timeline/TimelinePlugin.d.ts.map +1 -0
  193. package/lib/typescript/plugins/timeline/timelineStore.d.ts +24 -0
  194. package/lib/typescript/plugins/timeline/timelineStore.d.ts.map +1 -0
  195. package/package.json +131 -0
  196. package/src/core/DebugBubble.tsx +149 -0
  197. package/src/core/DebugOverlay.tsx +211 -0
  198. package/src/core/DebuggerProvider.tsx +211 -0
  199. package/src/core/GestureDetector.tsx +95 -0
  200. package/src/core/PluginRegistry.ts +118 -0
  201. package/src/core/theme.ts +41 -0
  202. package/src/core/types.ts +339 -0
  203. package/src/core/useDebugger.ts +24 -0
  204. package/src/core/utils.ts +221 -0
  205. package/src/index.ts +67 -0
  206. package/src/plugins/console/ConsoleInterceptor.ts +110 -0
  207. package/src/plugins/console/ConsoleViewerPlugin.tsx +241 -0
  208. package/src/plugins/crashReporter/CrashReporterPlugin.tsx +316 -0
  209. package/src/plugins/customActions/CustomActionsPlugin.tsx +199 -0
  210. package/src/plugins/customActions/actionStore.ts +49 -0
  211. package/src/plugins/deepLinkTester/DeepLinkTesterPlugin.tsx +213 -0
  212. package/src/plugins/deviceInfo/DeviceInfoPlugin.tsx +153 -0
  213. package/src/plugins/featureFlags/FeatureFlagsPlugin.tsx +338 -0
  214. package/src/plugins/featureFlags/flagStore.ts +118 -0
  215. package/src/plugins/navigationInspector/NavigationInspectorPlugin.tsx +170 -0
  216. package/src/plugins/navigationInspector/navigationStore.ts +70 -0
  217. package/src/plugins/network/NetworkInspectorPlugin.tsx +598 -0
  218. package/src/plugins/network/NetworkInterceptor.ts +344 -0
  219. package/src/plugins/performance/PerformanceMonitorPlugin.tsx +194 -0
  220. package/src/plugins/remoteConfig/RemoteConfigPlugin.tsx +205 -0
  221. package/src/plugins/remoteConfig/remoteConfigStore.ts +48 -0
  222. package/src/plugins/stateInspector/StateInspectorPlugin.tsx +342 -0
  223. package/src/plugins/stateInspector/stateAdapterRegistry.ts +66 -0
  224. package/src/plugins/storageBrowser/StorageBrowserPlugin.tsx +410 -0
  225. package/src/plugins/storageBrowser/storageAdapterRegistry.ts +47 -0
  226. package/src/plugins/timeline/TimelinePlugin.tsx +242 -0
  227. package/src/plugins/timeline/timelineStore.ts +65 -0
@@ -0,0 +1,344 @@
1
+ import type { NetworkRequest } from '../../core/types';
2
+ import { generateId, extractGraphQLInfo } from '../../core/utils';
3
+
4
+ type NetworkListener = (requests: NetworkRequest[]) => void;
5
+
6
+ /**
7
+ * NetworkInterceptor — Intercepts XMLHttpRequest and fetch to capture network traffic.
8
+ *
9
+ * Uses monkey-patching to wrap the global XHR and fetch implementations.
10
+ * Stores request/response data in an in-memory ring buffer.
11
+ * Thread-safe listener management for UI updates.
12
+ */
13
+ class NetworkInterceptorClass {
14
+ private requests: NetworkRequest[] = [];
15
+ private listeners: Set<NetworkListener> = new Set();
16
+ private maxRequests: number = 500;
17
+ private isActive: boolean = false;
18
+
19
+ // Stored originals for restoring
20
+ private originalXHROpen: typeof XMLHttpRequest.prototype.open | null = null;
21
+ private originalXHRSend: typeof XMLHttpRequest.prototype.send | null = null;
22
+ private originalXHRSetHeader: typeof XMLHttpRequest.prototype.setRequestHeader | null = null;
23
+ private originalFetch: typeof globalThis.fetch | null = null;
24
+
25
+ /** Start intercepting network requests. */
26
+ start(maxRequests: number = 500, forceEnable: boolean = false): void {
27
+ if (this.isActive && !forceEnable) return;
28
+ this.maxRequests = maxRequests;
29
+ this.isActive = true;
30
+ this.interceptXHR();
31
+ this.interceptFetch();
32
+ }
33
+
34
+ /** Stop intercepting and restore original implementations. */
35
+ stop(): void {
36
+ this.isActive = false;
37
+ this.restoreXHR();
38
+ this.restoreFetch();
39
+ }
40
+
41
+ /** Subscribe to request changes. Returns unsubscribe function. */
42
+ subscribe(listener: NetworkListener): () => void {
43
+ this.listeners.add(listener);
44
+ // Immediately notify with current data
45
+ listener(this.requests);
46
+ return () => this.listeners.delete(listener);
47
+ }
48
+
49
+ /** Get all captured requests. */
50
+ getAll(): NetworkRequest[] {
51
+ return [...this.requests];
52
+ }
53
+
54
+ /** Clear all captured requests. */
55
+ clear(): void {
56
+ this.requests = [];
57
+ this.notify();
58
+ }
59
+
60
+ /** Check if the interceptor is active. */
61
+ get active(): boolean {
62
+ return this.isActive;
63
+ }
64
+
65
+ // ─── XHR Interception ──────────────────────────────────────────────────
66
+
67
+ private interceptXHR(): void {
68
+ const self = this;
69
+ const OriginalXHR = XMLHttpRequest;
70
+
71
+ // Store originals
72
+ this.originalXHROpen = OriginalXHR.prototype.open;
73
+ this.originalXHRSend = OriginalXHR.prototype.send;
74
+ this.originalXHRSetHeader = OriginalXHR.prototype.setRequestHeader;
75
+
76
+ // Monkey-patch open
77
+ OriginalXHR.prototype.open = function (method: string, url: string | URL, ...rest: unknown[]) {
78
+ const xhr = this as XMLHttpRequest & {
79
+ _debugId: string;
80
+ _method: string;
81
+ _url: string;
82
+ _requestHeaders: Record<string, string>;
83
+ };
84
+
85
+ xhr._debugId = generateId();
86
+ xhr._method = method;
87
+ xhr._url = typeof url === 'string' ? url : url.toString();
88
+ xhr._requestHeaders = {};
89
+
90
+ return self.originalXHROpen!.apply(this, [method, url, ...rest] as Parameters<
91
+ typeof XMLHttpRequest.prototype.open
92
+ >);
93
+ };
94
+
95
+ // Monkey-patch setRequestHeader
96
+ OriginalXHR.prototype.setRequestHeader = function (name: string, value: string) {
97
+ const xhr = this as XMLHttpRequest & {
98
+ _requestHeaders: Record<string, string>;
99
+ };
100
+ if (xhr._requestHeaders) {
101
+ xhr._requestHeaders[name] = value;
102
+ }
103
+ return self.originalXHRSetHeader!.apply(this, [name, value]);
104
+ };
105
+
106
+ // Monkey-patch send
107
+ OriginalXHR.prototype.send = function (body?: string | Blob | ArrayBuffer | FormData | null) {
108
+ const xhr = this as XMLHttpRequest & {
109
+ _debugId: string;
110
+ _method: string;
111
+ _url: string;
112
+ _requestHeaders: Record<string, string>;
113
+ };
114
+
115
+ const startTime = Date.now();
116
+ const requestBody = typeof body === 'string' ? body : body ? String(body) : undefined;
117
+
118
+ // Extract GraphQL info
119
+ const gqlInfo = extractGraphQLInfo(requestBody);
120
+
121
+ const request: NetworkRequest = {
122
+ id: xhr._debugId,
123
+ method: xhr._method,
124
+ url: xhr._url,
125
+ startTime,
126
+ requestHeaders: { ...xhr._requestHeaders },
127
+ requestBody,
128
+ requestSize: requestBody ? new Blob([requestBody]).size : 0,
129
+ gqlOperation: gqlInfo?.operation,
130
+ gqlType: gqlInfo?.type,
131
+ };
132
+
133
+ self.addRequest(request);
134
+
135
+ // Listen for completion
136
+ const originalOnReadyStateChange = xhr.onreadystatechange;
137
+ xhr.onreadystatechange = function (...args: unknown[]) {
138
+ if (xhr.readyState === XMLHttpRequest.DONE) {
139
+ const endTime = Date.now();
140
+ let responseBody: string | undefined;
141
+ try {
142
+ responseBody = xhr.responseText;
143
+ } catch {
144
+ responseBody = '[Unable to read response]';
145
+ }
146
+
147
+ // Parse response headers
148
+ const rawHeaders = xhr.getAllResponseHeaders();
149
+ const responseHeaders: Record<string, string> = {};
150
+ if (rawHeaders) {
151
+ rawHeaders.split('\r\n').forEach((line) => {
152
+ const idx = line.indexOf(':');
153
+ if (idx > 0) {
154
+ responseHeaders[line.substring(0, idx).trim()] = line.substring(idx + 1).trim();
155
+ }
156
+ });
157
+ }
158
+
159
+ self.updateRequest(xhr._debugId, {
160
+ endTime,
161
+ duration: endTime - startTime,
162
+ status: xhr.status,
163
+ responseHeaders,
164
+ responseBody,
165
+ responseContentType: responseHeaders['content-type'] || responseHeaders['Content-Type'],
166
+ responseSize: responseBody ? new Blob([responseBody]).size : 0,
167
+ isError: xhr.status >= 400 || xhr.status === 0,
168
+ errorMessage: xhr.status === 0 ? 'Network Error' : undefined,
169
+ });
170
+ }
171
+
172
+ if (typeof originalOnReadyStateChange === 'function') {
173
+ originalOnReadyStateChange.apply(xhr, args as [Event]);
174
+ }
175
+ };
176
+
177
+ // Error handler
178
+ const originalOnError = xhr.onerror;
179
+ xhr.onerror = function (...args: unknown[]) {
180
+ self.updateRequest(xhr._debugId, {
181
+ endTime: Date.now(),
182
+ duration: Date.now() - startTime,
183
+ isError: true,
184
+ errorMessage: 'Network Error',
185
+ });
186
+ if (typeof originalOnError === 'function') {
187
+ originalOnError.apply(xhr, args as [ProgressEvent<EventTarget>]);
188
+ }
189
+ };
190
+
191
+ return self.originalXHRSend!.apply(this, [body]);
192
+ };
193
+ }
194
+
195
+ private restoreXHR(): void {
196
+ if (this.originalXHROpen) {
197
+ XMLHttpRequest.prototype.open = this.originalXHROpen;
198
+ this.originalXHROpen = null;
199
+ }
200
+ if (this.originalXHRSend) {
201
+ XMLHttpRequest.prototype.send = this.originalXHRSend;
202
+ this.originalXHRSend = null;
203
+ }
204
+ if (this.originalXHRSetHeader) {
205
+ XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetHeader;
206
+ this.originalXHRSetHeader = null;
207
+ }
208
+ }
209
+
210
+ // ─── Fetch Interception ────────────────────────────────────────────────
211
+
212
+ private interceptFetch(): void {
213
+ const self = this;
214
+ if (typeof globalThis.fetch !== 'function') return;
215
+
216
+ this.originalFetch = globalThis.fetch;
217
+
218
+ globalThis.fetch = async function (
219
+ input: RequestInfo | URL,
220
+ init?: RequestInit,
221
+ ): Promise<Response> {
222
+ const id = generateId();
223
+ const startTime = Date.now();
224
+
225
+ // Extract request info
226
+ const url =
227
+ typeof input === 'string'
228
+ ? input
229
+ : input instanceof URL
230
+ ? input.toString()
231
+ : (input as Request).url;
232
+
233
+ const method = init?.method || (input instanceof Request ? input.method : 'GET');
234
+
235
+ const requestHeaders: Record<string, string> = {};
236
+ if (init?.headers) {
237
+ if (init.headers instanceof Headers) {
238
+ init.headers.forEach((value: string, key: string) => {
239
+ requestHeaders[key] = value;
240
+ });
241
+ } else if (Array.isArray(init.headers)) {
242
+ init.headers.forEach(([key, value]) => {
243
+ requestHeaders[key] = value;
244
+ });
245
+ } else {
246
+ Object.assign(requestHeaders, init.headers);
247
+ }
248
+ }
249
+
250
+ const requestBody =
251
+ typeof init?.body === 'string' ? init.body : init?.body ? String(init.body) : undefined;
252
+
253
+ const gqlInfo = extractGraphQLInfo(requestBody);
254
+
255
+ const request: NetworkRequest = {
256
+ id,
257
+ method: method.toUpperCase(),
258
+ url,
259
+ startTime,
260
+ requestHeaders,
261
+ requestBody,
262
+ requestSize: requestBody ? new Blob([requestBody]).size : 0,
263
+ gqlOperation: gqlInfo?.operation,
264
+ gqlType: gqlInfo?.type,
265
+ };
266
+
267
+ self.addRequest(request);
268
+
269
+ try {
270
+ const response = await self.originalFetch!.call(globalThis, input as RequestInfo, init);
271
+ const endTime = Date.now();
272
+
273
+ // Clone response so the caller can still read it
274
+ const clone = response.clone();
275
+ let responseBody: string | undefined;
276
+ try {
277
+ responseBody = await clone.text();
278
+ } catch {
279
+ responseBody = '[Unable to read response body]';
280
+ }
281
+
282
+ const responseHeaders: Record<string, string> = {};
283
+ response.headers.forEach((value: string, key: string) => {
284
+ responseHeaders[key] = value;
285
+ });
286
+
287
+ self.updateRequest(id, {
288
+ endTime,
289
+ duration: endTime - startTime,
290
+ status: response.status,
291
+ responseHeaders,
292
+ responseBody,
293
+ responseContentType: responseHeaders['content-type'],
294
+ responseSize: responseBody ? new Blob([responseBody]).size : 0,
295
+ isError: response.status >= 400,
296
+ });
297
+
298
+ return response;
299
+ } catch (error) {
300
+ const endTime = Date.now();
301
+ self.updateRequest(id, {
302
+ endTime,
303
+ duration: endTime - startTime,
304
+ isError: true,
305
+ errorMessage: error instanceof Error ? error.message : 'Network Error',
306
+ });
307
+ throw error;
308
+ }
309
+ };
310
+ }
311
+
312
+ private restoreFetch(): void {
313
+ if (this.originalFetch) {
314
+ globalThis.fetch = this.originalFetch;
315
+ this.originalFetch = null;
316
+ }
317
+ }
318
+
319
+ // ─── Internal ──────────────────────────────────────────────────────────
320
+
321
+ private addRequest(request: NetworkRequest): void {
322
+ this.requests = [request, ...this.requests].slice(0, this.maxRequests);
323
+ this.notify();
324
+ }
325
+
326
+ private updateRequest(id: string, update: Partial<NetworkRequest>): void {
327
+ this.requests = this.requests.map((r) => (r.id === id ? { ...r, ...update } : r));
328
+ this.notify();
329
+ }
330
+
331
+ private notify(): void {
332
+ const snapshot = this.requests;
333
+ for (const listener of this.listeners) {
334
+ try {
335
+ listener(snapshot);
336
+ } catch {
337
+ // Silently handle listener errors
338
+ }
339
+ }
340
+ }
341
+ }
342
+
343
+ /** Singleton network interceptor instance. */
344
+ export const NetworkInterceptor = new NetworkInterceptorClass();
@@ -0,0 +1,194 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import type { PluginComponentProps, DebuggerPlugin } from '../../core/types';
4
+
5
+ // React Native may or may not expose the Performance API depending on engine
6
+ const _global = globalThis as Record<string, unknown>;
7
+ const perf: { now: () => number } =
8
+ typeof _global.performance !== 'undefined'
9
+ ? (_global.performance as { now: () => number })
10
+ : { now: () => Date.now() };
11
+
12
+ const PerformanceMonitorPanel: React.FC<PluginComponentProps> = ({ theme }) => {
13
+ const [fps, setFps] = useState(0);
14
+ const [avgFps, setAvgFps] = useState(0);
15
+ const [minFps, setMinFps] = useState(60);
16
+ const [maxFps, setMaxFps] = useState(0);
17
+ const [frameCount, setFrameCount] = useState(0);
18
+ const [jsDuration, setJsDuration] = useState(0);
19
+ const [memoryUsage, setMemoryUsage] = useState<number | null>(null);
20
+ const [uptime, setUptime] = useState(0);
21
+
22
+ const frameRef = useRef(0);
23
+ const lastTimeRef = useRef(perf.now());
24
+ const fpsHistoryRef = useRef<number[]>([]);
25
+ const rafRef = useRef<number>(0);
26
+ const startTimeRef = useRef(Date.now());
27
+ const totalFramesRef = useRef(0);
28
+
29
+ useEffect(() => {
30
+ let active = true;
31
+
32
+ const measureFrame = () => {
33
+ if (!active) return;
34
+
35
+ const now = perf.now();
36
+ const delta = now - lastTimeRef.current;
37
+
38
+ frameRef.current++;
39
+ totalFramesRef.current++;
40
+
41
+ if (delta >= 1000) {
42
+ const currentFps = Math.round((frameRef.current / delta) * 1000);
43
+ frameRef.current = 0;
44
+ lastTimeRef.current = now;
45
+
46
+ fpsHistoryRef.current.push(currentFps);
47
+ if (fpsHistoryRef.current.length > 60) fpsHistoryRef.current.shift();
48
+
49
+ const avg = Math.round(
50
+ fpsHistoryRef.current.reduce((a, b) => a + b, 0) / fpsHistoryRef.current.length,
51
+ );
52
+
53
+ setFps(currentFps);
54
+ setAvgFps(avg);
55
+ setMinFps((prev) => Math.min(prev, currentFps));
56
+ setMaxFps((prev) => Math.max(prev, currentFps));
57
+ setFrameCount(totalFramesRef.current);
58
+ setUptime(Math.floor((Date.now() - startTimeRef.current) / 1000));
59
+
60
+ // JS thread timing estimate
61
+ const jsStart = perf.now();
62
+ setTimeout(() => {
63
+ const jsEnd = perf.now();
64
+ const delay = jsEnd - jsStart;
65
+ setJsDuration(Math.round(delay));
66
+ }, 0);
67
+
68
+ // Memory (where available)
69
+ try {
70
+ const perfMemory = (
71
+ _global.performance as { memory?: { usedJSHeapSize?: number } } | undefined
72
+ )?.memory;
73
+ if (perfMemory?.usedJSHeapSize) {
74
+ setMemoryUsage(perfMemory.usedJSHeapSize);
75
+ }
76
+ } catch {
77
+ // Not available in all environments
78
+ }
79
+ }
80
+
81
+ rafRef.current = requestAnimationFrame(measureFrame);
82
+ };
83
+
84
+ rafRef.current = requestAnimationFrame(measureFrame);
85
+ return () => {
86
+ active = false;
87
+ cancelAnimationFrame(rafRef.current);
88
+ };
89
+ }, []);
90
+
91
+ const fpsColor = fps >= 50 ? theme.success : fps >= 30 ? theme.warning : theme.error;
92
+ const jsDurationColor =
93
+ jsDuration <= 16 ? theme.success : jsDuration <= 32 ? theme.warning : theme.error;
94
+
95
+ return (
96
+ <View style={styles.container}>
97
+ {/* FPS Hero */}
98
+ <View style={[styles.heroSection, { backgroundColor: theme.surface }]}>
99
+ <Text style={[styles.heroValue, { color: fpsColor }]}>{fps}</Text>
100
+ <Text style={[styles.heroLabel, { color: theme.textSecondary }]}>FPS</Text>
101
+ </View>
102
+
103
+ {/* Metrics Grid */}
104
+ <View style={styles.metricsGrid}>
105
+ <MetricCard label="Avg FPS" value={String(avgFps)} color={theme.info} theme={theme} />
106
+ <MetricCard label="Min FPS" value={String(minFps)} color={theme.error} theme={theme} />
107
+ <MetricCard label="Max FPS" value={String(maxFps)} color={theme.success} theme={theme} />
108
+ <MetricCard
109
+ label="JS Thread"
110
+ value={`${jsDuration}ms`}
111
+ color={jsDurationColor}
112
+ theme={theme}
113
+ />
114
+ <MetricCard
115
+ label="Memory"
116
+ value={memoryUsage ? `${(memoryUsage / 1024 / 1024).toFixed(1)}MB` : 'N/A'}
117
+ color={theme.accent}
118
+ theme={theme}
119
+ />
120
+ <MetricCard
121
+ label="Frames"
122
+ value={String(frameCount)}
123
+ color={theme.textSecondary}
124
+ theme={theme}
125
+ />
126
+ </View>
127
+
128
+ {/* FPS Bar Chart */}
129
+ <View style={[styles.chartSection, { backgroundColor: theme.surface }]}>
130
+ <Text style={[styles.chartTitle, { color: theme.text }]}>FPS History (last 60s)</Text>
131
+ <View style={styles.chartContainer}>
132
+ {fpsHistoryRef.current.slice(-30).map((val, i) => {
133
+ const height = Math.max(4, (val / 60) * 80);
134
+ const barColor = val >= 50 ? theme.success : val >= 30 ? theme.warning : theme.error;
135
+ return (
136
+ <View key={i} style={[styles.chartBar, { height, backgroundColor: barColor }]} />
137
+ );
138
+ })}
139
+ </View>
140
+ </View>
141
+
142
+ {/* Info */}
143
+ <View style={[styles.infoSection, { backgroundColor: theme.surface }]}>
144
+ <Text style={[styles.infoText, { color: theme.textMuted }]}>
145
+ Uptime: {Math.floor(uptime / 60)}m {uptime % 60}s
146
+ </Text>
147
+ <Text style={[styles.infoText, { color: theme.textMuted }]}>
148
+ Target: 60 FPS (16.67ms per frame)
149
+ </Text>
150
+ </View>
151
+ </View>
152
+ );
153
+ };
154
+
155
+ interface MetricCardProps {
156
+ label: string;
157
+ value: string;
158
+ color: string;
159
+ theme: PluginComponentProps['theme'];
160
+ }
161
+
162
+ const MetricCard: React.FC<MetricCardProps> = ({ label, value, color, theme }) => (
163
+ <View style={[styles.metricCard, { backgroundColor: theme.surfaceAlt }]}>
164
+ <Text style={[styles.metricValue, { color }]}>{value}</Text>
165
+ <Text style={[styles.metricLabel, { color: theme.textMuted }]}>{label}</Text>
166
+ </View>
167
+ );
168
+
169
+ const styles = StyleSheet.create({
170
+ container: { flex: 1 },
171
+ heroSection: { alignItems: 'center', paddingVertical: 24 },
172
+ heroValue: { fontSize: 64, fontWeight: '900', letterSpacing: -2 },
173
+ heroLabel: { fontSize: 14, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 2 },
174
+ metricsGrid: { flexDirection: 'row', flexWrap: 'wrap', padding: 8 },
175
+ metricCard: { width: '30%', margin: '1.5%', padding: 12, borderRadius: 12, alignItems: 'center' },
176
+ metricValue: { fontSize: 18, fontWeight: '800' },
177
+ metricLabel: { fontSize: 10, fontWeight: '600', marginTop: 4, textTransform: 'uppercase' },
178
+ chartSection: { margin: 12, padding: 12, borderRadius: 12 },
179
+ chartTitle: { fontSize: 12, fontWeight: '700', marginBottom: 8 },
180
+ chartContainer: { flexDirection: 'row', alignItems: 'flex-end', height: 80, gap: 2 },
181
+ chartBar: { flex: 1, borderRadius: 2, minWidth: 3 },
182
+ infoSection: { margin: 12, padding: 12, borderRadius: 12 },
183
+ infoText: { fontSize: 11, lineHeight: 18 },
184
+ });
185
+
186
+ export function createPerformanceMonitorPlugin(): DebuggerPlugin {
187
+ return {
188
+ id: 'performance-monitor',
189
+ name: 'Perf',
190
+ icon: '⚡',
191
+ component: PerformanceMonitorPanel,
192
+ order: 70,
193
+ };
194
+ }