feat: fix video recording session persistence and add HTTP request monitoring
Video Recording Fixes: - Fix session persistence issues where recording state was lost between tool calls - Improve page video object handling by triggering navigation when needed - Add browser_reveal_artifact_paths tool to show exact file locations - Enhance browser_recording_status with detailed debugging info and file listings - Add clearVideoRecordingState() method for proper state management - Keep recording config available for debugging until new session starts Request Monitoring System: - Add comprehensive RequestInterceptor class for HTTP traffic capture - Implement 5 new MCP tools for request monitoring and analysis - Support multiple export formats: JSON, HAR, CSV, and summary reports - Add filtering by domain, method, status codes, and response timing - Integrate with artifact storage for organized session-based file management - Enhance browser_network_requests with rich intercepted data Additional Improvements: - Add getBaseDirectory/getSessionDirectory methods to ArtifactManager - Fix floating promise in tab.ts extension console message polling - Add debug script for comprehensive video recording workflow testing - Update README with new tool documentation Resolves video recording workflow issues and adds powerful HTTP traffic analysis capabilities for web application debugging and security testing.
This commit is contained in:
parent
4ac76bd886
commit
9257404ba3
10 changed files with 1728 additions and 29 deletions
560
src/tools/requests.ts
Normal file
560
src/tools/requests.ts
Normal file
|
|
@ -0,0 +1,560 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { defineTool } from './tool.js';
|
||||
import { RequestInterceptorOptions } from '../requestInterceptor.js';
|
||||
import type { Context } from '../context.js';
|
||||
|
||||
const startMonitoringSchema = z.object({
|
||||
urlFilter: z.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
type: z.enum(['regex', 'function']),
|
||||
value: z.string()
|
||||
})
|
||||
]).optional().describe('Filter URLs to capture. Can be a string (contains match), regex pattern, or custom function. Examples: "/api/", ".*\\.json$", or custom logic'),
|
||||
|
||||
captureBody: z.boolean().optional().default(true).describe('Whether to capture request and response bodies (default: true)'),
|
||||
|
||||
maxBodySize: z.number().optional().default(10485760).describe('Maximum body size to capture in bytes (default: 10MB). Larger bodies will be truncated'),
|
||||
|
||||
autoSave: z.boolean().optional().default(false).describe('Automatically save captured requests after each response (default: false for performance)'),
|
||||
|
||||
outputPath: z.string().optional().describe('Custom output directory path. If not specified, uses session artifact directory')
|
||||
});
|
||||
|
||||
const getRequestsSchema = z.object({
|
||||
filter: z.enum(['all', 'failed', 'slow', 'errors', 'success']).optional().default('all').describe('Filter requests by type: all, failed (network failures), slow (>1s), errors (4xx/5xx), success (2xx/3xx)'),
|
||||
|
||||
domain: z.string().optional().describe('Filter requests by domain hostname'),
|
||||
|
||||
method: z.string().optional().describe('Filter requests by HTTP method (GET, POST, etc.)'),
|
||||
|
||||
status: z.number().optional().describe('Filter requests by HTTP status code'),
|
||||
|
||||
limit: z.number().optional().default(100).describe('Maximum number of requests to return (default: 100)'),
|
||||
|
||||
format: z.enum(['summary', 'detailed', 'stats']).optional().default('summary').describe('Response format: summary (basic info), detailed (full data), stats (statistics only)'),
|
||||
|
||||
slowThreshold: z.number().optional().default(1000).describe('Threshold in milliseconds for considering requests "slow" (default: 1000ms)')
|
||||
});
|
||||
|
||||
const exportRequestsSchema = z.object({
|
||||
format: z.enum(['json', 'har', 'csv', 'summary']).optional().default('json').describe('Export format: json (full data), har (HTTP Archive), csv (spreadsheet), summary (human-readable report)'),
|
||||
|
||||
filename: z.string().optional().describe('Custom filename for export. Auto-generated if not specified with timestamp'),
|
||||
|
||||
filter: z.enum(['all', 'failed', 'slow', 'errors', 'success']).optional().default('all').describe('Filter which requests to export'),
|
||||
|
||||
includeBody: z.boolean().optional().default(false).describe('Include request/response bodies in export (warning: may create large files)')
|
||||
});
|
||||
|
||||
/**
|
||||
* Start comprehensive request monitoring and interception
|
||||
*
|
||||
* This tool enables deep HTTP traffic analysis during browser automation.
|
||||
* Perfect for API reverse engineering, security testing, and performance analysis.
|
||||
*
|
||||
* Use Cases:
|
||||
* - Security testing: Capture all API calls for vulnerability analysis
|
||||
* - Performance monitoring: Identify slow endpoints and optimize
|
||||
* - API documentation: Generate comprehensive API usage reports
|
||||
* - Debugging: Analyze failed requests and error patterns
|
||||
*/
|
||||
const startRequestMonitoring = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_start_request_monitoring',
|
||||
title: 'Start request monitoring',
|
||||
description: 'Enable comprehensive HTTP request/response interception and analysis. Captures headers, bodies, timing, and failure information for all browser traffic. Essential for security testing, API analysis, and performance debugging.',
|
||||
inputSchema: startMonitoringSchema,
|
||||
type: 'destructive',
|
||||
},
|
||||
|
||||
handle: async (context: Context, params: z.output<typeof startMonitoringSchema>, response) => {
|
||||
try {
|
||||
await context.ensureTab();
|
||||
|
||||
// Parse URL filter
|
||||
let urlFilter: RequestInterceptorOptions['urlFilter'];
|
||||
if (params.urlFilter) {
|
||||
if (typeof params.urlFilter === 'string') {
|
||||
urlFilter = params.urlFilter;
|
||||
} else {
|
||||
// Handle regex or function
|
||||
if (params.urlFilter.type === 'regex') {
|
||||
urlFilter = new RegExp(params.urlFilter.value);
|
||||
} else {
|
||||
// Function - evaluate safely
|
||||
try {
|
||||
|
||||
urlFilter = eval(`(${params.urlFilter.value})`);
|
||||
} catch (error: any) {
|
||||
throw new Error(`Invalid filter function: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get output path from artifact manager or use default
|
||||
let outputPath = params.outputPath;
|
||||
if (!outputPath && context.sessionId) {
|
||||
const artifactManager = context.getArtifactManager();
|
||||
if (artifactManager)
|
||||
outputPath = artifactManager.getSubdirectory('requests');
|
||||
|
||||
}
|
||||
if (!outputPath)
|
||||
outputPath = context.config.outputDir + '/requests';
|
||||
|
||||
|
||||
const options: RequestInterceptorOptions = {
|
||||
urlFilter,
|
||||
captureBody: params.captureBody,
|
||||
maxBodySize: params.maxBodySize,
|
||||
autoSave: params.autoSave,
|
||||
outputPath
|
||||
};
|
||||
|
||||
// Start monitoring
|
||||
await context.startRequestMonitoring(options);
|
||||
|
||||
response.addResult('✅ **Request monitoring started successfully**');
|
||||
response.addResult('');
|
||||
response.addResult('📊 **Configuration:**');
|
||||
response.addResult(`• URL Filter: ${params.urlFilter || 'All requests'}`);
|
||||
response.addResult(`• Capture Bodies: ${params.captureBody ? 'Yes' : 'No'}`);
|
||||
response.addResult(`• Max Body Size: ${(params.maxBodySize! / 1024 / 1024).toFixed(1)}MB`);
|
||||
response.addResult(`• Auto Save: ${params.autoSave ? 'Yes' : 'No'}`);
|
||||
response.addResult(`• Output Path: ${outputPath}`);
|
||||
response.addResult('');
|
||||
response.addResult('🎯 **Next Steps:**');
|
||||
response.addResult('1. Navigate to pages and interact with the application');
|
||||
response.addResult('2. Use `browser_get_requests` to view captured traffic');
|
||||
response.addResult('3. Use `browser_export_requests` to save analysis results');
|
||||
response.addResult('4. Use `browser_clear_requests` to clear captured data');
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to start request monitoring: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieve and analyze captured HTTP requests
|
||||
*
|
||||
* Access comprehensive request data including timing, headers, bodies,
|
||||
* and failure information. Supports advanced filtering and analysis.
|
||||
*/
|
||||
const getRequests = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_get_requests',
|
||||
title: 'Get captured requests',
|
||||
description: 'Retrieve and analyze captured HTTP requests with advanced filtering. Shows timing, status codes, headers, and bodies. Perfect for identifying performance issues, failed requests, or analyzing API usage patterns.',
|
||||
inputSchema: getRequestsSchema,
|
||||
type: 'readOnly',
|
||||
},
|
||||
|
||||
handle: async (context: Context, params: z.output<typeof getRequestsSchema>, response) => {
|
||||
try {
|
||||
const interceptor = context.getRequestInterceptor();
|
||||
if (!interceptor) {
|
||||
response.addResult('❌ **Request monitoring not active**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 Start monitoring first with `browser_start_request_monitoring`');
|
||||
return;
|
||||
}
|
||||
|
||||
let requests = interceptor.getData();
|
||||
|
||||
// Apply filters
|
||||
if (params.filter !== 'all') {
|
||||
switch (params.filter) {
|
||||
case 'failed':
|
||||
requests = interceptor.getFailedRequests();
|
||||
break;
|
||||
case 'slow':
|
||||
requests = interceptor.getSlowRequests(params.slowThreshold);
|
||||
break;
|
||||
case 'errors':
|
||||
requests = requests.filter(r => r.response && r.response.status >= 400);
|
||||
break;
|
||||
case 'success':
|
||||
requests = requests.filter(r => r.response && r.response.status < 400);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.domain) {
|
||||
requests = requests.filter(r => {
|
||||
try {
|
||||
return new URL(r.url).hostname === params.domain;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (params.method)
|
||||
requests = requests.filter(r => r.method.toLowerCase() === params.method!.toLowerCase());
|
||||
|
||||
|
||||
if (params.status)
|
||||
requests = requests.filter(r => r.response?.status === params.status);
|
||||
|
||||
|
||||
// Limit results
|
||||
const limitedRequests = requests.slice(0, params.limit);
|
||||
|
||||
if (params.format === 'stats') {
|
||||
// Return statistics only
|
||||
const stats = interceptor.getStats();
|
||||
response.addResult('📊 **Request Statistics**');
|
||||
response.addResult('');
|
||||
response.addResult(`• Total Requests: ${stats.totalRequests}`);
|
||||
response.addResult(`• Successful: ${stats.successfulRequests} (${((stats.successfulRequests / stats.totalRequests) * 100).toFixed(1)}%)`);
|
||||
response.addResult(`• Failed: ${stats.failedRequests} (${((stats.failedRequests / stats.totalRequests) * 100).toFixed(1)}%)`);
|
||||
response.addResult(`• Errors: ${stats.errorResponses} (${((stats.errorResponses / stats.totalRequests) * 100).toFixed(1)}%)`);
|
||||
response.addResult(`• Average Response Time: ${stats.averageResponseTime}ms`);
|
||||
response.addResult(`• Slow Requests (>1s): ${stats.slowRequests}`);
|
||||
response.addResult('');
|
||||
response.addResult('**By Method:**');
|
||||
Object.entries(stats.requestsByMethod).forEach(([method, count]) => {
|
||||
response.addResult(` • ${method}: ${count}`);
|
||||
});
|
||||
response.addResult('');
|
||||
response.addResult('**By Status Code:**');
|
||||
Object.entries(stats.requestsByStatus).forEach(([status, count]) => {
|
||||
response.addResult(` • ${status}: ${count}`);
|
||||
});
|
||||
response.addResult('');
|
||||
response.addResult('**Top Domains:**');
|
||||
const topDomains = Object.entries(stats.requestsByDomain)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.slice(0, 5);
|
||||
topDomains.forEach(([domain, count]) => {
|
||||
response.addResult(` • ${domain}: ${count}`);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Return request data
|
||||
if (limitedRequests.length === 0) {
|
||||
response.addResult('ℹ️ **No requests found matching the criteria**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 Try different filters or ensure the page has made HTTP requests');
|
||||
return;
|
||||
}
|
||||
|
||||
response.addResult(`📋 **Captured Requests (${limitedRequests.length} of ${requests.length} total)**`);
|
||||
response.addResult('');
|
||||
|
||||
limitedRequests.forEach((req, index) => {
|
||||
const duration = req.duration ? `${req.duration}ms` : 'pending';
|
||||
const status = req.failed ? 'FAILED' : req.response?.status || 'pending';
|
||||
const size = req.response?.bodySize ? ` (${(req.response.bodySize / 1024).toFixed(1)}KB)` : '';
|
||||
|
||||
response.addResult(`**${index + 1}. ${req.method} ${status}** - ${duration}`);
|
||||
response.addResult(` ${req.url}${size}`);
|
||||
|
||||
if (params.format === 'detailed') {
|
||||
response.addResult(` 📅 ${req.timestamp}`);
|
||||
if (req.response) {
|
||||
response.addResult(` 📊 Status: ${req.response.status} ${req.response.statusText}`);
|
||||
response.addResult(` ⏱️ Duration: ${req.response.duration}ms`);
|
||||
response.addResult(` 🔄 From Cache: ${req.response.fromCache ? 'Yes' : 'No'}`);
|
||||
|
||||
// Show key headers
|
||||
const contentType = req.response.headers['content-type'];
|
||||
if (contentType)
|
||||
response.addResult(` 📄 Content-Type: ${contentType}`);
|
||||
|
||||
}
|
||||
|
||||
if (req.failed && req.failure)
|
||||
response.addResult(` ❌ Failure: ${req.failure.errorText}`);
|
||||
|
||||
|
||||
response.addResult('');
|
||||
}
|
||||
});
|
||||
|
||||
if (requests.length > params.limit)
|
||||
response.addResult(`💡 Showing first ${params.limit} results. Use higher limit or specific filters to see more.`);
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to get requests: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Export captured requests to various formats for external analysis
|
||||
*/
|
||||
const exportRequests = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_export_requests',
|
||||
title: 'Export captured requests',
|
||||
description: 'Export captured HTTP requests to various formats (JSON, HAR, CSV, or summary report). Perfect for sharing analysis results, importing into other tools, or creating audit reports.',
|
||||
inputSchema: exportRequestsSchema,
|
||||
type: 'readOnly',
|
||||
},
|
||||
|
||||
handle: async (context: Context, params: z.output<typeof exportRequestsSchema>, response) => {
|
||||
try {
|
||||
const interceptor = context.getRequestInterceptor();
|
||||
if (!interceptor) {
|
||||
response.addResult('❌ **Request monitoring not active**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 Start monitoring first with `browser_start_request_monitoring`');
|
||||
return;
|
||||
}
|
||||
|
||||
const requests = interceptor.getData();
|
||||
if (requests.length === 0) {
|
||||
response.addResult('ℹ️ **No requests to export**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 Navigate to pages and interact with the application first');
|
||||
return;
|
||||
}
|
||||
|
||||
let exportPath: string;
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const defaultFilename = `requests-${timestamp}`;
|
||||
|
||||
switch (params.format) {
|
||||
case 'har':
|
||||
exportPath = await interceptor.exportHAR(params.filename || `${defaultFilename}.har`);
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
exportPath = await interceptor.save(params.filename || `${defaultFilename}.json`);
|
||||
break;
|
||||
|
||||
case 'csv':
|
||||
// Create CSV export
|
||||
const csvData = requests.map(req => ({
|
||||
timestamp: req.timestamp,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
status: req.response?.status || (req.failed ? 'FAILED' : 'PENDING'),
|
||||
duration: req.duration || '',
|
||||
size: req.response?.bodySize || '',
|
||||
contentType: req.response?.headers['content-type'] || '',
|
||||
fromCache: req.response?.fromCache || false
|
||||
}));
|
||||
|
||||
const csvHeaders = Object.keys(csvData[0]).join(',');
|
||||
const csvRows = csvData.map(row => Object.values(row).join(','));
|
||||
const csvContent = [csvHeaders, ...csvRows].join('\n');
|
||||
|
||||
const csvFilename = params.filename || `${defaultFilename}.csv`;
|
||||
const csvPath = `${interceptor.getStatus().options.outputPath}/${csvFilename}`;
|
||||
await require('fs/promises').writeFile(csvPath, csvContent);
|
||||
exportPath = csvPath;
|
||||
break;
|
||||
|
||||
case 'summary':
|
||||
// Create human-readable summary
|
||||
const stats = interceptor.getStats();
|
||||
const summaryLines = [
|
||||
'# HTTP Request Analysis Summary',
|
||||
`Generated: ${new Date().toISOString()}`,
|
||||
'',
|
||||
'## Overview',
|
||||
`- Total Requests: ${stats.totalRequests}`,
|
||||
`- Successful: ${stats.successfulRequests}`,
|
||||
`- Failed: ${stats.failedRequests}`,
|
||||
`- Errors: ${stats.errorResponses}`,
|
||||
`- Average Response Time: ${stats.averageResponseTime}ms`,
|
||||
'',
|
||||
'## Performance',
|
||||
`- Fast Requests (<1s): ${stats.fastRequests}`,
|
||||
`- Slow Requests (>1s): ${stats.slowRequests}`,
|
||||
'',
|
||||
'## Request Methods',
|
||||
...Object.entries(stats.requestsByMethod).map(([method, count]) => `- ${method}: ${count}`),
|
||||
'',
|
||||
'## Status Codes',
|
||||
...Object.entries(stats.requestsByStatus).map(([status, count]) => `- ${status}: ${count}`),
|
||||
'',
|
||||
'## Top Domains',
|
||||
...Object.entries(stats.requestsByDomain)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([domain, count]) => `- ${domain}: ${count}`),
|
||||
'',
|
||||
'## Slow Requests (>1s)',
|
||||
...interceptor.getSlowRequests().map(req =>
|
||||
`- ${req.method} ${req.url} (${req.duration}ms)`
|
||||
),
|
||||
'',
|
||||
'## Failed Requests',
|
||||
...interceptor.getFailedRequests().map(req =>
|
||||
`- ${req.method} ${req.url} (${req.response?.status || 'NETWORK_FAILED'})`
|
||||
)
|
||||
];
|
||||
|
||||
const summaryFilename = params.filename || `${defaultFilename}-summary.md`;
|
||||
const summaryPath = `${interceptor.getStatus().options.outputPath}/${summaryFilename}`;
|
||||
await require('fs/promises').writeFile(summaryPath, summaryLines.join('\n'));
|
||||
exportPath = summaryPath;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported export format: ${params.format}`);
|
||||
}
|
||||
|
||||
response.addResult('✅ **Export completed successfully**');
|
||||
response.addResult('');
|
||||
response.addResult(`📁 **File saved:** ${exportPath}`);
|
||||
response.addResult(`📊 **Exported:** ${requests.length} requests`);
|
||||
response.addResult(`🗂️ **Format:** ${params.format.toUpperCase()}`);
|
||||
response.addResult('');
|
||||
|
||||
if (params.format === 'har') {
|
||||
response.addResult('💡 **HAR files** can be imported into:');
|
||||
response.addResult(' • Chrome DevTools (Network tab)');
|
||||
response.addResult(' • Insomnia or Postman');
|
||||
response.addResult(' • Online HAR viewers');
|
||||
} else if (params.format === 'json') {
|
||||
response.addResult('💡 **JSON files** contain full request/response data');
|
||||
response.addResult(' • Perfect for programmatic analysis');
|
||||
response.addResult(' • Includes headers, bodies, timing info');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to export requests: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Clear all captured request data from memory
|
||||
*/
|
||||
const clearRequests = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_clear_requests',
|
||||
title: 'Clear captured requests',
|
||||
description: 'Clear all captured HTTP request data from memory. Useful for freeing up memory during long sessions or when starting fresh analysis.',
|
||||
inputSchema: z.object({}),
|
||||
type: 'destructive',
|
||||
},
|
||||
|
||||
handle: async (context: Context, params, response) => {
|
||||
try {
|
||||
const interceptor = context.getRequestInterceptor();
|
||||
if (!interceptor) {
|
||||
response.addResult('ℹ️ **Request monitoring not active**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 Start monitoring first with `browser_start_request_monitoring`');
|
||||
return;
|
||||
}
|
||||
|
||||
const clearedCount = interceptor.clear();
|
||||
|
||||
response.addResult('✅ **Request data cleared successfully**');
|
||||
response.addResult('');
|
||||
response.addResult(`🗑️ **Cleared:** ${clearedCount} captured requests`);
|
||||
response.addResult('');
|
||||
response.addResult('💡 **Memory freed** - Ready for new monitoring session');
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to clear requests: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Get current request monitoring status and configuration
|
||||
*/
|
||||
const getMonitoringStatus = defineTool({
|
||||
capability: 'core',
|
||||
|
||||
schema: {
|
||||
name: 'browser_request_monitoring_status',
|
||||
title: 'Get request monitoring status',
|
||||
description: 'Check if request monitoring is active and view current configuration. Shows capture statistics, filter settings, and output paths.',
|
||||
inputSchema: z.object({}),
|
||||
type: 'readOnly',
|
||||
},
|
||||
|
||||
handle: async (context: Context, params, response) => {
|
||||
try {
|
||||
const interceptor = context.getRequestInterceptor();
|
||||
|
||||
if (!interceptor) {
|
||||
response.addResult('❌ **Request monitoring is not active**');
|
||||
response.addResult('');
|
||||
response.addResult('💡 **To start monitoring:**');
|
||||
response.addResult('1. Use `browser_start_request_monitoring` to enable');
|
||||
response.addResult('2. Navigate to pages and perform actions');
|
||||
response.addResult('3. Use `browser_get_requests` to view captured data');
|
||||
return;
|
||||
}
|
||||
|
||||
const status = interceptor.getStatus();
|
||||
const stats = interceptor.getStats();
|
||||
|
||||
response.addResult('✅ **Request monitoring is active**');
|
||||
response.addResult('');
|
||||
response.addResult('📊 **Current Statistics:**');
|
||||
response.addResult(`• Total Captured: ${stats.totalRequests} requests`);
|
||||
response.addResult(`• Successful: ${stats.successfulRequests}`);
|
||||
response.addResult(`• Failed: ${stats.failedRequests}`);
|
||||
response.addResult(`• Average Response Time: ${stats.averageResponseTime}ms`);
|
||||
response.addResult('');
|
||||
response.addResult('⚙️ **Configuration:**');
|
||||
response.addResult(`• Attached to Page: ${status.isAttached ? 'Yes' : 'No'}`);
|
||||
response.addResult(`• Current Page: ${status.pageUrl || 'None'}`);
|
||||
response.addResult(`• Capture Bodies: ${status.options.captureBody ? 'Yes' : 'No'}`);
|
||||
response.addResult(`• Max Body Size: ${status.options.maxBodySize ? (status.options.maxBodySize / 1024 / 1024).toFixed(1) + 'MB' : 'Unlimited'}`);
|
||||
response.addResult(`• Auto Save: ${status.options.autoSave ? 'Yes' : 'No'}`);
|
||||
response.addResult(`• Output Path: ${status.options.outputPath || 'Default'}`);
|
||||
|
||||
if (stats.totalRequests > 0) {
|
||||
response.addResult('');
|
||||
response.addResult('📈 **Recent Activity:**');
|
||||
const recentRequests = interceptor.getData().slice(-3);
|
||||
recentRequests.forEach((req, index) => {
|
||||
const duration = req.duration ? ` (${req.duration}ms)` : '';
|
||||
const status = req.failed ? 'FAILED' : req.response?.status || 'pending';
|
||||
response.addResult(` ${index + 1}. ${req.method} ${status} - ${new URL(req.url).pathname}${duration}`);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
throw new Error(`Failed to get monitoring status: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default [
|
||||
startRequestMonitoring,
|
||||
getRequests,
|
||||
exportRequests,
|
||||
clearRequests,
|
||||
getMonitoringStatus,
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue