UNPKG

@sentry/remix

Version:
206 lines (164 loc) 6.11 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const core = require('@sentry/core'); const react = require('@sentry/react'); const React = require('react'); const debugBuild = require('../utils/debug-build.js'); const remixRouteParameterization = require('./remixRouteParameterization.js'); let _useEffect; let _useLocation; let _useMatches; let _instrumentNavigation; function getInitPathName() { if (react.WINDOW.location) { return react.WINDOW.location.pathname; } return undefined; } /** * Determines the transaction name and source for a route. * Handles three cases: * 1. Dynamic routes with manifest (Vite apps): Use parameterized path with source 'route' * 2. Static routes with manifest (Vite apps): Use pathname with source 'url' * 3. Legacy apps without manifest: Use route ID with source 'route' */ function getTransactionNameAndSource( pathname, routeId, ) { const parameterizedRoute = pathname ? remixRouteParameterization.maybeParameterizeRemixRoute(pathname) : undefined; if (parameterizedRoute) { // We have a parameterized route from the manifest (dynamic route) return { name: parameterizedRoute, source: 'route' }; } if (remixRouteParameterization.hasManifest()) { // We have a manifest but no parameterization (static route) // Use the pathname with source 'url' return { name: pathname || routeId, source: 'url' }; } // No manifest available (legacy app without Vite plugin) // Fall back to route ID for backward compatibility return { name: routeId, source: 'route' }; } function startPageloadSpan(client) { const initPathName = getInitPathName(); if (!initPathName) { return; } // Try to parameterize the route using the route manifest const parameterizedRoute = remixRouteParameterization.maybeParameterizeRemixRoute(initPathName); const spanName = parameterizedRoute || initPathName; const source = parameterizedRoute ? 'route' : 'url'; const spanContext = { name: spanName, op: 'pageload', attributes: { [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.remix', [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }; react.startBrowserTracingPageLoadSpan(client, spanContext); } function startNavigationSpan(matches, location) { const lastMatch = matches[matches.length - 1]; const client = react.getClient(); if (!client || !lastMatch) { return; } const { name, source } = getTransactionNameAndSource(location.pathname, lastMatch.id); const spanContext = { name, op: 'navigation', attributes: { [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.remix', [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }; react.startBrowserTracingNavigationSpan(client, spanContext); } /** * Wraps a remix `root` (see: https://remix.run/docs/en/main/start/quickstart#the-root-route) * To enable pageload/navigation tracing on every route. * * @param OrigApp The Remix root to wrap * @param useEffect The `useEffect` hook from `react` * @param useLocation The `useLocation` hook from `@remix-run/react` * @param useMatches The `useMatches` hook from `@remix-run/react` * @param instrumentNavigation Whether to instrument navigation spans. Defaults to `true`. */ function withSentry( OrigApp, useEffect, useLocation, useMatches, instrumentNavigation, ) { const SentryRoot = (props) => { setGlobals({ useEffect, useLocation, useMatches, instrumentNavigation: instrumentNavigation || true }); // Early return when any of the required functions is not available. if (!_useEffect || !_useLocation || !_useMatches) { debugBuild.DEBUG_BUILD && !core.isNodeEnv() && core.debug.warn('Remix SDK was unable to wrap your root because of one or more missing parameters.'); // @ts-expect-error Setting more specific React Component typing for `R` generic above // will break advanced type inference done by react router params return React.createElement(OrigApp, { ...props,} ); } let isBaseLocation = false; const location = _useLocation(); const matches = _useMatches(); _useEffect(() => { const lastMatch = matches?.[matches.length - 1]; if (lastMatch) { const { name, source } = getTransactionNameAndSource(location.pathname, lastMatch.id); core.getCurrentScope().setTransactionName(name); const activeRootSpan = core.getActiveSpan(); if (activeRootSpan) { const transaction = core.getRootSpan(activeRootSpan); if (transaction) { transaction.updateName(name); transaction.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); } } } isBaseLocation = true; }, []); _useEffect(() => { const activeRootSpan = core.getActiveSpan(); if (isBaseLocation) { if (activeRootSpan) { activeRootSpan.end(); } return; } if (_instrumentNavigation && matches?.length) { if (activeRootSpan) { activeRootSpan.end(); } startNavigationSpan(matches, location); } }, [location]); isBaseLocation = false; // @ts-expect-error Setting more specific React Component typing for `R` generic above // will break advanced type inference done by react router params return React.createElement(OrigApp, { ...props,} ); }; // @ts-expect-error Setting more specific React Component typing for `R` generic above // will break advanced type inference done by react router params return SentryRoot; } function setGlobals({ useEffect, useLocation, useMatches, instrumentNavigation, } ) { _useEffect = useEffect || _useEffect; _useLocation = useLocation || _useLocation; _useMatches = useMatches || _useMatches; _instrumentNavigation = instrumentNavigation ?? _instrumentNavigation; } exports.setGlobals = setGlobals; exports.startPageloadSpan = startPageloadSpan; exports.withSentry = withSentry; //# sourceMappingURL=performance.js.map