@buoy-gg/network 7.0.36 → 7.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +45 -12
  2. package/lib/commonjs/index.js +26 -1
  3. package/lib/commonjs/index.web.js +26 -1
  4. package/lib/commonjs/network/components/NetworkEventDetailView.js +6 -1
  5. package/lib/commonjs/network/components/NetworkHeaderMenu.js +4 -0
  6. package/lib/commonjs/network/components/NetworkModal.js +24 -8
  7. package/lib/commonjs/network/conditions/NetworkConditionsControl.js +235 -0
  8. package/lib/commonjs/network/conditions/NetworkConditionsControl.test.js +136 -0
  9. package/lib/commonjs/network/conditions/NetworkConditionsIcon.js +33 -0
  10. package/lib/commonjs/network/conditions/NetworkFloatingTool.js +310 -0
  11. package/lib/commonjs/network/conditions/NetworkFloatingTool.test.js +219 -0
  12. package/lib/commonjs/network/conditions/store.js +75 -0
  13. package/lib/commonjs/network/conditions/wait.js +43 -0
  14. package/lib/commonjs/network/conditions/xhr.js +110 -0
  15. package/lib/commonjs/network/hooks/useNetworkEvents.js +1 -1
  16. package/lib/commonjs/network/overrides/applyToXhr.js +10 -8
  17. package/lib/commonjs/network/overrides/engine.js +2 -1
  18. package/lib/commonjs/network/sync/networkSyncAdapter.js +26 -1
  19. package/lib/commonjs/network/utils/generateNetworkCopyText.js +2 -0
  20. package/lib/commonjs/network/utils/networkEventStore.js +5 -77
  21. package/lib/commonjs/network/utils/networkListener.js +96 -51
  22. package/lib/commonjs/network/utils/networkSavedStore.js +2 -2
  23. package/lib/commonjs/network/utils/selectSavedEvents.js +2 -2
  24. package/lib/commonjs/preset.js +2 -1
  25. package/lib/module/index.js +2 -1
  26. package/lib/module/index.web.js +2 -1
  27. package/lib/module/network/components/NetworkEventDetailView.js +6 -1
  28. package/lib/module/network/components/NetworkHeaderMenu.js +4 -0
  29. package/lib/module/network/components/NetworkModal.js +25 -9
  30. package/lib/module/network/conditions/NetworkConditionsControl.js +231 -0
  31. package/lib/module/network/conditions/NetworkConditionsControl.test.js +135 -0
  32. package/lib/module/network/conditions/NetworkConditionsIcon.js +29 -0
  33. package/lib/module/network/conditions/NetworkFloatingTool.js +304 -0
  34. package/lib/module/network/conditions/NetworkFloatingTool.test.js +218 -0
  35. package/lib/module/network/conditions/store.js +65 -0
  36. package/lib/module/network/conditions/wait.js +38 -0
  37. package/lib/module/network/conditions/xhr.js +106 -0
  38. package/lib/module/network/hooks/useNetworkEvents.js +1 -1
  39. package/lib/module/network/overrides/applyToXhr.js +9 -8
  40. package/lib/module/network/overrides/engine.js +2 -1
  41. package/lib/module/network/sync/networkSyncAdapter.js +26 -1
  42. package/lib/module/network/utils/generateNetworkCopyText.js +2 -0
  43. package/lib/module/network/utils/networkEventStore.js +6 -77
  44. package/lib/module/network/utils/networkListener.js +97 -52
  45. package/lib/module/network/utils/networkSavedStore.js +2 -2
  46. package/lib/module/network/utils/selectSavedEvents.js +2 -2
  47. package/lib/module/preset.js +3 -2
  48. package/lib/typescript/index.d.ts +2 -0
  49. package/lib/typescript/index.d.ts.map +1 -1
  50. package/lib/typescript/index.web.d.ts +2 -0
  51. package/lib/typescript/index.web.d.ts.map +1 -1
  52. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -1
  53. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts +2 -1
  54. package/lib/typescript/network/components/NetworkHeaderMenu.d.ts.map +1 -1
  55. package/lib/typescript/network/components/NetworkModal.d.ts +3 -0
  56. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -1
  57. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts +12 -0
  58. package/lib/typescript/network/conditions/NetworkConditionsControl.d.ts.map +1 -0
  59. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts +2 -0
  60. package/lib/typescript/network/conditions/NetworkConditionsControl.test.d.ts.map +1 -0
  61. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts +4 -0
  62. package/lib/typescript/network/conditions/NetworkConditionsIcon.d.ts.map +1 -0
  63. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts +15 -0
  64. package/lib/typescript/network/conditions/NetworkFloatingTool.d.ts.map +1 -0
  65. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts +2 -0
  66. package/lib/typescript/network/conditions/NetworkFloatingTool.test.d.ts.map +1 -0
  67. package/lib/typescript/network/conditions/store.d.ts +30 -0
  68. package/lib/typescript/network/conditions/store.d.ts.map +1 -0
  69. package/lib/typescript/network/conditions/wait.d.ts +4 -0
  70. package/lib/typescript/network/conditions/wait.d.ts.map +1 -0
  71. package/lib/typescript/network/conditions/xhr.d.ts +5 -0
  72. package/lib/typescript/network/conditions/xhr.d.ts.map +1 -0
  73. package/lib/typescript/network/hooks/useNetworkEvents.d.ts +1 -1
  74. package/lib/typescript/network/overrides/applyToXhr.d.ts +2 -0
  75. package/lib/typescript/network/overrides/applyToXhr.d.ts.map +1 -1
  76. package/lib/typescript/network/overrides/engine.d.ts +0 -10
  77. package/lib/typescript/network/overrides/engine.d.ts.map +1 -1
  78. package/lib/typescript/network/sync/networkSyncAdapter.d.ts +17 -0
  79. package/lib/typescript/network/sync/networkSyncAdapter.d.ts.map +1 -1
  80. package/lib/typescript/network/types/index.d.ts +1 -0
  81. package/lib/typescript/network/types/index.d.ts.map +1 -1
  82. package/lib/typescript/network/utils/generateNetworkCopyText.d.ts.map +1 -1
  83. package/lib/typescript/network/utils/networkEventStore.d.ts +0 -15
  84. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -1
  85. package/lib/typescript/network/utils/networkListener.d.ts +2 -1
  86. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -1
  87. package/lib/typescript/network/utils/networkSavedStore.d.ts +2 -2
  88. package/lib/typescript/network/utils/selectSavedEvents.d.ts +2 -2
  89. package/lib/typescript/preset.d.ts +0 -16
  90. package/lib/typescript/preset.d.ts.map +1 -1
  91. package/package.json +2 -2
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@buoy-gg/network?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/network) [![npm downloads](https://img.shields.io/npm/dm/@buoy-gg/network?style=flat-square&labelColor=1c1c1c&color=10B981)](https://www.npmjs.com/package/@buoy-gg/network)
4
4
 
5
- **See every HTTP request your React Native app makes — URL, headers, payloads, timing, and errors — live on the device, no Flipper or proxy required.**
5
+ Inspect HTTP requests captured through your React Native app’s global `fetch` and `XMLHttpRequest` hooks. Open a request to see its URL, status, headers, timing, and captured body.
6
6
 
7
- Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside your React Native app. Install it and it auto-appears in the floating menu from [`@buoy-gg/core`](https://www.npmjs.com/package/@buoy-gg/core).
7
+ Part of [Buoy](https://github.com/Buoy-gg/buoy). Install the package, complete account setup, and follow the integration steps below.
8
8
 
9
9
  ![Network Monitor demo](https://github.com/user-attachments/assets/473ddf83-03cd-4bd1-8dc3-0f66eda9fa8a)
10
10
 
@@ -14,9 +14,27 @@ Part of [Buoy](https://github.com/Buoy-gg/buoy) — devtools that live inside yo
14
14
  npm install @buoy-gg/core @buoy-gg/network
15
15
  ```
16
16
 
17
+ ## Before you start
18
+
19
+ Use a development build with `@buoy-gg/core` and a Free or Pro Buoy account key. From your app’s directory, sign in:
20
+
21
+ ```bash
22
+ npx --package=@buoy-gg/core buoy login
23
+ ```
24
+
25
+ For Expo, initialize Buoy before rendering the menu:
26
+
27
+ ```tsx
28
+ import { Buoy } from "@buoy-gg/core";
29
+
30
+ Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
31
+ ```
32
+
33
+ The login command writes the Expo key to `.env.local`. For React Native CLI, pass the key from your app’s environment configuration; React Native does not load `.env.local` automatically. Mount `FloatingDevTools` inside the same providers as your screens and restart the development server after installation. The [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) shows the complete root component setup.
34
+
17
35
  ## Quick start
18
36
 
19
- Fully automatic. Render the floating menu once — the Network Monitor is auto-discovered as soon as this package is installed:
37
+ After initialization, render the menu in your app root:
20
38
 
21
39
  ```tsx
22
40
  import { FloatingDevTools } from "@buoy-gg/core";
@@ -33,30 +51,45 @@ export default function App() {
33
51
 
34
52
  Want a different label or color? Build the tool with `createNetworkTool({ name, colorPreset })` and pass it via `apps={[...]}`.
35
53
 
54
+ ## Check the integration
55
+
56
+ Open Network, trigger a new request in your app, and inspect its URL and status. Requests made before instrumentation and account admission, or through a separate native transport, may not appear. A cache hit may make no request. Saved bodies retain capture limits; saving does not recover truncated data.
57
+
36
58
  ## What you get
37
59
 
38
- - **Every request, zero config** — intercepts `fetch` and `XMLHttpRequest`, so fetch, axios, GraphQL clients, and gRPC-web all show up automatically.
39
- - **Boot requests included** — interception installs at app launch (dev builds), so module-scope fetches, session bootstrap, and your first queries are captured even though they fired before any tool or dashboard was open.
60
+ - **Supported requests** — intercepts global `fetch` and `XMLHttpRequest`. Clients using those paths can be captured; separate native transports and previously saved references may bypass the hooks.
61
+ - **Early capture** — development instrumentation can capture before the panel opens, once its hooks and account admission are ready. It cannot recover earlier requests.
40
62
  - **GraphQL gets special treatment** — operation names are extracted from queries, mutations, and subscriptions and shown with variables in arrow notation: `GetUser › 123`. No more guessing which `/graphql` request is which.
41
- - **Full request detail** — request/response headers, payloads, HTTP status, and duration for every call, with status-colored rows.
63
+ - **Full request detail** — request/response headers, payloads, HTTP status, and duration for captured calls, with status-colored rows.
42
64
  - **Filter and search** — narrow by status code, HTTP method, or URL pattern; search captured events; pause/resume capture; clear history.
43
- - **Pin the one that failed** — pin a request to hold it at the top of the list (ignoring your filters), or save it to a separate favorites list. Both keep a full snapshot, so it survives Clear, the 500-request cap, and an app restart. Long-press a row, or use the buttons in the request detail header.
65
+ - **Pin the one that failed** — pin a request to hold it at the top of the list (ignoring your filters), or save it to a separate favorites list. Both keep the captured snapshot, including any body truncation, so it survives Clear and the 500-request cap. Restart recovery requires a successful storage write. Long-press a row, or use the buttons in the request detail header.
44
66
  - **Step between requests** — the detail view has a Previous/Next footer that walks exactly what the list was showing (same pins, filters, and search — or your Saved list), so comparing two calls doesn't mean going back and finding your place.
45
67
  - **Ignore the noise** — a shared ignored-domains/URL filter (`useIgnoredPatterns`) hides endpoints you never want to see, shared with the Events tool.
46
68
  - **Hooks for custom UIs** — `useNetworkEvents`, `useNetworkEventList`, `useNetworkBadgeCounts`, and `useNetworkSaved` expose the capture stream with narrow per-consumer subscriptions.
47
69
 
48
- ## Desktop & AI
49
70
 
50
- The same live session streams to [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop) (free, macOS/Windows/Linux) and to Claude Code or Cursor via the [Buoy MCP server](https://buoy.gg/buoy/latest/docs/mcp).
71
+ ## Desktop & AI
51
72
 
52
- ## Free vs Pro
73
+ To connect a React Native app to [Buoy Desktop](https://github.com/Buoy-gg/Buoy-Desktop) or MCP, install `@buoy-gg/external-sync` and follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop). Sign in to Desktop separately. [MCP setup](https://buoy.gg/buoy/latest/docs/mcp) also requires a process account and Pro access. Available remote actions depend on the tool and app integration.
53
74
 
54
- Every tool is free. [Pro](https://buoy.gg/pricing) unlocks production builds, the MCP server, and unlimited capture. Every weekend, Pro features unlock free for anyone holding a key — including a free one (`npx buoy login`).
75
+ ## Account and plan requirements
55
76
 
56
- > 🎯 **Flutter (beta):** Buoy is now a full suite for Flutter — `flutter pub add buoy`. Network, storage, console, env, routes, images, Riverpod, events, and a live perf HUD, all streaming to the same desktop dashboard and MCP server. [Details →](https://buoy.gg/flutter)
77
+ Use a Free or Pro Buoy account. History limits and paid features vary by tool; see [pricing](https://buoy.gg/pricing). Production access requires Pro where supported. Development-only hooks and actions remain unavailable in release builds.
57
78
 
58
79
  ---
59
80
 
60
81
  📚 [Full docs](https://buoy.gg/buoy/latest/docs/tools/network) · [All Buoy tools](https://github.com/Buoy-gg/buoy)
61
82
 
62
83
  Proprietary software. © Buoy LLC. [Terms](https://buoy.gg/terms)
84
+
85
+ ## Network conditions
86
+
87
+ In development builds, open **⋯ → Network throttling** to minimize Network and show compact controls over the app. Restore Network from its minimized icon. Tap the signal icon to cycle through No throttling, Slow, Very slow and Offline. Each tap applies the profile immediately and updates the icon and delay. The profile text is a label and can be dragged to move the strip. Close restores Normal and dismisses the controls. Drag the background, delay label or grip to move the strip. Buttons keep their tap actions. In React Native development builds, an open strip returns after reload at its saved position, including when hidden at the edge. Close keeps it closed across reloads. The condition resets to No throttling. Tap the grip to hide or restore it. Free and Pro accounts can use Network conditions after account admission.
88
+
89
+ Offline rejects new HTTP(S) calls through the installed global fetch and React Native XHR hooks without sending them. Slow and Very slow add one delay before dispatch. They do not limit bandwidth or change device connectivity. Imported native transports, images and WebSockets can bypass these hooks.
90
+
91
+ Each call keeps the profile selected when it starts. Offline takes precedence over authored overrides without consuming their counters. Latency adds to an authored delay. A finite XHR timeout under active conditions covers the whole call from `send()`, including artificial waits; Normal preserves native timeout behavior.
92
+
93
+ Conditions remain active when the panel is closed or its list is paused. They reset on a full JS reload or account access loss and are never persisted. Captured requests retain their condition stamp for saved snapshots, detail views and export. Desktop and MCP can control a connected device that advertises the conditions actions.
94
+
95
+ Network conditions are a development preview. The native compatibility matrix across transports, runtimes and devices is still being worked through, so confirm the behaviour you rely on in your own build before you depend on it.
@@ -39,6 +39,12 @@ Object.defineProperty(exports, "MAX_SAVED", {
39
39
  return _networkSavedStore.MAX_SAVED;
40
40
  }
41
41
  });
42
+ Object.defineProperty(exports, "NETWORK_CONDITIONS", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _store.NETWORK_CONDITIONS;
46
+ }
47
+ });
42
48
  Object.defineProperty(exports, "NetworkBodyResolverProvider", {
43
49
  enumerable: true,
44
50
  get: function () {
@@ -159,6 +165,12 @@ Object.defineProperty(exports, "generateCurl", {
159
165
  return _generateCurl.generateCurl;
160
166
  }
161
167
  });
168
+ Object.defineProperty(exports, "getNetworkCondition", {
169
+ enumerable: true,
170
+ get: function () {
171
+ return _store.getNetworkCondition;
172
+ }
173
+ });
162
174
  Object.defineProperty(exports, "ignoredPatternsStore", {
163
175
  enumerable: true,
164
176
  get: function () {
@@ -219,12 +231,24 @@ Object.defineProperty(exports, "removeAllNetworkListeners", {
219
231
  return _networkListener.removeAllNetworkListeners;
220
232
  }
221
233
  });
234
+ Object.defineProperty(exports, "setNetworkCondition", {
235
+ enumerable: true,
236
+ get: function () {
237
+ return _store.setNetworkCondition;
238
+ }
239
+ });
222
240
  Object.defineProperty(exports, "splitPinned", {
223
241
  enumerable: true,
224
242
  get: function () {
225
243
  return _withPinnedFirst.splitPinned;
226
244
  }
227
245
  });
246
+ Object.defineProperty(exports, "subscribeNetworkConditions", {
247
+ enumerable: true,
248
+ get: function () {
249
+ return _store.subscribeNetworkConditions;
250
+ }
251
+ });
228
252
  Object.defineProperty(exports, "urlMatchesIgnoredPattern", {
229
253
  enumerable: true,
230
254
  get: function () {
@@ -297,4 +321,5 @@ var _networkBodyResolver = require("./network/sync/networkBodyResolver");
297
321
  var _networkEventStore = require("./network/utils/networkEventStore");
298
322
  var _networkSavedStore = require("./network/utils/networkSavedStore");
299
323
  var _generateCurl = require("./network/utils/generateCurl");
300
- var _networkListener = require("./network/utils/networkListener");
324
+ var _networkListener = require("./network/utils/networkListener");
325
+ var _store = require("./network/conditions/store");
@@ -39,6 +39,12 @@ Object.defineProperty(exports, "MAX_SAVED", {
39
39
  return _networkSavedStore.MAX_SAVED;
40
40
  }
41
41
  });
42
+ Object.defineProperty(exports, "NETWORK_CONDITIONS", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _store.NETWORK_CONDITIONS;
46
+ }
47
+ });
42
48
  Object.defineProperty(exports, "NetworkClearButton", {
43
49
  enumerable: true,
44
50
  get: function () {
@@ -219,6 +225,12 @@ Object.defineProperty(exports, "generateNetworkCopyText", {
219
225
  return _generateNetworkCopyText.generateNetworkCopyText;
220
226
  }
221
227
  });
228
+ Object.defineProperty(exports, "getNetworkCondition", {
229
+ enumerable: true,
230
+ get: function () {
231
+ return _store.getNetworkCondition;
232
+ }
233
+ });
222
234
  Object.defineProperty(exports, "isNetworkListening", {
223
235
  enumerable: true,
224
236
  get: function () {
@@ -279,6 +291,12 @@ Object.defineProperty(exports, "selectSavedEvents", {
279
291
  return _selectSavedEvents.selectSavedEvents;
280
292
  }
281
293
  });
294
+ Object.defineProperty(exports, "setNetworkCondition", {
295
+ enumerable: true,
296
+ get: function () {
297
+ return _store.setNetworkCondition;
298
+ }
299
+ });
282
300
  Object.defineProperty(exports, "splitPinned", {
283
301
  enumerable: true,
284
302
  get: function () {
@@ -297,6 +315,12 @@ Object.defineProperty(exports, "stopNetworkListener", {
297
315
  return _networkListener.stopNetworkListener;
298
316
  }
299
317
  });
318
+ Object.defineProperty(exports, "subscribeNetworkConditions", {
319
+ enumerable: true,
320
+ get: function () {
321
+ return _store.subscribeNetworkConditions;
322
+ }
323
+ });
300
324
  Object.defineProperty(exports, "toSelectionIds", {
301
325
  enumerable: true,
302
326
  get: function () {
@@ -392,4 +416,5 @@ var _matchRule = require("./network/overrides/matchRule");
392
416
  var _filterNetworkEvents = require("./network/utils/filterNetworkEvents");
393
417
  var _withPinnedFirst = require("./network/utils/withPinnedFirst");
394
418
  var _extractOperationName = require("./network/utils/extractOperationName");
395
- var _generateCurl = require("./network/utils/generateCurl");
419
+ var _generateCurl = require("./network/utils/generateCurl");
420
+ var _store = require("./network/conditions/store");
@@ -447,7 +447,12 @@ ${JSON.stringify(requestDetails.responseData, null, 2)}
447
447
  style: styles.pendingBadgeText,
448
448
  children: "Pending"
449
449
  })]
450
- }) : null, event.duration ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
450
+ }) : null, event.condition && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
451
+ style: {
452
+ color: "#FFCC66"
453
+ },
454
+ children: ["Conditions: ", event.condition.profile, " (+", event.condition.delayMs, " ms)"]
455
+ }), event.duration ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
451
456
  style: styles.httpDuration,
452
457
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
453
458
  size: 10,
@@ -82,11 +82,15 @@ function NetworkHeaderMenuItem({
82
82
  icon,
83
83
  label,
84
84
  detail,
85
+ testID,
85
86
  onPress
86
87
  }) {
87
88
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
88
89
  onPress: onPress,
89
90
  "sentry-label": "ignore header menu item",
91
+ testID: testID,
92
+ accessibilityRole: "button",
93
+ accessibilityLabel: label,
90
94
  style: styles.item,
91
95
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
92
96
  style: styles.itemIcon,
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.NetworkModal = NetworkModal;
7
7
  exports.NetworkToolView = NetworkToolView;
8
+ var _NetworkFloatingTool = require("../conditions/NetworkFloatingTool");
8
9
  var _react = require("react");
9
10
  var _reactNative = require("react-native");
10
11
  var _sharedUi = require("@buoy-gg/shared-ui");
@@ -183,7 +184,8 @@ function NetworkModalInner({
183
184
  enableSharedModalDimensions = false,
184
185
  inline = false,
185
186
  paused: pausedProp,
186
- onPausedChange
187
+ onPausedChange,
188
+ remoteConditions
187
189
  }) {
188
190
  const {
189
191
  isPro
@@ -213,6 +215,12 @@ function NetworkModalInner({
213
215
  const [showSavedView, setShowSavedView] = (0, _react.useState)(false);
214
216
  const [showOverridesView, setShowOverridesView] = (0, _react.useState)(false);
215
217
  const [showHeaderMenu, setShowHeaderMenu] = (0, _react.useState)(false);
218
+ const [minimizeRequest, setMinimizeRequest] = (0, _react.useState)(0);
219
+ const [remoteConditionsSource] = (0, _react.useState)(_NetworkFloatingTool.createRemoteConditionsSource);
220
+ (0, _react.useEffect)(() => {
221
+ remoteConditionsSource.update(remoteConditions);
222
+ }, [remoteConditionsSource, remoteConditions]);
223
+ (0, _react.useEffect)(() => () => remoteConditionsSource.update(undefined), [remoteConditionsSource]);
216
224
  /**
217
225
  * Overrides are a DEVICE capability — a rule runs inside the app's own
218
226
  * `fetch`/`XMLHttpRequest`, which the dashboard doesn't have.
@@ -493,7 +501,7 @@ function NetworkModalInner({
493
501
  }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.PowerToggleButton, {
494
502
  isEnabled: !paused,
495
503
  onToggle: () => setPaused(prev => !prev),
496
- accessibilityLabel: "Toggle network interception",
504
+ accessibilityLabel: "Pause network list",
497
505
  testID: "buoy-network-power"
498
506
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderLive.NetworkClearButton, {
499
507
  paused: paused
@@ -591,6 +599,18 @@ function NetworkModalInner({
591
599
  }), showHeaderMenu ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_NetworkHeaderMenu.NetworkHeaderMenu, {
592
600
  onClose: () => setShowHeaderMenu(false),
593
601
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderMenu.NetworkHeaderMenuItem, {
602
+ icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Wifi, {
603
+ size: 14,
604
+ color: _sharedUi.night.color.textSecondary
605
+ }),
606
+ label: "Network throttling",
607
+ testID: "buoy-network-menu-throttling",
608
+ onPress: () => {
609
+ setShowHeaderMenu(false);
610
+ (0, _NetworkFloatingTool.openNetworkFloatingTool)(remoteConditions ? remoteConditionsSource : undefined);
611
+ if (!inline) setMinimizeRequest(value => value + 1);
612
+ }
613
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkHeaderMenu.NetworkHeaderMenuItem, {
594
614
  icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
595
615
  size: 14,
596
616
  color: hasActiveFilter ? _sharedUi.night.color.accent : _sharedUi.night.color.textSecondary
@@ -629,12 +649,7 @@ function NetworkModalInner({
629
649
  })]
630
650
  }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.UpgradeModal, {
631
651
  visible: showUpgradeModal,
632
- onClose: () => setShowUpgradeModal(false)
633
- // "saved" is a count limit a free key can raise; "overrides" is one it
634
- // can't (1 on both tiers) and "export" is Pro-only — freeKeyRaises
635
- // sorts those out, so the mapping here can stay literal.
636
- ,
637
- limitKey: upgradeReason === "saved" ? "network.saved" : upgradeReason === "overrides" ? "network.overrideRules" : undefined,
652
+ onClose: () => setShowUpgradeModal(false),
638
653
  featureName: upgradeReason === "saved" ? "Saved Requests" : upgradeReason === "overrides" ? "Response Overrides" : "Network Export",
639
654
  description: upgradeReason === "saved" ? "Keep the requests that matter. Saved requests survive Clear, app restarts, and the 500-request history cap." : upgradeReason === "overrides" ? "Force any endpoint to fail, stall or return something else — no backend changes, no code changes. Free keeps one active rule." : "Export network requests to share with your team, debug APIs, or feed into AI tools.",
640
655
  benefits: upgradeReason === "overrides" ? [`Run ${_types.MAX_RULES} rules at once (free keeps ${_types.FREE_TIER_RULE_LIMIT})`, "Force a 500, a 401 or an empty list in one tap", "Simulate offline, timeouts and slow connections", "Rules survive an app reload, so you can test the boot path"] : upgradeReason === "saved" ? [`Save up to ${_networkSavedStore.MAX_SAVED} requests (free keeps ${_networkSavedStore.FREE_TIER_SAVED_LIMIT})`, "Failed requests stay put through a Clear", "Snapshots survive an app restart", "Export saved requests to teammates or AI assistants"] : ["Export to JSON, Markdown, or plain text", "Customizable copy presets (URLs only, LLM-ready, full data)", "Share request details with teammates", "Feed network data directly to AI assistants"]
@@ -657,6 +672,7 @@ function NetworkModalInner({
657
672
  visible: visible,
658
673
  onClose: onClose,
659
674
  onMinimize: onMinimize,
675
+ minimizeRequest: minimizeRequest,
660
676
  persistenceKey: persistenceKey,
661
677
  variant: "night",
662
678
  header: {
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkConditionsControl = NetworkConditionsControl;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _store = require("./store");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ const options = [{
13
+ profile: "normal",
14
+ label: "No throttling"
15
+ }, {
16
+ profile: "slow",
17
+ label: "Slow"
18
+ }, {
19
+ profile: "verySlow",
20
+ label: "Very slow"
21
+ }, {
22
+ profile: "offline",
23
+ label: "Offline"
24
+ }];
25
+ const summaries = {
26
+ normal: "No added delay",
27
+ slow: "+500 ms before each new request",
28
+ verySlow: "+2 seconds before each new request",
29
+ offline: "New requests fail before sending"
30
+ };
31
+ function NetworkConditionsControl({
32
+ remote
33
+ }) {
34
+ const local = (0, _react.useSyncExternalStore)(_store.subscribeNetworkConditions, _store.getNetworkCondition, _store.getNetworkCondition);
35
+ const localAvailable = (0, _react.useSyncExternalStore)(_store.subscribeNetworkConditions, _store.conditionsCanApply, _store.conditionsCanApply);
36
+ const [expanded, setExpanded] = (0, _react.useState)(false);
37
+ const [error, setError] = (0, _react.useState)();
38
+ const [pending, setPending] = (0, _react.useState)(false);
39
+ const busy = (0, _react.useRef)(false);
40
+ const mounted = (0, _react.useRef)(true);
41
+ (0, _react.useEffect)(() => {
42
+ mounted.current = true;
43
+ return () => {
44
+ mounted.current = false;
45
+ };
46
+ }, []);
47
+ const profile = remote ? remote.profile : local;
48
+ const available = remote ? remote.available : localAvailable;
49
+ const active = profile !== null && profile !== "normal";
50
+ const tint = profile === "offline" ? _sharedUi.night.color.danger : active ? _sharedUi.night.color.warning : _sharedUi.night.color.accent;
51
+ async function select(value) {
52
+ if (!available || busy.current) return;
53
+ if (value === profile) {
54
+ setExpanded(false);
55
+ return;
56
+ }
57
+ busy.current = true;
58
+ setPending(true);
59
+ setError(undefined);
60
+ try {
61
+ if (remote) await remote.onChange(value);else (0, _store.setNetworkCondition)(value);
62
+ if (mounted.current) setExpanded(false);
63
+ } catch (failure) {
64
+ if (mounted.current) setError(failure instanceof Error ? failure.message : String(failure));
65
+ } finally {
66
+ busy.current = false;
67
+ if (mounted.current) setPending(false);
68
+ }
69
+ }
70
+ const label = options.find(option => option.profile === profile)?.label ?? "Unavailable";
71
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
72
+ style: styles.container,
73
+ testID: "buoy-network-throttling-screen",
74
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
75
+ style: styles.heading,
76
+ children: "Connection profile"
77
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
78
+ testID: "buoy-network-conditions",
79
+ accessibilityRole: "button",
80
+ accessibilityLabel: `Network throttling: ${label}`,
81
+ accessibilityState: {
82
+ expanded,
83
+ disabled: !available || pending
84
+ },
85
+ disabled: !available || pending,
86
+ onPress: () => setExpanded(value => !value),
87
+ style: [styles.dropdown, expanded && styles.dropdownOpen, !available && styles.disabled],
88
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
89
+ style: [styles.value, active && {
90
+ color: tint
91
+ }],
92
+ children: label
93
+ }), pending ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ActivityIndicator, {
94
+ size: "small",
95
+ color: _sharedUi.night.color.textSecondary
96
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
97
+ size: 16,
98
+ color: _sharedUi.night.color.textSecondary
99
+ })]
100
+ }), expanded && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
101
+ accessibilityRole: "radiogroup",
102
+ accessibilityLabel: "Connection profile",
103
+ style: styles.options,
104
+ children: options.map(option => {
105
+ const selected = profile === option.profile;
106
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
107
+ testID: `buoy-network-condition-${option.profile}`,
108
+ accessibilityRole: "radio",
109
+ accessibilityLabel: option.label,
110
+ accessibilityState: {
111
+ checked: selected,
112
+ disabled: !available || pending
113
+ },
114
+ disabled: !available || pending,
115
+ onPress: () => void select(option.profile),
116
+ style: [styles.option, selected && styles.selected],
117
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
118
+ style: styles.optionBody,
119
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
120
+ style: [styles.optionTitle, selected && {
121
+ color: tint
122
+ }],
123
+ children: option.label
124
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
125
+ style: styles.caption,
126
+ children: summaries[option.profile]
127
+ })]
128
+ }), selected && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Check, {
129
+ size: 16,
130
+ color: tint
131
+ })]
132
+ }, option.profile);
133
+ })
134
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
135
+ style: styles.caption,
136
+ accessibilityLiveRegion: "polite",
137
+ children: pending ? "Applying to device…" : !available ? remote ? "Conditions unavailable on this device" : "Requires a signed-in account and a development build" : profile ? summaries[profile] : "Waiting for device status"
138
+ }), !!error && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
139
+ accessibilityRole: "alert",
140
+ style: styles.error,
141
+ children: error
142
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
143
+ style: styles.help,
144
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
145
+ style: styles.heading,
146
+ children: "How it works"
147
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
148
+ style: styles.caption,
149
+ children: "Affects new HTTP requests through global fetch and XHR. Slow adds a wait before sending; download speed stays unchanged."
150
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
151
+ style: styles.caption,
152
+ children: "Stays active when you close this tool or pause capture. Choose No throttling to stop. Reloading the app resets it."
153
+ })]
154
+ })]
155
+ });
156
+ }
157
+ const styles = _reactNative.StyleSheet.create({
158
+ container: {
159
+ padding: _sharedUi.night.gutter,
160
+ gap: 10
161
+ },
162
+ heading: {
163
+ fontSize: _sharedUi.night.font.caption,
164
+ fontWeight: "600",
165
+ color: _sharedUi.night.color.textSecondary
166
+ },
167
+ dropdown: {
168
+ flexDirection: "row",
169
+ alignItems: "center",
170
+ justifyContent: "space-between",
171
+ gap: 12,
172
+ minHeight: 44,
173
+ paddingHorizontal: 12,
174
+ paddingVertical: 10,
175
+ borderRadius: _sharedUi.night.radius.segment,
176
+ borderWidth: 1,
177
+ borderColor: _sharedUi.night.color.separator,
178
+ backgroundColor: _sharedUi.night.color.surfaceElevated
179
+ },
180
+ dropdownOpen: {
181
+ borderColor: _sharedUi.night.color.accentBorderStrong
182
+ },
183
+ value: {
184
+ flex: 1,
185
+ fontSize: _sharedUi.night.font.body,
186
+ color: _sharedUi.night.color.text
187
+ },
188
+ options: {
189
+ padding: 4,
190
+ borderRadius: _sharedUi.night.radius.row,
191
+ borderWidth: 1,
192
+ borderColor: _sharedUi.night.color.border,
193
+ backgroundColor: _sharedUi.night.color.surface
194
+ },
195
+ option: {
196
+ flexDirection: "row",
197
+ alignItems: "center",
198
+ gap: 12,
199
+ minHeight: 54,
200
+ padding: 10,
201
+ borderRadius: _sharedUi.night.radius.segment
202
+ },
203
+ optionBody: {
204
+ flex: 1,
205
+ gap: 4
206
+ },
207
+ optionTitle: {
208
+ fontSize: _sharedUi.night.font.caption,
209
+ fontWeight: "600",
210
+ color: _sharedUi.night.color.text
211
+ },
212
+ selected: {
213
+ backgroundColor: _sharedUi.night.color.surfaceElevated
214
+ },
215
+ disabled: {
216
+ opacity: 0.45
217
+ },
218
+ caption: {
219
+ fontSize: _sharedUi.night.font.caption,
220
+ lineHeight: 19,
221
+ color: _sharedUi.night.color.textSecondary
222
+ },
223
+ help: {
224
+ marginTop: 14,
225
+ paddingTop: 16,
226
+ gap: 10,
227
+ borderTopWidth: 1,
228
+ borderTopColor: _sharedUi.night.color.separator
229
+ },
230
+ error: {
231
+ fontSize: _sharedUi.night.font.caption,
232
+ lineHeight: 18,
233
+ color: _sharedUi.night.color.danger
234
+ }
235
+ });