UNPKG

browser-connect-mcp

Version:

MCP server for browser DevTools and backend debugging - analyze console logs, network requests, and backend logs with AI assistance

271 lines 10.4 kB
// Advanced pattern matcher with scoring export class AdvancedMatcher { options; compiledPatterns = new Map(); constructor(options) { this.options = options; this.compilePatterns(); } compilePatterns() { // Compile named patterns if (this.options.namedPatterns) { for (const [name, pattern] of Object.entries(this.options.namedPatterns)) { try { this.compiledPatterns.set(name, new RegExp(pattern, 'gi')); } catch (e) { console.error(`Invalid regex pattern for ${name}: ${pattern}`); } } } // Compile include/exclude patterns if (this.options.patterns) { const { include, exclude } = this.options.patterns; if (include) { include.forEach((pattern, i) => { try { this.compiledPatterns.set(`include_${i}`, new RegExp(pattern, 'gi')); } catch (e) { console.error(`Invalid include pattern: ${pattern}`); } }); } if (exclude) { exclude.forEach((pattern, i) => { try { this.compiledPatterns.set(`exclude_${i}`, new RegExp(pattern, 'gi')); } catch (e) { console.error(`Invalid exclude pattern: ${pattern}`); } }); } } } matchConsoleMessage(message) { const text = message.text; const matches = {}; let score = 0; // Check exclude patterns first if (this.options.patterns?.exclude) { for (let i = 0; i < this.options.patterns.exclude.length; i++) { const pattern = this.compiledPatterns.get(`exclude_${i}`); if (pattern && pattern.test(text)) { return null; // Excluded } } } // Check include patterns if (this.options.patterns?.include) { const mode = this.options.patterns.mode || 'any'; let matchCount = 0; for (let i = 0; i < this.options.patterns.include.length; i++) { const pattern = this.compiledPatterns.get(`include_${i}`); if (pattern) { const match = text.match(pattern); if (match) { matchCount++; matches[`include_${i}`] = match[0]; score += 10; } } } if (mode === 'all' && matchCount < this.options.patterns.include.length) { return null; // Didn't match all required patterns } if (mode === 'any' && matchCount === 0) { return null; // Didn't match any pattern } } // Check named patterns and extract groups if (this.options.namedPatterns) { for (const [name] of Object.entries(this.options.namedPatterns)) { const pattern = this.compiledPatterns.get(name); if (pattern) { const match = text.match(pattern); if (match) { matches[name] = match.length > 1 ? match.slice(1) : match[0]; score += 5; } } } } // Field-specific matching if (this.options.fields) { for (const [fieldName, fieldOptions] of Object.entries(this.options.fields)) { const fieldValue = this.getFieldValue(message, fieldName); if (fieldValue && this.matchField(fieldValue, fieldOptions)) { matches[`field_${fieldName}`] = fieldValue; score += 3; } } } return score > 0 ? { item: message, matches, score } : null; } matchNetworkRequest(request, response) { const matches = {}; let score = 0; // URL matching with patterns const url = request.url; // Check exclude patterns if (this.options.patterns?.exclude) { for (let i = 0; i < this.options.patterns.exclude.length; i++) { const pattern = this.compiledPatterns.get(`exclude_${i}`); if (pattern && pattern.test(url)) { return null; } } } // Check include patterns if (this.options.patterns?.include) { const mode = this.options.patterns.mode || 'any'; let matchCount = 0; for (let i = 0; i < this.options.patterns.include.length; i++) { const pattern = this.compiledPatterns.get(`include_${i}`); if (pattern && pattern.test(url)) { matchCount++; matches[`url_pattern_${i}`] = url.match(pattern)[0]; score += 10; } } if (mode === 'all' && matchCount < this.options.patterns.include.length) { return null; } if (mode === 'any' && matchCount === 0) { return null; } } // Named patterns on URL and headers if (this.options.namedPatterns) { for (const [name] of Object.entries(this.options.namedPatterns)) { const pattern = this.compiledPatterns.get(name); if (pattern) { // Check URL let match = url.match(pattern); if (match) { matches[`url_${name}`] = match.length > 1 ? match.slice(1) : match[0]; score += 5; } // Check headers for (const [headerName, headerValue] of Object.entries(request.headers)) { match = headerValue.match(pattern); if (match) { matches[`header_${headerName}_${name}`] = match.length > 1 ? match.slice(1) : match[0]; score += 3; } } } } } return score > 0 ? { item: { request, response }, matches, score } : null; } getFieldValue(obj, fieldPath) { const parts = fieldPath.split('.'); let current = obj; for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return undefined; } } return String(current); } matchField(value, options) { const { pattern, operator = 'contains', caseSensitive = false } = options; if (!pattern) return false; const compareValue = caseSensitive ? value : value.toLowerCase(); const comparePattern = caseSensitive ? pattern : pattern.toLowerCase(); switch (operator) { case 'equals': return compareValue === comparePattern; case 'contains': return compareValue.includes(comparePattern); case 'startsWith': return compareValue.startsWith(comparePattern); case 'endsWith': return compareValue.endsWith(comparePattern); case 'regex': try { const regex = new RegExp(pattern, caseSensitive ? 'g' : 'gi'); return regex.test(value); } catch { return false; } default: return false; } } } // Correlation utilities export function correlateMessages(messages, options) { const groups = new Map(); // Sort by timestamp const sorted = [...messages].sort((a, b) => a.timestamp - b.timestamp); for (let i = 0; i < sorted.length; i++) { const current = sorted[i]; const groupKey = options.fields ? options.fields.map(f => getFieldValue(current, f) || 'unknown').join('_') : `group_${i}`; const group = [current]; // Find related messages within time window for (let j = i + 1; j < sorted.length; j++) { const next = sorted[j]; if (next.timestamp - current.timestamp <= options.timeWindow) { if (!options.fields || options.fields.every(f => getFieldValue(current, f) === getFieldValue(next, f))) { group.push(next); } } else { break; // Outside time window } } if (group.length > 1) { groups.set(groupKey, group); } } return groups; } function getFieldValue(obj, fieldPath) { const parts = fieldPath.split('.'); let current = obj; for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return undefined; } } return String(current); } export function calculateSearchStats(results) { const patternCounts = new Map(); let totalScore = 0; for (const result of results) { if (result.matches) { for (const key of Object.keys(result.matches)) { patternCounts.set(key, (patternCounts.get(key) || 0) + 1); } } totalScore += result.score || 0; } const topPatterns = Array.from(patternCounts.entries()) .map(([pattern, count]) => ({ pattern, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); return { totalSearched: results.length, totalMatched: results.filter(r => r.score && r.score > 0).length, matchRate: results.length > 0 ? results.filter(r => r.score && r.score > 0).length / results.length : 0, topPatterns, avgScore: results.length > 0 ? totalScore / results.length : 0 }; } //# sourceMappingURL=advanced-search.js.map