glitchgrab 1.29.1 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -2
- package/dist/index.d.mts +266 -3
- package/dist/index.d.ts +266 -3
- package/dist/index.js +1675 -302
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1569 -204
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -146,6 +146,84 @@ function FeedbackWidget() {
|
|
|
146
146
|
|
|
147
147
|
Note: `openReportDialog()` requires a `<ReportButton>` to be mounted somewhere in the component tree. It triggers the same modal with screenshot capture.
|
|
148
148
|
|
|
149
|
+
## How do I collect feedback about my app?
|
|
150
|
+
|
|
151
|
+
Reports are for bugs. **Feedback** is for how your users feel about your app — a 1–5 star rating with an optional message. Glitchgrab stores it, so you don't write a table, a route, or a migration. Feedback never becomes a GitHub issue.
|
|
152
|
+
|
|
153
|
+
### Drop-in button
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { FeedbackButton } from "glitchgrab";
|
|
157
|
+
|
|
158
|
+
<FeedbackButton /> // floating, bottom-left
|
|
159
|
+
<FeedbackButton position="bottom-right" label="Rate us" />
|
|
160
|
+
|
|
161
|
+
// Your own trigger
|
|
162
|
+
<FeedbackButton>
|
|
163
|
+
{({ onClick }) => <button onClick={onClick}>How are we doing?</button>}
|
|
164
|
+
</FeedbackButton>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The dialog (stars + message) ships inside `GlitchgrabProvider` — the button is only the trigger. Open it from anywhere with `openFeedbackDialog()`.
|
|
168
|
+
|
|
169
|
+
### Your own UI
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
function RatingRow() {
|
|
173
|
+
const { sendFeedback } = useGlitchgrab();
|
|
174
|
+
|
|
175
|
+
return [1, 2, 3, 4, 5].map((stars) => (
|
|
176
|
+
<button key={stars} onClick={() => sendFeedback(stars, "Loved the new export flow")}>
|
|
177
|
+
{stars}★
|
|
178
|
+
</button>
|
|
179
|
+
));
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`sendFeedback(rating, message?, metadata?)` never throws — it returns `null` on failure. The reporter is taken from the `session` prop on `GlitchgrabProvider`, so pass a session if you want to know who rated you.
|
|
184
|
+
|
|
185
|
+
### Reading it back
|
|
186
|
+
|
|
187
|
+
Every entry shows up on your Glitchgrab **Feedback** page, where you press **publish** on the ones you want to reuse. Published entries are the only ones returned with `approvedOnly` — so a testimonials wall can never leak an unvetted complaint:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { useGlitchgrabFeedback } from "glitchgrab";
|
|
191
|
+
|
|
192
|
+
function Testimonials() {
|
|
193
|
+
const { feedback, isLoading } = useGlitchgrabFeedback({
|
|
194
|
+
token: process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!,
|
|
195
|
+
approvedOnly: true,
|
|
196
|
+
minRating: 4,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (isLoading) return null;
|
|
200
|
+
|
|
201
|
+
return feedback.map((f) => (
|
|
202
|
+
<blockquote key={f.id}>
|
|
203
|
+
{f.message} — {f.reporterName} ({f.rating}★)
|
|
204
|
+
</blockquote>
|
|
205
|
+
));
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Pass `userId` instead to show one user their own past ratings. `fetchGlitchgrabFeedback(...)` is the standalone fetcher for TanStack Query. Neither response includes email or phone, so both are safe to render on a public page.
|
|
210
|
+
|
|
211
|
+
### REST
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Submit
|
|
215
|
+
curl -X POST https://glitchgrab.dev/api/v1/sdk/feedback \
|
|
216
|
+
-H "Authorization: Bearer gg_xxxxx" \
|
|
217
|
+
-H "Content-Type: application/json" \
|
|
218
|
+
-d '{"rating":5,"message":"Fast and simple","metadata":{"sessionUserId":"user_123","sessionUserName":"Asha"}}'
|
|
219
|
+
|
|
220
|
+
# Read published entries
|
|
221
|
+
curl "https://glitchgrab.dev/api/v1/sdk/feedback?approved=true&minRating=4&limit=20" \
|
|
222
|
+
-H "Authorization: Bearer gg_xxxxx"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The repo is always derived from the token — there is no `repoId` to pass. Rate limit: 30 submissions per token per hour.
|
|
226
|
+
|
|
149
227
|
## What keyboard shortcuts are available?
|
|
150
228
|
|
|
151
229
|
Once `GlitchgrabProvider` is mounted, these shortcuts work globally:
|
|
@@ -455,6 +533,114 @@ import { GlitchgrabErrorBoundary } from "glitchgrab";
|
|
|
455
533
|
</GlitchgrabErrorBoundary>
|
|
456
534
|
```
|
|
457
535
|
|
|
536
|
+
⚠️ **This does not cover the Next.js App Router.** If your app has an `app/error.tsx`, Next's own boundary sits closer to the crashing component and catches first — `GlitchgrabErrorBoundary` never runs, and the crash is never reported. See [How do I capture App Router crashes?](#how-do-i-capture-app-router-crashes) below.
|
|
537
|
+
|
|
538
|
+
## How do I capture App Router crashes?
|
|
539
|
+
|
|
540
|
+
**Read this if you use `app/error.tsx` or `app/global-error.tsx` — otherwise your render crashes are silently lost.**
|
|
541
|
+
|
|
542
|
+
A React error that a boundary *handles* never reaches `window.onerror`, so provider auto-capture cannot see it. In an App Router app, Next's `error.tsx` is that boundary. The user sees the fallback screen, and the message and stack are gone.
|
|
543
|
+
|
|
544
|
+
Report it yourself with `captureError`:
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
// app/error.tsx
|
|
548
|
+
"use client";
|
|
549
|
+
|
|
550
|
+
import { useEffect } from "react";
|
|
551
|
+
import { useGlitchgrab } from "glitchgrab";
|
|
552
|
+
|
|
553
|
+
export default function Error({
|
|
554
|
+
error,
|
|
555
|
+
reset,
|
|
556
|
+
}: {
|
|
557
|
+
error: Error & { digest?: string };
|
|
558
|
+
reset: () => void;
|
|
559
|
+
}) {
|
|
560
|
+
const { captureError } = useGlitchgrab();
|
|
561
|
+
|
|
562
|
+
useEffect(() => {
|
|
563
|
+
captureError(error, { digest: error.digest, boundary: "next-app-router" });
|
|
564
|
+
}, [error, captureError]);
|
|
565
|
+
|
|
566
|
+
return (
|
|
567
|
+
<div>
|
|
568
|
+
<p>Something went wrong</p>
|
|
569
|
+
<button onClick={reset}>Try again</button>
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
`global-error.tsx` replaces the root layout, so it renders **outside** the provider tree and `useGlitchgrab()` would throw. Use the standalone export there — it reads the token from the last mounted provider:
|
|
576
|
+
|
|
577
|
+
```tsx
|
|
578
|
+
// app/global-error.tsx
|
|
579
|
+
"use client";
|
|
580
|
+
|
|
581
|
+
import { useEffect } from "react";
|
|
582
|
+
import { captureError } from "glitchgrab";
|
|
583
|
+
|
|
584
|
+
export default function GlobalError({
|
|
585
|
+
error,
|
|
586
|
+
}: {
|
|
587
|
+
error: Error & { digest?: string };
|
|
588
|
+
}) {
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
captureError(error, { digest: error.digest, boundary: "next-global-error" });
|
|
591
|
+
}, [error]);
|
|
592
|
+
|
|
593
|
+
return (
|
|
594
|
+
<html>
|
|
595
|
+
<body>
|
|
596
|
+
<p>Something went wrong</p>
|
|
597
|
+
</body>
|
|
598
|
+
</html>
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The same applies to React Router `errorElement`, Remix `ErrorBoundary`, and any hand-rolled `componentDidCatch` — call `captureError` from each.
|
|
604
|
+
|
|
605
|
+
### Catching every boundary error in one place (React 19)
|
|
606
|
+
|
|
607
|
+
React 19 lets you intercept *all* boundary-caught errors at the root, so you don't have to wire each boundary by hand:
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
// app/instrumentation-client.ts (or your custom hydrateRoot call)
|
|
611
|
+
import { captureError } from "glitchgrab";
|
|
612
|
+
|
|
613
|
+
hydrateRoot(document, <App />, {
|
|
614
|
+
onCaughtError: (error, errorInfo) => {
|
|
615
|
+
captureError(error, {
|
|
616
|
+
componentStack: errorInfo.componentStack ?? undefined,
|
|
617
|
+
boundary: "react-onCaughtError",
|
|
618
|
+
});
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Next.js does not expose `hydrateRoot` options, so App Router apps should use the `error.tsx` snippets above.
|
|
624
|
+
|
|
625
|
+
### captureError options
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
captureError(error: unknown, options?: {
|
|
629
|
+
componentStack?: string; // from componentDidCatch / onCaughtError
|
|
630
|
+
digest?: string; // Next.js error digest — also feeds dedup
|
|
631
|
+
boundary?: string; // which boundary caught it, stored as metadata
|
|
632
|
+
metadata?: Record<string, string>;
|
|
633
|
+
})
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
- Sends `source: "SDK_AUTO"`, `type: "BUG"` with the message, stack, component stack, breadcrumbs, device info, page URL and session identity — same shape as auto-capture.
|
|
637
|
+
- **Deduped.** An identical error repeating within 5 minutes files one issue, not N — a crash loop won't spam your repo.
|
|
638
|
+
- Honours the provider's `ignoreErrors`.
|
|
639
|
+
- Fire-and-forget. Never throws, never blocks your fallback UI from rendering.
|
|
640
|
+
- Runs in development too (unlike passive auto-capture), so you can verify the wiring the moment you add it.
|
|
641
|
+
- Pass `digest` whenever you have it. In production Next replaces server-boundary error messages with one generic string — without the digest, every distinct server crash on a page collapses into a single deduped issue.
|
|
642
|
+
- No-ops if no `GlitchgrabProvider` has rendered yet.
|
|
643
|
+
|
|
458
644
|
## What configuration options are available?
|
|
459
645
|
|
|
460
646
|
| Prop | Type | Default | Description |
|
|
@@ -468,6 +654,9 @@ import { GlitchgrabErrorBoundary } from "glitchgrab";
|
|
|
468
654
|
| `onReportSent` | `(result: ReportResult) => void` | - | Called after a report is sent |
|
|
469
655
|
| `fallback` | `ReactNode` | - | Error boundary fallback UI |
|
|
470
656
|
| `ignoreErrors` | `(string \| RegExp)[]` | - | Skip auto-capture for errors whose message matches (substring for `string`, `.test()` for `RegExp`) |
|
|
657
|
+
| `release` | `string` | env fallback | Build identifier on every report — version, tag, or commit SHA |
|
|
658
|
+
| `context` | `Record<string, unknown>` | - | App-owned key-values on every report (orgId, plan, flags) |
|
|
659
|
+
| `responseBodyOrigins` | `string[]` | - | Extra origins whose failed-request bodies may be recorded. Same-origin is always recorded; third parties never are unless listed |
|
|
471
660
|
|
|
472
661
|
### Ignoring known-noisy errors
|
|
473
662
|
|
|
@@ -482,6 +671,48 @@ Some errors that reach `window.onerror` aren't app bugs — browser extension br
|
|
|
482
671
|
</GlitchgrabProvider>
|
|
483
672
|
```
|
|
484
673
|
|
|
674
|
+
## How do I attach my own context to reports?
|
|
675
|
+
|
|
676
|
+
The SDK captures what a browser can see. It cannot know that this user is on the enterprise plan, in org 42, with the new billing flow switched on — and that is usually the difference between "a crash" and "a crash for one tenant on one flag".
|
|
677
|
+
|
|
678
|
+
Attach your own keys once; every report from then on carries them:
|
|
679
|
+
|
|
680
|
+
```tsx
|
|
681
|
+
const { setContext, setContexts } = useGlitchgrab();
|
|
682
|
+
|
|
683
|
+
setContext("orgId", org.id);
|
|
684
|
+
setContexts({ plan: org.plan, role: user.role, newBilling: flags.newBilling });
|
|
685
|
+
|
|
686
|
+
// Pass null to remove a key — a user who leaves an org stops reporting it
|
|
687
|
+
setContext("orgId", null);
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Or set them declaratively on the provider:
|
|
691
|
+
|
|
692
|
+
```tsx
|
|
693
|
+
<GlitchgrabProvider
|
|
694
|
+
token={process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!}
|
|
695
|
+
context={{ plan: org.plan, region: "in-south" }}
|
|
696
|
+
>
|
|
697
|
+
{children}
|
|
698
|
+
</GlitchgrabProvider>
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
They arrive on the report prefixed with `ctx_` (`ctx_orgId`, `ctx_plan`), so an app key named `timestamp` or `status` can never overwrite a field the dashboard relies on. `setContext` / `setContexts` are also exported standalone for non-React code, and the values survive navigation and provider remounts. Limits: 30 keys, 200 chars per value.
|
|
702
|
+
|
|
703
|
+
## How do I tell which deploy broke?
|
|
704
|
+
|
|
705
|
+
Pass a `release` and every report names the build it came from:
|
|
706
|
+
|
|
707
|
+
```tsx
|
|
708
|
+
<GlitchgrabProvider
|
|
709
|
+
token={process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!}
|
|
710
|
+
release={process.env.NEXT_PUBLIC_APP_VERSION}
|
|
711
|
+
>
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
If you don't pass one, the SDK falls back to `NEXT_PUBLIC_APP_VERSION`, then `NEXT_PUBLIC_RELEASE`, then `NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA` — so on Vercel this works with no configuration at all.
|
|
715
|
+
|
|
485
716
|
## Do I need a Content-Security-Policy allowance?
|
|
486
717
|
|
|
487
718
|
If your app sets a CSP header (e.g. via `proxy.ts` / `middleware.ts` in Next.js), allow Glitchgrab's API host so `fetch` calls from the SDK aren't blocked:
|
|
@@ -497,6 +728,7 @@ response.headers.set(
|
|
|
497
728
|
- `connect-src https://glitchgrab.dev` — required for `sendReport`, `enhanceText`, `transcribeAudio`, and the report-fetching hooks/REST calls. All SDK requests go to this single host by default.
|
|
498
729
|
- If you pass a custom `baseUrl` prop, allow that host instead (self-hosted or proxied deployments).
|
|
499
730
|
- Screenshot capture (`html2canvas-pro`) runs entirely client-side against `document.body` — no network request, no extra `img-src`/`connect-src` needed for it.
|
|
731
|
+
- `img-src` only matters if your `session` carries an avatar URL — the report dialog renders it. If your CSP blocks that host the dialog falls back to the reporter's initials, so it degrades rather than breaking.
|
|
500
732
|
- No `script-src`, `style-src`, or `frame-src` allowances are required — the SDK doesn't load remote scripts, styles, or iframes.
|
|
501
733
|
|
|
502
734
|
## How does auto-capture work?
|
|
@@ -506,10 +738,27 @@ In production (`NODE_ENV=production`), the SDK automatically captures:
|
|
|
506
738
|
- Unhandled promise rejections
|
|
507
739
|
- Console errors (as breadcrumbs)
|
|
508
740
|
- Navigation events (as breadcrumbs)
|
|
509
|
-
- API calls (as breadcrumbs)
|
|
741
|
+
- API calls, both `fetch` and `XMLHttpRequest` (as breadcrumbs)
|
|
742
|
+
|
|
743
|
+
Both HTTP paths are patched, so **axios works** — axios uses `XMLHttpRequest` in the browser, not `fetch`.
|
|
744
|
+
|
|
745
|
+
For a failed request the breadcrumb also carries the response body, so a `→ 500` says *why*. Guardrails:
|
|
746
|
+
|
|
747
|
+
- **Same-origin only.** Your own API's error shape is yours; a third-party 422 from Stripe or Auth0 echoes fields you don't control (dates of birth, phone numbers) that would end up in a public GitHub issue. Third-party calls are still recorded — status, method, duration — just never their body.
|
|
748
|
+
- Add trusted hosts explicitly when your API is on another origin:
|
|
749
|
+
```tsx
|
|
750
|
+
<GlitchgrabProvider
|
|
751
|
+
token={process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!}
|
|
752
|
+
responseBodyOrigins={["https://api.myapp.com"]}
|
|
753
|
+
>
|
|
754
|
+
```
|
|
755
|
+
- Truncated to 500 chars; sensitively-named JSON keys dropped; emails, JWTs and bearer tokens scrubbed.
|
|
756
|
+
- **Only non-2xx responses are read.** Successful traffic is never buffered.
|
|
510
757
|
|
|
511
758
|
Auto-capture is **disabled in development** to avoid noisy issues.
|
|
512
759
|
|
|
760
|
+
It does **not** cover React errors that a framework error boundary handles — those never reach `window.onerror`. Call [`captureError`](#how-do-i-capture-app-router-crashes) from your boundary to report them.
|
|
761
|
+
|
|
513
762
|
## What data is included in each report?
|
|
514
763
|
|
|
515
764
|
- Description from the user
|
|
@@ -517,8 +766,11 @@ Auto-capture is **disabled in development** to avoid noisy issues.
|
|
|
517
766
|
- Page URL and user agent
|
|
518
767
|
- Device info (screen size, viewport, platform, language, color scheme)
|
|
519
768
|
- Page navigation history
|
|
520
|
-
- Activity log (last 15 breadcrumbs)
|
|
769
|
+
- Activity log (last 15 breadcrumbs), including failed API calls with their response body
|
|
521
770
|
- Session info (userId, name, email, phone)
|
|
771
|
+
- Release / build identifier
|
|
772
|
+
- Your own context keys (`ctx_orgId`, `ctx_plan`, …)
|
|
773
|
+
- Runtime health — time on page, error count this session, tab visibility, JS heap usage, connection type and RTT (heap and connection are Chromium-only)
|
|
522
774
|
|
|
523
775
|
## License
|
|
524
776
|
|
package/dist/index.d.mts
CHANGED
|
@@ -35,6 +35,21 @@ interface ReportPayload {
|
|
|
35
35
|
deviceInfo?: DeviceInfo;
|
|
36
36
|
metadata?: Record<string, string>;
|
|
37
37
|
}
|
|
38
|
+
/** Extra context for an error your app already caught — see `captureError` */
|
|
39
|
+
interface CaptureErrorOptions {
|
|
40
|
+
/** React component stack, e.g. from `componentDidCatch` / `onCaughtError` */
|
|
41
|
+
componentStack?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Next.js error digest. In production Next replaces server-boundary error
|
|
44
|
+
* messages with a generic string — the digest is the only thing that tells
|
|
45
|
+
* two different crashes apart, so it also feeds the dedup signature.
|
|
46
|
+
*/
|
|
47
|
+
digest?: string;
|
|
48
|
+
/** Which boundary caught it, e.g. `"next-app-router"` — attached as metadata */
|
|
49
|
+
boundary?: string;
|
|
50
|
+
/** Extra metadata merged into the report */
|
|
51
|
+
metadata?: Record<string, string>;
|
|
52
|
+
}
|
|
38
53
|
interface ReportResult {
|
|
39
54
|
success: boolean;
|
|
40
55
|
reportId?: string;
|
|
@@ -44,6 +59,34 @@ interface ReportResult {
|
|
|
44
59
|
intent?: string;
|
|
45
60
|
message?: string;
|
|
46
61
|
}
|
|
62
|
+
interface FeedbackPayload {
|
|
63
|
+
token: string;
|
|
64
|
+
/** 1–5 stars */
|
|
65
|
+
rating: number;
|
|
66
|
+
message?: string;
|
|
67
|
+
pageUrl?: string;
|
|
68
|
+
userAgent?: string;
|
|
69
|
+
metadata?: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
interface FeedbackResult {
|
|
72
|
+
success: boolean;
|
|
73
|
+
feedbackId?: string;
|
|
74
|
+
rating?: number;
|
|
75
|
+
createdAt?: string;
|
|
76
|
+
message?: string;
|
|
77
|
+
}
|
|
78
|
+
/** One stored feedback entry, as returned by the read endpoint. */
|
|
79
|
+
interface GlitchgrabFeedback {
|
|
80
|
+
id: string;
|
|
81
|
+
rating: number;
|
|
82
|
+
message: string | null;
|
|
83
|
+
pageUrl: string | null;
|
|
84
|
+
/** Whether the repo owner published this entry for public display */
|
|
85
|
+
approved: boolean;
|
|
86
|
+
reporterPrimaryKey: string;
|
|
87
|
+
reporterName: string;
|
|
88
|
+
createdAt: string;
|
|
89
|
+
}
|
|
47
90
|
type BreadcrumbType = "console" | "navigation" | "api" | "click" | "error" | "custom";
|
|
48
91
|
interface Breadcrumb {
|
|
49
92
|
type: BreadcrumbType;
|
|
@@ -62,6 +105,26 @@ interface DeviceInfo {
|
|
|
62
105
|
colorScheme: string;
|
|
63
106
|
devicePixelRatio: number;
|
|
64
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Machine state at the moment of the crash, as opposed to `DeviceInfo`, which
|
|
110
|
+
* describes the machine itself. Most fields are Chromium-only and simply absent
|
|
111
|
+
* elsewhere — never assume one is present.
|
|
112
|
+
*/
|
|
113
|
+
interface RuntimeInfo {
|
|
114
|
+
/** ms since this page loaded */
|
|
115
|
+
timeOnPageMs: number;
|
|
116
|
+
/** How many errors the SDK has already auto-captured in this page session */
|
|
117
|
+
errorCount: number;
|
|
118
|
+
/** `document.visibilityState` — a crash in a background tab reads differently */
|
|
119
|
+
visibility: string;
|
|
120
|
+
jsHeapUsedMb?: number;
|
|
121
|
+
jsHeapLimitMb?: number;
|
|
122
|
+
/** `navigator.connection.effectiveType` — "4g", "3g", "2g", "slow-2g" */
|
|
123
|
+
connectionType?: string;
|
|
124
|
+
downlinkMbps?: number;
|
|
125
|
+
rttMs?: number;
|
|
126
|
+
saveData?: boolean;
|
|
127
|
+
}
|
|
65
128
|
interface CapturedContext {
|
|
66
129
|
url: string;
|
|
67
130
|
userAgent: string;
|
|
@@ -69,6 +132,12 @@ interface CapturedContext {
|
|
|
69
132
|
visitedPages: string[];
|
|
70
133
|
breadcrumbs: Breadcrumb[];
|
|
71
134
|
deviceInfo: DeviceInfo | null;
|
|
135
|
+
/** Runtime health snapshot — null when unavailable (SSR) */
|
|
136
|
+
runtime: RuntimeInfo | null;
|
|
137
|
+
/** Key-values the host app attached via `setContext` */
|
|
138
|
+
appContext: Record<string, string>;
|
|
139
|
+
/** Build identifier this crash came from, if known */
|
|
140
|
+
release?: string;
|
|
72
141
|
}
|
|
73
142
|
interface GlitchgrabSession {
|
|
74
143
|
/** Primary key of the user in your database (required) */
|
|
@@ -103,17 +172,69 @@ interface GlitchgrabProviderProps {
|
|
|
103
172
|
* signatures that aren't app bugs — e.g. browser extension bridge errors.
|
|
104
173
|
*/
|
|
105
174
|
ignoreErrors?: (string | RegExp)[];
|
|
175
|
+
/**
|
|
176
|
+
* Build identifier attached to every report — a version, a tag, a commit SHA.
|
|
177
|
+
* Tells you which deploy introduced a crash. Falls back to
|
|
178
|
+
* `NEXT_PUBLIC_APP_VERSION`, `NEXT_PUBLIC_RELEASE`, then
|
|
179
|
+
* `NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA` when not passed.
|
|
180
|
+
*/
|
|
181
|
+
release?: string;
|
|
182
|
+
/**
|
|
183
|
+
* App-owned context attached to every report — orgId, plan, role, feature
|
|
184
|
+
* flags. Merged with anything set imperatively via `setContext`.
|
|
185
|
+
*/
|
|
186
|
+
context?: Record<string, unknown>;
|
|
187
|
+
/**
|
|
188
|
+
* Extra origins whose failed-request bodies may be recorded in breadcrumbs,
|
|
189
|
+
* e.g. `["https://api.myapp.com"]`. Same-origin requests are always recorded;
|
|
190
|
+
* third-party APIs are excluded by default because their error envelopes carry
|
|
191
|
+
* data you don't control and shouldn't forward into a GitHub issue.
|
|
192
|
+
*/
|
|
193
|
+
responseBodyOrigins?: string[];
|
|
106
194
|
}
|
|
107
195
|
interface ReportButtonProps {
|
|
108
196
|
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
109
197
|
label?: string;
|
|
110
198
|
className?: string;
|
|
111
199
|
}
|
|
200
|
+
interface FeedbackButtonProps {
|
|
201
|
+
position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
|
|
202
|
+
label?: string;
|
|
203
|
+
className?: string;
|
|
204
|
+
/** Heading inside the dialog (default: "How are we doing?") */
|
|
205
|
+
title?: string;
|
|
206
|
+
/** Placeholder for the message box */
|
|
207
|
+
placeholder?: string;
|
|
208
|
+
/** Shown after a successful submit */
|
|
209
|
+
thanksMessage?: string;
|
|
210
|
+
}
|
|
112
211
|
interface UseGlitchgrabReturn {
|
|
113
212
|
/** Report a bug programmatically */
|
|
114
213
|
reportBug: (description: string, metadata?: Record<string, string>) => Promise<ReportResult | null>;
|
|
115
214
|
/** Report with a specific type */
|
|
116
215
|
report: (type: ReportType, description: string, metadata?: Record<string, string>) => Promise<ReportResult | null>;
|
|
216
|
+
/**
|
|
217
|
+
* Report an error your app already caught — a framework error boundary
|
|
218
|
+
* (`app/error.tsx`, React Router `errorElement`, Remix `ErrorBoundary`) or a
|
|
219
|
+
* `try/catch`. These never reach `window.onerror`, so auto-capture misses them.
|
|
220
|
+
* Sends `SDK_AUTO`. Fire-and-forget, never throws.
|
|
221
|
+
*/
|
|
222
|
+
captureError: (error: unknown, options?: CaptureErrorOptions) => void;
|
|
223
|
+
/**
|
|
224
|
+
* Attach a key-value to every future report — orgId, plan, role, feature flag.
|
|
225
|
+
* Pass `null` to remove a key. Survives navigation and provider remounts.
|
|
226
|
+
*/
|
|
227
|
+
setContext: (key: string, value: unknown) => void;
|
|
228
|
+
/** Set several context keys at once. Merges; does not replace. */
|
|
229
|
+
setContexts: (values: Record<string, unknown>) => void;
|
|
230
|
+
/**
|
|
231
|
+
* Save a 1–5 star rating your end-user left about your app. Stored by
|
|
232
|
+
* Glitchgrab and shown on your Feedback page — never becomes a GitHub issue.
|
|
233
|
+
* Returns null on any failure (never throws).
|
|
234
|
+
*/
|
|
235
|
+
sendFeedback: (rating: number, message?: string, metadata?: Record<string, string>) => Promise<FeedbackResult | null>;
|
|
236
|
+
/** Open the built-in feedback dialog (stars + message) programmatically */
|
|
237
|
+
openFeedbackDialog: () => void;
|
|
117
238
|
/** Add a custom breadcrumb */
|
|
118
239
|
addBreadcrumb: (message: string, data?: Record<string, string>) => void;
|
|
119
240
|
/** Open the ReportButton modal programmatically (captures screenshot + shows dialog) */
|
|
@@ -144,11 +265,14 @@ interface UseGlitchgrabReturn {
|
|
|
144
265
|
*
|
|
145
266
|
* @example
|
|
146
267
|
* ```tsx
|
|
147
|
-
* const { reportBug, report, addBreadcrumb } = useGlitchgrab();
|
|
268
|
+
* const { reportBug, report, captureError, addBreadcrumb } = useGlitchgrab();
|
|
148
269
|
*
|
|
149
270
|
* // Report a bug
|
|
150
271
|
* reportBug("Login button crashes on mobile");
|
|
151
272
|
*
|
|
273
|
+
* // Report an error your own boundary already caught (e.g. app/error.tsx)
|
|
274
|
+
* captureError(error, { digest: error.digest, boundary: "next-app-router" });
|
|
275
|
+
*
|
|
152
276
|
* // Report a feature request
|
|
153
277
|
* report("FEATURE_REQUEST", "Add dark mode");
|
|
154
278
|
*
|
|
@@ -188,6 +312,31 @@ declare function ReportButton({ position, label, className, children, }: ReportB
|
|
|
188
312
|
}) => ReactNode;
|
|
189
313
|
}): react_jsx_runtime.JSX.Element | null;
|
|
190
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Trigger for the built-in feedback dialog (stars + message).
|
|
317
|
+
*
|
|
318
|
+
* The dialog itself lives inside `GlitchgrabProvider` — this is only the visible
|
|
319
|
+
* trigger. Open it programmatically with `useGlitchgrab().openFeedbackDialog()`,
|
|
320
|
+
* or skip the dialog entirely and call `sendFeedback(rating, message)` from your
|
|
321
|
+
* own UI.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```tsx
|
|
325
|
+
* // Default floating button
|
|
326
|
+
* <FeedbackButton />
|
|
327
|
+
*
|
|
328
|
+
* // Your own trigger
|
|
329
|
+
* <FeedbackButton>
|
|
330
|
+
* {({ onClick }) => <button onClick={onClick}>Rate us</button>}
|
|
331
|
+
* </FeedbackButton>
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
declare function FeedbackButton({ position, label, className, children, }: FeedbackButtonProps & {
|
|
335
|
+
children?: (props: {
|
|
336
|
+
onClick: () => void;
|
|
337
|
+
}) => ReactNode;
|
|
338
|
+
}): react_jsx_runtime.JSX.Element | null;
|
|
339
|
+
|
|
191
340
|
interface ErrorBoundaryProps {
|
|
192
341
|
token: string;
|
|
193
342
|
baseUrl?: string;
|
|
@@ -207,6 +356,52 @@ declare class GlitchgrabErrorBoundary extends React.Component<ErrorBoundaryProps
|
|
|
207
356
|
render(): React.ReactNode;
|
|
208
357
|
}
|
|
209
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Report an error your app already caught — a framework error boundary
|
|
361
|
+
* (`app/error.tsx`, `app/global-error.tsx`, React Router `errorElement`, Remix
|
|
362
|
+
* `ErrorBoundary`), a `componentDidCatch`, or a `try/catch` you want filed.
|
|
363
|
+
*
|
|
364
|
+
* These never reach `window.onerror`, so provider auto-capture cannot see them.
|
|
365
|
+
*
|
|
366
|
+
* Fire-and-forget. Never throws, never blocks the fallback UI render. No-ops if
|
|
367
|
+
* no `GlitchgrabProvider` has rendered yet (nothing to authenticate with).
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```tsx
|
|
371
|
+
* // app/global-error.tsx — renders outside the provider tree
|
|
372
|
+
* "use client";
|
|
373
|
+
* import { captureError } from "glitchgrab";
|
|
374
|
+
*
|
|
375
|
+
* export default function GlobalError({ error }: { error: Error & { digest?: string } }) {
|
|
376
|
+
* useEffect(() => {
|
|
377
|
+
* captureError(error, { digest: error.digest, boundary: "next-global-error" });
|
|
378
|
+
* }, [error]);
|
|
379
|
+
* return <html><body><p>Something went wrong</p></body></html>;
|
|
380
|
+
* }
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
declare function captureError(error: unknown, options?: CaptureErrorOptions): void;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* App-owned context: key-values the host app attaches once and every report
|
|
387
|
+
* carries thereafter — orgId, plan, role, active feature flags. The SDK cannot
|
|
388
|
+
* guess these, and they are usually the difference between "a crash" and "a
|
|
389
|
+
* crash for enterprise tenants on the new billing flow".
|
|
390
|
+
*
|
|
391
|
+
* Module-level, not React state, so it survives the provider unmount that
|
|
392
|
+
* `global-error.tsx` causes and is readable from non-React call sites.
|
|
393
|
+
*/
|
|
394
|
+
/**
|
|
395
|
+
* Attach one key-value to every future report. Passing `null`/`undefined`
|
|
396
|
+
* removes the key — so a value that goes away (user logs out of an org) stops
|
|
397
|
+
* being reported instead of going stale.
|
|
398
|
+
*/
|
|
399
|
+
declare function setContext(key: string, value: unknown): void;
|
|
400
|
+
/** Set several keys at once. Merges — it does not replace what's already set. */
|
|
401
|
+
declare function setContexts(values: Record<string, unknown>): void;
|
|
402
|
+
declare function getAppContext(): Record<string, string>;
|
|
403
|
+
declare function clearAppContext(): void;
|
|
404
|
+
|
|
210
405
|
interface GlitchgrabReport {
|
|
211
406
|
id: string;
|
|
212
407
|
source: string;
|
|
@@ -299,7 +494,60 @@ declare function useGlitchgrabActions({ token, onSuccess, onError, }: {
|
|
|
299
494
|
error: string | null;
|
|
300
495
|
};
|
|
301
496
|
|
|
302
|
-
|
|
497
|
+
interface FeedbackQuery {
|
|
498
|
+
token: string;
|
|
499
|
+
/** Only entries the repo owner published — use this for a public testimonials wall */
|
|
500
|
+
approvedOnly?: boolean;
|
|
501
|
+
/** Only this end-user's feedback (their primary key in your DB) */
|
|
502
|
+
userId?: string;
|
|
503
|
+
/** Floor on the star rating, 1–5 */
|
|
504
|
+
minRating?: number;
|
|
505
|
+
/** Max results (default 50, max 100) */
|
|
506
|
+
limit?: number;
|
|
507
|
+
baseUrl?: string;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Standalone fetcher — use with TanStack Query or any data fetching library.
|
|
511
|
+
*
|
|
512
|
+
* ```tsx
|
|
513
|
+
* const { data } = useQuery({
|
|
514
|
+
* queryKey: ["testimonials"],
|
|
515
|
+
* queryFn: () => fetchGlitchgrabFeedback({
|
|
516
|
+
* token: process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!,
|
|
517
|
+
* approvedOnly: true,
|
|
518
|
+
* minRating: 4,
|
|
519
|
+
* }),
|
|
520
|
+
* });
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
declare function fetchGlitchgrabFeedback(query: FeedbackQuery): Promise<GlitchgrabFeedback[]>;
|
|
524
|
+
/**
|
|
525
|
+
* Hook to read back the feedback your end-users left — render it as a
|
|
526
|
+
* testimonials wall, or show a user their own past ratings.
|
|
527
|
+
*
|
|
528
|
+
* ```tsx
|
|
529
|
+
* const { feedback, isLoading, error, refetch } = useGlitchgrabFeedback({
|
|
530
|
+
* token: process.env.NEXT_PUBLIC_GLITCHGRAB_TOKEN!,
|
|
531
|
+
* approvedOnly: true,
|
|
532
|
+
* });
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
535
|
+
declare function useGlitchgrabFeedback(query: FeedbackQuery): {
|
|
536
|
+
feedback: GlitchgrabFeedback[];
|
|
537
|
+
isLoading: boolean;
|
|
538
|
+
isFetching: boolean;
|
|
539
|
+
error: string | null;
|
|
540
|
+
refetch: () => Promise<void>;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
interface BreadcrumbOptions {
|
|
544
|
+
/**
|
|
545
|
+
* Additional origins to capture error response bodies from, e.g.
|
|
546
|
+
* `["https://api.myapp.com"]`. Same-origin requests are always captured.
|
|
547
|
+
*/
|
|
548
|
+
responseBodyOrigins?: string[];
|
|
549
|
+
}
|
|
550
|
+
declare function initBreadcrumbs(max?: number, options?: BreadcrumbOptions): void;
|
|
303
551
|
declare function addBreadcrumb(type: BreadcrumbType, message: string, data?: Record<string, string>): void;
|
|
304
552
|
declare function getBreadcrumbs(): Breadcrumb[];
|
|
305
553
|
declare function clearBreadcrumbs(): void;
|
|
@@ -330,6 +578,15 @@ declare function captureContext(visitedPages: string[]): CapturedContext;
|
|
|
330
578
|
* Never throws — returns null on failure.
|
|
331
579
|
*/
|
|
332
580
|
declare function sendReport(payload: ReportPayload, baseUrl?: string): Promise<ReportResult | null>;
|
|
581
|
+
/**
|
|
582
|
+
* Save a star rating your end-user left about your app.
|
|
583
|
+
* Never throws — returns null on failure.
|
|
584
|
+
*
|
|
585
|
+
* Unlike `sendReport`, this is always a deliberate user action with a dialog
|
|
586
|
+
* open in front of them, so it retries on transient failure but skips the
|
|
587
|
+
* sendBeacon fallback: the caller needs a real result to show a thank-you.
|
|
588
|
+
*/
|
|
589
|
+
declare function sendFeedback(payload: FeedbackPayload, baseUrl?: string): Promise<FeedbackResult | null>;
|
|
333
590
|
interface EnhanceContext {
|
|
334
591
|
url?: string;
|
|
335
592
|
visitedPages?: string[];
|
|
@@ -349,8 +606,14 @@ declare function computeSignature(params: {
|
|
|
349
606
|
errorMessage: string | undefined;
|
|
350
607
|
pageUrl: string | undefined;
|
|
351
608
|
errorStack?: string | undefined;
|
|
609
|
+
/**
|
|
610
|
+
* Next.js error digest. Production strips server-boundary errors to a generic
|
|
611
|
+
* message with no useful stack, so without the digest every distinct server
|
|
612
|
+
* crash on one page collapses into a single signature.
|
|
613
|
+
*/
|
|
614
|
+
digest?: string | undefined;
|
|
352
615
|
}): string;
|
|
353
616
|
declare function shouldSkipDuplicate(signature: string, windowMs?: number, now?: number): boolean;
|
|
354
617
|
declare function clearDedupCache(): void;
|
|
355
618
|
|
|
356
|
-
export { type Breadcrumb, type BreadcrumbType, type CapturedContext, type DeviceInfo, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, type GlitchgrabConfig, GlitchgrabErrorBoundary, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabReports, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendReport, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabReports };
|
|
619
|
+
export { type Breadcrumb, type BreadcrumbType, type CaptureErrorOptions, type CapturedContext, type DeviceInfo, FeedbackButton, type FeedbackButtonProps, type FeedbackPayload, type FeedbackQuery, type FeedbackResult, GLITCHGRAB_SHORTCUT, GLITCHGRAB_SHORTCUT_MAC, type GlitchgrabConfig, GlitchgrabErrorBoundary, type GlitchgrabFeedback, GlitchgrabProvider, type GlitchgrabProviderProps, type GlitchgrabReport, type GlitchgrabSession, ReportButton, type ReportButtonProps, type ReportPayload, type ReportResult, type ReportSeverity, type ReportType, type RuntimeInfo, type UseGlitchgrabReturn, addBreadcrumb, captureContext, captureDeviceInfo, captureError, clearAppContext, clearBreadcrumbs, clearDedupCache, computeSignature, enhanceText, fetchGlitchgrabFeedback, fetchGlitchgrabReports, getAppContext, getBreadcrumbs, getShortcutLabel, initBreadcrumbs, matchesShortcut, sanitizeUrl, sendFeedback, sendReport, setContext, setContexts, shouldSkipDuplicate, useGlitchgrab, useGlitchgrabActions, useGlitchgrabFeedback, useGlitchgrabReports };
|