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,110 @@
1
+ import type { ConsoleEntry, ConsoleLevel } from '../../core/types';
2
+ import { generateId, safeStringify } from '../../core/utils';
3
+
4
+ type ConsoleListener = (entries: ConsoleEntry[]) => void;
5
+
6
+ /**
7
+ * ConsoleInterceptor — Captures console.log/warn/error/info/debug calls.
8
+ *
9
+ * Wraps the global console methods and stores entries in a ring buffer.
10
+ * Original console behavior is preserved — messages still appear in the developer console.
11
+ */
12
+ class ConsoleInterceptorClass {
13
+ private entries: ConsoleEntry[] = [];
14
+ private listeners: Set<ConsoleListener> = new Set();
15
+ private maxEntries: number = 1000;
16
+ private isActive: boolean = false;
17
+
18
+ private originals: Record<ConsoleLevel, (...args: unknown[]) => void> = {
19
+ log: console.log,
20
+ info: console.info,
21
+ warn: console.warn,
22
+ error: console.error,
23
+ debug: console.debug,
24
+ };
25
+
26
+ /** Start intercepting console calls. */
27
+ start(maxEntries: number = 1000): void {
28
+ if (this.isActive) return;
29
+ this.maxEntries = maxEntries;
30
+ this.isActive = true;
31
+
32
+ const levels: ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];
33
+ for (const level of levels) {
34
+ this.originals[level] = console[level].bind(console);
35
+ console[level] = (...args: unknown[]) => {
36
+ // Preserve original behavior
37
+ this.originals[level](...args);
38
+ // Capture entry
39
+ this.addEntry(level, args);
40
+ };
41
+ }
42
+ }
43
+
44
+ /** Stop intercepting and restore original console. */
45
+ stop(): void {
46
+ if (!this.isActive) return;
47
+ this.isActive = false;
48
+
49
+ const levels: ConsoleLevel[] = ['log', 'info', 'warn', 'error', 'debug'];
50
+ for (const level of levels) {
51
+ console[level] = this.originals[level];
52
+ }
53
+ }
54
+
55
+ /** Subscribe to entry changes. Returns unsubscribe function. */
56
+ subscribe(listener: ConsoleListener): () => void {
57
+ this.listeners.add(listener);
58
+ listener(this.entries);
59
+ return () => this.listeners.delete(listener);
60
+ }
61
+
62
+ /** Get all entries. */
63
+ getAll(): ConsoleEntry[] {
64
+ return [...this.entries];
65
+ }
66
+
67
+ /** Clear all entries. */
68
+ clear(): void {
69
+ this.entries = [];
70
+ this.notify();
71
+ }
72
+
73
+ get active(): boolean {
74
+ return this.isActive;
75
+ }
76
+
77
+ private addEntry(level: ConsoleLevel, args: unknown[]): void {
78
+ const message = args
79
+ .map((arg) => {
80
+ if (typeof arg === 'string') return arg;
81
+ return safeStringify(arg);
82
+ })
83
+ .join(' ');
84
+
85
+ const entry: ConsoleEntry = {
86
+ id: generateId(),
87
+ level,
88
+ timestamp: Date.now(),
89
+ message,
90
+ args,
91
+ };
92
+
93
+ this.entries = [entry, ...this.entries].slice(0, this.maxEntries);
94
+ this.notify();
95
+ }
96
+
97
+ private notify(): void {
98
+ const snapshot = this.entries;
99
+ for (const listener of this.listeners) {
100
+ try {
101
+ listener(snapshot);
102
+ } catch {
103
+ // Silently handle errors
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ /** Singleton console interceptor instance. */
110
+ export const ConsoleInterceptor = new ConsoleInterceptorClass();
@@ -0,0 +1,241 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ FlatList,
6
+ TouchableOpacity,
7
+ TextInput,
8
+ StyleSheet,
9
+ ScrollView,
10
+ } from 'react-native';
11
+ import type {
12
+ PluginComponentProps,
13
+ ConsoleEntry,
14
+ ConsoleLevel,
15
+ DebuggerPlugin,
16
+ } from '../../core/types';
17
+ import { ConsoleInterceptor } from './ConsoleInterceptor';
18
+ import { formatTimestamp, getLevelColor, copyToClipboard } from '../../core/utils';
19
+
20
+ const ConsoleViewerPanel: React.FC<PluginComponentProps> = ({ theme }) => {
21
+ const [entries, setEntries] = useState<ConsoleEntry[]>([]);
22
+ const [searchQuery, setSearchQuery] = useState('');
23
+ const [levelFilter, setLevelFilter] = useState<ConsoleLevel | null>(null);
24
+ const [expandedId, setExpandedId] = useState<string | null>(null);
25
+
26
+ useEffect(() => {
27
+ const unsubscribe = ConsoleInterceptor.subscribe(setEntries);
28
+ return unsubscribe;
29
+ }, []);
30
+
31
+ const filteredEntries = useMemo(() => {
32
+ let filtered = entries;
33
+ if (levelFilter) {
34
+ filtered = filtered.filter((e) => e.level === levelFilter);
35
+ }
36
+ if (searchQuery) {
37
+ const q = searchQuery.toLowerCase();
38
+ filtered = filtered.filter((e) => e.message.toLowerCase().includes(q));
39
+ }
40
+ return filtered;
41
+ }, [entries, searchQuery, levelFilter]);
42
+
43
+ const levelCounts = useMemo(() => {
44
+ const counts = { log: 0, info: 0, warn: 0, error: 0, debug: 0 };
45
+ for (const entry of entries) {
46
+ counts[entry.level]++;
47
+ }
48
+ return counts;
49
+ }, [entries]);
50
+
51
+ const handleClear = useCallback(() => {
52
+ ConsoleInterceptor.clear();
53
+ setExpandedId(null);
54
+ }, []);
55
+
56
+ return (
57
+ <View style={styles.container}>
58
+ {/* Level Filter Bar */}
59
+ <ScrollView
60
+ horizontal
61
+ showsHorizontalScrollIndicator={false}
62
+ style={[styles.filterBar, { backgroundColor: theme.surface }]}
63
+ contentContainerStyle={styles.filterContent}
64
+ >
65
+ <TouchableOpacity
66
+ style={[
67
+ styles.filterChip,
68
+ {
69
+ backgroundColor: !levelFilter ? theme.accent : theme.surfaceAlt,
70
+ borderColor: theme.border,
71
+ },
72
+ ]}
73
+ onPress={() => setLevelFilter(null)}
74
+ activeOpacity={0.7}
75
+ >
76
+ <Text style={[styles.filterText, { color: !levelFilter ? '#FFF' : theme.textSecondary }]}>
77
+ All ({entries.length})
78
+ </Text>
79
+ </TouchableOpacity>
80
+ {(['error', 'warn', 'info', 'log', 'debug'] as ConsoleLevel[]).map((level) => (
81
+ <TouchableOpacity
82
+ key={level}
83
+ style={[
84
+ styles.filterChip,
85
+ {
86
+ backgroundColor: levelFilter === level ? getLevelColor(level) : theme.surfaceAlt,
87
+ borderColor: theme.border,
88
+ },
89
+ ]}
90
+ onPress={() => setLevelFilter(levelFilter === level ? null : level)}
91
+ activeOpacity={0.7}
92
+ >
93
+ <Text
94
+ style={[
95
+ styles.filterText,
96
+ { color: levelFilter === level ? '#FFF' : getLevelColor(level) },
97
+ ]}
98
+ >
99
+ {level} ({levelCounts[level]})
100
+ </Text>
101
+ </TouchableOpacity>
102
+ ))}
103
+ <TouchableOpacity
104
+ onPress={handleClear}
105
+ style={[
106
+ styles.filterChip,
107
+ { backgroundColor: theme.surfaceAlt, borderColor: theme.error },
108
+ ]}
109
+ activeOpacity={0.7}
110
+ >
111
+ <Text style={[styles.filterText, { color: theme.error }]}>Clear</Text>
112
+ </TouchableOpacity>
113
+ </ScrollView>
114
+
115
+ {/* Search */}
116
+ <View style={[styles.searchContainer, { backgroundColor: theme.surface }]}>
117
+ <TextInput
118
+ style={[
119
+ styles.searchInput,
120
+ { color: theme.text, backgroundColor: theme.surfaceAlt, borderColor: theme.border },
121
+ ]}
122
+ placeholder="Search logs..."
123
+ placeholderTextColor={theme.textMuted}
124
+ value={searchQuery}
125
+ onChangeText={setSearchQuery}
126
+ autoCapitalize="none"
127
+ />
128
+ </View>
129
+
130
+ {/* Log List */}
131
+ <FlatList
132
+ data={filteredEntries}
133
+ keyExtractor={(item) => item.id}
134
+ renderItem={({ item }) => (
135
+ <ConsoleRow
136
+ entry={item}
137
+ theme={theme}
138
+ isExpanded={expandedId === item.id}
139
+ onToggle={() => setExpandedId(expandedId === item.id ? null : item.id)}
140
+ />
141
+ )}
142
+ ListEmptyComponent={
143
+ <View style={styles.emptyContainer}>
144
+ <Text style={[styles.emptyText, { color: theme.textMuted }]}>No console output</Text>
145
+ </View>
146
+ }
147
+ initialNumToRender={30}
148
+ maxToRenderPerBatch={15}
149
+ />
150
+ </View>
151
+ );
152
+ };
153
+
154
+ interface ConsoleRowProps {
155
+ entry: ConsoleEntry;
156
+ theme: PluginComponentProps['theme'];
157
+ isExpanded: boolean;
158
+ onToggle: () => void;
159
+ }
160
+
161
+ const ConsoleRow: React.FC<ConsoleRowProps> = React.memo(
162
+ ({ entry, theme, isExpanded, onToggle }) => {
163
+ const levelColor = getLevelColor(entry.level);
164
+ const levelBg = `${levelColor}15`;
165
+
166
+ return (
167
+ <TouchableOpacity
168
+ style={[styles.logRow, { backgroundColor: levelBg, borderBottomColor: theme.border }]}
169
+ onPress={onToggle}
170
+ onLongPress={() => copyToClipboard(entry.message)}
171
+ activeOpacity={0.7}
172
+ >
173
+ <View style={styles.logHeader}>
174
+ <View style={[styles.levelBadge, { backgroundColor: levelColor }]}>
175
+ <Text style={styles.levelText}>{entry.level.toUpperCase()}</Text>
176
+ </View>
177
+ <Text style={[styles.logTimestamp, { color: theme.textMuted }]}>
178
+ {formatTimestamp(entry.timestamp)}
179
+ </Text>
180
+ </View>
181
+ <Text
182
+ style={[styles.logMessage, { color: theme.text }]}
183
+ numberOfLines={isExpanded ? undefined : 3}
184
+ >
185
+ {entry.message}
186
+ </Text>
187
+ {isExpanded && entry.message.length > 100 && (
188
+ <TouchableOpacity
189
+ onPress={() => copyToClipboard(entry.message)}
190
+ style={[styles.copyLogButton, { backgroundColor: theme.surfaceAlt }]}
191
+ activeOpacity={0.7}
192
+ >
193
+ <Text style={[styles.copyLogText, { color: theme.accent }]}>Copy</Text>
194
+ </TouchableOpacity>
195
+ )}
196
+ </TouchableOpacity>
197
+ );
198
+ },
199
+ );
200
+
201
+ const styles = StyleSheet.create({
202
+ container: { flex: 1 },
203
+ filterBar: { maxHeight: 44 },
204
+ filterContent: { paddingHorizontal: 12, paddingVertical: 8, gap: 6 },
205
+ filterChip: { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12, borderWidth: 1 },
206
+ filterText: { fontSize: 11, fontWeight: '600' },
207
+ searchContainer: { paddingHorizontal: 12, paddingBottom: 8 },
208
+ searchInput: { height: 36, borderRadius: 8, paddingHorizontal: 12, fontSize: 13, borderWidth: 1 },
209
+ logRow: {
210
+ paddingHorizontal: 12,
211
+ paddingVertical: 8,
212
+ borderBottomWidth: StyleSheet.hairlineWidth,
213
+ },
214
+ logHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4 },
215
+ levelBadge: { paddingHorizontal: 6, paddingVertical: 1, borderRadius: 4 },
216
+ levelText: { fontSize: 9, fontWeight: '800', color: '#FFF', letterSpacing: 0.5 },
217
+ logTimestamp: { fontSize: 10 },
218
+ logMessage: { fontSize: 12, fontFamily: 'monospace', lineHeight: 18 },
219
+ copyLogButton: {
220
+ marginTop: 8,
221
+ alignSelf: 'flex-start',
222
+ paddingHorizontal: 10,
223
+ paddingVertical: 4,
224
+ borderRadius: 6,
225
+ },
226
+ copyLogText: { fontSize: 11, fontWeight: '600' },
227
+ emptyContainer: { alignItems: 'center', paddingVertical: 40 },
228
+ emptyText: { fontSize: 13 },
229
+ });
230
+
231
+ export function createConsoleViewerPlugin(): DebuggerPlugin {
232
+ return {
233
+ id: 'console-viewer',
234
+ name: 'Console',
235
+ icon: '📋',
236
+ component: ConsoleViewerPanel,
237
+ order: 20,
238
+ onInit: () => ConsoleInterceptor.start(),
239
+ onDestroy: () => ConsoleInterceptor.stop(),
240
+ };
241
+ }
@@ -0,0 +1,316 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import { View, Text, FlatList, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
3
+ import type { PluginComponentProps, CrashEntry, DebuggerPlugin } from '../../core/types';
4
+ import { generateId, formatTimestamp, copyToClipboard, shareText } from '../../core/utils';
5
+
6
+ // ─── Crash Store ────────────────────────────────────────────────────────────
7
+
8
+ type CrashListener = (entries: CrashEntry[]) => void;
9
+
10
+ class CrashStoreClass {
11
+ private entries: CrashEntry[] = [];
12
+ private listeners: Set<CrashListener> = new Set();
13
+ private isActive = false;
14
+ private maxEntries = 100;
15
+ private originalHandler: ((error: Error, isFatal?: boolean) => void) | null = null;
16
+
17
+ start(): void {
18
+ if (this.isActive) return;
19
+ this.isActive = true;
20
+
21
+ // Capture unhandled JS errors
22
+ const ErrorUtils = (
23
+ globalThis as unknown as {
24
+ ErrorUtils?: {
25
+ getGlobalHandler: () => (error: Error, isFatal?: boolean) => void;
26
+ setGlobalHandler: (handler: (error: Error, isFatal?: boolean) => void) => void;
27
+ };
28
+ }
29
+ ).ErrorUtils;
30
+ if (ErrorUtils) {
31
+ this.originalHandler = ErrorUtils.getGlobalHandler();
32
+ ErrorUtils.setGlobalHandler((error: Error, isFatal?: boolean) => {
33
+ this.addEntry({
34
+ id: generateId(),
35
+ timestamp: Date.now(),
36
+ message: error.message,
37
+ stack: error.stack,
38
+ isFatal: isFatal ?? false,
39
+ });
40
+ // Forward to original handler
41
+ if (this.originalHandler) {
42
+ this.originalHandler(error, isFatal);
43
+ }
44
+ });
45
+ }
46
+
47
+ // Capture unhandled promise rejections
48
+ const tracking = require('promise/setimmediate/rejection-tracking');
49
+ if (tracking) {
50
+ try {
51
+ tracking.enable({
52
+ allRejections: true,
53
+ onUnhandled: (_id: number, error: Error) => {
54
+ this.addEntry({
55
+ id: generateId(),
56
+ timestamp: Date.now(),
57
+ message: error?.message || 'Unhandled Promise Rejection',
58
+ stack: error?.stack,
59
+ isFatal: false,
60
+ });
61
+ },
62
+ });
63
+ } catch {
64
+ // Rejection tracking may not be available
65
+ }
66
+ }
67
+ }
68
+
69
+ stop(): void {
70
+ if (!this.isActive) return;
71
+ this.isActive = false;
72
+ const ErrorUtils = (
73
+ globalThis as unknown as {
74
+ ErrorUtils?: {
75
+ setGlobalHandler: (handler: (error: Error, isFatal?: boolean) => void) => void;
76
+ };
77
+ }
78
+ ).ErrorUtils;
79
+ if (ErrorUtils && this.originalHandler) {
80
+ ErrorUtils.setGlobalHandler(this.originalHandler);
81
+ this.originalHandler = null;
82
+ }
83
+ }
84
+
85
+ addEntry(entry: CrashEntry): void {
86
+ this.entries = [entry, ...this.entries].slice(0, this.maxEntries);
87
+ this.notify();
88
+ }
89
+
90
+ getAll(): CrashEntry[] {
91
+ return [...this.entries];
92
+ }
93
+ clear(): void {
94
+ this.entries = [];
95
+ this.notify();
96
+ }
97
+
98
+ subscribe(listener: CrashListener): () => void {
99
+ this.listeners.add(listener);
100
+ listener(this.entries);
101
+ return () => this.listeners.delete(listener);
102
+ }
103
+
104
+ private notify(): void {
105
+ const snapshot = this.entries;
106
+ for (const l of this.listeners) {
107
+ try {
108
+ l(snapshot);
109
+ } catch {
110
+ /* ignore */
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ const crashStore = new CrashStoreClass();
117
+
118
+ // ─── Crash Reporter Panel ───────────────────────────────────────────────────
119
+
120
+ const CrashReporterPanel: React.FC<PluginComponentProps> = ({ theme }) => {
121
+ const [entries, setEntries] = useState<CrashEntry[]>([]);
122
+ const [selectedEntry, setSelectedEntry] = useState<CrashEntry | null>(null);
123
+
124
+ useEffect(() => {
125
+ const unsub = crashStore.subscribe(setEntries);
126
+ return unsub;
127
+ }, []);
128
+
129
+ const handleClear = useCallback(() => {
130
+ crashStore.clear();
131
+ setSelectedEntry(null);
132
+ }, []);
133
+
134
+ if (selectedEntry) {
135
+ return (
136
+ <View style={styles.container}>
137
+ <View
138
+ style={[
139
+ styles.header,
140
+ { backgroundColor: theme.surface, borderBottomColor: theme.border },
141
+ ]}
142
+ >
143
+ <TouchableOpacity onPress={() => setSelectedEntry(null)} activeOpacity={0.7}>
144
+ <Text style={[styles.backBtn, { color: theme.accent }]}>← Back</Text>
145
+ </TouchableOpacity>
146
+ <View style={styles.headerActions}>
147
+ <TouchableOpacity
148
+ onPress={() =>
149
+ copyToClipboard(`${selectedEntry.message}\n\n${selectedEntry.stack || ''}`)
150
+ }
151
+ activeOpacity={0.7}
152
+ >
153
+ <Text style={[styles.actionText, { color: theme.accent }]}>Copy</Text>
154
+ </TouchableOpacity>
155
+ <TouchableOpacity
156
+ onPress={() =>
157
+ shareText(
158
+ `${selectedEntry.message}\n\n${selectedEntry.stack || ''}`,
159
+ 'Crash Report',
160
+ )
161
+ }
162
+ activeOpacity={0.7}
163
+ >
164
+ <Text style={[styles.actionText, { color: theme.accent }]}>Share</Text>
165
+ </TouchableOpacity>
166
+ </View>
167
+ </View>
168
+ <ScrollView style={styles.detailContent}>
169
+ <View
170
+ style={[
171
+ styles.crashBadge,
172
+ { backgroundColor: selectedEntry.isFatal ? theme.error : theme.warning },
173
+ ]}
174
+ >
175
+ <Text style={styles.crashBadgeText}>
176
+ {selectedEntry.isFatal ? 'FATAL' : 'NON-FATAL'}
177
+ </Text>
178
+ </View>
179
+ <Text style={[styles.crashTime, { color: theme.textMuted }]}>
180
+ {formatTimestamp(selectedEntry.timestamp)}
181
+ </Text>
182
+ <Text style={[styles.crashMessage, { color: theme.text }]}>{selectedEntry.message}</Text>
183
+ {selectedEntry.stack && (
184
+ <View style={[styles.stackBlock, { backgroundColor: theme.codeBackground }]}>
185
+ <Text style={[styles.stackText, { color: theme.codeText }]} selectable>
186
+ {selectedEntry.stack}
187
+ </Text>
188
+ </View>
189
+ )}
190
+ {selectedEntry.componentStack && (
191
+ <View
192
+ style={[styles.stackBlock, { backgroundColor: theme.codeBackground, marginTop: 12 }]}
193
+ >
194
+ <Text style={[styles.stackLabel, { color: theme.textSecondary }]}>
195
+ Component Stack
196
+ </Text>
197
+ <Text style={[styles.stackText, { color: theme.codeText }]} selectable>
198
+ {selectedEntry.componentStack}
199
+ </Text>
200
+ </View>
201
+ )}
202
+ </ScrollView>
203
+ </View>
204
+ );
205
+ }
206
+
207
+ return (
208
+ <View style={styles.container}>
209
+ <View
210
+ style={[styles.header, { backgroundColor: theme.surface, borderBottomColor: theme.border }]}
211
+ >
212
+ <Text style={[styles.headerTitle, { color: theme.text }]}>
213
+ {entries.length > 0 ? `${entries.length} crashes` : 'No crashes 🎉'}
214
+ </Text>
215
+ {entries.length > 0 && (
216
+ <TouchableOpacity onPress={handleClear} activeOpacity={0.7}>
217
+ <Text style={[styles.clearBtn, { color: theme.error }]}>Clear</Text>
218
+ </TouchableOpacity>
219
+ )}
220
+ </View>
221
+ <FlatList
222
+ data={entries}
223
+ keyExtractor={(item) => item.id}
224
+ renderItem={({ item }) => (
225
+ <TouchableOpacity
226
+ style={[
227
+ styles.crashRow,
228
+ {
229
+ borderBottomColor: theme.border,
230
+ backgroundColor: item.isFatal ? `${theme.error}10` : theme.surface,
231
+ },
232
+ ]}
233
+ onPress={() => setSelectedEntry(item)}
234
+ activeOpacity={0.7}
235
+ >
236
+ <View
237
+ style={[
238
+ styles.fatalDot,
239
+ { backgroundColor: item.isFatal ? theme.error : theme.warning },
240
+ ]}
241
+ />
242
+ <View style={styles.crashInfo}>
243
+ <Text style={[styles.crashMsg, { color: theme.text }]} numberOfLines={2}>
244
+ {item.message}
245
+ </Text>
246
+ <Text style={[styles.crashTimestamp, { color: theme.textMuted }]}>
247
+ {formatTimestamp(item.timestamp)}
248
+ </Text>
249
+ </View>
250
+ </TouchableOpacity>
251
+ )}
252
+ ListEmptyComponent={
253
+ <View style={styles.emptyContainer}>
254
+ <Text style={styles.emptyIcon}>✅</Text>
255
+ <Text style={[styles.emptyText, { color: theme.textMuted }]}>No crashes captured</Text>
256
+ </View>
257
+ }
258
+ />
259
+ </View>
260
+ );
261
+ };
262
+
263
+ const styles = StyleSheet.create({
264
+ container: { flex: 1 },
265
+ header: {
266
+ flexDirection: 'row',
267
+ justifyContent: 'space-between',
268
+ alignItems: 'center',
269
+ paddingHorizontal: 12,
270
+ paddingVertical: 10,
271
+ borderBottomWidth: StyleSheet.hairlineWidth,
272
+ },
273
+ headerTitle: { fontSize: 14, fontWeight: '700' },
274
+ clearBtn: { fontSize: 13, fontWeight: '700' },
275
+ backBtn: { fontSize: 14, fontWeight: '600' },
276
+ headerActions: { flexDirection: 'row', gap: 12 },
277
+ actionText: { fontSize: 12, fontWeight: '600' },
278
+ crashRow: {
279
+ flexDirection: 'row',
280
+ alignItems: 'center',
281
+ paddingHorizontal: 12,
282
+ paddingVertical: 10,
283
+ borderBottomWidth: StyleSheet.hairlineWidth,
284
+ },
285
+ fatalDot: { width: 8, height: 8, borderRadius: 4, marginRight: 10 },
286
+ crashInfo: { flex: 1 },
287
+ crashMsg: { fontSize: 13, fontWeight: '500' },
288
+ crashTimestamp: { fontSize: 10, marginTop: 2 },
289
+ emptyContainer: { alignItems: 'center', paddingVertical: 60 },
290
+ emptyIcon: { fontSize: 48, marginBottom: 12 },
291
+ emptyText: { fontSize: 13 },
292
+ detailContent: { flex: 1, padding: 12 },
293
+ crashBadge: {
294
+ alignSelf: 'flex-start',
295
+ paddingHorizontal: 10,
296
+ paddingVertical: 3,
297
+ borderRadius: 6,
298
+ marginBottom: 8,
299
+ },
300
+ crashBadgeText: { fontSize: 10, fontWeight: '800', color: '#FFF', letterSpacing: 1 },
301
+ crashTime: { fontSize: 11, marginBottom: 8 },
302
+ crashMessage: { fontSize: 16, fontWeight: '700', marginBottom: 16 },
303
+ stackBlock: { padding: 12, borderRadius: 8 },
304
+ stackLabel: { fontSize: 11, fontWeight: '700', marginBottom: 4, textTransform: 'uppercase' },
305
+ stackText: { fontSize: 11, fontFamily: 'monospace', lineHeight: 18 },
306
+ });
307
+
308
+ export function createCrashReporterPlugin(): DebuggerPlugin {
309
+ return {
310
+ id: 'crash-reporter',
311
+ name: 'Crashes',
312
+ icon: '💥',
313
+ component: CrashReporterPanel,
314
+ order: 100,
315
+ };
316
+ }