@critters/next
Version:
Secure bug reporting library for Next.js applications
261 lines (260 loc) • 12.2 kB
JavaScript
"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;