@buoy-gg/network 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +381 -0
  2. package/lib/commonjs/index.js +34 -0
  3. package/lib/commonjs/network/components/NetworkCopySettingsView.js +867 -0
  4. package/lib/commonjs/network/components/NetworkEventDetailView.js +837 -0
  5. package/lib/commonjs/network/components/NetworkEventItemCompact.js +323 -0
  6. package/lib/commonjs/network/components/NetworkFilterViewV3.js +297 -0
  7. package/lib/commonjs/network/components/NetworkModal.js +937 -0
  8. package/lib/commonjs/network/hooks/useNetworkEvents.js +320 -0
  9. package/lib/commonjs/network/hooks/useTickEveryMinute.js +34 -0
  10. package/lib/commonjs/network/index.js +102 -0
  11. package/lib/commonjs/network/types/index.js +1 -0
  12. package/lib/commonjs/network/utils/extractOperationName.js +80 -0
  13. package/lib/commonjs/network/utils/formatGraphQLVariables.js +219 -0
  14. package/lib/commonjs/network/utils/formatting.js +30 -0
  15. package/lib/commonjs/network/utils/networkEventStore.js +269 -0
  16. package/lib/commonjs/network/utils/networkListener.js +801 -0
  17. package/lib/commonjs/package.json +1 -0
  18. package/lib/commonjs/preset.js +83 -0
  19. package/lib/module/index.js +7 -0
  20. package/lib/module/network/components/NetworkCopySettingsView.js +862 -0
  21. package/lib/module/network/components/NetworkEventDetailView.js +834 -0
  22. package/lib/module/network/components/NetworkEventItemCompact.js +320 -0
  23. package/lib/module/network/components/NetworkFilterViewV3.js +293 -0
  24. package/lib/module/network/components/NetworkModal.js +933 -0
  25. package/lib/module/network/hooks/useNetworkEvents.js +316 -0
  26. package/lib/module/network/hooks/useTickEveryMinute.js +29 -0
  27. package/lib/module/network/index.js +20 -0
  28. package/lib/module/network/types/index.js +1 -0
  29. package/lib/module/network/utils/extractOperationName.js +76 -0
  30. package/lib/module/network/utils/formatGraphQLVariables.js +213 -0
  31. package/lib/module/network/utils/formatting.js +9 -0
  32. package/lib/module/network/utils/networkEventStore.js +265 -0
  33. package/lib/module/network/utils/networkListener.js +791 -0
  34. package/lib/module/preset.js +79 -0
  35. package/lib/typescript/index.d.ts +3 -0
  36. package/lib/typescript/index.d.ts.map +1 -0
  37. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts +26 -0
  38. package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +1 -0
  39. package/lib/typescript/network/components/NetworkEventDetailView.d.ts +13 -0
  40. package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -0
  41. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +12 -0
  42. package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -0
  43. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts +22 -0
  44. package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -0
  45. package/lib/typescript/network/components/NetworkModal.d.ts +14 -0
  46. package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -0
  47. package/lib/typescript/network/hooks/useNetworkEvents.d.ts +72 -0
  48. package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +1 -0
  49. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts +9 -0
  50. package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +1 -0
  51. package/lib/typescript/network/index.d.ts +12 -0
  52. package/lib/typescript/network/index.d.ts.map +1 -0
  53. package/lib/typescript/network/types/index.d.ts +88 -0
  54. package/lib/typescript/network/types/index.d.ts.map +1 -0
  55. package/lib/typescript/network/utils/extractOperationName.d.ts +41 -0
  56. package/lib/typescript/network/utils/extractOperationName.d.ts.map +1 -0
  57. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts +79 -0
  58. package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +1 -0
  59. package/lib/typescript/network/utils/formatting.d.ts +6 -0
  60. package/lib/typescript/network/utils/formatting.d.ts.map +1 -0
  61. package/lib/typescript/network/utils/networkEventStore.d.ts +81 -0
  62. package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -0
  63. package/lib/typescript/network/utils/networkListener.d.ts +191 -0
  64. package/lib/typescript/network/utils/networkListener.d.ts.map +1 -0
  65. package/lib/typescript/preset.d.ts +76 -0
  66. package/lib/typescript/preset.d.ts.map +1 -0
  67. package/package.json +69 -0
@@ -0,0 +1,937 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkModal = NetworkModal;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _NetworkEventItemCompact = require("./NetworkEventItemCompact");
11
+ var _NetworkFilterViewV = require("./NetworkFilterViewV3");
12
+ var _useTickEveryMinute = require("../hooks/useTickEveryMinute");
13
+ var _NetworkEventDetailView = require("./NetworkEventDetailView");
14
+ var _NetworkCopySettingsView = require("./NetworkCopySettingsView");
15
+ var _useNetworkEvents = require("../hooks/useNetworkEvents");
16
+ var _formatting = require("../utils/formatting");
17
+ var _jsxRuntime = require("react/jsx-runtime");
18
+ // Decompose by Responsibility: Extract empty state component
19
+ function EmptyState({
20
+ isEnabled
21
+ }) {
22
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
23
+ style: styles.emptyState,
24
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Globe, {
25
+ size: 32,
26
+ color: _sharedUi.macOSColors.text.muted
27
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
28
+ style: styles.emptyTitle,
29
+ children: "No network events"
30
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
31
+ style: styles.emptyText,
32
+ children: isEnabled ? "Network requests will appear here" : "Enable interception to start capturing"
33
+ })]
34
+ });
35
+ }
36
+ function NetworkModalInner({
37
+ visible,
38
+ onClose,
39
+ onBack,
40
+ onMinimize,
41
+ enableSharedModalDimensions = false
42
+ }) {
43
+ const {
44
+ isPro
45
+ } = (0, _sharedUi.useFeatureGate)();
46
+ const {
47
+ events,
48
+ stats,
49
+ filter,
50
+ setFilter,
51
+ clearEvents,
52
+ isEnabled,
53
+ toggleInterception,
54
+ hasLockedEvents,
55
+ lockedEventCount,
56
+ isEventLocked,
57
+ requestLimit
58
+ } = (0, _useNetworkEvents.useNetworkEvents)({
59
+ isPro
60
+ });
61
+ const handleModeChange = (0, _react.useCallback)(_mode => {
62
+ // Mode changes handled by JsModal
63
+ }, []);
64
+ const [selectedEvent, setSelectedEvent] = (0, _react.useState)(null);
65
+ const [showFilterView, setShowFilterView] = (0, _react.useState)(false);
66
+ const [activeTab, setActiveTab] = (0, _react.useState)("filters");
67
+ const [searchText, setSearchText] = (0, _react.useState)("");
68
+ const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
69
+ const searchInputRef = (0, _react.useRef)(null);
70
+ const [ignoredPatterns, setIgnoredPatterns] = (0, _react.useState)(new Set());
71
+ const [copySettings, setCopySettings] = (0, _react.useState)(_NetworkCopySettingsView.DEFAULT_COPY_SETTINGS);
72
+ const flatListRef = (0, _react.useRef)(null);
73
+ const hasLoadedFilters = (0, _react.useRef)(false);
74
+ const hasLoadedCopySettings = (0, _react.useRef)(false);
75
+ const {
76
+ getItem: safeGetItem,
77
+ setItem: safeSetItem
78
+ } = (0, _sharedUi.useSafeAsyncStorage)();
79
+ const [showUpgradeModal, setShowUpgradeModal] = (0, _react.useState)(false);
80
+
81
+ // Load persisted filters on mount
82
+ (0, _react.useEffect)(() => {
83
+ if (!visible || hasLoadedFilters.current) return;
84
+ const loadFilters = async () => {
85
+ try {
86
+ // Load ignored patterns (using domains key for now)
87
+ const storedPatterns = await safeGetItem(_sharedUi.devToolsStorageKeys.network.ignoredDomains());
88
+ if (storedPatterns) {
89
+ const patterns = JSON.parse(storedPatterns);
90
+ setIgnoredPatterns(new Set(patterns));
91
+ }
92
+ } catch (_error) {
93
+ // Silently fail - filters will use defaults
94
+ } finally {
95
+ hasLoadedFilters.current = true;
96
+ }
97
+ };
98
+ loadFilters();
99
+ }, [visible, safeGetItem]);
100
+
101
+ // Save filters when they change
102
+ (0, _react.useEffect)(() => {
103
+ if (!hasLoadedFilters.current) return; // Don't save on initial load
104
+
105
+ const saveFilters = async () => {
106
+ try {
107
+ // Save ignored patterns
108
+ const patterns = Array.from(ignoredPatterns);
109
+ await safeSetItem(_sharedUi.devToolsStorageKeys.network.ignoredDomains(), JSON.stringify(patterns));
110
+ } catch (_error) {
111
+ // Silently fail - filters will remain in memory
112
+ }
113
+ };
114
+ saveFilters();
115
+ }, [ignoredPatterns, safeSetItem]);
116
+
117
+ // Load persisted copy settings on mount
118
+ (0, _react.useEffect)(() => {
119
+ if (!visible || hasLoadedCopySettings.current) return;
120
+ const loadCopySettings = async () => {
121
+ try {
122
+ const stored = await safeGetItem(_sharedUi.devToolsStorageKeys.network.copyOptions());
123
+ if (stored) {
124
+ const settings = JSON.parse(stored);
125
+ setCopySettings(settings);
126
+ }
127
+ } catch (_error) {
128
+ // Silently fail - use defaults
129
+ } finally {
130
+ hasLoadedCopySettings.current = true;
131
+ }
132
+ };
133
+ loadCopySettings();
134
+ }, [visible, safeGetItem]);
135
+
136
+ // Save copy settings when they change
137
+ (0, _react.useEffect)(() => {
138
+ if (!hasLoadedCopySettings.current) return; // Don't save on initial load
139
+
140
+ const saveCopySettings = async () => {
141
+ try {
142
+ await safeSetItem(_sharedUi.devToolsStorageKeys.network.copyOptions(), JSON.stringify(copySettings));
143
+ } catch (_error) {
144
+ // Silently fail - settings will remain in memory
145
+ }
146
+ };
147
+ saveCopySettings();
148
+ }, [copySettings, safeSetItem]);
149
+
150
+ // Simple handlers - no useCallback needed per rule2
151
+ const handleEventPress = event => {
152
+ setSelectedEvent(event);
153
+ };
154
+ const handleBack = () => {
155
+ setSelectedEvent(null);
156
+ };
157
+ const handleSearch = text => {
158
+ setSearchText(text);
159
+ setFilter(prev => ({
160
+ ...prev,
161
+ searchText: text
162
+ }));
163
+ };
164
+ (0, _react.useEffect)(() => {
165
+ if (isSearchActive) {
166
+ requestAnimationFrame(() => {
167
+ searchInputRef.current?.focus();
168
+ });
169
+ }
170
+ }, [isSearchActive]);
171
+
172
+ // Filter events based on ignored patterns
173
+ const filteredEvents = (0, _react.useMemo)(() => {
174
+ if (ignoredPatterns.size === 0) return events;
175
+ return events.filter(event => {
176
+ const url = event.url.toLowerCase();
177
+
178
+ // Check if any pattern matches the URL
179
+ const isFiltered = Array.from(ignoredPatterns).some(pattern => url.includes(pattern.toLowerCase()));
180
+ return !isFiltered;
181
+ });
182
+ }, [events, ignoredPatterns]);
183
+
184
+ // Helper to check if payload should be included based on size
185
+ const shouldIncludePayload = (0, _react.useCallback)(data => {
186
+ if (copySettings.bodySizeThreshold === -1) return true;
187
+ if (!data) return true;
188
+ try {
189
+ const size = JSON.stringify(data).length;
190
+ return size < copySettings.bodySizeThreshold * 1024;
191
+ } catch {
192
+ return false;
193
+ }
194
+ }, [copySettings.bodySizeThreshold]);
195
+
196
+ // Generate copy text based on settings
197
+ const generateCopyText = (0, _react.useCallback)(() => {
198
+ let eventsToUse = filteredEvents;
199
+
200
+ // Apply filter mode
201
+ if (copySettings.filterMode === "failed") {
202
+ eventsToUse = eventsToUse.filter(e => e.error || e.status && e.status >= 400);
203
+ } else if (copySettings.filterMode === "success") {
204
+ eventsToUse = eventsToUse.filter(e => e.status && e.status >= 200 && e.status < 300);
205
+ }
206
+ if (eventsToUse.length === 0) {
207
+ return "No requests to copy";
208
+ }
209
+
210
+ // Plain text format (URLs only)
211
+ if (copySettings.format === "plaintext") {
212
+ return eventsToUse.map(event => `${event.method} ${event.url}`).join("\n");
213
+ }
214
+
215
+ // JSON format
216
+ if (copySettings.format === "json") {
217
+ const requests = eventsToUse.map(event => {
218
+ const obj = {};
219
+ if (copySettings.includeMethod) {
220
+ obj.method = event.method;
221
+ obj.url = event.url;
222
+ }
223
+ if (copySettings.includeStatus) {
224
+ obj.status = event.status;
225
+ obj.statusText = event.statusText;
226
+ }
227
+ if (copySettings.includeDuration) obj.duration = event.duration;
228
+ if (copySettings.includeTimestamp) obj.timestamp = event.timestamp;
229
+ if (copySettings.includeClient) obj.requestClient = event.requestClient;
230
+ if (copySettings.includeSizes) {
231
+ obj.requestSize = event.requestSize;
232
+ obj.responseSize = event.responseSize;
233
+ }
234
+ if (copySettings.includeErrors && event.error) obj.error = event.error;
235
+ if (copySettings.includeRequestHeaders) {
236
+ obj.requestHeaders = event.requestHeaders;
237
+ }
238
+ if (copySettings.includeResponseHeaders) {
239
+ obj.responseHeaders = event.responseHeaders;
240
+ }
241
+ if (copySettings.includeRequestBody && shouldIncludePayload(event.requestData)) {
242
+ obj.requestData = event.requestData;
243
+ }
244
+ if (copySettings.includeResponseBody && shouldIncludePayload(event.responseData)) {
245
+ obj.responseData = event.responseData;
246
+ }
247
+ return obj;
248
+ });
249
+ return JSON.stringify(requests, null, 2);
250
+ }
251
+
252
+ // Markdown format
253
+ const requests = eventsToUse.map((event, index) => {
254
+ let md = `# Request ${index + 1}\n\n`;
255
+ if (copySettings.includeMethod) {
256
+ md += `**Method:** ${event.method}\n`;
257
+ md += `**URL:** ${event.url}\n`;
258
+ }
259
+ if (copySettings.includeStatus) {
260
+ const status = event.status || "Pending";
261
+ md += `**Status:** ${status}${event.statusText ? ` (${event.statusText})` : ""}\n`;
262
+ }
263
+ if (copySettings.includeClient && event.requestClient) {
264
+ md += `**Client:** ${event.requestClient}\n`;
265
+ }
266
+ if (copySettings.includeDuration && event.duration) {
267
+ md += `**Duration:** ${event.duration}ms\n`;
268
+ }
269
+ if (copySettings.includeTimestamp) {
270
+ md += `**Timestamp:** ${new Date(event.timestamp).toISOString()}\n`;
271
+ }
272
+ if (copySettings.includeSizes) {
273
+ const reqSize = event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "N/A";
274
+ const resSize = event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "N/A";
275
+ md += `**Request Size:** ${reqSize}\n`;
276
+ md += `**Response Size:** ${resSize}\n`;
277
+ }
278
+ if (copySettings.includeErrors && event.error) {
279
+ md += `**Error:** ${event.error}\n`;
280
+ }
281
+ if (copySettings.includeRequestHeaders && Object.keys(event.requestHeaders).length > 0) {
282
+ md += `\n## Request Headers\n\`\`\`json\n${JSON.stringify(event.requestHeaders, null, 2)}\n\`\`\`\n`;
283
+ }
284
+ if (copySettings.includeRequestBody) {
285
+ md += `\n## Request Data\n`;
286
+ if (shouldIncludePayload(event.requestData)) {
287
+ md += `\`\`\`json\n${JSON.stringify(event.requestData, null, 2)}\n\`\`\`\n`;
288
+ } else {
289
+ const size = event.requestSize ? (0, _formatting.formatBytes)(event.requestSize) : "Unknown size";
290
+ md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
291
+ }
292
+ }
293
+ if (copySettings.includeResponseHeaders && Object.keys(event.responseHeaders).length > 0) {
294
+ md += `\n## Response Headers\n\`\`\`json\n${JSON.stringify(event.responseHeaders, null, 2)}\n\`\`\`\n`;
295
+ }
296
+ if (copySettings.includeResponseBody) {
297
+ md += `\n## Response Data\n`;
298
+ if (shouldIncludePayload(event.responseData)) {
299
+ md += `\`\`\`json\n${JSON.stringify(event.responseData, null, 2)}\n\`\`\`\n`;
300
+ } else {
301
+ const size = event.responseSize ? (0, _formatting.formatBytes)(event.responseSize) : "Unknown size";
302
+ md += `_Payload omitted (${size}). Exceeds ${copySettings.bodySizeThreshold}KB threshold._\n`;
303
+ }
304
+ }
305
+ md += `\n---\n`;
306
+ return md;
307
+ }).join("\n");
308
+ const header = `# Network Requests (${eventsToUse.length} total)\n\n`;
309
+ return header + requests;
310
+ }, [filteredEvents, copySettings, shouldIncludePayload]);
311
+
312
+ // Memoized copy text value for CopyButton
313
+ const copyTextValue = (0, _react.useMemo)(() => {
314
+ if (filteredEvents.length === 0) return "";
315
+ return generateCopyText();
316
+ }, [filteredEvents.length, generateCopyText]);
317
+
318
+ // FlatList optimization - only keep what's needed for FlatList performance
319
+ const keyExtractor = item => item.id;
320
+
321
+ // Filter to only show unlocked events in the list
322
+ const unlockedEvents = (0, _react.useMemo)(() => {
323
+ return filteredEvents.filter(event => !isEventLocked(event.id));
324
+ }, [filteredEvents, isEventLocked]);
325
+
326
+ // Keep renderItem memoized for FlatList performance (justified by FlatList docs)
327
+ const renderItem = (0, _react.useMemo)(() => {
328
+ return ({
329
+ item
330
+ }) => {
331
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkEventItemCompact.NetworkEventItemCompact, {
332
+ event: item,
333
+ onPress: handleEventPress
334
+ });
335
+ };
336
+ }, []); // handleEventPress defined inline
337
+
338
+ // Footer component showing upgrade banner when events are locked
339
+ const renderListFooter = (0, _react.useCallback)(() => {
340
+ if (!hasLockedEvents) return null;
341
+ const isSearching = searchText.length > 0;
342
+ const bannerText = isSearching ? `${lockedEventCount} matching ${lockedEventCount === 1 ? 'request' : 'requests'} in locked history` : `${lockedEventCount} requests locked`;
343
+ const subtextMessage = isSearching ? "Upgrade to Pro to search your full request history" : "Upgrade to Pro to unlock full request history";
344
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
345
+ style: styles.limitBanner,
346
+ onPress: () => setShowUpgradeModal(true),
347
+ activeOpacity: 0.8,
348
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
349
+ style: styles.limitBannerContent,
350
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
351
+ style: styles.limitBannerLeft,
352
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Zap, {
353
+ size: 16,
354
+ color: "#20C997"
355
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
356
+ style: styles.limitBannerText,
357
+ children: bannerText
358
+ })]
359
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProBadge, {})]
360
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
361
+ style: styles.limitBannerSubtext,
362
+ children: subtextMessage
363
+ })]
364
+ });
365
+ }, [hasLockedEvents, lockedEventCount, searchText]);
366
+
367
+ // Compact header with actions (like Sentry/Storage modals)
368
+ const renderHeaderContent = () => {
369
+ // Filter view header with tabs
370
+ if (showFilterView) {
371
+ // Copy tab is always visible and clickable - gating handled inside the view
372
+ const tabs = [{
373
+ key: "filters",
374
+ label: "Filters"
375
+ }, {
376
+ key: "copy",
377
+ label: "Copy"
378
+ }];
379
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
380
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
381
+ onBack: () => setShowFilterView(false)
382
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
383
+ title: "",
384
+ noMargin: true,
385
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TabSelector, {
386
+ tabs: tabs,
387
+ activeTab: activeTab,
388
+ onTabChange: tab => setActiveTab(tab)
389
+ })
390
+ })]
391
+ });
392
+ }
393
+
394
+ // Event detail view header
395
+ if (selectedEvent) {
396
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
397
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
398
+ onBack: handleBack
399
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
400
+ title: "Request Details",
401
+ centered: true
402
+ })]
403
+ });
404
+ }
405
+
406
+ // Main list view header with search and filters
407
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader, {
408
+ children: [onBack && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Navigation, {
409
+ onBack: onBack
410
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
411
+ title: "",
412
+ children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
413
+ style: styles.headerSearchContainer,
414
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
415
+ size: 14,
416
+ color: _sharedUi.macOSColors.text.secondary
417
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TextInput, {
418
+ ref: searchInputRef,
419
+ style: styles.headerSearchInput,
420
+ placeholder: "Search URL, method, operation, error...",
421
+ placeholderTextColor: _sharedUi.macOSColors.text.muted,
422
+ value: searchText,
423
+ onChangeText: handleSearch,
424
+ onSubmitEditing: () => setIsSearchActive(false),
425
+ onBlur: () => setIsSearchActive(false),
426
+ "sentry-label": "ignore network search header",
427
+ accessibilityLabel: "Search network requests",
428
+ autoCapitalize: "none",
429
+ autoCorrect: false,
430
+ returnKeyType: "search"
431
+ }), searchText.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
432
+ onPress: () => {
433
+ handleSearch("");
434
+ setIsSearchActive(false);
435
+ },
436
+ "sentry-label": "ignore clear search header",
437
+ style: styles.headerSearchClear,
438
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
439
+ size: 14,
440
+ color: _sharedUi.macOSColors.text.secondary
441
+ })
442
+ }) : null]
443
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
444
+ style: styles.headerChipRow,
445
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
446
+ style: [styles.headerChip, filter.status === "success" && styles.headerChipActive],
447
+ onPress: () => setFilter({
448
+ ...filter,
449
+ status: filter.status === "success" ? undefined : "success"
450
+ }),
451
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CheckCircle, {
452
+ size: 12,
453
+ color: _sharedUi.macOSColors.semantic.success
454
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
455
+ style: [styles.headerChipValue, {
456
+ color: _sharedUi.macOSColors.semantic.success
457
+ }],
458
+ children: stats.successfulRequests
459
+ })]
460
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
461
+ style: [styles.headerChip, filter.status === "error" && styles.headerChipActive],
462
+ onPress: () => setFilter({
463
+ ...filter,
464
+ status: filter.status === "error" ? undefined : "error"
465
+ }),
466
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.XCircle, {
467
+ size: 12,
468
+ color: _sharedUi.macOSColors.semantic.error
469
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
470
+ style: [styles.headerChipValue, {
471
+ color: _sharedUi.macOSColors.semantic.error
472
+ }],
473
+ children: stats.failedRequests
474
+ })]
475
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
476
+ style: [styles.headerChip, filter.status === "pending" && styles.headerChipActive],
477
+ onPress: () => setFilter({
478
+ ...filter,
479
+ status: filter.status === "pending" ? undefined : "pending"
480
+ }),
481
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
482
+ size: 12,
483
+ color: _sharedUi.macOSColors.semantic.warning
484
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
485
+ style: [styles.headerChipValue, {
486
+ color: _sharedUi.macOSColors.semantic.warning
487
+ }],
488
+ children: stats.pendingRequests
489
+ })]
490
+ })]
491
+ })
492
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ModalHeader.Actions, {
493
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
494
+ "sentry-label": "ignore open search",
495
+ onPress: () => setIsSearchActive(true),
496
+ style: styles.headerActionButton,
497
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Search, {
498
+ size: 14,
499
+ color: _sharedUi.macOSColors.text.secondary
500
+ })
501
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
502
+ "sentry-label": "ignore filter",
503
+ onPress: () => {
504
+ setShowFilterView(true);
505
+ },
506
+ style: [styles.headerActionButton, (filter.status || filter.method || filter.contentType) && styles.activeFilterButton],
507
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Filter, {
508
+ size: 14,
509
+ color: filter.status || filter.method || filter.contentType ? _sharedUi.macOSColors.semantic.info : _sharedUi.macOSColors.text.muted
510
+ })
511
+ }), isPro ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.CopyButton, {
512
+ value: copyTextValue,
513
+ size: 14,
514
+ buttonStyle: styles.headerActionButton,
515
+ colors: {
516
+ idle: filteredEvents.length > 0 ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled,
517
+ success: _sharedUi.macOSColors.semantic.success,
518
+ error: _sharedUi.macOSColors.semantic.error
519
+ },
520
+ disabled: filteredEvents.length === 0
521
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
522
+ "sentry-label": "ignore copy pro",
523
+ onPress: () => setShowUpgradeModal(true),
524
+ style: [styles.headerActionButton, filteredEvents.length === 0 && styles.headerActionButtonDisabled],
525
+ disabled: filteredEvents.length === 0,
526
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Copy, {
527
+ size: 14,
528
+ color: filteredEvents.length > 0 ? _sharedUi.macOSColors.text.secondary : _sharedUi.macOSColors.text.disabled
529
+ })
530
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
531
+ "sentry-label": "ignore toggle interception",
532
+ onPress: toggleInterception,
533
+ style: [styles.headerActionButton, isEnabled ? styles.startButton : styles.stopButton],
534
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
535
+ size: 14,
536
+ color: isEnabled ? _sharedUi.macOSColors.semantic.success : _sharedUi.macOSColors.semantic.error
537
+ })
538
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
539
+ "sentry-label": "ignore clear events",
540
+ onPress: clearEvents,
541
+ style: [styles.headerActionButton, events.length === 0 && styles.headerActionButtonDisabled],
542
+ disabled: events.length === 0,
543
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Trash2, {
544
+ size: 14,
545
+ color: events.length > 0 ? _sharedUi.macOSColors.text.muted : _sharedUi.macOSColors.text.disabled
546
+ })
547
+ })]
548
+ })]
549
+ });
550
+ };
551
+ const persistenceKey = enableSharedModalDimensions ? _sharedUi.devToolsStorageKeys.modal.root() : _sharedUi.devToolsStorageKeys.network.modal();
552
+ if (!visible) return null;
553
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.JsModal, {
554
+ visible: visible,
555
+ onClose: onClose,
556
+ onMinimize: onMinimize,
557
+ persistenceKey: persistenceKey,
558
+ header: {
559
+ showToggleButton: true,
560
+ customContent: renderHeaderContent()
561
+ },
562
+ onModeChange: handleModeChange,
563
+ enablePersistence: true,
564
+ initialMode: "bottomSheet",
565
+ enableGlitchEffects: true,
566
+ styles: {},
567
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
568
+ style: styles.container,
569
+ children: selectedEvent ?
570
+ /*#__PURE__*/
571
+ /* Show detail view if event is selected */
572
+ (0, _jsxRuntime.jsx)(_NetworkEventDetailView.NetworkEventDetailView, {
573
+ event: selectedEvent,
574
+ ignoredPatterns: ignoredPatterns,
575
+ onTogglePattern: pattern => {
576
+ const newPatterns = new Set(ignoredPatterns);
577
+ if (newPatterns.has(pattern)) {
578
+ newPatterns.delete(pattern);
579
+ } else {
580
+ newPatterns.add(pattern);
581
+ }
582
+ setIgnoredPatterns(newPatterns);
583
+ }
584
+ }) : showFilterView ? (/* Show filter/copy view based on active tab */
585
+ activeTab === "copy" ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkCopySettingsView.NetworkCopySettingsView, {
586
+ settings: copySettings,
587
+ onSettingsChange: setCopySettings,
588
+ events: filteredEvents
589
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_NetworkFilterViewV.NetworkFilterViewV3, {
590
+ events: events,
591
+ filter: filter,
592
+ onFilterChange: setFilter,
593
+ ignoredPatterns: ignoredPatterns,
594
+ onTogglePattern: pattern => {
595
+ const newPatterns = new Set(ignoredPatterns);
596
+ if (newPatterns.has(pattern)) {
597
+ newPatterns.delete(pattern);
598
+ } else {
599
+ newPatterns.add(pattern);
600
+ }
601
+ setIgnoredPatterns(newPatterns);
602
+ },
603
+ onAddPattern: pattern => {
604
+ const newPatterns = new Set(ignoredPatterns);
605
+ newPatterns.add(pattern);
606
+ setIgnoredPatterns(newPatterns);
607
+ }
608
+ })) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
609
+ children: [!isEnabled ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
610
+ style: styles.disabledBanner,
611
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Power, {
612
+ size: 14,
613
+ color: _sharedUi.macOSColors.semantic.warning
614
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
615
+ style: styles.disabledText,
616
+ children: "Network interception is disabled"
617
+ })]
618
+ }) : null, unlockedEvents.length > 0 || hasLockedEvents ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.FlatList, {
619
+ ref: flatListRef,
620
+ data: unlockedEvents,
621
+ renderItem: renderItem,
622
+ keyExtractor: keyExtractor,
623
+ contentContainerStyle: styles.listContent,
624
+ showsVerticalScrollIndicator: true,
625
+ removeClippedSubviews: true,
626
+ onEndReachedThreshold: 0.8,
627
+ initialNumToRender: 10,
628
+ maxToRenderPerBatch: 10,
629
+ windowSize: 10,
630
+ scrollEnabled: false,
631
+ "sentry-label": "ignore network events list",
632
+ ListFooterComponent: renderListFooter
633
+ }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
634
+ isEnabled: isEnabled
635
+ })]
636
+ })
637
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.UpgradeModal, {
638
+ visible: showUpgradeModal,
639
+ onClose: () => setShowUpgradeModal(false),
640
+ featureName: "Network Export",
641
+ description: "Export network requests to share with your team, debug APIs, or feed into AI tools.",
642
+ benefits: ["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"]
643
+ })]
644
+ });
645
+ }
646
+ const styles = _reactNative.StyleSheet.create({
647
+ container: {
648
+ flex: 1,
649
+ backgroundColor: _sharedUi.macOSColors.background.base
650
+ },
651
+ // Compact header styles matching Sentry/Storage modals
652
+ headerContainer: {
653
+ flexDirection: "row",
654
+ alignItems: "center",
655
+ flex: 1,
656
+ gap: 8,
657
+ minHeight: 32,
658
+ paddingLeft: 4
659
+ },
660
+ headerTitle: {
661
+ color: _sharedUi.macOSColors.text.primary,
662
+ fontSize: 14,
663
+ fontWeight: "500",
664
+ flex: 1,
665
+ marginLeft: 8
666
+ },
667
+ headerStats: {
668
+ display: "none"
669
+ },
670
+ headerStatsText: {
671
+ fontSize: 12,
672
+ color: _sharedUi.macOSColors.text.muted,
673
+ fontWeight: "500"
674
+ },
675
+ headerFilteredText: {
676
+ fontSize: 11,
677
+ color: _sharedUi.macOSColors.semantic.warning,
678
+ fontWeight: "500",
679
+ marginLeft: 4
680
+ },
681
+ headerActions: {
682
+ flexDirection: "row",
683
+ gap: 6,
684
+ marginLeft: "auto",
685
+ marginRight: 4
686
+ },
687
+ headerCenterArea: {
688
+ flex: 1,
689
+ marginHorizontal: 8
690
+ },
691
+ headerSearchContainer: {
692
+ flexDirection: "row",
693
+ alignItems: "center",
694
+ backgroundColor: _sharedUi.macOSColors.background.input,
695
+ borderRadius: 10,
696
+ borderWidth: 1,
697
+ borderColor: _sharedUi.macOSColors.border.default,
698
+ paddingHorizontal: 12,
699
+ paddingVertical: 5
700
+ },
701
+ headerSearchInput: {
702
+ flex: 1,
703
+ color: _sharedUi.macOSColors.text.primary,
704
+ fontSize: 13,
705
+ marginLeft: 6,
706
+ paddingVertical: 2
707
+ },
708
+ headerSearchClear: {
709
+ marginLeft: 6,
710
+ padding: 4
711
+ },
712
+ headerChipRow: {
713
+ flexDirection: "row",
714
+ alignItems: "center",
715
+ gap: 8
716
+ },
717
+ headerChip: {
718
+ flexDirection: "row",
719
+ alignItems: "center",
720
+ gap: 4,
721
+ backgroundColor: _sharedUi.macOSColors.background.hover,
722
+ paddingHorizontal: 10,
723
+ paddingVertical: 5,
724
+ borderRadius: 12,
725
+ borderWidth: 1,
726
+ borderColor: _sharedUi.macOSColors.border.default
727
+ },
728
+ headerChipActive: {
729
+ backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
730
+ borderColor: _sharedUi.macOSColors.semantic.info + "50",
731
+ shadowColor: _sharedUi.macOSColors.semantic.info,
732
+ shadowOffset: {
733
+ width: 0,
734
+ height: 1
735
+ },
736
+ shadowOpacity: 0.08,
737
+ shadowRadius: 2,
738
+ elevation: 1
739
+ },
740
+ headerChipValue: {
741
+ fontSize: 12,
742
+ fontWeight: "600",
743
+ fontFamily: "monospace"
744
+ },
745
+ headerActionButton: {
746
+ width: 32,
747
+ height: 32,
748
+ borderRadius: 8,
749
+ backgroundColor: _sharedUi.macOSColors.background.hover,
750
+ borderWidth: 1,
751
+ borderColor: _sharedUi.macOSColors.border.default,
752
+ alignItems: "center",
753
+ justifyContent: "center"
754
+ },
755
+ headerActionButtonDisabled: {
756
+ opacity: 0.55
757
+ },
758
+ // Shared navbar styles (matching React Query modal)
759
+ tabNavigationContainer: {
760
+ flexDirection: "row",
761
+ backgroundColor: _sharedUi.macOSColors.background.card,
762
+ borderRadius: 6,
763
+ padding: 2,
764
+ borderWidth: 1,
765
+ borderColor: _sharedUi.macOSColors.border.default,
766
+ justifyContent: "space-evenly",
767
+ flex: 1,
768
+ marginLeft: 8,
769
+ marginRight: 8
770
+ },
771
+ tabButton: {
772
+ paddingHorizontal: 8,
773
+ paddingVertical: 5,
774
+ borderRadius: 4,
775
+ alignItems: "center",
776
+ justifyContent: "center",
777
+ flex: 1,
778
+ marginHorizontal: 1
779
+ },
780
+ tabButtonActive: {
781
+ backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
782
+ borderWidth: 1,
783
+ borderColor: _sharedUi.macOSColors.semantic.info + "40"
784
+ },
785
+ tabButtonInactive: {
786
+ backgroundColor: "transparent"
787
+ },
788
+ tabButtonText: {
789
+ fontSize: 12,
790
+ fontWeight: "600",
791
+ letterSpacing: 0.5,
792
+ fontFamily: "monospace",
793
+ textTransform: "uppercase"
794
+ },
795
+ tabButtonTextActive: {
796
+ color: _sharedUi.macOSColors.semantic.info
797
+ },
798
+ tabButtonTextInactive: {
799
+ color: _sharedUi.macOSColors.text.muted
800
+ },
801
+ startButton: {
802
+ backgroundColor: _sharedUi.macOSColors.semantic.successBackground,
803
+ borderColor: _sharedUi.macOSColors.semantic.success + "40"
804
+ },
805
+ stopButton: {
806
+ backgroundColor: _sharedUi.macOSColors.semantic.errorBackground,
807
+ borderColor: _sharedUi.macOSColors.semantic.error + "40"
808
+ },
809
+ activeFilterButton: {
810
+ backgroundColor: _sharedUi.macOSColors.semantic.infoBackground,
811
+ borderColor: _sharedUi.macOSColors.semantic.info + "40"
812
+ },
813
+ activeIgnoreButton: {
814
+ backgroundColor: _sharedUi.macOSColors.semantic.warningBackground,
815
+ borderColor: _sharedUi.macOSColors.semantic.warning + "33"
816
+ },
817
+ detailHeaderActions: {
818
+ flexDirection: "row",
819
+ gap: 6,
820
+ marginLeft: "auto",
821
+ marginRight: 4
822
+ },
823
+ // Search bar - minimal design with theme colors
824
+ searchContainer: {
825
+ display: "none"
826
+ },
827
+ searchInput: {},
828
+ // Stats bar - minimal design
829
+ statsBar: {
830
+ display: "none"
831
+ },
832
+ statChip: {
833
+ flexDirection: "row",
834
+ alignItems: "center",
835
+ gap: 4,
836
+ backgroundColor: _sharedUi.macOSColors.background.hover,
837
+ paddingHorizontal: 8,
838
+ paddingVertical: 4,
839
+ borderRadius: 12,
840
+ borderWidth: 1,
841
+ borderColor: "transparent"
842
+ },
843
+ statChipActive: {
844
+ backgroundColor: _sharedUi.macOSColors.semantic.info + "26",
845
+ borderColor: _sharedUi.macOSColors.semantic.info + "66"
846
+ },
847
+ statValue: {
848
+ fontSize: 14,
849
+ fontWeight: "600",
850
+ fontFamily: "monospace"
851
+ },
852
+ statLabel: {
853
+ fontSize: 10,
854
+ color: _sharedUi.macOSColors.text.muted,
855
+ fontWeight: "500",
856
+ textTransform: "uppercase"
857
+ },
858
+ disabledBanner: {
859
+ flexDirection: "row",
860
+ alignItems: "center",
861
+ gap: 8,
862
+ padding: 10,
863
+ marginHorizontal: 12,
864
+ marginTop: 8,
865
+ backgroundColor: _sharedUi.macOSColors.semantic.warningBackground,
866
+ borderRadius: 8,
867
+ borderWidth: 1,
868
+ borderColor: _sharedUi.macOSColors.semantic.warning + "20"
869
+ },
870
+ disabledText: {
871
+ color: _sharedUi.macOSColors.semantic.warning,
872
+ fontSize: 11,
873
+ flex: 1
874
+ },
875
+ listContent: {
876
+ paddingTop: 8
877
+ },
878
+ emptyState: {
879
+ alignItems: "center",
880
+ paddingVertical: 40
881
+ },
882
+ emptyTitle: {
883
+ color: _sharedUi.macOSColors.text.primary,
884
+ fontSize: 14,
885
+ fontWeight: "600",
886
+ marginTop: 12,
887
+ marginBottom: 6
888
+ },
889
+ emptyText: {
890
+ color: _sharedUi.macOSColors.text.muted,
891
+ fontSize: 12,
892
+ textAlign: "center"
893
+ },
894
+ // Free tier limit banner
895
+ limitBanner: {
896
+ backgroundColor: "#1A1A1A",
897
+ borderRadius: 10,
898
+ borderWidth: 1,
899
+ borderColor: "#20C997" + "40",
900
+ padding: 12,
901
+ marginHorizontal: 12,
902
+ marginTop: 12,
903
+ marginBottom: 34
904
+ },
905
+ limitBannerContent: {
906
+ flexDirection: "row",
907
+ alignItems: "center",
908
+ justifyContent: "space-between",
909
+ marginBottom: 4
910
+ },
911
+ limitBannerLeft: {
912
+ flexDirection: "row",
913
+ alignItems: "center",
914
+ gap: 8
915
+ },
916
+ limitBannerText: {
917
+ color: "#E0E0E0",
918
+ fontSize: 13,
919
+ fontWeight: "600"
920
+ },
921
+ limitBannerSubtext: {
922
+ color: "#888888",
923
+ fontSize: 11
924
+ }
925
+ });
926
+
927
+ /**
928
+ * High-level modal surface that wraps the network monitoring experience with shared timing
929
+ * context. Automatically injects the `TickProvider` so timestamps refresh while the modal is open.
930
+ */
931
+ function NetworkModal(props) {
932
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_useTickEveryMinute.TickProvider, {
933
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(NetworkModalInner, {
934
+ ...props
935
+ })
936
+ });
937
+ }