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