react-native-debug-toolkit 3.1.3 → 3.1.5
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/README.md +83 -65
- package/README.zh-CN.md +82 -64
- package/bin/debug-toolkit.js +10 -2
- package/lib/commonjs/core/DebugToolkit.js +118 -97
- package/lib/commonjs/core/DebugToolkit.js.map +1 -1
- package/lib/commonjs/core/initialize.js +4 -4
- package/lib/commonjs/core/initialize.js.map +1 -1
- package/lib/commonjs/features/environment/index.js +22 -24
- package/lib/commonjs/features/environment/index.js.map +1 -1
- package/lib/commonjs/features/network/NetworkLogTab.js +7 -3
- package/lib/commonjs/features/network/NetworkLogTab.js.map +1 -1
- package/lib/commonjs/features/network/index.js +25 -47
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +3 -3
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/commonjs/index.js +0 -30
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/DaemonClient.js +37 -51
- package/lib/commonjs/utils/DaemonClient.js.map +1 -1
- package/lib/commonjs/utils/createChannelFeature.js +8 -1
- package/lib/commonjs/utils/createChannelFeature.js.map +1 -1
- package/lib/commonjs/utils/deviceReport.js +3 -1
- package/lib/commonjs/utils/deviceReport.js.map +1 -1
- package/lib/commonjs/utils/urlRewriter.js +15 -0
- package/lib/commonjs/utils/urlRewriter.js.map +1 -0
- package/lib/module/core/DebugToolkit.js +117 -96
- package/lib/module/core/DebugToolkit.js.map +1 -1
- package/lib/module/core/initialize.js +6 -7
- package/lib/module/core/initialize.js.map +1 -1
- package/lib/module/features/environment/index.js +22 -24
- package/lib/module/features/environment/index.js.map +1 -1
- package/lib/module/features/network/NetworkLogTab.js +7 -3
- package/lib/module/features/network/NetworkLogTab.js.map +1 -1
- package/lib/module/features/network/index.js +25 -46
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +3 -3
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/DaemonClient.js +38 -42
- package/lib/module/utils/DaemonClient.js.map +1 -1
- package/lib/module/utils/createChannelFeature.js +8 -1
- package/lib/module/utils/createChannelFeature.js.map +1 -1
- package/lib/module/utils/deviceReport.js +4 -2
- package/lib/module/utils/deviceReport.js.map +1 -1
- package/lib/module/utils/urlRewriter.js +10 -0
- package/lib/module/utils/urlRewriter.js.map +1 -0
- package/lib/typescript/src/core/DebugToolkit.d.ts +23 -10
- package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
- package/lib/typescript/src/core/initialize.d.ts.map +1 -1
- package/lib/typescript/src/features/environment/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/NetworkLogTab.d.ts.map +1 -1
- package/lib/typescript/src/features/network/index.d.ts +3 -3
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/feature.d.ts +5 -0
- package/lib/typescript/src/types/feature.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +1 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/DaemonClient.d.ts +5 -11
- package/lib/typescript/src/utils/DaemonClient.d.ts.map +1 -1
- package/lib/typescript/src/utils/createChannelFeature.d.ts +4 -0
- package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -1
- package/lib/typescript/src/utils/deviceReport.d.ts +10 -1
- package/lib/typescript/src/utils/deviceReport.d.ts.map +1 -1
- package/lib/typescript/src/utils/urlRewriter.d.ts +5 -0
- package/lib/typescript/src/utils/urlRewriter.d.ts.map +1 -0
- package/node/daemon/src/console/console.html +197 -27
- package/node/daemon/src/server.js +32 -2
- package/node/daemon/src/store.js +45 -6
- package/node/mcp/src/logs.js +15 -4
- package/node/mcp/src/tools.js +4 -2
- package/package.json +6 -2
- package/src/core/DebugToolkit.tsx +119 -105
- package/src/core/initialize.ts +7 -8
- package/src/features/environment/index.ts +25 -27
- package/src/features/network/NetworkLogTab.tsx +6 -3
- package/src/features/network/index.ts +30 -52
- package/src/features/network/networkInterceptor.ts +3 -3
- package/src/index.ts +3 -8
- package/src/types/feature.ts +6 -0
- package/src/types/index.ts +1 -0
- package/src/utils/DaemonClient.ts +39 -56
- package/src/utils/createChannelFeature.ts +12 -1
- package/src/utils/deviceReport.ts +12 -3
- package/src/utils/urlRewriter.ts +11 -0
- package/lib/commonjs/utils/urlRewriterRegistry.js +0 -14
- package/lib/commonjs/utils/urlRewriterRegistry.js.map +0 -1
- package/lib/module/utils/urlRewriterRegistry.js +0 -10
- package/lib/module/utils/urlRewriterRegistry.js.map +0 -1
- package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +0 -7
- package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +0 -1
- package/src/utils/urlRewriterRegistry.ts +0 -10
|
@@ -140,6 +140,25 @@ function toDevicePayload(deviceLog) {
|
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
function stripBodies(value, parentKey) {
|
|
144
|
+
if (Array.isArray(value)) {
|
|
145
|
+
return value.map((item) => stripBodies(item, parentKey));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!value || typeof value !== 'object') {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return Object.entries(value).reduce((acc, [key, child]) => {
|
|
153
|
+
const normalizedKey = key.toLowerCase();
|
|
154
|
+
if (normalizedKey === 'body' || (parentKey === 'response' && normalizedKey === 'data')) {
|
|
155
|
+
return acc;
|
|
156
|
+
}
|
|
157
|
+
acc[key] = stripBodies(child, normalizedKey);
|
|
158
|
+
return acc;
|
|
159
|
+
}, {});
|
|
160
|
+
}
|
|
161
|
+
|
|
143
162
|
function selectLogs(deviceLog, searchParams) {
|
|
144
163
|
if (!deviceLog) {
|
|
145
164
|
return [];
|
|
@@ -150,6 +169,8 @@ function selectLogs(deviceLog, searchParams) {
|
|
|
150
169
|
const limit = Number.isFinite(limitParam) && limitParam > 0
|
|
151
170
|
? Math.min(Math.floor(limitParam), 500)
|
|
152
171
|
: 50;
|
|
172
|
+
const entryId = searchParams.get('entryId');
|
|
173
|
+
const includeBodies = entryId ? true : searchParams.get('includeBodies') === 'true';
|
|
153
174
|
const failedOnly = searchParams.get('failedOnly') === 'true';
|
|
154
175
|
const logs = deviceLog.report.logs || {};
|
|
155
176
|
|
|
@@ -160,7 +181,12 @@ function selectLogs(deviceLog, searchParams) {
|
|
|
160
181
|
entries = Object.values(logs).flatMap((value) => Array.isArray(value) ? value : []);
|
|
161
182
|
}
|
|
162
183
|
|
|
163
|
-
if (
|
|
184
|
+
if (entryId) {
|
|
185
|
+
entries = entries.filter((entry) => (
|
|
186
|
+
entry && typeof entry === 'object' &&
|
|
187
|
+
(entry.id === entryId || entry.id === Number(entryId))
|
|
188
|
+
));
|
|
189
|
+
} else if (failedOnly) {
|
|
164
190
|
entries = entries.filter((entry) => (
|
|
165
191
|
entry &&
|
|
166
192
|
typeof entry === 'object' &&
|
|
@@ -173,7 +199,11 @@ function selectLogs(deviceLog, searchParams) {
|
|
|
173
199
|
));
|
|
174
200
|
}
|
|
175
201
|
|
|
176
|
-
|
|
202
|
+
if (!entryId) {
|
|
203
|
+
entries = entries.slice(-limit);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return includeBodies ? entries : entries.map(stripBodies);
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
function broadcastSSE(clients, eventType, deviceLog, delta) {
|
package/node/daemon/src/store.js
CHANGED
|
@@ -28,15 +28,33 @@ function slugPart(value) {
|
|
|
28
28
|
.slice(0, 80) || 'unknown';
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function ipTail(ip) {
|
|
32
|
+
if (!ip || typeof ip !== 'string') return '0';
|
|
33
|
+
const parts = ip.split('.');
|
|
34
|
+
return parts.length >= 2 ? parts[parts.length - 1] : slugPart(ip);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isSimulatorIp(ip) {
|
|
38
|
+
return ip === '127.0.0.1' || ip === '::1' || ip === '10.0.2.2' || ip === 'localhost';
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
function createDeviceId(report, source) {
|
|
32
42
|
const device = report && typeof report === 'object' && report.device && typeof report.device === 'object'
|
|
33
43
|
? report.device
|
|
34
44
|
: {};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
const platform = slugPart(device.platform);
|
|
46
|
+
const ip = source && source.ip ? String(source.ip) : '';
|
|
47
|
+
const sim = isSimulatorIp(ip);
|
|
48
|
+
let model = slugPart(device.model);
|
|
49
|
+
if (model === 'unknown' && platform !== 'unknown') {
|
|
50
|
+
model = sim ? 'sim' : 'device';
|
|
51
|
+
}
|
|
52
|
+
const ver = device.appVersion ? slugPart(device.appVersion) : '';
|
|
53
|
+
const tail = sim ? 'sim' : ipTail(ip);
|
|
54
|
+
const parts = [platform, model];
|
|
55
|
+
if (ver && ver !== 'unknown') parts.push(ver);
|
|
56
|
+
parts.push(tail);
|
|
57
|
+
return parts.join('_');
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
function readPersistedDevices(storagePath, maxDevices) {
|
|
@@ -92,6 +110,18 @@ function createMemoryStore(options = {}) {
|
|
|
92
110
|
const deviceId = createDeviceId(report, source);
|
|
93
111
|
const existingIndex = devices.findIndex((item) => item.deviceId === deviceId);
|
|
94
112
|
const existing = existingIndex >= 0 ? devices[existingIndex] : null;
|
|
113
|
+
const reportSessionId = report.session ? report.session.id : null;
|
|
114
|
+
if (reportSessionId && report.logs) {
|
|
115
|
+
Object.entries(report.logs).forEach(function(pair) {
|
|
116
|
+
if (!Array.isArray(pair[1])) return;
|
|
117
|
+
report.logs[pair[0]] = pair[1].map(function(entry) {
|
|
118
|
+
if (entry && typeof entry === 'object' && !entry.sessionId) {
|
|
119
|
+
return Object.assign({}, entry, { sessionId: reportSessionId });
|
|
120
|
+
}
|
|
121
|
+
return entry;
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
95
125
|
const deviceLog = {
|
|
96
126
|
deviceId,
|
|
97
127
|
firstSeenAt: existing ? existing.firstSeenAt : receivedAt,
|
|
@@ -99,6 +129,7 @@ function createMemoryStore(options = {}) {
|
|
|
99
129
|
receivedAt,
|
|
100
130
|
source,
|
|
101
131
|
device: report.device || null,
|
|
132
|
+
session: report.session || null,
|
|
102
133
|
report,
|
|
103
134
|
logCount: createLogCount(report),
|
|
104
135
|
};
|
|
@@ -124,6 +155,7 @@ function createMemoryStore(options = {}) {
|
|
|
124
155
|
}
|
|
125
156
|
|
|
126
157
|
const deltaLogs = (delta && delta.logs) || {};
|
|
158
|
+
const currentSessionId = deviceLog.session ? deviceLog.session.id : null;
|
|
127
159
|
Object.entries(deltaLogs).forEach(([type, entries]) => {
|
|
128
160
|
if (!Array.isArray(entries)) {
|
|
129
161
|
return;
|
|
@@ -131,7 +163,13 @@ function createMemoryStore(options = {}) {
|
|
|
131
163
|
if (!deviceLog.report.logs[type]) {
|
|
132
164
|
deviceLog.report.logs[type] = [];
|
|
133
165
|
}
|
|
134
|
-
|
|
166
|
+
const tagged = entries.map(function(entry) {
|
|
167
|
+
if (entry && typeof entry === 'object' && currentSessionId && !entry.sessionId) {
|
|
168
|
+
return Object.assign({}, entry, { sessionId: currentSessionId });
|
|
169
|
+
}
|
|
170
|
+
return entry;
|
|
171
|
+
});
|
|
172
|
+
deviceLog.report.logs[type].push(...tagged);
|
|
135
173
|
});
|
|
136
174
|
|
|
137
175
|
deviceLog.lastSeenAt = new Date(Date.now()).toISOString();
|
|
@@ -153,6 +191,7 @@ function createMemoryStore(options = {}) {
|
|
|
153
191
|
receivedAt: deviceLog.receivedAt,
|
|
154
192
|
device: deviceLog.device || null,
|
|
155
193
|
source: deviceLog.source || null,
|
|
194
|
+
session: deviceLog.session || null,
|
|
156
195
|
logCount: deviceLog.logCount,
|
|
157
196
|
}));
|
|
158
197
|
}
|
package/node/mcp/src/logs.js
CHANGED
|
@@ -40,7 +40,8 @@ function selectLogs(report, options = {}) {
|
|
|
40
40
|
const limit = Number.isFinite(options.limit) && options.limit > 0
|
|
41
41
|
? Math.min(Math.floor(options.limit), 200)
|
|
42
42
|
: 50;
|
|
43
|
-
const
|
|
43
|
+
const entryId = options.entryId;
|
|
44
|
+
const includeBodies = entryId ? true : options.includeBodies === true;
|
|
44
45
|
const failedOnly = options.failedOnly === true;
|
|
45
46
|
|
|
46
47
|
let entries;
|
|
@@ -54,11 +55,18 @@ function selectLogs(report, options = {}) {
|
|
|
54
55
|
));
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
if (
|
|
58
|
+
if (entryId) {
|
|
59
|
+
entries = entries.filter((item) => {
|
|
60
|
+
const e = item.entry || item;
|
|
61
|
+
return e.id === entryId || e.id === Number(entryId);
|
|
62
|
+
});
|
|
63
|
+
} else if (failedOnly) {
|
|
58
64
|
entries = entries.filter((item) => isFailedLog(item.entry || item));
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
if (!entryId) {
|
|
68
|
+
entries = entries.slice(-limit);
|
|
69
|
+
}
|
|
62
70
|
|
|
63
71
|
if (logType) {
|
|
64
72
|
return includeBodies ? entries : entries.map(stripBodies);
|
|
@@ -72,6 +80,8 @@ function selectLogs(report, options = {}) {
|
|
|
72
80
|
|
|
73
81
|
function createToolPayload(device, options = {}) {
|
|
74
82
|
const report = device.report || { version: 2, logs: {} };
|
|
83
|
+
const entryId = options.entryId;
|
|
84
|
+
const includeBodies = entryId ? true : options.includeBodies === true;
|
|
75
85
|
const logs = selectLogs(report, options);
|
|
76
86
|
|
|
77
87
|
return {
|
|
@@ -81,7 +91,8 @@ function createToolPayload(device, options = {}) {
|
|
|
81
91
|
lastSeenAt: device.lastSeenAt,
|
|
82
92
|
logType: options.logType || 'all',
|
|
83
93
|
failedOnly: options.failedOnly === true,
|
|
84
|
-
includeBodies
|
|
94
|
+
includeBodies,
|
|
95
|
+
entryId: entryId || undefined,
|
|
85
96
|
count: logs.length,
|
|
86
97
|
logs,
|
|
87
98
|
};
|
package/node/mcp/src/tools.js
CHANGED
|
@@ -5,7 +5,7 @@ const { KNOWN_LOG_TYPES, createToolPayload } = require('./logs');
|
|
|
5
5
|
|
|
6
6
|
const getAppLogsTool = {
|
|
7
7
|
name: 'get_app_logs',
|
|
8
|
-
description: 'Read React Native Debug Toolkit logs from the local daemon. Tip: if you have shell access, curl http://127.0.0.1:3799/devices/latest is more efficient.',
|
|
8
|
+
description: 'Read React Native Debug Toolkit logs from the local daemon. Bodies are excluded by default to reduce token usage; set includeBodies=true or pass an entryId to fetch details. Tip: if you have shell access, curl http://127.0.0.1:3799/devices/latest/logs?includeBodies=true is more efficient.',
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: 'object',
|
|
11
11
|
properties: {
|
|
@@ -16,7 +16,8 @@ const getAppLogsTool = {
|
|
|
16
16
|
},
|
|
17
17
|
limit: { type: 'number', default: 50 },
|
|
18
18
|
failedOnly: { type: 'boolean', default: false },
|
|
19
|
-
includeBodies: { type: 'boolean', default:
|
|
19
|
+
includeBodies: { type: 'boolean', default: false },
|
|
20
|
+
entryId: { type: 'string', description: 'Fetch a single entry by ID; forces includeBodies=true' },
|
|
20
21
|
},
|
|
21
22
|
},
|
|
22
23
|
};
|
|
@@ -66,6 +67,7 @@ async function callTool(name, args = {}, context = {}) {
|
|
|
66
67
|
limit: args.limit,
|
|
67
68
|
failedOnly: args.failedOnly,
|
|
68
69
|
includeBodies: args.includeBodies,
|
|
70
|
+
entryId: args.entryId,
|
|
69
71
|
});
|
|
70
72
|
} catch (error) {
|
|
71
73
|
return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-debug-toolkit",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "A local-first React Native
|
|
3
|
+
"version": "3.1.5",
|
|
4
|
+
"description": "A local-first React Native debug toolkit with Web Console, HTTP API, and MCP support for AI-readable app logs",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
7
7
|
"types": "lib/typescript/src/index.d.ts",
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
"keywords": [
|
|
31
31
|
"react-native",
|
|
32
32
|
"debug",
|
|
33
|
+
"log-bridge",
|
|
34
|
+
"web-console",
|
|
35
|
+
"ai-debugging",
|
|
33
36
|
"toolkit",
|
|
34
37
|
"http-inspector",
|
|
38
|
+
"mcp",
|
|
35
39
|
"development-tools",
|
|
36
40
|
"floating-panel"
|
|
37
41
|
],
|
|
@@ -1,176 +1,190 @@
|
|
|
1
|
-
import type { AnyDebugFeature } from '../types';
|
|
1
|
+
import type { AnyDebugFeature, DebugFeatureListener, FeatureDataProvider } from '../types';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
class DebugToolkitCore implements FeatureDataProvider {
|
|
4
|
+
private _features: AnyDebugFeature[] = [];
|
|
5
|
+
private _launcherVisible = false;
|
|
6
|
+
private _panelOpen = false;
|
|
7
|
+
private _enabled = true;
|
|
8
|
+
private _listeners = new Set<DebugFeatureListener>();
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let _panelOpen = false;
|
|
9
|
-
let _enabled = true;
|
|
10
|
+
private notify(): void {
|
|
11
|
+
this._listeners.forEach((l) => l());
|
|
12
|
+
}
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
listeners.forEach((l) => l());
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function setupFeature(feature: AnyDebugFeature): void {
|
|
16
|
-
feature.setup?.();
|
|
17
|
-
}
|
|
14
|
+
// --- FeatureDataProvider ---
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
16
|
+
get features(): AnyDebugFeature[] {
|
|
17
|
+
return [...this._features];
|
|
18
|
+
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
listeners.add(listener);
|
|
20
|
+
subscribe(listener: DebugFeatureListener): () => void {
|
|
21
|
+
this._listeners.add(listener);
|
|
26
22
|
return () => {
|
|
27
|
-
|
|
23
|
+
this._listeners.delete(listener);
|
|
28
24
|
};
|
|
29
|
-
}
|
|
25
|
+
}
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
return [..._features];
|
|
33
|
-
},
|
|
27
|
+
// --- Enabled ---
|
|
34
28
|
|
|
35
29
|
get enabled(): boolean {
|
|
36
|
-
return _enabled;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get launcherVisible(): boolean {
|
|
40
|
-
return _launcherVisible;
|
|
41
|
-
},
|
|
30
|
+
return this._enabled;
|
|
31
|
+
}
|
|
42
32
|
|
|
43
33
|
setEnabled(enabled: boolean): void {
|
|
44
|
-
if (_enabled === enabled) return;
|
|
45
|
-
_enabled = enabled;
|
|
34
|
+
if (this._enabled === enabled) return;
|
|
35
|
+
this._enabled = enabled;
|
|
46
36
|
if (!enabled) {
|
|
47
|
-
_launcherVisible = false;
|
|
48
|
-
_features.forEach(
|
|
49
|
-
_features = [];
|
|
37
|
+
this._launcherVisible = false;
|
|
38
|
+
this._features.forEach((f) => f.cleanup?.());
|
|
39
|
+
this._features = [];
|
|
50
40
|
}
|
|
51
|
-
notify();
|
|
52
|
-
}
|
|
41
|
+
this.notify();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Feature Management ---
|
|
53
45
|
|
|
54
46
|
replaceFeatures(features: AnyDebugFeature[]): void {
|
|
55
|
-
if (!_enabled) return;
|
|
47
|
+
if (!this._enabled) return;
|
|
56
48
|
const next = features.filter(
|
|
57
49
|
(f, i, arr) =>
|
|
58
50
|
f && typeof f.name === 'string' && arr.findIndex((item) => item.name === f.name) === i,
|
|
59
51
|
);
|
|
60
|
-
const prevMap = new Map(_features.map((f) => [f.name, f]));
|
|
52
|
+
const prevMap = new Map(this._features.map((f) => [f.name, f]));
|
|
61
53
|
const nextMap = new Map(next.map((f) => [f.name, f]));
|
|
62
54
|
|
|
63
|
-
_features.forEach((f) => {
|
|
64
|
-
if (!nextMap.get(f.name) || nextMap.get(f.name) !== f)
|
|
55
|
+
this._features.forEach((f) => {
|
|
56
|
+
if (!nextMap.get(f.name) || nextMap.get(f.name) !== f) f.cleanup?.();
|
|
65
57
|
});
|
|
66
58
|
next.forEach((f) => {
|
|
67
|
-
if (!prevMap.get(f.name) || prevMap.get(f.name) !== f)
|
|
59
|
+
if (!prevMap.get(f.name) || prevMap.get(f.name) !== f) f.setup?.();
|
|
68
60
|
});
|
|
69
61
|
|
|
70
|
-
_features = next;
|
|
71
|
-
notify();
|
|
72
|
-
}
|
|
62
|
+
this._features = next;
|
|
63
|
+
this.notify();
|
|
64
|
+
}
|
|
73
65
|
|
|
74
66
|
addFeature(feature: AnyDebugFeature): void {
|
|
75
|
-
if (!_enabled || !feature || typeof feature.name !== 'string') {
|
|
67
|
+
if (!this._enabled || !feature || typeof feature.name !== 'string') {
|
|
76
68
|
return;
|
|
77
69
|
}
|
|
78
70
|
|
|
79
|
-
const existingIndex = _features.findIndex((f) => f.name === feature.name);
|
|
71
|
+
const existingIndex = this._features.findIndex((f) => f.name === feature.name);
|
|
80
72
|
if (existingIndex >= 0) {
|
|
81
|
-
const existing = _features[existingIndex]!;
|
|
73
|
+
const existing = this._features[existingIndex]!;
|
|
82
74
|
if (existing === feature) {
|
|
83
75
|
return;
|
|
84
76
|
}
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
_features = [
|
|
89
|
-
..._features.slice(0, existingIndex),
|
|
78
|
+
existing.cleanup?.();
|
|
79
|
+
feature.setup?.();
|
|
80
|
+
this._features = [
|
|
81
|
+
...this._features.slice(0, existingIndex),
|
|
90
82
|
feature,
|
|
91
|
-
..._features.slice(existingIndex + 1),
|
|
83
|
+
...this._features.slice(existingIndex + 1),
|
|
92
84
|
];
|
|
93
|
-
notify();
|
|
85
|
+
this.notify();
|
|
94
86
|
return;
|
|
95
87
|
}
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
_features = [..._features, feature];
|
|
99
|
-
notify();
|
|
100
|
-
}
|
|
89
|
+
feature.setup?.();
|
|
90
|
+
this._features = [...this._features, feature];
|
|
91
|
+
this.notify();
|
|
92
|
+
}
|
|
101
93
|
|
|
102
94
|
removeFeature(name: string): void {
|
|
103
|
-
if (!_enabled) {
|
|
95
|
+
if (!this._enabled) {
|
|
104
96
|
return;
|
|
105
97
|
}
|
|
106
98
|
|
|
107
|
-
const feature = _features.find((f) => f.name === name);
|
|
99
|
+
const feature = this._features.find((f) => f.name === name);
|
|
108
100
|
if (!feature) {
|
|
109
101
|
return;
|
|
110
102
|
}
|
|
111
103
|
|
|
112
|
-
|
|
113
|
-
_features = _features.filter((f) => f.name !== name);
|
|
114
|
-
if (_features.length === 0) {
|
|
115
|
-
_launcherVisible = false;
|
|
104
|
+
feature.cleanup?.();
|
|
105
|
+
this._features = this._features.filter((f) => f.name !== name);
|
|
106
|
+
if (this._features.length === 0) {
|
|
107
|
+
this._launcherVisible = false;
|
|
116
108
|
}
|
|
117
|
-
notify();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
reset(): void {
|
|
121
|
-
_launcherVisible = false;
|
|
122
|
-
_panelOpen = false;
|
|
123
|
-
_features.forEach(cleanupFeature);
|
|
124
|
-
_features = [];
|
|
125
|
-
notify();
|
|
126
|
-
},
|
|
109
|
+
this.notify();
|
|
110
|
+
}
|
|
127
111
|
|
|
128
112
|
hasFeatures(): boolean {
|
|
129
|
-
return _features.length > 0;
|
|
130
|
-
}
|
|
113
|
+
return this._features.length > 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- Panel ---
|
|
131
117
|
|
|
132
118
|
get panelOpen(): boolean {
|
|
133
|
-
return _panelOpen;
|
|
134
|
-
}
|
|
119
|
+
return this._panelOpen;
|
|
120
|
+
}
|
|
135
121
|
|
|
136
122
|
openPanel(): void {
|
|
137
|
-
if (!_enabled || _panelOpen) return;
|
|
138
|
-
_panelOpen = true;
|
|
139
|
-
notify();
|
|
140
|
-
}
|
|
123
|
+
if (!this._enabled || this._panelOpen) return;
|
|
124
|
+
this._panelOpen = true;
|
|
125
|
+
this.notify();
|
|
126
|
+
}
|
|
141
127
|
|
|
142
128
|
closePanel(): void {
|
|
143
|
-
if (!_panelOpen) return;
|
|
144
|
-
_panelOpen = false;
|
|
145
|
-
notify();
|
|
146
|
-
}
|
|
129
|
+
if (!this._panelOpen) return;
|
|
130
|
+
this._panelOpen = false;
|
|
131
|
+
this.notify();
|
|
132
|
+
}
|
|
147
133
|
|
|
148
134
|
togglePanel(): void {
|
|
149
|
-
if (_panelOpen) {
|
|
150
|
-
|
|
135
|
+
if (this._panelOpen) {
|
|
136
|
+
this.closePanel();
|
|
151
137
|
} else {
|
|
152
|
-
|
|
138
|
+
this.openPanel();
|
|
153
139
|
}
|
|
154
|
-
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --- Launcher ---
|
|
143
|
+
|
|
144
|
+
get launcherVisible(): boolean {
|
|
145
|
+
return this._launcherVisible;
|
|
146
|
+
}
|
|
155
147
|
|
|
156
148
|
showLauncher(): void {
|
|
157
|
-
if (!_enabled) return;
|
|
158
|
-
_launcherVisible = true;
|
|
159
|
-
notify();
|
|
160
|
-
}
|
|
149
|
+
if (!this._enabled) return;
|
|
150
|
+
this._launcherVisible = true;
|
|
151
|
+
this.notify();
|
|
152
|
+
}
|
|
161
153
|
|
|
162
154
|
hideLauncher(): void {
|
|
163
|
-
_launcherVisible
|
|
164
|
-
|
|
165
|
-
|
|
155
|
+
if (!this._launcherVisible) return;
|
|
156
|
+
this._launcherVisible = false;
|
|
157
|
+
this.notify();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- Bulk Operations ---
|
|
166
161
|
|
|
167
162
|
clearAll(): void {
|
|
168
|
-
_features.forEach((f) => f.clear?.());
|
|
169
|
-
notify();
|
|
170
|
-
}
|
|
163
|
+
this._features.forEach((f) => f.clear?.());
|
|
164
|
+
this.notify();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
reset(): void {
|
|
168
|
+
this._launcherVisible = false;
|
|
169
|
+
this._panelOpen = false;
|
|
170
|
+
this._features.forEach((f) => f.cleanup?.());
|
|
171
|
+
this._features = [];
|
|
172
|
+
this.notify();
|
|
173
|
+
}
|
|
171
174
|
|
|
172
175
|
destroy(): void {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
176
|
+
this.reset();
|
|
177
|
+
this._listeners.clear();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Module-level default instance for non-React scenarios and backward compatibility. */
|
|
182
|
+
export const debugToolkit = new DebugToolkitCore();
|
|
183
|
+
|
|
184
|
+
export type DebugToolkit = DebugToolkitCore;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @deprecated Use `debugToolkit` or get instance from `initializeDebugToolkit()`.
|
|
188
|
+
* This is the default module-level instance exported for backward compatibility.
|
|
189
|
+
*/
|
|
190
|
+
export const DebugToolkit = debugToolkit;
|
package/src/core/initialize.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DebugToolkit } from './DebugToolkit';
|
|
2
|
-
import { createNetworkFeature } from '../features/network';
|
|
2
|
+
import { createNetworkFeature, addToBlacklist } from '../features/network';
|
|
3
3
|
import type { NetworkFeatureConfig } from '../features/network';
|
|
4
4
|
import { createConsoleLogFeature } from '../features/console';
|
|
5
5
|
import type { ConsoleFeatureConfig } from '../features/console';
|
|
@@ -11,16 +11,11 @@ import { createTrackFeature } from '../features/track';
|
|
|
11
11
|
import type { TrackFeatureConfig } from '../features/track';
|
|
12
12
|
import { createEnvironmentFeature } from '../features/environment';
|
|
13
13
|
import { createClipboardFeature } from '../features/clipboard';
|
|
14
|
-
import { daemonClient
|
|
15
|
-
import { _addDaemonEndpointToNetworkBlacklist } from '../features/network';
|
|
14
|
+
import { daemonClient } from '../utils/DaemonClient';
|
|
16
15
|
import type { AnyDebugFeature, BuiltInFeatureName } from '../types';
|
|
17
16
|
|
|
18
17
|
const isDebugMode = __DEV__;
|
|
19
18
|
|
|
20
|
-
daemonClient.setEndpointDetector((url) => {
|
|
21
|
-
_addDaemonEndpointToNetworkBlacklist(url);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
19
|
/** Feature-specific configuration map */
|
|
25
20
|
export interface FeatureConfigs {
|
|
26
21
|
network?: boolean | NetworkFeatureConfig;
|
|
@@ -114,13 +109,17 @@ export function initializeDebugToolkit(
|
|
|
114
109
|
|
|
115
110
|
DebugToolkit.replaceFeatures(resolvedFeatures);
|
|
116
111
|
|
|
112
|
+
daemonClient.setEndpointDetector((url) => {
|
|
113
|
+
addToBlacklist(url);
|
|
114
|
+
});
|
|
115
|
+
|
|
117
116
|
if (DebugToolkit.hasFeatures()) {
|
|
118
117
|
DebugToolkit.showLauncher();
|
|
119
118
|
} else {
|
|
120
119
|
DebugToolkit.hideLauncher();
|
|
121
120
|
}
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
daemonClient.restore().catch(() => {});
|
|
124
123
|
|
|
125
124
|
return DebugToolkit;
|
|
126
125
|
} catch (error) {
|