All files / src/lib sentry-client.ts

88% Statements 22/25
85.71% Branches 12/14
57.14% Functions 4/7
95% Lines 19/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84                                                  7x   2x   1x   4x                         6x 2x                             2x 2x 2x 2x 2x 2x 2x 2x       2x   2x 2x       1x       1x  
// Browser-side Sentry init for the marketplace. Loaded once via a module
// `<script>` in the shared layout, so it runs on every page.
//
// DSN + environment are build-time values inlined through the astro.config
// `define` block (PUBLIC_SENTRY_DSN / PUBLIC_ENVIRONMENT). With no DSN, or
// locally, this module does nothing and the SDK (~28 KB gzip) is never
// fetched: it is a dynamic import, split into its own chunk.
//
// While the SDK loads, uncaught errors and unhandled rejections are queued and
// replayed into Sentry once it is up, so the first moments of a page are
// still covered.
//
// Errors + browser tracing only (no Session Replay): the marketplace is
// LCP-sensitive and Replay roughly doubles the client SDK weight. Add
// `Sentry.replayIntegration()` here if replay is later prioritized.
import type { BrowserOptions } from "@sentry/browser";
 
type SentrySdk = typeof import("./sentry-sdk");
 
// Through a re-export module, not import("@sentry/browser") itself: a dynamic
// import of the package namespace can't be tree-shaken (it pulled in ~146 KB
// gzip with Replay; destructuring the import still left ~48 KB).
const loadSdk = (): Promise<SentrySdk> => import("./sentry-sdk");
 
export function tracesSampleRate(environment: string): number {
	switch (environment) {
		case "production":
			return 0.1;
		case "preview":
			return 0.5;
		default:
			return 1.0;
	}
}
 
/**
 * Build the Sentry browser init options (minus SDK-built integrations), or
 * `null` when Sentry should stay disabled (no DSN, or running locally). Pure +
 * exported so the gating logic is unit-testable without loading the SDK.
 */
export function sentryClientOptions(
	dsn: string | undefined,
	environment: string,
): BrowserOptions | null {
	if (!dsn || environment === "local") return null;
	return {
		dsn,
		environment,
		tracesSampleRate: tracesSampleRate(environment),
	};
}
 
/**
 * Queue early errors, load the SDK, init it with browser tracing, then replay
 * the queue. `load` is injectable for tests.
 */
export async function initSentryClient(
	options: BrowserOptions,
	load: () => Promise<SentrySdk> = loadSdk,
): Promise<void> {
	const early: unknown[] = [];
	const onError = (e: ErrorEvent) => early.push(e.error ?? e.message);
	const onRejection = (e: PromiseRejectionEvent) => early.push(e.reason);
	window.addEventListener("error", onError);
	window.addEventListener("unhandledrejection", onRejection);
	try {
		const Sentry = await load();
		Sentry.init({
			...options,
			integrations: [Sentry.browserTracingIntegration()],
		});
		for (const err of early) Sentry.captureException(err);
	} finally {
		window.removeEventListener("error", onError);
		window.removeEventListener("unhandledrejection", onRejection);
	}
}
 
const options = sentryClientOptions(
	import.meta.env.PUBLIC_SENTRY_DSN as string | undefined,
	(import.meta.env.PUBLIC_ENVIRONMENT as string | undefined) || "local",
);
Iif (options) void initSentryClient(options).catch(() => {});