1+ /* eslint-disable max-lines */ 
12// Inspired from Donnie McNeal's solution: 
23// https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 
34
4- import  {  WINDOW  }  from  '@sentry/browser' ; 
5- import  {  SEMANTIC_ATTRIBUTE_SENTRY_SOURCE  }  from  '@sentry/core' ; 
6- import  type  {  Transaction ,  TransactionContext ,  TransactionSource  }  from  '@sentry/types' ; 
5+ import  { 
6+   WINDOW , 
7+   browserTracingIntegration , 
8+   startBrowserTracingNavigationSpan , 
9+   startBrowserTracingPageLoadSpan , 
10+ }  from  '@sentry/browser' ; 
11+ import  { 
12+   SEMANTIC_ATTRIBUTE_SENTRY_OP , 
13+   SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN , 
14+   SEMANTIC_ATTRIBUTE_SENTRY_SOURCE , 
15+   getActiveSpan , 
16+   getRootSpan , 
17+   spanToJSON , 
18+ }  from  '@sentry/core' ; 
19+ import  type  { 
20+   Integration , 
21+   Span , 
22+   StartSpanOptions , 
23+   Transaction , 
24+   TransactionContext , 
25+   TransactionSource , 
26+ }  from  '@sentry/types' ; 
727import  {  getNumberOfUrlSegments ,  logger  }  from  '@sentry/utils' ; 
828import  hoistNonReactStatics  from  'hoist-non-react-statics' ; 
929import  *  as  React  from  'react' ; 
@@ -37,10 +57,77 @@ let _customStartTransaction: (context: TransactionContext) => Transaction | unde
3757let  _startTransactionOnLocationChange : boolean ; 
3858let  _stripBasename : boolean  =  false ; 
3959
40- const  SENTRY_TAGS  =  { 
41-   'routing.instrumentation' : 'react-router-v6' , 
42- } ; 
60+ interface  ReactRouterOptions  { 
61+   useEffect : UseEffect ; 
62+   useLocation : UseLocation ; 
63+   useNavigationType : UseNavigationType ; 
64+   createRoutesFromChildren : CreateRoutesFromChildren ; 
65+   matchRoutes : MatchRoutes ; 
66+   stripBasename ?: boolean ; 
67+ } 
68+ 
69+ /** 
70+  * A browser tracing integration that uses React Router v3 to instrument navigations. 
71+  * Expects `history` (and optionally `routes` and `matchPath`) to be passed as options. 
72+  */ 
73+ export  function  browserTracingReactRouterV6Integration ( 
74+   options : Parameters < typeof  browserTracingIntegration > [ 0 ]  &  ReactRouterOptions , 
75+ ) : Integration  { 
76+   const  integration  =  browserTracingIntegration ( { 
77+     ...options , 
78+     instrumentPageLoad : false , 
79+     instrumentNavigation : false , 
80+   } ) ; 
81+ 
82+   const  { 
83+     useEffect, 
84+     useLocation, 
85+     useNavigationType, 
86+     createRoutesFromChildren, 
87+     matchRoutes, 
88+     stripBasename, 
89+     instrumentPageLoad =  true , 
90+     instrumentNavigation =  true , 
91+   }  =  options ; 
92+ 
93+   return  { 
94+     ...integration , 
95+     afterAllSetup ( client )  { 
96+       integration . afterAllSetup ( client ) ; 
97+ 
98+       const  startNavigationCallback  =  ( startSpanOptions : StartSpanOptions ) : undefined  =>  { 
99+         startBrowserTracingNavigationSpan ( client ,  startSpanOptions ) ; 
100+         return  undefined ; 
101+       } ; 
102+ 
103+       const  initPathName  =  WINDOW  &&  WINDOW . location  &&  WINDOW . location . pathname ; 
104+       if  ( instrumentPageLoad  &&  initPathName )  { 
105+         startBrowserTracingPageLoadSpan ( client ,  { 
106+           name : initPathName , 
107+           attributes : { 
108+             [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' , 
109+             [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' , 
110+             [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.react.reactrouter_v6' , 
111+           } , 
112+         } ) ; 
113+       } 
43114
115+       _useEffect  =  useEffect ; 
116+       _useLocation  =  useLocation ; 
117+       _useNavigationType  =  useNavigationType ; 
118+       _matchRoutes  =  matchRoutes ; 
119+       _createRoutesFromChildren  =  createRoutesFromChildren ; 
120+       _stripBasename  =  stripBasename  ||  false ; 
121+ 
122+       _customStartTransaction  =  startNavigationCallback ; 
123+       _startTransactionOnLocationChange  =  instrumentNavigation ; 
124+     } , 
125+   } ; 
126+ } 
127+ 
128+ /** 
129+  * @deprecated  Use `browserTracingReactRouterV6Integration()` instead. 
130+  */ 
44131export  function  reactRouterV6Instrumentation ( 
45132  useEffect : UseEffect , 
46133  useLocation : UseLocation , 
@@ -58,11 +145,10 @@ export function reactRouterV6Instrumentation(
58145    if  ( startTransactionOnPageLoad  &&  initPathName )  { 
59146      activeTransaction  =  customStartTransaction ( { 
60147        name : initPathName , 
61-         op : 'pageload' , 
62-         origin : 'auto.pageload.react.reactrouterv6' , 
63-         tags : SENTRY_TAGS , 
64-         metadata : { 
65-           source : 'url' , 
148+         attributes : { 
149+           [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'url' , 
150+           [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' , 
151+           [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.react.reactrouter_v6' , 
66152        } , 
67153      } ) ; 
68154    } 
@@ -155,6 +241,7 @@ function getNormalizedName(
155241} 
156242
157243function  updatePageloadTransaction ( 
244+   activeRootSpan : Span  |  undefined , 
158245  location : Location , 
159246  routes : RouteObject [ ] , 
160247  matches ?: AgnosticDataRouteMatch , 
@@ -164,10 +251,10 @@ function updatePageloadTransaction(
164251    ? matches 
165252    : ( _matchRoutes ( routes ,  location ,  basename )  as  unknown  as  RouteMatch [ ] ) ; 
166253
167-   if  ( activeTransaction  &&  branches )  { 
254+   if  ( activeRootSpan  &&  branches )  { 
168255    const  [ name ,  source ]  =  getNormalizedName ( routes ,  location ,  branches ,  basename ) ; 
169-     activeTransaction . updateName ( name ) ; 
170-     activeTransaction . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,  source ) ; 
256+     activeRootSpan . updateName ( name ) ; 
257+     activeRootSpan . setAttribute ( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,  source ) ; 
171258  } 
172259} 
173260
@@ -188,11 +275,10 @@ function handleNavigation(
188275    const  [ name ,  source ]  =  getNormalizedName ( routes ,  location ,  branches ,  basename ) ; 
189276    activeTransaction  =  _customStartTransaction ( { 
190277      name, 
191-       op : 'navigation' , 
192-       origin : 'auto.navigation.react.reactrouterv6' , 
193-       tags : SENTRY_TAGS , 
194-       metadata : { 
195-         source, 
278+       attributes : { 
279+         [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : source , 
280+         [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' , 
281+         [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.react.reactrouter_v6' , 
196282      } , 
197283    } ) ; 
198284  } 
@@ -227,7 +313,7 @@ export function withSentryReactRouterV6Routing<P extends Record<string, any>, R
227313        const  routes  =  _createRoutesFromChildren ( props . children )  as  RouteObject [ ] ; 
228314
229315        if  ( isMountRenderPass )  { 
230-           updatePageloadTransaction ( location ,  routes ) ; 
316+           updatePageloadTransaction ( getActiveRootSpan ( ) ,   location ,  routes ) ; 
231317          isMountRenderPass  =  false ; 
232318        }  else  { 
233319          handleNavigation ( location ,  routes ,  navigationType ) ; 
@@ -285,7 +371,7 @@ export function wrapUseRoutes(origUseRoutes: UseRoutes): UseRoutes {
285371        typeof  stableLocationParam  ===  'string'  ? {  pathname : stableLocationParam  }  : stableLocationParam ; 
286372
287373      if  ( isMountRenderPass )  { 
288-         updatePageloadTransaction ( normalizedLocation ,  routes ) ; 
374+         updatePageloadTransaction ( getActiveRootSpan ( ) ,   normalizedLocation ,  routes ) ; 
289375        isMountRenderPass  =  false ; 
290376      }  else  { 
291377        handleNavigation ( normalizedLocation ,  routes ,  navigationType ) ; 
@@ -312,25 +398,41 @@ export function wrapCreateBrowserRouter<
312398    const  router  =  createRouterFunction ( routes ,  opts ) ; 
313399    const  basename  =  opts  &&  opts . basename ; 
314400
401+     const  activeRootSpan  =  getActiveRootSpan ( ) ; 
402+ 
315403    // The initial load ends when `createBrowserRouter` is called. 
316404    // This is the earliest convenient time to update the transaction name. 
317405    // Callbacks to `router.subscribe` are not called for the initial load. 
318-     if  ( router . state . historyAction  ===  'POP'  &&  activeTransaction )  { 
319-       updatePageloadTransaction ( router . state . location ,  routes ,  undefined ,  basename ) ; 
406+     if  ( router . state . historyAction  ===  'POP'  &&  activeRootSpan )  { 
407+       updatePageloadTransaction ( activeRootSpan ,   router . state . location ,  routes ,  undefined ,  basename ) ; 
320408    } 
321409
322410    router . subscribe ( ( state : RouterState )  =>  { 
323411      const  location  =  state . location ; 
324- 
325-       if  ( 
326-         _startTransactionOnLocationChange  && 
327-         ( state . historyAction  ===  'PUSH'  ||  state . historyAction  ===  'POP' )  && 
328-         activeTransaction 
329-       )  { 
412+       if  ( _startTransactionOnLocationChange  &&  ( state . historyAction  ===  'PUSH'  ||  state . historyAction  ===  'POP' ) )  { 
330413        handleNavigation ( location ,  routes ,  state . historyAction ,  undefined ,  basename ) ; 
331414      } 
332415    } ) ; 
333416
334417    return  router ; 
335418  } ; 
336419} 
420+ 
421+ function  getActiveRootSpan ( ) : Span  |  undefined  { 
422+   // Legacy behavior for "old" react router instrumentation 
423+   if  ( activeTransaction )  { 
424+     return  activeTransaction ; 
425+   } 
426+ 
427+   const  span  =  getActiveSpan ( ) ; 
428+   const  rootSpan  =  span  ? getRootSpan ( span )  : undefined ; 
429+ 
430+   if  ( ! rootSpan )  { 
431+     return  undefined ; 
432+   } 
433+ 
434+   const  op  =  spanToJSON ( rootSpan ) . op ; 
435+ 
436+   // Only use this root span if it is a pageload or navigation span 
437+   return  op  ===  'navigation'  ||  op  ===  'pageload'  ? rootSpan  : undefined ; 
438+ } 
0 commit comments