react-native-debug-toolkit 2.2.0 → 3.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.
- package/README.md +5 -8
- package/README.zh-CN.md +5 -8
- package/bin/debug-toolkit.js +114 -0
- package/lib/commonjs/features/network/index.js +32 -12
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +164 -201
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/commonjs/index.js +59 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/panel/DebugPanel.js +25 -0
- package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
- package/lib/commonjs/ui/panel/FloatPanelView.js +15 -62
- package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js +529 -0
- package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js +71 -0
- package/lib/commonjs/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/commonjs/utils/autoDetectDaemon.js +141 -0
- package/lib/commonjs/utils/autoDetectDaemon.js.map +1 -0
- package/lib/commonjs/utils/createPersistedObservableStore.js +23 -3
- package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/commonjs/utils/daemonConnection.js +81 -0
- package/lib/commonjs/utils/daemonConnection.js.map +1 -0
- package/lib/commonjs/utils/daemonSettings.js +110 -0
- package/lib/commonjs/utils/daemonSettings.js.map +1 -0
- package/lib/commonjs/utils/reportToDaemon.js +112 -0
- package/lib/commonjs/utils/reportToDaemon.js.map +1 -0
- package/lib/commonjs/utils/sessionReport.js +132 -0
- package/lib/commonjs/utils/sessionReport.js.map +1 -0
- package/lib/commonjs/utils/streamToDaemon.js +334 -0
- package/lib/commonjs/utils/streamToDaemon.js.map +1 -0
- package/lib/module/features/network/index.js +30 -12
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +163 -200
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/panel/DebugPanel.js +26 -1
- package/lib/module/ui/panel/DebugPanel.js.map +1 -1
- package/lib/module/ui/panel/FloatPanelView.js +16 -63
- package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
- package/lib/module/ui/panel/StreamingSettingsModal.js +524 -0
- package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -0
- package/lib/module/ui/panel/useTabAnimation.js +67 -0
- package/lib/module/ui/panel/useTabAnimation.js.map +1 -0
- package/lib/module/utils/autoDetectDaemon.js +136 -0
- package/lib/module/utils/autoDetectDaemon.js.map +1 -0
- package/lib/module/utils/createPersistedObservableStore.js +23 -3
- package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
- package/lib/module/utils/daemonConnection.js +77 -0
- package/lib/module/utils/daemonConnection.js.map +1 -0
- package/lib/module/utils/daemonSettings.js +102 -0
- package/lib/module/utils/daemonSettings.js.map +1 -0
- package/lib/module/utils/reportToDaemon.js +105 -0
- package/lib/module/utils/reportToDaemon.js.map +1 -0
- package/lib/module/utils/sessionReport.js +128 -0
- package/lib/module/utils/sessionReport.js.map +1 -0
- package/lib/module/utils/streamToDaemon.js +328 -0
- package/lib/module/utils/streamToDaemon.js.map +1 -0
- package/lib/typescript/src/features/network/index.d.ts +2 -4
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/networkInterceptor.d.ts +2 -15
- package/lib/typescript/src/features/network/networkInterceptor.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +11 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts +8 -0
- package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts +14 -0
- package/lib/typescript/src/ui/panel/useTabAnimation.d.ts.map +1 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts +15 -0
- package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +2 -1
- package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
- package/lib/typescript/src/utils/daemonConnection.d.ts +18 -0
- package/lib/typescript/src/utils/daemonConnection.d.ts.map +1 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts +19 -0
- package/lib/typescript/src/utils/daemonSettings.d.ts.map +1 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts +34 -0
- package/lib/typescript/src/utils/reportToDaemon.d.ts.map +1 -0
- package/lib/typescript/src/utils/sessionReport.d.ts +18 -0
- package/lib/typescript/src/utils/sessionReport.d.ts.map +1 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts +23 -0
- package/lib/typescript/src/utils/streamToDaemon.d.ts.map +1 -0
- package/node/daemon/src/cli.js +75 -0
- package/node/daemon/src/console/console.html +936 -0
- package/node/daemon/src/console/index.js +47 -0
- package/node/daemon/src/constants.js +32 -0
- package/node/daemon/src/index.js +11 -0
- package/node/daemon/src/server.js +365 -0
- package/node/daemon/src/store.js +110 -0
- package/node/mcp/src/cli.js +31 -0
- package/node/mcp/src/constants.js +13 -0
- package/node/mcp/src/daemonClient.js +132 -0
- package/node/mcp/src/httpClient.js +49 -0
- package/node/mcp/src/index.js +15 -0
- package/node/mcp/src/logs.js +95 -0
- package/node/mcp/src/server.js +144 -0
- package/node/mcp/src/tools.js +84 -0
- package/package.json +10 -5
- package/src/features/network/index.ts +35 -19
- package/src/features/network/networkInterceptor.ts +224 -236
- package/src/index.ts +15 -1
- package/src/ui/panel/DebugPanel.tsx +23 -1
- package/src/ui/panel/FloatPanelView.tsx +10 -68
- package/src/ui/panel/StreamingSettingsModal.tsx +566 -0
- package/src/ui/panel/useTabAnimation.ts +77 -0
- package/src/utils/autoDetectDaemon.ts +175 -0
- package/src/utils/createPersistedObservableStore.ts +16 -3
- package/src/utils/daemonConnection.ts +133 -0
- package/src/utils/daemonSettings.ts +134 -0
- package/src/utils/reportToDaemon.ts +172 -0
- package/src/utils/sessionReport.ts +203 -0
- package/src/utils/streamToDaemon.ts +419 -0
|
@@ -3,26 +3,10 @@ import { urlRewriter } from '../../utils/urlRewriterRegistry';
|
|
|
3
3
|
|
|
4
4
|
type NetworkLogPayload = Omit<NetworkLogEntry, 'id'>;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// ─── Minimal axios interface (no hard dependency) ──────
|
|
11
|
-
|
|
12
|
-
export interface AxiosInstanceLike {
|
|
13
|
-
interceptors: {
|
|
14
|
-
request: {
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
|
-
use(onFulfilled?: (config: any) => any, onRejected?: (error: any) => any): number;
|
|
17
|
-
eject(id: number): void;
|
|
18
|
-
};
|
|
19
|
-
response: {
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
-
use(onFulfilled?: (response: any) => any, onRejected?: (error: any) => any): number;
|
|
22
|
-
eject(id: number): void;
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
}
|
|
6
|
+
export type { NetworkLogPayload };
|
|
7
|
+
|
|
8
|
+
// Intercepts React Native's XMLHttpRequest transport layer.
|
|
9
|
+
// RN fetch and axios (default adapter) both go through XHR — one hook captures everything.
|
|
26
10
|
|
|
27
11
|
// ─── Shared helpers ────────────────────────────────────
|
|
28
12
|
|
|
@@ -38,47 +22,31 @@ function rewriteUrl(url: string): string {
|
|
|
38
22
|
}
|
|
39
23
|
}
|
|
40
24
|
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const result: Record<string, string> = {};
|
|
45
|
-
if (!headers) {
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
if (typeof (headers as Headers).forEach === 'function') {
|
|
49
|
-
(headers as Headers).forEach((value: string, key: string) => {
|
|
50
|
-
result[key] = value;
|
|
51
|
-
});
|
|
52
|
-
return result;
|
|
25
|
+
function parseRawHeaders(rawHeaders: string | null | undefined): Record<string, string> | undefined {
|
|
26
|
+
if (!rawHeaders) {
|
|
27
|
+
return undefined;
|
|
53
28
|
}
|
|
54
|
-
Object.keys(headers).forEach((key) => {
|
|
55
|
-
result[key] = (headers as Record<string, string>)[key]!;
|
|
56
|
-
});
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
29
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
30
|
+
const headers: Record<string, string> = {};
|
|
31
|
+
rawHeaders
|
|
32
|
+
.trim()
|
|
33
|
+
.split(/[\r\n]+/)
|
|
34
|
+
.forEach((line) => {
|
|
35
|
+
const separatorIndex = line.indexOf(':');
|
|
36
|
+
if (separatorIndex <= 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
40
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
41
|
+
if (key) {
|
|
42
|
+
headers[key] = value;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
75
46
|
}
|
|
76
47
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!raw) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
48
|
+
function parseBodyText(raw: string): unknown {
|
|
49
|
+
if (!raw) return undefined;
|
|
82
50
|
try {
|
|
83
51
|
return JSON.parse(raw);
|
|
84
52
|
} catch {
|
|
@@ -86,226 +54,246 @@ async function parseResponseBody(response: Response): Promise<unknown> {
|
|
|
86
54
|
}
|
|
87
55
|
}
|
|
88
56
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return undefined;
|
|
57
|
+
function normalizeXhrResponseBody(xhr: XMLHttpRequestLike): unknown {
|
|
58
|
+
const text = safeRead(() => xhr.responseText);
|
|
59
|
+
if (typeof text === 'string' && text) {
|
|
60
|
+
return parseBodyText(text);
|
|
94
61
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
(value, key) => {
|
|
100
|
-
result[key] = value;
|
|
101
|
-
},
|
|
102
|
-
);
|
|
103
|
-
return Object.keys(result).length > 0 ? result : undefined;
|
|
104
|
-
}
|
|
105
|
-
// Plain object
|
|
106
|
-
const result: Record<string, string> = {};
|
|
107
|
-
const obj = headers as Record<string, unknown>;
|
|
108
|
-
for (const key of Object.keys(obj)) {
|
|
109
|
-
const val = obj[key];
|
|
110
|
-
if (typeof val === 'string') {
|
|
111
|
-
result[key] = val;
|
|
112
|
-
}
|
|
62
|
+
|
|
63
|
+
const response = safeRead(() => xhr.response);
|
|
64
|
+
if (response != null) {
|
|
65
|
+
return response;
|
|
113
66
|
}
|
|
114
|
-
|
|
67
|
+
|
|
68
|
+
return undefined;
|
|
115
69
|
}
|
|
116
70
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
71
|
+
// ─── XMLHttpRequest interceptor ───────────────────────
|
|
72
|
+
|
|
73
|
+
interface XMLHttpRequestLike {
|
|
74
|
+
readyState: number;
|
|
75
|
+
DONE?: number;
|
|
76
|
+
status: number;
|
|
77
|
+
statusText?: string;
|
|
78
|
+
responseHeaders?: Record<string, string> | null;
|
|
79
|
+
responseType?: string;
|
|
80
|
+
response?: unknown;
|
|
81
|
+
responseText?: string;
|
|
82
|
+
responseURL?: string;
|
|
83
|
+
open(method: string, url: string, ...args: unknown[]): void;
|
|
84
|
+
send(body?: unknown): void;
|
|
85
|
+
setRequestHeader(header: string, value: string): void;
|
|
86
|
+
getAllResponseHeaders?(): string | null;
|
|
87
|
+
addEventListener(type: string, listener: () => void): void;
|
|
88
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type XMLHttpRequestConstructorLike = new () => XMLHttpRequestLike;
|
|
92
|
+
|
|
93
|
+
type XhrState = {
|
|
94
|
+
method: string;
|
|
95
|
+
url: string;
|
|
96
|
+
headers: Record<string, string>;
|
|
97
|
+
body?: unknown;
|
|
98
|
+
timestamp: number;
|
|
99
|
+
error?: string;
|
|
100
|
+
completed?: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
let originalXMLHttpRequest: XMLHttpRequestConstructorLike | null = null;
|
|
104
|
+
let originalXhrOpen: XMLHttpRequestLike['open'] | null = null;
|
|
105
|
+
let originalXhrSend: XMLHttpRequestLike['send'] | null = null;
|
|
106
|
+
let originalXhrSetRequestHeader: XMLHttpRequestLike['setRequestHeader'] | null = null;
|
|
107
|
+
let xhrRefCount = 0;
|
|
108
|
+
const pendingXhrRequests = new WeakMap<XMLHttpRequestLike, XhrState>();
|
|
109
|
+
|
|
110
|
+
function safeRead<T>(read: () => T): T | undefined {
|
|
111
|
+
try {
|
|
112
|
+
return read();
|
|
113
|
+
} catch {
|
|
114
|
+
return undefined;
|
|
125
115
|
}
|
|
126
|
-
return base.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
|
|
127
116
|
}
|
|
128
117
|
|
|
129
|
-
//
|
|
118
|
+
// URLs matching these patterns are skipped (Metro dev server internals).
|
|
119
|
+
const IGNORED_URL_PATTERNS = [
|
|
120
|
+
/\/symbolicate$/,
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
function shouldIgnoreUrl(url: string): boolean {
|
|
124
|
+
return IGNORED_URL_PATTERNS.some((p) => p.test(url));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getGlobalXMLHttpRequest(): XMLHttpRequestConstructorLike | undefined {
|
|
128
|
+
return (globalThis as { XMLHttpRequest?: XMLHttpRequestConstructorLike }).XMLHttpRequest;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getXhrResponseHeaders(xhr: XMLHttpRequestLike): Record<string, string> | undefined {
|
|
132
|
+
const rawHeaders = safeRead(() => xhr.getAllResponseHeaders?.());
|
|
133
|
+
const parsedHeaders = parseRawHeaders(rawHeaders);
|
|
134
|
+
if (parsedHeaders) {
|
|
135
|
+
return parsedHeaders;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const headers = xhr.responseHeaders;
|
|
139
|
+
if (!headers) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
143
|
+
}
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
145
|
+
function stopXMLHttpRequest(): void {
|
|
146
|
+
xhrRefCount = Math.max(0, xhrRefCount - 1);
|
|
147
|
+
if (xhrRefCount > 0 || !originalXMLHttpRequest) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
133
150
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
const CurrentXMLHttpRequest = getGlobalXMLHttpRequest();
|
|
152
|
+
if (CurrentXMLHttpRequest) {
|
|
153
|
+
if (originalXhrOpen) {
|
|
154
|
+
CurrentXMLHttpRequest.prototype.open = originalXhrOpen;
|
|
155
|
+
}
|
|
156
|
+
if (originalXhrSend) {
|
|
157
|
+
CurrentXMLHttpRequest.prototype.send = originalXhrSend;
|
|
158
|
+
}
|
|
159
|
+
if (originalXhrSetRequestHeader) {
|
|
160
|
+
CurrentXMLHttpRequest.prototype.setRequestHeader = originalXhrSetRequestHeader;
|
|
161
|
+
}
|
|
139
162
|
}
|
|
163
|
+
|
|
164
|
+
originalXMLHttpRequest = null;
|
|
165
|
+
originalXhrOpen = null;
|
|
166
|
+
originalXhrSend = null;
|
|
167
|
+
originalXhrSetRequestHeader = null;
|
|
140
168
|
}
|
|
141
169
|
|
|
142
|
-
export function
|
|
143
|
-
|
|
144
|
-
|
|
170
|
+
export function startXMLHttpRequest(
|
|
171
|
+
emit: (entry: NetworkLogPayload) => void,
|
|
172
|
+
): () => void {
|
|
173
|
+
const CurrentXMLHttpRequest = getGlobalXMLHttpRequest();
|
|
174
|
+
if (!CurrentXMLHttpRequest) {
|
|
175
|
+
return () => {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
xhrRefCount += 1;
|
|
179
|
+
if (originalXMLHttpRequest) {
|
|
145
180
|
return () => {
|
|
146
|
-
|
|
181
|
+
stopXMLHttpRequest();
|
|
147
182
|
};
|
|
148
183
|
}
|
|
149
184
|
|
|
150
|
-
|
|
185
|
+
originalXMLHttpRequest = CurrentXMLHttpRequest;
|
|
186
|
+
originalXhrOpen = CurrentXMLHttpRequest.prototype.open;
|
|
187
|
+
originalXhrSend = CurrentXMLHttpRequest.prototype.send;
|
|
188
|
+
originalXhrSetRequestHeader = CurrentXMLHttpRequest.prototype.setRequestHeader;
|
|
151
189
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
190
|
+
CurrentXMLHttpRequest.prototype.open = function interceptedOpen(
|
|
191
|
+
this: XMLHttpRequestLike,
|
|
192
|
+
method: string,
|
|
193
|
+
url: string,
|
|
194
|
+
...args: unknown[]
|
|
155
195
|
) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (urlRewriter.get()) {
|
|
160
|
-
if (typeof input === 'string') {
|
|
161
|
-
rewrittenInput = rewriteUrl(input);
|
|
162
|
-
} else if (input instanceof Request) {
|
|
163
|
-
rewrittenInput = new Request(rewriteUrl(input.url), input as RequestInit);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const request = getRequestSnapshot(rewrittenInput, init);
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const response = await originalFetch!.call(globalThis, rewrittenInput, init);
|
|
171
|
-
const duration = Date.now() - startTime;
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const data = await parseResponseBody(response);
|
|
175
|
-
emit({
|
|
176
|
-
timestamp: startTime,
|
|
177
|
-
duration,
|
|
178
|
-
request,
|
|
179
|
-
response: { status: response.status, statusText: response.statusText, data },
|
|
180
|
-
});
|
|
181
|
-
} catch {
|
|
182
|
-
emit({
|
|
183
|
-
timestamp: startTime,
|
|
184
|
-
duration,
|
|
185
|
-
request,
|
|
186
|
-
response: { status: response.status, statusText: response.statusText },
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return response;
|
|
191
|
-
} catch (error) {
|
|
192
|
-
emit({
|
|
193
|
-
timestamp: startTime,
|
|
194
|
-
duration: Date.now() - startTime,
|
|
195
|
-
request,
|
|
196
|
-
error: error instanceof Error ? error.message : String(error),
|
|
197
|
-
});
|
|
198
|
-
throw error;
|
|
196
|
+
const rewrittenUrl = urlRewriter.get() ? rewriteUrl(url) : url;
|
|
197
|
+
if (shouldIgnoreUrl(rewrittenUrl)) {
|
|
198
|
+
return originalXhrOpen!.call(this, method, rewrittenUrl, ...args);
|
|
199
199
|
}
|
|
200
|
+
pendingXhrRequests.set(this, {
|
|
201
|
+
method: (method || 'GET').toUpperCase(),
|
|
202
|
+
url: rewrittenUrl,
|
|
203
|
+
headers: {},
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
});
|
|
206
|
+
return originalXhrOpen!.call(this, method, rewrittenUrl, ...args);
|
|
200
207
|
};
|
|
201
208
|
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
CurrentXMLHttpRequest.prototype.setRequestHeader = function interceptedSetRequestHeader(
|
|
210
|
+
this: XMLHttpRequestLike,
|
|
211
|
+
header: string,
|
|
212
|
+
value: string,
|
|
213
|
+
) {
|
|
214
|
+
const state = pendingXhrRequests.get(this);
|
|
215
|
+
if (state) {
|
|
216
|
+
state.headers[header] = value;
|
|
217
|
+
}
|
|
218
|
+
return originalXhrSetRequestHeader!.call(this, header, value);
|
|
204
219
|
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ─── Axios interceptor ─────────────────────────────────
|
|
208
220
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (config.baseURL) {
|
|
228
|
-
config.baseURL = undefined;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
221
|
+
CurrentXMLHttpRequest.prototype.send = function interceptedSend(
|
|
222
|
+
this: XMLHttpRequestLike,
|
|
223
|
+
body?: unknown,
|
|
224
|
+
) {
|
|
225
|
+
const that = this;
|
|
226
|
+
const existingState = pendingXhrRequests.get(that);
|
|
227
|
+
if (!existingState) {
|
|
228
|
+
return originalXhrSend!.call(that, body);
|
|
229
|
+
}
|
|
230
|
+
const state = existingState;
|
|
231
|
+
state.body = body;
|
|
232
|
+
state.timestamp = Date.now();
|
|
233
|
+
pendingXhrRequests.set(that, state);
|
|
234
|
+
|
|
235
|
+
const complete = () => {
|
|
236
|
+
const currentState = pendingXhrRequests.get(that);
|
|
237
|
+
if (!currentState || currentState.completed) {
|
|
238
|
+
return;
|
|
231
239
|
}
|
|
240
|
+
currentState.completed = true;
|
|
232
241
|
|
|
233
|
-
|
|
234
|
-
},
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
const responseInterceptorId = axiosInstance.interceptors.response.use(
|
|
238
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
239
|
-
(response: any) => {
|
|
240
|
-
const config = response.config;
|
|
241
|
-
const pending = config ? pendingAxiosRequests.get(config) : undefined;
|
|
242
|
-
const startTime = pending?.timestamp ?? Date.now();
|
|
243
|
-
|
|
242
|
+
const headers = getXhrResponseHeaders(that);
|
|
244
243
|
emit({
|
|
245
|
-
timestamp:
|
|
246
|
-
duration: Date.now() -
|
|
244
|
+
timestamp: currentState.timestamp,
|
|
245
|
+
duration: Date.now() - currentState.timestamp,
|
|
247
246
|
request: {
|
|
248
|
-
url:
|
|
249
|
-
method:
|
|
250
|
-
headers:
|
|
251
|
-
|
|
247
|
+
url: currentState.url,
|
|
248
|
+
method: currentState.method,
|
|
249
|
+
headers: Object.keys(currentState.headers).length > 0
|
|
250
|
+
? currentState.headers
|
|
251
|
+
: undefined,
|
|
252
|
+
body: currentState.body,
|
|
252
253
|
},
|
|
253
254
|
response: {
|
|
254
|
-
status:
|
|
255
|
-
statusText:
|
|
256
|
-
headers
|
|
257
|
-
data:
|
|
258
|
-
success:
|
|
255
|
+
status: that.status,
|
|
256
|
+
statusText: that.statusText,
|
|
257
|
+
headers,
|
|
258
|
+
data: normalizeXhrResponseBody(that),
|
|
259
|
+
success: that.status >= 200 && that.status < 300,
|
|
259
260
|
},
|
|
261
|
+
error: currentState.error,
|
|
260
262
|
});
|
|
263
|
+
};
|
|
261
264
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const config = error.config;
|
|
267
|
-
const pending = config ? pendingAxiosRequests.get(config) : undefined;
|
|
268
|
-
const startTime = pending?.timestamp ?? Date.now();
|
|
269
|
-
|
|
270
|
-
if (config) {
|
|
271
|
-
emit({
|
|
272
|
-
timestamp: startTime,
|
|
273
|
-
duration: Date.now() - startTime,
|
|
274
|
-
request: {
|
|
275
|
-
url: buildFullUrl(config),
|
|
276
|
-
method: (config.method ?? 'GET').toUpperCase(),
|
|
277
|
-
headers: normalizeAxiosHeaders(config.headers),
|
|
278
|
-
body: config.data,
|
|
279
|
-
},
|
|
280
|
-
response: error.response
|
|
281
|
-
? {
|
|
282
|
-
status: error.response.status,
|
|
283
|
-
statusText: error.response.statusText,
|
|
284
|
-
headers: normalizeAxiosHeaders(error.response.headers),
|
|
285
|
-
data: error.response.data,
|
|
286
|
-
success: false,
|
|
287
|
-
}
|
|
288
|
-
: undefined,
|
|
289
|
-
error: error.message ?? String(error),
|
|
290
|
-
});
|
|
265
|
+
const markError = (message: string) => {
|
|
266
|
+
const currentState = pendingXhrRequests.get(that);
|
|
267
|
+
if (currentState) {
|
|
268
|
+
currentState.error = message;
|
|
291
269
|
}
|
|
270
|
+
};
|
|
292
271
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
272
|
+
that.addEventListener('error', () => {
|
|
273
|
+
markError('Network Error');
|
|
274
|
+
});
|
|
275
|
+
that.addEventListener('timeout', () => {
|
|
276
|
+
markError('Timeout');
|
|
277
|
+
});
|
|
278
|
+
that.addEventListener('abort', () => {
|
|
279
|
+
markError('Aborted');
|
|
280
|
+
});
|
|
281
|
+
that.addEventListener('loadend', complete);
|
|
282
|
+
|
|
283
|
+
return originalXhrSend!.call(that, body);
|
|
284
|
+
};
|
|
296
285
|
|
|
297
286
|
return () => {
|
|
298
|
-
|
|
299
|
-
axiosInstance.interceptors.response.eject(responseInterceptorId);
|
|
287
|
+
stopXMLHttpRequest();
|
|
300
288
|
};
|
|
301
289
|
}
|
|
302
290
|
|
|
303
291
|
// ─── Cleanup ───────────────────────────────────────────
|
|
304
292
|
|
|
305
293
|
export function resetInterceptors(): void {
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
294
|
+
if (originalXMLHttpRequest) {
|
|
295
|
+
xhrRefCount = 1;
|
|
296
|
+
stopXMLHttpRequest();
|
|
309
297
|
}
|
|
310
|
-
|
|
298
|
+
xhrRefCount = 0;
|
|
311
299
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type { InitializeOptions, FeatureConfigs } from './core/initialize';
|
|
|
8
8
|
|
|
9
9
|
// Feature factories
|
|
10
10
|
export { createNetworkFeature } from './features/network';
|
|
11
|
-
export type { NetworkFeatureConfig
|
|
11
|
+
export type { NetworkFeatureConfig } from './features/network';
|
|
12
12
|
export { createConsoleLogFeature } from './features/console';
|
|
13
13
|
export type { ConsoleFeatureConfig } from './features/console';
|
|
14
14
|
export { createZustandLogFeature, zustandLogMiddleware, addZustandLog } from './features/zustand';
|
|
@@ -28,6 +28,20 @@ export { useNavigationLogger } from './features/navigation/useNavigationLogger';
|
|
|
28
28
|
export { safeStringify } from './utils/safeStringify';
|
|
29
29
|
export { copyToComputer, logToComputer, fmt } from './utils/copyToComputer';
|
|
30
30
|
export type { CopyResult, CopyOptions, CopyMethod } from './utils/copyToComputer';
|
|
31
|
+
export { createDebugSessionReport } from './utils/sessionReport';
|
|
32
|
+
export type { DebugSessionReport, DebugSessionReportOptions } from './utils/sessionReport';
|
|
33
|
+
export { getDefaultDaemonEndpoint, reportDebugSessionToDaemon } from './utils/reportToDaemon';
|
|
34
|
+
export type { ReportResult, ReportToDaemonOptions } from './utils/reportToDaemon';
|
|
35
|
+
export { checkDaemonConnection } from './utils/daemonConnection';
|
|
36
|
+
export type {
|
|
37
|
+
DaemonConnectionFailureReason,
|
|
38
|
+
DaemonConnectionOptions,
|
|
39
|
+
DaemonConnectionResult,
|
|
40
|
+
} from './utils/daemonConnection';
|
|
41
|
+
export { startStreaming, stopStreaming, isStreaming } from './utils/streamToDaemon';
|
|
42
|
+
export type { StreamStatus, StreamToDaemonOptions } from './utils/streamToDaemon';
|
|
43
|
+
export { autoDetectDaemonIp, getMetroHost } from './utils/autoDetectDaemon';
|
|
44
|
+
export type { AutoDetectOptions, AutoDetectResult } from './utils/autoDetectDaemon';
|
|
31
45
|
|
|
32
46
|
// Types
|
|
33
47
|
export type {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useWindowDimensions,
|
|
11
11
|
} from 'react-native';
|
|
12
12
|
import { Colors } from '../theme/colors';
|
|
13
|
+
import { StreamingSettingsModal } from './StreamingSettingsModal';
|
|
13
14
|
|
|
14
15
|
interface DebugPanelProps {
|
|
15
16
|
onClose: () => void;
|
|
@@ -21,6 +22,7 @@ export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
|
|
|
21
22
|
const { height: screenHeight } = useWindowDimensions();
|
|
22
23
|
const panelTranslateY = useRef(new Animated.Value(screenHeight)).current;
|
|
23
24
|
const backdropOpacity = useRef(new Animated.Value(0)).current;
|
|
25
|
+
const [settingsVisible, setSettingsVisible] = useState(false);
|
|
24
26
|
|
|
25
27
|
useEffect(() => {
|
|
26
28
|
requestAnimationFrame(() => {
|
|
@@ -101,6 +103,13 @@ export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
|
|
|
101
103
|
<View style={styles.header}>
|
|
102
104
|
<Text style={styles.headerTitle}>Debug Toolkit</Text>
|
|
103
105
|
<View style={styles.headerButtons}>
|
|
106
|
+
<TouchableOpacity
|
|
107
|
+
onPress={() => setSettingsVisible(true)}
|
|
108
|
+
style={styles.settingsButton}
|
|
109
|
+
activeOpacity={0.6}
|
|
110
|
+
>
|
|
111
|
+
<Text style={styles.settingsButtonText}>⚙</Text>
|
|
112
|
+
</TouchableOpacity>
|
|
104
113
|
<TouchableOpacity
|
|
105
114
|
onPress={() => {
|
|
106
115
|
onClearAll();
|
|
@@ -119,6 +128,7 @@ export function DebugPanel({ onClose, onClearAll, children }: DebugPanelProps) {
|
|
|
119
128
|
</View>
|
|
120
129
|
<View style={styles.panelContent}>{children}</View>
|
|
121
130
|
</Animated.View>
|
|
131
|
+
<StreamingSettingsModal visible={settingsVisible} onClose={() => setSettingsVisible(false)} />
|
|
122
132
|
</View>
|
|
123
133
|
);
|
|
124
134
|
}
|
|
@@ -198,6 +208,18 @@ const styles = StyleSheet.create({
|
|
|
198
208
|
fontSize: 14,
|
|
199
209
|
fontWeight: '500',
|
|
200
210
|
},
|
|
211
|
+
settingsButton: {
|
|
212
|
+
width: 32,
|
|
213
|
+
height: 32,
|
|
214
|
+
borderRadius: 16,
|
|
215
|
+
backgroundColor: Colors.background,
|
|
216
|
+
alignItems: 'center',
|
|
217
|
+
justifyContent: 'center',
|
|
218
|
+
},
|
|
219
|
+
settingsButtonText: {
|
|
220
|
+
fontSize: 16,
|
|
221
|
+
color: Colors.textSecondary,
|
|
222
|
+
},
|
|
201
223
|
closeButton: {
|
|
202
224
|
width: 30,
|
|
203
225
|
height: 30,
|