@buoy-gg/network 7.0.15 → 7.0.17

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 (107) hide show
  1. package/lib/commonjs/index.web.js +395 -0
  2. package/lib/commonjs/network/components/DataViewer.web.js +879 -0
  3. package/lib/commonjs/network/components/NetworkCopyScreen.web.js +48 -0
  4. package/lib/commonjs/network/components/NetworkCopySettingsView.web.js +1242 -0
  5. package/lib/commonjs/network/components/NetworkDetailScreen.web.js +101 -0
  6. package/lib/commonjs/network/components/NetworkDetailStepper.web.js +296 -0
  7. package/lib/commonjs/network/components/NetworkEventDetailView.web.js +1498 -0
  8. package/lib/commonjs/network/components/NetworkEventItemCompact.web.js +802 -0
  9. package/lib/commonjs/network/components/NetworkFilterScreen.web.js +55 -0
  10. package/lib/commonjs/network/components/NetworkFilterViewV3.web.js +1310 -0
  11. package/lib/commonjs/network/components/NetworkHeaderLive.web.js +604 -0
  12. package/lib/commonjs/network/components/NetworkListScreen.js +2 -1
  13. package/lib/commonjs/network/components/NetworkListScreen.web.js +607 -0
  14. package/lib/commonjs/network/components/NetworkModal.js +12 -3
  15. package/lib/commonjs/network/components/NetworkNumberPrompt.web.js +232 -0
  16. package/lib/commonjs/network/components/NetworkOverrideEditor.web.js +942 -0
  17. package/lib/commonjs/network/components/NetworkOverrideHeaderButton.web.js +133 -0
  18. package/lib/commonjs/network/components/NetworkOverridesButton.web.js +143 -0
  19. package/lib/commonjs/network/components/NetworkOverridesScreen.js +160 -126
  20. package/lib/commonjs/network/components/NetworkOverridesScreen.web.js +830 -0
  21. package/lib/commonjs/network/components/NetworkSavedScreen.web.js +459 -0
  22. package/lib/commonjs/network/components/response-editor/Explorer.web.js +1136 -0
  23. package/lib/commonjs/network/hooks/useNetworkEventList.js +12 -2
  24. package/lib/commonjs/network/overrides/applyToXhr.js +276 -15
  25. package/lib/commonjs/network/utils/generateNetworkCopyText.js +32 -5
  26. package/lib/commonjs/network/utils/networkEventStore.js +12 -4
  27. package/lib/commonjs/network/utils/networkListener.js +1 -16
  28. package/lib/module/index.web.js +81 -0
  29. package/lib/module/network/components/DataViewer.web.js +875 -0
  30. package/lib/module/network/components/NetworkCopyScreen.web.js +44 -0
  31. package/lib/module/network/components/NetworkCopySettingsView.web.js +1237 -0
  32. package/lib/module/network/components/NetworkDetailScreen.web.js +97 -0
  33. package/lib/module/network/components/NetworkDetailStepper.web.js +294 -0
  34. package/lib/module/network/components/NetworkEventDetailView.web.js +1496 -0
  35. package/lib/module/network/components/NetworkEventItemCompact.web.js +800 -0
  36. package/lib/module/network/components/NetworkFilterScreen.web.js +51 -0
  37. package/lib/module/network/components/NetworkFilterViewV3.web.js +1306 -0
  38. package/lib/module/network/components/NetworkHeaderLive.web.js +598 -0
  39. package/lib/module/network/components/NetworkListScreen.js +2 -1
  40. package/lib/module/network/components/NetworkListScreen.web.js +605 -0
  41. package/lib/module/network/components/NetworkModal.js +12 -3
  42. package/lib/module/network/components/NetworkNumberPrompt.web.js +230 -0
  43. package/lib/module/network/components/NetworkOverrideEditor.web.js +939 -0
  44. package/lib/module/network/components/NetworkOverrideHeaderButton.web.js +131 -0
  45. package/lib/module/network/components/NetworkOverridesButton.web.js +141 -0
  46. package/lib/module/network/components/NetworkOverridesScreen.js +161 -127
  47. package/lib/module/network/components/NetworkOverridesScreen.web.js +826 -0
  48. package/lib/module/network/components/NetworkSavedScreen.web.js +457 -0
  49. package/lib/module/network/components/response-editor/Explorer.web.js +1133 -0
  50. package/lib/module/network/hooks/useNetworkEventList.js +12 -2
  51. package/lib/module/network/overrides/applyToXhr.js +275 -15
  52. package/lib/module/network/utils/generateNetworkCopyText.js +28 -1
  53. package/lib/module/network/utils/networkEventStore.js +12 -4
  54. package/lib/module/network/utils/networkListener.js +1 -16
  55. package/lib/typescript/index.web.d.ts +56 -0
  56. package/lib/typescript/index.web.d.ts.map +1 -0
  57. package/lib/typescript/network/components/DataViewer.web.d.ts +78 -0
  58. package/lib/typescript/network/components/DataViewer.web.d.ts.map +1 -0
  59. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts +27 -0
  60. package/lib/typescript/network/components/NetworkCopyScreen.web.d.ts.map +1 -0
  61. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts +68 -0
  62. package/lib/typescript/network/components/NetworkCopySettingsView.web.d.ts.map +1 -0
  63. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts +45 -0
  64. package/lib/typescript/network/components/NetworkDetailScreen.web.d.ts.map +1 -0
  65. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts +40 -0
  66. package/lib/typescript/network/components/NetworkDetailStepper.web.d.ts.map +1 -0
  67. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts +67 -0
  68. package/lib/typescript/network/components/NetworkEventDetailView.web.d.ts.map +1 -0
  69. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts +58 -0
  70. package/lib/typescript/network/components/NetworkEventItemCompact.web.d.ts.map +1 -0
  71. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts +28 -0
  72. package/lib/typescript/network/components/NetworkFilterScreen.web.d.ts.map +1 -0
  73. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts +74 -0
  74. package/lib/typescript/network/components/NetworkFilterViewV3.web.d.ts.map +1 -0
  75. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts +112 -0
  76. package/lib/typescript/network/components/NetworkHeaderLive.web.d.ts.map +1 -0
  77. package/lib/typescript/network/components/NetworkListScreen.d.ts.map +1 -1
  78. package/lib/typescript/network/components/NetworkListScreen.web.d.ts +63 -0
  79. package/lib/typescript/network/components/NetworkListScreen.web.d.ts.map +1 -0
  80. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  81. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts +38 -0
  82. package/lib/typescript/network/components/NetworkNumberPrompt.web.d.ts.map +1 -0
  83. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts +43 -0
  84. package/lib/typescript/network/components/NetworkOverrideEditor.web.d.ts.map +1 -0
  85. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts +32 -0
  86. package/lib/typescript/network/components/NetworkOverrideHeaderButton.web.d.ts.map +1 -0
  87. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts +26 -0
  88. package/lib/typescript/network/components/NetworkOverridesButton.web.d.ts.map +1 -0
  89. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts +9 -0
  90. package/lib/typescript/network/components/NetworkOverridesScreen.d.ts.map +1 -1
  91. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts +69 -0
  92. package/lib/typescript/network/components/NetworkOverridesScreen.web.d.ts.map +1 -0
  93. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts +67 -0
  94. package/lib/typescript/network/components/NetworkSavedScreen.web.d.ts.map +1 -0
  95. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts +56 -0
  96. package/lib/typescript/network/components/response-editor/Explorer.web.d.ts.map +1 -0
  97. package/lib/typescript/network/hooks/useNetworkEventList.d.ts +10 -0
  98. package/lib/typescript/network/hooks/useNetworkEventList.d.ts.map +1 -1
  99. package/lib/typescript/network/overrides/__tests__/applyToXhr.test.d.ts.map +1 -1
  100. package/lib/typescript/network/overrides/applyToXhr.d.ts +40 -14
  101. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  102. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts +34 -1
  103. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  104. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  105. package/lib/typescript/network/utils/networkListener.d.ts +0 -1
  106. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  107. package/package.json +2 -2
@@ -31,11 +31,21 @@ function useNetworkEventList(options) {
31
31
  filter,
32
32
  ignoredPatterns,
33
33
  paused,
34
+ visible = true,
34
35
  isPro
35
36
  } = options;
36
37
  const [events, setEvents] = (0, _react.useState)(() => _networkEventStore.networkEventStore.getEvents());
37
38
  (0, _react.useEffect)(() => {
38
- if (paused) return undefined;
39
+ // Hidden: neither subscribe nor sync — a hidden screen re-rendering for
40
+ // stream traffic is pure waste (the stable-router contract).
41
+ if (!visible) return undefined;
42
+ if (paused) {
43
+ // Pause freezes the STREAM, not the data. The store still holds the
44
+ // frozen events, and a screen entering visibility during a pause must
45
+ // show them rather than whatever it had at mount.
46
+ setEvents(_networkEventStore.networkEventStore.getEvents());
47
+ return undefined;
48
+ }
39
49
  // Coalesce store emissions to one commit per frame: a burst of N
40
50
  // concurrent requests emits 2N times (add + response each), and
41
51
  // uncoalesced each emission is its own commit re-rendering the
@@ -58,7 +68,7 @@ function useNetworkEventList(options) {
58
68
  unsubscribe();
59
69
  if (rafId != null) cancelAnimationFrame(rafId);
60
70
  };
61
- }, [paused]);
71
+ }, [paused, visible]);
62
72
  (0, _react.useEffect)(() => _networkEventStore.networkEventStore.onClear(() => {
63
73
  setEvents([]);
64
74
  }), []);
@@ -4,11 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.canSynthesize = canSynthesize;
7
+ exports.canSynthesizeStandard = canSynthesizeStandard;
7
8
  exports.runOverride = runOverride;
8
9
  /**
9
- * Perform an override outcome against a React Native XMLHttpRequest.
10
+ * Perform an override outcome against an XMLHttpRequest — React Native's OR a
11
+ * standards (browser/webview) one.
10
12
  *
11
- * WHY THIS IS POSSIBLE AT ALL: React Native's XMLHttpRequest
13
+ * WHY THIS IS POSSIBLE ON RN: React Native's XMLHttpRequest
12
14
  * (`react-native/Libraries/Network/XMLHttpRequest.js`) is pure JavaScript.
13
15
  * `readyState`, `status`, `responseHeaders` and `responseURL` are plain
14
16
  * writable fields, and `setResponseHeaders()` / `setReadyState()` are real
@@ -17,16 +19,33 @@ exports.runOverride = runOverride;
17
19
  * `_hasError` → error, otherwise load; always loadend). So a response can be
18
20
  * synthesized end to end without touching native code.
19
21
  *
20
- * WHY XHR IS THE ONLY PLACE WE HOOK: `react-native/Libraries/Network/fetch.js`
21
- * is `require('whatwg-fetch')` — RN's `fetch` IS the polyfill over
22
- * XMLHttpRequest. Overriding here therefore covers `fetch`, `axios` and raw
23
- * XHR through one code path, and the fake response flows back up through the
24
- * polyfill so the tool records it without any extra plumbing.
22
+ * WHY XHR IS THE ONLY PLACE WE HOOK ON RN:
23
+ * `react-native/Libraries/Network/fetch.js` is `require('whatwg-fetch')` —
24
+ * RN's `fetch` IS the polyfill over XMLHttpRequest. Overriding here therefore
25
+ * covers `fetch`, `axios` and raw XHR through one code path, and the fake
26
+ * response flows back up through the polyfill so the tool records it without
27
+ * any extra plumbing.
25
28
  *
26
29
  * Verified field-for-field on React Native 0.81.4 and 0.85.3.
27
30
  *
28
- * Four non-obvious behaviours are handled below; each one is a real bug if you
29
- * skip it, and each is commented at the line that fixes it.
31
+ * WHY THERE IS A SECOND PATH: on a STANDARDS XMLHttpRequest (a webview port —
32
+ * Tauri/Capacitor — or react-native-web) none of the RN internals exist, and
33
+ * `status` / `readyState` / `response` are readonly ACCESSORS on
34
+ * `XMLHttpRequest.prototype`, so plain assignment silently no-ops (or throws
35
+ * in strict mode). This file used to feature-detect that and give up —
36
+ * "degraded, but never broken" — which was right for react-native-web as a
37
+ * by-product of an RN app, and wrong the moment the web ports made the
38
+ * browser XHR a first-class target: the row got FLAGGED as overridden (the
39
+ * rule matched) while the real request went out. The fix is
40
+ * `runStandardOverride` below: `Object.defineProperty` INSTANCE own-properties
41
+ * shadow the prototype accessors (legal on any object), and the lifecycle
42
+ * events are dispatched by hand through the XHR's own EventTarget — which
43
+ * fires `addEventListener` listeners AND the `onload`/`onloadend`/
44
+ * `onreadystatechange` handler attributes, exactly what axios's XHR adapter
45
+ * listens on.
46
+ *
47
+ * Several non-obvious behaviours are handled below; each one is a real bug if
48
+ * you skip it, and each is commented at the line that fixes it.
30
49
  */
31
50
 
32
51
  const HEADERS_RECEIVED = 2;
@@ -45,22 +64,33 @@ const internalsOf = xhr => xhr;
45
64
  let warnedUnsupported = false;
46
65
 
47
66
  /**
48
- * Can this XMLHttpRequest be driven by hand?
67
+ * Can this XMLHttpRequest be driven through React Native's internals?
49
68
  *
50
- * The whole technique rests on RN internals, so it is feature-detected rather
51
- * than assumed: on a browser XMLHttpRequest (react-native-web) these methods
52
- * don't exist and `status` is a read-only getter. There, overrides no-op and
53
- * the request goes out for real — degraded, but never broken.
69
+ * The RN technique rests on those internals, so it is feature-detected rather
70
+ * than assumed. A request that fails this check is NOT automatically a lost
71
+ * cause — `canSynthesizeStandard` covers the standards XHR next.
54
72
  */
55
73
  function canSynthesize(xhr) {
56
74
  const internals = internalsOf(xhr);
57
75
  return typeof internals.setReadyState === "function" && typeof internals.setResponseHeaders === "function";
58
76
  }
77
+
78
+ /**
79
+ * Can this XMLHttpRequest be driven as a STANDARDS one?
80
+ *
81
+ * `dispatchEvent` is the load-bearing requirement: the whole standard path
82
+ * works by defining instance own-properties and then dispatching the
83
+ * lifecycle events through the XHR's own EventTarget. `removeEventListener`
84
+ * rules out half-mocks that could take the branch and then leak listeners.
85
+ */
86
+ function canSynthesizeStandard(xhr) {
87
+ return typeof xhr.addEventListener === "function" && typeof xhr.removeEventListener === "function" && typeof xhr.dispatchEvent === "function";
88
+ }
59
89
  function warnUnsupported() {
60
90
  if (warnedUnsupported) return;
61
91
  warnedUnsupported = true;
62
92
  if (typeof __DEV__ !== "undefined" && __DEV__) {
63
- console.warn("[Buoy] Network overrides need React Native's XMLHttpRequest and this " + "environment provides a different one — requests will run normally.");
93
+ console.warn("[Buoy] Network overrides need an XMLHttpRequest they can drive (React " + "Native's, or a standards EventTarget one) and this environment " + "provides neither — requests will run normally.");
64
94
  }
65
95
  }
66
96
 
@@ -120,6 +150,230 @@ function fail(xhr, outcome) {
120
150
  internals.setReadyState?.(DONE);
121
151
  }
122
152
 
153
+ // ============================================================================
154
+ // STANDARDS XMLHttpRequest path (webview ports / react-native-web)
155
+ // ============================================================================
156
+
157
+ /**
158
+ * Shadow a prototype accessor with an instance own-property.
159
+ *
160
+ * This is the entire trick: `xhr.status = 500` silently no-ops (or throws in
161
+ * strict mode) against the readonly getter on `XMLHttpRequest.prototype`, but
162
+ * an own DATA property on the instance is legal and takes precedence in every
163
+ * lookup. `configurable: true` so `readyState` can be redefined per
164
+ * transition. Works identically against RN's plain class fields, so nothing
165
+ * here is browser-only — the RN path just has a richer mechanism available.
166
+ */
167
+ function defineOwn(xhr, name, value) {
168
+ Object.defineProperty(xhr, name, {
169
+ value,
170
+ configurable: true,
171
+ enumerable: true,
172
+ writable: false
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Build a lifecycle event to dispatch by hand.
178
+ *
179
+ * `ProgressEvent` where the spec uses one (load/loadend/error/timeout/abort)
180
+ * and the constructor exists; plain `Event` otherwise (Node test runs have no
181
+ * ProgressEvent); a `{ type }` literal as the last resort for event-target
182
+ * shims. Dispatching through the XHR's own EventTarget is what makes the
183
+ * `onload`/`onloadend`/`onreadystatechange` handler ATTRIBUTES fire too —
184
+ * they're listeners in the same list, which is exactly what axios's adapter
185
+ * registers.
186
+ */
187
+ function makeEvent(type) {
188
+ if (type !== "readystatechange" && typeof ProgressEvent === "function") {
189
+ try {
190
+ return new ProgressEvent(type);
191
+ } catch {
192
+ // fall through
193
+ }
194
+ }
195
+ if (typeof Event === "function") {
196
+ try {
197
+ return new Event(type);
198
+ } catch {
199
+ // fall through
200
+ }
201
+ }
202
+ return {
203
+ type
204
+ };
205
+ }
206
+ function fireEvent(xhr, type) {
207
+ xhr.dispatchEvent(makeEvent(type));
208
+ }
209
+
210
+ /** Move the shadowed `readyState` and announce it — the interceptor's own
211
+ * listener stamps TTFB off the HEADERS_RECEIVED notification, same as a real
212
+ * response. */
213
+ function setStandardReadyState(xhr, state) {
214
+ defineOwn(xhr, "readyState", state);
215
+ fireEvent(xhr, "readystatechange");
216
+ }
217
+
218
+ /**
219
+ * `xhr.response` in the caller's requested shape.
220
+ *
221
+ * A standards XHR types `response` off `responseType`; axios sets it when the
222
+ * caller asked for one (default '' → text). JSON is parsed (spec: null on
223
+ * parse failure), arraybuffer/blob are best-effort encodings of the rule's
224
+ * text body, everything else falls back to the string.
225
+ */
226
+ function standardResponseFor(xhr, outcome) {
227
+ let responseType = "";
228
+ try {
229
+ responseType = xhr.responseType;
230
+ } catch {
231
+ // Some shims throw on access before send — treat as default text.
232
+ }
233
+ switch (responseType) {
234
+ case "":
235
+ case "text":
236
+ return outcome.body;
237
+ case "json":
238
+ try {
239
+ return JSON.parse(outcome.body);
240
+ } catch {
241
+ return null;
242
+ }
243
+ case "arraybuffer":
244
+ if (typeof TextEncoder === "function") {
245
+ return new TextEncoder().encode(outcome.body).buffer;
246
+ }
247
+ return outcome.body;
248
+ case "blob":
249
+ if (typeof Blob === "function") {
250
+ const contentType = Object.entries(outcome.headers).find(([name]) => name.toLowerCase() === "content-type")?.[1] ?? "";
251
+ return new Blob([outcome.body], {
252
+ type: contentType
253
+ });
254
+ }
255
+ return outcome.body;
256
+ default:
257
+ return outcome.body;
258
+ }
259
+ }
260
+
261
+ /** Synthesize a successful response on a standards XHR. */
262
+ function respondStandard(xhr, url, outcome, isAborted) {
263
+ const headers = outcome.headers;
264
+ // Spec shape: lowercase names, `name: value\r\n` per pair — axios splits on
265
+ // newlines and trims, raw XHR users get what a real response reads like.
266
+ const allHeaders = Object.entries(headers).map(([name, value]) => `${name.toLowerCase()}: ${value}\r\n`).join("");
267
+ defineOwn(xhr, "getAllResponseHeaders", () => allHeaders);
268
+ defineOwn(xhr, "getResponseHeader", name => {
269
+ const wanted = String(name).toLowerCase();
270
+ for (const [key, value] of Object.entries(headers)) {
271
+ if (key.toLowerCase() === wanted) return value;
272
+ }
273
+ return null;
274
+ });
275
+ defineOwn(xhr, "responseURL", url);
276
+ defineOwn(xhr, "status", outcome.status);
277
+ defineOwn(xhr, "statusText", outcome.statusText);
278
+ defineOwn(xhr, "response", standardResponseFor(xhr, outcome));
279
+ // Own property, so it answers with the body even under a non-text
280
+ // `responseType` (where the real accessor would throw). axios reads it
281
+ // whenever `responseType` is '' or 'text'.
282
+ defineOwn(xhr, "responseText", outcome.body);
283
+ setStandardReadyState(xhr, HEADERS_RECEIVED);
284
+ if (isAborted()) return;
285
+ setStandardReadyState(xhr, LOADING);
286
+ if (isAborted()) return;
287
+ setStandardReadyState(xhr, DONE);
288
+ fireEvent(xhr, "load");
289
+ fireEvent(xhr, "loadend");
290
+ }
291
+
292
+ /** Synthesize a transport failure on a standards XHR — offline or timeout. */
293
+ function failStandard(xhr, outcome) {
294
+ // `status` stays 0 (the prototype getter's answer for a request whose real
295
+ // send never ran) — exactly what a genuine network error reports. The
296
+ // 'error'/'timeout' event is the entire signal; axios turns them into
297
+ // ERR_NETWORK / ECONNABORTED the same way whatwg-fetch turns RN's flags
298
+ // into TypeErrors.
299
+ setStandardReadyState(xhr, DONE);
300
+ fireEvent(xhr, outcome.failKind === "timeout" ? "timeout" : "error");
301
+ fireEvent(xhr, "loadend");
302
+ }
303
+
304
+ /**
305
+ * Apply an outcome to a standards XMLHttpRequest that has NOT been sent.
306
+ *
307
+ * The real `send()` is never invoked for respond/fail — the request must not
308
+ * touch the network. That leaves `abort()` with nothing to do natively (per
309
+ * spec, abort on a request whose send flag is unset dispatches NOTHING), so
310
+ * an instance shadow gives it the in-flight behaviour the app expects:
311
+ * cancel the pending outcome and dispatch abort + loadend. The shadow deletes
312
+ * itself the moment it can't matter anymore — after settling, and before a
313
+ * `delay` pass-through hands the request to the real network, where the
314
+ * prototype `abort()` must be reachable again.
315
+ */
316
+ function runStandardOverride(xhr, url, outcome, passThrough) {
317
+ let timer = null;
318
+ let aborted = false;
319
+ let settled = false;
320
+ const restoreAbort = () => {
321
+ try {
322
+ delete xhr.abort;
323
+ } catch {
324
+ // Non-configurable shadow can't happen (we define it configurable), but
325
+ // never let cleanup throw into app code.
326
+ }
327
+ };
328
+ Object.defineProperty(xhr, "abort", {
329
+ configurable: true,
330
+ writable: true,
331
+ value: () => {
332
+ if (settled || aborted) return;
333
+ aborted = true;
334
+ settled = true;
335
+ if (timer !== null) {
336
+ clearTimeout(timer);
337
+ timer = null;
338
+ }
339
+ restoreAbort();
340
+ // The in-flight abort sequence: DONE, then 'abort', then 'loadend' —
341
+ // whatwg's request-error steps, which is also what RN's setReadyState
342
+ // dispatches off `_aborted`.
343
+ setStandardReadyState(xhr, DONE);
344
+ fireEvent(xhr, "abort");
345
+ fireEvent(xhr, "loadend");
346
+ }
347
+ });
348
+ const fire = () => {
349
+ timer = null;
350
+ if (aborted) return;
351
+ if (outcome.kind === "delay") {
352
+ // The real request is about to be genuinely in flight — put the
353
+ // prototype `abort()` back FIRST so cancelling reaches the network.
354
+ restoreAbort();
355
+ passThrough();
356
+ return;
357
+ }
358
+ if (outcome.kind === "fail") {
359
+ failStandard(xhr, outcome);
360
+ } else {
361
+ respondStandard(xhr, url, outcome, () => aborted);
362
+ }
363
+ settled = true;
364
+ restoreAbort();
365
+ };
366
+
367
+ // ALWAYS asynchronous, even at 0ms — same contract as the RN path: callers
368
+ // legitimately attach handlers after `send()` returns (axios does).
369
+ timer = setTimeout(fire, outcome.delayMs);
370
+ return true;
371
+ }
372
+
373
+ // ============================================================================
374
+ // Entry point
375
+ // ============================================================================
376
+
123
377
  /**
124
378
  * Apply an outcome to a request that has NOT been sent yet.
125
379
  *
@@ -130,6 +384,13 @@ function fail(xhr, outcome) {
130
384
  */
131
385
  function runOverride(xhr, url, outcome, passThrough) {
132
386
  if (!canSynthesize(xhr)) {
387
+ // Not React Native's XMLHttpRequest — a standards one (webview port,
388
+ // react-native-web) is driven through instance property shadowing
389
+ // instead. Order matters: RN's XHR also extends an EventTarget shim, so
390
+ // the RN check must win first.
391
+ if (canSynthesizeStandard(xhr)) {
392
+ return runStandardOverride(xhr, url, outcome, passThrough);
393
+ }
133
394
  warnUnsupported();
134
395
  return false;
135
396
  }
@@ -4,15 +4,42 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.generateNetworkCopyText = generateNetworkCopyText;
7
- var _formatting = require("./formatting");
8
7
  /**
9
8
  * Pure copy-text generation for the network tool (rule3: reusable logic
10
9
  * extracted from the modal). Ported verbatim from the `generateCopyText`
11
10
  * callback previously inlined in NetworkModal — kept pure so the header
12
11
  * CopyButton can resolve it LAZILY at copy time (CopyButton accepts a
13
12
  * function value) instead of rebuilding the string on every event.
13
+ *
14
+ * PLATFORM-FREE ON PURPOSE: this module is reused verbatim by the web ports
15
+ * (webview shims resolve `@buoy-gg/shared-ui` to a minimal storage-only shim),
16
+ * so it must not reach the RN component graph or the shared-ui barrel. That is
17
+ * why:
18
+ * - `CopySettings` is defined HERE rather than imported from
19
+ * NetworkCopySettingsView.tsx (which pulls in react-native + shared-ui RN
20
+ * chrome). The RN view keeps its own structurally-identical definition —
21
+ * keep the two shapes in sync.
22
+ * - `formatBytes` is a verbatim copy of
23
+ * shared/src/utils/formatting/dataFormatting.ts rather than the
24
+ * `./formatting` re-export of `@buoy-gg/shared-ui` (not in the web shim).
14
25
  */
15
26
 
27
+ /**
28
+ * What the copy exporter includes and how it formats it. Structurally
29
+ * identical to (and must stay in sync with) the `CopySettings` exported by
30
+ * NetworkCopySettingsView.tsx — see the platform-free note above for why the
31
+ * definition lives here.
32
+ */
33
+
34
+ /** shared/src/utils/formatting/dataFormatting.ts `formatBytes` — verbatim. */
35
+ function formatBytes(bytes) {
36
+ if (bytes === undefined || bytes === null) return "N/A";
37
+ if (bytes === 0) return "0 B";
38
+ const k = 1024;
39
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
40
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
41
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
42
+ }
16
43
  function shouldIncludePayload(data, bodySizeThresholdKb) {
17
44
  if (bodySizeThresholdKb === -1) return true;
18
45
  if (!data) return true;
@@ -99,8 +126,8 @@ function generateNetworkCopyText(events, copySettings) {
99
126
  md += `**Timestamp:** ${new Date(event.timestamp).toISOString()}\n`;
100
127
  }
101
128
  if (copySettings.includeSizes) {
102
- const reqSize = event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "N/A";
103
- const resSize = event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "N/A";
129
+ const reqSize = event.requestSize ? formatBytes(event.requestSize) : "N/A";
130
+ const resSize = event.responseSize ? formatBytes(event.responseSize) : "N/A";
104
131
  md += `**Request Size:** ${reqSize}\n`;
105
132
  md += `**Response Size:** ${resSize}\n`;
106
133
  }
@@ -115,7 +142,7 @@ function generateNetworkCopyText(events, copySettings) {
115
142
  if (shouldIncludePayload(event.requestData, copySettings.bodySizeThreshold)) {
116
143
  md += `\`\`\`json\n${JSON.stringify(event.requestData, null, 2)}\n\`\`\`\n`;
117
144
  } else {
118
- const size = event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "Unknown size";
145
+ const size = event.requestSize ? formatBytes(event.requestSize) : "Unknown size";
119
146
  md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
120
147
  }
121
148
  }
@@ -127,7 +154,7 @@ function generateNetworkCopyText(events, copySettings) {
127
154
  if (shouldIncludePayload(event.responseData, copySettings.bodySizeThreshold)) {
128
155
  md += `\`\`\`json\n${JSON.stringify(event.responseData, null, 2)}\n\`\`\`\n`;
129
156
  } else {
130
- const size = event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "Unknown size";
157
+ const size = event.responseSize ? formatBytes(event.responseSize) : "Unknown size";
131
158
  md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
132
159
  }
133
160
  }
@@ -111,8 +111,14 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
111
111
  const now = Date.now();
112
112
  const recent = this.recentRequests.get(requestKey);
113
113
 
114
- // If same request within 50ms, likely a duplicate from XHR/fetch dual interception
115
- if (recent && now - recent.at < 50) {
114
+ // If same request within 50ms, likely a duplicate from XHR/fetch dual
115
+ // interception. The age must be non-negative: Date.now() is not
116
+ // monotonic, and after a backwards clock adjustment (iOS time sync) a
117
+ // future-dated entry would satisfy `< 50` for EVERY request to this
118
+ // endpoint until the clock caught up — capture going silent for minutes
119
+ // to hours, "fixed" only by Clear wiping this map (field-reported). A
120
+ // negative age falls through and re-stamps the entry with the new clock.
121
+ if (recent && now - recent.at >= 0 && now - recent.at < 50) {
116
122
  // …but the duplicate is not always redundant. On React Native a
117
123
  // `fetch()` is seen twice: once by the fetch wrapper, which is what
118
124
  // creates this event, and again by the XHR wrapper underneath it —
@@ -129,11 +135,13 @@ class NetworkEventStore extends _sharedUi.BaseEventStore {
129
135
  id: request.id
130
136
  });
131
137
 
132
- // Clean up old entries to prevent memory leak
138
+ // Clean up old entries to prevent memory leak. Future-dated entries are
139
+ // stale by definition (clock moved backwards) — drop those too, or they
140
+ // survive every sweep until the clock overtakes them.
133
141
  if (this.recentRequests.size > 100) {
134
142
  const cutoff = now - 5000;
135
143
  for (const [key, entry] of this.recentRequests.entries()) {
136
- if (entry.at < cutoff) {
144
+ if (entry.at < cutoff || entry.at > now) {
137
145
  this.recentRequests.delete(key);
138
146
  }
139
147
  }
@@ -203,20 +203,6 @@ class NetworkListener {
203
203
  return `${Math.round(bytes / Math.pow(k, i) * 100) / 100} ${sizes[i]}`;
204
204
  }
205
205
 
206
- // Get response body size
207
- getResponseSize(body) {
208
- if (!body) return 0;
209
- if (typeof body === "string") return body.length;
210
- if (typeof body === "object") {
211
- try {
212
- return JSON.stringify(body).length;
213
- } catch {
214
- return 0;
215
- }
216
- }
217
- return 0;
218
- }
219
-
220
206
  /**
221
207
  * Handle XMLHttpRequest response processing
222
208
  * This method processes the response and emits appropriate events
@@ -544,8 +530,7 @@ class NetworkListener {
544
530
  const responseClone = response.clone();
545
531
  const {
546
532
  body,
547
- size: responseSize,
548
- truncated
533
+ size: responseSize
549
534
  } = await self.processResponseBody(responseClone);
550
535
 
551
536
  // Now the body is in — this is the comparable, total number.
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * @buoy-gg/network — Web Entry Point (webview ports: Tauri / Capacitor / web).
5
+ *
6
+ * Exposes the platform-free capture + data layer only. The capture core
7
+ * (networkListener) patches `globalThis.fetch` and `XMLHttpRequest.prototype`
8
+ * — standard web APIs — so it runs in a webview unchanged; the RN UI
9
+ * components (NetworkModal etc.) are NOT exported here. Their DOM twins are
10
+ * added feature-by-feature as the web tools port progresses.
11
+ *
12
+ * Consumers resolve `@buoy-gg/shared-ui` to a web shim (see
13
+ * example-tauri/src/shims/shared-ui.web.ts) providing localStorage-backed
14
+ * persistentStorage, devToolsStorageKeys, BaseEventStore, and the pure
15
+ * ignored-patterns helpers.
16
+ *
17
+ * @platform React DOM Web
18
+ */
19
+
20
+ // Capture core — fetch/XHR interception
21
+ export { networkListener, startNetworkListener, stopNetworkListener, addNetworkListener, removeAllNetworkListeners, isNetworkListening } from "./network/utils/networkListener";
22
+ // Event store + saved/pinned store
23
+ export { networkEventStore } from "./network/utils/networkEventStore";
24
+ export { networkSavedStore, flagsForEventId } from "./network/utils/networkSavedStore";
25
+
26
+ // Types
27
+
28
+ // Hooks (all platform-free: react + stores only)
29
+ export { useNetworkEvents, FREE_TIER_REQUEST_LIMIT } from "./network/hooks/useNetworkEvents";
30
+ export { useNetworkEventList } from "./network/hooks/useNetworkEventList";
31
+ export { useLiveNetworkEvent } from "./network/hooks/useLiveNetworkEvent";
32
+ export { useNetworkBadgeCounts } from "./network/hooks/useNetworkBadgeCounts";
33
+
34
+ // Selection cursor (detail view navigation) — platform-free store + hooks
35
+ export { useHasNetworkSelection, useSelectedNetworkEventId, useNetworkBodyEventId, useNetworkCursor } from "./network/hooks/useNetworkSelection";
36
+ export { networkSelectionStore, toSelectionIds } from "./network/utils/networkSelectionStore";
37
+
38
+ // DOM twins of the RN UI (added feature-by-feature; F2 = the LIST screen,
39
+ // F3 = the DETAIL view)
40
+ export { NetworkListScreen } from "./network/components/NetworkListScreen.web";
41
+ export { NetworkEventItemCompact } from "./network/components/NetworkEventItemCompact.web";
42
+ export { NetworkHeaderBadges, NetworkClearButton, NetworkSavedButton, NetworkDetailPinActions, NetworkHeaderCopyButton } from "./network/components/NetworkHeaderLive.web";
43
+ export { NetworkEventDetailView } from "./network/components/NetworkEventDetailView.web";
44
+ export { NetworkDetailStepper } from "./network/components/NetworkDetailStepper.web";
45
+ export { NetworkDetailScreen } from "./network/components/NetworkDetailScreen.web";
46
+
47
+ // F4 = FILTERS + SEARCH + IGNORED PATTERNS
48
+ export { NetworkFilterViewV3 } from "./network/components/NetworkFilterViewV3.web";
49
+ export { NetworkFilterScreen } from "./network/components/NetworkFilterScreen.web";
50
+
51
+ // F5 = SAVED/PINNED screen + COPY settings
52
+ export { NetworkSavedScreen } from "./network/components/NetworkSavedScreen.web";
53
+ export { NetworkCopySettingsView, DEFAULT_COPY_SETTINGS } from "./network/components/NetworkCopySettingsView.web";
54
+ export { NetworkCopyScreen } from "./network/components/NetworkCopyScreen.web";
55
+ export { generateNetworkCopyText } from "./network/utils/generateNetworkCopyText";
56
+ export { selectSavedEvents } from "./network/utils/selectSavedEvents";
57
+ export { FREE_TIER_SAVED_LIMIT, MAX_SAVED } from "./network/utils/networkSavedStore";
58
+
59
+ // F6 = RESPONSE OVERRIDES (screen + editor DOM twins on top of the
60
+ // platform-free rule store/engine the F1 capture core already applies)
61
+ export { NetworkOverridesScreen } from "./network/components/NetworkOverridesScreen.web";
62
+ export { NetworkOverrideEditor } from "./network/components/NetworkOverrideEditor.web";
63
+ export { NetworkNumberPrompt } from "./network/components/NetworkNumberPrompt.web";
64
+ export { NetworkOverridesButton } from "./network/components/NetworkOverridesButton.web";
65
+ export { NetworkOverrideHeaderButton } from "./network/components/NetworkOverrideHeaderButton.web";
66
+ export { useOverrideRules } from "./network/hooks/useOverrideRules";
67
+ // The persisted rule store — same `@react_buoy_network_overrides` storage key
68
+ // + JSON shape as native (persists through the shared-ui barrel, which web
69
+ // consumers resolve to their localStorage shim). Hosts call `ensureLoaded()`
70
+ // at boot, the way native preset.tsx does at import, so persisted rules fire
71
+ // from launch with the tool closed.
72
+ export { overrideRulesStore } from "./network/overrides/overrideRulesStore";
73
+ export { draftFromEvent, patternForUrl } from "./network/overrides/presets";
74
+ export { FREE_TIER_RULE_LIMIT, MAX_RULES, STATUS_MAX, STATUS_MIN } from "./network/overrides/types";
75
+ export { ruleMatches } from "./network/overrides/matchRule";
76
+
77
+ // Pure utils shared with the RN UI
78
+ export { filterNetworkEvents, applyIgnoredPatterns } from "./network/utils/filterNetworkEvents";
79
+ export { splitPinned } from "./network/utils/withPinnedFirst";
80
+ export { extractOperationName } from "./network/utils/extractOperationName";
81
+ export { generateCurl } from "./network/utils/generateCurl";