@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.
- package/README.md +381 -0
- package/lib/commonjs/index.js +34 -0
- package/lib/commonjs/network/components/NetworkCopySettingsView.js +867 -0
- package/lib/commonjs/network/components/NetworkEventDetailView.js +837 -0
- package/lib/commonjs/network/components/NetworkEventItemCompact.js +323 -0
- package/lib/commonjs/network/components/NetworkFilterViewV3.js +297 -0
- package/lib/commonjs/network/components/NetworkModal.js +937 -0
- package/lib/commonjs/network/hooks/useNetworkEvents.js +320 -0
- package/lib/commonjs/network/hooks/useTickEveryMinute.js +34 -0
- package/lib/commonjs/network/index.js +102 -0
- package/lib/commonjs/network/types/index.js +1 -0
- package/lib/commonjs/network/utils/extractOperationName.js +80 -0
- package/lib/commonjs/network/utils/formatGraphQLVariables.js +219 -0
- package/lib/commonjs/network/utils/formatting.js +30 -0
- package/lib/commonjs/network/utils/networkEventStore.js +269 -0
- package/lib/commonjs/network/utils/networkListener.js +801 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +83 -0
- package/lib/module/index.js +7 -0
- package/lib/module/network/components/NetworkCopySettingsView.js +862 -0
- package/lib/module/network/components/NetworkEventDetailView.js +834 -0
- package/lib/module/network/components/NetworkEventItemCompact.js +320 -0
- package/lib/module/network/components/NetworkFilterViewV3.js +293 -0
- package/lib/module/network/components/NetworkModal.js +933 -0
- package/lib/module/network/hooks/useNetworkEvents.js +316 -0
- package/lib/module/network/hooks/useTickEveryMinute.js +29 -0
- package/lib/module/network/index.js +20 -0
- package/lib/module/network/types/index.js +1 -0
- package/lib/module/network/utils/extractOperationName.js +76 -0
- package/lib/module/network/utils/formatGraphQLVariables.js +213 -0
- package/lib/module/network/utils/formatting.js +9 -0
- package/lib/module/network/utils/networkEventStore.js +265 -0
- package/lib/module/network/utils/networkListener.js +791 -0
- package/lib/module/preset.js +79 -0
- package/lib/typescript/index.d.ts +3 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkCopySettingsView.d.ts +26 -0
- package/lib/typescript/network/components/NetworkCopySettingsView.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts +13 -0
- package/lib/typescript/network/components/NetworkEventDetailView.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts +12 -0
- package/lib/typescript/network/components/NetworkEventItemCompact.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkFilterViewV3.d.ts +22 -0
- package/lib/typescript/network/components/NetworkFilterViewV3.d.ts.map +1 -0
- package/lib/typescript/network/components/NetworkModal.d.ts +14 -0
- package/lib/typescript/network/components/NetworkModal.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useNetworkEvents.d.ts +72 -0
- package/lib/typescript/network/hooks/useNetworkEvents.d.ts.map +1 -0
- package/lib/typescript/network/hooks/useTickEveryMinute.d.ts +9 -0
- package/lib/typescript/network/hooks/useTickEveryMinute.d.ts.map +1 -0
- package/lib/typescript/network/index.d.ts +12 -0
- package/lib/typescript/network/index.d.ts.map +1 -0
- package/lib/typescript/network/types/index.d.ts +88 -0
- package/lib/typescript/network/types/index.d.ts.map +1 -0
- package/lib/typescript/network/utils/extractOperationName.d.ts +41 -0
- package/lib/typescript/network/utils/extractOperationName.d.ts.map +1 -0
- package/lib/typescript/network/utils/formatGraphQLVariables.d.ts +79 -0
- package/lib/typescript/network/utils/formatGraphQLVariables.d.ts.map +1 -0
- package/lib/typescript/network/utils/formatting.d.ts +6 -0
- package/lib/typescript/network/utils/formatting.d.ts.map +1 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts +81 -0
- package/lib/typescript/network/utils/networkEventStore.d.ts.map +1 -0
- package/lib/typescript/network/utils/networkListener.d.ts +191 -0
- package/lib/typescript/network/utils/networkListener.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +76 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- 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
|
+
}
|