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
JavaScript
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}`,