react-native-debug-toolkit 0.1.7 → 0.2.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.
- package/index.js +11 -2
- package/lib/DebugToolKit.js +22 -15
- package/lib/features/ConsoleLogFeature.js +73 -0
- package/lib/features/PerformanceFeature.js +390 -0
- package/lib/index.js +72 -2
- package/lib/utils/DebugConst.js +14 -1
- package/lib/views/ConsoleLogDetails.js +314 -0
- package/lib/views/FloatPanelView.js +11 -0
- package/lib/views/SubViewConsoleLogs.js +209 -0
- package/lib/views/SubViewPerformance.js +515 -0
- package/package.json +3 -2
- package/react-native.config.js +5 -0
- package/index.d.ts +0 -155
package/index.js
CHANGED
|
@@ -3,9 +3,18 @@
|
|
|
3
3
|
* A comprehensive debugging toolkit for React Native
|
|
4
4
|
*/
|
|
5
5
|
import DebugToolKit from './lib/DebugToolKit'
|
|
6
|
+
import { initializeDebugToolkit } from './lib'
|
|
7
|
+
|
|
6
8
|
import { createNetworkFeature } from './lib/features/NetworkFeature'
|
|
9
|
+
import { createPerformanceFeature } from './lib/features/PerformanceFeature'
|
|
10
|
+
import { createConsoleLogFeature } from './lib/features/ConsoleLogFeature'
|
|
7
11
|
|
|
8
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
DebugToolKit,
|
|
14
|
+
initializeDebugToolkit,
|
|
15
|
+
createNetworkFeature,
|
|
16
|
+
createPerformanceFeature,
|
|
17
|
+
createConsoleLogFeature,
|
|
18
|
+
}
|
|
9
19
|
|
|
10
20
|
export default DebugToolKit
|
|
11
|
-
|
package/lib/DebugToolKit.js
CHANGED
|
@@ -3,34 +3,42 @@ import { BackHandler, Pressable, Text } from 'react-native'
|
|
|
3
3
|
import RootSiblings from 'react-native-root-siblings'
|
|
4
4
|
import FloatPanelView from './views/FloatPanelView'
|
|
5
5
|
|
|
6
|
-
class DebugToolKit {
|
|
6
|
+
export default class DebugToolKit {
|
|
7
7
|
static instance = null
|
|
8
8
|
|
|
9
|
-
constructor() {
|
|
9
|
+
constructor(config = {}) {
|
|
10
10
|
if (DebugToolKit.instance) {
|
|
11
11
|
return DebugToolKit.instance
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
this.floatPanel = null
|
|
15
15
|
this.features = []
|
|
16
|
+
this.isPanelVisible = false
|
|
17
|
+
|
|
18
|
+
const featuresToLoad = config.features || []
|
|
19
|
+
|
|
20
|
+
featuresToLoad.forEach(featureOrCreator => {
|
|
21
|
+
const feature = typeof featureOrCreator === 'function'
|
|
22
|
+
? featureOrCreator()
|
|
23
|
+
: featureOrCreator
|
|
24
|
+
this.addFeature(feature)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (config.autoShow) {
|
|
28
|
+
this.showDebugPanel()
|
|
29
|
+
}
|
|
16
30
|
|
|
17
31
|
BackHandler.addEventListener('hardwareBackPress', () => true)
|
|
18
32
|
DebugToolKit.instance = this
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
addFeature(feature) {
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
console.error('Invalid feature format', feature)
|
|
28
|
-
return this
|
|
36
|
+
if (feature && typeof feature.name === 'string') {
|
|
37
|
+
this.features.push(feature)
|
|
38
|
+
feature.setup?.()
|
|
39
|
+
} else {
|
|
40
|
+
console.warn('[DebugToolKit] Invalid feature added:', feature)
|
|
29
41
|
}
|
|
30
|
-
|
|
31
|
-
feature.setup()
|
|
32
|
-
this.features.push(feature)
|
|
33
|
-
this.updateDebugPanel()
|
|
34
42
|
return this
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -44,6 +52,7 @@ class DebugToolKit {
|
|
|
44
52
|
<FloatPanelView features={this.features} close={this.hideDebugPanel} />,
|
|
45
53
|
)
|
|
46
54
|
}
|
|
55
|
+
this.isPanelVisible = true
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
hideDebugPanel = () => {
|
|
@@ -79,5 +88,3 @@ class DebugToolKit {
|
|
|
79
88
|
BackHandler.removeEventListener('hardwareBackPress')
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
|
-
|
|
83
|
-
export default DebugToolKit
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const MAX_LOGS = 100; // Max number of console logs to store
|
|
2
|
+
const logs = [];
|
|
3
|
+
const originalConsole = {}; // Store original console methods
|
|
4
|
+
|
|
5
|
+
const _interceptConsole = () => {
|
|
6
|
+
const levels = ['log', 'info', 'warn', 'error'];
|
|
7
|
+
levels.forEach(level => {
|
|
8
|
+
if (typeof console[level] === 'function') { // Check if it's actually a function
|
|
9
|
+
originalConsole[level] = console[level]; // Store original
|
|
10
|
+
|
|
11
|
+
console[level] = (...args) => {
|
|
12
|
+
// Call original console method first
|
|
13
|
+
originalConsole[level].apply(console, args); // Use apply for proper context
|
|
14
|
+
|
|
15
|
+
// Add log entry
|
|
16
|
+
if (logs.length >= MAX_LOGS) {
|
|
17
|
+
logs.shift(); // Remove the oldest log if limit is reached
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Store log data
|
|
21
|
+
logs.push({
|
|
22
|
+
timestamp: new Date(),
|
|
23
|
+
level: level,
|
|
24
|
+
data: args, // Store all arguments passed to console[level]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// TODO: Notify UI if needed
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const _restoreConsole = () => {
|
|
34
|
+
Object.keys(originalConsole).forEach(level => {
|
|
35
|
+
if (originalConsole[level]) {
|
|
36
|
+
console[level] = originalConsole[level];
|
|
37
|
+
delete originalConsole[level]; // Clean up stored method
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const setup = () => {
|
|
43
|
+
if (!__DEV__) {
|
|
44
|
+
return; // No need to return 'this' in a functional approach
|
|
45
|
+
}
|
|
46
|
+
_interceptConsole();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getData = () => {
|
|
50
|
+
return logs;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const cleanup = () => {
|
|
54
|
+
_restoreConsole();
|
|
55
|
+
logs.length = 0; // More efficient way to clear array
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Factory function remains similar but uses the module-level functions
|
|
59
|
+
export const createConsoleLogFeature = () => {
|
|
60
|
+
// Ensure setup is only called once if multiple features might be created (though unlikely for console)
|
|
61
|
+
// A simple flag could work here if needed, but maybe setup belongs in the main toolkit init.
|
|
62
|
+
return {
|
|
63
|
+
name: 'console',
|
|
64
|
+
label: 'Console Logs',
|
|
65
|
+
setup: setup, // Reference module-level function
|
|
66
|
+
getData: getData, // Reference module-level function
|
|
67
|
+
cleanup: cleanup, // Reference module-level function
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Note: This refactor removes the class structure entirely, relying on module scope
|
|
72
|
+
// for the "singleton" nature. Decide if this fits the overall pattern of other features.
|
|
73
|
+
// The original class-based singleton is also perfectly valid.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
// import SubViewPerformance from '../views/SubViewPerformance';
|
|
2
|
+
import performance, {
|
|
3
|
+
setResourceLoggingEnabled,
|
|
4
|
+
PerformanceObserver
|
|
5
|
+
} from 'react-native-performance';
|
|
6
|
+
|
|
7
|
+
class PerformanceFeature {
|
|
8
|
+
static instance = null;
|
|
9
|
+
static MAX_ENTRIES = 100;
|
|
10
|
+
|
|
11
|
+
// Native mark names based on vanilla example
|
|
12
|
+
static NATIVE_MARKS = {
|
|
13
|
+
NATIVE_LAUNCH_START: 'nativeLaunchStart',
|
|
14
|
+
NATIVE_LAUNCH_END: 'nativeLaunchEnd',
|
|
15
|
+
RUN_JS_BUNDLE_START: 'runJsBundleStart',
|
|
16
|
+
RUN_JS_BUNDLE_END: 'runJsBundleEnd',
|
|
17
|
+
CONTENT_APPEARED: 'contentAppeared'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
if (PerformanceFeature.instance) {
|
|
22
|
+
return PerformanceFeature.instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.name = 'performance';
|
|
26
|
+
this.label = 'Performance';
|
|
27
|
+
this.performanceData = {
|
|
28
|
+
measures: [],
|
|
29
|
+
metrics: [],
|
|
30
|
+
resources: [],
|
|
31
|
+
nativeMarks: []
|
|
32
|
+
};
|
|
33
|
+
this.observers = [];
|
|
34
|
+
this.isSetup = false;
|
|
35
|
+
this.thresholds = new Map(); // Performance thresholds for alerts
|
|
36
|
+
this.timeOrigin = performance.timeOrigin;
|
|
37
|
+
this.resourceLoggingEnabled = false;
|
|
38
|
+
|
|
39
|
+
PerformanceFeature.instance = this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setup(options = {}) {
|
|
43
|
+
if (!__DEV__ || this.isSetup) {
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { enableResourceLogging = true } = options;
|
|
48
|
+
|
|
49
|
+
// Enable resource logging if specified
|
|
50
|
+
if (enableResourceLogging) {
|
|
51
|
+
setResourceLoggingEnabled(true);
|
|
52
|
+
this.resourceLoggingEnabled = true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Mark when the feature is initialized
|
|
56
|
+
performance.mark('DebugToolkit.Init');
|
|
57
|
+
|
|
58
|
+
// Setup observers for different entry types
|
|
59
|
+
this._setupObservers();
|
|
60
|
+
|
|
61
|
+
// Measure app launch metrics
|
|
62
|
+
this._setupAppLaunchMeasurements();
|
|
63
|
+
|
|
64
|
+
this.isSetup = true;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_setupObservers() {
|
|
69
|
+
// Use the same pattern as in the vanilla example App.tsx
|
|
70
|
+
const setupObserver = (type, dataKey) => {
|
|
71
|
+
try {
|
|
72
|
+
const observer = new PerformanceObserver((list) => {
|
|
73
|
+
// Get entries and sort them by startTime as in the example
|
|
74
|
+
const entries = list.getEntries().sort((a, b) => a.startTime - b.startTime);
|
|
75
|
+
|
|
76
|
+
// Store entries
|
|
77
|
+
this._addEntries(dataKey, entries);
|
|
78
|
+
|
|
79
|
+
// Check thresholds for measures
|
|
80
|
+
if (type === 'measure') {
|
|
81
|
+
this._checkThresholds(entries);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Observe with buffered set to true to get existing entries
|
|
86
|
+
observer.observe({ type, buffered: true });
|
|
87
|
+
this.observers.push(observer);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error(`Failed to setup PerformanceObserver for type "${type}":`, e);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Setup observers for all entry types
|
|
94
|
+
setupObserver('mark', 'marks');
|
|
95
|
+
setupObserver('measure', 'measures');
|
|
96
|
+
setupObserver('metric', 'metrics');
|
|
97
|
+
setupObserver('resource', 'resources');
|
|
98
|
+
setupObserver('react-native-mark', 'nativeMarks');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_setupAppLaunchMeasurements() {
|
|
102
|
+
// Create a one-time observer for app launch measurements
|
|
103
|
+
try {
|
|
104
|
+
const observer = new PerformanceObserver((list, obs) => {
|
|
105
|
+
const entries = list.getEntries();
|
|
106
|
+
if (entries.some(entry => entry.name === PerformanceFeature.NATIVE_MARKS.RUN_JS_BUNDLE_END)) {
|
|
107
|
+
this._createLaunchMeasurements();
|
|
108
|
+
|
|
109
|
+
obs.disconnect();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
observer.observe({ type: 'react-native-mark', buffered: true });
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error('Failed to setup app launch measurements:', e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_createLaunchMeasurements() {
|
|
119
|
+
const {
|
|
120
|
+
NATIVE_LAUNCH_START,
|
|
121
|
+
NATIVE_LAUNCH_END,
|
|
122
|
+
RUN_JS_BUNDLE_START,
|
|
123
|
+
RUN_JS_BUNDLE_END,
|
|
124
|
+
CONTENT_APPEARED
|
|
125
|
+
} = PerformanceFeature.NATIVE_MARKS;
|
|
126
|
+
|
|
127
|
+
// Measure app launch time (native initialization)
|
|
128
|
+
if (this.hasMark(NATIVE_LAUNCH_START) && this.hasMark(NATIVE_LAUNCH_END)) {
|
|
129
|
+
this.measure('nativeLaunch', NATIVE_LAUNCH_START, NATIVE_LAUNCH_END);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Measure JS bundle execution time
|
|
133
|
+
if (this.hasMark(RUN_JS_BUNDLE_START) && this.hasMark(RUN_JS_BUNDLE_END)) {
|
|
134
|
+
this.measure('runJsBundle', RUN_JS_BUNDLE_START, RUN_JS_BUNDLE_END);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Measure time to first render if content appeared mark exists
|
|
138
|
+
if (this.hasMark(NATIVE_LAUNCH_START) && this.hasMark(CONTENT_APPEARED)) {
|
|
139
|
+
this.measure('timeToContent', NATIVE_LAUNCH_START, CONTENT_APPEARED);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_addEntries(type, entries) {
|
|
144
|
+
if (!entries || entries.length === 0) return;
|
|
145
|
+
|
|
146
|
+
// Keep only the last MAX_ENTRIES entries
|
|
147
|
+
this.performanceData[type] = [
|
|
148
|
+
...entries,
|
|
149
|
+
...this.performanceData[type]
|
|
150
|
+
].slice(0, PerformanceFeature.MAX_ENTRIES);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
_checkThresholds(entries) {
|
|
154
|
+
if (!entries || entries.length === 0 || this.thresholds.size === 0) return;
|
|
155
|
+
|
|
156
|
+
entries.forEach(entry => {
|
|
157
|
+
const threshold = this.thresholds.get(entry.name);
|
|
158
|
+
if (threshold && entry.duration > threshold.value) {
|
|
159
|
+
console.warn(`Performance threshold exceeded: ${entry.name} took ${entry.duration}ms (threshold: ${threshold.value}ms)`);
|
|
160
|
+
if (threshold.callback) {
|
|
161
|
+
threshold.callback(entry);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
hasMark(name) {
|
|
168
|
+
try {
|
|
169
|
+
return performance.getEntriesByName(name, 'mark').length > 0 ||
|
|
170
|
+
performance.getEntriesByName(name, 'react-native-mark').length > 0;
|
|
171
|
+
} catch (e) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Convert performance timestamp to unix epoch timestamp
|
|
177
|
+
// Based on example: Date.now() - performance.timeOrigin + entry.startTime
|
|
178
|
+
getUnixTimestamp(entry) {
|
|
179
|
+
if (!entry || typeof entry.startTime !== 'number') return null;
|
|
180
|
+
return Date.now() - this.timeOrigin + entry.startTime;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Enable or disable resource logging at runtime
|
|
184
|
+
setResourceLoggingEnabled(enabled) {
|
|
185
|
+
if (this.resourceLoggingEnabled !== enabled) {
|
|
186
|
+
setResourceLoggingEnabled(enabled);
|
|
187
|
+
this.resourceLoggingEnabled = enabled;
|
|
188
|
+
}
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Threshold management
|
|
193
|
+
setThreshold(name, value, callback) {
|
|
194
|
+
this.thresholds.set(name, { value, callback });
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
removeThreshold(name) {
|
|
199
|
+
this.thresholds.delete(name);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
clearThresholds() {
|
|
204
|
+
this.thresholds.clear();
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Mark events (following pattern from vanilla example)
|
|
209
|
+
mark(name, detail = {}) {
|
|
210
|
+
if (!__DEV__ || !this.isSetup) return this;
|
|
211
|
+
|
|
212
|
+
if (typeof detail !== 'object') {
|
|
213
|
+
detail = { value: detail };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
performance.mark(name, { detail });
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
measure(name, startMarkOrOptions, endMark, detail = {}) {
|
|
221
|
+
if (!__DEV__ || !this.isSetup) return this;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
if (typeof startMarkOrOptions === 'string' && endMark) {
|
|
225
|
+
performance.measure(name, startMarkOrOptions, endMark, { detail });
|
|
226
|
+
} else if (typeof startMarkOrOptions === 'object') {
|
|
227
|
+
performance.measure(name, startMarkOrOptions);
|
|
228
|
+
} else {
|
|
229
|
+
performance.measure(name, { detail });
|
|
230
|
+
}
|
|
231
|
+
} catch (e) {
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Record custom metrics (following pattern from vanilla example)
|
|
238
|
+
metric(name, value, detail = {}) {
|
|
239
|
+
if (!__DEV__ || !this.isSetup) return this;
|
|
240
|
+
|
|
241
|
+
if (typeof detail !== 'object') {
|
|
242
|
+
detail = { info: detail };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
performance.metric(name, value, { detail });
|
|
246
|
+
return this;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Measure execution time of a function
|
|
250
|
+
measureFunction(name, fn, ...args) {
|
|
251
|
+
if (!__DEV__ || !this.isSetup) {
|
|
252
|
+
return fn(...args);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const markName = `${name}_start`;
|
|
256
|
+
this.mark(markName);
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const result = fn(...args);
|
|
260
|
+
|
|
261
|
+
// Handle promises
|
|
262
|
+
if (result instanceof Promise) {
|
|
263
|
+
return result.finally(() => {
|
|
264
|
+
this.measure(name, markName);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle synchronous functions
|
|
269
|
+
this.measure(name, markName);
|
|
270
|
+
return result;
|
|
271
|
+
} catch (e) {
|
|
272
|
+
this.measure(`${name}_error`, markName, undefined, { error: e.message });
|
|
273
|
+
throw e;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get formatted performance data
|
|
278
|
+
getData() {
|
|
279
|
+
return this.performanceData;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getTimingData(options = {}) {
|
|
283
|
+
const { includeMarks = true, includeMeasures = true, filterByName } = options;
|
|
284
|
+
|
|
285
|
+
let data = [];
|
|
286
|
+
|
|
287
|
+
if (includeMarks) {
|
|
288
|
+
data = [...data, ...this.performanceData.marks, ...this.performanceData.nativeMarks];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (includeMeasures) {
|
|
292
|
+
data = [...data, ...this.performanceData.measures];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (filterByName) {
|
|
296
|
+
if (filterByName instanceof RegExp) {
|
|
297
|
+
data = data.filter(entry => filterByName.test(entry.name));
|
|
298
|
+
} else if (typeof filterByName === 'string') {
|
|
299
|
+
data = data.filter(entry => entry.name.includes(filterByName));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return data.sort((a, b) => a.startTime - b.startTime);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Get network resource data (similar to the demo pattern)
|
|
307
|
+
getResourceData() {
|
|
308
|
+
return this.performanceData.resources.sort((a, b) => a.startTime - b.startTime);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Trigger a test network request (for demo purposes)
|
|
312
|
+
generateTestNetworkRequest() {
|
|
313
|
+
if (!__DEV__ || !this.isSetup || !this.resourceLoggingEnabled) return this;
|
|
314
|
+
|
|
315
|
+
// Similar to the fetch in the vanilla example
|
|
316
|
+
fetch('https://jsonplaceholder.typicode.com/todos/1', { cache: 'no-cache' })
|
|
317
|
+
.catch(err => console.error('Test network request failed:', err));
|
|
318
|
+
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Clear specific type of entries
|
|
323
|
+
clear(type) {
|
|
324
|
+
if (this.performanceData[type]) {
|
|
325
|
+
this.performanceData[type] = [];
|
|
326
|
+
}
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Clear all performance data
|
|
331
|
+
clearAll() {
|
|
332
|
+
Object.keys(this.performanceData).forEach(key => {
|
|
333
|
+
this.performanceData[key] = [];
|
|
334
|
+
});
|
|
335
|
+
return this;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
cleanup() {
|
|
339
|
+
// Disconnect all observers
|
|
340
|
+
this.observers.forEach(observer => {
|
|
341
|
+
observer.disconnect();
|
|
342
|
+
});
|
|
343
|
+
this.observers = [];
|
|
344
|
+
|
|
345
|
+
// Disable resource logging if it was enabled
|
|
346
|
+
if (this.resourceLoggingEnabled) {
|
|
347
|
+
setResourceLoggingEnabled(false);
|
|
348
|
+
this.resourceLoggingEnabled = false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Clear stored data
|
|
352
|
+
this.clearAll();
|
|
353
|
+
this.clearThresholds();
|
|
354
|
+
|
|
355
|
+
this.isSetup = false;
|
|
356
|
+
return this;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export const createPerformanceFeature = () => {
|
|
361
|
+
const feature = new PerformanceFeature();
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
name: feature.name,
|
|
365
|
+
label: feature.label,
|
|
366
|
+
setup: (options) => feature.setup(options),
|
|
367
|
+
getData: () => feature.getData(),
|
|
368
|
+
getTimingData: (options) => feature.getTimingData(options),
|
|
369
|
+
getResourceData: () => feature.getResourceData(),
|
|
370
|
+
getUnixTimestamp: (entry) => feature.getUnixTimestamp(entry),
|
|
371
|
+
hasMark: (name) => feature.hasMark(name),
|
|
372
|
+
setResourceLoggingEnabled: (enabled) => feature.setResourceLoggingEnabled(enabled),
|
|
373
|
+
generateTestNetworkRequest: () => feature.generateTestNetworkRequest(),
|
|
374
|
+
cleanup: () => feature.cleanup(),
|
|
375
|
+
mark: (name, detail) => feature.mark(name, detail),
|
|
376
|
+
measure: (name, startMarkOrOptions, endMark, detail) =>
|
|
377
|
+
feature.measure(name, startMarkOrOptions, endMark, detail),
|
|
378
|
+
metric: (name, value, detail) => feature.metric(name, value, detail),
|
|
379
|
+
measureFunction: (name, fn, ...args) => feature.measureFunction(name, fn, ...args),
|
|
380
|
+
clear: (type) => feature.clear(type),
|
|
381
|
+
clearAll: () => feature.clearAll(),
|
|
382
|
+
setThreshold: (name, value, callback) => feature.setThreshold(name, value, callback),
|
|
383
|
+
removeThreshold: (name) => feature.removeThreshold(name),
|
|
384
|
+
clearThresholds: () => feature.clearThresholds(),
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// For backward compatibility
|
|
389
|
+
const instance = new PerformanceFeature();
|
|
390
|
+
export default instance;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
import DebugToolKit from './DebugToolKit'
|
|
2
2
|
import { createNetworkFeature } from './features/NetworkFeature'
|
|
3
|
+
import { createPerformanceFeature} from './features/PerformanceFeature'
|
|
4
|
+
import { createConsoleLogFeature } from './features/ConsoleLogFeature'
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
// Registry mapping feature names (strings) to their creator functions
|
|
7
|
+
const featureRegistry = {
|
|
8
|
+
network: createNetworkFeature,
|
|
9
|
+
console: createConsoleLogFeature,
|
|
10
|
+
performance: createPerformanceFeature,
|
|
11
|
+
// Add other built-in features here
|
|
12
|
+
// 'storage': createStorageFeature,
|
|
13
|
+
}
|
|
5
14
|
|
|
6
|
-
|
|
15
|
+
// List of default features (can use strings now)
|
|
16
|
+
const defaultFeatures = ['network', 'console']
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initializes and shows the Debug ToolKit panel with specified features.
|
|
20
|
+
* Only runs in development mode (__DEV__ === true).
|
|
21
|
+
* Features can be specified as strings (e.g., 'network') or creator functions.
|
|
22
|
+
* @param {Array<string|Function>} features - Array of feature names (strings) or creator functions. Defaults to standard features.
|
|
23
|
+
*/
|
|
24
|
+
export function initializeDebugToolkit(features = defaultFeatures) {
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const debugToolKit = new DebugToolKit();
|
|
28
|
+
|
|
29
|
+
features.forEach(featureIdentifier => {
|
|
30
|
+
let feature = null;
|
|
31
|
+
let featureCreator = null;
|
|
32
|
+
|
|
33
|
+
if (typeof featureIdentifier === 'string') {
|
|
34
|
+
// It's a string, look it up in the registry
|
|
35
|
+
featureCreator = featureRegistry[featureIdentifier];
|
|
36
|
+
if (!featureCreator) {
|
|
37
|
+
console.warn(`[DebugToolKit] Unknown feature identifier string: "${featureIdentifier}". Skipping.`);
|
|
38
|
+
return; // Skip this identifier
|
|
39
|
+
}
|
|
40
|
+
feature = featureCreator();
|
|
41
|
+
} else if (typeof featureIdentifier === 'function') {
|
|
42
|
+
// It's a function, assume it's a creator
|
|
43
|
+
featureCreator = featureIdentifier;
|
|
44
|
+
try {
|
|
45
|
+
feature = featureCreator();
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(`[DebugToolKit] Error calling feature creator function:`, e);
|
|
48
|
+
return; // Skip if creator fails
|
|
49
|
+
}
|
|
50
|
+
} else if (typeof featureIdentifier === 'object' && featureIdentifier !== null && featureIdentifier.name) {
|
|
51
|
+
// It might be a pre-created feature object
|
|
52
|
+
feature = featureIdentifier;
|
|
53
|
+
console.warn(`[DebugToolKit] Passing pre-created feature objects is supported but using strings or creators is recommended.`);
|
|
54
|
+
} else {
|
|
55
|
+
console.warn('[DebugToolKit] Invalid feature identifier type:', typeof featureIdentifier, featureIdentifier, '. Skipping.');
|
|
56
|
+
return; // Skip invalid types
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (feature) {
|
|
60
|
+
debugToolKit.addFeature(feature);
|
|
61
|
+
} else {
|
|
62
|
+
// This case might happen if a creator function returns null/undefined
|
|
63
|
+
console.warn(`[DebugToolKit] Feature creator for identifier "${typeof featureIdentifier === 'string' ? featureIdentifier : 'custom function'}" did not return a valid feature object.`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
debugToolKit.showDebugPanel();
|
|
68
|
+
return debugToolKit; // Return instance if needed elsewhere
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error('[DebugToolKit] Failed to initialize:', error);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Export existing stuff and the new initializer
|
|
76
|
+
export { DebugToolKit, createNetworkFeature, createPerformanceFeature, createConsoleLogFeature, featureRegistry };
|
package/lib/utils/DebugConst.js
CHANGED
|
@@ -16,7 +16,20 @@ export const DebugColors = {
|
|
|
16
16
|
error: '#FF3B30',
|
|
17
17
|
warning: '#FF9500'
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
export const getLogLevelColor = (level) => {
|
|
20
|
+
switch (level?.toLowerCase()) {
|
|
21
|
+
case 'log':
|
|
22
|
+
return '#666';
|
|
23
|
+
case 'info':
|
|
24
|
+
return '#0D96F2';
|
|
25
|
+
case 'warn':
|
|
26
|
+
return '#FCA130';
|
|
27
|
+
case 'error':
|
|
28
|
+
return '#F93E3E';
|
|
29
|
+
default:
|
|
30
|
+
return '#666666';
|
|
31
|
+
}
|
|
32
|
+
};
|
|
20
33
|
export const DebugImgs = {
|
|
21
34
|
iconLink: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVGhD7ZgxDoMwDEV9/xvQtReoGBASYmFhYmNiYmNhYWJiQQIJqUh8y05wlAYw+T/pD5Dg9xwnhPR6vV6v1+v1er3eGvuOb7yIhxEQvjAC4hRGQJzGCIhLGAFxGSMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIWxgBcQsjIG5hBMQtjIC4hREQtzAC4hZGQNzCCIhbGAFxCyMgbmEExC2MgLiFERC3MALiFkZA3MIIiFsYAXELIyBuYQTELYyAuIURELcwAuIWRkDcwgiIW/+PYBgfhPtL9WBrfKUAAAAASUVORK5CYII='
|
|
22
35
|
};
|