| 1 | // A small ring buffer of recent app events, attached to crash reports so a stack |
| 2 | // trace arrives with the steps that led to it. Self-contained (no app imports) so |
| 3 | // the crash overlay can read it even when the rest of the app is broken. |
| 4 | |
| 5 | export type Breadcrumb = { t: number; cat: string; msg: string }; |
| 6 | |
| 7 | const MAX = 30; |
| 8 | const ring: Breadcrumb[] = []; |
| 9 | let browserHooksInstalled = false; |
| 10 | |
| 11 | export function addBreadcrumb(cat: string, msg: string): void { |
| 12 | ring.push({ t: Date.now(), cat, msg: msg.length > 200 ? `${msg.slice(0, 200)}…` : msg }); |
| 13 | if (ring.length > MAX) ring.shift(); |
| 14 | } |
| 15 | |
| 16 | export function snapshotBreadcrumbs(): Breadcrumb[] { |
| 17 | return ring.map((b) => ({ ...b })); |
| 18 | } |
| 19 | |
| 20 | export function dumpBreadcrumbs(): string { |
| 21 | if (!ring.length) return ""; |
| 22 | const now = Date.now(); |
| 23 | return ring.map((b) => `-${((now - b.t) / 1000).toFixed(1)}s [${b.cat}] ${b.msg}`).join("\n"); |
| 24 | } |
| 25 | |
| 26 | function stringifyArg(a: unknown): string { |
| 27 | if (typeof a === "string") return a; |
| 28 | if (a instanceof Error) return a.message; |
| 29 | try { |
| 30 | return JSON.stringify(a); |
| 31 | } catch { |
| 32 | return String(a); |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | export function installBreadcrumbConsoleHook(): void { |
| 37 | if (!browserHooksInstalled) { |
| 38 | browserHooksInstalled = true; |
| 39 | addBreadcrumb("app", "start"); |
| 40 | if (typeof window !== "undefined") { |
| 41 | window.addEventListener("online", () => addBreadcrumb("network", "online")); |
| 42 | window.addEventListener("offline", () => addBreadcrumb("network", "offline")); |
| 43 | document.addEventListener("visibilitychange", () => addBreadcrumb("view", document.visibilityState)); |
| 44 | } |
| 45 | } |
| 46 | for (const level of ["error", "warn"] as const) { |
| 47 | const orig = console[level].bind(console); |
| 48 | console[level] = (...args: unknown[]) => { |
| 49 | addBreadcrumb(`console.${level}`, args.map(stringifyArg).join(" ")); |
| 50 | orig(...args); |
| 51 | }; |
| 52 | } |
| 53 | } |
| 54 |