@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,323 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkEventItemCompact = void 0;
7
+ var _react = require("react");
8
+ var _reactNative = require("react-native");
9
+ var _sharedUi = require("@buoy-gg/shared-ui");
10
+ var _formatting = require("../utils/formatting");
11
+ var _useTickEveryMinute = require("../hooks/useTickEveryMinute");
12
+ var _formatGraphQLVariables = require("../utils/formatGraphQLVariables");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ // Get color based on status
15
+ function getStatusColor(status, error) {
16
+ if (error) return _sharedUi.macOSColors.semantic.error;
17
+ if (!status) return _sharedUi.macOSColors.semantic.warning;
18
+ if (status >= 200 && status < 300) return _sharedUi.macOSColors.semantic.success;
19
+ if (status >= 300 && status < 400) return _sharedUi.macOSColors.semantic.info;
20
+ if (status >= 400) return _sharedUi.macOSColors.semantic.error;
21
+ return _sharedUi.macOSColors.text.muted;
22
+ }
23
+
24
+ // Get content type badge with color
25
+ function getContentTypeBadge(headers) {
26
+ const contentType = headers?.["content-type"] || headers?.["Content-Type"] || "";
27
+ if (contentType.includes("json")) return "JSON";
28
+ if (contentType.includes("xml")) return "XML";
29
+ if (contentType.includes("html")) return "HTML";
30
+ if (contentType.includes("text")) return "TEXT";
31
+ if (contentType.includes("image")) return "IMG";
32
+ if (contentType.includes("video")) return "VIDEO";
33
+ if (contentType.includes("audio")) return "AUDIO";
34
+ if (contentType.includes("form")) return "FORM";
35
+ return null;
36
+ }
37
+
38
+ // Decomposed components following rule3 - Component Composition
39
+
40
+ // Status indicator component - single responsibility
41
+ function StatusIndicator({
42
+ event,
43
+ isPending,
44
+ statusColor
45
+ }) {
46
+ if (isPending) {
47
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
48
+ style: styles.pendingBadge,
49
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Clock, {
50
+ size: 10,
51
+ color: _sharedUi.macOSColors.semantic.warning
52
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
53
+ style: styles.pendingText,
54
+ children: "..."
55
+ })]
56
+ });
57
+ }
58
+ if (event.error) {
59
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
60
+ style: styles.errorBadge,
61
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertCircle, {
62
+ size: 10,
63
+ color: _sharedUi.macOSColors.semantic.error
64
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
65
+ style: styles.errorText,
66
+ children: "ERR"
67
+ })]
68
+ });
69
+ }
70
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
71
+ style: styles.statusBadge,
72
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
73
+ style: [styles.statusText, {
74
+ color: statusColor
75
+ }],
76
+ children: String(event.status)
77
+ })
78
+ });
79
+ }
80
+
81
+ // Size indicators component - single responsibility
82
+ function SizeIndicators({
83
+ requestSize,
84
+ responseSize
85
+ }) {
86
+ if (!requestSize && !responseSize) return null;
87
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
88
+ style: styles.sizeRow,
89
+ children: [requestSize ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
90
+ style: styles.sizeItem,
91
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Upload, {
92
+ size: 8,
93
+ color: _sharedUi.macOSColors.semantic.info
94
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
95
+ style: styles.sizeText,
96
+ children: (0, _formatting.formatBytes)(requestSize)
97
+ })]
98
+ }) : null, responseSize ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
99
+ style: styles.sizeItem,
100
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Download, {
101
+ size: 8,
102
+ color: _sharedUi.macOSColors.semantic.success
103
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
104
+ style: styles.sizeText,
105
+ children: (0, _formatting.formatBytes)(responseSize)
106
+ })]
107
+ }) : null]
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Compact list-row representation of a network event. Optimized for large lists with memoization
113
+ * and periodic refresh via `useTickEveryMinute` to keep relative timestamps accurate.
114
+ */
115
+ const NetworkEventItemCompact = exports.NetworkEventItemCompact = /*#__PURE__*/(0, _react.memo)(({
116
+ event,
117
+ onPress
118
+ }) => {
119
+ const tick = (0, _useTickEveryMinute.useTickEveryMinute)();
120
+ const statusColor = getStatusColor(event.status, event.error);
121
+ const isPending = !event.status && !event.error;
122
+ const contentType = getContentTypeBadge(event.responseHeaders);
123
+
124
+ // Format URL for display (max 2 lines)
125
+ let displayUrl = event.path || event.url.replace(/^https?:\/\/[^/]+/, "");
126
+
127
+ // If this is a GraphQL request, show operation name with variables using arrow notation
128
+ // Matches React Query pattern: ["pokemon", "Sandshrew"] → "pokemon › Sandshrew"
129
+ if (event.requestClient === "graphql") {
130
+ if (event.operationName) {
131
+ // Format: GetPokemon › Sandshrew (matches React Query pattern)
132
+ displayUrl = (0, _formatGraphQLVariables.formatGraphQLDisplay)(event.operationName, event.graphqlVariables);
133
+ } else {
134
+ // If no operation name found, just remove the redundant /graphql path
135
+ displayUrl = displayUrl.replace(/\/graphql[^?]*/, "/graphql");
136
+ }
137
+ } else if (event.operationName) {
138
+ // For non-GraphQL requests with operation names (e.g., gRPC)
139
+ displayUrl = `${displayUrl}\n(${event.operationName})`;
140
+ }
141
+
142
+ // Format time with both absolute and relative
143
+ const timeString = new Date(event.timestamp).toLocaleTimeString("en-US", {
144
+ hour: "numeric",
145
+ minute: "2-digit",
146
+ second: "2-digit",
147
+ hour12: true
148
+ });
149
+ const relativeTime = (0, _sharedUi.formatRelativeTime)(event.timestamp, tick);
150
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ListItem, {
151
+ onPress: () => onPress(event),
152
+ style: [styles.container, {
153
+ borderLeftColor: statusColor
154
+ }],
155
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
156
+ style: styles.leftSection,
157
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.MethodBadge, {
158
+ method: event.method,
159
+ size: "small"
160
+ }), event.requestClient && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
161
+ style: [styles.clientBadge, {
162
+ backgroundColor: event.requestClient === "fetch" ? "rgba(74, 144, 226, 0.15)" : event.requestClient === "graphql" ? "rgba(229, 53, 171, 0.15)" : event.requestClient === "grpc-web" ? "rgba(16, 185, 129, 0.15)" : "rgba(147, 51, 234, 0.15)"
163
+ }],
164
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
165
+ style: [styles.clientText, {
166
+ color: event.requestClient === "fetch" ? "#4A90E2" : event.requestClient === "graphql" ? "#E535AB" : event.requestClient === "grpc-web" ? "#10B981" : "#9333EA"
167
+ }],
168
+ children: event.requestClient === "graphql" ? "GQL" : event.requestClient === "grpc-web" ? "gRPC" : event.requestClient
169
+ })
170
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(SizeIndicators, {
171
+ requestSize: event.requestSize,
172
+ responseSize: event.responseSize
173
+ })]
174
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
175
+ style: styles.middleSection,
176
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
177
+ style: styles.urlText,
178
+ numberOfLines: 2,
179
+ children: displayUrl
180
+ })
181
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
182
+ style: styles.rightSection,
183
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
184
+ style: styles.rightTopRow,
185
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(StatusIndicator, {
186
+ event: event,
187
+ isPending: isPending,
188
+ statusColor: statusColor
189
+ }), event.duration ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
190
+ style: styles.durationText,
191
+ children: (0, _formatting.formatDuration)(event.duration)
192
+ }) : null, contentType ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.TypeBadge, {
193
+ type: contentType,
194
+ size: "small"
195
+ }) : null]
196
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
197
+ style: styles.rightBottomRow,
198
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.ListItem.Metadata, {
199
+ children: [timeString, " (", relativeTime, ")"]
200
+ })
201
+ })]
202
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronRight, {
203
+ size: 14,
204
+ color: _sharedUi.macOSColors.text.muted
205
+ })]
206
+ });
207
+ });
208
+ const styles = _reactNative.StyleSheet.create({
209
+ container: {
210
+ flexDirection: "row",
211
+ alignItems: "center",
212
+ backgroundColor: _sharedUi.macOSColors.background.card,
213
+ borderRadius: 6,
214
+ paddingVertical: 8,
215
+ paddingHorizontal: 10,
216
+ paddingLeft: 8,
217
+ marginBottom: 4,
218
+ marginHorizontal: 12,
219
+ minHeight: 44,
220
+ borderLeftWidth: 3,
221
+ borderLeftColor: "transparent"
222
+ },
223
+ leftSection: {
224
+ marginRight: 8,
225
+ alignItems: "flex-start",
226
+ paddingTop: 2
227
+ },
228
+ middleSection: {
229
+ flex: 1,
230
+ justifyContent: "center",
231
+ paddingRight: 8
232
+ },
233
+ urlText: {
234
+ fontSize: 12,
235
+ color: _sharedUi.macOSColors.text.primary,
236
+ lineHeight: 16,
237
+ fontFamily: "monospace"
238
+ },
239
+ rightSection: {
240
+ alignItems: "flex-end",
241
+ justifyContent: "center",
242
+ marginRight: 4
243
+ },
244
+ rightTopRow: {
245
+ flexDirection: "row",
246
+ alignItems: "center",
247
+ gap: 6,
248
+ marginBottom: 2
249
+ },
250
+ rightBottomRow: {
251
+ flexDirection: "row",
252
+ alignItems: "center",
253
+ gap: 4
254
+ },
255
+ statusBadge: {
256
+ paddingHorizontal: 4,
257
+ paddingVertical: 1,
258
+ borderRadius: 3
259
+ },
260
+ statusText: {
261
+ fontSize: 10,
262
+ fontWeight: "600"
263
+ },
264
+ pendingBadge: {
265
+ flexDirection: "row",
266
+ alignItems: "center",
267
+ gap: 2,
268
+ paddingHorizontal: 4,
269
+ paddingVertical: 1,
270
+ backgroundColor: _sharedUi.macOSColors.semantic.warning + "26",
271
+ borderRadius: 3
272
+ },
273
+ pendingText: {
274
+ fontSize: 10,
275
+ color: _sharedUi.macOSColors.semantic.warning,
276
+ fontWeight: "600"
277
+ },
278
+ errorBadge: {
279
+ flexDirection: "row",
280
+ alignItems: "center",
281
+ gap: 2,
282
+ paddingHorizontal: 4,
283
+ paddingVertical: 1,
284
+ backgroundColor: _sharedUi.macOSColors.semantic.error + "26",
285
+ borderRadius: 3
286
+ },
287
+ errorText: {
288
+ fontSize: 10,
289
+ color: _sharedUi.macOSColors.semantic.error,
290
+ fontWeight: "600"
291
+ },
292
+ durationText: {
293
+ fontSize: 9,
294
+ color: _sharedUi.macOSColors.text.secondary
295
+ },
296
+ sizeRow: {
297
+ flexDirection: "row",
298
+ gap: 4,
299
+ marginTop: 4
300
+ },
301
+ sizeItem: {
302
+ flexDirection: "row",
303
+ alignItems: "center",
304
+ gap: 2
305
+ },
306
+ sizeText: {
307
+ fontSize: 8,
308
+ color: _sharedUi.macOSColors.text.secondary,
309
+ fontFamily: "monospace"
310
+ },
311
+ clientBadge: {
312
+ paddingHorizontal: 4,
313
+ paddingVertical: 2,
314
+ borderRadius: 3,
315
+ marginTop: 4
316
+ },
317
+ clientText: {
318
+ fontSize: 8,
319
+ fontWeight: "700",
320
+ letterSpacing: 0.3,
321
+ textTransform: "uppercase"
322
+ }
323
+ });
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.NetworkFilterViewV3 = NetworkFilterViewV3;
7
+ var _sharedUi = require("@buoy-gg/shared-ui");
8
+ var _react = require("react");
9
+ var _jsxRuntime = require("react/jsx-runtime");
10
+ function getContentType(event) {
11
+ const headers = event.responseHeaders || event.requestHeaders;
12
+ const contentType = headers?.["content-type"] || headers?.["Content-Type"] || "";
13
+ if (contentType.includes("json")) return {
14
+ type: "JSON",
15
+ color: _sharedUi.macOSColors.semantic.info
16
+ };
17
+ if (contentType.includes("xml")) return {
18
+ type: "XML",
19
+ color: _sharedUi.macOSColors.semantic.success
20
+ };
21
+ if (contentType.includes("html")) return {
22
+ type: "HTML",
23
+ color: _sharedUi.macOSColors.semantic.warning
24
+ };
25
+ if (contentType.includes("text")) return {
26
+ type: "TEXT",
27
+ color: _sharedUi.macOSColors.semantic.success
28
+ };
29
+ if (contentType.includes("image")) return {
30
+ type: "IMAGE",
31
+ color: _sharedUi.macOSColors.semantic.error
32
+ };
33
+ if (contentType.includes("video")) return {
34
+ type: "VIDEO",
35
+ color: _sharedUi.macOSColors.semantic.error
36
+ };
37
+ if (contentType.includes("audio")) return {
38
+ type: "AUDIO",
39
+ color: _sharedUi.macOSColors.semantic.debug
40
+ };
41
+ if (contentType.includes("form")) return {
42
+ type: "FORM",
43
+ color: _sharedUi.macOSColors.semantic.info
44
+ };
45
+ return {
46
+ type: "OTHER",
47
+ color: _sharedUi.macOSColors.text.muted
48
+ };
49
+ }
50
+ function getContentTypeIcon(type) {
51
+ switch (type) {
52
+ case "JSON":
53
+ return _sharedUi.FileJson;
54
+ case "HTML":
55
+ case "XML":
56
+ case "TEXT":
57
+ return _sharedUi.FileText;
58
+ case "IMAGE":
59
+ return _sharedUi.Image;
60
+ case "VIDEO":
61
+ return _sharedUi.Film;
62
+ case "AUDIO":
63
+ return _sharedUi.Music;
64
+ default:
65
+ return _sharedUi.Globe;
66
+ }
67
+ }
68
+ function getMethodColor(method) {
69
+ switch (method) {
70
+ case "GET":
71
+ return _sharedUi.macOSColors.semantic.success;
72
+ case "POST":
73
+ return _sharedUi.macOSColors.semantic.info;
74
+ case "PUT":
75
+ return _sharedUi.macOSColors.semantic.warning;
76
+ case "DELETE":
77
+ return _sharedUi.macOSColors.semantic.error;
78
+ case "PATCH":
79
+ return _sharedUi.macOSColors.semantic.success;
80
+ default:
81
+ return _sharedUi.macOSColors.text.muted;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Control panel for filtering captured network events by status, method, host, and content type.
87
+ * Provides quick toggles, stats, and ignored-pattern management reminiscent of desktop tooling.
88
+ */
89
+ function NetworkFilterViewV3({
90
+ events,
91
+ filter,
92
+ onFilterChange,
93
+ ignoredPatterns = new Set(),
94
+ onTogglePattern = () => {},
95
+ onAddPattern = () => {}
96
+ }) {
97
+ const statusCounts = (0, _react.useMemo)(() => {
98
+ const counts = {
99
+ all: events.length,
100
+ success: 0,
101
+ error: 0,
102
+ pending: 0
103
+ };
104
+ events.forEach(event => {
105
+ if (event.error || event.status && event.status >= 400) {
106
+ counts.error += 1;
107
+ return;
108
+ }
109
+ if (event.status && event.status >= 200 && event.status < 300) {
110
+ counts.success += 1;
111
+ return;
112
+ }
113
+ if (!event.status && !event.error) {
114
+ counts.pending += 1;
115
+ }
116
+ });
117
+ return counts;
118
+ }, [events]);
119
+ const methodCounts = (0, _react.useMemo)(() => {
120
+ return events.reduce((acc, event) => {
121
+ if (!event.method) return acc;
122
+ acc[event.method] = (acc[event.method] || 0) + 1;
123
+ return acc;
124
+ }, {});
125
+ }, [events]);
126
+ const contentTypeCounts = (0, _react.useMemo)(() => {
127
+ return events.reduce((acc, event) => {
128
+ const {
129
+ type
130
+ } = getContentType(event);
131
+ acc[type] = (acc[type] || 0) + 1;
132
+ return acc;
133
+ }, {});
134
+ }, [events]);
135
+ const availableDomains = (0, _react.useMemo)(() => {
136
+ const domains = new Set();
137
+ events.forEach(event => {
138
+ if (event.host) {
139
+ domains.add(event.host);
140
+ return;
141
+ }
142
+ try {
143
+ if (event.url) {
144
+ const parsed = new URL(event.url);
145
+ const host = String(parsed.host || "");
146
+ if (host) domains.add(host);
147
+ }
148
+ } catch {
149
+ // Ignore relative URLs
150
+ }
151
+ });
152
+ return Array.from(domains).filter(domain => domain && domain !== "").sort((a, b) => a.localeCompare(b));
153
+ }, [events]);
154
+ const availableUrls = (0, _react.useMemo)(() => {
155
+ const urls = new Set();
156
+ events.forEach(event => {
157
+ if (!event.url) return;
158
+ try {
159
+ const parsed = new URL(event.url);
160
+ const origin = String(parsed.origin || "");
161
+ const pathname = String(parsed.pathname || "");
162
+ const normalized = `${origin}${pathname}`;
163
+ urls.add(normalized);
164
+ } catch {
165
+ // URL constructor fails for relative paths - fall back to raw value
166
+ urls.add(event.url);
167
+ }
168
+ });
169
+ return Array.from(urls).filter(url => url && url !== "").sort((a, b) => a.localeCompare(b));
170
+ }, [events]);
171
+ const suggestionItems = (0, _react.useMemo)(() => {
172
+ const maxItemsPerGroup = 30;
173
+ const domainSuggestions = availableDomains.slice(0, maxItemsPerGroup);
174
+ const urlSuggestions = availableUrls.slice(0, maxItemsPerGroup);
175
+ return [...domainSuggestions, ...urlSuggestions];
176
+ }, [availableDomains, availableUrls]);
177
+ const handleDynamicFilterChange = (0, _react.useCallback)((optionId, value) => {
178
+ const [group] = optionId.split("::");
179
+ if (group === "status") {
180
+ const nextStatus = value === "all" ? undefined : value;
181
+ onFilterChange({
182
+ ...filter,
183
+ status: nextStatus
184
+ });
185
+ return;
186
+ }
187
+ if (group === "method") {
188
+ const methodValue = String(value);
189
+ const currentMethods = filter.method || [];
190
+ const hasMethod = currentMethods.includes(methodValue);
191
+ const updatedMethods = hasMethod ? currentMethods.filter(method => method !== methodValue) : [methodValue];
192
+ onFilterChange({
193
+ ...filter,
194
+ method: updatedMethods.length > 0 ? updatedMethods : undefined
195
+ });
196
+ return;
197
+ }
198
+ if (group === "contentType") {
199
+ const typeValue = String(value);
200
+ const currentTypes = filter.contentType || [];
201
+ const hasType = currentTypes.includes(typeValue);
202
+ const updatedTypes = hasType ? currentTypes.filter(type => type !== typeValue) : [typeValue];
203
+ onFilterChange({
204
+ ...filter,
205
+ contentType: updatedTypes.length > 0 ? updatedTypes : undefined
206
+ });
207
+ }
208
+ }, [filter, onFilterChange]);
209
+ const filterSections = (0, _react.useMemo)(() => {
210
+ const sections = [];
211
+ sections.push({
212
+ id: "status",
213
+ title: "Status",
214
+ type: "status",
215
+ data: ["all", "success", "error", "pending"].map(status => ({
216
+ id: `status::${status}`,
217
+ label: status.charAt(0).toUpperCase() + status.slice(1),
218
+ count: statusCounts[status],
219
+ icon: status === "success" ? _sharedUi.CheckCircle : status === "error" ? _sharedUi.XCircle : status === "pending" ? _sharedUi.Clock : _sharedUi.Globe,
220
+ color: status === "success" ? _sharedUi.macOSColors.semantic.success : status === "error" ? _sharedUi.macOSColors.semantic.error : status === "pending" ? _sharedUi.macOSColors.semantic.warning : _sharedUi.macOSColors.semantic.info,
221
+ isActive: filter.status === status || !filter.status && status === "all",
222
+ value: status
223
+ }))
224
+ });
225
+ const methodEntries = Object.entries(methodCounts);
226
+ if (methodEntries.length > 0) {
227
+ sections.push({
228
+ id: "method",
229
+ title: "Method",
230
+ type: "method",
231
+ data: methodEntries.map(([method, count]) => ({
232
+ id: `method::${method}`,
233
+ label: method,
234
+ count,
235
+ color: getMethodColor(method),
236
+ isActive: filter.method?.includes(method) ?? false,
237
+ value: method
238
+ }))
239
+ });
240
+ }
241
+ const contentTypeEntries = Object.entries(contentTypeCounts);
242
+ if (contentTypeEntries.length > 0) {
243
+ sections.push({
244
+ id: "contentType",
245
+ title: "Content Type",
246
+ type: "contentType",
247
+ data: contentTypeEntries.map(([type, count]) => {
248
+ const representativeEvent = events.find(event => getContentType(event).type === type);
249
+ const {
250
+ color
251
+ } = representativeEvent ? getContentType(representativeEvent) : {
252
+ color: _sharedUi.macOSColors.text.muted
253
+ };
254
+ return {
255
+ id: `contentType::${type}`,
256
+ label: type,
257
+ count,
258
+ icon: getContentTypeIcon(type),
259
+ color,
260
+ isActive: filter.contentType?.includes(type) ?? false,
261
+ value: type
262
+ };
263
+ })
264
+ });
265
+ }
266
+ return sections;
267
+ }, [contentTypeCounts, events, filter.contentType, filter.method, filter.status, methodCounts, statusCounts]);
268
+ const dynamicFilterConfig = (0, _react.useMemo)(() => ({
269
+ sections: filterSections,
270
+ addFilterSection: {
271
+ enabled: true,
272
+ placeholder: "Enter domain or URL pattern...",
273
+ title: "ACTIVE FILTERS",
274
+ icon: _sharedUi.Filter
275
+ },
276
+ availableItemsSection: {
277
+ enabled: true,
278
+ title: "AVAILABLE DOMAINS & URLS",
279
+ emptyMessage: "No network events captured yet. Domains and URLs will appear here.",
280
+ items: suggestionItems
281
+ },
282
+ howItWorksSection: {
283
+ enabled: true,
284
+ title: "HOW NETWORK FILTERS WORK",
285
+ description: "Patterns hide matching requests from the network event list. Filters match if the domain or URL contains the provided text.",
286
+ examples: ["• example.com → filters any request whose host includes example.com", "• https://api.example.com/v1/users → filters that exact endpoint", "• /health → filters any URL path containing /health"],
287
+ icon: _sharedUi.Filter
288
+ },
289
+ onFilterChange: handleDynamicFilterChange,
290
+ onPatternAdd: onAddPattern,
291
+ onPatternToggle: onTogglePattern,
292
+ activePatterns: ignoredPatterns
293
+ }), [filterSections, handleDynamicFilterChange, ignoredPatterns, onAddPattern, onTogglePattern, suggestionItems]);
294
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DynamicFilterView, {
295
+ ...dynamicFilterConfig
296
+ });
297
+ }