@buoy-gg/network 3.0.1 → 3.0.2

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.
@@ -3,6 +3,12 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "NetworkBodyResolverProvider", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _networkBodyResolver.NetworkBodyResolverProvider;
10
+ }
11
+ });
6
12
  Object.defineProperty(exports, "NetworkEventDetailView", {
7
13
  enumerable: true,
8
14
  get: function () {
@@ -69,6 +75,12 @@ Object.defineProperty(exports, "networkListener", {
69
75
  return _networkListener.networkListener;
70
76
  }
71
77
  });
78
+ Object.defineProperty(exports, "networkSyncAdapter", {
79
+ enumerable: true,
80
+ get: function () {
81
+ return _networkSyncAdapter.networkSyncAdapter;
82
+ }
83
+ });
72
84
  Object.defineProperty(exports, "networkToolPreset", {
73
85
  enumerable: true,
74
86
  get: function () {
@@ -100,5 +112,7 @@ var _NetworkEventItemCompact = require("./network/components/NetworkEventItemCom
100
112
  var _useNetworkEvents = require("./network/hooks/useNetworkEvents");
101
113
  var _useTickEveryMinute = require("./network/hooks/useTickEveryMinute");
102
114
  var _formatting = require("./network/utils/formatting");
115
+ var _networkSyncAdapter = require("./network/sync/networkSyncAdapter");
116
+ var _networkBodyResolver = require("./network/sync/networkBodyResolver");
103
117
  var _networkEventStore = require("./network/utils/networkEventStore");
104
118
  var _networkListener = require("./network/utils/networkListener");
@@ -10,6 +10,7 @@ var _sharedUi = require("@buoy-gg/shared-ui");
10
10
  var _formatting = require("../utils/formatting");
11
11
  var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
12
12
  var _formatGraphQLVariables = require("../utils/formatGraphQLVariables");
13
+ var _networkBodyResolver = require("../sync/networkBodyResolver");
13
14
  var _jsxRuntime = require("react/jsx-runtime");
14
15
  // Component for collapsible sections matching Sentry style
15
16
  const CollapsibleSection = ({
@@ -189,6 +190,37 @@ function NetworkEventDetailView({
189
190
  }
190
191
  }, [showUpgradeModal, isPro]);
191
192
 
193
+ // Large bodies are omitted from synced snapshots (see networkSyncAdapter) and
194
+ // fetched on demand through the resolver. On the device there is no resolver
195
+ // and bodies are always inline, so this is a no-op there.
196
+ const resolveBody = (0, _networkBodyResolver.useNetworkBodyResolver)();
197
+ const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
198
+ const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
199
+ const [resolvedBody, setResolvedBody] = (0, _react.useState)(null);
200
+ const [isLoadingBody, setIsLoadingBody] = (0, _react.useState)(false);
201
+ (0, _react.useEffect)(() => {
202
+ setResolvedBody(null);
203
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
204
+ setIsLoadingBody(false);
205
+ return;
206
+ }
207
+ let cancelled = false;
208
+ setIsLoadingBody(true);
209
+ resolveBody(event.id).then(body => {
210
+ if (!cancelled) setResolvedBody(body);
211
+ }).catch(() => {}).finally(() => {
212
+ if (!cancelled) setIsLoadingBody(false);
213
+ });
214
+ return () => {
215
+ cancelled = true;
216
+ };
217
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
218
+
219
+ // Effective bodies: inline data when present, otherwise the lazily resolved
220
+ // body. `null`/`undefined` both collapse to undefined ("no body").
221
+ const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
222
+ const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
223
+
192
224
  // Generate full request details for copying
193
225
  const getFullRequestDetails = () => {
194
226
  const requestDetails = {
@@ -199,9 +231,9 @@ function NetworkEventDetailView({
199
231
  timestamp: new Date(event.timestamp).toISOString(),
200
232
  duration: event.duration ? `${event.duration}ms` : "N/A",
201
233
  requestHeaders: event.requestHeaders,
202
- requestData: event.requestData,
234
+ requestData,
203
235
  responseHeaders: event.responseHeaders,
204
- responseData: event.responseData,
236
+ responseData,
205
237
  requestSize: event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "N/A",
206
238
  responseSize: event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "N/A",
207
239
  error: event.error,
@@ -298,7 +330,7 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
298
330
  })]
299
331
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(UrlBreakdown, {
300
332
  url: event.url,
301
- requestData: event.requestData,
333
+ requestData: requestData,
302
334
  operationName: event.operationName,
303
335
  graphqlVariables: event.graphqlVariables,
304
336
  requestClient: event.requestClient,
@@ -401,7 +433,7 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
401
433
  style: styles.emptyText,
402
434
  children: "No response headers yet"
403
435
  })
404
- }), event.requestData ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
436
+ }), requestData !== undefined ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
405
437
  title: "Request Body",
406
438
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
407
439
  size: 14,
@@ -412,14 +444,25 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
412
444
  style: styles.dataViewerContainer,
413
445
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
414
446
  title: "",
415
- data: event.requestData,
447
+ data: requestData,
416
448
  showTypeFilter: true,
417
449
  rawMode: true,
418
450
  initialExpanded: true,
419
451
  disableCopy: !isPro
420
452
  })
421
453
  })
422
- }) : null, event.responseData ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
454
+ }) : requestBodyOmitted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
455
+ title: "Request Body",
456
+ icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
457
+ size: 14,
458
+ color: _sharedUi.macOSColors.semantic.info
459
+ }),
460
+ defaultOpen: true,
461
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
462
+ style: styles.emptyText,
463
+ children: isLoadingBody ? `Loading request body (${(0, _formatting.formatBytes)(event.requestSize ?? 0)})…` : "Request body unavailable"
464
+ })
465
+ }) : null, responseData !== undefined ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
423
466
  title: "Response Body",
424
467
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
425
468
  size: 14,
@@ -430,13 +473,24 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
430
473
  style: styles.dataViewerContainer,
431
474
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
432
475
  title: "",
433
- data: event.responseData,
476
+ data: responseData,
434
477
  showTypeFilter: true,
435
478
  rawMode: true,
436
479
  initialExpanded: true,
437
480
  disableCopy: !isPro
438
481
  })
439
482
  })
483
+ }) : responseBodyOmitted ? /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
484
+ title: "Response Body",
485
+ icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.FileJson, {
486
+ size: 14,
487
+ color: _sharedUi.macOSColors.semantic.success
488
+ }),
489
+ defaultOpen: true,
490
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
491
+ style: styles.emptyText,
492
+ children: isLoadingBody ? `Loading response body (${(0, _formatting.formatBytes)(event.responseSize ?? 0)})…` : "Response body unavailable"
493
+ })
440
494
  }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(CollapsibleSection, {
441
495
  title: "Filter Options",
442
496
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
@@ -159,7 +159,7 @@ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(
159
159
  event: event,
160
160
  isPending: isPending,
161
161
  statusColor: statusColor
162
- }), event.requestClient && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
162
+ }), event.requestClient ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
163
163
  style: [styles.topRightBadge, {
164
164
  backgroundColor: event.requestClient === "fetch" ? "rgba(74, 144, 226, 0.15)" : event.requestClient === "graphql" ? "rgba(229, 53, 171, 0.15)" : event.requestClient === "grpc-web" ? "rgba(16, 185, 129, 0.15)" : event.requestClient === "xhr" ? "rgba(245, 158, 11, 0.15)" : "rgba(147, 51, 234, 0.15)"
165
165
  }],
@@ -169,7 +169,7 @@ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(
169
169
  }],
170
170
  children: event.requestClient === "graphql" ? "GQL" : event.requestClient === "grpc-web" ? "gRPC" : event.requestClient === "xhr" ? "XHR" : event.requestClient
171
171
  })
172
- })]
172
+ }) : null]
173
173
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
174
174
  style: styles.leftSection,
175
175
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.MethodBadge, {
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkBodyResolverProvider = NetworkBodyResolverProvider;
7
+ exports.useNetworkBodyResolver = useNetworkBodyResolver;
8
+ var _react = require("react");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ /**
11
+ * The full request/response bodies for a single network event, fetched on
12
+ * demand. Either field may be `null` when the event genuinely has no body.
13
+ */
14
+
15
+ /**
16
+ * Resolves request/response bodies that were omitted from a synced snapshot.
17
+ *
18
+ * Large bodies (see `networkSyncAdapter`) are stripped from the list snapshot
19
+ * so they aren't re-streamed every ~200ms; the detail view fetches them lazily
20
+ * through this resolver when a request is opened.
21
+ *
22
+ * On the device this is never provided — bodies are always inline — so the
23
+ * detail view simply falls back to the inline data. The desktop dashboard
24
+ * provides a resolver that fetches the body over the devtool protocol.
25
+ */
26
+
27
+ const NetworkBodyResolverContext = /*#__PURE__*/(0, _react.createContext)(null);
28
+ function NetworkBodyResolverProvider({
29
+ resolver,
30
+ children
31
+ }) {
32
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkBodyResolverContext.Provider, {
33
+ value: resolver,
34
+ children: children
35
+ });
36
+ }
37
+ function useNetworkBodyResolver() {
38
+ return (0, _react.useContext)(NetworkBodyResolverContext);
39
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.networkSyncAdapter = exports.SNAPSHOT_BODY_INLINE_LIMIT = void 0;
7
+ var _networkEventStore = require("../utils/networkEventStore");
8
+ /**
9
+ * Request/response bodies larger than this (using the cached size proxy already
10
+ * on the event — no per-snapshot stringify) are NOT streamed in the list
11
+ * snapshot. These are the multi-MB payloads (images, large JSON list responses)
12
+ * that, re-sent every ~200ms for up to 500 events, freeze the desktop
13
+ * dashboard's main thread while it deserializes and re-renders them. Smaller
14
+ * bodies stay inline so the common case needs no round-trip (instant detail
15
+ * view, and they remain available to copy/export). Stripped bodies are fetched
16
+ * on demand via the `getEventBody` action when a request's detail view opens.
17
+ */
18
+ const SNAPSHOT_BODY_INLINE_LIMIT = exports.SNAPSHOT_BODY_INLINE_LIMIT = 16 * 1024;
19
+ const stripLargeBodies = event => {
20
+ const stripRequest = event.requestData !== undefined && (event.requestSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
21
+ const stripResponse = event.responseData !== undefined && (event.responseSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
22
+ if (!stripRequest && !stripResponse) return event;
23
+ // Keep sizes/headers/metadata; only drop the heavy data. `undefined` keys are
24
+ // omitted by JSON serialization, so this shrinks the wire payload, and the
25
+ // dashboard treats "data missing but size > 0" as "fetch on demand".
26
+ const stripped = {
27
+ ...event
28
+ };
29
+ if (stripRequest) stripped.requestData = undefined;
30
+ if (stripResponse) stripped.responseData = undefined;
31
+ return stripped;
32
+ };
33
+
34
+ /**
35
+ * Sync adapter for the network tool, consumed by @buoy-gg/external-sync's
36
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
37
+ * this package doesn't need a dependency on it).
38
+ *
39
+ * Subscribing starts the network interceptor, so requests are only captured
40
+ * while a dashboard is watching the network panel.
41
+ *
42
+ * v2: large bodies are stripped from the snapshot and fetched lazily via
43
+ * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
44
+ */
45
+ const networkSyncAdapter = exports.networkSyncAdapter = {
46
+ version: 2,
47
+ getSnapshot: () => _networkEventStore.networkEventStore.getEvents().map(stripLargeBodies),
48
+ subscribe: onChange => _networkEventStore.networkEventStore.subscribeToEvents(onChange),
49
+ actions: {
50
+ clearEvents: () => {
51
+ _networkEventStore.networkEventStore.clearEvents();
52
+ },
53
+ /**
54
+ * Return the full (un-stripped) request/response bodies for one event,
55
+ * fetched on demand by the dashboard when its detail view opens. Returns
56
+ * null if the event is no longer in the store.
57
+ */
58
+ getEventBody: params => {
59
+ const id = params && typeof params === "object" ? params.id : undefined;
60
+ if (!id) return null;
61
+ const event = _networkEventStore.networkEventStore.getEventById(id);
62
+ if (!event) return null;
63
+ return {
64
+ requestData: event.requestData ?? null,
65
+ responseData: event.responseData ?? null
66
+ };
67
+ }
68
+ }
69
+ };
@@ -37,6 +37,12 @@ export { formatBytes, formatDuration, formatHttpStatus } from "./network/utils/f
37
37
  // TYPES (For TypeScript users)
38
38
  // =============================================================================
39
39
 
40
+ // =============================================================================
41
+ // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
42
+ // =============================================================================
43
+ export { networkSyncAdapter } from "./network/sync/networkSyncAdapter";
44
+ export { NetworkBodyResolverProvider } from "./network/sync/networkBodyResolver";
45
+
40
46
  // =============================================================================
41
47
  // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
42
48
  // These are re-exported for cross-package communication within the monorepo.
@@ -7,6 +7,7 @@ import { formatBytes, formatDuration, formatHttpStatus } from "../utils/formatti
7
7
  import { formatRelativeTime } from "@buoy-gg/shared-ui";
8
8
  import { DataViewer } from "@buoy-gg/shared-ui/dataViewer";
9
9
  import { formatGraphQLDisplay } from "../utils/formatGraphQLVariables";
10
+ import { useNetworkBodyResolver } from "../sync/networkBodyResolver";
10
11
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
12
  // Component for collapsible sections matching Sentry style
12
13
  const CollapsibleSection = ({
@@ -186,6 +187,37 @@ export function NetworkEventDetailView({
186
187
  }
187
188
  }, [showUpgradeModal, isPro]);
188
189
 
190
+ // Large bodies are omitted from synced snapshots (see networkSyncAdapter) and
191
+ // fetched on demand through the resolver. On the device there is no resolver
192
+ // and bodies are always inline, so this is a no-op there.
193
+ const resolveBody = useNetworkBodyResolver();
194
+ const requestBodyOmitted = event.requestData === undefined && (event.requestSize ?? 0) > 0;
195
+ const responseBodyOmitted = event.responseData === undefined && (event.responseSize ?? 0) > 0;
196
+ const [resolvedBody, setResolvedBody] = useState(null);
197
+ const [isLoadingBody, setIsLoadingBody] = useState(false);
198
+ useEffect(() => {
199
+ setResolvedBody(null);
200
+ if (!resolveBody || !requestBodyOmitted && !responseBodyOmitted) {
201
+ setIsLoadingBody(false);
202
+ return;
203
+ }
204
+ let cancelled = false;
205
+ setIsLoadingBody(true);
206
+ resolveBody(event.id).then(body => {
207
+ if (!cancelled) setResolvedBody(body);
208
+ }).catch(() => {}).finally(() => {
209
+ if (!cancelled) setIsLoadingBody(false);
210
+ });
211
+ return () => {
212
+ cancelled = true;
213
+ };
214
+ }, [event.id, resolveBody, requestBodyOmitted, responseBodyOmitted]);
215
+
216
+ // Effective bodies: inline data when present, otherwise the lazily resolved
217
+ // body. `null`/`undefined` both collapse to undefined ("no body").
218
+ const requestData = event.requestData ?? resolvedBody?.requestData ?? undefined;
219
+ const responseData = event.responseData ?? resolvedBody?.responseData ?? undefined;
220
+
189
221
  // Generate full request details for copying
190
222
  const getFullRequestDetails = () => {
191
223
  const requestDetails = {
@@ -196,9 +228,9 @@ export function NetworkEventDetailView({
196
228
  timestamp: new Date(event.timestamp).toISOString(),
197
229
  duration: event.duration ? `${event.duration}ms` : "N/A",
198
230
  requestHeaders: event.requestHeaders,
199
- requestData: event.requestData,
231
+ requestData,
200
232
  responseHeaders: event.responseHeaders,
201
- responseData: event.responseData,
233
+ responseData,
202
234
  requestSize: event.requestSize ? formatBytes(event.requestSize) : "N/A",
203
235
  responseSize: event.responseSize ? formatBytes(event.responseSize) : "N/A",
204
236
  error: event.error,
@@ -295,7 +327,7 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
295
327
  })]
296
328
  }), /*#__PURE__*/_jsx(UrlBreakdown, {
297
329
  url: event.url,
298
- requestData: event.requestData,
330
+ requestData: requestData,
299
331
  operationName: event.operationName,
300
332
  graphqlVariables: event.graphqlVariables,
301
333
  requestClient: event.requestClient,
@@ -398,7 +430,7 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
398
430
  style: styles.emptyText,
399
431
  children: "No response headers yet"
400
432
  })
401
- }), event.requestData ? /*#__PURE__*/_jsx(CollapsibleSection, {
433
+ }), requestData !== undefined ? /*#__PURE__*/_jsx(CollapsibleSection, {
402
434
  title: "Request Body",
403
435
  icon: /*#__PURE__*/_jsx(FileJson, {
404
436
  size: 14,
@@ -409,14 +441,25 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
409
441
  style: styles.dataViewerContainer,
410
442
  children: /*#__PURE__*/_jsx(DataViewer, {
411
443
  title: "",
412
- data: event.requestData,
444
+ data: requestData,
413
445
  showTypeFilter: true,
414
446
  rawMode: true,
415
447
  initialExpanded: true,
416
448
  disableCopy: !isPro
417
449
  })
418
450
  })
419
- }) : null, event.responseData ? /*#__PURE__*/_jsx(CollapsibleSection, {
451
+ }) : requestBodyOmitted ? /*#__PURE__*/_jsx(CollapsibleSection, {
452
+ title: "Request Body",
453
+ icon: /*#__PURE__*/_jsx(FileJson, {
454
+ size: 14,
455
+ color: macOSColors.semantic.info
456
+ }),
457
+ defaultOpen: true,
458
+ children: /*#__PURE__*/_jsx(Text, {
459
+ style: styles.emptyText,
460
+ children: isLoadingBody ? `Loading request body (${formatBytes(event.requestSize ?? 0)})…` : "Request body unavailable"
461
+ })
462
+ }) : null, responseData !== undefined ? /*#__PURE__*/_jsx(CollapsibleSection, {
420
463
  title: "Response Body",
421
464
  icon: /*#__PURE__*/_jsx(FileJson, {
422
465
  size: 14,
@@ -427,13 +470,24 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
427
470
  style: styles.dataViewerContainer,
428
471
  children: /*#__PURE__*/_jsx(DataViewer, {
429
472
  title: "",
430
- data: event.responseData,
473
+ data: responseData,
431
474
  showTypeFilter: true,
432
475
  rawMode: true,
433
476
  initialExpanded: true,
434
477
  disableCopy: !isPro
435
478
  })
436
479
  })
480
+ }) : responseBodyOmitted ? /*#__PURE__*/_jsx(CollapsibleSection, {
481
+ title: "Response Body",
482
+ icon: /*#__PURE__*/_jsx(FileJson, {
483
+ size: 14,
484
+ color: macOSColors.semantic.success
485
+ }),
486
+ defaultOpen: true,
487
+ children: /*#__PURE__*/_jsx(Text, {
488
+ style: styles.emptyText,
489
+ children: isLoadingBody ? `Loading response body (${formatBytes(event.responseSize ?? 0)})…` : "Response body unavailable"
490
+ })
437
491
  }) : null, /*#__PURE__*/_jsx(CollapsibleSection, {
438
492
  title: "Filter Options",
439
493
  icon: /*#__PURE__*/_jsx(Filter, {
@@ -156,7 +156,7 @@ export const NetworkEventItemCompact = /*#__PURE__*/memo(({
156
156
  event: event,
157
157
  isPending: isPending,
158
158
  statusColor: statusColor
159
- }), event.requestClient && /*#__PURE__*/_jsx(View, {
159
+ }), event.requestClient ? /*#__PURE__*/_jsx(View, {
160
160
  style: [styles.topRightBadge, {
161
161
  backgroundColor: event.requestClient === "fetch" ? "rgba(74, 144, 226, 0.15)" : event.requestClient === "graphql" ? "rgba(229, 53, 171, 0.15)" : event.requestClient === "grpc-web" ? "rgba(16, 185, 129, 0.15)" : event.requestClient === "xhr" ? "rgba(245, 158, 11, 0.15)" : "rgba(147, 51, 234, 0.15)"
162
162
  }],
@@ -166,7 +166,7 @@ export const NetworkEventItemCompact = /*#__PURE__*/memo(({
166
166
  }],
167
167
  children: event.requestClient === "graphql" ? "GQL" : event.requestClient === "grpc-web" ? "gRPC" : event.requestClient === "xhr" ? "XHR" : event.requestClient
168
168
  })
169
- })]
169
+ }) : null]
170
170
  }), /*#__PURE__*/_jsxs(View, {
171
171
  style: styles.leftSection,
172
172
  children: [/*#__PURE__*/_jsx(MethodBadge, {
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+
3
+ import { createContext, useContext } from "react";
4
+
5
+ /**
6
+ * The full request/response bodies for a single network event, fetched on
7
+ * demand. Either field may be `null` when the event genuinely has no body.
8
+ */
9
+
10
+ /**
11
+ * Resolves request/response bodies that were omitted from a synced snapshot.
12
+ *
13
+ * Large bodies (see `networkSyncAdapter`) are stripped from the list snapshot
14
+ * so they aren't re-streamed every ~200ms; the detail view fetches them lazily
15
+ * through this resolver when a request is opened.
16
+ *
17
+ * On the device this is never provided — bodies are always inline — so the
18
+ * detail view simply falls back to the inline data. The desktop dashboard
19
+ * provides a resolver that fetches the body over the devtool protocol.
20
+ */
21
+ import { jsx as _jsx } from "react/jsx-runtime";
22
+ const NetworkBodyResolverContext = /*#__PURE__*/createContext(null);
23
+ export function NetworkBodyResolverProvider({
24
+ resolver,
25
+ children
26
+ }) {
27
+ return /*#__PURE__*/_jsx(NetworkBodyResolverContext.Provider, {
28
+ value: resolver,
29
+ children: children
30
+ });
31
+ }
32
+ export function useNetworkBodyResolver() {
33
+ return useContext(NetworkBodyResolverContext);
34
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+
3
+ import { networkEventStore } from "../utils/networkEventStore";
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.
13
+ */
14
+ export const SNAPSHOT_BODY_INLINE_LIMIT = 16 * 1024;
15
+ const stripLargeBodies = event => {
16
+ const stripRequest = event.requestData !== undefined && (event.requestSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
17
+ const stripResponse = event.responseData !== undefined && (event.responseSize ?? 0) > SNAPSHOT_BODY_INLINE_LIMIT;
18
+ if (!stripRequest && !stripResponse) return event;
19
+ // Keep sizes/headers/metadata; only drop the heavy data. `undefined` keys are
20
+ // omitted by JSON serialization, so this shrinks the wire payload, and the
21
+ // dashboard treats "data missing but size > 0" as "fetch on demand".
22
+ const stripped = {
23
+ ...event
24
+ };
25
+ if (stripRequest) stripped.requestData = undefined;
26
+ if (stripResponse) stripped.responseData = undefined;
27
+ return stripped;
28
+ };
29
+
30
+ /**
31
+ * Sync adapter for the network tool, consumed by @buoy-gg/external-sync's
32
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
33
+ * this package doesn't need a dependency on it).
34
+ *
35
+ * Subscribing starts the network interceptor, so requests are only captured
36
+ * while a dashboard is watching the network panel.
37
+ *
38
+ * v2: large bodies are stripped from the snapshot and fetched lazily via
39
+ * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
40
+ */
41
+ export const networkSyncAdapter = {
42
+ version: 2,
43
+ getSnapshot: () => networkEventStore.getEvents().map(stripLargeBodies),
44
+ subscribe: onChange => networkEventStore.subscribeToEvents(onChange),
45
+ actions: {
46
+ clearEvents: () => {
47
+ networkEventStore.clearEvents();
48
+ },
49
+ /**
50
+ * Return the full (un-stripped) request/response bodies for one event,
51
+ * fetched on demand by the dashboard when its detail view opens. Returns
52
+ * null if the event is no longer in the store.
53
+ */
54
+ getEventBody: params => {
55
+ const id = params && typeof params === "object" ? params.id : undefined;
56
+ if (!id) return null;
57
+ const event = networkEventStore.getEventById(id);
58
+ if (!event) return null;
59
+ return {
60
+ requestData: event.requestData ?? null,
61
+ responseData: event.responseData ?? null
62
+ };
63
+ }
64
+ }
65
+ };
@@ -15,6 +15,8 @@ export { useNetworkEvents } from "./network/hooks/useNetworkEvents";
15
15
  export { TickProvider, useTickEveryMinute } from "./network/hooks/useTickEveryMinute";
16
16
  export { formatBytes, formatDuration, formatHttpStatus, } from "./network/utils/formatting";
17
17
  export type { NetworkEvent, NetworkStats, NetworkFilter, NetworkEventStatus, NetworkInsight, } from "./network/types";
18
+ export { networkSyncAdapter } from "./network/sync/networkSyncAdapter";
19
+ export { NetworkBodyResolverProvider, type NetworkBodyResolver, type ResolvedNetworkBody, } from "./network/sync/networkBodyResolver";
18
20
  /** @internal */
19
21
  export { networkEventStore } from "./network/utils/networkEventStore";
20
22
  /** @internal */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AAKvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAKtF,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AAKpC,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAOzB,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,gBAAgB;AAChB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,6CAA6C,CAAC;AACrF,OAAO,EAAE,uBAAuB,EAAE,MAAM,8CAA8C,CAAC;AAKvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAKtF,OAAO,EACL,WAAW,EACX,cAAc,EACd,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AAKpC,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EACL,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,GACzB,MAAM,oCAAoC,CAAC;AAO5C,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,gBAAgB;AAChB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkEventDetailView.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAU7C,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;CAC7C;AAqKD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,eAA2B,EAC3B,eAA0B,GAC3B,EAAE,2BAA2B,+BAkZ7B"}
1
+ {"version":3,"file":"NetworkEventDetailView.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkEventDetailView.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAc7C,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;CAC7C;AAqKD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,KAAK,EACL,eAA2B,EAC3B,eAA0B,GAC3B,EAAE,2BAA2B,+BAmd7B"}
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from "react";
2
+ /**
3
+ * The full request/response bodies for a single network event, fetched on
4
+ * demand. Either field may be `null` when the event genuinely has no body.
5
+ */
6
+ export interface ResolvedNetworkBody {
7
+ requestData?: unknown;
8
+ responseData?: unknown;
9
+ }
10
+ /**
11
+ * Resolves request/response bodies that were omitted from a synced snapshot.
12
+ *
13
+ * Large bodies (see `networkSyncAdapter`) are stripped from the list snapshot
14
+ * so they aren't re-streamed every ~200ms; the detail view fetches them lazily
15
+ * through this resolver when a request is opened.
16
+ *
17
+ * On the device this is never provided — bodies are always inline — so the
18
+ * detail view simply falls back to the inline data. The desktop dashboard
19
+ * provides a resolver that fetches the body over the devtool protocol.
20
+ */
21
+ export type NetworkBodyResolver = (id: string) => Promise<ResolvedNetworkBody | null>;
22
+ export declare function NetworkBodyResolverProvider({ resolver, children, }: {
23
+ resolver: NetworkBodyResolver | null;
24
+ children: ReactNode;
25
+ }): import("react").JSX.Element;
26
+ export declare function useNetworkBodyResolver(): NetworkBodyResolver | null;
27
+ //# sourceMappingURL=networkBodyResolver.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,41 @@
1
+ import type { NetworkEvent } from "../types";
2
+ /**
3
+ * Request/response bodies larger than this (using the cached size proxy already
4
+ * on the event — no per-snapshot stringify) are NOT streamed in the list
5
+ * snapshot. These are the multi-MB payloads (images, large JSON list responses)
6
+ * that, re-sent every ~200ms for up to 500 events, freeze the desktop
7
+ * dashboard's main thread while it deserializes and re-renders them. Smaller
8
+ * bodies stay inline so the common case needs no round-trip (instant detail
9
+ * view, and they remain available to copy/export). Stripped bodies are fetched
10
+ * on demand via the `getEventBody` action when a request's detail view opens.
11
+ */
12
+ export declare const SNAPSHOT_BODY_INLINE_LIMIT: number;
13
+ /**
14
+ * Sync adapter for the network tool, consumed by @buoy-gg/external-sync's
15
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
16
+ * this package doesn't need a dependency on it).
17
+ *
18
+ * Subscribing starts the network interceptor, so requests are only captured
19
+ * while a dashboard is watching the network panel.
20
+ *
21
+ * v2: large bodies are stripped from the snapshot and fetched lazily via
22
+ * `getEventBody` (see SNAPSHOT_BODY_INLINE_LIMIT).
23
+ */
24
+ export declare const networkSyncAdapter: {
25
+ version: number;
26
+ getSnapshot: () => NetworkEvent[];
27
+ subscribe: (onChange: () => void) => () => void;
28
+ actions: {
29
+ clearEvents: () => void;
30
+ /**
31
+ * Return the full (un-stripped) request/response bodies for one event,
32
+ * fetched on demand by the dashboard when its detail view opens. Returns
33
+ * null if the event is no longer in the store.
34
+ */
35
+ getEventBody: (params: unknown) => {
36
+ requestData: {} | null;
37
+ responseData: {} | null;
38
+ } | null;
39
+ };
40
+ };
41
+ //# sourceMappingURL=networkSyncAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"networkSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/network/sync/networkSyncAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,QAAY,CAAC;AAmBpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB;;;0BAGP,MAAM,IAAI;;;QAM9B;;;;WAIG;+BACoB,OAAO;;;;;CAcjC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/network",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "network package",
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": "3.0.1"
29
+ "@buoy-gg/shared-ui": "3.0.2"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": "*",