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