UNPKG

@critters/next

Version:

Secure bug reporting library for Next.js applications

261 lines (260 loc) 12.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Critters = void 0; const config_1 = require("./config"); const utils_1 = require("./utils"); const collectors_1 = require("./collectors"); /** * Core bug reporting functionality */ class Critters { constructor() { this.config = config_1.BugTrackerConfig.getInstance(); } /** * Get the reporter instance */ static getInstance() { if (!Critters.instance) { Critters.instance = new Critters(); } return Critters.instance; } /** * Report a bug to the configured endpoint */ catch(error, metadata) { return __awaiter(this, void 0, void 0, function* () { if (!this.config.isEnabled()) { (0, utils_1.debugLog)("Bug tracker not initialized or disabled"); return { success: false, message: "Bug tracker not initialized or disabled", }; } try { const errorObj = (0, utils_1.formatError)(error); // Create the bug report const bugReport = yield this.createBugReport(errorObj, metadata); // Run the onBeforeReport hook if configured const onBeforeReport = this.config.getOnBeforeReport(); if (onBeforeReport) { const modifiedReport = onBeforeReport(bugReport); if (modifiedReport === false) { (0, utils_1.debugLog)("Bug report cancelled by onBeforeReport hook"); return { success: false, message: "Bug report cancelled by onBeforeReport hook", }; } // If the hook returned a modified report, use it if (modifiedReport) { Object.assign(bugReport, modifiedReport); } } // Send the bug report const result = yield this.sendBugReport(bugReport); // Run the onAfterReport hook if configured const onAfterReport = this.config.getOnAfterReport(); if (onAfterReport) { onAfterReport(bugReport, result); } // Clear captured data after successful report if (result.success) { (0, collectors_1.clearCapturedData)(); } return result; } catch (reportError) { (0, utils_1.debugLog)("Error reporting bug:", reportError); return { success: false, message: "Error reporting bug" }; } }); } /** * Report a bug to the configured endpoint * @deprecated Use catch() instead */ reportBug(error, metadata) { return __awaiter(this, void 0, void 0, function* () { return this.catch(error, metadata); }); } /** * Create a bug report from an error */ createBugReport(errorObj, metadata) { return __awaiter(this, void 0, void 0, function* () { // Basic report structure const bugReport = { title: errorObj.name || "Error", error_message: errorObj.message, description: errorObj.message, source: (0, utils_1.isBrowser)() ? "CLIENT" : "SERVER", severity: "MEDIUM", stack_trace: errorObj.stack, timestamp: (0, utils_1.getTimestamp)(), // Project information projectId: this.config.getProjectId(), // Additional metadata metadata: metadata ? (0, utils_1.sanitizeData)(metadata) : {}, }; // Add user ID if provided in metadata if (metadata === null || metadata === void 0 ? void 0 : metadata.userId) { bugReport.userId = metadata.userId; } // Add component stack if provided in metadata if (metadata === null || metadata === void 0 ? void 0 : metadata.componentStack) { bugReport.component_stack = metadata.componentStack; } // Add browser-specific information if ((0, utils_1.isBrowser)()) { // Add browser info bugReport.browser_info = (0, collectors_1.getBrowserData)(); // Add URL bugReport.url = window.location.href; // Add user agent bugReport.userAgent = window.navigator.userAgent; // Add console logs if enabled if (this.config.shouldCaptureConsole()) { bugReport.console_logs = (0, collectors_1.getConsoleLogs)(); } // Add network requests if enabled if (this.config.shouldCaptureNetworkRequests()) { bugReport.network_requests = (0, collectors_1.getNetworkRequests)(); } // Add user interactions if enabled if (this.config.shouldCaptureUserInteractions()) { bugReport.user_interaction = (0, collectors_1.getUserInteractions)(); } // Add application state if enabled and provided if (this.config.shouldCaptureApplicationState() && (metadata === null || metadata === void 0 ? void 0 : metadata.applicationState)) { bugReport.application_state = (0, utils_1.sanitizeData)(metadata.applicationState); } // Add props context if provided if (metadata === null || metadata === void 0 ? void 0 : metadata.props) { bugReport.props_context = (0, utils_1.sanitizeData)(metadata.props); } // Add error boundary information if provided if (metadata === null || metadata === void 0 ? void 0 : metadata.errorBoundary) { bugReport.error_boundary = metadata.errorBoundary; } // Add source map URL if available if (errorObj.stack) { // Extract source map URL from stack trace if present const sourceMapMatch = errorObj.stack.match(/\/\/# sourceMappingURL=(.+)$/m); if (sourceMapMatch && sourceMapMatch[1]) { bugReport.source_map_url = sourceMapMatch[1]; } } } // For server-side errors, add request information if available if (!(0, utils_1.isBrowser)() && (metadata === null || metadata === void 0 ? void 0 : metadata.route)) { bugReport.metadata = Object.assign(Object.assign({}, bugReport.metadata), { route: metadata.route, method: metadata.method, query: (0, utils_1.sanitizeData)(metadata.query || {}), headers: (0, utils_1.sanitizeData)(metadata.headers || {}) }); } return bugReport; }); } /** * Send a bug report to the configured endpoint */ sendBugReport(bugReport) { return __awaiter(this, void 0, void 0, function* () { try { // Get the endpoint from configuration const endpoint = this.config.getEndpoint(); // Get the API key from configuration const apiKey = this.config.getApiKey(); // Get the secret key from configuration or fetch it let secretKey = this.config.getSecretKey(); let projectId = this.config.getProjectId(); // If secret key is not provided, fetch it from the API if (!secretKey) { try { // Fetch configuration from the API const configResponse = yield fetch(`${endpoint}/config`, { method: "GET", headers: { "X-API-Key": apiKey, }, }); if (configResponse.ok) { const configData = yield configResponse.json(); secretKey = configData.secretKey; // Update configuration with fetched values if (secretKey) { this.config.setSecretKey(secretKey); } // Update project ID if provided and not already set if (configData.projectId && !projectId) { projectId = configData.projectId; if (projectId) { this.config.setProjectId(projectId); bugReport.projectId = projectId; } } } else { (0, utils_1.debugLog)("Failed to fetch configuration"); return { success: false, message: "Failed to fetch configuration" }; } } catch (configError) { (0, utils_1.debugLog)("Error fetching configuration:", configError); return { success: false, message: "Error fetching configuration" }; } } // Ensure we have a secret key if (!secretKey) { (0, utils_1.debugLog)("No secret key available for signing the request"); return { success: false, message: "No secret key available for signing the request", }; } // Convert the bug report to a JSON string const payload = (0, utils_1.safeStringify)(bugReport); // Generate a signature for the request const signature = (0, utils_1.generateSignature)(payload, secretKey); // Send the request const response = yield fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": apiKey, "X-Signature": signature, }, body: payload, }); // Parse the response const result = yield response.json(); // Log the result in debug mode (0, utils_1.debugLog)("Bug report result:", result); // Return the result return { success: response.ok, message: response.ok ? "Bug reported successfully" : "Failed to report bug", reportId: result.id, timestamp: result.timestamp, }; } catch (error) { (0, utils_1.debugLog)("Error sending bug report:", error); return { success: false, message: "Error sending bug report" }; } }); } } exports.Critters = Critters;