Files
threadcount-community/lib/errors.ts
T
ThreadCount 7d650e4c10 ThreadCount Community edition
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from d947f89 on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
2026-09-15 18:27:45 +10:00

48 lines
1.9 KiB
TypeScript

"use client";
/* One place the app reports a crash from.
*
* The error boundaries call this rather than talking to a reporting SDK directly, so that what
* gets sent — and what gets stripped first — is decided in a single file rather than at each
* boundary. ThreadCount's URLs carry staff, location and order ids, exactly as they do for
* analytics, and a crash report is if anything more likely to drag one along: the page URL, the
* referrer and the breadcrumb trail all contain them.
*
* Until the reporter is wired this is a no-op in production and a console line in development,
* which is deliberately better than the previous behaviour of losing the error entirely. */
import { scrubPath } from "@/lib/analytics";
type Reporter = {
captureException: (e: unknown, ctx?: Record<string, unknown>) => void;
};
/** Set by the reporting bootstrap once it has initialised. Absent = reporting is off. */
function reporter(): Reporter | undefined {
if (typeof window === "undefined") return undefined;
return (window as unknown as { __tcReporter?: Reporter }).__tcReporter;
}
/** The current location with record ids removed, safe to attach to a report. */
export function safeLocation(): string {
if (typeof location === "undefined") return "";
return scrubPath(location.pathname);
}
/**
* Report a crash.
* `where` distinguishes a route boundary from the root one — a global error means the layout
* itself failed, which is a different and more serious shape of problem.
*/
export function reportError(error: unknown, where: "route" | "global" | "client") {
const r = reporter();
if (r) {
try {
r.captureException(error, { tags: { boundary: where }, extra: { path: safeLocation() } });
return;
} catch { /* a reporter that throws must not take the page with it */ }
}
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line no-console
console.error(`[threadcount:${where}]`, error);
}
}