reactrouter.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const browser = require('@sentry/browser');
  3. const core = require('@sentry/core');
  4. const hoistNonReactStatics = require('hoist-non-react-statics');
  5. const React = require('react');
  6. const _interopDefault = e => e && e.__esModule ? e.default : e;
  7. function _interopNamespace(e) {
  8. if (e && e.__esModule) return e;
  9. const n = Object.create(null);
  10. if (e) {
  11. for (const k in e) {
  12. n[k] = e[k];
  13. }
  14. }
  15. n.default = e;
  16. return n;
  17. }
  18. const hoistNonReactStatics__default = /*#__PURE__*/_interopDefault(hoistNonReactStatics);
  19. const React__namespace = /*#__PURE__*/_interopNamespace(React);
  20. const _jsxFileName = "/home/runner/work/sentry-javascript/sentry-javascript/packages/react/src/reactrouter.tsx";
  21. // We need to disable eslint no-explict-any because any is required for the
  22. // react-router typings.
  23. let activeTransaction;
  24. /**
  25. * A browser tracing integration that uses React Router v4 to instrument navigations.
  26. * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
  27. */
  28. function reactRouterV4BrowserTracingIntegration(
  29. options,
  30. ) {
  31. const integration = browser.browserTracingIntegration({
  32. ...options,
  33. instrumentPageLoad: false,
  34. instrumentNavigation: false,
  35. });
  36. const { history, routes, matchPath, instrumentPageLoad = true, instrumentNavigation = true } = options;
  37. return {
  38. ...integration,
  39. afterAllSetup(client) {
  40. integration.afterAllSetup(client);
  41. const startPageloadCallback = (startSpanOptions) => {
  42. browser.startBrowserTracingPageLoadSpan(client, startSpanOptions);
  43. return undefined;
  44. };
  45. const startNavigationCallback = (startSpanOptions) => {
  46. browser.startBrowserTracingNavigationSpan(client, startSpanOptions);
  47. return undefined;
  48. };
  49. // eslint-disable-next-line deprecation/deprecation
  50. const instrumentation = reactRouterV4Instrumentation(history, routes, matchPath);
  51. // Now instrument page load & navigation with correct settings
  52. instrumentation(startPageloadCallback, instrumentPageLoad, false);
  53. instrumentation(startNavigationCallback, false, instrumentNavigation);
  54. },
  55. };
  56. }
  57. /**
  58. * A browser tracing integration that uses React Router v5 to instrument navigations.
  59. * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options.
  60. */
  61. function reactRouterV5BrowserTracingIntegration(
  62. options,
  63. ) {
  64. const integration = browser.browserTracingIntegration({
  65. ...options,
  66. instrumentPageLoad: false,
  67. instrumentNavigation: false,
  68. });
  69. const { history, routes, matchPath } = options;
  70. return {
  71. ...integration,
  72. afterAllSetup(client) {
  73. integration.afterAllSetup(client);
  74. const startPageloadCallback = (startSpanOptions) => {
  75. browser.startBrowserTracingPageLoadSpan(client, startSpanOptions);
  76. return undefined;
  77. };
  78. const startNavigationCallback = (startSpanOptions) => {
  79. browser.startBrowserTracingNavigationSpan(client, startSpanOptions);
  80. return undefined;
  81. };
  82. // eslint-disable-next-line deprecation/deprecation
  83. const instrumentation = reactRouterV5Instrumentation(history, routes, matchPath);
  84. // Now instrument page load & navigation with correct settings
  85. instrumentation(startPageloadCallback, options.instrumentPageLoad, false);
  86. instrumentation(startNavigationCallback, false, options.instrumentNavigation);
  87. },
  88. };
  89. }
  90. /**
  91. * @deprecated Use `browserTracingReactRouterV4()` instead.
  92. */
  93. function reactRouterV4Instrumentation(
  94. history,
  95. routes,
  96. matchPath,
  97. ) {
  98. return createReactRouterInstrumentation(history, 'reactrouter_v4', routes, matchPath);
  99. }
  100. /**
  101. * @deprecated Use `browserTracingReactRouterV5()` instead.
  102. */
  103. function reactRouterV5Instrumentation(
  104. history,
  105. routes,
  106. matchPath,
  107. ) {
  108. return createReactRouterInstrumentation(history, 'reactrouter_v5', routes, matchPath);
  109. }
  110. function createReactRouterInstrumentation(
  111. history,
  112. instrumentationName,
  113. allRoutes = [],
  114. matchPath,
  115. ) {
  116. function getInitPathName() {
  117. if (history && history.location) {
  118. return history.location.pathname;
  119. }
  120. if (browser.WINDOW && browser.WINDOW.location) {
  121. return browser.WINDOW.location.pathname;
  122. }
  123. return undefined;
  124. }
  125. /**
  126. * Normalizes a transaction name. Returns the new name as well as the
  127. * source of the transaction.
  128. *
  129. * @param pathname The initial pathname we normalize
  130. */
  131. function normalizeTransactionName(pathname) {
  132. if (allRoutes.length === 0 || !matchPath) {
  133. return [pathname, 'url'];
  134. }
  135. const branches = matchRoutes(allRoutes, pathname, matchPath);
  136. // eslint-disable-next-line @typescript-eslint/prefer-for-of
  137. for (let x = 0; x < branches.length; x++) {
  138. if (branches[x].match.isExact) {
  139. return [branches[x].match.path, 'route'];
  140. }
  141. }
  142. return [pathname, 'url'];
  143. }
  144. return (customStartTransaction, startTransactionOnPageLoad = true, startTransactionOnLocationChange = true) => {
  145. const initPathName = getInitPathName();
  146. if (startTransactionOnPageLoad && initPathName) {
  147. const [name, source] = normalizeTransactionName(initPathName);
  148. activeTransaction = customStartTransaction({
  149. name,
  150. attributes: {
  151. [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
  152. [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.${instrumentationName}`,
  153. [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
  154. },
  155. });
  156. }
  157. if (startTransactionOnLocationChange && history.listen) {
  158. history.listen((location, action) => {
  159. if (action && (action === 'PUSH' || action === 'POP')) {
  160. if (activeTransaction) {
  161. activeTransaction.end();
  162. }
  163. const [name, source] = normalizeTransactionName(location.pathname);
  164. activeTransaction = customStartTransaction({
  165. name,
  166. attributes: {
  167. [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
  168. [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.${instrumentationName}`,
  169. [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
  170. },
  171. });
  172. }
  173. });
  174. }
  175. };
  176. }
  177. /**
  178. * Matches a set of routes to a pathname
  179. * Based on implementation from
  180. */
  181. function matchRoutes(
  182. routes,
  183. pathname,
  184. matchPath,
  185. branch = [],
  186. ) {
  187. routes.some(route => {
  188. const match = route.path
  189. ? matchPath(pathname, route)
  190. : branch.length
  191. ? branch[branch.length - 1].match // use parent match
  192. : computeRootMatch(pathname); // use default "root" match
  193. if (match) {
  194. branch.push({ route, match });
  195. if (route.routes) {
  196. matchRoutes(route.routes, pathname, matchPath, branch);
  197. }
  198. }
  199. return !!match;
  200. });
  201. return branch;
  202. }
  203. function computeRootMatch(pathname) {
  204. return { path: '/', url: '/', params: {}, isExact: pathname === '/' };
  205. }
  206. /* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
  207. function withSentryRouting(Route) {
  208. const componentDisplayName = (Route ).displayName || (Route ).name;
  209. const activeRootSpan = getActiveRootSpan();
  210. const WrappedRoute = (props) => {
  211. if (activeRootSpan && props && props.computedMatch && props.computedMatch.isExact) {
  212. activeRootSpan.updateName(props.computedMatch.path);
  213. activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
  214. }
  215. // @ts-expect-error Setting more specific React Component typing for `R` generic above
  216. // will break advanced type inference done by react router params:
  217. // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
  218. return React__namespace.createElement(Route, { ...props, __self: this, __source: {fileName: _jsxFileName, lineNumber: 277}} );
  219. };
  220. WrappedRoute.displayName = `sentryRoute(${componentDisplayName})`;
  221. hoistNonReactStatics__default(WrappedRoute, Route);
  222. // @ts-expect-error Setting more specific React Component typing for `R` generic above
  223. // will break advanced type inference done by react router params:
  224. // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/13dc4235c069e25fe7ee16e11f529d909f9f3ff8/types/react-router/index.d.ts#L154-L164
  225. return WrappedRoute;
  226. }
  227. /* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access */
  228. function getActiveRootSpan() {
  229. // Legacy behavior for "old" react router instrumentation
  230. if (activeTransaction) {
  231. return activeTransaction;
  232. }
  233. const span = core.getActiveSpan();
  234. const rootSpan = span ? core.getRootSpan(span) : undefined;
  235. if (!rootSpan) {
  236. return undefined;
  237. }
  238. const op = core.spanToJSON(rootSpan).op;
  239. // Only use this root span if it is a pageload or navigation span
  240. return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;
  241. }
  242. exports.reactRouterV4BrowserTracingIntegration = reactRouterV4BrowserTracingIntegration;
  243. exports.reactRouterV4Instrumentation = reactRouterV4Instrumentation;
  244. exports.reactRouterV5BrowserTracingIntegration = reactRouterV5BrowserTracingIntegration;
  245. exports.reactRouterV5Instrumentation = reactRouterV5Instrumentation;
  246. exports.withSentryRouting = withSentryRouting;
  247. //# sourceMappingURL=reactrouter.js.map