@buoy-gg/network 7.0.17 → 7.0.18

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.
@@ -324,13 +324,17 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
324
324
  // fetched on demand through the resolver. On the device there is no resolver
325
325
  // and bodies are always inline, so this is a no-op there.
326
326
  const resolveBody = (0, _networkBodyResolver.useNetworkBodyResolver)();
327
- const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
328
- const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
327
+ // The explicit flag is authoritative (adapter v5+). The size>0 fallback keeps
328
+ // detail-on-demand working against a device on an older adapter, which marked
329
+ // a withheld body only by leaving `data` undefined with a non-zero size.
330
+ const requestBodyOmitted = event.requestData === undefined && (event.requestBodyOmitted === true || (event.requestSize ?? 0) > 0);
331
+ const responseBodyOmitted = event.responseData === undefined && (event.responseBodyOmitted === true || (event.responseSize ?? 0) > 0);
332
+ const headersOmitted = event.headersOmitted === true;
329
333
  const [resolvedBody, setResolvedBody] = (0, _react.useState)(null);
330
334
  const [isLoadingBody, setIsLoadingBody] = (0, _react.useState)(false);
331
335
  (0, _react.useEffect)(() => {
332
336
  setResolvedBody(null);
333
- if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
337
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted && !headersOmitted) {
334
338
  setIsLoadingBody(false);
335
339
  return;
336
340
  }
@@ -344,12 +348,14 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
344
348
  return () => {
345
349
  cancelled = true;
346
350
  };
347
- }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
351
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted, headersOmitted]);
348
352
 
349
353
  // Effective bodies: inline data when present, otherwise the lazily resolved
350
354
  // body. `null`/`undefined` both collapse to undefined ("no body").
351
355
  const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
352
356
  const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
357
+ const requestHeaders = (headersOmitted ? resolvedBody?.requestHeaders : undefined) ?? event.requestHeaders;
358
+ const responseHeaders = (headersOmitted ? resolvedBody?.responseHeaders : undefined) ?? event.responseHeaders;
353
359
 
354
360
  // Generate full request details for copying
355
361
  const getFullRequestDetails = () => {
@@ -360,9 +366,9 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
360
366
  statusText: event.statusText,
361
367
  timestamp: new Date(event.timestamp).toISOString(),
362
368
  duration: event.duration ? `${event.duration}ms` : "N/A",
363
- requestHeaders: event.requestHeaders,
369
+ requestHeaders,
364
370
  requestData,
365
- responseHeaders: event.responseHeaders,
371
+ responseHeaders,
366
372
  responseData,
367
373
  requestSize: event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "N/A",
368
374
  responseSize: event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "N/A",
@@ -551,12 +557,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
551
557
  color: _sharedUi.macOSColors.semantic.info
552
558
  }),
553
559
  defaultOpen: false,
554
- count: Object.keys(event.requestHeaders).length,
555
- children: Object.keys(event.requestHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
560
+ count: Object.keys(requestHeaders).length,
561
+ children: Object.keys(requestHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
556
562
  style: styles.dataViewerContainer,
557
563
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
558
564
  title: "",
559
- data: event.requestHeaders,
565
+ data: requestHeaders,
560
566
  showTypeFilter: true,
561
567
  rawMode: true,
562
568
  initialExpanded: true,
@@ -573,12 +579,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
573
579
  color: NEUTRAL_ICON
574
580
  }),
575
581
  defaultOpen: false,
576
- count: Object.keys(event.responseHeaders).length,
577
- children: Object.keys(event.responseHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
582
+ count: Object.keys(responseHeaders).length,
583
+ children: Object.keys(responseHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
578
584
  style: styles.dataViewerContainer,
579
585
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
580
586
  title: "",
581
- data: event.responseHeaders,
587
+ data: responseHeaders,
582
588
  showTypeFilter: true,
583
589
  rawMode: true,
584
590
  initialExpanded: true,
@@ -637,13 +637,17 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
637
637
  // webview port there is normally no resolver and bodies are always inline,
638
638
  // so this is a no-op there — exactly like on the device.
639
639
  const resolveBody = (0, _networkBodyResolver.useNetworkBodyResolver)();
640
- const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
641
- const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
640
+ // The explicit flag is authoritative (adapter v5+). The size>0 fallback keeps
641
+ // detail-on-demand working against a device on an older adapter, which marked
642
+ // a withheld body only by leaving `data` undefined with a non-zero size.
643
+ const requestBodyOmitted = event.requestData === undefined && (event.requestBodyOmitted === true || (event.requestSize ?? 0) > 0);
644
+ const responseBodyOmitted = event.responseData === undefined && (event.responseBodyOmitted === true || (event.responseSize ?? 0) > 0);
645
+ const headersOmitted = event.headersOmitted === true;
642
646
  const [resolvedBody, setResolvedBody] = (0, _react.useState)(null);
643
647
  const [isLoadingBody, setIsLoadingBody] = (0, _react.useState)(false);
644
648
  (0, _react.useEffect)(() => {
645
649
  setResolvedBody(null);
646
- if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
650
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted && !headersOmitted) {
647
651
  setIsLoadingBody(false);
648
652
  return;
649
653
  }
@@ -657,12 +661,14 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
657
661
  return () => {
658
662
  cancelled = true;
659
663
  };
660
- }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
664
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted, headersOmitted]);
661
665
 
662
666
  // Effective bodies: inline data when present, otherwise the lazily resolved
663
667
  // body. `null`/`undefined` both collapse to undefined ("no body").
664
668
  const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
665
669
  const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
670
+ const requestHeaders = (headersOmitted ? resolvedBody?.requestHeaders : undefined) ?? event.requestHeaders;
671
+ const responseHeaders = (headersOmitted ? resolvedBody?.responseHeaders : undefined) ?? event.responseHeaders;
666
672
 
667
673
  // Generate full request details for copying (resolved LAZILY at press time —
668
674
  // see the copy button below).
@@ -674,9 +680,9 @@ const NetworkEventDetailView = exports.NetworkEventDetailView = /*#__PURE__*/(0,
674
680
  statusText: event.statusText,
675
681
  timestamp: new Date(event.timestamp).toISOString(),
676
682
  duration: event.duration ? `${event.duration}ms` : "N/A",
677
- requestHeaders: event.requestHeaders,
683
+ requestHeaders,
678
684
  requestData,
679
- responseHeaders: event.responseHeaders,
685
+ responseHeaders,
680
686
  responseData,
681
687
  requestSize: event.requestSize ? formatBytes(event.requestSize) : "N/A",
682
688
  responseSize: event.responseSize ? formatBytes(event.responseSize) : "N/A",
@@ -849,12 +855,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
849
855
  color: macOSColors.semantic.info
850
856
  }),
851
857
  defaultOpen: false,
852
- count: Object.keys(event.requestHeaders).length,
853
- children: Object.keys(event.requestHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
858
+ count: Object.keys(requestHeaders).length,
859
+ children: Object.keys(requestHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
854
860
  style: styles.dataViewerContainer,
855
861
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataViewer.DataViewer, {
856
862
  title: "",
857
- data: event.requestHeaders,
863
+ data: requestHeaders,
858
864
  showTypeFilter: true,
859
865
  rawMode: true,
860
866
  initialExpanded: true
@@ -870,12 +876,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
870
876
  color: NEUTRAL_ICON
871
877
  }),
872
878
  defaultOpen: false,
873
- count: Object.keys(event.responseHeaders).length,
874
- children: Object.keys(event.responseHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
879
+ count: Object.keys(responseHeaders).length,
880
+ children: Object.keys(responseHeaders).length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
875
881
  style: styles.dataViewerContainer,
876
882
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_DataViewer.DataViewer, {
877
883
  title: "",
878
- data: event.responseHeaders,
884
+ data: responseHeaders,
879
885
  showTypeFilter: true,
880
886
  rawMode: true,
881
887
  initialExpanded: true
@@ -3,38 +3,232 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.networkSyncAdapter = exports.SNAPSHOT_BODY_INLINE_LIMIT = void 0;
6
+ exports.networkSyncAdapter = exports.SNAPSHOT_HEADER_VALUE_LIMIT = exports.SNAPSHOT_EVENTS_BUDGET = exports.SNAPSHOT_BODY_INLINE_LIMIT = void 0;
7
7
  var _networkEventStore = require("../utils/networkEventStore");
8
8
  var _networkSavedStore = require("../utils/networkSavedStore");
9
9
  var _overrideRulesStore = require("../overrides/overrideRulesStore");
10
10
  var _presets = require("../overrides/presets");
11
11
  var _engine = require("../overrides/engine");
12
12
  var _networkListener = require("../utils/networkListener");
13
+ var _sharedUi = require("@buoy-gg/shared-ui");
13
14
  /**
14
- * Request/response bodies larger than this (using the cached size proxy already
15
- * on the event — no per-snapshot stringify) are NOT streamed in the list
16
- * snapshot. These are the multi-MB payloads (images, large JSON list responses)
17
- * that, re-sent every ~200ms for up to 500 events, freeze the desktop
18
- * dashboard's main thread while it deserializes and re-renders them. Smaller
19
- * bodies stay inline so the common case needs no round-trip (instant detail
20
- * view, and they remain available to copy/export). Stripped bodies are fetched
21
- * on demand via the `getEventBody` action when a request's detail view opens.
15
+ * Request/response bodies larger than this are NOT streamed in the list
16
+ * snapshot. Prefer the cached size proxy already on the event (free); if that
17
+ * field is missing or wrong (0 for a fat/cyclic body), fall back to an
18
+ * O(limit) walk. Stripped bodies come back via `getEventBody` on detail open.
22
19
  */
23
20
  const SNAPSHOT_BODY_INLINE_LIMIT = exports.SNAPSHOT_BODY_INLINE_LIMIT = 16 * 1024;
24
- const stripLargeBodies = event => {
25
- const stripRequest = event.requestData !== undefined && (event.requestSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
26
- const stripResponse = event.responseData !== undefined && (event.responseSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
27
- if (!stripRequest && !stripResponse) return event;
28
- // Keep sizes/headers/metadata; only drop the heavy data. `undefined` keys are
29
- // omitted by JSON serialization, so this shrinks the wire payload, and the
30
- // dashboard treats "data missing but size > 0" as "fetch on demand".
21
+
22
+ /**
23
+ * Total inline budget for one snapshot's event list, held under the emit
24
+ * layer's 2MB so a snapshot is never DROPPED (a drop leaves the whole panel
25
+ * stale). Per-field caps alone don't get there: 500 requests with 9KB
26
+ * responses are all individually legal and still add up to ~4.7MB. Newest
27
+ * events keep their bodies inline (that's what a developer is looking at);
28
+ * once the budget is spent, older events degrade to metadata + fetch-on-demand.
29
+ */
30
+ const SNAPSHOT_EVENTS_BUDGET = exports.SNAPSHOT_EVENTS_BUDGET = 1.25 * 1024 * 1024;
31
+ function shouldStripBody(data, reportedSize) {
32
+ if (data === undefined) return false;
33
+ // The cached size proxy already on the event is free when it's right; the
34
+ // walk is the fallback for when it's missing or wrong (0 for a fat body).
35
+ if ((reportedSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT) return true;
36
+ return (0, _sharedUi.isOverWireBudget)(data, SNAPSHOT_BODY_INLINE_LIMIT);
37
+ }
38
+
39
+ /** One event's wire form plus its measured cost, so the budget pass is free. */
40
+
41
+ /**
42
+ * Per-event wire cache. `networkEventStore` never mutates an event in place —
43
+ * a response/override update replaces the object — so keying on identity is
44
+ * correct and self-invalidating, and a completed request is converted once
45
+ * instead of on all ~5 snapshots per second for as long as it stays in the
46
+ * list. This is what keeps the guards off the hot path.
47
+ */
48
+ const wireCache = new WeakMap();
49
+ const strippedCache = new WeakMap();
50
+
51
+ /** Per-field guards: oversized bodies out, heavy headers/variables slimmed. */
52
+ function toWireEvent(event) {
53
+ const cached = wireCache.get(event);
54
+ if (cached) return cached;
55
+ let entry;
56
+ try {
57
+ entry = buildWireEvent(event);
58
+ } catch {
59
+ // A body with a throwing getter or `toJSON` can blow up the size walk
60
+ // itself. Degrading this ONE event to metadata beats letting getSnapshot
61
+ // throw, which would take the whole panel down with it.
62
+ entry = buildMetadataOnly(event);
63
+ }
64
+ wireCache.set(event, entry);
65
+ return entry;
66
+ }
67
+ function buildMetadataOnly(event) {
31
68
  const stripped = {
32
- ...event
69
+ ...event,
70
+ requestData: undefined,
71
+ responseData: undefined,
72
+ graphqlVariables: undefined,
73
+ requestBodyOmitted: event.requestData !== undefined ? true : undefined,
74
+ responseBodyOmitted: event.responseData !== undefined ? true : undefined
33
75
  };
34
- if (stripRequest) stripped.requestData = undefined;
35
- if (stripResponse) stripped.responseData = undefined;
36
- return stripped;
37
- };
76
+ // The payload fields are gone, so measuring this is safe even when measuring
77
+ // the original blew up. Fall back to a nominal cost only if even THIS throws
78
+ // (a hostile getter on the event itself rather than on its body).
79
+ let bytes = 1024;
80
+ try {
81
+ bytes = (0, _sharedUi.approxJsonSize)(stripped, SNAPSHOT_EVENTS_BUDGET).bytes;
82
+ } catch {
83
+ /* keep the nominal estimate */
84
+ }
85
+ return {
86
+ event: stripped,
87
+ bytes
88
+ };
89
+ }
90
+ function buildWireEvent(event) {
91
+ const stripRequest = shouldStripBody(event.requestData, event.requestSize);
92
+ const stripResponse = shouldStripBody(event.responseData, event.responseSize);
93
+ const graphqlVariables = toWireGraphqlVariables(event.graphqlVariables);
94
+ const requestHeaders = toWireHeaders(event.requestHeaders);
95
+ const responseHeaders = toWireHeaders(event.responseHeaders);
96
+ const changed = stripRequest || stripResponse || graphqlVariables !== event.graphqlVariables || requestHeaders !== event.requestHeaders || responseHeaders !== event.responseHeaders;
97
+ let wire = event;
98
+ if (changed) {
99
+ // `undefined` keys are omitted by JSON serialization, so dropping the data
100
+ // shrinks the wire payload. Sizes stay HONEST — the explicit
101
+ // request/responseBodyOmitted flags are what tell the dashboard to fetch,
102
+ // matching `OverrideRule.bodyOmitted`. Full values come from `getEventBody`.
103
+ wire = {
104
+ ...event
105
+ };
106
+ if (stripRequest) {
107
+ wire.requestData = undefined;
108
+ wire.requestBodyOmitted = true;
109
+ }
110
+ if (stripResponse) {
111
+ wire.responseData = undefined;
112
+ wire.responseBodyOmitted = true;
113
+ }
114
+ if (graphqlVariables !== event.graphqlVariables) {
115
+ wire.graphqlVariables = graphqlVariables;
116
+ }
117
+ if (requestHeaders !== event.requestHeaders) {
118
+ wire.requestHeaders = requestHeaders;
119
+ wire.headersOmitted = true;
120
+ }
121
+ if (responseHeaders !== event.responseHeaders) {
122
+ wire.responseHeaders = responseHeaders;
123
+ wire.headersOmitted = true;
124
+ }
125
+ }
126
+ return {
127
+ event: wire,
128
+ bytes: (0, _sharedUi.approxJsonSize)(wire, SNAPSHOT_EVENTS_BUDGET).bytes
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Metadata-only form, used for the tail of the list once the snapshot budget
134
+ * is spent. Method/url/status/timing all stay — only the payloads go.
135
+ */
136
+ function toStrippedEvent(event) {
137
+ const cached = strippedCache.get(event);
138
+ if (cached) return cached;
139
+ const {
140
+ event: wire
141
+ } = toWireEvent(event);
142
+ const stripped = {
143
+ ...wire
144
+ };
145
+ if (stripped.requestData !== undefined) {
146
+ stripped.requestData = undefined;
147
+ stripped.requestBodyOmitted = true;
148
+ }
149
+ if (stripped.responseData !== undefined) {
150
+ stripped.responseData = undefined;
151
+ stripped.responseBodyOmitted = true;
152
+ }
153
+ if (stripped.graphqlVariables !== undefined) {
154
+ stripped.graphqlVariables = toWireGraphqlVariables(stripped.graphqlVariables, /* force */true);
155
+ }
156
+ const entry = {
157
+ event: stripped,
158
+ bytes: (0, _sharedUi.approxJsonSize)(stripped, SNAPSHOT_EVENTS_BUDGET).bytes
159
+ };
160
+ strippedCache.set(event, entry);
161
+ return entry;
162
+ }
163
+
164
+ /**
165
+ * Spend the snapshot budget newest-first. The store keeps events newest-first
166
+ * already, so this hands the developer full detail on what they are actually
167
+ * looking at and degrades the tail rather than dropping the whole panel.
168
+ */
169
+ function fitEventsToBudget(events) {
170
+ const out = new Array(events.length);
171
+ let spent = 0;
172
+ for (let i = 0; i < events.length; i++) {
173
+ const full = toWireEvent(events[i]);
174
+ if (spent + full.bytes <= SNAPSHOT_EVENTS_BUDGET) {
175
+ out[i] = full.event;
176
+ spent += full.bytes;
177
+ } else {
178
+ const lean = toStrippedEvent(events[i]);
179
+ out[i] = lean.event;
180
+ spent += lean.bytes;
181
+ }
182
+ }
183
+ return out;
184
+ }
185
+
186
+ /**
187
+ * GraphQL variables are copied off `request.data.variables` onto their own
188
+ * field for the list subtitle (`GetPokemon › Sandshrew`). Capping requestData
189
+ * still left that copy on the wire. Keep short primitives for the subtitle;
190
+ * drop nested/fat values (file uploads, base64). Full variables stay in
191
+ * `requestData` on-device and come back via `getEventBody`.
192
+ */
193
+ function toWireGraphqlVariables(vars, force = false) {
194
+ if (!vars) return vars;
195
+ if (!force && !shouldStripBody(vars, undefined)) return vars;
196
+ const slim = {};
197
+ for (const [key, value] of Object.entries(vars)) {
198
+ if (typeof value === "string" && value.length <= 64) slim[key] = value;else if (typeof value === "number" || typeof value === "boolean") {
199
+ slim[key] = value;
200
+ }
201
+ }
202
+ return Object.keys(slim).length > 0 ? slim : undefined;
203
+ }
204
+
205
+ /**
206
+ * Header values (Cookie, Authorization, custom JSON) still rode every
207
+ * snapshot after bodies were stripped. 500 events × a 4KB session cookie
208
+ * is a 2MB drop of the whole Network panel. Keep short values inline
209
+ * (content-type, accept); truncate the rest. Full maps stay on-device
210
+ * and come back via `getEventBody`.
211
+ *
212
+ * Returns the SAME object when nothing was truncated: callers use identity to
213
+ * decide whether anything was omitted, so a fresh `{}` here would flag every
214
+ * header-less event as truncated (and defeat the no-copy fast path).
215
+ */
216
+ const SNAPSHOT_HEADER_VALUE_LIMIT = exports.SNAPSHOT_HEADER_VALUE_LIMIT = 64;
217
+ function toWireHeaders(headers) {
218
+ if (!headers) return headers;
219
+ let changed = false;
220
+ const slim = {};
221
+ for (const [key, value] of Object.entries(headers)) {
222
+ if (typeof value !== "string") continue;
223
+ if (value.length <= SNAPSHOT_HEADER_VALUE_LIMIT) {
224
+ slim[key] = value;
225
+ } else {
226
+ slim[key] = `${value.slice(0, SNAPSHOT_HEADER_VALUE_LIMIT)}… [${value.length - SNAPSHOT_HEADER_VALUE_LIMIT} more]`;
227
+ changed = true;
228
+ }
229
+ }
230
+ return changed ? slim : headers;
231
+ }
38
232
 
39
233
  /**
40
234
  * Drop oversized rule bodies from the snapshot, flagging what was dropped.
@@ -152,7 +346,9 @@ function baseFromRequest(params) {
152
346
  * this package doesn't need a dependency on it).
153
347
  *
154
348
  * Subscribing starts the network interceptor, so requests are only captured
155
- * while a dashboard is watching the network panel.
349
+ * while a dashboard is watching the network panel — or while an MCP
350
+ * client holds a capture-mode watch (`devtool-watch` with `stream:false`),
351
+ * which records without asking the device to push snapshots.
156
352
  *
157
353
  * v2: large bodies are stripped from the snapshot and fetched lazily via
158
354
  * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
@@ -164,9 +360,12 @@ function baseFromRequest(params) {
164
360
  * `NetworkPanel`/`NetworkInline` accept either shape.
165
361
  */
166
362
  const networkSyncAdapter = exports.networkSyncAdapter = {
167
- version: 4,
363
+ // v5: honest request/responseSize + explicit request/responseBodyOmitted
364
+ // flags, and a total snapshot budget so the list degrades instead of the
365
+ // whole panel being dropped at the emit layer.
366
+ version: 5,
168
367
  getSnapshot: () => ({
169
- events: _networkEventStore.networkEventStore.getEvents().map(stripLargeBodies),
368
+ events: fitEventsToBudget(_networkEventStore.networkEventStore.getEvents()),
170
369
  /**
171
370
  * Response-override rules live on the DEVICE — that's the only place they
172
371
  * can run, since a rule is applied inside the app's own XMLHttpRequest.
@@ -178,7 +377,7 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
178
377
  // the same 200ms loop that the body-stripping exists to protect.
179
378
  saved: _networkSavedStore.networkSavedStore.getRecords().map(record => ({
180
379
  ...record,
181
- event: stripLargeBodies(record.event)
380
+ event: toWireEvent(record.event).event
182
381
  }))
183
382
  }),
184
383
  subscribe: onChange => {
@@ -212,7 +411,9 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
212
411
  if (!event) return null;
213
412
  return {
214
413
  requestData: event.requestData ?? null,
215
- responseData: event.responseData ?? null
414
+ responseData: event.responseData ?? null,
415
+ requestHeaders: event.requestHeaders ?? null,
416
+ responseHeaders: event.responseHeaders ?? null
216
417
  };
217
418
  },
218
419
  /** Pin/unpin a request from the dashboard. */
@@ -359,6 +560,44 @@ const networkSyncAdapter = exports.networkSyncAdapter = {
359
560
  };
360
561
  },
361
562
  /** Why isn't my rule firing? Reports the engine's own view of the world. */
563
+ /**
564
+ * Is the device actually recording right now, and why not?
565
+ *
566
+ * The honest answer to an empty request list. Without this a caller can
567
+ * only guess, and the plausible guesses ("interception is broken on this
568
+ * runtime") are all wrong — the usual answer is simply that nothing is
569
+ * holding capture open.
570
+ */
571
+ getCaptureStatus: () => {
572
+ // RECORDING is what the caller actually wants to know, and it is driven
573
+ // by the store's subscriber count — not by whether the interceptor is
574
+ // installed. Those genuinely differ: an enabled override rule pins the
575
+ // interceptor (`overrideRulesStore.syncKeepAlive`) without anything
576
+ // writing to the event store, so reporting `isNetworkListening()` as
577
+ // "capturing" would answer "yes" to a device recording nothing. That is
578
+ // the same species of misleading answer this action exists to replace.
579
+ const subscribers = _networkEventStore.networkEventStore.getSubscriberCounts().total;
580
+
581
+ // `isNetworkListening()` lazily CONSTRUCTS the listener, which reaches
582
+ // for XMLHttpRequest — absent in Node and in any headless host. A
583
+ // diagnostic that throws is worse than no diagnostic, so probe
584
+ // defensively and report what we do know either way.
585
+ let interceptorInstalled = null;
586
+ let listenerCount = null;
587
+ try {
588
+ interceptorInstalled = (0, _networkListener.isNetworkListening)();
589
+ listenerCount = (0, _networkListener.getNetworkListenerCount)();
590
+ } catch {
591
+ /* no interceptable runtime here */
592
+ }
593
+ return {
594
+ capturing: subscribers > 0,
595
+ subscribers,
596
+ interceptorInstalled,
597
+ listenerCount,
598
+ eventCount: _networkEventStore.networkEventStore.getEvents().length
599
+ };
600
+ },
362
601
  debugOverrides: params => {
363
602
  const url = (params && typeof params === "object" ? params.url : undefined) ?? "https://example.com/";
364
603
  return {
@@ -323,13 +323,17 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
323
323
  // fetched on demand through the resolver. On the device there is no resolver
324
324
  // and bodies are always inline, so this is a no-op there.
325
325
  const resolveBody = useNetworkBodyResolver();
326
- const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
327
- const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
326
+ // The explicit flag is authoritative (adapter v5+). The size>0 fallback keeps
327
+ // detail-on-demand working against a device on an older adapter, which marked
328
+ // a withheld body only by leaving `data` undefined with a non-zero size.
329
+ const requestBodyOmitted = event.requestData === undefined && (event.requestBodyOmitted === true || (event.requestSize ?? 0) > 0);
330
+ const responseBodyOmitted = event.responseData === undefined && (event.responseBodyOmitted === true || (event.responseSize ?? 0) > 0);
331
+ const headersOmitted = event.headersOmitted === true;
328
332
  const [resolvedBody, setResolvedBody] = useState(null);
329
333
  const [isLoadingBody, setIsLoadingBody] = useState(false);
330
334
  useEffect(() => {
331
335
  setResolvedBody(null);
332
- if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
336
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted && !headersOmitted) {
333
337
  setIsLoadingBody(false);
334
338
  return;
335
339
  }
@@ -343,12 +347,14 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
343
347
  return () => {
344
348
  cancelled = true;
345
349
  };
346
- }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
350
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted, headersOmitted]);
347
351
 
348
352
  // Effective bodies: inline data when present, otherwise the lazily resolved
349
353
  // body. `null`/`undefined` both collapse to undefined ("no body").
350
354
  const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
351
355
  const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
356
+ const requestHeaders = (headersOmitted ? resolvedBody?.requestHeaders : undefined) ?? event.requestHeaders;
357
+ const responseHeaders = (headersOmitted ? resolvedBody?.responseHeaders : undefined) ?? event.responseHeaders;
352
358
 
353
359
  // Generate full request details for copying
354
360
  const getFullRequestDetails = () => {
@@ -359,9 +365,9 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
359
365
  statusText: event.statusText,
360
366
  timestamp: new Date(event.timestamp).toISOString(),
361
367
  duration: event.duration ? `${event.duration}ms` : "N/A",
362
- requestHeaders: event.requestHeaders,
368
+ requestHeaders,
363
369
  requestData,
364
- responseHeaders: event.responseHeaders,
370
+ responseHeaders,
365
371
  responseData,
366
372
  requestSize: event.requestSize ? formatBytes(event.requestSize) : "N/A",
367
373
  responseSize: event.responseSize ? formatBytes(event.responseSize) : "N/A",
@@ -550,12 +556,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
550
556
  color: macOSColors.semantic.info
551
557
  }),
552
558
  defaultOpen: false,
553
- count: Object.keys(event.requestHeaders).length,
554
- children: Object.keys(event.requestHeaders).length > 0 ? /*#__PURE__*/_jsx(View, {
559
+ count: Object.keys(requestHeaders).length,
560
+ children: Object.keys(requestHeaders).length > 0 ? /*#__PURE__*/_jsx(View, {
555
561
  style: styles.dataViewerContainer,
556
562
  children: /*#__PURE__*/_jsx(DataViewer, {
557
563
  title: "",
558
- data: event.requestHeaders,
564
+ data: requestHeaders,
559
565
  showTypeFilter: true,
560
566
  rawMode: true,
561
567
  initialExpanded: true,
@@ -572,12 +578,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
572
578
  color: NEUTRAL_ICON
573
579
  }),
574
580
  defaultOpen: false,
575
- count: Object.keys(event.responseHeaders).length,
576
- children: Object.keys(event.responseHeaders).length > 0 ? /*#__PURE__*/_jsx(View, {
581
+ count: Object.keys(responseHeaders).length,
582
+ children: Object.keys(responseHeaders).length > 0 ? /*#__PURE__*/_jsx(View, {
577
583
  style: styles.dataViewerContainer,
578
584
  children: /*#__PURE__*/_jsx(DataViewer, {
579
585
  title: "",
580
- data: event.responseHeaders,
586
+ data: responseHeaders,
581
587
  showTypeFilter: true,
582
588
  rawMode: true,
583
589
  initialExpanded: true,
@@ -635,13 +635,17 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
635
635
  // webview port there is normally no resolver and bodies are always inline,
636
636
  // so this is a no-op there — exactly like on the device.
637
637
  const resolveBody = useNetworkBodyResolver();
638
- const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
639
- const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
638
+ // The explicit flag is authoritative (adapter v5+). The size>0 fallback keeps
639
+ // detail-on-demand working against a device on an older adapter, which marked
640
+ // a withheld body only by leaving `data` undefined with a non-zero size.
641
+ const requestBodyOmitted = event.requestData === undefined && (event.requestBodyOmitted === true || (event.requestSize ?? 0) > 0);
642
+ const responseBodyOmitted = event.responseData === undefined && (event.responseBodyOmitted === true || (event.responseSize ?? 0) > 0);
643
+ const headersOmitted = event.headersOmitted === true;
640
644
  const [resolvedBody, setResolvedBody] = useState(null);
641
645
  const [isLoadingBody, setIsLoadingBody] = useState(false);
642
646
  useEffect(() => {
643
647
  setResolvedBody(null);
644
- if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
648
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted && !headersOmitted) {
645
649
  setIsLoadingBody(false);
646
650
  return;
647
651
  }
@@ -655,12 +659,14 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
655
659
  return () => {
656
660
  cancelled = true;
657
661
  };
658
- }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
662
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted, headersOmitted]);
659
663
 
660
664
  // Effective bodies: inline data when present, otherwise the lazily resolved
661
665
  // body. `null`/`undefined` both collapse to undefined ("no body").
662
666
  const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
663
667
  const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
668
+ const requestHeaders = (headersOmitted ? resolvedBody?.requestHeaders : undefined) ?? event.requestHeaders;
669
+ const responseHeaders = (headersOmitted ? resolvedBody?.responseHeaders : undefined) ?? event.responseHeaders;
664
670
 
665
671
  // Generate full request details for copying (resolved LAZILY at press time —
666
672
  // see the copy button below).
@@ -672,9 +678,9 @@ export const NetworkEventDetailView = /*#__PURE__*/memo(function NetworkEventDet
672
678
  statusText: event.statusText,
673
679
  timestamp: new Date(event.timestamp).toISOString(),
674
680
  duration: event.duration ? `${event.duration}ms` : "N/A",
675
- requestHeaders: event.requestHeaders,
681
+ requestHeaders,
676
682
  requestData,
677
- responseHeaders: event.responseHeaders,
683
+ responseHeaders,
678
684
  responseData,
679
685
  requestSize: event.requestSize ? formatBytes(event.requestSize) : "N/A",
680
686
  responseSize: event.responseSize ? formatBytes(event.responseSize) : "N/A",
@@ -847,12 +853,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
847
853
  color: macOSColors.semantic.info
848
854
  }),
849
855
  defaultOpen: false,
850
- count: Object.keys(event.requestHeaders).length,
851
- children: Object.keys(event.requestHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
856
+ count: Object.keys(requestHeaders).length,
857
+ children: Object.keys(requestHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
852
858
  style: styles.dataViewerContainer,
853
859
  children: /*#__PURE__*/_jsx(DataViewer, {
854
860
  title: "",
855
- data: event.requestHeaders,
861
+ data: requestHeaders,
856
862
  showTypeFilter: true,
857
863
  rawMode: true,
858
864
  initialExpanded: true
@@ -868,12 +874,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
868
874
  color: NEUTRAL_ICON
869
875
  }),
870
876
  defaultOpen: false,
871
- count: Object.keys(event.responseHeaders).length,
872
- children: Object.keys(event.responseHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
877
+ count: Object.keys(responseHeaders).length,
878
+ children: Object.keys(responseHeaders).length > 0 ? /*#__PURE__*/_jsx("div", {
873
879
  style: styles.dataViewerContainer,
874
880
  children: /*#__PURE__*/_jsx(DataViewer, {
875
881
  title: "",
876
- data: event.responseHeaders,
882
+ data: responseHeaders,
877
883
  showTypeFilter: true,
878
884
  rawMode: true,
879
885
  initialExpanded: true
@@ -6,31 +6,226 @@ import { overrideRulesStore } from "../overrides/overrideRulesStore";
6
6
  import { draftFromEvent } from "../overrides/presets";
7
7
  import { inspectEngine } from "../overrides/engine";
8
8
  import { getNetworkListenerCount, isNetworkListening } from "../utils/networkListener";
9
+ import { approxJsonSize, isOverWireBudget } from "@buoy-gg/shared-ui";
10
+
9
11
  /**
10
- * Request/response bodies larger than this (using the cached size proxy already
11
- * on the event — no per-snapshot stringify) are NOT streamed in the list
12
- * snapshot. These are the multi-MB payloads (images, large JSON list responses)
13
- * that, re-sent every ~200ms for up to 500 events, freeze the desktop
14
- * dashboard's main thread while it deserializes and re-renders them. Smaller
15
- * bodies stay inline so the common case needs no round-trip (instant detail
16
- * view, and they remain available to copy/export). Stripped bodies are fetched
17
- * on demand via the `getEventBody` action when a request's detail view opens.
12
+ * Request/response bodies larger than this are NOT streamed in the list
13
+ * snapshot. Prefer the cached size proxy already on the event (free); if that
14
+ * field is missing or wrong (0 for a fat/cyclic body), fall back to an
15
+ * O(limit) walk. Stripped bodies come back via `getEventBody` on detail open.
18
16
  */
19
17
  export const SNAPSHOT_BODY_INLINE_LIMIT = 16 * 1024;
20
- const stripLargeBodies = event => {
21
- const stripRequest = event.requestData !== undefined && (event.requestSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
22
- const stripResponse = event.responseData !== undefined && (event.responseSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
23
- if (!stripRequest && !stripResponse) return event;
24
- // Keep sizes/headers/metadata; only drop the heavy data. `undefined` keys are
25
- // omitted by JSON serialization, so this shrinks the wire payload, and the
26
- // dashboard treats "data missing but size > 0" as "fetch on demand".
18
+
19
+ /**
20
+ * Total inline budget for one snapshot's event list, held under the emit
21
+ * layer's 2MB so a snapshot is never DROPPED (a drop leaves the whole panel
22
+ * stale). Per-field caps alone don't get there: 500 requests with 9KB
23
+ * responses are all individually legal and still add up to ~4.7MB. Newest
24
+ * events keep their bodies inline (that's what a developer is looking at);
25
+ * once the budget is spent, older events degrade to metadata + fetch-on-demand.
26
+ */
27
+ export const SNAPSHOT_EVENTS_BUDGET = 1.25 * 1024 * 1024;
28
+ function shouldStripBody(data, reportedSize) {
29
+ if (data === undefined) return false;
30
+ // The cached size proxy already on the event is free when it's right; the
31
+ // walk is the fallback for when it's missing or wrong (0 for a fat body).
32
+ if ((reportedSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT) return true;
33
+ return isOverWireBudget(data, SNAPSHOT_BODY_INLINE_LIMIT);
34
+ }
35
+
36
+ /** One event's wire form plus its measured cost, so the budget pass is free. */
37
+
38
+ /**
39
+ * Per-event wire cache. `networkEventStore` never mutates an event in place —
40
+ * a response/override update replaces the object — so keying on identity is
41
+ * correct and self-invalidating, and a completed request is converted once
42
+ * instead of on all ~5 snapshots per second for as long as it stays in the
43
+ * list. This is what keeps the guards off the hot path.
44
+ */
45
+ const wireCache = new WeakMap();
46
+ const strippedCache = new WeakMap();
47
+
48
+ /** Per-field guards: oversized bodies out, heavy headers/variables slimmed. */
49
+ function toWireEvent(event) {
50
+ const cached = wireCache.get(event);
51
+ if (cached) return cached;
52
+ let entry;
53
+ try {
54
+ entry = buildWireEvent(event);
55
+ } catch {
56
+ // A body with a throwing getter or `toJSON` can blow up the size walk
57
+ // itself. Degrading this ONE event to metadata beats letting getSnapshot
58
+ // throw, which would take the whole panel down with it.
59
+ entry = buildMetadataOnly(event);
60
+ }
61
+ wireCache.set(event, entry);
62
+ return entry;
63
+ }
64
+ function buildMetadataOnly(event) {
27
65
  const stripped = {
28
- ...event
66
+ ...event,
67
+ requestData: undefined,
68
+ responseData: undefined,
69
+ graphqlVariables: undefined,
70
+ requestBodyOmitted: event.requestData !== undefined ? true : undefined,
71
+ responseBodyOmitted: event.responseData !== undefined ? true : undefined
29
72
  };
30
- if (stripRequest) stripped.requestData = undefined;
31
- if (stripResponse) stripped.responseData = undefined;
32
- return stripped;
33
- };
73
+ // The payload fields are gone, so measuring this is safe even when measuring
74
+ // the original blew up. Fall back to a nominal cost only if even THIS throws
75
+ // (a hostile getter on the event itself rather than on its body).
76
+ let bytes = 1024;
77
+ try {
78
+ bytes = approxJsonSize(stripped, SNAPSHOT_EVENTS_BUDGET).bytes;
79
+ } catch {
80
+ /* keep the nominal estimate */
81
+ }
82
+ return {
83
+ event: stripped,
84
+ bytes
85
+ };
86
+ }
87
+ function buildWireEvent(event) {
88
+ const stripRequest = shouldStripBody(event.requestData, event.requestSize);
89
+ const stripResponse = shouldStripBody(event.responseData, event.responseSize);
90
+ const graphqlVariables = toWireGraphqlVariables(event.graphqlVariables);
91
+ const requestHeaders = toWireHeaders(event.requestHeaders);
92
+ const responseHeaders = toWireHeaders(event.responseHeaders);
93
+ const changed = stripRequest || stripResponse || graphqlVariables !== event.graphqlVariables || requestHeaders !== event.requestHeaders || responseHeaders !== event.responseHeaders;
94
+ let wire = event;
95
+ if (changed) {
96
+ // `undefined` keys are omitted by JSON serialization, so dropping the data
97
+ // shrinks the wire payload. Sizes stay HONEST — the explicit
98
+ // request/responseBodyOmitted flags are what tell the dashboard to fetch,
99
+ // matching `OverrideRule.bodyOmitted`. Full values come from `getEventBody`.
100
+ wire = {
101
+ ...event
102
+ };
103
+ if (stripRequest) {
104
+ wire.requestData = undefined;
105
+ wire.requestBodyOmitted = true;
106
+ }
107
+ if (stripResponse) {
108
+ wire.responseData = undefined;
109
+ wire.responseBodyOmitted = true;
110
+ }
111
+ if (graphqlVariables !== event.graphqlVariables) {
112
+ wire.graphqlVariables = graphqlVariables;
113
+ }
114
+ if (requestHeaders !== event.requestHeaders) {
115
+ wire.requestHeaders = requestHeaders;
116
+ wire.headersOmitted = true;
117
+ }
118
+ if (responseHeaders !== event.responseHeaders) {
119
+ wire.responseHeaders = responseHeaders;
120
+ wire.headersOmitted = true;
121
+ }
122
+ }
123
+ return {
124
+ event: wire,
125
+ bytes: approxJsonSize(wire, SNAPSHOT_EVENTS_BUDGET).bytes
126
+ };
127
+ }
128
+
129
+ /**
130
+ * Metadata-only form, used for the tail of the list once the snapshot budget
131
+ * is spent. Method/url/status/timing all stay — only the payloads go.
132
+ */
133
+ function toStrippedEvent(event) {
134
+ const cached = strippedCache.get(event);
135
+ if (cached) return cached;
136
+ const {
137
+ event: wire
138
+ } = toWireEvent(event);
139
+ const stripped = {
140
+ ...wire
141
+ };
142
+ if (stripped.requestData !== undefined) {
143
+ stripped.requestData = undefined;
144
+ stripped.requestBodyOmitted = true;
145
+ }
146
+ if (stripped.responseData !== undefined) {
147
+ stripped.responseData = undefined;
148
+ stripped.responseBodyOmitted = true;
149
+ }
150
+ if (stripped.graphqlVariables !== undefined) {
151
+ stripped.graphqlVariables = toWireGraphqlVariables(stripped.graphqlVariables, /* force */true);
152
+ }
153
+ const entry = {
154
+ event: stripped,
155
+ bytes: approxJsonSize(stripped, SNAPSHOT_EVENTS_BUDGET).bytes
156
+ };
157
+ strippedCache.set(event, entry);
158
+ return entry;
159
+ }
160
+
161
+ /**
162
+ * Spend the snapshot budget newest-first. The store keeps events newest-first
163
+ * already, so this hands the developer full detail on what they are actually
164
+ * looking at and degrades the tail rather than dropping the whole panel.
165
+ */
166
+ function fitEventsToBudget(events) {
167
+ const out = new Array(events.length);
168
+ let spent = 0;
169
+ for (let i = 0; i < events.length; i++) {
170
+ const full = toWireEvent(events[i]);
171
+ if (spent + full.bytes <= SNAPSHOT_EVENTS_BUDGET) {
172
+ out[i] = full.event;
173
+ spent += full.bytes;
174
+ } else {
175
+ const lean = toStrippedEvent(events[i]);
176
+ out[i] = lean.event;
177
+ spent += lean.bytes;
178
+ }
179
+ }
180
+ return out;
181
+ }
182
+
183
+ /**
184
+ * GraphQL variables are copied off `request.data.variables` onto their own
185
+ * field for the list subtitle (`GetPokemon › Sandshrew`). Capping requestData
186
+ * still left that copy on the wire. Keep short primitives for the subtitle;
187
+ * drop nested/fat values (file uploads, base64). Full variables stay in
188
+ * `requestData` on-device and come back via `getEventBody`.
189
+ */
190
+ function toWireGraphqlVariables(vars, force = false) {
191
+ if (!vars) return vars;
192
+ if (!force && !shouldStripBody(vars, undefined)) return vars;
193
+ const slim = {};
194
+ for (const [key, value] of Object.entries(vars)) {
195
+ if (typeof value === "string" && value.length <= 64) slim[key] = value;else if (typeof value === "number" || typeof value === "boolean") {
196
+ slim[key] = value;
197
+ }
198
+ }
199
+ return Object.keys(slim).length > 0 ? slim : undefined;
200
+ }
201
+
202
+ /**
203
+ * Header values (Cookie, Authorization, custom JSON) still rode every
204
+ * snapshot after bodies were stripped. 500 events × a 4KB session cookie
205
+ * is a 2MB drop of the whole Network panel. Keep short values inline
206
+ * (content-type, accept); truncate the rest. Full maps stay on-device
207
+ * and come back via `getEventBody`.
208
+ *
209
+ * Returns the SAME object when nothing was truncated: callers use identity to
210
+ * decide whether anything was omitted, so a fresh `{}` here would flag every
211
+ * header-less event as truncated (and defeat the no-copy fast path).
212
+ */
213
+ export const SNAPSHOT_HEADER_VALUE_LIMIT = 64;
214
+ function toWireHeaders(headers) {
215
+ if (!headers) return headers;
216
+ let changed = false;
217
+ const slim = {};
218
+ for (const [key, value] of Object.entries(headers)) {
219
+ if (typeof value !== "string") continue;
220
+ if (value.length <= SNAPSHOT_HEADER_VALUE_LIMIT) {
221
+ slim[key] = value;
222
+ } else {
223
+ slim[key] = `${value.slice(0, SNAPSHOT_HEADER_VALUE_LIMIT)}… [${value.length - SNAPSHOT_HEADER_VALUE_LIMIT} more]`;
224
+ changed = true;
225
+ }
226
+ }
227
+ return changed ? slim : headers;
228
+ }
34
229
 
35
230
  /**
36
231
  * Drop oversized rule bodies from the snapshot, flagging what was dropped.
@@ -148,7 +343,9 @@ function baseFromRequest(params) {
148
343
  * this package doesn't need a dependency on it).
149
344
  *
150
345
  * Subscribing starts the network interceptor, so requests are only captured
151
- * while a dashboard is watching the network panel.
346
+ * while a dashboard is watching the network panel — or while an MCP
347
+ * client holds a capture-mode watch (`devtool-watch` with `stream:false`),
348
+ * which records without asking the device to push snapshots.
152
349
  *
153
350
  * v2: large bodies are stripped from the snapshot and fetched lazily via
154
351
  * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
@@ -160,9 +357,12 @@ function baseFromRequest(params) {
160
357
  * `NetworkPanel`/`NetworkInline` accept either shape.
161
358
  */
162
359
  export const networkSyncAdapter = {
163
- version: 4,
360
+ // v5: honest request/responseSize + explicit request/responseBodyOmitted
361
+ // flags, and a total snapshot budget so the list degrades instead of the
362
+ // whole panel being dropped at the emit layer.
363
+ version: 5,
164
364
  getSnapshot: () => ({
165
- events: networkEventStore.getEvents().map(stripLargeBodies),
365
+ events: fitEventsToBudget(networkEventStore.getEvents()),
166
366
  /**
167
367
  * Response-override rules live on the DEVICE — that's the only place they
168
368
  * can run, since a rule is applied inside the app's own XMLHttpRequest.
@@ -174,7 +374,7 @@ export const networkSyncAdapter = {
174
374
  // the same 200ms loop that the body-stripping exists to protect.
175
375
  saved: networkSavedStore.getRecords().map(record => ({
176
376
  ...record,
177
- event: stripLargeBodies(record.event)
377
+ event: toWireEvent(record.event).event
178
378
  }))
179
379
  }),
180
380
  subscribe: onChange => {
@@ -208,7 +408,9 @@ export const networkSyncAdapter = {
208
408
  if (!event) return null;
209
409
  return {
210
410
  requestData: event.requestData ?? null,
211
- responseData: event.responseData ?? null
411
+ responseData: event.responseData ?? null,
412
+ requestHeaders: event.requestHeaders ?? null,
413
+ responseHeaders: event.responseHeaders ?? null
212
414
  };
213
415
  },
214
416
  /** Pin/unpin a request from the dashboard. */
@@ -355,6 +557,44 @@ export const networkSyncAdapter = {
355
557
  };
356
558
  },
357
559
  /** Why isn't my rule firing? Reports the engine's own view of the world. */
560
+ /**
561
+ * Is the device actually recording right now, and why not?
562
+ *
563
+ * The honest answer to an empty request list. Without this a caller can
564
+ * only guess, and the plausible guesses ("interception is broken on this
565
+ * runtime") are all wrong — the usual answer is simply that nothing is
566
+ * holding capture open.
567
+ */
568
+ getCaptureStatus: () => {
569
+ // RECORDING is what the caller actually wants to know, and it is driven
570
+ // by the store's subscriber count — not by whether the interceptor is
571
+ // installed. Those genuinely differ: an enabled override rule pins the
572
+ // interceptor (`overrideRulesStore.syncKeepAlive`) without anything
573
+ // writing to the event store, so reporting `isNetworkListening()` as
574
+ // "capturing" would answer "yes" to a device recording nothing. That is
575
+ // the same species of misleading answer this action exists to replace.
576
+ const subscribers = networkEventStore.getSubscriberCounts().total;
577
+
578
+ // `isNetworkListening()` lazily CONSTRUCTS the listener, which reaches
579
+ // for XMLHttpRequest — absent in Node and in any headless host. A
580
+ // diagnostic that throws is worse than no diagnostic, so probe
581
+ // defensively and report what we do know either way.
582
+ let interceptorInstalled = null;
583
+ let listenerCount = null;
584
+ try {
585
+ interceptorInstalled = isNetworkListening();
586
+ listenerCount = getNetworkListenerCount();
587
+ } catch {
588
+ /* no interceptable runtime here */
589
+ }
590
+ return {
591
+ capturing: subscribers > 0,
592
+ subscribers,
593
+ interceptorInstalled,
594
+ listenerCount,
595
+ eventCount: networkEventStore.getEvents().length
596
+ };
597
+ },
358
598
  debugOverrides: params => {
359
599
  const url = (params && typeof params === "object" ? params.url : undefined) ?? "https://example.com/";
360
600
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkEventDetailView.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,UAAU,CAAC;AA0B7D,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA+QD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,sBAAsB,mEA6gBjC,CAAC"}
1
+ {"version":3,"file":"NetworkEventDetailView.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,UAAU,CAAC;AA0B7D,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA+QD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,sBAAsB,mEAoiBjC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkEventDetailView.web.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAWH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,UAAU,CAAC;AA8S7D,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuOD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mEA+djC,CAAC"}
1
+ {"version":3,"file":"NetworkEventDetailView.web.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.web.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAWH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,UAAU,CAAC;AA8S7D,UAAU,2BAA2B;IACnC,KAAK,EAAE,YAAY,CAAC;IACpB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAuOD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,mEAsfjC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=networkSyncAdapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"networkSyncAdapter.test.d.ts","sourceRoot":"","sources":["../../../../../src/network/sync/__tests__/networkSyncAdapter.test.ts"],"names":[],"mappings":""}
@@ -6,6 +6,8 @@ import { type ReactNode } from "react";
6
6
  export interface ResolvedNetworkBody {
7
7
  requestData?: unknown;
8
8
  responseData?: unknown;
9
+ requestHeaders?: Record<string, string> | null;
10
+ responseHeaders?: Record<string, string> | null;
9
11
  }
10
12
  /**
11
13
  * Resolves request/response bodies that were omitted from a synced snapshot.
@@ -1 +1 @@
1
- {"version":3,"file":"networkBodyResolver.d.ts","sourceRoot":"","sources":["../../../../src/network/sync/networkBodyResolver.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,EAAE,EAAE,MAAM,KACP,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;AAMzC,wBAAgB,2BAA2B,CAAC,EAC1C,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAEnE"}
1
+ {"version":3,"file":"networkBodyResolver.d.ts","sourceRoot":"","sources":["../../../../src/network/sync/networkBodyResolver.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC/C,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,EAAE,EAAE,MAAM,KACP,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;AAMzC,wBAAgB,2BAA2B,CAAC,EAC1C,QAAQ,EACR,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACrC,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAEnE"}
@@ -2,23 +2,42 @@ import type { OverrideRulesState } from "../overrides/overrideRulesStore";
2
2
  import type { OverrideRuleKind } from "../overrides/types";
3
3
  import type { NetworkEvent } from "../types";
4
4
  /**
5
- * Request/response bodies larger than this (using the cached size proxy already
6
- * on the event — no per-snapshot stringify) are NOT streamed in the list
7
- * snapshot. These are the multi-MB payloads (images, large JSON list responses)
8
- * that, re-sent every ~200ms for up to 500 events, freeze the desktop
9
- * dashboard's main thread while it deserializes and re-renders them. Smaller
10
- * bodies stay inline so the common case needs no round-trip (instant detail
11
- * view, and they remain available to copy/export). Stripped bodies are fetched
12
- * on demand via the `getEventBody` action when a request's detail view opens.
5
+ * Request/response bodies larger than this are NOT streamed in the list
6
+ * snapshot. Prefer the cached size proxy already on the event (free); if that
7
+ * field is missing or wrong (0 for a fat/cyclic body), fall back to an
8
+ * O(limit) walk. Stripped bodies come back via `getEventBody` on detail open.
13
9
  */
14
10
  export declare const SNAPSHOT_BODY_INLINE_LIMIT: number;
11
+ /**
12
+ * Total inline budget for one snapshot's event list, held under the emit
13
+ * layer's 2MB so a snapshot is never DROPPED (a drop leaves the whole panel
14
+ * stale). Per-field caps alone don't get there: 500 requests with 9KB
15
+ * responses are all individually legal and still add up to ~4.7MB. Newest
16
+ * events keep their bodies inline (that's what a developer is looking at);
17
+ * once the budget is spent, older events degrade to metadata + fetch-on-demand.
18
+ */
19
+ export declare const SNAPSHOT_EVENTS_BUDGET: number;
20
+ /**
21
+ * Header values (Cookie, Authorization, custom JSON) still rode every
22
+ * snapshot after bodies were stripped. 500 events × a 4KB session cookie
23
+ * is a 2MB drop of the whole Network panel. Keep short values inline
24
+ * (content-type, accept); truncate the rest. Full maps stay on-device
25
+ * and come back via `getEventBody`.
26
+ *
27
+ * Returns the SAME object when nothing was truncated: callers use identity to
28
+ * decide whether anything was omitted, so a fresh `{}` here would flag every
29
+ * header-less event as truncated (and defeat the no-copy fast path).
30
+ */
31
+ export declare const SNAPSHOT_HEADER_VALUE_LIMIT = 64;
15
32
  /**
16
33
  * Sync adapter for the network tool, consumed by @buoy-gg/external-sync's
17
34
  * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
18
35
  * this package doesn't need a dependency on it).
19
36
  *
20
37
  * Subscribing starts the network interceptor, so requests are only captured
21
- * while a dashboard is watching the network panel.
38
+ * while a dashboard is watching the network panel — or while an MCP
39
+ * client holds a capture-mode watch (`devtool-watch` with `stream:false`),
40
+ * which records without asking the device to push snapshots.
22
41
  *
23
42
  * v2: large bodies are stripped from the snapshot and fetched lazily via
24
43
  * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
@@ -59,12 +78,14 @@ export declare const networkSyncAdapter: {
59
78
  * back to the saved store, which may hold the only remaining copy of a
60
79
  * request that has since been cleared. Returns null if neither has it.
61
80
  */
62
- getEventBody: (params: unknown) => {
81
+ getEventBody: (params?: unknown) => {
63
82
  requestData: {} | null;
64
83
  responseData: {} | null;
84
+ requestHeaders: Record<string, string>;
85
+ responseHeaders: Record<string, string>;
65
86
  } | null;
66
87
  /** Pin/unpin a request from the dashboard. */
67
- setPinned: (params: unknown) => {
88
+ setPinned: (params?: unknown) => {
68
89
  ok: true;
69
90
  active: boolean;
70
91
  } | {
@@ -74,7 +95,7 @@ export declare const networkSyncAdapter: {
74
95
  ok: boolean;
75
96
  } | null;
76
97
  /** Save/unsave a request from the dashboard. */
77
- setSaved: (params: unknown) => {
98
+ setSaved: (params?: unknown) => {
78
99
  ok: true;
79
100
  active: boolean;
80
101
  } | {
@@ -84,7 +105,7 @@ export declare const networkSyncAdapter: {
84
105
  ok: boolean;
85
106
  } | null;
86
107
  /** Drop one pinned/saved record by its stable key. */
87
- removeSavedRecord: (params: unknown) => {
108
+ removeSavedRecord: (params?: unknown) => {
88
109
  ok: boolean;
89
110
  } | null;
90
111
  clearSavedRequests: () => {
@@ -94,12 +115,12 @@ export declare const networkSyncAdapter: {
94
115
  ok: boolean;
95
116
  };
96
117
  /** Master switch — turns every rule off without losing any of them. */
97
- setOverridesEnabled: (params: unknown) => {
118
+ setOverridesEnabled: (params?: unknown) => {
98
119
  ok: boolean;
99
120
  enabled: boolean;
100
121
  };
101
122
  /** Create a rule, or replace one by id. */
102
- upsertOverrideRule: (params: unknown) => {
123
+ upsertOverrideRule: (params?: unknown) => {
103
124
  ok: boolean;
104
125
  error: string;
105
126
  rule?: undefined;
@@ -115,14 +136,14 @@ export declare const networkSyncAdapter: {
115
136
  };
116
137
  error?: undefined;
117
138
  };
118
- setOverrideRuleEnabled: (params: unknown) => {
139
+ setOverrideRuleEnabled: (params?: unknown) => {
119
140
  ok: boolean;
120
141
  error: string;
121
142
  } | {
122
143
  ok: boolean;
123
144
  error?: undefined;
124
145
  };
125
- deleteOverrideRule: (params: unknown) => {
146
+ deleteOverrideRule: (params?: unknown) => {
126
147
  ok: boolean;
127
148
  error: string;
128
149
  } | {
@@ -139,11 +160,26 @@ export declare const networkSyncAdapter: {
139
160
  * (see `stripLargeRuleBodies`). Fetched when the user opens the rule, which
140
161
  * is the only moment it's needed.
141
162
  */
142
- getOverrideRuleBody: (params: unknown) => {
163
+ getOverrideRuleBody: (params?: unknown) => {
143
164
  body: string | null;
144
165
  } | null;
145
166
  /** Why isn't my rule firing? Reports the engine's own view of the world. */
146
- debugOverrides: (params: unknown) => {
167
+ /**
168
+ * Is the device actually recording right now, and why not?
169
+ *
170
+ * The honest answer to an empty request list. Without this a caller can
171
+ * only guess, and the plausible guesses ("interception is broken on this
172
+ * runtime") are all wrong — the usual answer is simply that nothing is
173
+ * holding capture open.
174
+ */
175
+ getCaptureStatus: () => {
176
+ capturing: boolean;
177
+ subscribers: number;
178
+ interceptorInstalled: boolean | null;
179
+ listenerCount: number | null;
180
+ eventCount: number;
181
+ };
182
+ debugOverrides: (params?: unknown) => {
147
183
  interceptorInstalled: boolean;
148
184
  listenerCount: number;
149
185
  engine: {
@@ -1 +1 @@
1
- {"version":3,"file":"networkSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/network/sync/networkSyncAdapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,QAAY,CAAC;AA8JpD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,kBAAkB;;;;QAI3B;;;;WAIG;;;;;;;;;;;;;0BAUiB,MAAM,IAAI;;;QAkB9B;;;;;WAKG;+BACoB,OAAO;;;;QAU9B,8CAA8C;4BAC1B,OAAO;;;;;;;;;QAY3B,gDAAgD;2BAC7B,OAAO;;;;;;;;;QAU1B,sDAAsD;oCAC1B,OAAO;;;;;;;;;QAsBnC,uEAAuE;sCACzC,OAAO;;;;QAKrC,2CAA2C;qCACd,OAAO;;;;;;;;;;;;;;;;yCAmCH,OAAO;;;;;;;qCAOX,OAAO;;;;;;;;;;QAYpC,+EAA+E;;QAG/E;;;;WAIG;sCAC2B,OAAO;;;QAYrC,4EAA4E;iCACnD,OAAO;;;;;;;;;;;;;;;;CAiBnC,CAAC"}
1
+ {"version":3,"file":"networkSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/network/sync/networkSyncAdapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAY,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,QAAqB,CAAC;AA2LzD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AA+J9C;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB;;;;QAO3B;;;;WAIG;;;;;;;;;;;;;0BAUiB,MAAM,IAAI;;;QAkB9B;;;;;WAKG;gCACqB,OAAO;;;;;;QAY/B,8CAA8C;6BACzB,OAAO;;;;;;;;;QAY5B,gDAAgD;4BAC5B,OAAO;;;;;;;;;QAU3B,sDAAsD;qCACzB,OAAO;;;;;;;;;QAsBpC,uEAAuE;uCACxC,OAAO;;;;QAKtC,2CAA2C;sCACb,OAAO;;;;;;;;;;;;;;;;0CAmCH,OAAO;;;;;;;sCAOX,OAAO;;;;;;;;;;QAYrC,+EAA+E;;QAG/E;;;;WAIG;uCAC4B,OAAO;;;QAYtC,4EAA4E;QAC5E;;;;;;;WAOG;;;;;;;;kCA+BuB,OAAO;;;;;;;;;;;;;;;;CAiBpC,CAAC"}
@@ -95,6 +95,20 @@ export interface NetworkEvent {
95
95
  * // Display: "GetPokemon › Sandshrew"
96
96
  */
97
97
  graphqlVariables?: Record<string, unknown>;
98
+ /**
99
+ * WIRE-ONLY: at least one header value was truncated on the snapshot.
100
+ * Full headers stay on-device and come back via `getEventBody`.
101
+ */
102
+ headersOmitted?: boolean;
103
+ /**
104
+ * WIRE-ONLY: `requestData` was withheld from the snapshot (too large, not
105
+ * serializable, or past the snapshot's total budget). Sizes stay honest —
106
+ * this flag, not a doctored `requestSize`, is what marks fetch-on-demand.
107
+ * Mirrors `OverrideRule.bodyOmitted`. Fetched via `getEventBody`.
108
+ */
109
+ requestBodyOmitted?: boolean;
110
+ /** WIRE-ONLY: `responseData` was withheld. See `requestBodyOmitted`. */
111
+ responseBodyOmitted?: boolean;
98
112
  /**
99
113
  * Set when a response-override rule affected this request.
100
114
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/network/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/network/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/network",
3
- "version": "7.0.17",
3
+ "version": "7.0.18",
4
4
  "description": "In-app network inspector for React Native — requests, responses, timing & errors live, with GraphQL operation parsing. Part of Buoy devtools.",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/shared-ui": "^7.0.17"
29
+ "@buoy-gg/shared-ui": "^7.0.18"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": "*",