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.
Files changed (115) hide show
  1. package/README.md +5 -8
  2. package/README.zh-CN.md +5 -8
  3. package/bin/debug-toolkit.js +114 -0
  4. package/lib/commonjs/features/network/index.js +32 -12
  5. package/lib/commonjs/features/network/index.js.map +1 -1
  6. package/lib/commonjs/features/network/networkInterceptor.js +164 -201
  7. package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
  8. package/lib/commonjs/index.js +59 -0
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/ui/panel/DebugPanel.js +25 -0
  11. package/lib/commonjs/ui/panel/DebugPanel.js.map +1 -1
  12. package/lib/commonjs/ui/panel/FloatPanelView.js +15 -62
  13. package/lib/commonjs/ui/panel/FloatPanelView.js.map +1 -1
  14. package/lib/commonjs/ui/panel/StreamingSettingsModal.js +529 -0
  15. package/lib/commonjs/ui/panel/StreamingSettingsModal.js.map +1 -0
  16. package/lib/commonjs/ui/panel/useTabAnimation.js +71 -0
  17. package/lib/commonjs/ui/panel/useTabAnimation.js.map +1 -0
  18. package/lib/commonjs/utils/autoDetectDaemon.js +141 -0
  19. package/lib/commonjs/utils/autoDetectDaemon.js.map +1 -0
  20. package/lib/commonjs/utils/createPersistedObservableStore.js +23 -3
  21. package/lib/commonjs/utils/createPersistedObservableStore.js.map +1 -1
  22. package/lib/commonjs/utils/daemonConnection.js +81 -0
  23. package/lib/commonjs/utils/daemonConnection.js.map +1 -0
  24. package/lib/commonjs/utils/daemonSettings.js +110 -0
  25. package/lib/commonjs/utils/daemonSettings.js.map +1 -0
  26. package/lib/commonjs/utils/reportToDaemon.js +112 -0
  27. package/lib/commonjs/utils/reportToDaemon.js.map +1 -0
  28. package/lib/commonjs/utils/sessionReport.js +132 -0
  29. package/lib/commonjs/utils/sessionReport.js.map +1 -0
  30. package/lib/commonjs/utils/streamToDaemon.js +334 -0
  31. package/lib/commonjs/utils/streamToDaemon.js.map +1 -0
  32. package/lib/module/features/network/index.js +30 -12
  33. package/lib/module/features/network/index.js.map +1 -1
  34. package/lib/module/features/network/networkInterceptor.js +163 -200
  35. package/lib/module/features/network/networkInterceptor.js.map +1 -1
  36. package/lib/module/index.js +5 -0
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/module/ui/panel/DebugPanel.js +26 -1
  39. package/lib/module/ui/panel/DebugPanel.js.map +1 -1
  40. package/lib/module/ui/panel/FloatPanelView.js +16 -63
  41. package/lib/module/ui/panel/FloatPanelView.js.map +1 -1
  42. package/lib/module/ui/panel/StreamingSettingsModal.js +524 -0
  43. package/lib/module/ui/panel/StreamingSettingsModal.js.map +1 -0
  44. package/lib/module/ui/panel/useTabAnimation.js +67 -0
  45. package/lib/module/ui/panel/useTabAnimation.js.map +1 -0
  46. package/lib/module/utils/autoDetectDaemon.js +136 -0
  47. package/lib/module/utils/autoDetectDaemon.js.map +1 -0
  48. package/lib/module/utils/createPersistedObservableStore.js +23 -3
  49. package/lib/module/utils/createPersistedObservableStore.js.map +1 -1
  50. package/lib/module/utils/daemonConnection.js +77 -0
  51. package/lib/module/utils/daemonConnection.js.map +1 -0
  52. package/lib/module/utils/daemonSettings.js +102 -0
  53. package/lib/module/utils/daemonSettings.js.map +1 -0
  54. package/lib/module/utils/reportToDaemon.js +105 -0
  55. package/lib/module/utils/reportToDaemon.js.map +1 -0
  56. package/lib/module/utils/sessionReport.js +128 -0
  57. package/lib/module/utils/sessionReport.js.map +1 -0
  58. package/lib/module/utils/streamToDaemon.js +328 -0
  59. package/lib/module/utils/streamToDaemon.js.map +1 -0
  60. package/lib/typescript/src/features/network/index.d.ts +2 -4
  61. package/lib/typescript/src/features/network/index.d.ts.map +1 -1
  62. package/lib/typescript/src/features/network/networkInterceptor.d.ts +2 -15
  63. package/lib/typescript/src/features/network/networkInterceptor.d.ts.map +1 -1
  64. package/lib/typescript/src/index.d.ts +11 -1
  65. package/lib/typescript/src/index.d.ts.map +1 -1
  66. package/lib/typescript/src/ui/panel/DebugPanel.d.ts.map +1 -1
  67. package/lib/typescript/src/ui/panel/FloatPanelView.d.ts.map +1 -1
  68. package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts +8 -0
  69. package/lib/typescript/src/ui/panel/StreamingSettingsModal.d.ts.map +1 -0
  70. package/lib/typescript/src/ui/panel/useTabAnimation.d.ts +14 -0
  71. package/lib/typescript/src/ui/panel/useTabAnimation.d.ts.map +1 -0
  72. package/lib/typescript/src/utils/autoDetectDaemon.d.ts +15 -0
  73. package/lib/typescript/src/utils/autoDetectDaemon.d.ts.map +1 -0
  74. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts +2 -1
  75. package/lib/typescript/src/utils/createPersistedObservableStore.d.ts.map +1 -1
  76. package/lib/typescript/src/utils/daemonConnection.d.ts +18 -0
  77. package/lib/typescript/src/utils/daemonConnection.d.ts.map +1 -0
  78. package/lib/typescript/src/utils/daemonSettings.d.ts +19 -0
  79. package/lib/typescript/src/utils/daemonSettings.d.ts.map +1 -0
  80. package/lib/typescript/src/utils/reportToDaemon.d.ts +34 -0
  81. package/lib/typescript/src/utils/reportToDaemon.d.ts.map +1 -0
  82. package/lib/typescript/src/utils/sessionReport.d.ts +18 -0
  83. package/lib/typescript/src/utils/sessionReport.d.ts.map +1 -0
  84. package/lib/typescript/src/utils/streamToDaemon.d.ts +23 -0
  85. package/lib/typescript/src/utils/streamToDaemon.d.ts.map +1 -0
  86. package/node/daemon/src/cli.js +75 -0
  87. package/node/daemon/src/console/console.html +936 -0
  88. package/node/daemon/src/console/index.js +47 -0
  89. package/node/daemon/src/constants.js +32 -0
  90. package/node/daemon/src/index.js +11 -0
  91. package/node/daemon/src/server.js +365 -0
  92. package/node/daemon/src/store.js +110 -0
  93. package/node/mcp/src/cli.js +31 -0
  94. package/node/mcp/src/constants.js +13 -0
  95. package/node/mcp/src/daemonClient.js +132 -0
  96. package/node/mcp/src/httpClient.js +49 -0
  97. package/node/mcp/src/index.js +15 -0
  98. package/node/mcp/src/logs.js +95 -0
  99. package/node/mcp/src/server.js +144 -0
  100. package/node/mcp/src/tools.js +84 -0
  101. package/package.json +10 -5
  102. package/src/features/network/index.ts +35 -19
  103. package/src/features/network/networkInterceptor.ts +224 -236
  104. package/src/index.ts +15 -1
  105. package/src/ui/panel/DebugPanel.tsx +23 -1
  106. package/src/ui/panel/FloatPanelView.tsx +10 -68
  107. package/src/ui/panel/StreamingSettingsModal.tsx +566 -0
  108. package/src/ui/panel/useTabAnimation.ts +77 -0
  109. package/src/utils/autoDetectDaemon.ts +175 -0
  110. package/src/utils/createPersistedObservableStore.ts +16 -3
  111. package/src/utils/daemonConnection.ts +133 -0
  112. package/src/utils/daemonSettings.ts +134 -0
  113. package/src/utils/reportToDaemon.ts +172 -0
  114. package/src/utils/sessionReport.ts +203 -0
  115. 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
- // Intercepts fetch requests and optionally axios via interceptors.
7
- // Axios response data is captured directly through interceptor callbacks,
8
- // avoiding the unreliable XHR response interception in React Native.
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 headersToObject(
42
- headers: Record<string, string> | Headers | undefined,
43
- ): Record<string, string> {
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
- function getRequestSnapshot(
61
- input: unknown,
62
- init?: RequestInit,
63
- ): NetworkLogEntry['request'] {
64
- const request = input instanceof Request ? input : null;
65
- return {
66
- url: typeof input === 'string' ? input : request?.url ?? String(input),
67
- method: (init?.method || request?.method || 'GET').toUpperCase(),
68
- headers: init?.headers
69
- ? headersToObject(init.headers as Record<string, string> | Headers)
70
- : request?.headers
71
- ? headersToObject(request.headers)
72
- : undefined,
73
- body: init?.body,
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
- async function parseResponseBody(response: Response): Promise<unknown> {
78
- const raw = await response.clone().text();
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 normalizeAxiosHeaders(
90
- headers: unknown,
91
- ): Record<string, string> | undefined {
92
- if (!headers || typeof headers !== 'object') {
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
- // AxiosHeaders instance
96
- if (typeof (headers as Record<string, unknown>).forEach === 'function') {
97
- const result: Record<string, string> = {};
98
- (headers as { forEach(fn: (value: string, key: string) => void): void }).forEach(
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
- return Object.keys(result).length > 0 ? result : undefined;
67
+
68
+ return undefined;
115
69
  }
116
70
 
117
- function buildFullUrl(config: {
118
- baseURL?: string;
119
- url?: string;
120
- }): string {
121
- const base = config.baseURL ?? '';
122
- const url = config.url ?? '';
123
- if (!base || url.startsWith('http')) {
124
- return url;
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
- // ─── Fetch interceptor ─────────────────────────────────
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
- let originalFetch: typeof globalThis.fetch | null = null;
132
- let fetchRefCount = 0;
145
+ function stopXMLHttpRequest(): void {
146
+ xhrRefCount = Math.max(0, xhrRefCount - 1);
147
+ if (xhrRefCount > 0 || !originalXMLHttpRequest) {
148
+ return;
149
+ }
133
150
 
134
- function stopFetch(): void {
135
- fetchRefCount = Math.max(0, fetchRefCount - 1);
136
- if (fetchRefCount === 0 && originalFetch) {
137
- globalThis.fetch = originalFetch;
138
- originalFetch = null;
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 startFetch(emit: (entry: NetworkLogPayload) => void): () => void {
143
- fetchRefCount += 1;
144
- if (originalFetch) {
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
- stopFetch();
181
+ stopXMLHttpRequest();
147
182
  };
148
183
  }
149
184
 
150
- originalFetch = globalThis.fetch;
185
+ originalXMLHttpRequest = CurrentXMLHttpRequest;
186
+ originalXhrOpen = CurrentXMLHttpRequest.prototype.open;
187
+ originalXhrSend = CurrentXMLHttpRequest.prototype.send;
188
+ originalXhrSetRequestHeader = CurrentXMLHttpRequest.prototype.setRequestHeader;
151
189
 
152
- globalThis.fetch = async function interceptedFetch(
153
- input: Parameters<typeof fetch>[0],
154
- init?: Parameters<typeof fetch>[1],
190
+ CurrentXMLHttpRequest.prototype.open = function interceptedOpen(
191
+ this: XMLHttpRequestLike,
192
+ method: string,
193
+ url: string,
194
+ ...args: unknown[]
155
195
  ) {
156
- const startTime = Date.now();
157
-
158
- let rewrittenInput: typeof input = input;
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
- return () => {
203
- stopFetch();
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
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
210
- const pendingAxiosRequests = new WeakMap<any, { startTime: number; timestamp: number }>();
211
-
212
- export function startAxios(
213
- axiosInstance: AxiosInstanceLike,
214
- emit: (entry: NetworkLogPayload) => void,
215
- ): () => void {
216
- const requestInterceptorId = axiosInstance.interceptors.request.use(
217
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
218
- (config: any) => {
219
- const now = Date.now();
220
- pendingAxiosRequests.set(config, { startTime: now, timestamp: now });
221
-
222
- if (urlRewriter.get() && config.url) {
223
- const fullUrl = buildFullUrl(config);
224
- const rewritten = rewriteUrl(fullUrl);
225
- if (rewritten !== fullUrl) {
226
- config.url = rewritten;
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
- return config;
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: startTime,
246
- duration: Date.now() - startTime,
244
+ timestamp: currentState.timestamp,
245
+ duration: Date.now() - currentState.timestamp,
247
246
  request: {
248
- url: buildFullUrl(config),
249
- method: (config?.method ?? 'GET').toUpperCase(),
250
- headers: normalizeAxiosHeaders(config?.headers),
251
- body: config?.data,
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: response.status,
255
- statusText: response.statusText,
256
- headers: normalizeAxiosHeaders(response.headers),
257
- data: response.data,
258
- success: response.status >= 200 && response.status < 300,
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
- return response;
263
- },
264
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
265
- (error: any) => {
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
- return Promise.reject(error);
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
- axiosInstance.interceptors.request.eject(requestInterceptorId);
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 (originalFetch) {
307
- globalThis.fetch = originalFetch;
308
- originalFetch = null;
294
+ if (originalXMLHttpRequest) {
295
+ xhrRefCount = 1;
296
+ stopXMLHttpRequest();
309
297
  }
310
- fetchRefCount = 0;
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, AxiosInstanceLike } from './features/network';
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,