react-native-debug-toolkit 2.2.0 → 2.3.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/lib/commonjs/features/network/index.js +4 -10
- package/lib/commonjs/features/network/index.js.map +1 -1
- package/lib/commonjs/features/network/networkInterceptor.js +158 -203
- package/lib/commonjs/features/network/networkInterceptor.js.map +1 -1
- package/lib/module/features/network/index.js +5 -11
- package/lib/module/features/network/index.js.map +1 -1
- package/lib/module/features/network/networkInterceptor.js +157 -202
- package/lib/module/features/network/networkInterceptor.js.map +1 -1
- package/lib/typescript/src/features/network/index.d.ts +0 -4
- package/lib/typescript/src/features/network/index.d.ts.map +1 -1
- package/lib/typescript/src/features/network/networkInterceptor.d.ts +1 -14
- package/lib/typescript/src/features/network/networkInterceptor.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/features/network/index.ts +5 -16
- package/src/features/network/networkInterceptor.ts +212 -237
- package/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-debug-toolkit",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A dev-only floating debug panel for React Native with network, console, Zustand, navigation, and event logs",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
37
|
-
"url": "https://github.com/
|
|
37
|
+
"url": "https://github.com/superzcj/react-native-debug-toolkit"
|
|
38
38
|
},
|
|
39
|
-
"homepage": "https://github.com/
|
|
39
|
+
"homepage": "https://github.com/superzcj/react-native-debug-toolkit#readme",
|
|
40
40
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/superzcj/react-native-debug-toolkit/issues"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@react-native-clipboard/clipboard": ">=1.0.0",
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { NetworkLogTab } from './NetworkLogTab';
|
|
2
2
|
|
|
3
|
-
export type { AxiosInstanceLike } from './networkInterceptor';
|
|
4
3
|
import type { DebugFeature, NetworkLogEntry } from '../../types';
|
|
5
4
|
import { createEventChannel } from '../../utils/createEventChannel';
|
|
6
5
|
import { createPersistedObservableStore } from '../../utils/createPersistedObservableStore';
|
|
7
6
|
import { KEYS } from '../../utils/debugPreferences';
|
|
8
7
|
import { urlRewriter } from '../../utils/urlRewriterRegistry';
|
|
9
|
-
import type { AxiosInstanceLike } from './networkInterceptor';
|
|
10
8
|
import {
|
|
11
|
-
|
|
12
|
-
startAxios,
|
|
9
|
+
startXMLHttpRequest,
|
|
13
10
|
resetInterceptors,
|
|
14
11
|
} from './networkInterceptor';
|
|
15
12
|
|
|
@@ -46,8 +43,6 @@ export interface NetworkFeatureConfig {
|
|
|
46
43
|
maxLogs?: number;
|
|
47
44
|
/** URLs to filter out from logging */
|
|
48
45
|
blacklist?: Array<string | RegExp>;
|
|
49
|
-
/** Axios instance to intercept. Pass your axios.create() instance to capture axios requests. */
|
|
50
|
-
axiosInstance?: AxiosInstanceLike;
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeature<NetworkLogEntry[]> => {
|
|
@@ -59,8 +54,7 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeatur
|
|
|
59
54
|
});
|
|
60
55
|
let initialized = false;
|
|
61
56
|
let unsubscribeLogs: (() => void) | null = null;
|
|
62
|
-
let
|
|
63
|
-
let stopAxiosFn: (() => void) | null = null;
|
|
57
|
+
let stopXhrFn: (() => void) | null = null;
|
|
64
58
|
|
|
65
59
|
const handleLog = (entry: NetworkLogPayload) => {
|
|
66
60
|
if (isUrlBlacklisted(entry.request.url, blacklist)) {
|
|
@@ -78,10 +72,7 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeatur
|
|
|
78
72
|
return;
|
|
79
73
|
}
|
|
80
74
|
unsubscribeLogs = networkChannel.subscribe(handleLog);
|
|
81
|
-
|
|
82
|
-
if (config?.axiosInstance) {
|
|
83
|
-
stopAxiosFn = startAxios(config.axiosInstance, emitNetworkLog);
|
|
84
|
-
}
|
|
75
|
+
stopXhrFn = startXMLHttpRequest(emitNetworkLog);
|
|
85
76
|
initialized = true;
|
|
86
77
|
},
|
|
87
78
|
getSnapshot: () => logStore.getData(),
|
|
@@ -95,10 +86,8 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeatur
|
|
|
95
86
|
urlRewriter.set(null);
|
|
96
87
|
unsubscribeLogs?.();
|
|
97
88
|
unsubscribeLogs = null;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
stopAxiosFn?.();
|
|
101
|
-
stopAxiosFn = null;
|
|
89
|
+
stopXhrFn?.();
|
|
90
|
+
stopXhrFn = null;
|
|
102
91
|
logStore.clear();
|
|
103
92
|
initialized = false;
|
|
104
93
|
},
|
|
@@ -3,26 +3,8 @@ import { urlRewriter } from '../../utils/urlRewriterRegistry';
|
|
|
3
3
|
|
|
4
4
|
type NetworkLogPayload = Omit<NetworkLogEntry, 'id'>;
|
|
5
5
|
|
|
6
|
-
// Intercepts
|
|
7
|
-
//
|
|
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
|
+
// Intercepts React Native's XMLHttpRequest transport layer.
|
|
7
|
+
// RN fetch and axios (default adapter) both go through XHR — one hook captures everything.
|
|
26
8
|
|
|
27
9
|
// ─── Shared helpers ────────────────────────────────────
|
|
28
10
|
|
|
@@ -38,47 +20,31 @@ function rewriteUrl(url: string): string {
|
|
|
38
20
|
}
|
|
39
21
|
}
|
|
40
22
|
|
|
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;
|
|
23
|
+
function parseRawHeaders(rawHeaders: string | null | undefined): Record<string, string> | undefined {
|
|
24
|
+
if (!rawHeaders) {
|
|
25
|
+
return undefined;
|
|
53
26
|
}
|
|
54
|
-
Object.keys(headers).forEach((key) => {
|
|
55
|
-
result[key] = (headers as Record<string, string>)[key]!;
|
|
56
|
-
});
|
|
57
|
-
return result;
|
|
58
|
-
}
|
|
59
27
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
28
|
+
const headers: Record<string, string> = {};
|
|
29
|
+
rawHeaders
|
|
30
|
+
.trim()
|
|
31
|
+
.split(/[\r\n]+/)
|
|
32
|
+
.forEach((line) => {
|
|
33
|
+
const separatorIndex = line.indexOf(':');
|
|
34
|
+
if (separatorIndex <= 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
38
|
+
const value = line.slice(separatorIndex + 1).trim();
|
|
39
|
+
if (key) {
|
|
40
|
+
headers[key] = value;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
75
44
|
}
|
|
76
45
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!raw) {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
46
|
+
function parseBodyText(raw: string): unknown {
|
|
47
|
+
if (!raw) return undefined;
|
|
82
48
|
try {
|
|
83
49
|
return JSON.parse(raw);
|
|
84
50
|
} catch {
|
|
@@ -86,226 +52,235 @@ async function parseResponseBody(response: Response): Promise<unknown> {
|
|
|
86
52
|
}
|
|
87
53
|
}
|
|
88
54
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
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;
|
|
55
|
+
function normalizeXhrResponseBody(xhr: XMLHttpRequestLike): unknown {
|
|
56
|
+
const text = safeRead(() => xhr.responseText);
|
|
57
|
+
if (typeof text === 'string' && text) {
|
|
58
|
+
return parseBodyText(text);
|
|
104
59
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const val = obj[key];
|
|
110
|
-
if (typeof val === 'string') {
|
|
111
|
-
result[key] = val;
|
|
112
|
-
}
|
|
60
|
+
|
|
61
|
+
const response = safeRead(() => xhr.response);
|
|
62
|
+
if (response != null) {
|
|
63
|
+
return response;
|
|
113
64
|
}
|
|
114
|
-
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
115
67
|
}
|
|
116
68
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
69
|
+
// ─── XMLHttpRequest interceptor ───────────────────────
|
|
70
|
+
|
|
71
|
+
interface XMLHttpRequestLike {
|
|
72
|
+
readyState: number;
|
|
73
|
+
DONE?: number;
|
|
74
|
+
status: number;
|
|
75
|
+
statusText?: string;
|
|
76
|
+
responseHeaders?: Record<string, string> | null;
|
|
77
|
+
responseType?: string;
|
|
78
|
+
response?: unknown;
|
|
79
|
+
responseText?: string;
|
|
80
|
+
responseURL?: string;
|
|
81
|
+
open(method: string, url: string, ...args: unknown[]): void;
|
|
82
|
+
send(body?: unknown): void;
|
|
83
|
+
setRequestHeader(header: string, value: string): void;
|
|
84
|
+
getAllResponseHeaders?(): string | null;
|
|
85
|
+
addEventListener(type: string, listener: () => void): void;
|
|
86
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
type XMLHttpRequestConstructorLike = new () => XMLHttpRequestLike;
|
|
90
|
+
|
|
91
|
+
type XhrState = {
|
|
92
|
+
method: string;
|
|
93
|
+
url: string;
|
|
94
|
+
headers: Record<string, string>;
|
|
95
|
+
body?: unknown;
|
|
96
|
+
timestamp: number;
|
|
97
|
+
error?: string;
|
|
98
|
+
completed?: boolean;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let originalXMLHttpRequest: XMLHttpRequestConstructorLike | null = null;
|
|
102
|
+
let originalXhrOpen: XMLHttpRequestLike['open'] | null = null;
|
|
103
|
+
let originalXhrSend: XMLHttpRequestLike['send'] | null = null;
|
|
104
|
+
let originalXhrSetRequestHeader: XMLHttpRequestLike['setRequestHeader'] | null = null;
|
|
105
|
+
let xhrRefCount = 0;
|
|
106
|
+
const pendingXhrRequests = new WeakMap<XMLHttpRequestLike, XhrState>();
|
|
107
|
+
|
|
108
|
+
function safeRead<T>(read: () => T): T | undefined {
|
|
109
|
+
try {
|
|
110
|
+
return read();
|
|
111
|
+
} catch {
|
|
112
|
+
return undefined;
|
|
125
113
|
}
|
|
126
|
-
return base.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
|
|
127
114
|
}
|
|
128
115
|
|
|
129
|
-
|
|
116
|
+
function getGlobalXMLHttpRequest(): XMLHttpRequestConstructorLike | undefined {
|
|
117
|
+
return (globalThis as { XMLHttpRequest?: XMLHttpRequestConstructorLike }).XMLHttpRequest;
|
|
118
|
+
}
|
|
130
119
|
|
|
131
|
-
|
|
132
|
-
|
|
120
|
+
function getXhrResponseHeaders(xhr: XMLHttpRequestLike): Record<string, string> | undefined {
|
|
121
|
+
const rawHeaders = safeRead(() => xhr.getAllResponseHeaders?.());
|
|
122
|
+
const parsedHeaders = parseRawHeaders(rawHeaders);
|
|
123
|
+
if (parsedHeaders) {
|
|
124
|
+
return parsedHeaders;
|
|
125
|
+
}
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
globalThis.fetch = originalFetch;
|
|
138
|
-
originalFetch = null;
|
|
127
|
+
const headers = xhr.responseHeaders;
|
|
128
|
+
if (!headers) {
|
|
129
|
+
return undefined;
|
|
139
130
|
}
|
|
131
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
140
132
|
}
|
|
141
133
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
return
|
|
146
|
-
stopFetch();
|
|
147
|
-
};
|
|
134
|
+
function stopXMLHttpRequest(): void {
|
|
135
|
+
xhrRefCount = Math.max(0, xhrRefCount - 1);
|
|
136
|
+
if (xhrRefCount > 0 || !originalXMLHttpRequest) {
|
|
137
|
+
return;
|
|
148
138
|
}
|
|
149
139
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
init?: Parameters<typeof fetch>[1],
|
|
155
|
-
) {
|
|
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
|
-
}
|
|
140
|
+
const CurrentXMLHttpRequest = getGlobalXMLHttpRequest();
|
|
141
|
+
if (CurrentXMLHttpRequest) {
|
|
142
|
+
if (originalXhrOpen) {
|
|
143
|
+
CurrentXMLHttpRequest.prototype.open = originalXhrOpen;
|
|
165
144
|
}
|
|
166
|
-
|
|
167
|
-
|
|
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;
|
|
145
|
+
if (originalXhrSend) {
|
|
146
|
+
CurrentXMLHttpRequest.prototype.send = originalXhrSend;
|
|
199
147
|
}
|
|
200
|
-
|
|
148
|
+
if (originalXhrSetRequestHeader) {
|
|
149
|
+
CurrentXMLHttpRequest.prototype.setRequestHeader = originalXhrSetRequestHeader;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
201
152
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
153
|
+
originalXMLHttpRequest = null;
|
|
154
|
+
originalXhrOpen = null;
|
|
155
|
+
originalXhrSend = null;
|
|
156
|
+
originalXhrSetRequestHeader = null;
|
|
205
157
|
}
|
|
206
158
|
|
|
207
|
-
|
|
208
|
-
|
|
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,
|
|
159
|
+
export function startXMLHttpRequest(
|
|
214
160
|
emit: (entry: NetworkLogPayload) => void,
|
|
215
161
|
): () => void {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
162
|
+
const CurrentXMLHttpRequest = getGlobalXMLHttpRequest();
|
|
163
|
+
if (!CurrentXMLHttpRequest) {
|
|
164
|
+
return () => {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
xhrRefCount += 1;
|
|
168
|
+
if (originalXMLHttpRequest) {
|
|
169
|
+
return () => {
|
|
170
|
+
stopXMLHttpRequest();
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
originalXMLHttpRequest = CurrentXMLHttpRequest;
|
|
175
|
+
originalXhrOpen = CurrentXMLHttpRequest.prototype.open;
|
|
176
|
+
originalXhrSend = CurrentXMLHttpRequest.prototype.send;
|
|
177
|
+
originalXhrSetRequestHeader = CurrentXMLHttpRequest.prototype.setRequestHeader;
|
|
178
|
+
|
|
179
|
+
CurrentXMLHttpRequest.prototype.open = function interceptedOpen(
|
|
180
|
+
this: XMLHttpRequestLike,
|
|
181
|
+
method: string,
|
|
182
|
+
url: string,
|
|
183
|
+
...args: unknown[]
|
|
184
|
+
) {
|
|
185
|
+
const rewrittenUrl = urlRewriter.get() ? rewriteUrl(url) : url;
|
|
186
|
+
pendingXhrRequests.set(this, {
|
|
187
|
+
method: (method || 'GET').toUpperCase(),
|
|
188
|
+
url: rewrittenUrl,
|
|
189
|
+
headers: {},
|
|
190
|
+
timestamp: Date.now(),
|
|
191
|
+
});
|
|
192
|
+
return originalXhrOpen!.call(this, method, rewrittenUrl, ...args);
|
|
193
|
+
};
|
|
232
194
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
195
|
+
CurrentXMLHttpRequest.prototype.setRequestHeader = function interceptedSetRequestHeader(
|
|
196
|
+
this: XMLHttpRequestLike,
|
|
197
|
+
header: string,
|
|
198
|
+
value: string,
|
|
199
|
+
) {
|
|
200
|
+
const state = pendingXhrRequests.get(this);
|
|
201
|
+
if (state) {
|
|
202
|
+
state.headers[header] = value;
|
|
203
|
+
}
|
|
204
|
+
return originalXhrSetRequestHeader!.call(this, header, value);
|
|
205
|
+
};
|
|
236
206
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
207
|
+
CurrentXMLHttpRequest.prototype.send = function interceptedSend(
|
|
208
|
+
this: XMLHttpRequestLike,
|
|
209
|
+
body?: unknown,
|
|
210
|
+
) {
|
|
211
|
+
const that = this;
|
|
212
|
+
const state = pendingXhrRequests.get(that) ?? {
|
|
213
|
+
method: 'GET',
|
|
214
|
+
url: '',
|
|
215
|
+
headers: {},
|
|
216
|
+
timestamp: Date.now(),
|
|
217
|
+
};
|
|
218
|
+
state.body = body;
|
|
219
|
+
state.timestamp = Date.now();
|
|
220
|
+
pendingXhrRequests.set(that, state);
|
|
221
|
+
|
|
222
|
+
const complete = () => {
|
|
223
|
+
const currentState = pendingXhrRequests.get(that);
|
|
224
|
+
if (!currentState || currentState.completed) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
currentState.completed = true;
|
|
243
228
|
|
|
229
|
+
const headers = getXhrResponseHeaders(that);
|
|
244
230
|
emit({
|
|
245
|
-
timestamp:
|
|
246
|
-
duration: Date.now() -
|
|
231
|
+
timestamp: currentState.timestamp,
|
|
232
|
+
duration: Date.now() - currentState.timestamp,
|
|
247
233
|
request: {
|
|
248
|
-
url:
|
|
249
|
-
method:
|
|
250
|
-
headers:
|
|
251
|
-
|
|
234
|
+
url: currentState.url,
|
|
235
|
+
method: currentState.method,
|
|
236
|
+
headers: Object.keys(currentState.headers).length > 0
|
|
237
|
+
? currentState.headers
|
|
238
|
+
: undefined,
|
|
239
|
+
body: currentState.body,
|
|
252
240
|
},
|
|
253
241
|
response: {
|
|
254
|
-
status:
|
|
255
|
-
statusText:
|
|
256
|
-
headers
|
|
257
|
-
data:
|
|
258
|
-
success:
|
|
242
|
+
status: that.status,
|
|
243
|
+
statusText: that.statusText,
|
|
244
|
+
headers,
|
|
245
|
+
data: normalizeXhrResponseBody(that),
|
|
246
|
+
success: that.status >= 200 && that.status < 300,
|
|
259
247
|
},
|
|
248
|
+
error: currentState.error,
|
|
260
249
|
});
|
|
250
|
+
};
|
|
261
251
|
|
|
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
|
-
});
|
|
252
|
+
const markError = (message: string) => {
|
|
253
|
+
const currentState = pendingXhrRequests.get(that);
|
|
254
|
+
if (currentState) {
|
|
255
|
+
currentState.error = message;
|
|
291
256
|
}
|
|
257
|
+
};
|
|
292
258
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
259
|
+
that.addEventListener('error', () => {
|
|
260
|
+
markError('Network Error');
|
|
261
|
+
});
|
|
262
|
+
that.addEventListener('timeout', () => {
|
|
263
|
+
markError('Timeout');
|
|
264
|
+
});
|
|
265
|
+
that.addEventListener('abort', () => {
|
|
266
|
+
markError('Aborted');
|
|
267
|
+
});
|
|
268
|
+
that.addEventListener('loadend', complete);
|
|
269
|
+
|
|
270
|
+
return originalXhrSend!.call(that, body);
|
|
271
|
+
};
|
|
296
272
|
|
|
297
273
|
return () => {
|
|
298
|
-
|
|
299
|
-
axiosInstance.interceptors.response.eject(responseInterceptorId);
|
|
274
|
+
stopXMLHttpRequest();
|
|
300
275
|
};
|
|
301
276
|
}
|
|
302
277
|
|
|
303
278
|
// ─── Cleanup ───────────────────────────────────────────
|
|
304
279
|
|
|
305
280
|
export function resetInterceptors(): void {
|
|
306
|
-
if (
|
|
307
|
-
|
|
308
|
-
|
|
281
|
+
if (originalXMLHttpRequest) {
|
|
282
|
+
xhrRefCount = 1;
|
|
283
|
+
stopXMLHttpRequest();
|
|
309
284
|
}
|
|
310
|
-
|
|
285
|
+
xhrRefCount = 0;
|
|
311
286
|
}
|
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';
|