featurely-error-tracker
v1.1.0
Published
Advanced error tracking SDK for Featurely with breadcrumbs, device info, and complete context
Maintainers
Readme
featurely-error-tracker
⚠️ CRITICAL UPDATE (v1.0.23): Fixes infinite error reporting loop. If you're experiencing error spam or rapid-fire API requests, update immediately. See FIXES.md for details.
Advanced error tracking SDK for JavaScript and TypeScript. Automatically captures unhandled errors and promise rejections with full context: breadcrumbs, device info, network conditions, and performance metrics. Errors appear in the Featurely dashboard with stack traces and reproduction context.
Installation
npm install featurely-error-trackerRequired API key permission: errors:write
Quick Start
import { ErrorTracker } from "featurely-error-tracker";
const tracker = new ErrorTracker({
apiKey: "ft_live_your_api_key_here",
environment: "production",
appVersion: "1.0.0",
});
tracker.install(); // registers window.onerror + unhandledrejection
tracker.setUser(user.id, user.email); // call after loginConfiguration
ErrorTrackerConfig
| Option | Type | Default | Description |
| ---------------- | ----------------------------------------------- | ------------------------------------------------- | ----------------------------------------- |
| apiKey | string | required | API key with errors:write permission |
| environment | "development" \| "staging" \| "production" | — | Environment tag attached to every error |
| appVersion | string | — | Application version string |
| releaseId | string | — | Release identifier (e.g. git commit hash) |
| apiUrl | string | "https://www.featurely.no/api/public/v1/errors" | Custom API endpoint |
| maxBreadcrumbs | number | 50 | Maximum breadcrumbs retained per session |
| enabled | boolean | true | Master on/off switch |
| validateApiKey | boolean | true | Validate API key format on init |
| sampleRate | number | 1.0 | Fraction of errors to send (0.0–1.0) |
| beforeSend | (event: ErrorPayload) => ErrorPayload \| null | — | Mutate or drop events before sending |
| onError | (error: Error) => void | — | Called when the SDK itself fails |
| toast | ToastConfig | see below | In-page error toast notifications |
| performance | PerformanceConfig | see below | Web Vitals and page load tracking |
| offline | OfflineConfig | see below | Queue errors when the user is offline |
| privacy | PrivacyConfig | see below | PII scrubbing |
ToastConfig
| Option | Type | Default | Description |
| ------------------- | --------------- | ------------- | -------------------------------------------- |
| enabled | boolean | false | Show toast notifications for errors |
| position | ToastPosition | "top-right" | Toast anchor position |
| duration | number | 5000 | Auto-dismiss delay in ms (0 = never) |
| showOnAutoCapture | boolean | true | Show toast for automatically captured errors |
ToastPosition: "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"
PerformanceConfig
| Option | Type | Default | Description |
| ---------------- | --------- | ------- | ----------------------------------------- |
| enabled | boolean | false | Enable performance monitoring |
| trackWebVitals | boolean | true | Track LCP and FCP via PerformanceObserver |
| sendWithErrors | boolean | true | Attach performance data to error reports |
OfflineConfig
| Option | Type | Default | Description |
| -------------- | ---------------------------- | ---------------- | ---------------------------------------------- |
| enabled | boolean | false | Queue errors when offline |
| maxQueueSize | number | 50 | Maximum queued errors before oldest is dropped |
| storage | "localStorage" \| "memory" | "localStorage" | Queue persistence strategy |
PrivacyConfig
| Option | Type | Default | Description |
| ------------------ | ---------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| scrubPII | boolean | false | Auto-redact emails, credit cards, SSNs, phone numbers, and IP addresses |
| scrubQueryParams | string[] | ["token","apiKey","key","password","secret","api_key","access_token"] | URL query params to redact |
| customPatterns | RegExp[] | [] | Additional patterns to redact |
Methods
tracker.install(): void
Registers window.onerror, unhandledrejection, navigation tracking, click tracking, console error interception, and fetch tracking. Call once at app startup.
tracker.uninstall(): void
Removes all registered event listeners and resets installed state.
tracker.reportError(error, severity?, context?): Promise<void>
Manually report an error.
| Parameter | Type | Default |
| ---------- | ------------------------------------------- | ---------- |
| error | Error | required |
| severity | "low" \| "medium" \| "high" \| "critical" | "medium" |
| context | Record<string, unknown> | — |
tracker.setUser(userId?, email?): void
Associate a user with all subsequent error reports. Call after login.
tracker.clearUser(): void
Clear user context. Call after logout.
tracker.addBreadcrumb(data): void
Manually add a breadcrumb. Type must be one of: "navigation" | "click" | "input" | "http" | "console" | "custom".
tracker.trackPerformance(name, value?): void
Record a custom performance mark. Requires performance.enabled: true.
tracker.showToast(options): void
Show an in-page toast notification. Requires toast.enabled: true.
tracker.showToast({
message: "Saved successfully",
style: "success", // "error" | "warning" | "success" | "info"
position: "top-right",
duration: 3000,
closable: true,
});tracker.getSessionId(): string
Returns the current session ID generated at construction.
tracker.isTrackerInstalled(): boolean
Returns true if install() has been called and not yet undone by uninstall().
Examples
Basic setup
import { ErrorTracker } from "featurely-error-tracker";
const tracker = new ErrorTracker({
apiKey: process.env.FEATURELY_API_KEY!,
environment: process.env.NODE_ENV as "development" | "production",
appVersion: "2.1.0",
});
tracker.install();Manual error reporting
try {
await fetchUserData(userId);
} catch (error) {
await tracker.reportError(error as Error, "high", {
userId,
action: "fetch_user_data",
});
}Toast notifications
const tracker = new ErrorTracker({
apiKey: process.env.FEATURELY_API_KEY!,
environment: "production",
appVersion: "1.0.0",
toast: {
enabled: true,
position: "top-right",
duration: 5000,
showOnAutoCapture: true,
},
});
tracker.install();PII scrubbing + beforeSend
const tracker = new ErrorTracker({
apiKey: process.env.FEATURELY_API_KEY!,
environment: "production",
appVersion: "1.0.0",
privacy: {
scrubPII: true,
scrubQueryParams: ["token", "apiKey", "password"],
customPatterns: [/api[_-]?key/gi],
},
beforeSend: (event) => {
if (event.url?.includes("/internal")) return null; // drop
return event;
},
sampleRate: 0.5, // send only 50% of errors
});
tracker.install();React / Next.js App Router
"use client";
import { useEffect } from "react";
import { ErrorTracker } from "featurely-error-tracker";
let tracker: ErrorTracker | null = null;
export function ErrorTrackerProvider({
children,
user,
}: {
children: React.ReactNode;
user?: { id: string; email: string };
}) {
useEffect(() => {
if (!tracker) {
tracker = new ErrorTracker({
apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
environment: process.env.NODE_ENV as "development" | "production",
appVersion: process.env.NEXT_PUBLIC_APP_VERSION ?? "1.0.0",
});
tracker.install();
}
if (user) tracker.setUser(user.id, user.email);
return () => {
tracker?.uninstall();
tracker = null;
};
}, [user]);
return <>{children}</>;
}Environment Support
| Environment | Supported | Notes |
| ----------------------- | --------- | --------------------------------------------------------------------------- |
| Browser | ✅ | Full support — window.onerror, unhandledrejection, Web Vitals, toast UI |
| Node.js | ✅ | Manual reportError() and breadcrumbs; no DOM features or Web Vitals |
| React Server Components | ✅ | Use reportError() server-side; skip install() |
| Edge / Workers | ⚠️ | Manual reporting only; no global handlers |
Ships as CJS + ESM with TypeScript declarations.
Framework Integrations
React / Next.js App Router
Use a singleton pattern to avoid re-installing global handlers on re-renders:
"use client";
import { useEffect } from "react";
import { ErrorTracker } from "featurely-error-tracker";
let tracker: ErrorTracker | null = null;
export function ErrorTrackerProvider({
children,
user,
}: {
children: React.ReactNode;
user?: { id: string; email: string };
}) {
useEffect(() => {
if (!tracker) {
tracker = new ErrorTracker({
apiKey: process.env.NEXT_PUBLIC_FEATURELY_API_KEY!,
environment: process.env.NODE_ENV as "development" | "production",
appVersion: process.env.NEXT_PUBLIC_APP_VERSION ?? "1.0.0",
});
tracker.install();
}
if (user) tracker.setUser(user.id, user.email);
return () => {
tracker?.uninstall();
tracker = null;
};
}, [user]);
return <>{children}</>;
}Security & Privacy
privacy.scrubPII: true — Auto-redacts emails, credit card numbers, SSNs, phone numbers, and IP addresses from all payloads before sending.
privacy.scrubQueryParams — Redacts sensitive URL query parameters. Default list: token, apiKey, key, password, secret, api_key, access_token.
privacy.customPatterns: RegExp[] — Additional patterns for proprietary identifiers.
beforeSend hook — Inspect, mutate, or drop any event before it is sent. Return null to discard.
beforeSend: (event) => {
if (event.url?.includes("/internal")) return null;
return event;
};sampleRate: 0.0–1.0 — Send only a fraction of errors in high-traffic environments.
Debugging / Local Development
const tracker = new ErrorTracker({
apiKey: process.env.FEATURELY_API_KEY!,
environment: "development",
appVersion: "1.0.0",
enabled: process.env.NODE_ENV === "production", // disable in tests/CI
toast: {
enabled: true, // show a toast for every captured error
position: "bottom-right",
},
sampleRate: 1.0, // capture all errors locally
});
tracker.install();TypeScript
All public types are exported from the package entry point:
import type {
ErrorTrackerConfig, // Full constructor config
ToastConfig, // toast sub-config
PerformanceConfig, // performance sub-config
OfflineConfig, // offline queue sub-config
PrivacyConfig, // PII scrubbing sub-config
ToastOptions, // options for showToast()
ErrorPayload, // event object passed to beforeSend
ToastPosition, // "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right"
ToastStyle, // "error" | "warning" | "success" | "info"
} from "featurely-error-tracker";Named error classes: InvalidApiKeyError, ConfigurationError.
Troubleshooting
Safari IndexedDB Crashes
Safari on iOS can terminate IndexedDB connections when:
- The browser is backgrounded
- Memory pressure occurs
- Private browsing mode is active
SDK Behavior (v1.0.23+): The SDK now handles these gracefully with:
- Circuit breaker to prevent infinite loops
- Exponential backoff (1s → 2s → 4s ... up to 60s) on API failures
- Silent failure after logging to console
Your App Should:
// Listen for IndexedDB connection drops
db.addEventListener("close", () => {
console.warn("IndexedDB connection closed");
// Fall back to localStorage or re-establish connection
});
db.addEventListener("error", (event) => {
console.error("IndexedDB error:", event);
event.preventDefault(); // Don't let it crash your app
});Error Spam / Infinite Loops
Symptoms: Hundreds of error reports in seconds, console spam with "[ERROR TRACKER] SDK Error"
Fix: Update to v1.0.23 or later:
npm install featurely-error-tracker@latestSee FIXES.md for details on the circuit breaker and exponential backoff fixes.
API Rate Limiting (429)
The SDK automatically backs off when receiving 429 responses:
- 1st failure: waits 1 second
- 2nd failure: waits 2 seconds
- 3rd failure: waits 4 seconds
- Capped at 60 seconds maximum
Check console for: [Featurely] Failed to report error: 429 → Backoff: waiting XXXXms
Network/CSP Errors
If you see "NetworkError" or "Failed to fetch" in console:
Check CSP: Add Featurely to your Content-Security-Policy:
<meta http-equiv="Content-Security-Policy" content="connect-src 'self' https://www.featurely.no" />Check CORS: Ensure your domain is allowed in your Featurely project settings
Check the browser's network tab for blocked requests
