import { browserTracingIntegration, startBrowserTracingPageLoadSpan, startBrowserTracingNavigationSpan, WINDOW } from '@sentry/browser'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getRootSpan, spanToJSON } from '@sentry/core'; import hoistNonReactStatics from 'hoist-non-react-statics'; import * as React from 'react'; const _jsxFileName = "/home/runner/work/sentry-javascript/sentry-javascript/packages/react/src/reactrouter.tsx"; // We need to disable eslint no-explict-any because any is required for the // react-router typings. let activeTransaction; /** * A browser tracing integration that uses React Router v4 to instrument navigations. * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options. */ function reactRouterV4BrowserTracingIntegration( options, ) { const integration = browserTracingIntegration({ ...options, instrumentPageLoad: false, instrumentNavigation: false, }); const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options; return { ...integration, afterAllSetup(client) { integration.afterAllSetup(client); const startPageloadCallback = (startSpanOptions) => { startBrowserTracingPageLoadSpan(client, startSpanOptions); return undefined; }; const startNavigationCallback = (startSpanOptions) => { startBrowserTracingNavigationSpan(client, startSpanOptions); return undefined; }; // eslint-disable-next-line deprecation/deprecation const instrumentation = reactRouterV4Instrumentation(history, routes, matchPath); // Now instrument page load & navigation with correct settings instrumentation(startPageloadCallback, instrumentPageLoad, false); instrumentation(startNavigationCallback, false, instrumentNavigation); }, }; } /** * A browser tracing integration that uses React Router v5 to instrument navigations. * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options. */ function reactRouterV5BrowserTracingIntegration( options, ) { const integration = browserTracingIntegration({ ...options, instrumentPageLoad: false, instrumentNavigation: false, }); const { history, routes, matchPath } = options; return { ...integration, afterAllSetup(client) { integration.afterAllSetup(client); const startPageloadCallback = (startSpanOptions) => { startBrowserTracingPageLoadSpan(client, startSpanOptions); return undefined; }; const startNavigationCallback = (startSpanOptions) => { startBrowserTracingNavigationSpan(client, startSpanOptions); return undefined; }; // eslint-disable-next-line deprecation/deprecation const instrumentation = reactRouterV5Instrumentation(history, routes, matchPath); // Now instrument page load & navigation with correct settings instrumentation(startPageloadCallback, options.instrumentPageLoad, false); instrumentation(startNavigationCallback, false, options.instrumentNavigation); }, }; } /** * @deprecated Use `browserTracingReactRouterV4()` instead. */ function reactRouterV4Instrumentation( history, routes, matchPath, ) { return createReactRouterInstrumentation(history, 'reactrouter_v4', routes, matchPath); } /** * @deprecated Use `browserTracingReactRouterV5()` instead. */ function reactRouterV5Instrumentation( history, routes, matchPath, ) { return createReactRouterInstrumentation(history, 'reactrouter_v5', routes, matchPath); } function createReactRouterInstrumentation( history, instrumentationName, allRoutes = [], matchPath, ) { function getInitPathName() { if (history && history.location) { return history.location.pathname; } if (WINDOW && WINDOW.location) { return WINDOW.location.pathname; } return undefined; } /** * Normalizes a transaction name. Returns the new name as well as the * source of the transaction. * * @param pathname The initial pathname we normalize */ function normalizeTransactionName(pathname) { if (allRoutes.length === 0 || !matchPath) { return [pathname, 'url']; } const branches = matchRoutes(allRoutes, pathname, matchPath); // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let x = 0; x < branches.length; x++) { if (branches[x].match.isExact) { return [branches[x].match.path, 'route']; } } return [pathname, 'url']; } return (customStartTransaction, startTransactionOnPageLoad = true, startTransactionOnLocationChange = true) => { const initPathName = getInitPathName(); if (startTransactionOnPageLoad && initPathName) { const [name, source] = normalizeTransactionName(initPathName); activeTransaction = customStartTransaction({ name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.${instrumentationName}`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }); } if (startTransactionOnLocationChange && history.listen) { history.listen((location, action) => { if (action && (action === 'PUSH' || action === 'POP')) { if (activeTransaction) { activeTransaction.end(); } const [name, source] = normalizeTransactionName(location.pathname); activeTransaction = customStartTransaction({ name, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, }, }); } }); } }; } /** * Matches a set of routes to a pathname * Based on implementation from */ function matchRoutes( routes, pathname, matchPath, branch = [], ) { routes.some(route => { const match = route.path ? matchPath(pathname, route) : branch.length ? branch[branch.length - 1].match // use parent match : computeRootMatch(pathname); // use default "root" match if (match) { branch.push({ route, match }); if (route.routes) { matchRoutes(route.routes, pathname, matchPath, branch); } } return !!match; }); return branch; } function computeRootMatch(pathname) { return { path: '/', url: '/', params: {}, isExact: pathname === '/' }; } /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */ function withSentryRouting(Route) { const componentDisplayName = (Route ).displayName || (Route ).name; const activeRootSpan = getActiveRootSpan(); const WrappedRoute = (props) => { if (activeRootSpan && props && props.computedMatch && props.computedMatch.isExact) { activeRootSpan.updateName(props.computedMatch.path); activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route'); } // @ts-expect-error Setting more specific React Component typing for `R` generic above // will break advanced type inference done by react router params: // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164 return React.createElement(Route, { ...props, __self: this, __source: {fileName: _jsxFileName, lineNumber: 277}} ); }; WrappedRoute.displayName = `sentryRoute(${componentDisplayName})`; hoistNonReactStatics(WrappedRoute, Route); // @ts-expect-error Setting more specific React Component typing for `R` generic above // will break advanced type inference done by react router params: // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164 return WrappedRoute; } /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */ function getActiveRootSpan() { // Legacy behavior for "old" react router instrumentation if (activeTransaction) { return activeTransaction; } const span = getActiveSpan(); const rootSpan = span ? getRootSpan(span) : undefined; if (!rootSpan) { return undefined; } const op = spanToJSON(rootSpan).op; // Only use this root span if it is a pageload or navigation span return op === 'navigation' || op === 'pageload' ? rootSpan : undefined; } export { reactRouterV4BrowserTracingIntegration, reactRouterV4Instrumentation, reactRouterV5BrowserTracingIntegration, reactRouterV5Instrumentation, withSentryRouting }; //# sourceMappingURL=reactrouter.js.map