UNPKG

@sentry/remix

Version:
129 lines (126 loc) • 4.45 kB
import { browserTracingIntegration as browserTracingIntegration$1, getActiveSpan, getRootSpan, WINDOW, resolveCurrentRoute, resolveRoute, startBrowserTracingNavigationSpan, getRouteProvider, getCurrentScope } from '@sentry/browser'; import { SENTRY_OP, SENTRY_ORIGIN, URL_TEMPLATE, SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; import { spanToJSON, NAVIGATION_SPAN_NAME_FALLBACK, hasSpanStreamingEnabled, addFetchInstrumentationHandler } from '@sentry/core'; import { parseRouteTiming, ROUTE_TIMING_NAME } from '../routeTiming.js'; const FRAME_REQUEST_HEADER = "x-remix-frame"; function browserTracingIntegration(options = {}) { const integration = browserTracingIntegration$1({ ...options, instrumentNavigation: false }); return { ...integration, afterAllSetup(client) { integration.afterAllSetup(client); if (options.instrumentPageLoad !== false) { nameActivePageLoadSpan(client); } if (options.instrumentNavigation !== false) { instrumentNavigationApi(client); } } }; } function nameActivePageLoadSpan(client) { const activeSpan = getActiveSpan(); if (!activeSpan) { return; } const rootSpan = getRootSpan(activeSpan); if (spanToJSON(rootSpan).attributes[SENTRY_OP] !== "pageload") { return; } recordRoute(client, WINDOW.location?.pathname, getDocumentRoute()); const route = resolveCurrentRoute(client); if (route) { applyRoute(rootSpan, route); } } function instrumentNavigationApi(client) { const navigation = WINDOW.navigation; if (!navigation) { return; } let pending; client.on("spanEnd", (span) => { if (pending?.span === span) { pending = void 0; } }); navigation.addEventListener("navigate", (event) => { const url = event.destination?.url; if (!url || !isRuntimeNavigation(event, url)) { return; } const pathname = pathnameOf(url) || "/"; const route = resolveRoute(url, client); const span = startBrowserTracingNavigationSpan( client, { name: route ?? (hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : pathname), attributes: { [SENTRY_SEGMENT_NAME_SOURCE]: route ? "route" : "url", ...route && { [URL_TEMPLATE]: route }, [SENTRY_ORIGIN]: "auto.navigation.remix_v3" } }, // The span needs the destination: `location` still points at the previous page until the // navigation finishes. { url } ); pending = span && !route ? { span, url, pathname } : void 0; }); addFetchInstrumentationHandler(({ fetchData, headers, response }) => { if (!pending || !response) { return; } if (headers?.get(FRAME_REQUEST_HEADER) !== "true") { return; } if (pathnameOf(fetchData.url, WINDOW.location?.href) !== pending.pathname) { return; } const { span, url, pathname } = pending; pending = void 0; const servedPathname = pathnameOf(response.url, WINDOW.location?.href) || pathname; recordRoute(client, servedPathname, parseRouteTiming(response.headers.get("server-timing"))); const route = resolveRoute(response.url || url, client); if (route) { applyRoute(span, route); } }); } function recordRoute(client, pathname, route) { const provider = getRouteProvider(client); if (provider && "record" in provider && typeof provider.record === "function") { provider.record(pathname, route); } } function applyRoute(span, route) { span.updateName(route); span.setAttributes({ [SENTRY_SEGMENT_NAME_SOURCE]: "route", [URL_TEMPLATE]: route }); const activeSpan = getActiveSpan(); if (activeSpan && getRootSpan(activeSpan) === span) { getCurrentScope().setTransactionName(route); } } function getDocumentRoute() { const entry = WINDOW.performance?.getEntriesByType?.("navigation")[0]; return entry?.serverTiming?.find((timing) => timing.name === ROUTE_TIMING_NAME)?.description; } function isRuntimeNavigation(event, url) { return event.canIntercept && event.info !== "remix-document-reload" && isSameOrigin(url); } function isSameOrigin(url) { try { return new URL(url).origin === WINDOW.location?.origin; } catch { return false; } } function pathnameOf(url, base) { try { return new URL(url, base).pathname; } catch { return void 0; } } export { browserTracingIntegration }; //# sourceMappingURL=browserTracingIntegration.js.map