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,205 @@
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ FlatList,
6
+ TouchableOpacity,
7
+ TextInput,
8
+ StyleSheet,
9
+ ActivityIndicator,
10
+ } from 'react-native';
11
+ import type { PluginComponentProps, RemoteConfigValue, DebuggerPlugin } from '../../core/types';
12
+ import { remoteConfigStore } from './remoteConfigStore';
13
+ import { copyToClipboard, safeStringify, formatTimeAgo } from '../../core/utils';
14
+
15
+ const RemoteConfigPanel: React.FC<PluginComponentProps> = ({ theme }) => {
16
+ const [values, setValues] = useState<RemoteConfigValue[]>([]);
17
+ const [loading, setLoading] = useState(false);
18
+ const [searchQuery, setSearchQuery] = useState('');
19
+ const [lastFetch, setLastFetch] = useState<number | null>(null);
20
+ const [hasProvider, setHasProvider] = useState(false);
21
+ const [error, setError] = useState<string | null>(null);
22
+
23
+ const fetchValues = useCallback(async () => {
24
+ const provider = remoteConfigStore.get();
25
+ if (!provider) return;
26
+ setLoading(true);
27
+ setError(null);
28
+ try {
29
+ const result = await provider.getAll();
30
+ setValues(result);
31
+ if (provider.getLastFetchTime) {
32
+ const time = await provider.getLastFetchTime();
33
+ setLastFetch(time);
34
+ }
35
+ } catch (err) {
36
+ setError(err instanceof Error ? err.message : 'Failed to fetch');
37
+ }
38
+ setLoading(false);
39
+ }, []);
40
+
41
+ useEffect(() => {
42
+ const unsubscribe = remoteConfigStore.subscribe(() => {
43
+ setHasProvider(!!remoteConfigStore.get());
44
+ fetchValues();
45
+ });
46
+ setHasProvider(!!remoteConfigStore.get());
47
+ fetchValues();
48
+ return unsubscribe;
49
+ }, [fetchValues]);
50
+
51
+ const filteredValues = searchQuery
52
+ ? values.filter(
53
+ (v) =>
54
+ v.key.toLowerCase().includes(searchQuery.toLowerCase()) ||
55
+ String(v.value).toLowerCase().includes(searchQuery.toLowerCase()),
56
+ )
57
+ : values;
58
+
59
+ if (!hasProvider) {
60
+ return (
61
+ <View style={styles.emptyContainer}>
62
+ <Text style={styles.emptyIcon}>⚙️</Text>
63
+ <Text style={[styles.emptyTitle, { color: theme.text }]}>No Config Provider</Text>
64
+ <Text style={[styles.emptyDesc, { color: theme.textMuted }]}>
65
+ Register a provider:{'\n\n'}
66
+ <Text style={{ fontFamily: 'monospace', color: theme.codeText }}>
67
+ {'setRemoteConfigProvider({\n name: "Firebase",\n getAll: () => [...],\n})'}
68
+ </Text>
69
+ </Text>
70
+ </View>
71
+ );
72
+ }
73
+
74
+ return (
75
+ <View style={styles.container}>
76
+ <View
77
+ style={[styles.header, { backgroundColor: theme.surface, borderBottomColor: theme.border }]}
78
+ >
79
+ <View>
80
+ <Text style={[styles.headerTitle, { color: theme.text }]}>
81
+ {remoteConfigStore.get()?.name || 'Remote Config'}
82
+ </Text>
83
+ {lastFetch && (
84
+ <Text style={[styles.lastFetch, { color: theme.textMuted }]}>
85
+ Last fetched: {formatTimeAgo(lastFetch)}
86
+ </Text>
87
+ )}
88
+ </View>
89
+ <TouchableOpacity onPress={fetchValues} activeOpacity={0.7}>
90
+ <Text style={[styles.refreshBtn, { color: theme.accent }]}>
91
+ {loading ? '...' : '↻ Refresh'}
92
+ </Text>
93
+ </TouchableOpacity>
94
+ </View>
95
+
96
+ <View style={[styles.searchContainer, { backgroundColor: theme.surface }]}>
97
+ <TextInput
98
+ style={[
99
+ styles.searchInput,
100
+ { color: theme.text, backgroundColor: theme.surfaceAlt, borderColor: theme.border },
101
+ ]}
102
+ placeholder="Search config..."
103
+ placeholderTextColor={theme.textMuted}
104
+ value={searchQuery}
105
+ onChangeText={setSearchQuery}
106
+ autoCapitalize="none"
107
+ />
108
+ </View>
109
+
110
+ {error && (
111
+ <View style={[styles.errorBanner, { backgroundColor: `${theme.error}20` }]}>
112
+ <Text style={[styles.errorText, { color: theme.error }]}>{error}</Text>
113
+ </View>
114
+ )}
115
+
116
+ {loading && !values.length ? (
117
+ <View style={styles.loadingContainer}>
118
+ <ActivityIndicator color={theme.accent} />
119
+ </View>
120
+ ) : (
121
+ <FlatList
122
+ data={filteredValues}
123
+ keyExtractor={(item) => item.key}
124
+ renderItem={({ item }) => (
125
+ <TouchableOpacity
126
+ style={[styles.configRow, { borderBottomColor: theme.border }]}
127
+ onLongPress={() => copyToClipboard(safeStringify(item.value, 0))}
128
+ activeOpacity={0.7}
129
+ >
130
+ <View style={styles.configInfo}>
131
+ <Text style={[styles.configKey, { color: theme.text }]}>{item.key}</Text>
132
+ <Text style={[styles.configSource, { color: theme.textMuted }]}>{item.source}</Text>
133
+ </View>
134
+ <Text style={[styles.configValue, { color: theme.accent }]} numberOfLines={2}>
135
+ {typeof item.value === 'object' ? JSON.stringify(item.value) : String(item.value)}
136
+ </Text>
137
+ </TouchableOpacity>
138
+ )}
139
+ ListEmptyComponent={
140
+ <View style={styles.emptyListContainer}>
141
+ <Text style={[styles.emptyText, { color: theme.textMuted }]}>
142
+ {searchQuery ? 'No matching config' : 'No config values'}
143
+ </Text>
144
+ </View>
145
+ }
146
+ />
147
+ )}
148
+ </View>
149
+ );
150
+ };
151
+
152
+ const styles = StyleSheet.create({
153
+ container: { flex: 1 },
154
+ emptyContainer: {
155
+ flex: 1,
156
+ alignItems: 'center',
157
+ justifyContent: 'center',
158
+ paddingHorizontal: 32,
159
+ },
160
+ emptyIcon: { fontSize: 48, marginBottom: 16 },
161
+ emptyTitle: { fontSize: 16, fontWeight: '700', marginBottom: 8 },
162
+ emptyDesc: { fontSize: 13, textAlign: 'center', lineHeight: 20 },
163
+ header: {
164
+ flexDirection: 'row',
165
+ justifyContent: 'space-between',
166
+ alignItems: 'center',
167
+ paddingHorizontal: 12,
168
+ paddingVertical: 10,
169
+ borderBottomWidth: StyleSheet.hairlineWidth,
170
+ },
171
+ headerTitle: { fontSize: 14, fontWeight: '700' },
172
+ lastFetch: { fontSize: 10, marginTop: 2 },
173
+ refreshBtn: { fontSize: 13, fontWeight: '600' },
174
+ searchContainer: { paddingHorizontal: 12, paddingVertical: 8 },
175
+ searchInput: { height: 36, borderRadius: 8, paddingHorizontal: 12, fontSize: 13, borderWidth: 1 },
176
+ errorBanner: { padding: 12, marginHorizontal: 12, borderRadius: 8, marginBottom: 8 },
177
+ errorText: { fontSize: 12, fontWeight: '600' },
178
+ loadingContainer: { flex: 1, alignItems: 'center', justifyContent: 'center' },
179
+ configRow: {
180
+ paddingHorizontal: 12,
181
+ paddingVertical: 10,
182
+ borderBottomWidth: StyleSheet.hairlineWidth,
183
+ },
184
+ configInfo: {
185
+ flexDirection: 'row',
186
+ justifyContent: 'space-between',
187
+ alignItems: 'center',
188
+ marginBottom: 4,
189
+ },
190
+ configKey: { fontSize: 13, fontWeight: '600', flex: 1 },
191
+ configSource: { fontSize: 10, fontWeight: '500', textTransform: 'uppercase' },
192
+ configValue: { fontSize: 12, fontFamily: 'monospace' },
193
+ emptyListContainer: { alignItems: 'center', paddingVertical: 40 },
194
+ emptyText: { fontSize: 13 },
195
+ });
196
+
197
+ export function createRemoteConfigPlugin(): DebuggerPlugin {
198
+ return {
199
+ id: 'remote-config',
200
+ name: 'Config',
201
+ icon: '⚙️',
202
+ component: RemoteConfigPanel,
203
+ order: 50,
204
+ };
205
+ }
@@ -0,0 +1,48 @@
1
+ import type { RemoteConfigProvider } from '../../core/types';
2
+
3
+ type Listener = () => void;
4
+
5
+ class RemoteConfigStoreClass {
6
+ private provider: RemoteConfigProvider | null = null;
7
+ private listeners: Set<Listener> = new Set();
8
+
9
+ /** Set the remote config provider. */
10
+ set(provider: RemoteConfigProvider): void {
11
+ this.provider = provider;
12
+ this.notify();
13
+ }
14
+
15
+ /** Get the current provider. */
16
+ get(): RemoteConfigProvider | null {
17
+ return this.provider;
18
+ }
19
+
20
+ /** Remove the provider. */
21
+ remove(): void {
22
+ this.provider = null;
23
+ this.notify();
24
+ }
25
+
26
+ /** Subscribe to provider changes. */
27
+ subscribe(listener: Listener): () => void {
28
+ this.listeners.add(listener);
29
+ return () => this.listeners.delete(listener);
30
+ }
31
+
32
+ private notify(): void {
33
+ for (const l of this.listeners) {
34
+ try {
35
+ l();
36
+ } catch {
37
+ /* ignore */
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ export const remoteConfigStore = new RemoteConfigStoreClass();
44
+
45
+ /** Set the remote config provider (e.g., Firebase). */
46
+ export function setRemoteConfigProvider(provider: RemoteConfigProvider): void {
47
+ remoteConfigStore.set(provider);
48
+ }
@@ -0,0 +1,342 @@
1
+ import React, { useState, useEffect, useCallback, useMemo } from 'react';
2
+ import { View, Text, TouchableOpacity, ScrollView, TextInput, StyleSheet } from 'react-native';
3
+ import type { PluginComponentProps, DebuggerPlugin } from '../../core/types';
4
+ import { stateAdapterRegistry, type StateAdapter } from './stateAdapterRegistry';
5
+ import { safeStringify, copyToClipboard } from '../../core/utils';
6
+
7
+ // ─── JSON Tree Viewer ───────────────────────────────────────────────────────
8
+
9
+ interface JsonNodeProps {
10
+ keyName: string;
11
+ value: unknown;
12
+ depth: number;
13
+ theme: PluginComponentProps['theme'];
14
+ searchQuery: string;
15
+ }
16
+
17
+ const JsonNode: React.FC<JsonNodeProps> = React.memo(
18
+ ({ keyName, value, depth, theme, searchQuery }) => {
19
+ const [expanded, setExpanded] = useState(depth < 2);
20
+
21
+ const isObject = typeof value === 'object' && value !== null;
22
+ const isArray = Array.isArray(value);
23
+ const entries = isObject ? Object.entries(value as Record<string, unknown>) : [];
24
+ const typeLabel = isArray ? `Array(${entries.length})` : `Object(${entries.length})`;
25
+
26
+ const matchesSearch = searchQuery && keyName.toLowerCase().includes(searchQuery.toLowerCase());
27
+
28
+ const valueDisplay = useMemo(() => {
29
+ if (value === null) return 'null';
30
+ if (value === undefined) return 'undefined';
31
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
32
+ if (typeof value === 'number') return String(value);
33
+ if (typeof value === 'string')
34
+ return `"${value.length > 100 ? value.slice(0, 100) + '…' : value}"`;
35
+ return null;
36
+ }, [value]);
37
+
38
+ const valueColor = useMemo(() => {
39
+ if (value === null || value === undefined) return theme.textMuted;
40
+ if (typeof value === 'boolean') return theme.accent;
41
+ if (typeof value === 'number') return theme.success;
42
+ if (typeof value === 'string') return theme.warning;
43
+ return theme.text;
44
+ }, [value, theme]);
45
+
46
+ if (!isObject) {
47
+ return (
48
+ <TouchableOpacity
49
+ style={[styles.nodeRow, { paddingLeft: depth * 16 }]}
50
+ onLongPress={() => copyToClipboard(safeStringify(value, 0))}
51
+ activeOpacity={0.7}
52
+ >
53
+ <Text style={[styles.nodeKey, { color: matchesSearch ? theme.accent : theme.info }]}>
54
+ {keyName}:{' '}
55
+ </Text>
56
+ <Text style={[styles.nodeValue, { color: valueColor }]}>{valueDisplay}</Text>
57
+ </TouchableOpacity>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <View>
63
+ <TouchableOpacity
64
+ style={[styles.nodeRow, { paddingLeft: depth * 16 }]}
65
+ onPress={() => setExpanded(!expanded)}
66
+ onLongPress={() => copyToClipboard(safeStringify(value))}
67
+ activeOpacity={0.7}
68
+ >
69
+ <Text style={[styles.nodeExpander, { color: theme.textMuted }]}>
70
+ {expanded ? '▼' : '▶'}{' '}
71
+ </Text>
72
+ <Text style={[styles.nodeKey, { color: matchesSearch ? theme.accent : theme.info }]}>
73
+ {keyName}
74
+ </Text>
75
+ <Text style={[styles.nodeType, { color: theme.textMuted }]}> {typeLabel}</Text>
76
+ </TouchableOpacity>
77
+ {expanded &&
78
+ entries.map(([key, val]) => (
79
+ <JsonNode
80
+ key={key}
81
+ keyName={key}
82
+ value={val}
83
+ depth={depth + 1}
84
+ theme={theme}
85
+ searchQuery={searchQuery}
86
+ />
87
+ ))}
88
+ </View>
89
+ );
90
+ },
91
+ );
92
+
93
+ // ─── State Inspector Panel ──────────────────────────────────────────────────
94
+
95
+ const StateInspectorPanel: React.FC<PluginComponentProps> = ({ theme }) => {
96
+ const [adapters, setAdapters] = useState<Map<string, StateAdapter>>(new Map());
97
+ const [states, setStates] = useState<Map<string, unknown>>(new Map());
98
+ const [activeStore, setActiveStore] = useState<string | null>(null);
99
+ const [searchQuery, setSearchQuery] = useState('');
100
+ const [, forceUpdate] = useState(0);
101
+
102
+ // Listen for adapter registry changes
103
+ useEffect(() => {
104
+ const unsubscribe = stateAdapterRegistry.subscribe(() => {
105
+ setAdapters(stateAdapterRegistry.getAll());
106
+ });
107
+ setAdapters(stateAdapterRegistry.getAll());
108
+ return unsubscribe;
109
+ }, []);
110
+
111
+ // Subscribe to state changes from all adapters
112
+ useEffect(() => {
113
+ const unsubscribers: (() => void)[] = [];
114
+
115
+ const refreshStates = () => {
116
+ const newStates = new Map<string, unknown>();
117
+ for (const [id, adapter] of adapters) {
118
+ try {
119
+ newStates.set(id, adapter.getState());
120
+ } catch {
121
+ newStates.set(id, { error: 'Failed to read state' });
122
+ }
123
+ }
124
+ setStates(newStates);
125
+ };
126
+
127
+ refreshStates();
128
+
129
+ for (const [id, adapter] of adapters) {
130
+ if (adapter.subscribe) {
131
+ const unsub = adapter.subscribe(() => {
132
+ try {
133
+ setStates((prev) => {
134
+ const next = new Map(prev);
135
+ next.set(id, adapter.getState());
136
+ return next;
137
+ });
138
+ } catch {
139
+ // ignore
140
+ }
141
+ });
142
+ unsubscribers.push(unsub);
143
+ }
144
+ }
145
+
146
+ return () => unsubscribers.forEach((u) => u());
147
+ }, [adapters]);
148
+
149
+ const handleRefresh = useCallback(() => {
150
+ const newStates = new Map<string, unknown>();
151
+ for (const [id, adapter] of adapters) {
152
+ try {
153
+ newStates.set(id, adapter.getState());
154
+ } catch {
155
+ newStates.set(id, { error: 'Failed to read state' });
156
+ }
157
+ }
158
+ setStates(newStates);
159
+ forceUpdate((n) => n + 1);
160
+ }, [adapters]);
161
+
162
+ const handleCopyState = useCallback(
163
+ (storeId: string) => {
164
+ const state = states.get(storeId);
165
+ if (state) copyToClipboard(safeStringify(state));
166
+ },
167
+ [states],
168
+ );
169
+
170
+ const storeIds = useMemo(() => Array.from(adapters.keys()), [adapters]);
171
+ const activeStoreId = activeStore || storeIds[0] || null;
172
+ const activeState = activeStoreId ? states.get(activeStoreId) : null;
173
+ const activeAdapter = activeStoreId ? adapters.get(activeStoreId) : null;
174
+
175
+ if (storeIds.length === 0) {
176
+ return (
177
+ <View style={styles.emptyContainer}>
178
+ <Text style={[styles.emptyIcon]}>🔍</Text>
179
+ <Text style={[styles.emptyTitle, { color: theme.text }]}>No State Stores Registered</Text>
180
+ <Text style={[styles.emptyDesc, { color: theme.textMuted }]}>
181
+ Register your stores using:{'\n\n'}
182
+ <Text style={{ fontFamily: 'monospace', color: theme.codeText }}>
183
+ {
184
+ 'setStateAdapter("redux", {\n name: "Redux Store",\n getState: () => store.getState(),\n subscribe: (cb) => store.subscribe(cb)\n})'
185
+ }
186
+ </Text>
187
+ </Text>
188
+ </View>
189
+ );
190
+ }
191
+
192
+ return (
193
+ <View style={styles.container}>
194
+ {/* Store Selector */}
195
+ {storeIds.length > 1 && (
196
+ <ScrollView
197
+ horizontal
198
+ showsHorizontalScrollIndicator={false}
199
+ style={[styles.storeBar, { backgroundColor: theme.surface }]}
200
+ contentContainerStyle={styles.storeBarContent}
201
+ >
202
+ {storeIds.map((id) => {
203
+ const adapter = adapters.get(id);
204
+ return (
205
+ <TouchableOpacity
206
+ key={id}
207
+ style={[
208
+ styles.storeChip,
209
+ {
210
+ backgroundColor: id === activeStoreId ? theme.accent : theme.surfaceAlt,
211
+ borderColor: theme.border,
212
+ },
213
+ ]}
214
+ onPress={() => setActiveStore(id)}
215
+ activeOpacity={0.7}
216
+ >
217
+ <Text
218
+ style={[
219
+ styles.storeChipText,
220
+ { color: id === activeStoreId ? '#FFF' : theme.textSecondary },
221
+ ]}
222
+ >
223
+ {adapter?.name || id}
224
+ </Text>
225
+ </TouchableOpacity>
226
+ );
227
+ })}
228
+ </ScrollView>
229
+ )}
230
+
231
+ {/* Actions */}
232
+ <View
233
+ style={[
234
+ styles.actionBar,
235
+ { backgroundColor: theme.surface, borderBottomColor: theme.border },
236
+ ]}
237
+ >
238
+ <Text style={[styles.storeName, { color: theme.text }]}>
239
+ {activeAdapter?.name || activeStoreId}
240
+ </Text>
241
+ <View style={styles.actionButtons}>
242
+ <TouchableOpacity onPress={handleRefresh} activeOpacity={0.7} style={styles.actionBtn}>
243
+ <Text style={[styles.actionBtnText, { color: theme.accent }]}>↻ Refresh</Text>
244
+ </TouchableOpacity>
245
+ {activeStoreId && (
246
+ <TouchableOpacity
247
+ onPress={() => handleCopyState(activeStoreId)}
248
+ activeOpacity={0.7}
249
+ style={styles.actionBtn}
250
+ >
251
+ <Text style={[styles.actionBtnText, { color: theme.accent }]}>Copy</Text>
252
+ </TouchableOpacity>
253
+ )}
254
+ </View>
255
+ </View>
256
+
257
+ {/* Search */}
258
+ <View style={[styles.searchContainer, { backgroundColor: theme.surface }]}>
259
+ <TextInput
260
+ style={[
261
+ styles.searchInput,
262
+ { color: theme.text, backgroundColor: theme.surfaceAlt, borderColor: theme.border },
263
+ ]}
264
+ placeholder="Search keys..."
265
+ placeholderTextColor={theme.textMuted}
266
+ value={searchQuery}
267
+ onChangeText={setSearchQuery}
268
+ autoCapitalize="none"
269
+ />
270
+ </View>
271
+
272
+ {/* State Tree */}
273
+ <ScrollView style={styles.treeContainer}>
274
+ {activeState && typeof activeState === 'object' ? (
275
+ Object.entries(activeState as Record<string, unknown>).map(([key, val]) => (
276
+ <JsonNode
277
+ key={key}
278
+ keyName={key}
279
+ value={val}
280
+ depth={0}
281
+ theme={theme}
282
+ searchQuery={searchQuery}
283
+ />
284
+ ))
285
+ ) : (
286
+ <View style={{ padding: 12 }}>
287
+ <Text style={[styles.nodeValue, { color: theme.text }]}>
288
+ {safeStringify(activeState)}
289
+ </Text>
290
+ </View>
291
+ )}
292
+ </ScrollView>
293
+ </View>
294
+ );
295
+ };
296
+
297
+ const styles = StyleSheet.create({
298
+ container: { flex: 1 },
299
+ emptyContainer: {
300
+ flex: 1,
301
+ alignItems: 'center',
302
+ justifyContent: 'center',
303
+ paddingHorizontal: 32,
304
+ },
305
+ emptyIcon: { fontSize: 48, marginBottom: 16 },
306
+ emptyTitle: { fontSize: 16, fontWeight: '700', marginBottom: 8 },
307
+ emptyDesc: { fontSize: 13, textAlign: 'center', lineHeight: 20 },
308
+ storeBar: { maxHeight: 44 },
309
+ storeBarContent: { paddingHorizontal: 12, paddingVertical: 8, gap: 6 },
310
+ storeChip: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 12, borderWidth: 1 },
311
+ storeChipText: { fontSize: 12, fontWeight: '600' },
312
+ actionBar: {
313
+ flexDirection: 'row',
314
+ justifyContent: 'space-between',
315
+ alignItems: 'center',
316
+ paddingHorizontal: 12,
317
+ paddingVertical: 8,
318
+ borderBottomWidth: StyleSheet.hairlineWidth,
319
+ },
320
+ storeName: { fontSize: 14, fontWeight: '700' },
321
+ actionButtons: { flexDirection: 'row', gap: 12 },
322
+ actionBtn: {},
323
+ actionBtnText: { fontSize: 12, fontWeight: '600' },
324
+ searchContainer: { paddingHorizontal: 12, paddingVertical: 8 },
325
+ searchInput: { height: 36, borderRadius: 8, paddingHorizontal: 12, fontSize: 13, borderWidth: 1 },
326
+ treeContainer: { flex: 1 },
327
+ nodeRow: { flexDirection: 'row', alignItems: 'center', paddingVertical: 4, paddingRight: 12 },
328
+ nodeExpander: { fontSize: 10 },
329
+ nodeKey: { fontSize: 12, fontWeight: '600' },
330
+ nodeType: { fontSize: 10, fontStyle: 'italic' },
331
+ nodeValue: { fontSize: 12, flex: 1 },
332
+ });
333
+
334
+ export function createStateInspectorPlugin(): DebuggerPlugin {
335
+ return {
336
+ id: 'state-inspector',
337
+ name: 'State',
338
+ icon: '🔍',
339
+ component: StateInspectorPanel,
340
+ order: 30,
341
+ };
342
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * State Adapter Registry — Allows consumers to register their state stores.
3
+ *
4
+ * Supports Redux, Zustand, MobX, or any custom state store via a simple adapter interface.
5
+ */
6
+
7
+ export interface StateAdapter {
8
+ /** Adapter name (e.g., "Redux", "Zustand - userStore"). */
9
+ name: string;
10
+ /** Get the current state snapshot. */
11
+ getState: () => unknown;
12
+ /** Subscribe to state changes. Returns unsubscribe function. */
13
+ subscribe?: (listener: () => void) => () => void;
14
+ }
15
+
16
+ type AdapterListener = () => void;
17
+
18
+ class StateAdapterRegistryClass {
19
+ private adapters: Map<string, StateAdapter> = new Map();
20
+ private listeners: Set<AdapterListener> = new Set();
21
+
22
+ /** Register a state adapter. */
23
+ set(id: string, adapter: StateAdapter): void {
24
+ this.adapters.set(id, adapter);
25
+ this.notify();
26
+ }
27
+
28
+ /** Remove a state adapter. */
29
+ remove(id: string): void {
30
+ this.adapters.delete(id);
31
+ this.notify();
32
+ }
33
+
34
+ /** Get all registered adapters. */
35
+ getAll(): Map<string, StateAdapter> {
36
+ return new Map(this.adapters);
37
+ }
38
+
39
+ /** Subscribe to adapter changes. */
40
+ subscribe(listener: AdapterListener): () => void {
41
+ this.listeners.add(listener);
42
+ return () => this.listeners.delete(listener);
43
+ }
44
+
45
+ private notify(): void {
46
+ for (const listener of this.listeners) {
47
+ try {
48
+ listener();
49
+ } catch {
50
+ /* ignore */
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ export const stateAdapterRegistry = new StateAdapterRegistryClass();
57
+
58
+ /** Register a state store adapter. */
59
+ export function setStateAdapter(id: string, adapter: StateAdapter): void {
60
+ stateAdapterRegistry.set(id, adapter);
61
+ }
62
+
63
+ /** Remove a state store adapter. */
64
+ export function removeStateAdapter(id: string): void {
65
+ stateAdapterRegistry.remove(id);
66
+ }