react-native-debug-toolkit 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -11,6 +11,7 @@ import { createConsoleLogFeature } from './lib/features/ConsoleLogFeature'
11
11
  import { createZustandLogFeature, addZustandLog } from './lib/features/ZustandLogFeature'
12
12
  import { createNavigationLogFeature, addNavigationLog } from './lib/features/NavigationLogFeature'
13
13
  import { useNavigationLogger } from './lib/hooks/useNavigationLogger'
14
+ import { createTrackFeature, addTrackLog } from './lib/features/TrackFeature'
14
15
 
15
16
  // Function to clear all logs and close the panel
16
17
  const clearAll = () => {
@@ -30,9 +31,11 @@ export {
30
31
  addZustandLog,
31
32
  createNavigationLogFeature,
32
33
  addNavigationLog,
34
+ addTrackLog,
33
35
  useNavigationLogger,
34
36
  isDebugMode,
35
- clearAll
37
+ clearAll,
38
+ createTrackFeature
36
39
  }
37
40
 
38
41
  export default DebugToolKit
@@ -40,9 +40,6 @@ const _restoreConsole = () => {
40
40
  };
41
41
 
42
42
  const setup = () => {
43
- if (!__DEV__) {
44
- return; // No need to return 'this' in a functional approach
45
- }
46
43
  _interceptConsole();
47
44
  };
48
45
 
@@ -3,9 +3,6 @@ const logs = [];
3
3
  const originalNavigationMethods = {}; // Store original navigation methods
4
4
 
5
5
  const setup = () => {
6
- if (!__DEV__) {
7
- return;
8
- }
9
6
  // Note: Actual navigation interception will be done in the main app code
10
7
  // This is just a setup function for compatibility with the feature API
11
8
  };
@@ -18,9 +18,6 @@ class NetworkFeature {
18
18
  }
19
19
 
20
20
  setup() {
21
- if (!__DEV__) {
22
- return
23
- }
24
21
 
25
22
  // this._interceptFetch()
26
23
  return this
@@ -74,9 +71,6 @@ class NetworkFeature {
74
71
  }
75
72
 
76
73
  setupAxiosInterceptors(axiosInstance) {
77
- if (!__DEV__) {
78
- return
79
- }
80
74
 
81
75
  axiosInstance.interceptors.request.use(
82
76
  (config) => {
@@ -233,9 +227,6 @@ class NetworkFeature {
233
227
  }
234
228
 
235
229
  _interceptFetch() {
236
- if (!__DEV__) {
237
- return
238
- }
239
230
 
240
231
  this.originalFetch = global.fetch
241
232
 
@@ -40,7 +40,7 @@ class PerformanceFeature {
40
40
  }
41
41
 
42
42
  setup(options = {}) {
43
- if (!__DEV__ || this.isSetup) {
43
+ if (this.isSetup) {
44
44
  return this;
45
45
  }
46
46
 
@@ -207,7 +207,7 @@ class PerformanceFeature {
207
207
 
208
208
  // Mark events (following pattern from vanilla example)
209
209
  mark(name, detail = {}) {
210
- if (!__DEV__ || !this.isSetup) return this;
210
+ if (!this.isSetup) return this;
211
211
 
212
212
  if (typeof detail !== 'object') {
213
213
  detail = { value: detail };
@@ -218,7 +218,7 @@ class PerformanceFeature {
218
218
  }
219
219
 
220
220
  measure(name, startMarkOrOptions, endMark, detail = {}) {
221
- if (!__DEV__ || !this.isSetup) return this;
221
+ if (!this.isSetup) return this;
222
222
 
223
223
  try {
224
224
  if (typeof startMarkOrOptions === 'string' && endMark) {
@@ -236,7 +236,7 @@ class PerformanceFeature {
236
236
 
237
237
  // Record custom metrics (following pattern from vanilla example)
238
238
  metric(name, value, detail = {}) {
239
- if (!__DEV__ || !this.isSetup) return this;
239
+ if (!this.isSetup) return this;
240
240
 
241
241
  if (typeof detail !== 'object') {
242
242
  detail = { info: detail };
@@ -248,7 +248,7 @@ class PerformanceFeature {
248
248
 
249
249
  // Measure execution time of a function
250
250
  measureFunction(name, fn, ...args) {
251
- if (!__DEV__ || !this.isSetup) {
251
+ if (!this.isSetup) {
252
252
  return fn(...args);
253
253
  }
254
254
 
@@ -310,7 +310,7 @@ class PerformanceFeature {
310
310
 
311
311
  // Trigger a test network request (for demo purposes)
312
312
  generateTestNetworkRequest() {
313
- if (!__DEV__ || !this.isSetup || !this.resourceLoggingEnabled) return this;
313
+ if (!this.isSetup || !this.resourceLoggingEnabled) return this;
314
314
 
315
315
  // Similar to the fetch in the vanilla example
316
316
  fetch('https://jsonplaceholder.typicode.com/todos/1', { cache: 'no-cache' })
@@ -37,9 +37,7 @@ const getPlatformLibs = () => {
37
37
 
38
38
  // Setup function is minimal since we're just presenting UI options
39
39
  const setup = () => {
40
- if (!__DEV__) {
41
- return;
42
- }
40
+
43
41
  // No additional setup needed beyond initialization
44
42
  };
45
43
 
@@ -0,0 +1,94 @@
1
+ const MAX_LOGS = 200; // Max number of track events to store
2
+ const logs = [];
3
+
4
+ const setup = () => {
5
+
6
+ // Note: Actual event tracking will be done in the main app code
7
+ // This is just a setup function for compatibility with the feature API
8
+ };
9
+
10
+ // Function to add track event log
11
+ export const addTrackLog = (eventData) => {
12
+
13
+ // Store log data
14
+ const logEntry = {
15
+ timestamp: new Date(),
16
+ eventName: eventData.eventName || 'unknown_event',
17
+ entityType: eventData.entityType,
18
+ entityName: eventData.entityName,
19
+ pageId: eventData.pageId,
20
+ objId: eventData.objId,
21
+ entityPath: eventData.entityPath,
22
+ objType: eventData.objType,
23
+ objPt: eventData.objPt,
24
+ refPageLocation: eventData.refPageLocation,
25
+ position: eventData.position,
26
+ entityLocation: eventData.entityLocation,
27
+ frontOperation: eventData.frontOperation,
28
+ sessionId: eventData.sessionId,
29
+ requestId: eventData.requestId,
30
+ searchKeywored: eventData.searchKeywored,
31
+ enSearchKeywored: eventData.enSearchKeywored,
32
+ // Include any additional custom properties
33
+ ...eventData
34
+ };
35
+
36
+ logs.push(logEntry);
37
+
38
+ // Trim logs if they exceed the maximum limit
39
+ if (logs.length > MAX_LOGS) {
40
+ logs.splice(0, logs.length - MAX_LOGS);
41
+ }
42
+ };
43
+
44
+ // Get event statistics
45
+ export const getEventStats = () => {
46
+ const stats = {};
47
+ logs.forEach(log => {
48
+ const eventName = log.eventName;
49
+ if (!stats[eventName]) {
50
+ stats[eventName] = {
51
+ count: 0,
52
+ lastSeen: log.timestamp,
53
+ entityTypes: new Set(),
54
+ operations: new Set()
55
+ };
56
+ }
57
+ stats[eventName].count++;
58
+ if (log.timestamp > stats[eventName].lastSeen) {
59
+ stats[eventName].lastSeen = log.timestamp;
60
+ }
61
+ if (log.entityType) {
62
+ stats[eventName].entityTypes.add(log.entityType);
63
+ }
64
+ if (log.frontOperation) {
65
+ stats[eventName].operations.add(log.frontOperation);
66
+ }
67
+ });
68
+
69
+ // Convert Sets to Arrays for display
70
+ Object.keys(stats).forEach(eventName => {
71
+ stats[eventName].entityTypes = Array.from(stats[eventName].entityTypes);
72
+ stats[eventName].operations = Array.from(stats[eventName].operations);
73
+ });
74
+
75
+ return stats;
76
+ };
77
+
78
+ const getData = () => {
79
+ return logs;
80
+ };
81
+
82
+ const cleanup = () => {
83
+ logs.length = 0; // Clear array
84
+ };
85
+
86
+ export const createTrackFeature = () => {
87
+ return {
88
+ name: 'track',
89
+ label: 'Track Events',
90
+ setup: setup,
91
+ getData: getData,
92
+ cleanup: cleanup,
93
+ };
94
+ };
@@ -20,9 +20,7 @@ export const addZustandLog = (action, prevState, nextState, actionCompleteTime,
20
20
  };
21
21
 
22
22
  const setup = () => {
23
- if (!__DEV__) {
24
- return;
25
- }
23
+
26
24
  // Note: The actual middleware setup happens in the store creation
27
25
  // This function is mainly for compatibility with the feature API
28
26
  };
@@ -27,7 +27,7 @@ export function useNavigationLogger(navigationRef) {
27
27
  const routeRef = useRef(null);
28
28
 
29
29
  useEffect(() => {
30
- if (!__DEV__ || !navigationRef.current) {
30
+ if (!navigationRef.current) {
31
31
  return;
32
32
  }
33
33
 
package/lib/index.js CHANGED
@@ -5,11 +5,13 @@ import { createConsoleLogFeature } from './features/ConsoleLogFeature'
5
5
  import { createZustandLogFeature, zustandLogMiddleware } from './features/ZustandLogFeature'
6
6
  import { createNavigationLogFeature, addNavigationLog } from './features/NavigationLogFeature'
7
7
  import { createThirdPartyLibsFeature } from './features/ThirdPartyLibsFeature'
8
+ import { createTrackFeature, addTrackLog } from './features/TrackFeature'
8
9
  import NativeDebugLibs from './NativeDebugLibs'
9
10
 
10
11
  // Registry mapping feature names (strings) to their creator functions
11
12
  const featureRegistry = {
12
13
  network: createNetworkFeature,
14
+ track: createTrackFeature,
13
15
  console: createConsoleLogFeature,
14
16
  // performance: createPerformanceFeature,
15
17
  zustand: createZustandLogFeature,
@@ -20,7 +22,7 @@ const featureRegistry = {
20
22
  }
21
23
 
22
24
  // List of default features (can use strings now)
23
- const defaultFeatures = ['network', 'console', 'zustand', 'navigation', 'thirdPartyLibs']
25
+ const defaultFeatures = ['network', 'track', 'navigation','zustand', 'thirdPartyLibs', 'console']
24
26
 
25
27
  // Export the debug mode status - this is used by the app
26
28
  // const isDebugMode = NativeDebugLibs.isDebugBuild();
@@ -104,6 +106,8 @@ export {
104
106
  createNavigationLogFeature,
105
107
  createThirdPartyLibsFeature,
106
108
  addNavigationLog,
109
+ createTrackFeature,
110
+ addTrackLog,
107
111
  zustandLogMiddleware, // Export middleware for use in Zustand stores
108
112
  featureRegistry,
109
113
  isDebugMode
@@ -0,0 +1,92 @@
1
+ // Define the entity types and names as constants for better type safety
2
+ export const EntityType = {
3
+ mine_module: 'mine_module',
4
+ search_module: 'search_module',
5
+ page_view: 'page_view',
6
+ error_module: 'error_module',
7
+ navigation: 'navigation',
8
+ content: 'content',
9
+ user_action: 'user_action',
10
+ } as const
11
+
12
+ export const EntityName = {
13
+ MINE_POST: 'MINE_POST',
14
+ HOME_PAGE: 'HOME_PAGE',
15
+ SEARCH_BAR: 'SEARCH_BAR',
16
+ PROFILE_PAGE: 'PROFILE_PAGE',
17
+ NAVIGATION_TAB: 'NAVIGATION_TAB',
18
+ CONTENT_ITEM: 'CONTENT_ITEM',
19
+ USER_BUTTON: 'USER_BUTTON',
20
+ } as const
21
+
22
+ export type TrackEventOptions = {
23
+ entityType: (typeof EntityType)[keyof typeof EntityType]
24
+ entityName?: (typeof EntityName)[keyof typeof EntityName]
25
+ pageId?: string
26
+ objId?: string
27
+ entityPath?: string
28
+ objType?: number
29
+ objPt?: string // 瀑布流作品pt
30
+ refPageLocation?: string
31
+ position?: string
32
+ entityLocation?: string
33
+ frontOperation?: string
34
+ sessionId?: string //搜索模块需要
35
+ requestId?: string //搜索模块需要
36
+ searchKeywored?: string //搜索模块需要
37
+ enSearchKeywored?: string //搜索模块需要
38
+ }
39
+
40
+ // Complete event structure that includes the event name and all tracking options
41
+ export type TrackEvent = {
42
+ eventName: string
43
+ timestamp: Date
44
+ } & TrackEventOptions
45
+
46
+ // Track feature interface
47
+ export interface ITrackFeature {
48
+ setup(): ITrackFeature
49
+ getData(): TrackEvent[]
50
+ cleanup(): void
51
+ logEvent(eventData: TrackEventOptions & { eventName: string }): void
52
+ getEventStats(): EventStats
53
+ addEventToBlacklist(pattern: string | RegExp): void
54
+ removeEventFromBlacklist(pattern: string | RegExp): void
55
+ clearBlacklist(): void
56
+ getBlacklist(): (string | RegExp)[]
57
+ }
58
+
59
+ // Event statistics structure
60
+ export type EventStats = {
61
+ [eventName: string]: {
62
+ count: number
63
+ lastSeen: Date
64
+ entityTypes: string[]
65
+ operations: string[]
66
+ }
67
+ }
68
+
69
+ // Common event names used in the application
70
+ export const CommonEventNames = {
71
+ LOG_ACTION_FRONT: 'log_action_front',
72
+ LOG_ERROR_FRONT: 'log_error_front',
73
+ LOG_VIEW_FRONT: 'log_view_front',
74
+ LOG_SEARCH_FRONT: 'log_search_front',
75
+ LOG_NAVIGATION_FRONT: 'log_navigation_front',
76
+ } as const
77
+
78
+ // Common front operations
79
+ export const FrontOperations = {
80
+ CLICK: 'click',
81
+ VIEW: 'view',
82
+ SEARCH: 'search',
83
+ SWIPE: 'swipe',
84
+ SCROLL: 'scroll',
85
+ ERROR: 'error',
86
+ NAVIGATE: 'navigate',
87
+ } as const
88
+
89
+ // Helper type for creating events with better type safety
90
+ export type CreateTrackEvent<T extends keyof typeof CommonEventNames> = {
91
+ eventName: (typeof CommonEventNames)[T]
92
+ } & TrackEventOptions
@@ -21,6 +21,8 @@ import SubViewConsoleLogs from './SubViewConsoleLogs'
21
21
  import SubViewZustandLogs from './SubViewZustandLogs'
22
22
  import SubViewNavigationLogs from './SubViewNavigationLogs'
23
23
  import SubViewThirdPartyLibs from './SubViewThirdPartyLibs'
24
+ import SubViewTrackLogs from './SubViewTrackLogs'
25
+
24
26
  const { width: screenWidth, height: screenHeight } = Dimensions.get('window')
25
27
 
26
28
  export default class FloatPanelView extends Component {
@@ -320,6 +322,10 @@ export default class FloatPanelView extends Component {
320
322
  if (feature.name === 'navigation') {
321
323
  return <SubViewNavigationLogs logs={data} />
322
324
  }
325
+
326
+ if (feature.name === 'track') {
327
+ return <SubViewTrackLogs logs={data} />
328
+ }
323
329
 
324
330
  if (feature.name === 'thirdPartyLibs') {
325
331
  return <SubViewThirdPartyLibs libraries={data} />
@@ -0,0 +1,318 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ FlatList,
7
+ TouchableOpacity,
8
+ } from 'react-native'
9
+ import TrackLogDetails from './TrackLogDetails'
10
+
11
+ const SubViewTrackLogs = ({ logs = [] }) => {
12
+ const [selectedLog, setSelectedLog] = useState(null)
13
+
14
+ const getEventColor = (eventName) => {
15
+ if (eventName?.includes('error')) return '#ff4444'
16
+ if (eventName?.includes('click')) return '#4CAF50'
17
+ if (eventName?.includes('view')) return '#2196F3'
18
+ if (eventName?.includes('search')) return '#FF9800'
19
+ return '#9C27B0'
20
+ }
21
+
22
+ const getTimeAgo = (timestamp) => {
23
+ const now = new Date()
24
+ const eventTime = new Date(timestamp)
25
+ const diffMs = now - eventTime
26
+ const diffSeconds = Math.floor(diffMs / 1000)
27
+ const diffMinutes = Math.floor(diffSeconds / 60)
28
+ const diffHours = Math.floor(diffMinutes / 60)
29
+
30
+ if (diffSeconds < 60) {
31
+ return `${diffSeconds}s ago`
32
+ } else if (diffMinutes < 60) {
33
+ return `${diffMinutes}m ago`
34
+ } else if (diffHours < 24) {
35
+ return `${diffHours}h ago`
36
+ } else {
37
+ const diffDays = Math.floor(diffHours / 24)
38
+ return `${diffDays}d ago`
39
+ }
40
+ }
41
+
42
+ const renderLogItem = ({ item }) => {
43
+ const eventColor = getEventColor(item.eventName)
44
+
45
+ return (
46
+ <TouchableOpacity
47
+ style={styles.logItem}
48
+ onPress={() => setSelectedLog(item)}>
49
+ <View style={styles.logItemContainer}>
50
+ <View
51
+ style={[styles.eventIndicator, { backgroundColor: eventColor }]}
52
+ />
53
+
54
+ <View style={styles.logContent}>
55
+ <View style={styles.eventRow}>
56
+ <Text style={[styles.eventName, { color: eventColor }]}>
57
+ {item.eventName || 'Unknown Event'}
58
+ </Text>
59
+ {item.entityType && (
60
+ <Text style={styles.entityType}>
61
+ {item.entityType}
62
+ </Text>
63
+ )}
64
+ </View>
65
+
66
+ {item.entityName && (
67
+ <Text style={styles.entityName} numberOfLines={1}>
68
+ {item.entityName}
69
+ </Text>
70
+ )}
71
+
72
+ {item.frontOperation && (
73
+ <Text style={styles.operation} numberOfLines={1}>
74
+ Operation: {item.frontOperation}
75
+ </Text>
76
+ )}
77
+
78
+ <View style={styles.logFooter}>
79
+ <Text style={styles.time}>
80
+ {item.timestamp
81
+ ? getTimeAgo(item.timestamp)
82
+ : 'Unknown time'}
83
+ </Text>
84
+
85
+ <View style={styles.additionalInfo}>
86
+ {item.pageId && (
87
+ <Text style={styles.pageId} numberOfLines={1}>
88
+ Page: {item.pageId}
89
+ </Text>
90
+ )}
91
+ {item.position && (
92
+ <Text style={styles.position}>
93
+ Pos: {item.position}
94
+ </Text>
95
+ )}
96
+ </View>
97
+ </View>
98
+ </View>
99
+ </View>
100
+ </TouchableOpacity>
101
+ )
102
+ }
103
+
104
+ // If a log is selected, show the details view with a back button
105
+ if (selectedLog) {
106
+ return (
107
+ <View style={styles.container}>
108
+ <View style={styles.detailsHeader}>
109
+ <TouchableOpacity
110
+ style={styles.backButton}
111
+ onPress={() => setSelectedLog(null)}>
112
+ <Text style={styles.backButtonText}>← Back</Text>
113
+ </TouchableOpacity>
114
+ <View style={styles.headerEventInfo}>
115
+ <Text
116
+ style={[
117
+ styles.headerEvent,
118
+ { color: getEventColor(selectedLog.eventName) },
119
+ ]}>
120
+ {selectedLog.eventName || 'Unknown Event'}
121
+ </Text>
122
+ {selectedLog.entityType && (
123
+ <Text style={styles.headerEntityType}>
124
+ {selectedLog.entityType}
125
+ </Text>
126
+ )}
127
+ </View>
128
+ </View>
129
+ <TrackLogDetails log={selectedLog} />
130
+ </View>
131
+ )
132
+ }
133
+
134
+ // Otherwise show the list view
135
+ return (
136
+ <View style={styles.container}>
137
+ {logs.length === 0 ? (
138
+ <View style={styles.emptyContainer}>
139
+ <Text style={styles.emptyText}>No track events logged yet</Text>
140
+ <Text style={styles.emptySubtext}>
141
+ Track events will appear here as they occur during development
142
+ </Text>
143
+ </View>
144
+ ) : (
145
+ <>
146
+ <View style={styles.statsContainer}>
147
+ <Text style={styles.statsText}>
148
+ {logs.length} events logged
149
+ </Text>
150
+ </View>
151
+ <FlatList
152
+ data={[...logs].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))}
153
+ renderItem={renderLogItem}
154
+ keyExtractor={(item, index) => `${item.timestamp}-${index}`}
155
+ style={styles.list}
156
+ showsVerticalScrollIndicator={true}
157
+ />
158
+ </>
159
+ )}
160
+ </View>
161
+ )
162
+ }
163
+
164
+ const styles = StyleSheet.create({
165
+ container: {
166
+ flex: 1,
167
+ backgroundColor: '#fff',
168
+ },
169
+ list: {
170
+ flex: 1,
171
+ },
172
+ emptyContainer: {
173
+ flex: 1,
174
+ justifyContent: 'center',
175
+ alignItems: 'center',
176
+ padding: 40,
177
+ },
178
+ emptyText: {
179
+ textAlign: 'center',
180
+ color: '#666',
181
+ fontSize: 16,
182
+ fontWeight: '500',
183
+ marginBottom: 8,
184
+ },
185
+ emptySubtext: {
186
+ textAlign: 'center',
187
+ color: '#999',
188
+ fontSize: 14,
189
+ },
190
+ statsContainer: {
191
+ padding: 12,
192
+ borderBottomWidth: 1,
193
+ borderBottomColor: '#eee',
194
+ backgroundColor: '#f8f9fa',
195
+ },
196
+ statsText: {
197
+ fontSize: 12,
198
+ color: '#666',
199
+ fontWeight: '500',
200
+ },
201
+ logItem: {
202
+ borderBottomWidth: 1,
203
+ borderBottomColor: '#eee',
204
+ },
205
+ logItemContainer: {
206
+ flexDirection: 'row',
207
+ padding: 12,
208
+ },
209
+ eventIndicator: {
210
+ width: 4,
211
+ borderRadius: 2,
212
+ marginRight: 12,
213
+ },
214
+ logContent: {
215
+ flex: 1,
216
+ },
217
+ eventRow: {
218
+ flexDirection: 'row',
219
+ alignItems: 'center',
220
+ marginBottom: 4,
221
+ },
222
+ eventName: {
223
+ fontSize: 15,
224
+ fontWeight: 'bold',
225
+ marginRight: 8,
226
+ },
227
+ entityType: {
228
+ fontSize: 11,
229
+ backgroundColor: '#f0f0f0',
230
+ color: '#666',
231
+ paddingHorizontal: 6,
232
+ paddingVertical: 2,
233
+ borderRadius: 6,
234
+ fontWeight: '500',
235
+ },
236
+ entityName: {
237
+ fontSize: 14,
238
+ color: '#333',
239
+ marginBottom: 4,
240
+ fontWeight: '500',
241
+ },
242
+ operation: {
243
+ fontSize: 13,
244
+ color: '#666',
245
+ marginBottom: 6,
246
+ fontStyle: 'italic',
247
+ },
248
+ logFooter: {
249
+ flexDirection: 'row',
250
+ justifyContent: 'space-between',
251
+ alignItems: 'flex-end',
252
+ },
253
+ time: {
254
+ fontSize: 12,
255
+ color: '#999',
256
+ },
257
+ additionalInfo: {
258
+ flexDirection: 'row',
259
+ alignItems: 'center',
260
+ gap: 8,
261
+ },
262
+ pageId: {
263
+ fontSize: 11,
264
+ color: '#666',
265
+ backgroundColor: '#f8f9fa',
266
+ paddingHorizontal: 4,
267
+ paddingVertical: 1,
268
+ borderRadius: 3,
269
+ maxWidth: 80,
270
+ },
271
+ position: {
272
+ fontSize: 11,
273
+ color: '#666',
274
+ backgroundColor: '#f8f9fa',
275
+ paddingHorizontal: 4,
276
+ paddingVertical: 1,
277
+ borderRadius: 3,
278
+ },
279
+ detailsHeader: {
280
+ flexDirection: 'row',
281
+ alignItems: 'center',
282
+ padding: 15,
283
+ borderBottomWidth: 1,
284
+ borderBottomColor: '#eee',
285
+ backgroundColor: '#f8f9fa',
286
+ },
287
+ backButton: {
288
+ paddingHorizontal: 10,
289
+ paddingVertical: 5,
290
+ borderRadius: 4,
291
+ backgroundColor: '#eee',
292
+ marginRight: 10,
293
+ },
294
+ backButtonText: {
295
+ color: '#333',
296
+ fontWeight: '500',
297
+ },
298
+ headerEventInfo: {
299
+ flexDirection: 'row',
300
+ alignItems: 'center',
301
+ },
302
+ headerEvent: {
303
+ fontSize: 16,
304
+ fontWeight: 'bold',
305
+ marginRight: 10,
306
+ },
307
+ headerEntityType: {
308
+ fontSize: 12,
309
+ backgroundColor: '#f0f0f0',
310
+ color: '#666',
311
+ paddingHorizontal: 8,
312
+ paddingVertical: 3,
313
+ borderRadius: 8,
314
+ fontWeight: '500',
315
+ },
316
+ })
317
+
318
+ export default SubViewTrackLogs
@@ -0,0 +1,481 @@
1
+ import React, { useState, useCallback } from 'react'
2
+ import { View, Text, StyleSheet, Clipboard, Dimensions } from 'react-native'
3
+ import { ScrollView, Pressable } from 'react-native'
4
+ import JSONTree from 'react-native-json-tree'
5
+
6
+ const { width: SCREEN_WIDTH } = Dimensions.get('window')
7
+
8
+ const theme = {
9
+ scheme: 'monokai',
10
+ author: 'wimer hazenberg (http://www.monokai.nl)',
11
+ base00: '#272822',
12
+ base01: '#383830',
13
+ base02: '#49483e',
14
+ base03: '#75715e',
15
+ base04: '#a59f85',
16
+ base05: '#f8f8f2',
17
+ base06: '#f5f4f1',
18
+ base07: '#f9f8f5',
19
+ base08: '#f92672',
20
+ base09: '#fd971f',
21
+ base0A: '#f4bf75',
22
+ base0B: '#a6e22e',
23
+ base0C: '#a1efe4',
24
+ base0D: '#66d9ef',
25
+ base0E: '#ae81ff',
26
+ base0F: '#cc6633'
27
+ }
28
+
29
+ const CopyButton = ({ text, style }) => {
30
+ const [copied, setCopied] = useState(false)
31
+
32
+ const handleCopy = async () => {
33
+ await Clipboard.setString(text)
34
+ setCopied(true)
35
+ setTimeout(() => setCopied(false), 2000)
36
+ }
37
+
38
+ return (
39
+ <Pressable style={[styles.copyButton, style]} onPress={handleCopy}>
40
+ <Text style={styles.copyButtonText}>{copied ? 'Copied!' : 'Copy'}</Text>
41
+ </Pressable>
42
+ )
43
+ }
44
+
45
+ const CollapsibleSection = ({ title, children, initiallyExpanded = false }) => {
46
+ const [expanded, setExpanded] = useState(initiallyExpanded)
47
+
48
+ return (
49
+ <View style={styles.collapsibleSection}>
50
+ <Pressable
51
+ style={styles.sectionHeader}
52
+ onPress={() => setExpanded(!expanded)}>
53
+ <Text style={styles.sectionTitle}>{title}</Text>
54
+ <Text style={styles.expandIcon}>{expanded ? '▼' : '▶'}</Text>
55
+ </Pressable>
56
+ {expanded && children}
57
+ </View>
58
+ )
59
+ }
60
+
61
+ const JSONValue = ({ value, path = '', level = 0, maxExpandLevel = 2 }) => {
62
+ if (value === null) {
63
+ return <Text style={styles.jsonNull}>null</Text>
64
+ }
65
+
66
+ if (value === undefined) {
67
+ return <Text style={styles.jsonNull}>undefined</Text>
68
+ }
69
+
70
+ if (typeof value === 'boolean') {
71
+ return <Text style={styles.jsonBoolean}>{value.toString()}</Text>
72
+ }
73
+
74
+ if (typeof value === 'number') {
75
+ return <Text style={styles.jsonNumber}>{value}</Text>
76
+ }
77
+
78
+ if (typeof value === 'string') {
79
+ return (
80
+ <Text style={styles.jsonString} selectable={true}>
81
+ {value}
82
+ </Text>
83
+ )
84
+ }
85
+
86
+ // For objects and arrays, use JSONTree for improved large data handling
87
+ if (typeof value === 'object') {
88
+ return (
89
+ <JSONTree
90
+ data={value}
91
+ theme={theme}
92
+ invertTheme={true}
93
+ hideRoot={true}
94
+ shouldExpandNode={(keyPath, nodeData, currentLevel) => currentLevel < maxExpandLevel}
95
+ />
96
+ )
97
+ }
98
+
99
+ return <Text>{String(value)}</Text>
100
+ }
101
+
102
+ const EventTypeBadge = ({ eventName, entityType }) => {
103
+ const getEventColor = () => {
104
+ if (eventName?.includes('error')) return '#ff4444'
105
+ if (eventName?.includes('click')) return '#4CAF50'
106
+ if (eventName?.includes('view')) return '#2196F3'
107
+ if (eventName?.includes('search')) return '#FF9800'
108
+ return '#9C27B0'
109
+ }
110
+
111
+ return (
112
+ <View style={styles.badgeContainer}>
113
+ <Text style={[styles.eventBadge, { backgroundColor: getEventColor() }]}>
114
+ {eventName || 'Unknown Event'}
115
+ </Text>
116
+ {entityType && (
117
+ <Text style={[styles.entityBadge]}>
118
+ {entityType}
119
+ </Text>
120
+ )}
121
+ </View>
122
+ )
123
+ }
124
+
125
+ const TrackLogDetails = ({ log }) => {
126
+ const generateAnalyticsPayload = useCallback(() => {
127
+ if (!log) {
128
+ return 'No event data available'
129
+ }
130
+
131
+ const payload = {
132
+ eventName: log.eventName,
133
+ timestamp: log.timestamp,
134
+ ...Object.fromEntries(
135
+ Object.entries(log).filter(([key, value]) =>
136
+ key !== 'timestamp' && value !== undefined && value !== null
137
+ )
138
+ )
139
+ }
140
+
141
+ return JSON.stringify(payload, null, 2)
142
+ }, [log])
143
+
144
+ if (!log) {
145
+ return (
146
+ <View style={styles.errorContainer}>
147
+ <Text style={styles.errorText}>Event data is missing</Text>
148
+ </View>
149
+ )
150
+ }
151
+
152
+ const coreProperties = {
153
+ eventName: log.eventName,
154
+ entityType: log.entityType,
155
+ entityName: log.entityName,
156
+ frontOperation: log.frontOperation
157
+ }
158
+
159
+ const identificationProperties = {
160
+ pageId: log.pageId,
161
+ objId: log.objId,
162
+ entityPath: log.entityPath,
163
+ objType: log.objType,
164
+ objPt: log.objPt
165
+ }
166
+
167
+ const locationProperties = {
168
+ refPageLocation: log.refPageLocation,
169
+ position: log.position,
170
+ entityLocation: log.entityLocation
171
+ }
172
+
173
+ const searchProperties = {
174
+ sessionId: log.sessionId,
175
+ requestId: log.requestId,
176
+ searchKeywored: log.searchKeywored,
177
+ enSearchKeywored: log.enSearchKeywored
178
+ }
179
+
180
+ const filterUndefinedProperties = (obj) => {
181
+ return Object.fromEntries(
182
+ Object.entries(obj).filter(([key, value]) => value !== undefined && value !== null && value !== '')
183
+ )
184
+ }
185
+
186
+ return (
187
+ <ScrollView
188
+ style={styles.container}
189
+ contentContainerStyle={styles.contentContainer}
190
+ showsVerticalScrollIndicator={true}
191
+ scrollEventThrottle={16}
192
+ keyboardShouldPersistTaps='handled'>
193
+
194
+ <View style={styles.header}>
195
+ <View style={styles.headerInfo}>
196
+ <EventTypeBadge eventName={log.eventName} entityType={log.entityType} />
197
+ {log.entityName && (
198
+ <Text style={styles.entityName} selectable={true}>
199
+ {log.entityName}
200
+ </Text>
201
+ )}
202
+ {log.frontOperation && (
203
+ <Text style={styles.operation}>
204
+ Operation: {log.frontOperation}
205
+ </Text>
206
+ )}
207
+ </View>
208
+ <CopyButton text={generateAnalyticsPayload()} />
209
+ </View>
210
+
211
+ <CollapsibleSection title='Core Properties' initiallyExpanded={true}>
212
+ <View style={styles.content}>
213
+ <View style={styles.propertiesGrid}>
214
+ {Object.entries(filterUndefinedProperties(coreProperties)).map(([key, value]) => (
215
+ <View key={key} style={styles.propertyRow}>
216
+ <Text style={styles.propertyKey}>{key}:</Text>
217
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
218
+ </View>
219
+ ))}
220
+ </View>
221
+ </View>
222
+ </CollapsibleSection>
223
+
224
+ {Object.keys(filterUndefinedProperties(identificationProperties)).length > 0 && (
225
+ <CollapsibleSection title='Identification Properties'>
226
+ <View style={styles.content}>
227
+ <View style={styles.propertiesGrid}>
228
+ {Object.entries(filterUndefinedProperties(identificationProperties)).map(([key, value]) => (
229
+ <View key={key} style={styles.propertyRow}>
230
+ <Text style={styles.propertyKey}>{key}:</Text>
231
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
232
+ </View>
233
+ ))}
234
+ </View>
235
+ </View>
236
+ </CollapsibleSection>
237
+ )}
238
+
239
+ {Object.keys(filterUndefinedProperties(locationProperties)).length > 0 && (
240
+ <CollapsibleSection title='Location Properties'>
241
+ <View style={styles.content}>
242
+ <View style={styles.propertiesGrid}>
243
+ {Object.entries(filterUndefinedProperties(locationProperties)).map(([key, value]) => (
244
+ <View key={key} style={styles.propertyRow}>
245
+ <Text style={styles.propertyKey}>{key}:</Text>
246
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
247
+ </View>
248
+ ))}
249
+ </View>
250
+ </View>
251
+ </CollapsibleSection>
252
+ )}
253
+
254
+ {Object.keys(filterUndefinedProperties(searchProperties)).length > 0 && (
255
+ <CollapsibleSection title='Search Properties'>
256
+ <View style={styles.content}>
257
+ <View style={styles.propertiesGrid}>
258
+ {Object.entries(filterUndefinedProperties(searchProperties)).map(([key, value]) => (
259
+ <View key={key} style={styles.propertyRow}>
260
+ <Text style={styles.propertyKey}>{key}:</Text>
261
+ <Text style={styles.propertyValue} selectable={true}>{value}</Text>
262
+ </View>
263
+ ))}
264
+ </View>
265
+ </View>
266
+ </CollapsibleSection>
267
+ )}
268
+
269
+ <CollapsibleSection title='Complete Event Data'>
270
+ <View style={styles.content}>
271
+ <View style={styles.dataSectionHeader}>
272
+ <Text style={styles.dataSectionTitle}>Full JSON Payload</Text>
273
+ <CopyButton text={generateAnalyticsPayload()} />
274
+ </View>
275
+ <View style={styles.dataContentWrapper}>
276
+ <ScrollView
277
+ style={styles.dataContent}
278
+ nestedScrollEnabled={true}
279
+ bounces={false}
280
+ showsVerticalScrollIndicator={true}>
281
+ <JSONValue value={log} maxExpandLevel={2} />
282
+ </ScrollView>
283
+ </View>
284
+ </View>
285
+ </CollapsibleSection>
286
+
287
+ <CollapsibleSection title='Timing Information'>
288
+ <View style={styles.content}>
289
+ <View style={styles.propertyRow}>
290
+ <Text style={styles.propertyKey}>Timestamp:</Text>
291
+ <Text style={styles.propertyValue}>
292
+ {log.timestamp
293
+ ? new Date(log.timestamp).toLocaleString()
294
+ : 'Unknown'}
295
+ </Text>
296
+ </View>
297
+ <View style={styles.propertyRow}>
298
+ <Text style={styles.propertyKey}>Time ago:</Text>
299
+ <Text style={styles.propertyValue}>
300
+ {log.timestamp
301
+ ? getTimeAgo(log.timestamp)
302
+ : 'Unknown'}
303
+ </Text>
304
+ </View>
305
+ </View>
306
+ </CollapsibleSection>
307
+ </ScrollView>
308
+ )
309
+ }
310
+
311
+ const getTimeAgo = (timestamp) => {
312
+ const now = new Date()
313
+ const eventTime = new Date(timestamp)
314
+ const diffMs = now - eventTime
315
+ const diffSeconds = Math.floor(diffMs / 1000)
316
+ const diffMinutes = Math.floor(diffSeconds / 60)
317
+ const diffHours = Math.floor(diffMinutes / 60)
318
+
319
+ if (diffSeconds < 60) {
320
+ return `${diffSeconds} seconds ago`
321
+ } else if (diffMinutes < 60) {
322
+ return `${diffMinutes} minutes ago`
323
+ } else if (diffHours < 24) {
324
+ return `${diffHours} hours ago`
325
+ } else {
326
+ const diffDays = Math.floor(diffHours / 24)
327
+ return `${diffDays} days ago`
328
+ }
329
+ }
330
+
331
+ const styles = StyleSheet.create({
332
+ container: {
333
+ flex: 1,
334
+ backgroundColor: '#fff',
335
+ },
336
+ contentContainer: {
337
+ paddingBottom: 20,
338
+ },
339
+ header: {
340
+ flexDirection: 'row',
341
+ padding: 15,
342
+ borderBottomWidth: 1,
343
+ borderBottomColor: '#eee',
344
+ alignItems: 'flex-start',
345
+ },
346
+ headerInfo: {
347
+ flex: 1,
348
+ marginRight: 10,
349
+ },
350
+ badgeContainer: {
351
+ flexDirection: 'row',
352
+ alignItems: 'center',
353
+ marginBottom: 8,
354
+ },
355
+ eventBadge: {
356
+ paddingHorizontal: 12,
357
+ paddingVertical: 4,
358
+ borderRadius: 12,
359
+ color: 'white',
360
+ fontSize: 12,
361
+ fontWeight: 'bold',
362
+ marginRight: 8,
363
+ },
364
+ entityBadge: {
365
+ paddingHorizontal: 8,
366
+ paddingVertical: 2,
367
+ borderRadius: 8,
368
+ backgroundColor: '#f0f0f0',
369
+ color: '#333',
370
+ fontSize: 11,
371
+ fontWeight: '500',
372
+ },
373
+ entityName: {
374
+ fontSize: 16,
375
+ color: '#333',
376
+ marginBottom: 4,
377
+ fontWeight: '600',
378
+ },
379
+ operation: {
380
+ fontSize: 14,
381
+ color: '#666',
382
+ fontStyle: 'italic',
383
+ },
384
+ collapsibleSection: {
385
+ marginBottom: 1,
386
+ backgroundColor: '#fff',
387
+ },
388
+ sectionHeader: {
389
+ flexDirection: 'row',
390
+ justifyContent: 'space-between',
391
+ alignItems: 'center',
392
+ padding: 15,
393
+ backgroundColor: '#f5f5f5',
394
+ },
395
+ sectionTitle: {
396
+ fontSize: 15,
397
+ fontWeight: 'bold',
398
+ color: '#333',
399
+ },
400
+ expandIcon: {
401
+ fontSize: 14,
402
+ color: '#666',
403
+ },
404
+ content: {
405
+ padding: 15,
406
+ },
407
+ propertiesGrid: {
408
+ gap: 8,
409
+ },
410
+ propertyRow: {
411
+ marginBottom: 8,
412
+ },
413
+ propertyKey: {
414
+ fontSize: 14,
415
+ fontWeight: 'bold',
416
+ color: '#666',
417
+ marginBottom: 2,
418
+ },
419
+ propertyValue: {
420
+ fontSize: 14,
421
+ color: '#333',
422
+ },
423
+ errorContainer: {
424
+ flex: 1,
425
+ justifyContent: 'center',
426
+ alignItems: 'center',
427
+ padding: 20,
428
+ },
429
+ errorText: {
430
+ color: '#ff4444',
431
+ fontSize: 16,
432
+ },
433
+ dataSectionHeader: {
434
+ flexDirection: 'row',
435
+ justifyContent: 'space-between',
436
+ alignItems: 'center',
437
+ marginBottom: 8,
438
+ },
439
+ dataSectionTitle: {
440
+ fontSize: 14,
441
+ fontWeight: 'bold',
442
+ color: '#666',
443
+ },
444
+ dataContentWrapper: {
445
+ flex: 1,
446
+ },
447
+ dataContent: {
448
+ flex: 1,
449
+ backgroundColor: '#f8f9fa',
450
+ padding: 10,
451
+ borderRadius: 4,
452
+ borderWidth: 1,
453
+ borderColor: '#e9ecef',
454
+ },
455
+ copyButton: {
456
+ backgroundColor: '#e9ecef',
457
+ paddingHorizontal: 10,
458
+ paddingVertical: 5,
459
+ borderRadius: 4,
460
+ },
461
+ copyButtonText: {
462
+ fontSize: 12,
463
+ color: '#666',
464
+ },
465
+ jsonString: {
466
+ color: '#CB772F',
467
+ },
468
+ jsonNumber: {
469
+ color: '#2878D0',
470
+ },
471
+ jsonBoolean: {
472
+ color: '#2878D0',
473
+ fontWeight: 'bold',
474
+ },
475
+ jsonNull: {
476
+ color: '#A0A0A0',
477
+ fontStyle: 'italic',
478
+ },
479
+ })
480
+
481
+ export default TrackLogDetails
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-debug-toolkit",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "A simple yet powerful debugging toolkit for React Native with a convenient floating UI for development",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",