import type { HTTPRequest } from '../types/httpRequest' export interface CSVExportOptions { includeHeaders?: boolean selectedColumns?: string[] } const DEFAULT_COLUMNS = [ 'method', 'url', 'hostname', 'statusCode', 'resourceType', 'priority', 'protocol', 'connectionNumber', 'requestNumberOnConnection', 'timing.start', 'timing.queueTime', 'dnsTime', 'connectionTime', 'timing.serverLatency', 'timing.duration', 'timing.totalResponseTime', 'dataRate', 'size', 'contentLength', 'fromCache', 'connectionReused', 'cdnAnalysis.provider', 'queueAnalysis.reason' ] // Format a value for CSV output const formatCSVValue = (value: any): string => { if (value === null || value === undefined) { return '' } if (typeof value === 'number') { return value.toString() } if (typeof value === 'boolean') { return value ? 'true' : 'false' } const stringValue = value.toString() // Escape quotes and wrap in quotes if contains comma, quote, or newline if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) { return `"${stringValue.replace(/"/g, '""')}"` } return stringValue } // Calculate derived values for CSV export const calculateDerivedValue = (request: HTTPRequest, path: string): any => { switch (path) { case 'dnsTime': if (request.timing.dnsStart !== undefined && request.timing.dnsEnd !== undefined && request.timing.dnsStart >= 0 && request.timing.dnsEnd >= 0) { return request.timing.dnsEnd - request.timing.dnsStart } return null case 'connectionTime': if (request.timing.connectStart !== undefined && request.timing.connectEnd !== undefined && request.timing.connectStart >= 0 && request.timing.connectEnd >= 0) { return request.timing.connectEnd - request.timing.connectStart } return null case 'dataRate': if (!request.timing.duration || request.timing.duration <= 0) return null const bytes = request.contentLength && request.contentLength > 0 ? request.contentLength : request.encodedDataLength if (!bytes) return null const durationSeconds = request.timing.duration / 1000000 return bytes / durationSeconds // bytes per second case 'size': return request.encodedDataLength || request.contentLength || null default: return getNestedValue(request, path) } } // Get nested property value from object const getNestedValue = (obj: any, path: string): any => { return path.split('.').reduce((current, key) => { return current && current[key] !== undefined ? current[key] : null }, obj) } // Convert HTTP requests to CSV format export const exportRequestsToCSV = ( requests: HTTPRequest[], options: CSVExportOptions = {} ): string => { const { includeHeaders = true, selectedColumns = DEFAULT_COLUMNS } = options if (requests.length === 0) { return includeHeaders ? selectedColumns.join(',') + '\n' : '' } const lines: string[] = [] // Add headers if requested if (includeHeaders) { const headers = selectedColumns.map(col => { // Add units to timing-related headers switch (col) { case 'timing.start': return 'Start Time (μs)' case 'timing.queueTime': return 'Queue Time (μs)' case 'dnsTime': return 'DNS Time (μs)' case 'connectionTime': return 'Connection Time (μs)' case 'timing.serverLatency': return 'Server Latency (μs)' case 'timing.duration': return 'Duration (μs)' case 'timing.totalResponseTime': return 'Total Response Time (μs)' case 'timing.downloadTime': return 'Download Time (μs)' case 'dataRate': return 'Data Rate (bytes/sec)' case 'size': return 'Size (bytes)' case 'contentLength': return 'Content Length (bytes)' case 'encodedDataLength': return 'Encoded Data Length (bytes)' default: // Clean up column names for other headers return col .replace(/([A-Z])/g, ' $1') // Add space before capitals .replace(/^\w/, c => c.toUpperCase()) // Capitalize first letter .replace(/\./g, ' ') // Replace dots with spaces .trim() } }) lines.push(headers.map(formatCSVValue).join(',')) } // Add data rows for (const request of requests) { const values = selectedColumns.map(column => { const value = calculateDerivedValue(request, column) return formatCSVValue(value) }) lines.push(values.join(',')) } return lines.join('\n') } // Download CSV file export const downloadCSV = (csvContent: string, filename: string = 'http-requests.csv'): void => { const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') if (link.download !== undefined) { // Use HTML5 download attribute const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', filename) link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } } // Get all available columns from HTTP requests export const getAvailableColumns = (requests: HTTPRequest[]): string[] => { if (requests.length === 0) { return DEFAULT_COLUMNS } const columns = new Set() const extractColumns = (obj: any, prefix = ''): void => { Object.keys(obj).forEach(key => { const fullKey = prefix ? `${prefix}.${key}` : key const value = obj[key] if (value !== null && typeof value === 'object' && !Array.isArray(value)) { // Recursively extract nested properties extractColumns(value, fullKey) } else { columns.add(fullKey) } }) } // Extract columns from first request extractColumns(requests[0]) return Array.from(columns).sort() }