react-native-debug-toolkit 2.0.0 → 2.1.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 (228) hide show
  1. package/README.md +72 -72
  2. package/README.zh-CN.md +209 -0
  3. package/lib/commonjs/components/ClipboardTab.js +15 -16
  4. package/lib/commonjs/components/ClipboardTab.js.map +1 -1
  5. package/lib/commonjs/components/ConsoleLogTab.js +69 -202
  6. package/lib/commonjs/components/ConsoleLogTab.js.map +1 -1
  7. package/lib/commonjs/components/DebugPanel.js +230 -0
  8. package/lib/commonjs/components/DebugPanel.js.map +1 -0
  9. package/lib/commonjs/components/DebugView.js +66 -0
  10. package/lib/commonjs/components/DebugView.js.map +1 -0
  11. package/lib/commonjs/components/EnvironmentTab.js +22 -23
  12. package/lib/commonjs/components/EnvironmentTab.js.map +1 -1
  13. package/lib/commonjs/components/FeatureTabBar.js +182 -0
  14. package/lib/commonjs/components/FeatureTabBar.js.map +1 -0
  15. package/lib/commonjs/components/FloatIcon.js +187 -0
  16. package/lib/commonjs/components/FloatIcon.js.map +1 -0
  17. package/lib/commonjs/components/FloatPanelView.js +140 -723
  18. package/lib/commonjs/components/FloatPanelView.js.map +1 -1
  19. package/lib/commonjs/components/NavigationLogTab.js +8 -8
  20. package/lib/commonjs/components/NavigationLogTab.js.map +1 -1
  21. package/lib/commonjs/components/NetworkLogTab.js +179 -350
  22. package/lib/commonjs/components/NetworkLogTab.js.map +1 -1
  23. package/lib/commonjs/components/ThirdPartyLibsTab.js +8 -8
  24. package/lib/commonjs/components/ThirdPartyLibsTab.js.map +1 -1
  25. package/lib/commonjs/components/TrackLogTab.js +106 -248
  26. package/lib/commonjs/components/TrackLogTab.js.map +1 -1
  27. package/lib/commonjs/components/ZustandLogTab.js +148 -288
  28. package/lib/commonjs/components/ZustandLogTab.js.map +1 -1
  29. package/lib/commonjs/components/shared/CollapsibleSection.js +4 -4
  30. package/lib/commonjs/components/shared/CollapsibleSection.js.map +1 -1
  31. package/lib/commonjs/components/shared/CopyButton.js +3 -3
  32. package/lib/commonjs/components/shared/CopyButton.js.map +1 -1
  33. package/lib/commonjs/components/shared/LogListScreen.js +174 -0
  34. package/lib/commonjs/components/shared/LogListScreen.js.map +1 -0
  35. package/lib/commonjs/core/DebugToolkit.js +92 -112
  36. package/lib/commonjs/core/DebugToolkit.js.map +1 -1
  37. package/lib/commonjs/core/DebugToolkitProvider.js +30 -28
  38. package/lib/commonjs/core/DebugToolkitProvider.js.map +1 -1
  39. package/lib/commonjs/features/ClipboardFeature.js +6 -2
  40. package/lib/commonjs/features/ClipboardFeature.js.map +1 -1
  41. package/lib/commonjs/features/ConsoleLogFeature.js +66 -49
  42. package/lib/commonjs/features/ConsoleLogFeature.js.map +1 -1
  43. package/lib/commonjs/features/EnvironmentFeature.js +5 -13
  44. package/lib/commonjs/features/EnvironmentFeature.js.map +1 -1
  45. package/lib/commonjs/features/NavigationLogFeature.js +17 -38
  46. package/lib/commonjs/features/NavigationLogFeature.js.map +1 -1
  47. package/lib/commonjs/features/NetworkFeature.js +35 -256
  48. package/lib/commonjs/features/NetworkFeature.js.map +1 -1
  49. package/lib/commonjs/features/TrackFeature.js +17 -38
  50. package/lib/commonjs/features/TrackFeature.js.map +1 -1
  51. package/lib/commonjs/features/ZustandLogFeature.js +21 -38
  52. package/lib/commonjs/features/ZustandLogFeature.js.map +1 -1
  53. package/lib/commonjs/hooks/useNavigationLogger.js.map +1 -1
  54. package/lib/commonjs/index.js +7 -20
  55. package/lib/commonjs/index.js.map +1 -1
  56. package/lib/commonjs/initialize.js +19 -96
  57. package/lib/commonjs/initialize.js.map +1 -1
  58. package/lib/commonjs/interceptors/networkInterceptor.js +466 -0
  59. package/lib/commonjs/interceptors/networkInterceptor.js.map +1 -0
  60. package/lib/commonjs/utils/colors.js +48 -0
  61. package/lib/commonjs/utils/colors.js.map +1 -0
  62. package/lib/commonjs/utils/createChannelFeature.js +48 -0
  63. package/lib/commonjs/utils/createChannelFeature.js.map +1 -0
  64. package/lib/commonjs/utils/layout.js +8 -0
  65. package/lib/commonjs/utils/layout.js.map +1 -0
  66. package/lib/commonjs/utils/urlRewriterRegistry.js +14 -0
  67. package/lib/commonjs/utils/urlRewriterRegistry.js.map +1 -0
  68. package/lib/module/components/ClipboardTab.js +8 -8
  69. package/lib/module/components/ClipboardTab.js.map +1 -1
  70. package/lib/module/components/ConsoleLogTab.js +66 -199
  71. package/lib/module/components/ConsoleLogTab.js.map +1 -1
  72. package/lib/module/components/DebugPanel.js +225 -0
  73. package/lib/module/components/DebugPanel.js.map +1 -0
  74. package/lib/module/components/DebugView.js +61 -0
  75. package/lib/module/components/DebugView.js.map +1 -0
  76. package/lib/module/components/EnvironmentTab.js +3 -3
  77. package/lib/module/components/EnvironmentTab.js.map +1 -1
  78. package/lib/module/components/FeatureTabBar.js +177 -0
  79. package/lib/module/components/FeatureTabBar.js.map +1 -0
  80. package/lib/module/components/FloatIcon.js +182 -0
  81. package/lib/module/components/FloatIcon.js.map +1 -0
  82. package/lib/module/components/FloatPanelView.js +141 -723
  83. package/lib/module/components/FloatPanelView.js.map +1 -1
  84. package/lib/module/components/NavigationLogTab.js +1 -1
  85. package/lib/module/components/NavigationLogTab.js.map +1 -1
  86. package/lib/module/components/NetworkLogTab.js +167 -338
  87. package/lib/module/components/NetworkLogTab.js.map +1 -1
  88. package/lib/module/components/ThirdPartyLibsTab.js +1 -1
  89. package/lib/module/components/ThirdPartyLibsTab.js.map +1 -1
  90. package/lib/module/components/TrackLogTab.js +94 -236
  91. package/lib/module/components/TrackLogTab.js.map +1 -1
  92. package/lib/module/components/ZustandLogTab.js +136 -276
  93. package/lib/module/components/ZustandLogTab.js.map +1 -1
  94. package/lib/module/components/shared/CollapsibleSection.js +1 -1
  95. package/lib/module/components/shared/CollapsibleSection.js.map +1 -1
  96. package/lib/module/components/shared/CopyButton.js +1 -1
  97. package/lib/module/components/shared/CopyButton.js.map +1 -1
  98. package/lib/module/components/shared/LogListScreen.js +169 -0
  99. package/lib/module/components/shared/LogListScreen.js.map +1 -0
  100. package/lib/module/core/DebugToolkit.js +92 -111
  101. package/lib/module/core/DebugToolkit.js.map +1 -1
  102. package/lib/module/core/DebugToolkitProvider.js +31 -29
  103. package/lib/module/core/DebugToolkitProvider.js.map +1 -1
  104. package/lib/module/features/ClipboardFeature.js +6 -2
  105. package/lib/module/features/ClipboardFeature.js.map +1 -1
  106. package/lib/module/features/ConsoleLogFeature.js +65 -49
  107. package/lib/module/features/ConsoleLogFeature.js.map +1 -1
  108. package/lib/module/features/EnvironmentFeature.js +5 -13
  109. package/lib/module/features/EnvironmentFeature.js.map +1 -1
  110. package/lib/module/features/NavigationLogFeature.js +16 -38
  111. package/lib/module/features/NavigationLogFeature.js.map +1 -1
  112. package/lib/module/features/NetworkFeature.js +34 -255
  113. package/lib/module/features/NetworkFeature.js.map +1 -1
  114. package/lib/module/features/TrackFeature.js +16 -38
  115. package/lib/module/features/TrackFeature.js.map +1 -1
  116. package/lib/module/features/ZustandLogFeature.js +22 -38
  117. package/lib/module/features/ZustandLogFeature.js.map +1 -1
  118. package/lib/module/hooks/useNavigationLogger.js.map +1 -1
  119. package/lib/module/index.js +2 -3
  120. package/lib/module/index.js.map +1 -1
  121. package/lib/module/initialize.js +19 -95
  122. package/lib/module/initialize.js.map +1 -1
  123. package/lib/module/interceptors/networkInterceptor.js +460 -0
  124. package/lib/module/interceptors/networkInterceptor.js.map +1 -0
  125. package/lib/module/utils/colors.js +43 -0
  126. package/lib/module/utils/colors.js.map +1 -0
  127. package/lib/module/utils/createChannelFeature.js +44 -0
  128. package/lib/module/utils/createChannelFeature.js.map +1 -0
  129. package/lib/module/utils/layout.js +4 -0
  130. package/lib/module/utils/layout.js.map +1 -0
  131. package/lib/module/utils/urlRewriterRegistry.js +10 -0
  132. package/lib/module/utils/urlRewriterRegistry.js.map +1 -0
  133. package/lib/typescript/src/components/ClipboardTab.d.ts.map +1 -1
  134. package/lib/typescript/src/components/ConsoleLogTab.d.ts.map +1 -1
  135. package/lib/typescript/src/components/DebugPanel.d.ts +9 -0
  136. package/lib/typescript/src/components/DebugPanel.d.ts.map +1 -0
  137. package/lib/typescript/src/components/DebugView.d.ts +19 -0
  138. package/lib/typescript/src/components/DebugView.d.ts.map +1 -0
  139. package/lib/typescript/src/components/EnvironmentTab.d.ts.map +1 -1
  140. package/lib/typescript/src/components/FeatureTabBar.d.ts +13 -0
  141. package/lib/typescript/src/components/FeatureTabBar.d.ts.map +1 -0
  142. package/lib/typescript/src/components/FloatIcon.d.ts +12 -0
  143. package/lib/typescript/src/components/FloatIcon.d.ts.map +1 -0
  144. package/lib/typescript/src/components/FloatPanelView.d.ts +4 -59
  145. package/lib/typescript/src/components/FloatPanelView.d.ts.map +1 -1
  146. package/lib/typescript/src/components/NetworkLogTab.d.ts.map +1 -1
  147. package/lib/typescript/src/components/ThirdPartyLibsTab.d.ts.map +1 -1
  148. package/lib/typescript/src/components/TrackLogTab.d.ts.map +1 -1
  149. package/lib/typescript/src/components/ZustandLogTab.d.ts.map +1 -1
  150. package/lib/typescript/src/components/shared/LogListScreen.d.ts +21 -0
  151. package/lib/typescript/src/components/shared/LogListScreen.d.ts.map +1 -0
  152. package/lib/typescript/src/core/DebugToolkit.d.ts +11 -18
  153. package/lib/typescript/src/core/DebugToolkit.d.ts.map +1 -1
  154. package/lib/typescript/src/core/DebugToolkitProvider.d.ts +2 -5
  155. package/lib/typescript/src/core/DebugToolkitProvider.d.ts.map +1 -1
  156. package/lib/typescript/src/features/ClipboardFeature.d.ts +4 -0
  157. package/lib/typescript/src/features/ClipboardFeature.d.ts.map +1 -1
  158. package/lib/typescript/src/features/ConsoleLogFeature.d.ts +2 -0
  159. package/lib/typescript/src/features/ConsoleLogFeature.d.ts.map +1 -1
  160. package/lib/typescript/src/features/EnvironmentFeature.d.ts.map +1 -1
  161. package/lib/typescript/src/features/NavigationLogFeature.d.ts +2 -0
  162. package/lib/typescript/src/features/NavigationLogFeature.d.ts.map +1 -1
  163. package/lib/typescript/src/features/NetworkFeature.d.ts +7 -20
  164. package/lib/typescript/src/features/NetworkFeature.d.ts.map +1 -1
  165. package/lib/typescript/src/features/TrackFeature.d.ts +2 -0
  166. package/lib/typescript/src/features/TrackFeature.d.ts.map +1 -1
  167. package/lib/typescript/src/features/ZustandLogFeature.d.ts +2 -0
  168. package/lib/typescript/src/features/ZustandLogFeature.d.ts.map +1 -1
  169. package/lib/typescript/src/hooks/useNavigationLogger.d.ts +1 -8
  170. package/lib/typescript/src/hooks/useNavigationLogger.d.ts.map +1 -1
  171. package/lib/typescript/src/index.d.ts +5 -5
  172. package/lib/typescript/src/index.d.ts.map +1 -1
  173. package/lib/typescript/src/initialize.d.ts +3 -22
  174. package/lib/typescript/src/initialize.d.ts.map +1 -1
  175. package/lib/typescript/src/interceptors/networkInterceptor.d.ts +18 -0
  176. package/lib/typescript/src/interceptors/networkInterceptor.d.ts.map +1 -0
  177. package/lib/typescript/src/types/index.d.ts +26 -29
  178. package/lib/typescript/src/types/index.d.ts.map +1 -1
  179. package/lib/typescript/src/utils/colors.d.ts +21 -0
  180. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  181. package/lib/typescript/src/utils/createChannelFeature.d.ts +18 -0
  182. package/lib/typescript/src/utils/createChannelFeature.d.ts.map +1 -0
  183. package/lib/typescript/src/utils/layout.d.ts +2 -0
  184. package/lib/typescript/src/utils/layout.d.ts.map +1 -0
  185. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts +7 -0
  186. package/lib/typescript/src/utils/urlRewriterRegistry.d.ts.map +1 -0
  187. package/package.json +5 -1
  188. package/src/components/ClipboardTab.tsx +8 -8
  189. package/src/components/ConsoleLogTab.tsx +49 -163
  190. package/src/components/DebugPanel.tsx +215 -0
  191. package/src/components/DebugView.tsx +80 -0
  192. package/src/components/EnvironmentTab.tsx +3 -3
  193. package/src/components/FeatureTabBar.tsx +204 -0
  194. package/src/components/FloatIcon.tsx +171 -0
  195. package/src/components/FloatPanelView.tsx +135 -647
  196. package/src/components/NavigationLogTab.tsx +1 -1
  197. package/src/components/NetworkLogTab.tsx +128 -269
  198. package/src/components/ThirdPartyLibsTab.tsx +3 -3
  199. package/src/components/TrackLogTab.tsx +53 -188
  200. package/src/components/ZustandLogTab.tsx +79 -181
  201. package/src/components/shared/CollapsibleSection.tsx +1 -1
  202. package/src/components/shared/CopyButton.tsx +1 -1
  203. package/src/components/shared/LogListScreen.tsx +164 -0
  204. package/src/core/DebugToolkit.tsx +114 -138
  205. package/src/core/DebugToolkitProvider.tsx +32 -38
  206. package/src/features/ClipboardFeature.ts +6 -2
  207. package/src/features/ConsoleLogFeature.ts +66 -68
  208. package/src/features/EnvironmentFeature.ts +5 -13
  209. package/src/features/NavigationLogFeature.ts +12 -42
  210. package/src/features/NetworkFeature.ts +43 -405
  211. package/src/features/TrackFeature.ts +14 -49
  212. package/src/features/ZustandLogFeature.ts +16 -42
  213. package/src/hooks/useNavigationLogger.ts +1 -6
  214. package/src/index.ts +5 -9
  215. package/src/initialize.ts +28 -120
  216. package/src/interceptors/networkInterceptor.ts +646 -0
  217. package/src/types/index.ts +25 -36
  218. package/src/utils/colors.ts +38 -0
  219. package/src/utils/createChannelFeature.ts +55 -0
  220. package/src/utils/layout.ts +1 -0
  221. package/src/utils/urlRewriterRegistry.ts +10 -0
  222. package/lib/commonjs/utils/constants.js +0 -135
  223. package/lib/commonjs/utils/constants.js.map +0 -1
  224. package/lib/module/utils/constants.js +0 -130
  225. package/lib/module/utils/constants.js.map +0 -1
  226. package/lib/typescript/src/utils/constants.d.ts +0 -96
  227. package/lib/typescript/src/utils/constants.d.ts.map +0 -1
  228. package/src/utils/constants.ts +0 -91
@@ -2,356 +2,18 @@ import { NetworkLogTab } from '../components/NetworkLogTab';
2
2
  import type { DebugFeature, NetworkLogEntry } from '../types';
3
3
  import { createEventChannel } from '../utils/createEventChannel';
4
4
  import { createObservableStore } from '../utils/createObservableStore';
5
+ import { urlRewriter } from '../utils/urlRewriterRegistry';
6
+ import {
7
+ startFetch,
8
+ startXMLHttpRequest,
9
+ setupAxios,
10
+ resetInterceptors,
11
+ } from '../interceptors/networkInterceptor';
12
+ import type { AxiosInstanceLike } from '../interceptors/networkInterceptor';
5
13
 
6
14
  type NetworkLogPayload = Omit<NetworkLogEntry, 'id'>;
7
15
 
8
- interface AxiosInterceptorManager {
9
- use: (onFulfilled: unknown, onRejected?: unknown) => number | void;
10
- eject?: (id: number) => void;
11
- }
12
-
13
- interface AxiosRequestConfigLike {
14
- baseURL?: string;
15
- url?: string;
16
- method?: string;
17
- headers?: Record<string, string>;
18
- data?: unknown;
19
- params?: unknown;
20
- }
21
-
22
- interface AxiosResponseLike {
23
- status: number;
24
- statusText: string;
25
- headers?: Record<string, string>;
26
- data?: unknown;
27
- config: AxiosRequestConfigLike;
28
- }
29
-
30
- interface AxiosErrorLike {
31
- message: string;
32
- config?: AxiosRequestConfigLike;
33
- }
34
-
35
- interface AxiosInstanceLike {
36
- interceptors: {
37
- request: AxiosInterceptorManager;
38
- response: AxiosInterceptorManager;
39
- };
40
- }
41
-
42
- interface AxiosRegistration {
43
- refCount: number;
44
- requestId?: number;
45
- responseId?: number;
46
- }
47
-
48
- const DEFAULT_MAX_LOGS = 200;
49
- const networkChannel = createEventChannel<NetworkLogPayload>();
50
- const pendingAxiosRequests = new Map<string, { startTime: number; timestamp: number }>();
51
- const axiosRegistrations = new WeakMap<AxiosInstanceLike, AxiosRegistration>();
52
-
53
- let originalFetch: typeof globalThis.fetch | null = null;
54
- let fetchInterceptorRefCount = 0;
55
- let urlRewriter: ((url: string) => string) | null = null;
56
-
57
- export function setUrlRewriter(rewriter: ((url: string) => string) | null): void {
58
- urlRewriter = rewriter;
59
- }
60
-
61
- function rewriteUrl(url: string): string {
62
- if (!urlRewriter) return url;
63
- try {
64
- return urlRewriter(url);
65
- } catch {
66
- return url;
67
- }
68
- }
69
-
70
- function emitNetworkLog(entry: NetworkLogPayload): void {
71
- networkChannel.emit(entry);
72
- }
73
-
74
- function headersToObject(
75
- headers: Record<string, string> | Headers | undefined,
76
- ): Record<string, string> {
77
- const result: Record<string, string> = {};
78
-
79
- if (!headers) {
80
- return result;
81
- }
82
-
83
- if (typeof (headers as Headers).forEach === 'function') {
84
- (headers as Headers).forEach((value: string, key: string) => {
85
- result[key] = value;
86
- });
87
- return result;
88
- }
89
-
90
- Object.keys(headers).forEach((key) => {
91
- result[key] = (headers as Record<string, string>)[key]!;
92
- });
93
-
94
- return result;
95
- }
96
-
97
- function getRequestSnapshot(
98
- input: unknown,
99
- init?: RequestInit,
100
- ): NetworkLogEntry['request'] {
101
- const request = input instanceof Request ? input : null;
102
-
103
- return {
104
- url:
105
- typeof input === 'string'
106
- ? input
107
- : request?.url ?? String(input),
108
- method: (init?.method || request?.method || 'GET').toUpperCase(),
109
- headers: init?.headers
110
- ? headersToObject(init.headers as Record<string, string> | Headers)
111
- : request?.headers
112
- ? headersToObject(request.headers)
113
- : undefined,
114
- body: init?.body,
115
- };
116
- }
117
-
118
- async function parseResponseBody(response: Response): Promise<unknown> {
119
- const raw = await response.clone().text();
120
-
121
- if (!raw) {
122
- return undefined;
123
- }
124
-
125
- try {
126
- return JSON.parse(raw);
127
- } catch {
128
- return raw;
129
- }
130
- }
131
-
132
- function stopFetchInterceptor(): void {
133
- fetchInterceptorRefCount = Math.max(0, fetchInterceptorRefCount - 1);
134
-
135
- if (fetchInterceptorRefCount === 0 && originalFetch) {
136
- globalThis.fetch = originalFetch;
137
- originalFetch = null;
138
- }
139
- }
140
-
141
- function startFetchInterceptor(): () => void {
142
- fetchInterceptorRefCount += 1;
143
-
144
- if (originalFetch) {
145
- return () => {
146
- stopFetchInterceptor();
147
- };
148
- }
149
-
150
- originalFetch = globalThis.fetch;
151
-
152
- globalThis.fetch = async function interceptedFetch(
153
- input: Parameters<typeof fetch>[0],
154
- init?: Parameters<typeof fetch>[1],
155
- ) {
156
- const startTime = Date.now();
157
-
158
- let rewrittenInput: typeof input = input;
159
- if (urlRewriter) {
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
- emitNetworkLog({
176
- timestamp: startTime,
177
- duration,
178
- request,
179
- response: {
180
- status: response.status,
181
- statusText: response.statusText,
182
- data,
183
- },
184
- });
185
- } catch {
186
- emitNetworkLog({
187
- timestamp: startTime,
188
- duration,
189
- request,
190
- response: {
191
- status: response.status,
192
- statusText: response.statusText,
193
- },
194
- });
195
- }
196
-
197
- return response;
198
- } catch (error) {
199
- emitNetworkLog({
200
- timestamp: startTime,
201
- duration: Date.now() - startTime,
202
- request,
203
- error: error instanceof Error ? error.message : String(error),
204
- });
205
- throw error;
206
- }
207
- };
208
-
209
- return () => {
210
- stopFetchInterceptor();
211
- };
212
- }
213
-
214
- function teardownAxiosInterceptors(axiosInstance: AxiosInstanceLike): void {
215
- const registration = axiosRegistrations.get(axiosInstance);
216
-
217
- if (!registration) {
218
- return;
219
- }
220
-
221
- registration.refCount -= 1;
222
-
223
- if (registration.refCount > 0) {
224
- return;
225
- }
226
-
227
- if (typeof registration.requestId === 'number') {
228
- axiosInstance.interceptors.request.eject?.(registration.requestId);
229
- }
230
-
231
- if (typeof registration.responseId === 'number') {
232
- axiosInstance.interceptors.response.eject?.(registration.responseId);
233
- }
234
-
235
- axiosRegistrations.delete(axiosInstance);
236
- }
237
-
238
- function setupAxiosInterceptorsInternal(
239
- axiosInstance: AxiosInstanceLike,
240
- ): () => void {
241
- const existingRegistration = axiosRegistrations.get(axiosInstance);
242
-
243
- if (existingRegistration) {
244
- existingRegistration.refCount += 1;
245
- return () => {
246
- teardownAxiosInterceptors(axiosInstance);
247
- };
248
- }
249
-
250
- const requestId = axiosInstance.interceptors.request.use(
251
- (config: AxiosRequestConfigLike) => {
252
- // Rewrite URL if rewriter is set
253
- if (urlRewriter && config.url) {
254
- const fullUrl = config.baseURL
255
- ? `${config.baseURL}${config.url}`
256
- : config.url;
257
- const rewritten = rewriteUrl(fullUrl);
258
- if (rewritten !== fullUrl) {
259
- if (config.baseURL && rewritten.startsWith(config.baseURL)) {
260
- config.url = rewritten.slice(config.baseURL.length);
261
- } else {
262
- config.baseURL = '';
263
- config.url = rewritten;
264
- }
265
- }
266
- }
267
-
268
- if (!config.headers) {
269
- config.headers = {};
270
- }
271
-
272
- if (!config.headers['X-Request-Id']) {
273
- config.headers['X-Request-Id'] =
274
- Date.now().toString() + Math.random().toString(36).substring(2, 10);
275
- }
276
-
277
- const trackId = config.headers['X-Request-Id'] as string;
278
-
279
- pendingAxiosRequests.set(trackId, {
280
- startTime: Date.now(),
281
- timestamp: Date.now(),
282
- });
283
-
284
- return config;
285
- },
286
- (error: unknown) => Promise.reject(error),
287
- );
288
-
289
- const responseId = axiosInstance.interceptors.response.use(
290
- (response: AxiosResponseLike) => {
291
- const trackId = response.config?.headers?.['X-Request-Id'] as string;
292
- const pending = pendingAxiosRequests.get(trackId);
293
-
294
- if (pending) {
295
- emitNetworkLog({
296
- timestamp: pending.timestamp,
297
- duration: Date.now() - pending.startTime,
298
- request: {
299
- url: `${response.config.baseURL || ''}${response.config.url}`,
300
- method: (response.config.method || 'GET').toUpperCase(),
301
- headers: response.config.headers,
302
- body: response.config.data || response.config.params,
303
- },
304
- response: {
305
- status: response.status,
306
- statusText: response.statusText,
307
- headers: response.headers,
308
- data: response.data,
309
- success:
310
- response.data && typeof response.data === 'object'
311
- ? (response.data as Record<string, unknown>).success !== false
312
- : response.status >= 200 && response.status < 300,
313
- },
314
- });
315
-
316
- pendingAxiosRequests.delete(trackId);
317
- }
318
-
319
- return response;
320
- },
321
- (error: AxiosErrorLike) => {
322
- if (error.config) {
323
- const trackId = error.config.headers?.['X-Request-Id'] as string;
324
- const pending = pendingAxiosRequests.get(trackId);
325
-
326
- emitNetworkLog({
327
- timestamp: pending ? pending.timestamp : Date.now(),
328
- duration: pending ? Date.now() - pending.startTime : undefined,
329
- request: {
330
- url: `${error.config.baseURL || ''}${error.config.url}`,
331
- method: (error.config.method || 'GET').toUpperCase(),
332
- headers: error.config.headers,
333
- body: error.config.data || error.config.params,
334
- },
335
- error: error.message,
336
- });
337
-
338
- pendingAxiosRequests.delete(trackId);
339
- }
340
-
341
- return Promise.reject(error);
342
- },
343
- );
344
-
345
- axiosRegistrations.set(axiosInstance, {
346
- refCount: 1,
347
- requestId: typeof requestId === 'number' ? requestId : undefined,
348
- responseId: typeof responseId === 'number' ? responseId : undefined,
349
- });
350
-
351
- return () => {
352
- teardownAxiosInterceptors(axiosInstance);
353
- };
354
- }
16
+ // ─── Utilities ────────────────────────────────────────
355
17
 
356
18
  function isUrlBlacklisted(
357
19
  url: string,
@@ -360,20 +22,25 @@ function isUrlBlacklisted(
360
22
  if (!url) {
361
23
  return false;
362
24
  }
363
-
364
25
  return blacklist.some((pattern) =>
365
26
  pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern),
366
27
  );
367
28
  }
368
29
 
369
- export interface NetworkFeatureAPI extends DebugFeature<NetworkLogEntry> {
370
- setupAxiosInterceptors: (axiosInstance: AxiosInstanceLike) => () => void;
371
- addUrlToBlacklist: (pattern: string | RegExp) => void;
372
- removeUrlFromBlacklist: (pattern: string | RegExp) => void;
373
- clearBlacklist: () => void;
374
- getBlacklist: () => Array<string | RegExp>;
30
+ // ─── Channel (shared pub-sub backbone) ─────────────────
31
+
32
+ let networkChannel = createEventChannel<NetworkLogPayload>();
33
+
34
+ function emitNetworkLog(entry: NetworkLogPayload): void {
35
+ networkChannel.emit(entry);
375
36
  }
376
37
 
38
+ // ─── Feature factory ──────────────────────────────────
39
+
40
+ const DEFAULT_MAX_LOGS = 200;
41
+
42
+ export type { AxiosInstanceLike };
43
+
377
44
  export interface NetworkFeatureConfig {
378
45
  /** Maximum number of network logs to keep (default: 200) */
379
46
  maxLogs?: number;
@@ -381,7 +48,9 @@ export interface NetworkFeatureConfig {
381
48
  blacklist?: Array<string | RegExp>;
382
49
  }
383
50
 
384
- export const createNetworkFeature = (config?: NetworkFeatureConfig): NetworkFeatureAPI => {
51
+ export const createNetworkFeature = (config?: NetworkFeatureConfig): DebugFeature<NetworkLogEntry> & {
52
+ setupAxiosInterceptors: (axiosInstance: AxiosInstanceLike) => () => void;
53
+ } => {
385
54
  const maxLogs = config?.maxLogs ?? DEFAULT_MAX_LOGS;
386
55
  const blacklist: Array<string | RegExp> = config?.blacklist ? [...config.blacklist] : [];
387
56
  const logStore = createObservableStore<NetworkLogEntry>();
@@ -390,25 +59,14 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): NetworkFeat
390
59
  let nextId = 0;
391
60
  let initialized = false;
392
61
  let unsubscribeLogs: (() => void) | null = null;
393
- let stopFetch: (() => void) | null = null;
62
+ let stopFetchFn: (() => void) | null = null;
63
+ let stopXMLHttpRequestFn: (() => void) | null = null;
394
64
 
395
65
  const handleLog = (entry: NetworkLogPayload) => {
396
66
  if (isUrlBlacklisted(entry.request.url, blacklist)) {
397
67
  return;
398
68
  }
399
-
400
- logStore.push(
401
- {
402
- ...entry,
403
- id: String(nextId++),
404
- },
405
- maxLogs,
406
- );
407
- };
408
-
409
- const cleanupAxiosRegistrations = () => {
410
- axiosCleanupMap.forEach((cleanup) => cleanup());
411
- axiosCleanupMap.clear();
69
+ logStore.push({ ...entry, id: String(nextId++) }, maxLogs);
412
70
  };
413
71
 
414
72
  return {
@@ -419,9 +77,9 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): NetworkFeat
419
77
  if (initialized) {
420
78
  return;
421
79
  }
422
-
423
80
  unsubscribeLogs = networkChannel.subscribe(handleLog);
424
- stopFetch = startFetchInterceptor();
81
+ stopFetchFn = startFetch(emitNetworkLog);
82
+ stopXMLHttpRequestFn = startXMLHttpRequest(emitNetworkLog);
425
83
  initialized = true;
426
84
  },
427
85
  getData: () => logStore.getData(),
@@ -432,57 +90,37 @@ export const createNetworkFeature = (config?: NetworkFeatureConfig): NetworkFeat
432
90
  if (!initialized) {
433
91
  return;
434
92
  }
435
-
436
- setUrlRewriter(null);
93
+ urlRewriter.set(null);
437
94
  unsubscribeLogs?.();
438
95
  unsubscribeLogs = null;
439
- stopFetch?.();
440
- stopFetch = null;
441
- cleanupAxiosRegistrations();
96
+ stopFetchFn?.();
97
+ stopFetchFn = null;
98
+ stopXMLHttpRequestFn?.();
99
+ stopXMLHttpRequestFn = null;
100
+ axiosCleanupMap.forEach((c) => c());
101
+ axiosCleanupMap.clear();
442
102
  logStore.clear();
443
103
  initialized = false;
444
104
  },
445
105
  subscribe: (listener) => logStore.subscribe(listener),
446
106
  setupAxiosInterceptors: (axiosInstance) => {
447
107
  const existingCleanup = axiosCleanupMap.get(axiosInstance);
448
-
449
108
  if (existingCleanup) {
450
109
  return existingCleanup;
451
110
  }
452
-
453
- const cleanup = setupAxiosInterceptorsInternal(axiosInstance);
111
+ const cleanup = setupAxios(axiosInstance, emitNetworkLog);
454
112
  const trackedCleanup = () => {
455
113
  cleanup();
456
114
  axiosCleanupMap.delete(axiosInstance);
457
115
  };
458
-
459
116
  axiosCleanupMap.set(axiosInstance, trackedCleanup);
460
117
  return trackedCleanup;
461
118
  },
462
- addUrlToBlacklist: (pattern) => {
463
- const alreadyExists = blacklist.some((item) =>
464
- item instanceof RegExp && pattern instanceof RegExp
465
- ? item.toString() === pattern.toString()
466
- : item === pattern,
467
- );
468
-
469
- if (!alreadyExists) {
470
- blacklist.push(pattern);
471
- }
472
- },
473
- removeUrlFromBlacklist: (pattern) => {
474
- const nextBlacklist = blacklist.filter((item) =>
475
- item instanceof RegExp && pattern instanceof RegExp
476
- ? item.toString() !== pattern.toString()
477
- : item !== pattern,
478
- );
479
-
480
- blacklist.length = 0;
481
- blacklist.push(...nextBlacklist);
482
- },
483
- clearBlacklist: () => {
484
- blacklist.length = 0;
485
- },
486
- getBlacklist: () => [...blacklist],
487
119
  };
488
120
  };
121
+
122
+ /** Reset module-level state for testing */
123
+ export function _resetNetworkForTesting(): void {
124
+ networkChannel = createEventChannel<NetworkLogPayload>();
125
+ resetInterceptors();
126
+ }
@@ -1,25 +1,19 @@
1
1
  import { TrackLogTab } from '../components/TrackLogTab';
2
2
  import type { DebugFeature, TrackLogEntry } from '../types';
3
3
  import { createEventChannel } from '../utils/createEventChannel';
4
- import { createObservableStore } from '../utils/createObservableStore';
4
+ import { createChannelFeature } from '../utils/createChannelFeature';
5
5
 
6
6
  export interface TrackEventData {
7
7
  eventName: string;
8
8
  [key: string]: unknown;
9
9
  }
10
10
 
11
- type TrackLogPayload = TrackEventData & {
12
- timestamp: number;
13
- };
11
+ type TrackLogPayload = TrackEventData & { timestamp: number };
14
12
 
15
- const DEFAULT_MAX_LOGS = 200;
16
- const trackChannel = createEventChannel<TrackLogPayload>();
13
+ let trackChannel = createEventChannel<TrackLogPayload>();
17
14
 
18
15
  export const addTrackLog = (eventData: TrackEventData): void => {
19
- trackChannel.emit({
20
- timestamp: Date.now(),
21
- ...eventData,
22
- });
16
+ trackChannel.emit({ timestamp: Date.now(), ...eventData });
23
17
  };
24
18
 
25
19
  export interface TrackFeatureConfig {
@@ -27,43 +21,14 @@ export interface TrackFeatureConfig {
27
21
  maxLogs?: number;
28
22
  }
29
23
 
30
- export const createTrackFeature = (config?: TrackFeatureConfig): DebugFeature<TrackLogEntry> => {
31
- const maxLogs = config?.maxLogs ?? DEFAULT_MAX_LOGS;
32
- const logStore = createObservableStore<TrackLogEntry>();
33
- let nextId = 0;
34
- let unsubscribeLogs: (() => void) | null = null;
35
- let initialized = false;
36
-
37
- return {
38
- name: 'track',
39
- label: 'Track',
40
- renderContent: TrackLogTab,
41
- setup: () => {
42
- if (initialized) {
43
- return;
44
- }
24
+ export const createTrackFeature = (config?: TrackFeatureConfig): DebugFeature<TrackLogEntry> =>
25
+ createChannelFeature(
26
+ () => trackChannel,
27
+ (payload, id) => ({ ...payload, id }),
28
+ { name: 'track', label: 'Track', renderContent: TrackLogTab, maxLogs: config?.maxLogs },
29
+ );
45
30
 
46
- unsubscribeLogs = trackChannel.subscribe((entry) => {
47
- logStore.push(
48
- {
49
- ...entry,
50
- id: String(nextId++),
51
- },
52
- maxLogs,
53
- );
54
- });
55
- initialized = true;
56
- },
57
- getData: () => logStore.getData(),
58
- clear: () => {
59
- logStore.clear();
60
- },
61
- cleanup: () => {
62
- unsubscribeLogs?.();
63
- unsubscribeLogs = null;
64
- logStore.clear();
65
- initialized = false;
66
- },
67
- subscribe: (listener) => logStore.subscribe(listener),
68
- };
69
- };
31
+ /** Reset module-level state for testing */
32
+ export function _resetTrackForTesting(): void {
33
+ trackChannel = createEventChannel<TrackLogPayload>();
34
+ }
@@ -1,12 +1,11 @@
1
1
  import { ZustandLogTab } from '../components/ZustandLogTab';
2
2
  import type { DebugFeature, ZustandLogEntry } from '../types';
3
3
  import { createEventChannel } from '../utils/createEventChannel';
4
- import { createObservableStore } from '../utils/createObservableStore';
4
+ import { createChannelFeature } from '../utils/createChannelFeature';
5
5
 
6
6
  type ZustandLogPayload = Omit<ZustandLogEntry, 'id'>;
7
7
 
8
- const DEFAULT_MAX_LOGS = 200;
9
- const zustandChannel = createEventChannel<ZustandLogPayload>();
8
+ let zustandChannel = createEventChannel<ZustandLogPayload>();
10
9
 
11
10
  export const addZustandLog = (
12
11
  action: string,
@@ -25,6 +24,8 @@ export const addZustandLog = (
25
24
  });
26
25
  };
27
26
 
27
+ // ─── Zustand middleware (remains here — it's user-facing API) ──────────
28
+
28
29
  type ZustandSetState<T> = (
29
30
  partial: T | Partial<T> | ((state: T) => Partial<T> | T),
30
31
  replace?: boolean | undefined,
@@ -80,48 +81,21 @@ export const zustandLogMiddleware = <T>(config: ZustandConfig<T>) => (
80
81
  api,
81
82
  );
82
83
 
84
+ // ─── Feature factory ──────────────────────────────────────────────────
85
+
83
86
  export interface ZustandFeatureConfig {
84
87
  /** Maximum number of zustand state changes to keep (default: 200) */
85
88
  maxLogs?: number;
86
89
  }
87
90
 
88
- export const createZustandLogFeature = (config?: ZustandFeatureConfig): DebugFeature<ZustandLogEntry> => {
89
- const maxLogs = config?.maxLogs ?? DEFAULT_MAX_LOGS;
90
- const logStore = createObservableStore<ZustandLogEntry>();
91
- let nextId = 0;
92
- let unsubscribeLogs: (() => void) | null = null;
93
- let initialized = false;
94
-
95
- return {
96
- name: 'zustand',
97
- label: 'Zustand',
98
- renderContent: ZustandLogTab,
99
- setup: () => {
100
- if (initialized) {
101
- return;
102
- }
91
+ export const createZustandLogFeature = (config?: ZustandFeatureConfig): DebugFeature<ZustandLogEntry> =>
92
+ createChannelFeature(
93
+ () => zustandChannel,
94
+ (payload, id) => ({ ...payload, id }),
95
+ { name: 'zustand', label: 'Zustand', renderContent: ZustandLogTab, maxLogs: config?.maxLogs },
96
+ );
103
97
 
104
- unsubscribeLogs = zustandChannel.subscribe((entry) => {
105
- logStore.push(
106
- {
107
- ...entry,
108
- id: String(nextId++),
109
- },
110
- maxLogs,
111
- );
112
- });
113
- initialized = true;
114
- },
115
- getData: () => logStore.getData(),
116
- clear: () => {
117
- logStore.clear();
118
- },
119
- cleanup: () => {
120
- unsubscribeLogs?.();
121
- unsubscribeLogs = null;
122
- logStore.clear();
123
- initialized = false;
124
- },
125
- subscribe: (listener) => logStore.subscribe(listener),
126
- };
127
- };
98
+ /** Reset module-level state for testing */
99
+ export function _resetZustandForTesting(): void {
100
+ zustandChannel = createEventChannel<ZustandLogPayload>();
101
+ }
@@ -1,12 +1,7 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
  import { addNavigationLog } from '../features/NavigationLogFeature';
3
3
  import { safeStringify } from '../utils/safeStringify';
4
-
5
- interface NavigationContainerRef {
6
- getCurrentRoute?: () => { name?: string } | undefined;
7
- getRootState?: () => unknown;
8
- addListener: (event: string, callback: () => void) => () => void;
9
- }
4
+ import type { NavigationContainerRef } from '../types';
10
5
 
11
6
  function getActiveRouteName(state: unknown): string {
12
7
  let currentState = state as