@sparse-bug/react
Version:
Official React SDK for Sparse Bug, a lightweight, self-hosted error tracker.
140 lines (132 loc) • 4.01 kB
JavaScript
// src/hooks.ts
import { useContext } from "react";
// src/context.ts
import { createContext } from "react";
var SparseBugContext = createContext({
client: null
});
// src/hooks.ts
var useSparseBug = () => {
const context = useContext(SparseBugContext);
if (!context?.client) {
throw new Error("useSparseBug must be used within a SparseBugProvider");
}
return context.client;
};
useSparseBug.Context = SparseBugContext;
// src/provider.tsx
import { useEffect, useMemo } from "react";
// src/client.ts
var SparseBugClient = class {
config;
userContext = null;
constructor(config) {
if (!config.apiKey || !config.endpoint) {
console.error(
"[SparseBug] apiKey and endpoint are required for initialization."
);
}
this.config = { enabled: true, ...config };
}
/**
* Sets user information to be sent with subsequent error reports.
* @param user - An object containing user details, or null to clear.
*/
setUser(user) {
this.userContext = user;
}
/**
* Reports an error to the Sparse Bug API.
* @param error - The Error object to report.
* @param componentStack - Optional React component stack trace.
*/
report(error, componentStack) {
if (!this.config.enabled) {
console.log("[SparseBug] Reporting disabled. Error not sent.", { error });
return;
}
const payload = {
errorMessage: error.message,
stackTrace: componentStack ? `${error.stack}
Component Stack:
${componentStack}` : error.stack || "No stack trace available",
context: {
url: window.location.href,
userAgent: navigator.userAgent,
user: this.userContext
}
};
try {
const data = JSON.stringify(payload);
const reportUrl = `${this.config.endpoint}/report`;
fetch(reportUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": this.config.apiKey
},
body: data,
keepalive: true
// Crucial for requests during page unload
}).catch(() => {
});
} catch (e) {
console.error("[SparseBug] Failed to report error.", e);
}
}
};
// src/ErrorBoundary.tsx
import React from "react";
var ErrorBoundary = class extends React.Component {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static contextType = useSparseBug.Context;
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
const client = this.context?.client;
client?.report(error, errorInfo.componentStack);
}
render() {
if (this.state.hasError && this.props.fallback !== void 0 && this.state.error) {
if (typeof this.props.fallback === "function") {
return this.props.fallback(this.state.error);
}
return this.props.fallback;
}
return this.props.children;
}
};
// src/provider.tsx
import { jsx } from "react/jsx-runtime";
function SparseBugProvider({
children,
config,
fallback
}) {
const client = useMemo(() => new SparseBugClient(config), [config]);
useEffect(() => {
const handleError = (event) => {
client.report(event.error);
};
const handleRejection = (event) => {
const reason = event.reason instanceof Error ? event.reason : new Error(String(event.reason || "Unhandled promise rejection"));
client.report(reason);
};
window.addEventListener("error", handleError);
window.addEventListener("unhandledrejection", handleRejection);
return () => {
window.removeEventListener("error", handleError);
window.removeEventListener("unhandledrejection", handleRejection);
};
}, [client]);
return /* @__PURE__ */ jsx(SparseBugContext.Provider, { value: { client }, children: /* @__PURE__ */ jsx(ErrorBoundary, { fallback, children }) });
}
export {
SparseBugProvider,
useSparseBug
};