UNPKG

wingman-monitor

Version:

Runtime error monitoring package with React provider that automatically reports errors to webhook endpoints

375 lines 15.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WingmanMonitor = void 0; const axios_1 = __importDefault(require("axios")); const config_1 = require("./config"); class WingmanMonitor { constructor(projectPath) { this.config = null; this.isStarted = false; this.originalErrorHandler = null; this.originalUnhandledRejectionHandler = null; this.recentLogs = []; this.maxLogs = 50; this.configManager = new config_1.ConfigManager(projectPath); this.originalConsoleError = console.error; this.originalConsoleWarn = console.warn; this.originalConsoleLog = console.log; } captureConsoleLog(type, args) { const logEntry = { type, message: args.map(arg => { try { return typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg); } catch { return '[Circular Object]'; } }).join(' '), timestamp: Date.now(), args }; this.recentLogs.push(logEntry); if (this.recentLogs.length > this.maxLogs) { this.recentLogs.shift(); // Remove oldest log } } getRelevantLogs(errorTimestamp, windowMs = 5000) { // Get logs from 5 seconds before the error const startTime = errorTimestamp - windowMs; const relevantLogs = this.recentLogs.filter(log => log.timestamp >= startTime && log.timestamp <= errorTimestamp); if (relevantLogs.length === 0) return ''; let logContext = '\n\n--- Recent Console Activity ---\n'; relevantLogs.forEach(log => { const timestamp = new Date(log.timestamp).toISOString(); logContext += `[${timestamp}] ${log.type.toUpperCase()}: ${log.message}\n`; }); return logContext; } buildComprehensiveMessage(primaryMessage, errorContext = {}) { let message = primaryMessage; // Add relevant recent logs const relevantLogs = this.getRelevantLogs(Date.now()); if (relevantLogs) { message += relevantLogs; } // Add error context if (errorContext.url) { message += `\n\n--- Context ---\nURL: ${errorContext.url}`; } if (errorContext.userAgent) { message += `\nUser Agent: ${errorContext.userAgent}`; } if (errorContext.stack) { message += `\n\n--- Stack Trace ---\n${errorContext.stack}`; } return message; } async start() { try { // Load configuration this.config = await this.configManager.load(); if (!this.config) { console.warn('Wingman: No configuration found. Run "wingman init <accessToken>" first.'); return; } if (!this.config.enabled) { console.log('Wingman: Monitoring is disabled.'); return; } if (this.isStarted) { console.warn('Wingman: Already started.'); return; } this.setupErrorHandlers(); this.isStarted = true; console.log(`Wingman: Monitoring started for ${this.config.environment} environment`); } catch (error) { console.error('Wingman: Failed to start monitoring:', error); } } stop() { if (!this.isStarted) return; this.removeErrorHandlers(); this.isStarted = false; console.log('Wingman: Monitoring stopped'); } setupErrorHandlers() { // Handle uncaught exceptions (Node.js) if (typeof process !== 'undefined' && process.on) { process.on('uncaughtException', this.handleUncaughtException.bind(this)); process.on('unhandledRejection', this.handleUnhandledRejection.bind(this)); } // Handle browser errors if (typeof window !== 'undefined') { this.originalErrorHandler = window.onerror; this.originalUnhandledRejectionHandler = window.onunhandledrejection; window.onerror = this.handleWindowError.bind(this); window.onunhandledrejection = this.handleWindowUnhandledRejection.bind(this); } // Monkey patch console methods for log capture and error tracking this.originalConsoleError = console.error; this.originalConsoleWarn = console.warn; this.originalConsoleLog = console.log; console.error = (...args) => { this.originalConsoleError.apply(console, args); this.captureConsoleLog('error', args); this.handleConsoleError(args); }; console.warn = (...args) => { this.originalConsoleWarn.apply(console, args); this.captureConsoleLog('warn', args); }; console.log = (...args) => { this.originalConsoleLog.apply(console, args); this.captureConsoleLog('log', args); }; } removeErrorHandlers() { if (typeof process !== 'undefined' && process.removeListener) { process.removeListener('uncaughtException', this.handleUncaughtException); process.removeListener('unhandledRejection', this.handleUnhandledRejection); } if (typeof window !== 'undefined') { if (this.originalErrorHandler) { window.onerror = this.originalErrorHandler; } if (this.originalUnhandledRejectionHandler) { window.onunhandledrejection = this.originalUnhandledRejectionHandler; } } } handleUncaughtException(error) { const timestamp = Date.now(); const comprehensiveMessage = this.buildComprehensiveMessage(error.message, { stack: error.stack, url: typeof window !== 'undefined' ? window.location?.href : undefined, userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined }); this.reportError({ message: comprehensiveMessage, stack: error.stack, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'uncaughtException', severity: 'critical', accessToken: this.config?.accessToken || '' }); } handleUnhandledRejection(reason) { const timestamp = Date.now(); const message = reason instanceof Error ? reason.message : String(reason); const stack = reason instanceof Error ? reason.stack : undefined; const comprehensiveMessage = this.buildComprehensiveMessage(message, { stack, url: typeof window !== 'undefined' ? window.location?.href : undefined, userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined }); this.reportError({ message: comprehensiveMessage, stack, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'unhandledRejection', severity: 'high', accessToken: this.config?.accessToken || '' }); } handleWindowError(message, source, lineno, colno, error) { const timestamp = Date.now(); const errorMessage = typeof message === 'string' ? message : 'Unknown error'; const comprehensiveMessage = this.buildComprehensiveMessage(errorMessage, { stack: error?.stack, url: window.location?.href, userAgent: navigator?.userAgent, source, lineno, colno }); this.reportError({ message: comprehensiveMessage, stack: error?.stack, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'windowError', severity: 'medium', accessToken: this.config?.accessToken || '', metadata: { source, lineno, colno } }); return this.originalErrorHandler ? this.originalErrorHandler(message, source, lineno, colno, error) : true; } handleWindowUnhandledRejection(event) { const timestamp = Date.now(); const reason = event.reason; const message = reason instanceof Error ? reason.message : String(reason); const stack = reason instanceof Error ? reason.stack : undefined; const comprehensiveMessage = this.buildComprehensiveMessage(message, { stack, url: window.location?.href, userAgent: navigator?.userAgent }); this.reportError({ message: comprehensiveMessage, stack, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'windowUnhandledRejection', severity: 'high', accessToken: this.config?.accessToken || '' }); if (this.originalUnhandledRejectionHandler) { this.originalUnhandledRejectionHandler(event); } return true; } handleConsoleError(args) { const timestamp = Date.now(); const message = args.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' '); // Only report if it looks like an actual error if (message.toLowerCase().includes('error') || args.some(arg => arg instanceof Error)) { const comprehensiveMessage = this.buildComprehensiveMessage(message, { url: typeof window !== 'undefined' ? window.location?.href : undefined, userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined }); this.reportError({ message: comprehensiveMessage, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'consoleError', severity: 'low', accessToken: this.config?.accessToken || '', metadata: { args: args.map(arg => { try { return typeof arg === 'object' ? JSON.stringify(arg) : String(arg); } catch { return '[Circular Object]'; } }) } }); } } async reportError(errorReport) { if (!this.config) return; try { // Get webhook URL from environment variable or default (same as CLI) const webhookUrl = process.env.WINGMAN_WEBHOOK_URL || 'https://patchworks-seven.vercel.app/webhook'; // Format payload to match webhook's expected structure const payload = { event: 'error.runtime', data: { message: errorReport.message, errorType: errorReport.errorType, severity: errorReport.severity, environment: errorReport.environment, accessToken: errorReport.accessToken, timestamp: errorReport.timestamp, // This is now a number (Date.now()) stack: errorReport.stack, projectInfo: errorReport.projectInfo, metadata: errorReport.metadata }, timestamp: errorReport.timestamp, source: 'wingman-monitor' }; await axios_1.default.post(webhookUrl, payload, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${errorReport.accessToken}`, 'User-Agent': 'Wingman-Monitor/1.0.0' }, timeout: 5000 // 5 second timeout }); console.log('Wingman: Error reported successfully'); } catch (error) { console.error('Wingman: Failed to report error:', error); } } getProjectInfo() { try { // Try to read package.json for project info const fs = require('fs'); const path = require('path'); const packageJsonPath = path.join(this.config?.projectPath || process.cwd(), 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); return { name: packageJson.name, version: packageJson.version, url: packageJson.homepage || packageJson.repository?.url }; } } catch (error) { // Ignore errors when reading package.json } return {}; } // Public method to manually report errors async reportCustomError(error, metadata) { const timestamp = Date.now(); const comprehensiveMessage = this.buildComprehensiveMessage(error.message, { stack: error.stack, url: typeof window !== 'undefined' ? window.location?.href : undefined, userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined }); await this.reportError({ message: comprehensiveMessage, stack: error.stack, timestamp, environment: this.config?.environment || 'unknown', projectInfo: this.getProjectInfo(), errorType: 'customError', severity: 'medium', accessToken: this.config?.accessToken || '', metadata }); } // Check if monitoring is active isActive() { return this.isStarted && this.config?.enabled === true; } // Temporary method for testing webhook calls async testWebhook() { const testError = new Error('Temporary test error'); const timestamp = Date.now(); const comprehensiveMessage = this.buildComprehensiveMessage(testError.message, { stack: testError.stack, url: typeof window !== 'undefined' ? window.location?.href : undefined, userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined }); await this.reportError({ message: comprehensiveMessage, stack: testError.stack, timestamp, environment: this.config?.environment || 'test', projectInfo: this.getProjectInfo(), errorType: 'testError', severity: 'low', accessToken: this.config?.accessToken || '', metadata: { test: true } }); console.log('Test webhook call sent successfully.'); } } exports.WingmanMonitor = WingmanMonitor; //# sourceMappingURL=monitor.js.map