@g-studio/report-widget
v0.7.0
Published
Embeddable bug-report widget for the cac tracker — capture + telemetry breadcrumbs, POSTs to the public ingest endpoint.
Downloads
110
Readme
@g-studio/report-widget
Embeddable bug-report widget for the cac tracker. Installs passive telemetry
breadcrumbs (JS errors, console.error/warn, failed requests, navigation) on
init and attaches them — plus page context and screenshots — to reports it POSTs
to the public ingest endpoint. Payment/auth hosts are hard-denylisted; the
fetch/XHR patch is pure passthrough in try/catch, so the widget can never
cause a failed payment or login.
Install
bun add @g-studio/report-widget # or npm/pnpm/yarnOption 1 — React / Next.js
"use client";
import { ReportWidget } from "@g-studio/report-widget/react";
export function BugReporter() {
return (
<ReportWidget
projectKey={process.env.NEXT_PUBLIC_REPORT_KEY!}
endpoint="https://cac.guz-studio.dev"
locale="es"
reporter={() => ({ id: session.user.id, name: session.user.name })}
snapshot={{ localStorage: ["feature-flags", "app-state"] }}
context={() => ({ appVersion: process.env.NEXT_PUBLIC_VERSION })}
theme={{ color: "#4f46e5", position: "bottom-right" }}
/>
);
}Render it once in a client component (e.g. the dashboard layout). The launcher opens a modal with two tabs: Report (new report) and My reports — where the reporter sees the reports they've filed, follows their status, reads the team's replies and responds. No email or login: each report gets a signed token stored in the browser (the only thing persisted to localStorage).
Option 2 — Headless (any framework)
Drive your own UI; the widget just collects + submits.
import { createReporter } from "@g-studio/report-widget";
const reporter = createReporter({
projectKey: "pk_…",
endpoint: "https://cac.guz-studio.dev",
reporter: () => ({ id: currentUser.id, name: currentUser.name }),
});
// submit a report (context + telemetry + identity attached automatically)
await reporter.submit({ title: "Checkout broke", description: "…", images: [file] });
// follow-up (no email/login — per-report tokens are stored in the browser)
reporter.myReports(); // reports filed from this browser: {id,folio,title,…}
await reporter.viewReport(id); // status + thread (team replies included)
await reporter.reply(id, "It still fails on mobile", [file]);
reporter.telemetry(); // current breadcrumbs (for a preview)
reporter.destroy(); // stop collecting, restore patched globalsOption 3 — Script tag (no build step)
<script
src="https://cac.guz-studio.dev/widget.js"
data-project-key="pk_…"
data-endpoint="https://cac.guz-studio.dev"
data-color="#2563eb"
data-position="bottom-right"
></script>Single self-contained file (~10 KB), no peer deps. Auto-mounts a launcher. Only
requirement: add the ingest origin to your site's connect-src CSP.
Config (WidgetConfig)
| Field | Type | Notes |
|---|---|---|
| projectKey | string | required — public write-only ingest key (pk_…) |
| endpoint | string | ingest base URL (default https://cac.guz-studio.dev) |
| locale | 'es' \| 'en' | UI language (default es) |
| reporter | () => { id?; name?; email? } | reporter identity from the host app's session — shows who reported it in the console (no email asked in the form) |
| context | () => object | curated data attached to every report |
| snapshot | { localStorage?: string[]; cookies?: boolean } | opt-in allowlist (never a wholesale dump) |
| captureBodies | string[] | path globs whose failed request bodies are captured (payment/auth hosts always excluded) |
| scrubFields | string[] | extra body field names to redact (merged with defaults) |
| theme | { color?; position? } | launcher styling |
Telemetry is scrubbed in the browser before sending; the server re-redacts and encrypts it at rest (AES-GCM) with a retention TTL.
