@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,867 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.DEFAULT_COPY_SETTINGS = void 0;
7
+ exports.NetworkCopySettingsView = NetworkCopySettingsView;
8
+ var _sharedUi = require("@buoy-gg/shared-ui");
9
+ var _dataViewer = require("@buoy-gg/shared-ui/dataViewer");
10
+ var _react = require("react");
11
+ var _reactNative = require("react-native");
12
+ var _useTickEveryMinute = require("../hooks/useTickEveryMinute");
13
+ var _jsxRuntime = require("react/jsx-runtime");
14
+ const DEFAULT_COPY_SETTINGS = exports.DEFAULT_COPY_SETTINGS = {
15
+ includeMethod: true,
16
+ includeStatus: true,
17
+ includeDuration: true,
18
+ includeTimestamp: true,
19
+ includeClient: true,
20
+ includeSizes: true,
21
+ includeErrors: true,
22
+ includeRequestHeaders: true,
23
+ includeResponseHeaders: true,
24
+ includeRequestBody: true,
25
+ includeResponseBody: true,
26
+ bodySizeThreshold: 10,
27
+ format: "markdown",
28
+ filterMode: "all"
29
+ };
30
+
31
+ // Preset configurations for comparison
32
+ const PRESET_CONFIGS = {
33
+ urls: {
34
+ includeMethod: true,
35
+ includeStatus: false,
36
+ includeDuration: false,
37
+ includeTimestamp: false,
38
+ includeClient: false,
39
+ includeSizes: false,
40
+ includeErrors: false,
41
+ includeRequestHeaders: false,
42
+ includeResponseHeaders: false,
43
+ includeRequestBody: false,
44
+ includeResponseBody: false,
45
+ bodySizeThreshold: 10,
46
+ format: "plaintext",
47
+ filterMode: "all"
48
+ },
49
+ llm: {
50
+ includeMethod: true,
51
+ includeStatus: true,
52
+ includeDuration: true,
53
+ includeTimestamp: true,
54
+ includeClient: true,
55
+ includeSizes: true,
56
+ includeErrors: true,
57
+ includeRequestHeaders: true,
58
+ includeResponseHeaders: true,
59
+ includeRequestBody: true,
60
+ includeResponseBody: true,
61
+ bodySizeThreshold: 10,
62
+ format: "markdown",
63
+ filterMode: "all"
64
+ },
65
+ json: {
66
+ includeMethod: true,
67
+ includeStatus: true,
68
+ includeDuration: true,
69
+ includeTimestamp: true,
70
+ includeClient: true,
71
+ includeSizes: true,
72
+ includeErrors: true,
73
+ includeRequestHeaders: true,
74
+ includeResponseHeaders: true,
75
+ includeRequestBody: true,
76
+ includeResponseBody: true,
77
+ bodySizeThreshold: -1,
78
+ format: "json",
79
+ filterMode: "all"
80
+ },
81
+ full: {
82
+ includeMethod: true,
83
+ includeStatus: true,
84
+ includeDuration: true,
85
+ includeTimestamp: true,
86
+ includeClient: true,
87
+ includeSizes: true,
88
+ includeErrors: true,
89
+ includeRequestHeaders: true,
90
+ includeResponseHeaders: true,
91
+ includeRequestBody: true,
92
+ includeResponseBody: true,
93
+ bodySizeThreshold: -1,
94
+ format: "markdown",
95
+ filterMode: "all"
96
+ }
97
+ };
98
+ // Detect which preset matches current settings
99
+ function detectActivePreset(settings) {
100
+ for (const [presetName, presetConfig] of Object.entries(PRESET_CONFIGS)) {
101
+ const matches = Object.keys(presetConfig).every(key => presetConfig[key] === settings[key]);
102
+ if (matches) return presetName;
103
+ }
104
+ return null; // No preset matches = Custom
105
+ }
106
+
107
+ // Size thresholds for warnings
108
+ const SIZE_WARNING_THRESHOLD = 100 * 1024; // 100KB - show warning
109
+ const SIZE_DANGER_THRESHOLD = 500 * 1024; // 500KB - show danger warning
110
+
111
+ // Helper to format bytes for display
112
+ function formatBytes(bytes) {
113
+ if (bytes < 1024) return `${bytes} B`;
114
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
115
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
116
+ }
117
+ function NetworkCopySettingsView({
118
+ settings,
119
+ onSettingsChange,
120
+ events = []
121
+ }) {
122
+ // Check Pro status for gating
123
+ const {
124
+ isPro
125
+ } = (0, _sharedUi.useFeatureGate)();
126
+
127
+ // Use tick for updating relative time
128
+ const tick = (0, _useTickEveryMinute.useTickEveryMinute)();
129
+
130
+ // State for preview expansion - collapsed by default to prevent crashes
131
+ const [isPreviewExpanded, setIsPreviewExpanded] = (0, _react.useState)(false);
132
+
133
+ // Auto-detect if we have live data
134
+ const hasLiveData = events.length > 0;
135
+
136
+ // Get the most recent event timestamp for "last updated" display
137
+ const lastEventTimestamp = (0, _react.useMemo)(() => {
138
+ if (!hasLiveData) return null;
139
+ const timestamps = events.map(e => typeof e.timestamp === 'number' ? e.timestamp : new Date(e.timestamp).getTime());
140
+ return Math.max(...timestamps);
141
+ }, [events, hasLiveData]);
142
+
143
+ // Detect active preset
144
+ const activePreset = (0, _react.useMemo)(() => detectActivePreset(settings), [settings]);
145
+
146
+ // Quick preset handlers
147
+ const applyUrlsPreset = (0, _react.useCallback)(() => {
148
+ onSettingsChange(PRESET_CONFIGS.urls);
149
+ }, [onSettingsChange]);
150
+ const applyLLMPreset = (0, _react.useCallback)(() => {
151
+ onSettingsChange(PRESET_CONFIGS.llm);
152
+ }, [onSettingsChange]);
153
+ const applyJSONPreset = (0, _react.useCallback)(() => {
154
+ onSettingsChange(PRESET_CONFIGS.json);
155
+ }, [onSettingsChange]);
156
+ const applyFullPreset = (0, _react.useCallback)(() => {
157
+ onSettingsChange(PRESET_CONFIGS.full);
158
+ }, [onSettingsChange]);
159
+
160
+ // Handle individual option changes
161
+ const handleOptionChange = (0, _react.useCallback)((optionId, value) => {
162
+ const [group, key] = optionId.split("::");
163
+ if (group === "preset") {
164
+ switch (key) {
165
+ case "urls":
166
+ applyUrlsPreset();
167
+ break;
168
+ case "llm":
169
+ applyLLMPreset();
170
+ break;
171
+ case "json":
172
+ applyJSONPreset();
173
+ break;
174
+ case "full":
175
+ applyFullPreset();
176
+ break;
177
+ }
178
+ return;
179
+ }
180
+ if (group === "requestInfo") {
181
+ // Toggle boolean value
182
+ onSettingsChange({
183
+ ...settings,
184
+ [key]: !settings[key]
185
+ });
186
+ return;
187
+ }
188
+ if (group === "headers") {
189
+ // Toggle boolean value
190
+ onSettingsChange({
191
+ ...settings,
192
+ [key]: !settings[key]
193
+ });
194
+ return;
195
+ }
196
+ if (group === "body") {
197
+ if (key === "bodySizeThreshold") {
198
+ onSettingsChange({
199
+ ...settings,
200
+ bodySizeThreshold: value
201
+ });
202
+ } else {
203
+ // Toggle boolean value
204
+ onSettingsChange({
205
+ ...settings,
206
+ [key]: !settings[key]
207
+ });
208
+ }
209
+ return;
210
+ }
211
+ if (group === "format") {
212
+ onSettingsChange({
213
+ ...settings,
214
+ format: value
215
+ });
216
+ return;
217
+ }
218
+ if (group === "filter") {
219
+ onSettingsChange({
220
+ ...settings,
221
+ filterMode: value
222
+ });
223
+ }
224
+ }, [settings, onSettingsChange, applyUrlsPreset, applyLLMPreset, applyJSONPreset, applyFullPreset]);
225
+
226
+ // Mock data for preview
227
+ const mockRequestData = (0, _react.useMemo)(() => ({
228
+ method: "POST",
229
+ url: "https://api.example.com/v1/users",
230
+ status: 201,
231
+ duration: 342,
232
+ timestamp: new Date().toISOString(),
233
+ requestHeaders: {
234
+ "Content-Type": "application/json",
235
+ "Authorization": "Bearer ***"
236
+ },
237
+ responseHeaders: {
238
+ "Content-Type": "application/json",
239
+ "X-Request-ID": "abc123"
240
+ },
241
+ requestBody: {
242
+ name: "John Doe",
243
+ email: "john@example.com"
244
+ },
245
+ responseBody: {
246
+ id: "user_123",
247
+ name: "John Doe",
248
+ email: "john@example.com",
249
+ createdAt: "2025-01-10T12:00:00Z"
250
+ }
251
+ }), []);
252
+
253
+ // Get data source - auto-detect live vs mock
254
+ const previewData = (0, _react.useMemo)(() => {
255
+ if (hasLiveData) {
256
+ return events; // Show ALL events
257
+ }
258
+ // Mock data - return as array for consistency
259
+ return [mockRequestData];
260
+ }, [hasLiveData, events, mockRequestData]);
261
+
262
+ // Estimate the size of the preview content to warn users about large payloads
263
+ const estimatedSize = (0, _react.useMemo)(() => {
264
+ const dataSource = hasLiveData ? events : [mockRequestData];
265
+ let totalSize = 0;
266
+ for (const eventData of dataSource) {
267
+ // Estimate based on what's included
268
+ if (settings.includeMethod) {
269
+ totalSize += (eventData.url?.length || 0) + 10;
270
+ }
271
+ if (settings.includeStatus) {
272
+ totalSize += 30;
273
+ }
274
+ if (settings.includeTimestamp) {
275
+ totalSize += 30;
276
+ }
277
+ if (settings.includeRequestHeaders) {
278
+ try {
279
+ totalSize += JSON.stringify(eventData.requestHeaders || {}).length;
280
+ } catch {
281
+ totalSize += 100;
282
+ }
283
+ }
284
+ if (settings.includeRequestBody) {
285
+ const body = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
286
+ try {
287
+ totalSize += JSON.stringify(body || {}).length;
288
+ } catch {
289
+ totalSize += 100;
290
+ }
291
+ }
292
+ if (settings.includeResponseBody) {
293
+ const body = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
294
+ try {
295
+ totalSize += JSON.stringify(body || {}).length;
296
+ } catch {
297
+ totalSize += 100;
298
+ }
299
+ }
300
+ }
301
+ return totalSize;
302
+ }, [hasLiveData, events, mockRequestData, settings]);
303
+
304
+ // Determine warning level based on estimated size
305
+ const sizeWarningLevel = (0, _react.useMemo)(() => {
306
+ if (estimatedSize >= SIZE_DANGER_THRESHOLD) return 'danger';
307
+ if (estimatedSize >= SIZE_WARNING_THRESHOLD) return 'warning';
308
+ return 'none';
309
+ }, [estimatedSize]);
310
+
311
+ // Generate copy text - should match preview content exactly
312
+ const generateCopyText = (0, _react.useCallback)(() => {
313
+ const dataSource = hasLiveData ? events : [mockRequestData];
314
+ const {
315
+ format,
316
+ includeMethod,
317
+ includeStatus,
318
+ includeTimestamp,
319
+ includeRequestHeaders,
320
+ includeRequestBody,
321
+ includeResponseBody
322
+ } = settings;
323
+
324
+ // Build filtered data for a single event
325
+ const buildFilteredData = eventData => {
326
+ const data = {};
327
+ if (includeMethod) {
328
+ data.method = eventData.method;
329
+ data.url = eventData.url;
330
+ }
331
+ if (includeStatus) {
332
+ data.status = eventData.status;
333
+ data.duration = eventData.duration;
334
+ }
335
+ if (includeTimestamp) {
336
+ data.timestamp = eventData.timestamp;
337
+ }
338
+ if (includeRequestHeaders) {
339
+ data.requestHeaders = eventData.requestHeaders;
340
+ }
341
+ if (includeRequestBody) {
342
+ data.requestBody = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
343
+ }
344
+ if (includeResponseBody) {
345
+ data.responseBody = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
346
+ }
347
+ return data;
348
+ };
349
+ if (format === "json") {
350
+ const allFilteredData = dataSource.map(buildFilteredData);
351
+ return JSON.stringify(allFilteredData, null, 2);
352
+ }
353
+ if (format === "markdown") {
354
+ const allRequests = dataSource.map((eventData, index) => {
355
+ const sections = [];
356
+ sections.push(`# Request ${index + 1}`);
357
+ if (includeMethod) {
358
+ sections.push(`\n**${eventData.method}** ${eventData.url}`);
359
+ }
360
+ const metaInfo = [];
361
+ if (includeStatus) {
362
+ metaInfo.push(`**Status:** ${eventData.status}`);
363
+ metaInfo.push(`**Duration:** ${eventData.duration}ms`);
364
+ }
365
+ if (includeTimestamp) {
366
+ metaInfo.push(`**Timestamp:** ${eventData.timestamp}`);
367
+ }
368
+ if (metaInfo.length > 0) {
369
+ sections.push('\n' + metaInfo.join('\n'));
370
+ }
371
+ if (includeRequestHeaders) {
372
+ const headers = eventData.requestHeaders || {};
373
+ sections.push(`\n## Request Headers\n\`\`\`json\n${JSON.stringify(headers, null, 2)}\n\`\`\``);
374
+ }
375
+ if (includeRequestBody) {
376
+ const body = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
377
+ sections.push(`\n## Request Body\n\`\`\`json\n${JSON.stringify(body || {}, null, 2)}\n\`\`\``);
378
+ }
379
+ if (includeResponseBody) {
380
+ const body = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
381
+ sections.push(`\n## Response Body\n\`\`\`json\n${JSON.stringify(body || {}, null, 2)}\n\`\`\``);
382
+ }
383
+ return sections.join('\n');
384
+ });
385
+ return allRequests.join('\n\n---\n\n') || "No data included";
386
+ }
387
+
388
+ // Plaintext
389
+ const allRequests = dataSource.map((eventData, index) => {
390
+ const lines = [];
391
+ lines.push(`Request ${index + 1}:`);
392
+ if (includeMethod) {
393
+ lines.push(`${eventData.method} ${eventData.url}`);
394
+ }
395
+ if (includeStatus) {
396
+ lines.push(`Status: ${eventData.status}`);
397
+ lines.push(`Duration: ${eventData.duration}ms`);
398
+ }
399
+ if (includeTimestamp) {
400
+ lines.push(`Timestamp: ${eventData.timestamp}`);
401
+ }
402
+ if (includeRequestBody) {
403
+ const body = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
404
+ lines.push('\nRequest:');
405
+ lines.push(JSON.stringify(body || {}, null, 2));
406
+ }
407
+ if (includeResponseBody) {
408
+ const body = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
409
+ lines.push('\nResponse:');
410
+ lines.push(JSON.stringify(body || {}, null, 2));
411
+ }
412
+ return lines.join('\n');
413
+ });
414
+ return allRequests.join('\n\n---\n\n') || "No data included";
415
+ }, [hasLiveData, events, mockRequestData, settings]);
416
+
417
+ // Generate the expanded preview content (only called when expanded)
418
+ const generateExpandedPreviewContent = (0, _react.useCallback)(() => {
419
+ const dataSource = hasLiveData ? events : [mockRequestData];
420
+ const {
421
+ format,
422
+ includeMethod,
423
+ includeStatus,
424
+ includeTimestamp,
425
+ includeRequestHeaders,
426
+ includeRequestBody,
427
+ includeResponseBody
428
+ } = settings;
429
+
430
+ // Build filtered data for a single event
431
+ const buildFilteredData = eventData => {
432
+ const data = {};
433
+ if (includeMethod) {
434
+ data.method = eventData.method;
435
+ data.url = eventData.url;
436
+ }
437
+ if (includeStatus) {
438
+ data.status = eventData.status;
439
+ data.duration = eventData.duration;
440
+ }
441
+ if (includeTimestamp) {
442
+ data.timestamp = eventData.timestamp;
443
+ }
444
+ if (includeRequestHeaders) {
445
+ data.requestHeaders = eventData.requestHeaders;
446
+ }
447
+ if (includeRequestBody) {
448
+ data.requestBody = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
449
+ }
450
+ if (includeResponseBody) {
451
+ data.responseBody = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
452
+ }
453
+ return data;
454
+ };
455
+ if (format === "json") {
456
+ const allFilteredData = dataSource.map(buildFilteredData);
457
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_dataViewer.DataViewer, {
458
+ data: allFilteredData,
459
+ title: "",
460
+ showTypeFilter: false
461
+ });
462
+ }
463
+ if (format === "markdown") {
464
+ const allRequests = dataSource.map((eventData, index) => {
465
+ const sections = [];
466
+ sections.push(`# Request ${index + 1}`);
467
+ if (includeMethod) {
468
+ sections.push(`\n**${eventData.method}** ${eventData.url}`);
469
+ }
470
+ const metaInfo = [];
471
+ if (includeStatus) {
472
+ metaInfo.push(`**Status:** ${eventData.status}`);
473
+ metaInfo.push(`**Duration:** ${eventData.duration}ms`);
474
+ }
475
+ if (includeTimestamp) {
476
+ metaInfo.push(`**Timestamp:** ${eventData.timestamp}`);
477
+ }
478
+ if (metaInfo.length > 0) {
479
+ sections.push('\n' + metaInfo.join('\n'));
480
+ }
481
+ if (includeRequestHeaders) {
482
+ const headers = eventData.requestHeaders || {};
483
+ sections.push(`\n## Request Headers\n\`\`\`json\n${JSON.stringify(headers, null, 2)}\n\`\`\``);
484
+ }
485
+ if (includeRequestBody) {
486
+ const body = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
487
+ sections.push(`\n## Request Body\n\`\`\`json\n${JSON.stringify(body || {}, null, 2)}\n\`\`\``);
488
+ }
489
+ if (includeResponseBody) {
490
+ const body = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
491
+ sections.push(`\n## Response Body\n\`\`\`json\n${JSON.stringify(body || {}, null, 2)}\n\`\`\``);
492
+ }
493
+ return sections.join('\n');
494
+ });
495
+ const markdown = allRequests.join('\n\n---\n\n');
496
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
497
+ style: styles.previewScroll,
498
+ nestedScrollEnabled: true,
499
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
500
+ style: styles.markdownText,
501
+ selectable: isPro,
502
+ children: markdown || "No data included"
503
+ })
504
+ });
505
+ }
506
+
507
+ // Plaintext
508
+ const allRequests = dataSource.map((eventData, index) => {
509
+ const lines = [];
510
+ lines.push(`Request ${index + 1}:`);
511
+ if (includeMethod) {
512
+ lines.push(`${eventData.method} ${eventData.url}`);
513
+ }
514
+ if (includeStatus) {
515
+ lines.push(`Status: ${eventData.status}`);
516
+ lines.push(`Duration: ${eventData.duration}ms`);
517
+ }
518
+ if (includeTimestamp) {
519
+ lines.push(`Timestamp: ${eventData.timestamp}`);
520
+ }
521
+ if (includeRequestBody) {
522
+ const body = 'requestBody' in eventData ? eventData.requestBody : eventData.requestData;
523
+ lines.push('\nRequest:');
524
+ lines.push(JSON.stringify(body || {}, null, 2));
525
+ }
526
+ if (includeResponseBody) {
527
+ const body = 'responseBody' in eventData ? eventData.responseBody : eventData.responseData;
528
+ lines.push('\nResponse:');
529
+ lines.push(JSON.stringify(body || {}, null, 2));
530
+ }
531
+ return lines.join('\n');
532
+ });
533
+ const plaintext = allRequests.join('\n\n---\n\n');
534
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
535
+ style: styles.previewScroll,
536
+ nestedScrollEnabled: true,
537
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
538
+ style: styles.plaintextText,
539
+ selectable: isPro,
540
+ children: plaintext || "No data included"
541
+ })
542
+ });
543
+ }, [settings, hasLiveData, events, mockRequestData, isPro]);
544
+
545
+ // Generate formatted output for copying/preview - with collapsible support
546
+ const generatePreviewContent = (0, _react.useCallback)(() => {
547
+ const dataSource = hasLiveData ? events : [mockRequestData];
548
+ const requestCount = dataSource.length;
549
+
550
+ // Collapsed state - show summary with expand button
551
+ if (!isPreviewExpanded) {
552
+ const warningColor = sizeWarningLevel === 'danger' ? _sharedUi.macOSColors.semantic.error : sizeWarningLevel === 'warning' ? _sharedUi.macOSColors.semantic.warning : _sharedUi.macOSColors.text.secondary;
553
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
554
+ style: styles.collapsedPreview,
555
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
556
+ style: styles.expandButton,
557
+ onPress: () => setIsPreviewExpanded(true),
558
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
559
+ style: styles.expandButtonContent,
560
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.Eye, {
561
+ size: 16,
562
+ color: _sharedUi.macOSColors.semantic.info
563
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
564
+ style: styles.expandButtonTextContainer,
565
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
566
+ style: styles.expandButtonTitle,
567
+ children: "Show Preview"
568
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
569
+ style: styles.expandButtonSubtitle,
570
+ children: [requestCount, " request", requestCount !== 1 ? 's' : '', " \u2022 ~", formatBytes(estimatedSize)]
571
+ })]
572
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronDown, {
573
+ size: 16,
574
+ color: _sharedUi.macOSColors.text.muted
575
+ })]
576
+ })
577
+ }), sizeWarningLevel !== 'none' && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
578
+ style: [styles.sizeWarning, sizeWarningLevel === 'danger' && styles.sizeWarningDanger],
579
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.AlertTriangle, {
580
+ size: 12,
581
+ color: warningColor
582
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
583
+ style: [styles.sizeWarningText, {
584
+ color: warningColor
585
+ }],
586
+ children: sizeWarningLevel === 'danger' ? 'Very large payload - may cause performance issues' : 'Large payload - preview may be slow'
587
+ })]
588
+ })]
589
+ });
590
+ }
591
+
592
+ // Expanded state - show collapse button + actual content
593
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
594
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
595
+ style: styles.collapseButton,
596
+ onPress: () => setIsPreviewExpanded(false),
597
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
598
+ style: styles.collapseButtonText,
599
+ children: "Hide Preview"
600
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ChevronUp, {
601
+ size: 14,
602
+ color: _sharedUi.macOSColors.text.secondary
603
+ })]
604
+ }), generateExpandedPreviewContent()]
605
+ });
606
+ }, [settings, hasLiveData, events, mockRequestData, isPreviewExpanded, estimatedSize, sizeWarningLevel, generateExpandedPreviewContent]);
607
+
608
+ // Render preview header actions (status label and Copy button)
609
+ const renderPreviewHeaderActions = (0, _react.useCallback)(() => {
610
+ const statusLabel = hasLiveData && lastEventTimestamp ? `Live data • Updated ${(0, _sharedUi.formatRelativeTime)(lastEventTimestamp, tick)}` : "Mock data (no events captured)";
611
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
612
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
613
+ style: styles.statusLabel,
614
+ children: statusLabel
615
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolbarCopyButton, {
616
+ value: generateCopyText(),
617
+ disabled: !isPro
618
+ })]
619
+ });
620
+ }, [hasLiveData, lastEventTimestamp, tick, generateCopyText, isPro]);
621
+
622
+ // Render preview content only
623
+ const renderPreviewContent = (0, _react.useCallback)(() => {
624
+ return generatePreviewContent();
625
+ }, [generatePreviewContent]);
626
+ const dynamicFilterConfig = (0, _react.useMemo)(() => ({
627
+ sections: [
628
+ // Quick Presets Section - Hero element
629
+ {
630
+ id: "presets",
631
+ title: "Presets",
632
+ type: "custom",
633
+ data: [{
634
+ id: "preset::urls",
635
+ label: "URLs",
636
+ icon: _sharedUi.Link,
637
+ color: _sharedUi.macOSColors.semantic.info,
638
+ value: "urls",
639
+ isActive: activePreset === "urls"
640
+ }, {
641
+ id: "preset::llm",
642
+ label: "LLM",
643
+ icon: _sharedUi.Zap,
644
+ color: _sharedUi.macOSColors.semantic.success,
645
+ value: "llm",
646
+ isActive: activePreset === "llm"
647
+ }, {
648
+ id: "preset::json",
649
+ label: "JSON",
650
+ icon: _sharedUi.FileCode,
651
+ color: _sharedUi.macOSColors.semantic.warning,
652
+ value: "json",
653
+ isActive: activePreset === "json"
654
+ }, {
655
+ id: "preset::full",
656
+ label: "Full",
657
+ icon: _sharedUi.FileText,
658
+ color: _sharedUi.macOSColors.semantic.info,
659
+ value: "full",
660
+ isActive: activePreset === "full"
661
+ }, {
662
+ id: "preset::custom",
663
+ label: "Custom",
664
+ icon: _sharedUi.Settings,
665
+ color: _sharedUi.macOSColors.text.secondary,
666
+ value: "custom",
667
+ isActive: activePreset === null
668
+ }]
669
+ },
670
+ // Include Section - Consolidated
671
+ {
672
+ id: "include",
673
+ title: "Include",
674
+ type: "custom",
675
+ data: [{
676
+ id: "requestInfo::includeMethod",
677
+ label: "URL",
678
+ value: "includeMethod",
679
+ isActive: settings.includeMethod
680
+ }, {
681
+ id: "requestInfo::includeStatus",
682
+ label: "Status",
683
+ value: "includeStatus",
684
+ isActive: settings.includeStatus
685
+ }, {
686
+ id: "requestInfo::includeTimestamp",
687
+ label: "Time",
688
+ value: "includeTimestamp",
689
+ isActive: settings.includeTimestamp
690
+ }, {
691
+ id: "headers::includeRequestHeaders",
692
+ label: "Headers",
693
+ value: "includeRequestHeaders",
694
+ isActive: settings.includeRequestHeaders
695
+ }, {
696
+ id: "body::includeRequestBody",
697
+ label: "Req Body",
698
+ value: "includeRequestBody",
699
+ isActive: settings.includeRequestBody
700
+ }, {
701
+ id: "body::includeResponseBody",
702
+ label: "Res Body",
703
+ value: "includeResponseBody",
704
+ isActive: settings.includeResponseBody
705
+ }, {
706
+ id: "requestInfo::includeErrors",
707
+ label: "Errors",
708
+ value: "includeErrors",
709
+ isActive: settings.includeErrors
710
+ }]
711
+ },
712
+ // Output Format
713
+ {
714
+ id: "format",
715
+ title: "Format",
716
+ type: "custom",
717
+ data: [{
718
+ id: "format::markdown",
719
+ label: "Markdown",
720
+ icon: _sharedUi.FileText,
721
+ color: _sharedUi.macOSColors.semantic.info,
722
+ value: "markdown",
723
+ isActive: settings.format === "markdown"
724
+ }, {
725
+ id: "format::json",
726
+ label: "JSON",
727
+ icon: _sharedUi.FileCode,
728
+ color: _sharedUi.macOSColors.semantic.warning,
729
+ value: "json",
730
+ isActive: settings.format === "json"
731
+ }, {
732
+ id: "format::plaintext",
733
+ label: "Text",
734
+ icon: _sharedUi.Hash,
735
+ color: _sharedUi.macOSColors.text.secondary,
736
+ value: "plaintext",
737
+ isActive: settings.format === "plaintext"
738
+ }]
739
+ }],
740
+ previewSection: {
741
+ enabled: true,
742
+ title: "PREVIEW",
743
+ icon: _sharedUi.Eye,
744
+ content: renderPreviewContent,
745
+ headerActions: renderPreviewHeaderActions
746
+ },
747
+ onFilterChange: handleOptionChange
748
+ }), [settings, handleOptionChange, renderPreviewContent, renderPreviewHeaderActions, activePreset]);
749
+
750
+ // For free users, show banner at the top explaining this is a Pro feature
751
+ // Users can interact with controls but copy is blocked at the button level
752
+ if (!isPro) {
753
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
754
+ style: styles.gatedContainer,
755
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ProFeatureBanner, {
756
+ featureName: "Network Export",
757
+ description: "Export network requests to share with your team, debug APIs, or feed into AI tools.",
758
+ 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"]
759
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
760
+ style: styles.gatedPreview,
761
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DynamicFilterView, {
762
+ ...dynamicFilterConfig
763
+ })
764
+ })]
765
+ });
766
+ }
767
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.DynamicFilterView, {
768
+ ...dynamicFilterConfig
769
+ });
770
+ }
771
+ const styles = _reactNative.StyleSheet.create({
772
+ previewScroll: {
773
+ maxHeight: 300
774
+ },
775
+ markdownText: {
776
+ fontFamily: "monospace",
777
+ fontSize: 11,
778
+ color: _sharedUi.macOSColors.text.primary,
779
+ lineHeight: 18
780
+ },
781
+ plaintextText: {
782
+ fontFamily: "monospace",
783
+ fontSize: 11,
784
+ color: _sharedUi.macOSColors.text.primary,
785
+ lineHeight: 18
786
+ },
787
+ statusLabel: {
788
+ fontSize: 11,
789
+ fontWeight: "600",
790
+ color: _sharedUi.macOSColors.text.muted,
791
+ marginRight: 8
792
+ },
793
+ // Collapsible preview styles
794
+ collapsedPreview: {
795
+ gap: 8
796
+ },
797
+ expandButton: {
798
+ backgroundColor: _sharedUi.macOSColors.background.input,
799
+ borderRadius: 8,
800
+ borderWidth: 1,
801
+ borderColor: _sharedUi.macOSColors.border.default,
802
+ padding: 12
803
+ },
804
+ expandButtonContent: {
805
+ flexDirection: 'row',
806
+ alignItems: 'center',
807
+ gap: 10
808
+ },
809
+ expandButtonTextContainer: {
810
+ flex: 1
811
+ },
812
+ expandButtonTitle: {
813
+ fontSize: 13,
814
+ fontWeight: '600',
815
+ color: _sharedUi.macOSColors.text.primary
816
+ },
817
+ expandButtonSubtitle: {
818
+ fontSize: 11,
819
+ color: _sharedUi.macOSColors.text.secondary,
820
+ marginTop: 2
821
+ },
822
+ collapseButton: {
823
+ flexDirection: 'row',
824
+ alignItems: 'center',
825
+ justifyContent: 'center',
826
+ gap: 6,
827
+ paddingVertical: 8,
828
+ marginBottom: 8,
829
+ backgroundColor: _sharedUi.macOSColors.background.hover,
830
+ borderRadius: 6,
831
+ borderWidth: 1,
832
+ borderColor: _sharedUi.macOSColors.border.default
833
+ },
834
+ collapseButtonText: {
835
+ fontSize: 12,
836
+ fontWeight: '500',
837
+ color: _sharedUi.macOSColors.text.secondary
838
+ },
839
+ sizeWarning: {
840
+ flexDirection: 'row',
841
+ alignItems: 'center',
842
+ gap: 6,
843
+ paddingHorizontal: 10,
844
+ paddingVertical: 6,
845
+ backgroundColor: _sharedUi.macOSColors.semantic.warningBackground,
846
+ borderRadius: 6,
847
+ borderWidth: 1,
848
+ borderColor: _sharedUi.macOSColors.semantic.warning + '30'
849
+ },
850
+ sizeWarningDanger: {
851
+ backgroundColor: _sharedUi.macOSColors.semantic.errorBackground,
852
+ borderColor: _sharedUi.macOSColors.semantic.error + '30'
853
+ },
854
+ sizeWarningText: {
855
+ fontSize: 11,
856
+ fontWeight: '500',
857
+ flex: 1
858
+ },
859
+ // Gated container styles for free users
860
+ gatedContainer: {
861
+ flex: 1,
862
+ padding: 12
863
+ },
864
+ gatedPreview: {
865
+ flex: 1
866
+ }
867
+ });