UNPKG

unified-error-handling

Version:

A lightweight, zero-dependency error handling library with dynamic adapter loading for multiple error tracking services

1,621 lines (1,606 loc) 55.6 kB
var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; // src/adapters/base-adapter.ts var BaseAdapter; var init_base_adapter = __esm({ "src/adapters/base-adapter.ts"() { "use strict"; BaseAdapter = class { constructor() { __publicField(this, "config"); __publicField(this, "sdkInstance"); __publicField(this, "sdkLoaded", false); } async initialize(config) { this.config = config; await this.loadSDK(); await this.initializeSDK(); } async captureMessage(message, level) { const error = { message, timestamp: Date.now(), context: {}, breadcrumbs: [], level: level || "info", handled: true, source: "manual" }; await this.captureError(error); } async setContext(_context) { } async addBreadcrumb(_breadcrumb) { } async flush() { } async close() { } async dynamicImport(packageName) { try { return await import(packageName); } catch (error) { throw new Error( `Failed to load ${packageName}. Please install it: npm install ${packageName} or yarn add ${packageName}` ); } } }; } }); // src/adapters/sentry-adapter.ts var sentry_adapter_exports = {}; __export(sentry_adapter_exports, { SentryAdapter: () => SentryAdapter }); var SentryAdapter; var init_sentry_adapter = __esm({ "src/adapters/sentry-adapter.ts"() { "use strict"; init_base_adapter(); SentryAdapter = class extends BaseAdapter { constructor() { super(...arguments); __publicField(this, "name", "sentry"); __publicField(this, "Sentry"); } async loadSDK() { if (this.sdkLoaded) return; try { const sentryModule = await this.dynamicImport("@sentry/browser"); this.Sentry = sentryModule; this.sdkLoaded = true; } catch (error) { if (typeof window !== "undefined" && window.Sentry) { this.Sentry = window.Sentry; this.sdkLoaded = true; } else { throw error; } } } async initializeSDK() { if (!this.config.dsn) { throw new Error("Sentry DSN is required"); } this.Sentry.init({ dsn: this.config.dsn, environment: this.config.environment, release: this.config.release, debug: this.config.debug, beforeSend: this.config.beforeSend, // Disable Sentry's own global handlers since we handle them integrations: this.config.integrations ? this.config.integrations : (integrations) => integrations.filter( (integration) => !["GlobalHandlers", "TryCatch"].includes(integration.name) ) }); } async captureError(error) { const sentryError = new Error(error.message); if (error.stack) { sentryError.stack = error.stack; } this.Sentry.captureException(sentryError, { level: error.level, contexts: { custom: error.context.custom, device: error.context.device }, tags: error.context.tags, extra: { ...error.context.extra, fingerprint: error.fingerprint, handled: error.handled, source: error.source }, user: error.context.user, breadcrumbs: error.breadcrumbs.map((b) => ({ timestamp: b.timestamp / 1e3, // Sentry expects seconds message: b.message, category: b.category, level: b.level, data: b.data })) }); } async setContext(context) { if (context.user) { this.Sentry.setUser(context.user); } if (context.tags) { Object.entries(context.tags).forEach(([key, value]) => { this.Sentry.setTag(key, value); }); } if (context.extra) { Object.entries(context.extra).forEach(([key, value]) => { this.Sentry.setExtra(key, value); }); } if (context.custom) { this.Sentry.setContext("custom", context.custom); } if (context.device) { this.Sentry.setContext("device", context.device); } } async addBreadcrumb(breadcrumb) { this.Sentry.addBreadcrumb({ timestamp: breadcrumb.timestamp / 1e3, message: breadcrumb.message, category: breadcrumb.category, level: breadcrumb.level, data: breadcrumb.data }); } async flush() { await this.Sentry.flush(); } async close() { await this.Sentry.close(); } }; } }); // src/adapters/firebase-adapter.ts var firebase_adapter_exports = {}; __export(firebase_adapter_exports, { FirebaseAdapter: () => FirebaseAdapter }); var FirebaseAdapter; var init_firebase_adapter = __esm({ "src/adapters/firebase-adapter.ts"() { "use strict"; init_base_adapter(); FirebaseAdapter = class extends BaseAdapter { constructor() { super(...arguments); __publicField(this, "name", "firebase"); __publicField(this, "crashlytics"); __publicField(this, "analytics"); } async loadSDK() { if (this.sdkLoaded) return; try { const { initializeApp, getApps } = await this.dynamicImport("firebase/app"); const { getCrashlytics } = await this.dynamicImport("firebase/crashlytics"); const { getAnalytics } = await this.dynamicImport("firebase/analytics"); let app; if (getApps().length === 0) { if (!this.config.firebaseConfig) { throw new Error("Firebase configuration is required"); } app = initializeApp(this.config.firebaseConfig); } else { app = getApps()[0]; } this.crashlytics = getCrashlytics(app); if (this.config.enableAnalytics !== false) { try { this.analytics = getAnalytics(app); } catch (e) { console.warn("Firebase Analytics not available:", e); } } this.sdkLoaded = true; } catch (error) { throw new Error( `Failed to load Firebase SDK. Please install: npm install firebase or include Firebase SDK via CDN` ); } } async initializeSDK() { } async captureError(error) { const { recordException, log, setCustomKey } = await this.dynamicImport("firebase/crashlytics"); if (error.context.tags) { for (const [key, value] of Object.entries(error.context.tags)) { setCustomKey(this.crashlytics, key, value); } } if (error.context.extra) { for (const [key, value] of Object.entries(error.context.extra)) { setCustomKey(this.crashlytics, key, JSON.stringify(value)); } } setCustomKey(this.crashlytics, "error_level", error.level || "error"); setCustomKey(this.crashlytics, "error_source", error.source || "unknown"); setCustomKey(this.crashlytics, "error_handled", String(error.handled)); for (const breadcrumb of error.breadcrumbs) { log(this.crashlytics, `[${breadcrumb.category || "general"}] ${breadcrumb.message}`); } const errorObj = new Error(error.message); if (error.stack) { errorObj.stack = error.stack; } errorObj.name = error.type || "Error"; recordException(this.crashlytics, errorObj); if (this.analytics) { const { logEvent } = await this.dynamicImport("firebase/analytics"); logEvent(this.analytics, "exception", { description: error.message, fatal: error.level === "fatal" }); } } async setContext(context) { const { setUserId, setCustomKey } = await this.dynamicImport("firebase/crashlytics"); if (context.user?.id) { setUserId(this.crashlytics, context.user.id); } if (context.user) { for (const [key, value] of Object.entries(context.user)) { if (key !== "id") { setCustomKey(this.crashlytics, `user_${key}`, String(value)); } } } if (context.device) { for (const [key, value] of Object.entries(context.device)) { setCustomKey(this.crashlytics, `device_${key}`, String(value)); } } if (context.custom) { for (const [key, value] of Object.entries(context.custom)) { setCustomKey(this.crashlytics, key, JSON.stringify(value)); } } } async addBreadcrumb(breadcrumb) { const { log } = await this.dynamicImport("firebase/crashlytics"); const level = breadcrumb.level || "info"; const category = breadcrumb.category || "general"; log(this.crashlytics, `[${level}][${category}] ${breadcrumb.message}`); } async flush() { } async close() { } }; } }); // src/adapters/custom-adapter.ts function createCustomAdapter(config) { return new CustomAdapter(config); } var CustomAdapter; var init_custom_adapter = __esm({ "src/adapters/custom-adapter.ts"() { "use strict"; CustomAdapter = class { constructor(config) { __publicField(this, "name", "custom"); __publicField(this, "config"); __publicField(this, "context", {}); __publicField(this, "breadcrumbs", []); this.config = config; } async initialize() { if (this.config.initialize) { await this.config.initialize(); } } async captureError(error) { const fullContext = { ...this.context, ...error.context }; await this.config.send(error, fullContext); } async captureMessage(message, level) { const error = { message, timestamp: Date.now(), context: this.context, breadcrumbs: [...this.breadcrumbs], level: level || "info", handled: true, source: "manual" }; await this.captureError(error); } async setContext(context) { this.context = { ...context }; if (this.config.setContext) { await this.config.setContext(context); } } async addBreadcrumb(breadcrumb) { this.breadcrumbs.push(breadcrumb); if (this.config.addBreadcrumb) { await this.config.addBreadcrumb(breadcrumb); } } async flush() { if (this.config.flush) { await this.config.flush(); } } async close() { if (this.config.close) { await this.config.close(); } } }; } }); // src/adapters/index.ts var adapters_exports = {}; __export(adapters_exports, { BaseAdapter: () => BaseAdapter, CustomAdapter: () => CustomAdapter, FirebaseAdapter: () => FirebaseAdapter, SentryAdapter: () => SentryAdapter, createCustomAdapter: () => createCustomAdapter, loadAdapter: () => loadAdapter }); async function loadAdapter(name) { switch (name) { case "sentry": const { SentryAdapter: SentryAdapter2 } = await Promise.resolve().then(() => (init_sentry_adapter(), sentry_adapter_exports)); return new SentryAdapter2(); case "firebase": const { FirebaseAdapter: FirebaseAdapter2 } = await Promise.resolve().then(() => (init_firebase_adapter(), firebase_adapter_exports)); return new FirebaseAdapter2(); default: return null; } } var init_adapters = __esm({ "src/adapters/index.ts"() { "use strict"; init_base_adapter(); init_sentry_adapter(); init_firebase_adapter(); init_custom_adapter(); } }); // src/react/hooks.ts import { useCallback, useEffect, useState, useSyncExternalStore } from "react"; // src/utils/error-enricher.ts function enrichError(error) { if (typeof window !== "undefined") { error.context.device = { ...error.context.device, userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, cookieEnabled: navigator.cookieEnabled, onLine: navigator.onLine, viewport: { width: window.innerWidth, height: window.innerHeight }, screen: { width: screen.width, height: screen.height, colorDepth: screen.colorDepth } }; error.context.extra = { ...error.context.extra, url: window.location.href, referrer: document.referrer }; } if (!error.fingerprint) { error.fingerprint = generateFingerprint(error); } if (error.stack) { const parsedStack = parseStackTrace(error.stack); error.context.extra = { ...error.context.extra, parsedStack }; } return error; } function generateFingerprint(error) { const parts = [ error.type || "Error", error.message, error.stack ? extractTopStackFrame(error.stack) : "" ].filter(Boolean); return parts.join("|").replace(/\s+/g, " ").trim(); } function extractTopStackFrame(stack) { const lines = stack.split("\n"); for (const line of lines) { if (line.includes("at ") && !line.includes("Error")) { return line.trim(); } } return ""; } function parseStackTrace(stack) { const frames = []; const lines = stack.split("\n"); for (const line of lines) { if (!line.includes("at ")) continue; const functionMatch = line.match(/at\s+([^\s]+)\s+\(/); const functionName = functionMatch ? functionMatch[1] : "<anonymous>"; const fileMatch = line.match(/\((.+):(\d+):(\d+)\)/); if (fileMatch) { frames.push({ function: functionName, file: fileMatch[1], line: parseInt(fileMatch[2], 10), column: parseInt(fileMatch[3], 10) }); } else { const altMatch = line.match(/at\s+(.+):(\d+):(\d+)/); if (altMatch) { frames.push({ file: altMatch[1], line: parseInt(altMatch[2], 10), column: parseInt(altMatch[3], 10) }); } } } return frames; } // src/utils/console-interceptor.ts var ConsoleInterceptor = class { constructor() { __publicField(this, "originalConsole", { error: console.error, warn: console.warn, log: console.log, info: console.info }); __publicField(this, "enabled", false); } enable() { if (this.enabled) return; console.error = (...args) => { this.originalConsole.error.apply(console, args); const error = args[0] instanceof Error ? args[0] : new Error(String(args[0])); errorStore.captureError(error, { extra: { source: "console.error", consoleArgs: args.slice(1) } }); errorStore.addBreadcrumb({ message: `console.error: ${args.map((arg) => String(arg)).join(" ")}`, category: "console", level: "error" }); }; console.warn = (...args) => { this.originalConsole.warn.apply(console, args); errorStore.addBreadcrumb({ message: `console.warn: ${args.map((arg) => String(arg)).join(" ")}`, category: "console", level: "warning" }); }; console.log = (...args) => { this.originalConsole.log.apply(console, args); errorStore.addBreadcrumb({ message: `console.log: ${args.map((arg) => String(arg)).join(" ")}`, category: "console", level: "info" }); }; console.info = (...args) => { this.originalConsole.info.apply(console, args); errorStore.addBreadcrumb({ message: `console.info: ${args.map((arg) => String(arg)).join(" ")}`, category: "console", level: "info" }); }; this.enabled = true; } disable() { if (!this.enabled) return; console.error = this.originalConsole.error; console.warn = this.originalConsole.warn; console.log = this.originalConsole.log; console.info = this.originalConsole.info; this.enabled = false; } }; var consoleInterceptor = new ConsoleInterceptor(); // src/utils/network-interceptor.ts var NetworkInterceptor = class { constructor() { __publicField(this, "enabled", false); __publicField(this, "originalFetch", typeof window !== "undefined" ? window.fetch : void 0); __publicField(this, "xhrPrototype", typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest.prototype : void 0); __publicField(this, "originalOpen", this.xhrPrototype?.open); __publicField(this, "originalSend", this.xhrPrototype?.send); } enable() { if (this.enabled || typeof window === "undefined" || !this.originalFetch || !this.xhrPrototype) return; window.fetch = async (...args) => { const [input, init] = args; const url = typeof input === "string" ? input : input instanceof Request ? input.url : input.toString(); const method = init?.method || "GET"; const startTime = Date.now(); try { const response = await this.originalFetch.apply(window, args); const duration = Date.now() - startTime; errorStore.addBreadcrumb({ message: `${method} ${url} (${response.status})`, category: "fetch", level: response.ok ? "info" : "warning", data: { method, url, status: response.status, statusText: response.statusText, duration } }); if (!response.ok && response.status >= 400) { const error = new Error(`HTTP ${response.status}: ${response.statusText}`); errorStore.captureError(error, { extra: { source: "fetch", url, method, status: response.status, statusText: response.statusText, duration } }); } return response; } catch (error) { const duration = Date.now() - startTime; errorStore.addBreadcrumb({ message: `${method} ${url} (failed)`, category: "fetch", level: "error", data: { method, url, error: String(error), duration } }); errorStore.captureError(error, { extra: { source: "fetch", url, method, duration } }); throw error; } }; const self = this; this.xhrPrototype.open = function(method, url, ...args) { this._method = method; this._url = url; this._startTime = Date.now(); return self.originalOpen.apply(this, [method, url, ...args]); }; this.xhrPrototype.send = function(...args) { const xhr = this; xhr.addEventListener("load", function() { const duration = Date.now() - (xhr._startTime || 0); errorStore.addBreadcrumb({ message: `${xhr._method} ${xhr._url} (${xhr.status})`, category: "xhr", level: xhr.status >= 200 && xhr.status < 400 ? "info" : "warning", data: { method: xhr._method, url: xhr._url, status: xhr.status, statusText: xhr.statusText, duration } }); if (xhr.status >= 400) { const error = new Error(`HTTP ${xhr.status}: ${xhr.statusText}`); errorStore.captureError(error, { extra: { source: "xhr", url: xhr._url, method: xhr._method, status: xhr.status, statusText: xhr.statusText, duration } }); } }); xhr.addEventListener("error", function() { const duration = Date.now() - (xhr._startTime || 0); errorStore.addBreadcrumb({ message: `${xhr._method} ${xhr._url} (failed)`, category: "xhr", level: "error", data: { method: xhr._method, url: xhr._url, duration } }); const error = new Error(`XHR failed: ${xhr._method} ${xhr._url}`); errorStore.captureError(error, { extra: { source: "xhr", url: xhr._url, method: xhr._method, duration } }); }); return self.originalSend.apply(this, args); }; this.enabled = true; } disable() { if (!this.enabled || typeof window === "undefined" || !this.originalFetch || !this.xhrPrototype) return; window.fetch = this.originalFetch; if (this.originalOpen) this.xhrPrototype.open = this.originalOpen; if (this.originalSend) this.xhrPrototype.send = this.originalSend; this.enabled = false; } }; var networkInterceptor = new NetworkInterceptor(); // src/store/error-store.ts var ErrorStoreImpl = class { constructor() { // State __publicField(this, "context", {}); __publicField(this, "breadcrumbs", []); __publicField(this, "adapters", /* @__PURE__ */ new Map()); __publicField(this, "activeAdapter", null); __publicField(this, "config", { maxBreadcrumbs: 100, enableGlobalHandlers: true, enableOfflineQueue: true, enableConsoleCapture: true, enableNetworkCapture: false, debug: false }); __publicField(this, "initialized", false); __publicField(this, "offline", false); __publicField(this, "errorQueue", []); // Private properties __publicField(this, "listeners", /* @__PURE__ */ new Set()); __publicField(this, "globalHandlersInstalled", false); __publicField(this, "offlineHandler"); __publicField(this, "onlineHandler"); if (typeof window !== "undefined") { this.setupOfflineHandlers(); } } // Actions initialize(config) { if (this.initialized) { console.warn("[ErrorStore] Already initialized"); return; } this.config = { ...this.config, ...config }; this.initialized = true; if (this.config.enableGlobalHandlers && typeof window !== "undefined") { this.installGlobalHandlers(); } if (this.config.enableConsoleCapture) { consoleInterceptor.enable(); } if (this.config.enableNetworkCapture) { networkInterceptor.enable(); } if (typeof window !== "undefined") { this.setContext({ device: { platform: "web", userAgent: navigator.userAgent, language: navigator.language, viewport: { width: window.innerWidth, height: window.innerHeight } } }); } } captureError(error, additionalContext) { if (!this.initialized) { console.warn("[ErrorStore] Not initialized. Call initialize() first."); return; } let normalizedError = this.normalizeError(error, additionalContext); normalizedError = enrichError(normalizedError); if (this.config.beforeSend) { const processed = this.config.beforeSend(normalizedError); if (!processed) return; } if (this.offline && this.config.enableOfflineQueue) { this.errorQueue.push(normalizedError); return; } this.sendToAdapter(normalizedError); this.listeners.forEach((listener) => listener(normalizedError)); } captureMessage(message, level = "info") { const error = new Error(message); error.name = "CapturedMessage"; this.captureError(error, { extra: { level } }); } setUser(user) { if (user === null) { delete this.context.user; } else { this.context.user = { ...user }; } if (this.activeAdapter) { const adapter = this.adapters.get(this.activeAdapter); adapter?.setContext?.(this.context); } } setContext(context) { this.context = { ...this.context, ...context, // Deep merge for nested objects user: context.user ? { ...this.context.user, ...context.user } : this.context.user, device: context.device ? { ...this.context.device, ...context.device } : this.context.device, custom: context.custom ? { ...this.context.custom, ...context.custom } : this.context.custom, tags: context.tags ? { ...this.context.tags, ...context.tags } : this.context.tags, extra: context.extra ? { ...this.context.extra, ...context.extra } : this.context.extra }; if (this.activeAdapter) { const adapter = this.adapters.get(this.activeAdapter); adapter?.setContext?.(this.context); } } addBreadcrumb(breadcrumb) { const fullBreadcrumb = { ...breadcrumb, timestamp: Date.now() }; this.breadcrumbs.push(fullBreadcrumb); if (this.config.maxBreadcrumbs && this.breadcrumbs.length > this.config.maxBreadcrumbs) { this.breadcrumbs = this.breadcrumbs.slice(-this.config.maxBreadcrumbs); } if (this.activeAdapter) { const adapter = this.adapters.get(this.activeAdapter); adapter?.addBreadcrumb?.(fullBreadcrumb); } } clearBreadcrumbs() { this.breadcrumbs = []; } async useAdapter(name, config) { let adapter = this.adapters.get(name); if (!adapter) { adapter = await this.loadBuiltInAdapter(name) || void 0; if (!adapter) { throw new Error(`Adapter '${name}' not found. You may need to register a custom adapter.`); } } await adapter.initialize(config); this.adapters.set(name, adapter); this.activeAdapter = name; if (adapter.setContext) { await adapter.setContext(this.context); } if (adapter.addBreadcrumb) { for (const breadcrumb of this.breadcrumbs) { await adapter.addBreadcrumb(breadcrumb); } } if (this.errorQueue.length > 0) { await this.processOfflineQueue(); } } removeAdapter(name) { const adapter = this.adapters.get(name); if (adapter) { adapter.close?.(); this.adapters.delete(name); if (this.activeAdapter === name) { this.activeAdapter = null; } } } async flush() { if (this.activeAdapter) { const adapter = this.adapters.get(this.activeAdapter); await adapter?.flush?.(); } } reset() { this.adapters.forEach((adapter) => adapter.close?.()); this.context = {}; this.breadcrumbs = []; this.adapters.clear(); this.activeAdapter = null; this.errorQueue = []; this.initialized = false; if (this.globalHandlersInstalled) { this.removeGlobalHandlers(); } consoleInterceptor.disable(); networkInterceptor.disable(); } // Public methods for advanced usage subscribe(listener) { this.listeners.add(listener); return () => this.listeners.delete(listener); } registerAdapter(name, adapter) { this.adapters.set(name, adapter); } // Private methods normalizeError(error, additionalContext) { const errorObj = typeof error === "string" ? new Error(error) : error; return { message: errorObj.message || "Unknown error", stack: errorObj.stack, type: errorObj.name || "Error", timestamp: Date.now(), context: { ...this.context, ...additionalContext }, breadcrumbs: [...this.breadcrumbs], level: "error", handled: true, source: "manual" }; } async sendToAdapter(error) { if (!this.activeAdapter) { if (this.config.debug) { console.error("[ErrorStore] No active adapter. Error:", error); } return; } const adapter = this.adapters.get(this.activeAdapter); if (adapter) { try { await adapter.captureError(error); } catch (err) { console.error(`[ErrorStore] Failed to send error to adapter '${this.activeAdapter}':`, err); } } } installGlobalHandlers() { if (this.globalHandlersInstalled) return; window.addEventListener("error", (event) => { this.captureError(event.error || new Error(event.message), { extra: { source: "global", filename: event.filename, lineno: event.lineno, colno: event.colno } }); }); window.addEventListener("unhandledrejection", (event) => { const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason)); this.captureError(error, { extra: { source: "unhandledRejection", promise: event.promise } }); }); this.globalHandlersInstalled = true; } removeGlobalHandlers() { this.globalHandlersInstalled = false; } setupOfflineHandlers() { this.offline = !navigator.onLine; this.offlineHandler = () => { this.offline = true; this.addBreadcrumb({ message: "Device went offline", category: "network", level: "warning" }); }; this.onlineHandler = () => { this.offline = false; this.addBreadcrumb({ message: "Device came online", category: "network", level: "info" }); if (this.errorQueue.length > 0) { this.processOfflineQueue(); } }; window.addEventListener("offline", this.offlineHandler); window.addEventListener("online", this.onlineHandler); } async processOfflineQueue() { const errors = [...this.errorQueue]; this.errorQueue = []; for (const error of errors) { await this.sendToAdapter(error); } } async loadBuiltInAdapter(name) { switch (name) { case "console": return { name: "console", async initialize() { const originalLog = consoleInterceptor["originalConsole"]?.log || console.log; originalLog("[ConsoleAdapter] Initialized"); }, async captureError(error) { const originalError = consoleInterceptor["originalConsole"]?.error || console.error; originalError("[Error]", error); }, async captureMessage(message, level) { const originalLog = consoleInterceptor["originalConsole"]?.log || console.log; originalLog(`[${level || "info"}]`, message); } }; case "sentry": case "firebase": const { loadAdapter: loadAdapter2 } = await Promise.resolve().then(() => (init_adapters(), adapters_exports)); return await loadAdapter2(name); default: return null; } } }; var errorStore = new ErrorStoreImpl(); var initialize = errorStore.initialize.bind(errorStore); var captureError = errorStore.captureError.bind(errorStore); var captureMessage = errorStore.captureMessage.bind(errorStore); var setUser = errorStore.setUser.bind(errorStore); var setContext = errorStore.setContext.bind(errorStore); var addBreadcrumb = errorStore.addBreadcrumb.bind(errorStore); var clearBreadcrumbs = errorStore.clearBreadcrumbs.bind(errorStore); var useAdapter = errorStore.useAdapter.bind(errorStore); var removeAdapter = errorStore.removeAdapter.bind(errorStore); var flush = errorStore.flush.bind(errorStore); var reset = errorStore.reset.bind(errorStore); var subscribe = errorStore.subscribe.bind(errorStore); var registerAdapter = errorStore.registerAdapter.bind(errorStore); // src/react/hooks.ts function useErrorStore() { const state = useSyncExternalStore( (_callback) => { return () => { }; }, () => ({ initialized: errorStore.initialized, offline: errorStore.offline, activeAdapter: errorStore.activeAdapter }), () => ({ initialized: errorStore.initialized, offline: errorStore.offline, activeAdapter: errorStore.activeAdapter }) ); return { ...state, captureError: errorStore.captureError.bind(errorStore), captureMessage: errorStore.captureMessage.bind(errorStore), setUser: errorStore.setUser.bind(errorStore), setContext: errorStore.setContext.bind(errorStore), addBreadcrumb: errorStore.addBreadcrumb.bind(errorStore), clearBreadcrumbs: errorStore.clearBreadcrumbs.bind(errorStore), useAdapter: errorStore.useAdapter.bind(errorStore), removeAdapter: errorStore.removeAdapter.bind(errorStore), flush: errorStore.flush.bind(errorStore), reset: errorStore.reset.bind(errorStore) }; } function useErrorHandler() { const { captureError: captureError2 } = useErrorStore(); const handleError = useCallback((error, context) => { captureError2(error, { ...context, extra: { ...context?.extra, source: "react-hook" } }); }, [captureError2]); return handleError; } function useAsyncError() { const handleError = useErrorHandler(); return useCallback((error) => { handleError(error, { extra: { source: "async-error" } }); }, [handleError]); } function useErrorTracking(componentName) { const { addBreadcrumb: addBreadcrumb2 } = useErrorStore(); useEffect(() => { addBreadcrumb2({ message: `Component mounted: ${componentName}`, category: "navigation", level: "info" }); return () => { addBreadcrumb2({ message: `Component unmounted: ${componentName}`, category: "navigation", level: "info" }); }; }, [componentName, addBreadcrumb2]); } function useAsyncOperation(operation, deps = []) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const handleError = useErrorHandler(); const execute = useCallback(async () => { setLoading(true); setError(null); try { const result = await operation(); setData(result); return result; } catch (err) { const error2 = err instanceof Error ? err : new Error(String(err)); setError(error2); handleError(error2, { extra: { source: "async-operation" } }); throw error2; } finally { setLoading(false); } }, deps); return { data, loading, error, execute }; } // src/react/hooks-extensions.ts import { useCallback as useCallback2 } from "react"; function useComponentError(componentName) { const handleError = useErrorHandler(); const logComponentError = useCallback2((error, phase, context) => { return handleError(error, { tags: { component: componentName, phase }, extra: context }); }, [handleError, componentName]); return { logComponentError }; } function usePerformanceMonitor() { const measurePerformance = useCallback2((name, fn) => { const start = performance.now(); try { const result = fn(); const duration = performance.now() - start; errorStore.addBreadcrumb({ message: `Performance: ${name}`, category: "performance", level: "info", data: { duration } }); return result; } catch (error) { const duration = performance.now() - start; errorStore.addBreadcrumb({ message: `Performance error: ${name}`, category: "performance", level: "error", data: { duration, error: String(error) } }); throw error; } }, []); return { measurePerformance }; } function useExtendedErrorHandler() { const handleError = useErrorHandler(); const logError = useCallback2((error, context) => { return handleError(error, context); }, [handleError]); const logNavigation = useCallback2((from, to, metadata) => { errorStore.addBreadcrumb({ message: `Navigation: ${from} \u2192 ${to}`, category: "navigation", level: "info", data: { from, to, ...metadata } }); }, []); const logUserAction = useCallback2((action, metadata) => { errorStore.addBreadcrumb({ message: `User action: ${action}`, category: "user", level: "info", data: metadata }); }, []); const setTags = useCallback2((tags) => { errorStore.setContext({ ...errorStore.context, tags: { ...errorStore.context.tags, ...tags } }); }, []); return { logError, logNavigation, logUserAction, setTags }; } // src/react/error-boundary.tsx import { Component } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; var ErrorBoundary = class extends Component { constructor(props) { super(props); __publicField(this, "resetTimeoutId", null); __publicField(this, "retryCount", 0); __publicField(this, "maxRetries", 3); /** * Handle error reporting */ __publicField(this, "handleError", async (error, errorInfo) => { const { onError, level = "error", context = {}, tags = {} } = this.props; if (onError) { try { onError(error, errorInfo); } catch (handlerError) { console.error("Error in error handler:", handlerError); } } try { errorStore.captureError(error, { tags, extra: { ...context, componentStack: errorInfo.componentStack, errorBoundary: true, errorId: this.state.errorId, retryCount: this.retryCount, timestamp: (/* @__PURE__ */ new Date()).toISOString(), level, source: "react" } }); } catch (reportError) { console.error("Failed to report error:", reportError); } }); /** * Reset error state */ __publicField(this, "resetError", () => { this.retryCount = 0; this.setState({ hasError: false, error: null, errorInfo: null, errorId: null }); }); /** * Retry with exponential backoff */ __publicField(this, "retry", () => { if (this.retryCount >= this.maxRetries) { console.warn("Maximum retry attempts reached"); return; } this.retryCount++; const delay = Math.pow(2, this.retryCount) * 1e3; this.resetTimeoutId = window.setTimeout(() => { this.resetError(); }, delay); }); this.state = { hasError: false, error: null, errorInfo: null, errorId: null }; } /** * Catch errors in child components */ static getDerivedStateFromError(error) { return { hasError: true, error, errorId: `error-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` }; } /** * Handle component errors */ componentDidCatch(error, errorInfo) { this.setState({ errorInfo }); this.handleError(error, errorInfo); } /** * Reset error boundary on prop changes */ componentDidUpdate(prevProps) { const { resetOnPropsChange, resetKey } = this.props; const { hasError } = this.state; if (hasError && resetOnPropsChange && prevProps.children !== this.props.children) { this.resetError(); } if (hasError && resetKey !== prevProps.resetKey) { this.resetError(); } } /** * Cleanup on unmount */ componentWillUnmount() { if (this.resetTimeoutId) { clearTimeout(this.resetTimeoutId); } } render() { const { hasError, error, errorInfo, errorId } = this.state; const { fallback: FallbackComponent, children, isolate: _isolate } = this.props; if (hasError && error && errorInfo) { if (FallbackComponent) { return /* @__PURE__ */ jsx( FallbackComponent, { error, errorInfo, errorId, resetError: this.resetError, retry: this.retry } ); } return /* @__PURE__ */ jsx( DefaultErrorFallback, { error, errorInfo, errorId, resetError: this.resetError, retry: this.retry } ); } return children; } }; var DefaultErrorFallback = ({ error, errorInfo, errorId, resetError, retry }) => { const isDevelopment = true; return /* @__PURE__ */ jsxs("div", { style: { padding: "24px", border: "2px solid #ff6b6b", borderRadius: "12px", backgroundColor: "#fff5f5", color: "#c92a2a", fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', maxWidth: "600px", margin: "20px auto", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)" }, children: [ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", marginBottom: "16px" }, children: [ /* @__PURE__ */ jsx("div", { style: { width: "24px", height: "24px", backgroundColor: "#ff6b6b", borderRadius: "50%", display: "flex", alignItems: "center", justifyContent: "center", marginRight: "12px", fontSize: "14px", fontWeight: "bold", color: "white" }, children: "!" }), /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "20px", fontWeight: "600" }, children: "Something went wrong" }) ] }), /* @__PURE__ */ jsx("p", { style: { margin: "0 0 16px 0", fontSize: "14px", lineHeight: "1.5", color: "#868e96" }, children: "An unexpected error occurred. The error has been reported and we're working to fix it." }), errorId && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px", backgroundColor: "#f8f9fa", border: "1px solid #e9ecef", borderRadius: "6px", fontSize: "12px", fontFamily: "monospace", color: "#495057", marginBottom: "16px" }, children: [ "Error ID: ", errorId ] }), isDevelopment && /* @__PURE__ */ jsxs("details", { style: { marginBottom: "16px", backgroundColor: "#f8f9fa", border: "1px solid #e9ecef", borderRadius: "6px", padding: "12px" }, children: [ /* @__PURE__ */ jsx("summary", { style: { cursor: "pointer", fontWeight: "500", marginBottom: "8px", fontSize: "14px" }, children: "Error Details (Development)" }), /* @__PURE__ */ jsxs("div", { style: { fontSize: "12px", fontFamily: "monospace", color: "#495057" }, children: [ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "8px" }, children: [ /* @__PURE__ */ jsx("strong", { children: "Error:" }), " ", error.message ] }), /* @__PURE__ */ jsxs("div", { style: { marginBottom: "8px" }, children: [ /* @__PURE__ */ jsx("strong", { children: "Stack:" }), /* @__PURE__ */ jsx("pre", { style: { margin: "4px 0 0 0", padding: "8px", backgroundColor: "#ffffff", border: "1px solid #dee2e6", borderRadius: "4px", overflow: "auto", maxHeight: "150px", fontSize: "11px" }, children: error.stack }) ] }), errorInfo.componentStack && /* @__PURE__ */ jsxs("div", { children: [ /* @__PURE__ */ jsx("strong", { children: "Component Stack:" }), /* @__PURE__ */ jsx("pre", { style: { margin: "4px 0 0 0", padding: "8px", backgroundColor: "#ffffff", border: "1px solid #dee2e6", borderRadius: "4px", overflow: "auto", maxHeight: "150px", fontSize: "11px" }, children: errorInfo.componentStack }) ] }) ] }) ] }), /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px", flexWrap: "wrap" }, children: [ /* @__PURE__ */ jsx( "button", { onClick: resetError, style: { padding: "10px 16px", backgroundColor: "#ff6b6b", color: "white", border: "none", borderRadius: "6px", cursor: "pointer", fontSize: "14px", fontWeight: "500", transition: "background-color 0.2s" }, onMouseOver: (e) => { e.currentTarget.style.backgroundColor = "#ff5252"; }, onMouseOut: (e) => { e.currentTarget.style.backgroundColor = "#ff6b6b"; }, children: "Try Again" } ), /* @__PURE__ */ jsx( "button", { onClick: retry, style: { padding: "10px 16px", backgroundColor: "transparent", color: "#ff6b6b", border: "1px solid #ff6b6b", borderRadius: "6px", cursor: "pointer", fontSize: "14px", fontWeight: "500", transition: "all 0.2s" }, onMouseOver: (e) => { e.currentTarget.style.backgroundColor = "#ff6b6b"; e.currentTarget.style.color = "white"; }, onMouseOut: (e) => { e.currentTarget.style.backgroundColor = "transparent"; e.currentTarget.style.color = "#ff6b6b"; }, children: "Retry with Delay" } ), /* @__PURE__ */ jsx( "button", { onClick: () => window.location.reload(), style: { padding: "10px 16px", backgroundColor: "transparent", color: "#868e96", border: "1px solid #dee2e6", borderRadius: "6px", cursor: "pointer", fontSize: "14px", fontWeight: "500", transition: "all 0.2s" }, onMouseOver: (e) => { e.currentTarget.style.backgroundColor = "#f8f9fa"; }, onMouseOut: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, children: "Refresh Page" } ) ] }) ] }); }; // src/react/with-error-boundary.tsx import { jsx as jsx2 } from "react/jsx-runtime"; function withErrorBoundary(Component2, errorBoundaryProps) { const WrappedComponent = (props) => { return /* @__PURE__ */ jsx2(ErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ jsx2(Component2, { ...props }) }); }; WrappedComponent.displayName = `withErrorBoundary(${Component2.displayName || Component2.name})`; return WrappedComponent; } // src/react/hoc.tsx import React2, { forwardRef, useCallback as useCallback3, useRef, useEffect as useEffect2 } from "react"; import { jsx as jsx3 } from "react/jsx-runtime"; function withErrorBoundary2(WrappedComponent, options = {}) { const WithErrorBoundaryComponent = forwardRef((props, ref) => { const { fallback, onError, level, context, tags, isolate, resetOnPropsChange, resetKey } = options; const componentName = WrappedComponent.displayName || WrappedComponent.name || "Component"; return /* @__PURE__ */ jsx3( ErrorBoundary, { fallback, onError, level, context: { ...context, wrappedComponent: componentName }, tags, isolate, resetOnPropsChange, resetKey, children: /* @__PURE__ */ jsx3(WrappedComponent, { ...props, ref }) } ); }); WithErrorBoundaryComponent.displayName = `withErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name})`; return WithErrorBoundaryComponent; } function withErrorHandler(WrappedComponent, options = {}) { const WithErrorHandlerComponent = forwardRef((props, ref) => { const { trackLifecycle: _trackLifecycle = true, trackPerformance = false, context = {}, tags = {}, trackNavigation = false, trackUserActions = false } = options; const componentName = WrappedComponent.displayName || WrappedComponent.name || "Component"; const { logError: _logError, logNavigation, logUserAction, setTags } = useExtendedErrorHandler(); const { logComponentError } = useComponentError(componentName); const { measurePerformance } = usePerformanceMonitor(); useEffect2(() => { if (Object.keys(tags).length > 0) { setTags({ ...tags, component: componentName }); } }, [componentName, tags, setTags]); const handleError = useCallback3((error, phase = "unknown") => { return logComponentError(error, phase, { ...context, props: Object.keys(props), timestamp: (/* @__PURE__ */ new Date()).toISOString() }); }, [logComponentError, context, props]); const handleNavigation = useCallback3((from, to, metadata) => { if (trackNavigation) { logNavigation(from, to, { ...metadata, component: componentName }); } }, [logNavigation, trackNavigation, componentName]); const handleUserAction = useCallback3((action, metadata) => { if (trackUserActions) { logUserAction(action, { ...metadata, component: componentName }); } }, [logUserAction, trackUserActions, componentName]); const measureComponentPerformance = useCallback3((name, fn) => { if (trackPerformance) { return measurePerformance(`${componentName}:${name}`,