1
- import { Transaction , TransactionContext } from '@sentry/types' ;
1
+ import { Transaction } from '@sentry/types' ;
2
2
import { getGlobalObject } from '@sentry/utils' ;
3
+ import * as React from 'react' ;
3
4
4
- type ReactRouterInstrumentation = < T extends Transaction > (
5
- startTransaction : ( context : TransactionContext ) => T | undefined ,
6
- startTransactionOnPageLoad ?: boolean ,
7
- startTransactionOnLocationChange ?: boolean ,
8
- ) => void ;
5
+ import { Action , Location , ReactRouterInstrumentation } from './types' ;
9
6
10
- // Many of the types below had to be mocked out to prevent typescript issues
11
- // these types are required for correct functionality.
7
+ type Match = { path : string ; url : string ; params : Record < string , any > ; isExact : boolean } ;
12
8
13
- export type Route = { path ?: string ; childRoutes ?: Route [ ] } ;
14
-
15
- export type Match = (
16
- props : { location : Location ; routes : Route [ ] } ,
17
- cb : ( error ?: Error , _redirectLocation ?: Location , renderProps ?: { routes ?: Route [ ] } ) => void ,
18
- ) => void ;
19
-
20
- type Location = {
21
- pathname : string ;
22
- action ?: 'PUSH' | 'REPLACE' | 'POP' ;
23
- } & Record < string , any > ;
24
-
25
- type History = {
9
+ export type RouterHistory = {
26
10
location ?: Location ;
27
- listen ?( cb : ( location : Location ) => void ) : void ;
11
+ listen ?( cb : ( location : Location , action : Action ) => void ) : void ;
28
12
} & Record < string , any > ;
29
13
14
+ export type RouteConfig = {
15
+ path ?: string | string [ ] ;
16
+ exact ?: boolean ;
17
+ component ?: JSX . Element ;
18
+ routes ?: RouteConfig [ ] ;
19
+ [ propName : string ] : any ;
20
+ } ;
21
+
22
+ type MatchPath = ( pathname : string , props : string | string [ ] | any , parent ?: Match | null ) => Match | null ;
23
+
30
24
const global = getGlobalObject < Window > ( ) ;
31
25
32
- /**
33
- * Creates routing instrumentation for React Router v3
34
- * Works for React Router >= 3.2.0 and < 4.0.0
35
- *
36
- * @param history object from the `history` library
37
- * @param routes a list of all routes, should be
38
- * @param match `Router.match` utility
39
- */
40
- export function reactRouterV3Instrumentation (
41
- history : History ,
42
- routes : Route [ ] ,
43
- match : Match ,
26
+ let activeTransaction : Transaction | undefined ;
27
+
28
+ export function reactRouterV4Instrumentation (
29
+ history : RouterHistory ,
30
+ routes ?: RouteConfig [ ] ,
31
+ matchPath ?: MatchPath ,
32
+ ) : ReactRouterInstrumentation {
33
+ return reactRouterInstrumentation ( history , 'react-router-v4' , routes , matchPath ) ;
34
+ }
35
+
36
+ export function reactRouterV5Instrumentation (
37
+ history : RouterHistory ,
38
+ routes ?: RouteConfig [ ] ,
39
+ matchPath ?: MatchPath ,
44
40
) : ReactRouterInstrumentation {
45
- return (
46
- startTransaction : ( context : TransactionContext ) => Transaction | undefined ,
47
- startTransactionOnPageLoad : boolean = true ,
48
- startTransactionOnLocationChange : boolean = true ,
49
- ) => {
50
- let activeTransaction : Transaction | undefined ;
51
- let prevName : string | undefined ;
41
+ return reactRouterInstrumentation ( history , 'react-router-v5' , routes , matchPath ) ;
42
+ }
43
+
44
+ function reactRouterInstrumentation (
45
+ history : RouterHistory ,
46
+ name : string ,
47
+ allRoutes : RouteConfig [ ] = [ ] ,
48
+ matchPath ?: MatchPath ,
49
+ ) : ReactRouterInstrumentation {
50
+ function getName ( pathname : string ) : string {
51
+ if ( allRoutes === [ ] || ! matchPath ) {
52
+ return pathname ;
53
+ }
52
54
55
+ const branches = matchRoutes ( allRoutes , pathname , matchPath ) ;
56
+ // tslint:disable-next-line: prefer-for-of
57
+ for ( let x = 0 ; x < branches . length ; x ++ ) {
58
+ if ( branches [ x ] . match . isExact ) {
59
+ return branches [ x ] . match . path ;
60
+ }
61
+ }
62
+
63
+ return pathname ;
64
+ }
65
+
66
+ return ( startTransaction , startTransactionOnPageLoad = true , startTransactionOnLocationChange = true ) => {
53
67
if ( startTransactionOnPageLoad && global && global . location ) {
54
- // Have to use global.location because history.location might not be defined.
55
- prevName = normalizeTransactionName ( routes , global . location , match ) ;
56
68
activeTransaction = startTransaction ( {
57
- name : prevName ,
69
+ name : getName ( global . location . pathname ) ,
58
70
op : 'pageload' ,
59
71
tags : {
60
- 'routing.instrumentation' : 'react-router-v3' ,
72
+ 'routing.instrumentation' : name ,
61
73
} ,
62
74
} ) ;
63
75
}
64
76
65
77
if ( startTransactionOnLocationChange && history . listen ) {
66
- history . listen ( location => {
67
- if ( location . action === 'PUSH' ) {
78
+ history . listen ( ( location , action ) => {
79
+ if ( action && ( action === 'PUSH' || action === 'POP' ) ) {
68
80
if ( activeTransaction ) {
69
81
activeTransaction . finish ( ) ;
70
82
}
71
- const tags : Record < string , string > = { 'routing.instrumentation' : 'react-router-v3' } ;
72
- if ( prevName ) {
73
- tags . from = prevName ;
74
- }
83
+ const tags = {
84
+ 'routing.instrumentation' : name ,
85
+ } ;
75
86
76
- prevName = normalizeTransactionName ( routes , location , match ) ;
77
87
activeTransaction = startTransaction ( {
78
- name : prevName ,
88
+ name : getName ( location . pathname ) ,
79
89
op : 'navigation' ,
80
90
tags,
81
91
} ) ;
@@ -86,54 +96,43 @@ export function reactRouterV3Instrumentation(
86
96
}
87
97
88
98
/**
89
- * Normalize transaction names using `Router.match`
99
+ * Matches a set of routes to a pathname
100
+ * Based on implementation from
90
101
*/
91
- function normalizeTransactionName ( appRoutes : Route [ ] , location : Location , match : Match ) : string {
92
- let name = location . pathname ;
93
- match (
94
- {
95
- location,
96
- routes : appRoutes ,
97
- } ,
98
- ( error , _redirectLocation , renderProps ) => {
99
- if ( error || ! renderProps ) {
100
- return name ;
102
+ function matchRoutes (
103
+ routes : RouteConfig [ ] ,
104
+ pathname : string ,
105
+ matchPath : MatchPath ,
106
+ branch : Array < { route : RouteConfig ; match : Match } > = [ ] ,
107
+ ) : Array < { route : RouteConfig ; match : Match } > {
108
+ routes . some ( route => {
109
+ const match = route . path
110
+ ? matchPath ( pathname , route )
111
+ : branch . length
112
+ ? branch [ branch . length - 1 ] . match // use parent match
113
+ : computeRootMatch ( pathname ) ; // use default "root" match
114
+
115
+ if ( match ) {
116
+ branch . push ( { route, match } ) ;
117
+
118
+ if ( route . routes ) {
119
+ matchRoutes ( route . routes , pathname , matchPath , branch ) ;
101
120
}
121
+ }
102
122
103
- const routePath = getRouteStringFromRoutes ( renderProps . routes || [ ] ) ;
104
- if ( routePath . length === 0 || routePath === '/*' ) {
105
- return name ;
106
- }
123
+ return ! ! match ;
124
+ } ) ;
107
125
108
- name = routePath ;
109
- return name ;
110
- } ,
111
- ) ;
112
- return name ;
126
+ return branch ;
113
127
}
114
128
115
- /**
116
- * Generate route name from array of routes
117
- */
118
- function getRouteStringFromRoutes ( routes : Route [ ] ) : string {
119
- if ( ! Array . isArray ( routes ) || routes . length === 0 ) {
120
- return '' ;
121
- }
122
-
123
- const routesWithPaths : Route [ ] = routes . filter ( ( route : Route ) => ! ! route . path ) ;
129
+ function computeRootMatch ( pathname : string ) : Match {
130
+ return { path : '/' , url : '/' , params : { } , isExact : pathname === '/' } ;
131
+ }
124
132
125
- let index = - 1 ;
126
- for ( let x = routesWithPaths . length - 1 ; x >= 0 ; x -- ) {
127
- const route = routesWithPaths [ x ] ;
128
- if ( route . path && route . path . startsWith ( '/' ) ) {
129
- index = x ;
130
- break ;
131
- }
133
+ export const withSentryRouting = ( Route : React . ElementType ) => ( props : { computedMatch ?: Match } ) => {
134
+ if ( activeTransaction && props && props . computedMatch && props . computedMatch . isExact ) {
135
+ activeTransaction . setName ( props . computedMatch . path ) ;
132
136
}
133
-
134
- return routesWithPaths
135
- . slice ( index )
136
- . filter ( ( { path } ) => ! ! path )
137
- . map ( ( { path } ) => path )
138
- . join ( '' ) ;
139
- }
137
+ return < Route { ...props } /> ;
138
+ } ;
0 commit comments