@buoy-gg/network 3.0.1 → 4.0.1

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 () {
@@ -57,6 +63,18 @@ Object.defineProperty(exports, "formatHttpStatus", {
57
63
  return _formatting.formatHttpStatus;
58
64
  }
59
65
  });
66
+ Object.defineProperty(exports, "ignoredPatternsStore", {
67
+ enumerable: true,
68
+ get: function () {
69
+ return _sharedUi.ignoredPatternsStore;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "isUrlIgnored", {
73
+ enumerable: true,
74
+ get: function () {
75
+ return _sharedUi.isUrlIgnored;
76
+ }
77
+ });
60
78
  Object.defineProperty(exports, "networkEventStore", {
61
79
  enumerable: true,
62
80
  get: function () {
@@ -69,6 +87,12 @@ Object.defineProperty(exports, "networkListener", {
69
87
  return _networkListener.networkListener;
70
88
  }
71
89
  });
90
+ Object.defineProperty(exports, "networkSyncAdapter", {
91
+ enumerable: true,
92
+ get: function () {
93
+ return _networkSyncAdapter.networkSyncAdapter;
94
+ }
95
+ });
72
96
  Object.defineProperty(exports, "networkToolPreset", {
73
97
  enumerable: true,
74
98
  get: function () {
@@ -81,6 +105,18 @@ Object.defineProperty(exports, "removeAllNetworkListeners", {
81
105
  return _networkListener.removeAllNetworkListeners;
82
106
  }
83
107
  });
108
+ Object.defineProperty(exports, "urlMatchesIgnoredPattern", {
109
+ enumerable: true,
110
+ get: function () {
111
+ return _sharedUi.urlMatchesIgnoredPattern;
112
+ }
113
+ });
114
+ Object.defineProperty(exports, "useIgnoredPatterns", {
115
+ enumerable: true,
116
+ get: function () {
117
+ return _sharedUi.useIgnoredPatterns;
118
+ }
119
+ });
84
120
  Object.defineProperty(exports, "useNetworkEvents", {
85
121
  enumerable: true,
86
122
  get: function () {
@@ -100,5 +136,8 @@ var _NetworkEventItemCompact = require("./network/components/NetworkEventItemCom
100
136
  var _useNetworkEvents = require("./network/hooks/useNetworkEvents");
101
137
  var _useTickEveryMinute = require("./network/hooks/useTickEveryMinute");
102
138
  var _formatting = require("./network/utils/formatting");
139
+ var _sharedUi = require("@buoy-gg/shared-ui");
140
+ var _networkSyncAdapter = require("./network/sync/networkSyncAdapter");
141
+ var _networkBodyResolver = require("./network/sync/networkBodyResolver");
103
142
  var _networkEventStore = require("./network/utils/networkEventStore");
104
143
  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, {
@@ -15,33 +15,6 @@ var _NetworkCopySettingsView = require("./NetworkCopySettingsView");
15
15
  var _useNetworkEvents = require("../hooks/useNetworkEvents");
16
16
  var _formatting = require("../utils/formatting");
17
17
  var _jsxRuntime = require("react/jsx-runtime");
18
- /**
19
- * Returns true when `url` matches the ignored `pattern` according to its mode.
20
- *
21
- * `contains` → case-insensitive substring on the full URL (legacy behavior).
22
- * `exact` → smart equality: pattern starting with `/` compares URL.pathname,
23
- * full URLs compare origin+pathname (ignoring query/hash), bare
24
- * values compare URL.host. Falls back to literal equality if URL
25
- * parsing fails (e.g. relative URLs).
26
- */function urlMatchesIgnoredPattern(url, pattern) {
27
- const lowerUrl = url.toLowerCase();
28
- const lowerValue = pattern.value.toLowerCase();
29
- if (pattern.mode === "contains") {
30
- return lowerUrl.includes(lowerValue);
31
- }
32
- try {
33
- const parsed = new URL(url);
34
- if (lowerValue.startsWith("/")) {
35
- return parsed.pathname.toLowerCase() === lowerValue;
36
- }
37
- if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
38
- return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
39
- }
40
- return parsed.host.toLowerCase() === lowerValue;
41
- } catch {
42
- return lowerUrl === lowerValue;
43
- }
44
- }
45
18
  // Decompose by Responsibility: Extract empty state component
46
19
  function EmptyState({
47
20
  isEnabled
@@ -104,15 +77,20 @@ function NetworkModalInner({
104
77
  const [searchText, setSearchText] = (0, _react.useState)("");
105
78
  const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
106
79
  const searchInputRef = (0, _react.useRef)(null);
107
- const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)([
108
- // Auto-hide Buoy license API requests by default
109
- {
110
- value: "api.keygen.sh",
111
- mode: "contains"
112
- }]);
80
+ // Ignored domains/URL patterns now live in a shared, persisted singleton store
81
+ // (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync and
82
+ // share the exact same filter logic. The four handlers below are thin wrappers
83
+ // exposed by the hook; `values` is the derived Set used for presence checks.
84
+ const {
85
+ patterns: ignoredPatterns,
86
+ values: ignoredPatternValues,
87
+ add: addIgnoredPattern,
88
+ remove: removeIgnoredPattern,
89
+ toggle: toggleIgnoredPattern,
90
+ toggleMode: toggleIgnoredPatternMode
91
+ } = (0, _sharedUi.useIgnoredPatterns)();
113
92
  const [copySettings, setCopySettings] = (0, _react.useState)(_NetworkCopySettingsView.DEFAULT_COPY_SETTINGS);
114
93
  const flatListRef = (0, _react.useRef)(null);
115
- const hasLoadedFilters = (0, _react.useRef)(false);
116
94
  const hasLoadedCopySettings = (0, _react.useRef)(false);
117
95
  const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
118
96
 
@@ -123,62 +101,6 @@ function NetworkModalInner({
123
101
  }
124
102
  }, [showUpgradeModal, isPro]);
125
103
 
126
- // Load persisted filters on mount
127
- (0, _react.useEffect)(() => {
128
- if (!visible || hasLoadedFilters.current) return;
129
- const loadFilters = async () => {
130
- try {
131
- const storedPatterns = await _sharedUi.persistentStorage.getItem(_sharedUi.devToolsStorageKeys.network.ignoredDomains());
132
- if (storedPatterns) {
133
- // Tolerate legacy string[] format — entries without a mode default to "contains".
134
- const raw = JSON.parse(storedPatterns);
135
- const migrated = [];
136
- for (const entry of raw) {
137
- if (typeof entry === "string" && entry.trim()) {
138
- migrated.push({
139
- value: entry,
140
- mode: "contains"
141
- });
142
- } else if (entry && typeof entry === "object" && typeof entry.value === "string") {
143
- const e = entry;
144
- migrated.push({
145
- value: e.value,
146
- mode: e.mode === "exact" ? "exact" : "contains"
147
- });
148
- }
149
- }
150
- // Always ensure Buoy license API is hidden
151
- if (!migrated.some(p => p.value === "api.keygen.sh")) {
152
- migrated.push({
153
- value: "api.keygen.sh",
154
- mode: "contains"
155
- });
156
- }
157
- setIgnoredPatterns(migrated);
158
- }
159
- } catch (_error) {
160
- // Silently fail - filters will use defaults
161
- } finally {
162
- hasLoadedFilters.current = true;
163
- }
164
- };
165
- loadFilters();
166
- }, [visible]);
167
-
168
- // Save filters when they change
169
- (0, _react.useEffect)(() => {
170
- if (!hasLoadedFilters.current) return; // Don't save on initial load
171
-
172
- const saveFilters = async () => {
173
- try {
174
- await _sharedUi.persistentStorage.setItem(_sharedUi.devToolsStorageKeys.network.ignoredDomains(), JSON.stringify(ignoredPatterns));
175
- } catch (_error) {
176
- // Silently fail - filters will remain in memory
177
- }
178
- };
179
- saveFilters();
180
- }, [ignoredPatterns]);
181
-
182
104
  // Load persisted copy settings on mount
183
105
  (0, _react.useEffect)(() => {
184
106
  if (!visible || hasLoadedCopySettings.current) return;
@@ -234,49 +156,15 @@ function NetworkModalInner({
234
156
  }
235
157
  }, [isSearchActive]);
236
158
 
237
- // Derived set of pattern values (for components that only care about presence).
238
- const ignoredPatternValues = (0, _react.useMemo)(() => new Set(ignoredPatterns.map(p => p.value)), [ignoredPatterns]);
239
-
240
159
  // Filter events based on ignored patterns (mode-aware)
241
160
  const filteredEvents = (0, _react.useMemo)(() => {
242
161
  if (ignoredPatterns.length === 0) return events;
243
162
  return events.filter(event => {
244
- const isFiltered = ignoredPatterns.some(pattern => urlMatchesIgnoredPattern(event.url, pattern));
163
+ const isFiltered = ignoredPatterns.some(pattern => (0, _sharedUi.urlMatchesIgnoredPattern)(event.url, pattern));
245
164
  return !isFiltered;
246
165
  });
247
166
  }, [events, ignoredPatterns]);
248
167
 
249
- // Add or no-op (used by add-pattern UI and suggested-items taps)
250
- const addIgnoredPattern = (0, _react.useCallback)(pattern => {
251
- const value = pattern.value.trim();
252
- if (!value) return;
253
- setIgnoredPatterns(prev => prev.some(p => p.value === value) ? prev : [...prev, {
254
- value,
255
- mode: pattern.mode
256
- }]);
257
- }, []);
258
-
259
- // Remove by value (used by the filter list X button and the detail view chips)
260
- const removeIgnoredPattern = (0, _react.useCallback)(value => {
261
- setIgnoredPatterns(prev => prev.filter(p => p.value !== value));
262
- }, []);
263
-
264
- // Toggle presence (used by the detail-view domain/url chips — adds as contains)
265
- const toggleIgnoredPattern = (0, _react.useCallback)(value => {
266
- setIgnoredPatterns(prev => prev.some(p => p.value === value) ? prev.filter(p => p.value !== value) : [...prev, {
267
- value,
268
- mode: "contains"
269
- }]);
270
- }, []);
271
-
272
- // Flip an existing pattern's mode between contains <-> exact
273
- const toggleIgnoredPatternMode = (0, _react.useCallback)(value => {
274
- setIgnoredPatterns(prev => prev.map(p => p.value === value ? {
275
- ...p,
276
- mode: p.mode === "contains" ? "exact" : "contains"
277
- } : p));
278
- }, []);
279
-
280
168
  // Helper to check if payload should be included based on size
281
169
  const shouldIncludePayload = (0, _react.useCallback)(data => {
282
170
  if (copySettings.bodySizeThreshold === -1) return true;
@@ -417,7 +305,7 @@ function NetworkModalInner({
417
305
  let successful = 0;
418
306
  let failed = 0;
419
307
  let pending = 0;
420
- const eventsForStats = ignoredPatterns.length === 0 ? allEvents : allEvents.filter(event => !ignoredPatterns.some(pattern => urlMatchesIgnoredPattern(event.url, pattern)));
308
+ const eventsForStats = ignoredPatterns.length === 0 ? allEvents : allEvents.filter(event => !ignoredPatterns.some(pattern => (0, _sharedUi.urlMatchesIgnoredPattern)(event.url, pattern)));
421
309
  for (const event of eventsForStats) {
422
310
  if (event.status && event.status >= 200 && event.status < 300) {
423
311
  successful++;
@@ -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,20 @@ export { formatBytes, formatDuration, formatHttpStatus } from "./network/utils/f
37
37
  // TYPES (For TypeScript users)
38
38
  // =============================================================================
39
39
 
40
+ // =============================================================================
41
+ // SHARED IGNORED-PATTERN FILTERING
42
+ // Re-exported from @buoy-gg/shared-ui so consumers wiring the Network detail
43
+ // page into other tools (e.g. the Events tool) can share the exact same
44
+ // ignored-domains/URL filter store + matching logic.
45
+ // =============================================================================
46
+ export { useIgnoredPatterns, ignoredPatternsStore, urlMatchesIgnoredPattern, isUrlIgnored } from "@buoy-gg/shared-ui";
47
+
48
+ // =============================================================================
49
+ // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
50
+ // =============================================================================
51
+ export { networkSyncAdapter } from "./network/sync/networkSyncAdapter";
52
+ export { NetworkBodyResolverProvider } from "./network/sync/networkBodyResolver";
53
+
40
54
  // =============================================================================
41
55
  // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
42
56
  // 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, {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useRef, useMemo, useCallback, useEffect } from "react";
4
4
  import { View, Text, StyleSheet, TouchableOpacity, TextInput, FlatList } from "react-native";
5
- import { Globe, Trash2, Power, Search, Filter, CheckCircle, XCircle, Clock, X, Copy, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage, CopyButton, TabSelector, useFeatureGate, UpgradeModal, ProBadge, Zap, PowerToggleButton } from "@buoy-gg/shared-ui";
5
+ import { Globe, Trash2, Power, Search, Filter, CheckCircle, XCircle, Clock, X, Copy, JsModal, ModalHeader, devToolsStorageKeys, macOSColors, persistentStorage, CopyButton, TabSelector, useFeatureGate, UpgradeModal, ProBadge, Zap, PowerToggleButton, useIgnoredPatterns, urlMatchesIgnoredPattern } from "@buoy-gg/shared-ui";
6
6
  import { NetworkEventItemCompact } from "./NetworkEventItemCompact";
7
7
  import { NetworkFilterViewV3 } from "./NetworkFilterViewV3";
8
8
  import { TickProvider } from "../hooks/useTickEveryMinute";
@@ -10,36 +10,7 @@ import { NetworkEventDetailView } from "./NetworkEventDetailView";
10
10
  import { NetworkCopySettingsView, DEFAULT_COPY_SETTINGS } from "./NetworkCopySettingsView";
11
11
  import { useNetworkEvents } from "../hooks/useNetworkEvents";
12
12
  import { formatBytes } from "../utils/formatting";
13
-
14
- /**
15
- * Returns true when `url` matches the ignored `pattern` according to its mode.
16
- *
17
- * `contains` → case-insensitive substring on the full URL (legacy behavior).
18
- * `exact` → smart equality: pattern starting with `/` compares URL.pathname,
19
- * full URLs compare origin+pathname (ignoring query/hash), bare
20
- * values compare URL.host. Falls back to literal equality if URL
21
- * parsing fails (e.g. relative URLs).
22
- */
23
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
24
- function urlMatchesIgnoredPattern(url, pattern) {
25
- const lowerUrl = url.toLowerCase();
26
- const lowerValue = pattern.value.toLowerCase();
27
- if (pattern.mode === "contains") {
28
- return lowerUrl.includes(lowerValue);
29
- }
30
- try {
31
- const parsed = new URL(url);
32
- if (lowerValue.startsWith("/")) {
33
- return parsed.pathname.toLowerCase() === lowerValue;
34
- }
35
- if (lowerValue.startsWith("http://") || lowerValue.startsWith("https://")) {
36
- return `${parsed.origin}${parsed.pathname}`.toLowerCase() === lowerValue;
37
- }
38
- return parsed.host.toLowerCase() === lowerValue;
39
- } catch {
40
- return lowerUrl === lowerValue;
41
- }
42
- }
43
14
  // Decompose by Responsibility: Extract empty state component
44
15
  function EmptyState({
45
16
  isEnabled
@@ -102,15 +73,20 @@ function NetworkModalInner({
102
73
  const [searchText, setSearchText] = useState("");
103
74
  const [isSearchActive, setIsSearchActive] = useState(false);
104
75
  const searchInputRef = useRef(null);
105
- const [ignoredPatterns, setIgnoredPatterns] = useState([
106
- // Auto-hide Buoy license API requests by default
107
- {
108
- value: "api.keygen.sh",
109
- mode: "contains"
110
- }]);
76
+ // Ignored domains/URL patterns now live in a shared, persisted singleton store
77
+ // (@buoy-gg/shared-ui) so the Network tool and the Events tool stay in sync and
78
+ // share the exact same filter logic. The four handlers below are thin wrappers
79
+ // exposed by the hook; `values` is the derived Set used for presence checks.
80
+ const {
81
+ patterns: ignoredPatterns,
82
+ values: ignoredPatternValues,
83
+ add: addIgnoredPattern,
84
+ remove: removeIgnoredPattern,
85
+ toggle: toggleIgnoredPattern,
86
+ toggleMode: toggleIgnoredPatternMode
87
+ } = useIgnoredPatterns();
111
88
  const [copySettings, setCopySettings] = useState(DEFAULT_COPY_SETTINGS);
112
89
  const flatListRef = useRef(null);
113
- const hasLoadedFilters = useRef(false);
114
90
  const hasLoadedCopySettings = useRef(false);
115
91
  const [showUpgradeModal, setShowUpgradeModal] = useState(false);
116
92
 
@@ -121,62 +97,6 @@ function NetworkModalInner({
121
97
  }
122
98
  }, [showUpgradeModal, isPro]);
123
99
 
124
- // Load persisted filters on mount
125
- useEffect(() => {
126
- if (!visible || hasLoadedFilters.current) return;
127
- const loadFilters = async () => {
128
- try {
129
- const storedPatterns = await persistentStorage.getItem(devToolsStorageKeys.network.ignoredDomains());
130
- if (storedPatterns) {
131
- // Tolerate legacy string[] format — entries without a mode default to "contains".
132
- const raw = JSON.parse(storedPatterns);
133
- const migrated = [];
134
- for (const entry of raw) {
135
- if (typeof entry === "string" && entry.trim()) {
136
- migrated.push({
137
- value: entry,
138
- mode: "contains"
139
- });
140
- } else if (entry && typeof entry === "object" && typeof entry.value === "string") {
141
- const e = entry;
142
- migrated.push({
143
- value: e.value,
144
- mode: e.mode === "exact" ? "exact" : "contains"
145
- });
146
- }
147
- }
148
- // Always ensure Buoy license API is hidden
149
- if (!migrated.some(p => p.value === "api.keygen.sh")) {
150
- migrated.push({
151
- value: "api.keygen.sh",
152
- mode: "contains"
153
- });
154
- }
155
- setIgnoredPatterns(migrated);
156
- }
157
- } catch (_error) {
158
- // Silently fail - filters will use defaults
159
- } finally {
160
- hasLoadedFilters.current = true;
161
- }
162
- };
163
- loadFilters();
164
- }, [visible]);
165
-
166
- // Save filters when they change
167
- useEffect(() => {
168
- if (!hasLoadedFilters.current) return; // Don't save on initial load
169
-
170
- const saveFilters = async () => {
171
- try {
172
- await persistentStorage.setItem(devToolsStorageKeys.network.ignoredDomains(), JSON.stringify(ignoredPatterns));
173
- } catch (_error) {
174
- // Silently fail - filters will remain in memory
175
- }
176
- };
177
- saveFilters();
178
- }, [ignoredPatterns]);
179
-
180
100
  // Load persisted copy settings on mount
181
101
  useEffect(() => {
182
102
  if (!visible || hasLoadedCopySettings.current) return;
@@ -232,9 +152,6 @@ function NetworkModalInner({
232
152
  }
233
153
  }, [isSearchActive]);
234
154
 
235
- // Derived set of pattern values (for components that only care about presence).
236
- const ignoredPatternValues = useMemo(() => new Set(ignoredPatterns.map(p => p.value)), [ignoredPatterns]);
237
-
238
155
  // Filter events based on ignored patterns (mode-aware)
239
156
  const filteredEvents = useMemo(() => {
240
157
  if (ignoredPatterns.length === 0) return events;
@@ -244,37 +161,6 @@ function NetworkModalInner({
244
161
  });
245
162
  }, [events, ignoredPatterns]);
246
163
 
247
- // Add or no-op (used by add-pattern UI and suggested-items taps)
248
- const addIgnoredPattern = useCallback(pattern => {
249
- const value = pattern.value.trim();
250
- if (!value) return;
251
- setIgnoredPatterns(prev => prev.some(p => p.value === value) ? prev : [...prev, {
252
- value,
253
- mode: pattern.mode
254
- }]);
255
- }, []);
256
-
257
- // Remove by value (used by the filter list X button and the detail view chips)
258
- const removeIgnoredPattern = useCallback(value => {
259
- setIgnoredPatterns(prev => prev.filter(p => p.value !== value));
260
- }, []);
261
-
262
- // Toggle presence (used by the detail-view domain/url chips — adds as contains)
263
- const toggleIgnoredPattern = useCallback(value => {
264
- setIgnoredPatterns(prev => prev.some(p => p.value === value) ? prev.filter(p => p.value !== value) : [...prev, {
265
- value,
266
- mode: "contains"
267
- }]);
268
- }, []);
269
-
270
- // Flip an existing pattern's mode between contains <-> exact
271
- const toggleIgnoredPatternMode = useCallback(value => {
272
- setIgnoredPatterns(prev => prev.map(p => p.value === value ? {
273
- ...p,
274
- mode: p.mode === "contains" ? "exact" : "contains"
275
- } : p));
276
- }, []);
277
-
278
164
  // Helper to check if payload should be included based on size
279
165
  const shouldIncludePayload = useCallback(data => {
280
166
  if (copySettings.bodySizeThreshold === -1) return true;
@@ -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
+ };
@@ -14,7 +14,10 @@ export { NetworkEventItemCompact } from "./network/components/NetworkEventItemCo
14
14
  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
- export type { NetworkEvent, NetworkStats, NetworkFilter, NetworkEventStatus, NetworkInsight, } from "./network/types";
17
+ export type { NetworkEvent, NetworkStats, NetworkFilter, NetworkEventStatus, NetworkInsight, IgnoredPattern, IgnoredPatternMatchMode, } from "./network/types";
18
+ export { useIgnoredPatterns, ignoredPatternsStore, urlMatchesIgnoredPattern, isUrlIgnored, } from "@buoy-gg/shared-ui";
19
+ export { networkSyncAdapter } from "./network/sync/networkSyncAdapter";
20
+ export { NetworkBodyResolverProvider, type NetworkBodyResolver, type ResolvedNetworkBody, } from "./network/sync/networkBodyResolver";
18
21
  /** @internal */
19
22
  export { networkEventStore } from "./network/utils/networkEventStore";
20
23
  /** @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,EACd,cAAc,EACd,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAK5B,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"AA2EA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAslCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD"}
1
+ {"version":3,"file":"NetworkModal.d.ts","sourceRoot":"","sources":["../../../../src/network/components/NetworkModal.tsx"],"names":[],"mappings":"AA8CA,UAAU,iBAAiB;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAu/BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,+BAMpD"}
@@ -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"}
@@ -76,19 +76,11 @@ export interface NetworkFilter {
76
76
  /** Human readable status classifications derived from request/response metadata. */
77
77
  export type NetworkEventStatus = "pending" | "success" | "error" | "timeout";
78
78
  /**
79
- * How an ignored-pattern entry should be compared against captured URLs.
80
- *
81
- * - `contains`: case-insensitive substring match on the full URL (legacy behavior).
82
- * - `exact`: smart equality — if pattern starts with `/`, matches URL.pathname;
83
- * if it starts with `http://`/`https://`, matches origin+pathname; otherwise
84
- * matches URL.host.
79
+ * Ignored-pattern types now live in @buoy-gg/shared-ui so the Network and Events
80
+ * tools share one canonical definition + filter store. Re-exported here for
81
+ * backwards compatibility with existing `../types` imports.
85
82
  */
86
- export type IgnoredPatternMatchMode = "contains" | "exact";
87
- /** A single network-events exclude pattern with its match mode. */
88
- export interface IgnoredPattern {
89
- value: string;
90
- mode: IgnoredPatternMatchMode;
91
- }
83
+ export type { IgnoredPattern, IgnoredPatternMatchMode, } from "@buoy-gg/shared-ui";
92
84
  /**
93
85
  * Insight surfaces highlight notable traffic patterns or issues for the current session.
94
86
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/network/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EACF,KAAK,GACL,MAAM,GACN,KAAK,GACL,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IACnE;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE7E;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,OAAO,CAAC;AAE3D,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,CAAC;IAC5D,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/network/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EACF,KAAK,GACL,MAAM,GACN,KAAK,GACL,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IACnE;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,oFAAoF;AACpF,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAE7E;;;;GAIG;AACH,YAAY,EACV,cAAc,EACd,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,CAAC;IAC5D,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/network",
3
- "version": "3.0.1",
3
+ "version": "4.0.1",
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": "4.0.1"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": "*",