UNPKG

browser-debugger-cli

Version:

DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.

236 lines 9.27 kB
import { PatternDetector } from '../../daemon/patternDetector.js'; import { generatePatternHint } from '../../ui/messages/hints.js'; import { filterDefined } from '../../utils/objects.js'; import { VERSION } from '../../utils/version.js'; /** Maximum number of items returned by peek command to prevent memory issues */ const MAX_PEEK_ITEMS = 10000; /** Default number of items to return when not specified */ const DEFAULT_PEEK_ITEMS = 10; /** * Calculate effective lastN value from request params. * * @param requestLastN - Value from request (0 = all, undefined = default) * @returns Effective limit (Infinity for all, capped otherwise) */ function calculateLastN(requestLastN) { if (requestLastN === 0) return Infinity; return Math.min(requestLastN ?? DEFAULT_PEEK_ITEMS, MAX_PEEK_ITEMS); } /** * Calculate slice bounds for pagination. * * @param total - Total number of items * @param lastN - Number of items to return * @param offset - Offset from end * @returns Start and end indices for slice */ function calculateSliceBounds(total, lastN, offset) { return { start: Math.max(0, total - lastN - offset), end: Math.max(0, total - offset), }; } function mapNetworkRequestToPreview(req) { return filterDefined({ requestId: req.requestId, timestamp: req.timestamp, method: req.method, url: req.url, status: req.status, mimeType: req.mimeType, resourceType: req.resourceType, }); } /** * Map console message to preview format. * * @param msg - Full console message * @returns Filtered message with only preview fields */ function mapConsoleMessageToPreview(msg) { const result = { timestamp: msg.timestamp, type: msg.type, text: msg.text, }; if (msg.stackTrace) { result.stackTrace = msg.stackTrace; } if (msg.navigationId !== undefined) { result.navigationId = msg.navigationId; } return result; } /** * Find network request by ID or throw. * * @param requests - Array of network requests * @param id - Request ID to find * @returns Found request * @throws Error if not found */ function findNetworkRequestOrThrow(requests, id) { const request = requests.find((r) => r.requestId === id); if (!request) { throw new Error(`Network request not found: ${id}`); } return request; } /** * Find console message by index or throw. * * @param messages - Array of console messages * @param indexStr - Index as string * @returns Found message * @throws Error if invalid index or not found */ function findConsoleMessageOrThrow(messages, indexStr) { const index = parseInt(indexStr, 10); if (isNaN(index) || index < 0 || index >= messages.length) { throw new Error(`Console message not found at index: ${indexStr} (available: 0-${messages.length - 1})`); } const message = messages[index]; if (!message) { throw new Error(`Console message not found at index: ${indexStr}`); } return message; } /** * Find target request for headers command. * * @param store - Telemetry store * @param requestId - Optional specific request ID * @returns Target request with headers * @throws Error if no suitable request found */ function findTargetRequestForHeaders(store, requestId) { if (requestId) { const request = store.networkRequests.find((r) => r.requestId === requestId); if (!request) { throw new Error(`Network request not found: ${requestId}`); } return request; } const currentNavId = store.getCurrentNavigationId?.() ?? 0; const byDocument = store.networkRequests.findLast((r) => r.navigationId === currentNavId && r.resourceType === 'Document'); if (byDocument) return byDocument; const byHtml = store.networkRequests.findLast((r) => r.mimeType?.includes('html')); if (byHtml) return byHtml; const byHeaders = store.networkRequests.findLast((r) => r.responseHeaders && Object.keys(r.responseHeaders).length > 0); if (byHeaders) return byHeaders; throw new Error('No network requests with headers found'); } /** * Filter headers by name (case-insensitive). * * @param headers - Headers object * @param headerName - Header name to filter by * @returns Filtered headers */ function filterHeadersByName(headers, headerName) { const name = headerName.toLowerCase(); return Object.fromEntries(Object.entries(headers).filter(([k]) => k.toLowerCase() === name)); } export function createCommandRegistry(store) { const patternDetector = new PatternDetector(); return { worker_peek: async (_cdp, params) => { const lastN = calculateLastN(params.lastN); const offset = params.offset ?? 0; const duration = Date.now() - store.sessionStartTime; const totalNetwork = store.networkRequests.length; const totalConsole = store.consoleMessages.length; const networkBounds = calculateSliceBounds(totalNetwork, lastN, offset); const consoleBounds = calculateSliceBounds(totalConsole, lastN, offset); const recentNetwork = store.networkRequests .slice(networkBounds.start, networkBounds.end) .map(mapNetworkRequestToPreview); const recentConsole = store.consoleMessages .slice(consoleBounds.start, consoleBounds.end) .map(mapConsoleMessageToPreview); return Promise.resolve({ version: VERSION, startTime: store.sessionStartTime, duration, target: { url: store.targetInfo?.url ?? '', title: store.targetInfo?.title ?? '', }, activeTelemetry: store.activeTelemetry, network: recentNetwork, console: recentConsole, totalNetwork, totalConsole, hasMoreNetwork: networkBounds.start > 0, hasMoreConsole: consoleBounds.start > 0, }); }, worker_details: async (_cdp, params) => { if (params.itemType === 'network') { const request = findNetworkRequestOrThrow(store.networkRequests, params.id); return Promise.resolve({ item: request }); } if (params.itemType === 'console') { const message = findConsoleMessageOrThrow(store.consoleMessages, params.id); return Promise.resolve({ item: message }); } return Promise.reject(new Error(`Unknown itemType: ${String(params.itemType)}. Expected 'network' or 'console'.`)); }, worker_status: async (_cdp, _params) => { const duration = Date.now() - store.sessionStartTime; const lastNetworkRequest = store.networkRequests[store.networkRequests.length - 1]; const lastConsoleMessage = store.consoleMessages[store.consoleMessages.length - 1]; const result = { startTime: store.sessionStartTime, duration, target: { url: store.targetInfo?.url ?? '', title: store.targetInfo?.title ?? '', }, activeTelemetry: store.activeTelemetry, activity: filterDefined({ networkRequestsCaptured: store.networkRequests.length, consoleMessagesCaptured: store.consoleMessages.length, lastNetworkRequestAt: lastNetworkRequest?.timestamp, lastConsoleMessageAt: lastConsoleMessage?.timestamp, }), navigationId: store.getCurrentNavigationId?.() ?? 0, }; return Promise.resolve(result); }, worker_har_data: async (_cdp, _params) => { return Promise.resolve({ requests: store.networkRequests, }); }, worker_network_headers: async (_cdp, params) => { const targetRequest = findTargetRequestForHeaders(store, params.id); let requestHeaders = targetRequest.requestHeaders ?? {}; let responseHeaders = targetRequest.responseHeaders ?? {}; if (params.headerName) { requestHeaders = filterHeadersByName(requestHeaders, params.headerName); responseHeaders = filterHeadersByName(responseHeaders, params.headerName); } return Promise.resolve({ url: targetRequest.url, requestId: targetRequest.requestId, requestHeaders, responseHeaders, }); }, cdp_call: async (cdp, params) => { const result = await cdp.send(params.method, params.params ?? {}); const detectionResult = patternDetector.trackCommand(params.method); let hint; if (detectionResult.shouldShow && detectionResult.pattern) { hint = generatePatternHint(detectionResult.pattern); } return { result, hint }; }, }; } //# sourceMappingURL=commandRegistry.js.map