1
|
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); |
2
|
|
|
|
3
|
|
|
const browser = require('@sentry/browser'); |
4
|
|
|
const core = require('@sentry/core'); |
5
|
|
|
const React = require('react'); |
6
|
|
|
const debugBuild = require('../debug-build.js'); |
7
|
|
|
const hoistNonReactStatics = require('../hoist-non-react-statics.js'); |
8
|
|
|
const lazyRoutes = require('./lazy-routes.js'); |
9
|
|
|
const utils = require('./utils.js'); |
10
|
|
|
|
11
|
|
|
/* eslint-disable max-lines */ |
12
|
|
|
// Inspired from Donnie McNeal's solution: |
13
|
|
|
// https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536 |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
let _useEffect; |
17
|
|
|
let _useLocation; |
18
|
|
|
let _useNavigationType; |
19
|
|
|
let _createRoutesFromChildren; |
20
|
|
|
let _matchRoutes; |
21
|
|
|
let _enableAsyncRouteHandlers = false; |
22
|
|
|
|
23
|
|
|
const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet(); |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Adds resolved routes as children to the parent route. |
27
|
|
|
* Prevents duplicate routes by checking if they already exist. |
28
|
|
|
*/ |
29
|
|
|
function addResolvedRoutesToParent(resolvedRoutes, parentRoute) { |
30
|
|
|
const existingChildren = parentRoute.children || []; |
31
|
|
|
|
32
|
|
|
const newRoutes = resolvedRoutes.filter( |
33
|
|
|
newRoute => |
34
|
|
|
!existingChildren.some( |
35
|
|
|
existing => |
36
|
|
|
existing === newRoute || |
37
|
|
|
(newRoute.path && existing.path === newRoute.path) || |
38
|
|
|
(newRoute.id && existing.id === newRoute.id), |
39
|
|
|
), |
40
|
|
|
); |
41
|
|
|
|
42
|
|
|
if (newRoutes.length > 0) { |
43
|
|
|
parentRoute.children = [...existingChildren, ...newRoutes]; |
44
|
|
|
} |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
// Keeping as a global variable for cross-usage in multiple functions |
48
|
|
|
const allRoutes = new Set(); |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Processes resolved routes by adding them to allRoutes and checking for nested async handlers. |
52
|
|
|
*/ |
53
|
|
|
function processResolvedRoutes( |
54
|
|
|
resolvedRoutes, |
55
|
|
|
parentRoute, |
56
|
|
|
currentLocation = null, |
57
|
|
|
) { |
58
|
|
|
resolvedRoutes.forEach(child => { |
59
|
|
|
allRoutes.add(child); |
60
|
|
|
// Only check for async handlers if the feature is enabled |
61
|
|
|
if (_enableAsyncRouteHandlers) { |
62
|
|
|
lazyRoutes.checkRouteForAsyncHandler(child, processResolvedRoutes); |
63
|
|
|
} |
64
|
|
|
}); |
65
|
|
|
|
66
|
|
|
if (parentRoute) { |
67
|
|
|
// If a parent route is provided, add the resolved routes as children to the parent route |
68
|
|
|
addResolvedRoutesToParent(resolvedRoutes, parentRoute); |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
// After processing lazy routes, check if we need to update an active transaction |
72
|
|
|
const activeRootSpan = getActiveRootSpan(); |
73
|
|
|
if (activeRootSpan) { |
74
|
|
|
const spanOp = core.spanToJSON(activeRootSpan).op; |
75
|
|
|
|
76
|
|
|
// Try to use the provided location first, then fall back to global window location if needed |
77
|
|
|
let location = currentLocation; |
78
|
|
|
if (!location) { |
79
|
|
|
if (typeof browser.WINDOW !== 'undefined') { |
80
|
|
|
const globalLocation = browser.WINDOW.location; |
81
|
|
|
if (globalLocation) { |
82
|
|
|
location = { pathname: globalLocation.pathname }; |
83
|
|
|
} |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
if (location) { |
88
|
|
|
if (spanOp === 'pageload') { |
89
|
|
|
// Re-run the pageload transaction update with the newly loaded routes |
90
|
|
|
updatePageloadTransaction({ |
91
|
|
|
activeRootSpan, |
92
|
|
|
location: { pathname: location.pathname }, |
93
|
|
|
routes: Array.from(allRoutes), |
94
|
|
|
allRoutes: Array.from(allRoutes), |
95
|
|
|
}); |
96
|
|
|
} else if (spanOp === 'navigation') { |
97
|
|
|
// For navigation spans, update the name with the newly loaded routes |
98
|
|
|
updateNavigationSpan(activeRootSpan, location, Array.from(allRoutes), false, _matchRoutes); |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
} |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Updates a navigation span with the correct route name after lazy routes have been loaded. |
106
|
|
|
*/ |
107
|
|
|
function updateNavigationSpan( |
108
|
|
|
activeRootSpan, |
109
|
|
|
location, |
110
|
|
|
allRoutes, |
111
|
|
|
forceUpdate = false, |
112
|
|
|
matchRoutes, |
113
|
|
|
) { |
114
|
|
|
// Check if this span has already been named to avoid multiple updates |
115
|
|
|
// But allow updates if this is a forced update (e.g., when lazy routes are loaded) |
116
|
|
|
const hasBeenNamed = |
117
|
|
|
!forceUpdate && |
118
|
|
|
( |
119
|
|
|
activeRootSpan |
120
|
|
|
|
121
|
|
|
)?.__sentry_navigation_name_set__; |
122
|
|
|
|
123
|
|
|
if (!hasBeenNamed) { |
124
|
|
|
// Get fresh branches for the current location with all loaded routes |
125
|
|
|
const currentBranches = matchRoutes(allRoutes, location); |
126
|
|
|
const [name, source] = utils.resolveRouteNameAndSource( |
127
|
|
|
location, |
128
|
|
|
allRoutes, |
129
|
|
|
allRoutes, |
130
|
|
|
(currentBranches ) || [], |
131
|
|
|
'', |
132
|
|
|
); |
133
|
|
|
|
134
|
|
|
// Only update if we have a valid name and the span hasn't finished |
135
|
|
|
const spanJson = core.spanToJSON(activeRootSpan); |
136
|
|
|
if (name && !spanJson.timestamp) { |
137
|
|
|
activeRootSpan.updateName(name); |
138
|
|
|
activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); |
139
|
|
|
|
140
|
|
|
// Mark this span as having its name set to prevent future updates |
141
|
|
|
core.addNonEnumerableProperty( |
142
|
|
|
activeRootSpan , |
143
|
|
|
'__sentry_navigation_name_set__', |
144
|
|
|
true, |
145
|
|
|
); |
146
|
|
|
} |
147
|
|
|
} |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions. |
152
|
|
|
*/ |
153
|
|
|
function createV6CompatibleWrapCreateBrowserRouter |
154
|
|
|
|
155
|
|
|
( |
156
|
|
|
createRouterFunction, |
157
|
|
|
version, |
158
|
|
|
) { |
159
|
|
|
if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) { |
160
|
|
|
debugBuild.DEBUG_BUILD && |
161
|
|
|
core.debug.warn( |
162
|
|
|
`reactRouterV${version}Instrumentation was unable to wrap the \`createRouter\` function because of one or more missing parameters.`, |
163
|
|
|
); |
164
|
|
|
|
165
|
|
|
return createRouterFunction; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return function (routes, opts) { |
169
|
|
|
addRoutesToAllRoutes(routes); |
170
|
|
|
|
171
|
|
|
// Check for async handlers that might contain sub-route declarations (only if enabled) |
172
|
|
|
if (_enableAsyncRouteHandlers) { |
173
|
|
|
for (const route of routes) { |
174
|
|
|
lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes); |
175
|
|
|
} |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
// Wrap patchRoutesOnNavigation to detect when lazy routes are loaded |
179
|
|
|
const wrappedOpts = wrapPatchRoutesOnNavigation(opts); |
180
|
|
|
|
181
|
|
|
const router = createRouterFunction(routes, wrappedOpts); |
182
|
|
|
const basename = opts?.basename; |
183
|
|
|
|
184
|
|
|
const activeRootSpan = getActiveRootSpan(); |
185
|
|
|
|
186
|
|
|
// The initial load ends when `createBrowserRouter` is called. |
187
|
|
|
// This is the earliest convenient time to update the transaction name. |
188
|
|
|
// Callbacks to `router.subscribe` are not called for the initial load. |
189
|
|
|
if (router.state.historyAction === 'POP' && activeRootSpan) { |
190
|
|
|
updatePageloadTransaction({ |
191
|
|
|
activeRootSpan, |
192
|
|
|
location: router.state.location, |
193
|
|
|
routes, |
194
|
|
|
basename, |
195
|
|
|
allRoutes: Array.from(allRoutes), |
196
|
|
|
}); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
router.subscribe((state) => { |
200
|
|
|
if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { |
201
|
|
|
// Wait for the next render if loading an unsettled route |
202
|
|
|
if (state.navigation.state !== 'idle') { |
203
|
|
|
requestAnimationFrame(() => { |
204
|
|
|
handleNavigation({ |
205
|
|
|
location: state.location, |
206
|
|
|
routes, |
207
|
|
|
navigationType: state.historyAction, |
208
|
|
|
version, |
209
|
|
|
basename, |
210
|
|
|
allRoutes: Array.from(allRoutes), |
211
|
|
|
}); |
212
|
|
|
}); |
213
|
|
|
} else { |
214
|
|
|
handleNavigation({ |
215
|
|
|
location: state.location, |
216
|
|
|
routes, |
217
|
|
|
navigationType: state.historyAction, |
218
|
|
|
version, |
219
|
|
|
basename, |
220
|
|
|
allRoutes: Array.from(allRoutes), |
221
|
|
|
}); |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
}); |
225
|
|
|
|
226
|
|
|
return router; |
227
|
|
|
}; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* Creates a wrapCreateMemoryRouter function that can be used with all React Router v6 compatible versions. |
232
|
|
|
*/ |
233
|
|
|
function createV6CompatibleWrapCreateMemoryRouter |
234
|
|
|
|
235
|
|
|
( |
236
|
|
|
createRouterFunction, |
237
|
|
|
version, |
238
|
|
|
) { |
239
|
|
|
if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) { |
240
|
|
|
debugBuild.DEBUG_BUILD && |
241
|
|
|
core.debug.warn( |
242
|
|
|
`reactRouterV${version}Instrumentation was unable to wrap the \`createMemoryRouter\` function because of one or more missing parameters.`, |
243
|
|
|
); |
244
|
|
|
|
245
|
|
|
return createRouterFunction; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
return function ( |
249
|
|
|
routes, |
250
|
|
|
opts |
251
|
|
|
|
252
|
|
|
, |
253
|
|
|
) { |
254
|
|
|
addRoutesToAllRoutes(routes); |
255
|
|
|
|
256
|
|
|
// Check for async handlers that might contain sub-route declarations (only if enabled) |
257
|
|
|
if (_enableAsyncRouteHandlers) { |
258
|
|
|
for (const route of routes) { |
259
|
|
|
lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes); |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
// Wrap patchRoutesOnNavigation to detect when lazy routes are loaded |
264
|
|
|
const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true); |
265
|
|
|
|
266
|
|
|
const router = createRouterFunction(routes, wrappedOpts); |
267
|
|
|
const basename = opts?.basename; |
268
|
|
|
|
269
|
|
|
const activeRootSpan = getActiveRootSpan(); |
270
|
|
|
let initialEntry = undefined; |
271
|
|
|
|
272
|
|
|
const initialEntries = opts?.initialEntries; |
273
|
|
|
const initialIndex = opts?.initialIndex; |
274
|
|
|
|
275
|
|
|
const hasOnlyOneInitialEntry = initialEntries && initialEntries.length === 1; |
276
|
|
|
const hasIndexedEntry = initialIndex !== undefined && initialEntries && initialEntries[initialIndex]; |
277
|
|
|
|
278
|
|
|
initialEntry = hasOnlyOneInitialEntry |
279
|
|
|
? initialEntries[0] |
280
|
|
|
: hasIndexedEntry |
281
|
|
|
? initialEntries[initialIndex] |
282
|
|
|
: undefined; |
283
|
|
|
|
284
|
|
|
const location = initialEntry |
285
|
|
|
? typeof initialEntry === 'string' |
286
|
|
|
? { pathname: initialEntry } |
287
|
|
|
: initialEntry |
288
|
|
|
: router.state.location; |
289
|
|
|
|
290
|
|
|
if (router.state.historyAction === 'POP' && activeRootSpan) { |
291
|
|
|
updatePageloadTransaction({ |
292
|
|
|
activeRootSpan, |
293
|
|
|
location, |
294
|
|
|
routes, |
295
|
|
|
basename, |
296
|
|
|
allRoutes: Array.from(allRoutes), |
297
|
|
|
}); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
router.subscribe((state) => { |
301
|
|
|
const location = state.location; |
302
|
|
|
if (state.historyAction === 'PUSH' || state.historyAction === 'POP') { |
303
|
|
|
handleNavigation({ |
304
|
|
|
location, |
305
|
|
|
routes, |
306
|
|
|
navigationType: state.historyAction, |
307
|
|
|
version, |
308
|
|
|
basename, |
309
|
|
|
allRoutes: Array.from(allRoutes), |
310
|
|
|
}); |
311
|
|
|
} |
312
|
|
|
}); |
313
|
|
|
|
314
|
|
|
return router; |
315
|
|
|
}; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* Creates a browser tracing integration that can be used with all React Router v6 compatible versions. |
320
|
|
|
*/ |
321
|
|
|
function createReactRouterV6CompatibleTracingIntegration( |
322
|
|
|
options, |
323
|
|
|
version, |
324
|
|
|
) { |
325
|
|
|
const integration = browser.browserTracingIntegration({ |
326
|
|
|
...options, |
327
|
|
|
instrumentPageLoad: false, |
328
|
|
|
instrumentNavigation: false, |
329
|
|
|
}); |
330
|
|
|
|
331
|
|
|
const { |
332
|
|
|
useEffect, |
333
|
|
|
useLocation, |
334
|
|
|
useNavigationType, |
335
|
|
|
createRoutesFromChildren, |
336
|
|
|
matchRoutes, |
337
|
|
|
stripBasename, |
338
|
|
|
enableAsyncRouteHandlers = false, |
339
|
|
|
instrumentPageLoad = true, |
340
|
|
|
instrumentNavigation = true, |
341
|
|
|
} = options; |
342
|
|
|
|
343
|
|
|
return { |
344
|
|
|
...integration, |
345
|
|
|
setup(client) { |
346
|
|
|
integration.setup(client); |
347
|
|
|
|
348
|
|
|
_useEffect = useEffect; |
349
|
|
|
_useLocation = useLocation; |
350
|
|
|
_useNavigationType = useNavigationType; |
351
|
|
|
_matchRoutes = matchRoutes; |
352
|
|
|
_createRoutesFromChildren = createRoutesFromChildren; |
353
|
|
|
_enableAsyncRouteHandlers = enableAsyncRouteHandlers; |
354
|
|
|
|
355
|
|
|
// Initialize the router utils with the required dependencies |
356
|
|
|
utils.initializeRouterUtils(matchRoutes, stripBasename || false); |
357
|
|
|
}, |
358
|
|
|
afterAllSetup(client) { |
359
|
|
|
integration.afterAllSetup(client); |
360
|
|
|
|
361
|
|
|
const initPathName = browser.WINDOW.location?.pathname; |
362
|
|
|
if (instrumentPageLoad && initPathName) { |
363
|
|
|
browser.startBrowserTracingPageLoadSpan(client, { |
364
|
|
|
name: initPathName, |
365
|
|
|
attributes: { |
366
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', |
367
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload', |
368
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.reactrouter_v${version}`, |
369
|
|
|
}, |
370
|
|
|
}); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
if (instrumentNavigation) { |
374
|
|
|
CLIENTS_WITH_INSTRUMENT_NAVIGATION.add(client); |
375
|
|
|
} |
376
|
|
|
}, |
377
|
|
|
}; |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
function createV6CompatibleWrapUseRoutes(origUseRoutes, version) { |
381
|
|
|
if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) { |
382
|
|
|
debugBuild.DEBUG_BUILD && |
383
|
|
|
core.debug.warn( |
384
|
|
|
'reactRouterV6Instrumentation was unable to wrap `useRoutes` because of one or more missing parameters.', |
385
|
|
|
); |
386
|
|
|
|
387
|
|
|
return origUseRoutes; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
const SentryRoutes |
391
|
|
|
|
392
|
|
|
= (props) => { |
393
|
|
|
const isMountRenderPass = React.useRef(true); |
394
|
|
|
const { routes, locationArg } = props; |
395
|
|
|
|
396
|
|
|
const Routes = origUseRoutes(routes, locationArg); |
397
|
|
|
|
398
|
|
|
const location = _useLocation(); |
399
|
|
|
const navigationType = _useNavigationType(); |
400
|
|
|
|
401
|
|
|
// A value with stable identity to either pick `locationArg` if available or `location` if not |
402
|
|
|
const stableLocationParam = |
403
|
|
|
typeof locationArg === 'string' || locationArg?.pathname ? (locationArg ) : location; |
404
|
|
|
|
405
|
|
|
_useEffect(() => { |
406
|
|
|
const normalizedLocation = |
407
|
|
|
typeof stableLocationParam === 'string' ? { pathname: stableLocationParam } : stableLocationParam; |
408
|
|
|
|
409
|
|
|
if (isMountRenderPass.current) { |
410
|
|
|
addRoutesToAllRoutes(routes); |
411
|
|
|
|
412
|
|
|
updatePageloadTransaction({ |
413
|
|
|
activeRootSpan: getActiveRootSpan(), |
414
|
|
|
location: normalizedLocation, |
415
|
|
|
routes, |
416
|
|
|
allRoutes: Array.from(allRoutes), |
417
|
|
|
}); |
418
|
|
|
isMountRenderPass.current = false; |
419
|
|
|
} else { |
420
|
|
|
handleNavigation({ |
421
|
|
|
location: normalizedLocation, |
422
|
|
|
routes, |
423
|
|
|
navigationType, |
424
|
|
|
version, |
425
|
|
|
allRoutes: Array.from(allRoutes), |
426
|
|
|
}); |
427
|
|
|
} |
428
|
|
|
}, [navigationType, stableLocationParam]); |
429
|
|
|
|
430
|
|
|
return Routes; |
431
|
|
|
}; |
432
|
|
|
|
433
|
|
|
// eslint-disable-next-line react/display-name |
434
|
|
|
return (routes, locationArg) => { |
435
|
|
|
return React.createElement(SentryRoutes, { routes: routes, locationArg: locationArg,} ); |
436
|
|
|
}; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
function wrapPatchRoutesOnNavigation( |
440
|
|
|
opts, |
441
|
|
|
isMemoryRouter = false, |
442
|
|
|
) { |
443
|
|
|
if (!opts || !('patchRoutesOnNavigation' in opts) || typeof opts.patchRoutesOnNavigation !== 'function') { |
444
|
|
|
return opts || {}; |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
const originalPatchRoutes = opts.patchRoutesOnNavigation; |
448
|
|
|
return { |
449
|
|
|
...opts, |
450
|
|
|
patchRoutesOnNavigation: async (args) => { |
451
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access |
452
|
|
|
const targetPath = (args )?.path; |
453
|
|
|
|
454
|
|
|
// For browser router, wrap the patch function to update span during patching |
455
|
|
|
if (!isMemoryRouter) { |
456
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access |
457
|
|
|
const originalPatch = (args )?.patch; |
458
|
|
|
if (originalPatch) { |
459
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access |
460
|
|
|
(args ).patch = (routeId, children) => { |
461
|
|
|
addRoutesToAllRoutes(children); |
462
|
|
|
const activeRootSpan = getActiveRootSpan(); |
463
|
|
|
if (activeRootSpan && (core.spanToJSON(activeRootSpan) ).op === 'navigation') { |
464
|
|
|
updateNavigationSpan( |
465
|
|
|
activeRootSpan, |
466
|
|
|
{ |
467
|
|
|
pathname: targetPath, |
468
|
|
|
search: '', |
469
|
|
|
hash: '', |
470
|
|
|
state: null, |
471
|
|
|
key: 'default', |
472
|
|
|
}, |
473
|
|
|
Array.from(allRoutes), |
474
|
|
|
true, // forceUpdate = true since we're loading lazy routes |
475
|
|
|
_matchRoutes, |
476
|
|
|
); |
477
|
|
|
} |
478
|
|
|
return originalPatch(routeId, children); |
479
|
|
|
}; |
480
|
|
|
} |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
const result = await originalPatchRoutes(args); |
484
|
|
|
|
485
|
|
|
// Update navigation span after routes are patched |
486
|
|
|
const activeRootSpan = getActiveRootSpan(); |
487
|
|
|
if (activeRootSpan && (core.spanToJSON(activeRootSpan) ).op === 'navigation') { |
488
|
|
|
// For memory routers, we should not access window.location; use targetPath only |
489
|
|
|
const pathname = isMemoryRouter ? targetPath : targetPath || browser.WINDOW.location?.pathname; |
490
|
|
|
if (pathname) { |
491
|
|
|
updateNavigationSpan( |
492
|
|
|
activeRootSpan, |
493
|
|
|
{ |
494
|
|
|
pathname, |
495
|
|
|
search: '', |
496
|
|
|
hash: '', |
497
|
|
|
state: null, |
498
|
|
|
key: 'default', |
499
|
|
|
}, |
500
|
|
|
Array.from(allRoutes), |
501
|
|
|
false, // forceUpdate = false since this is after lazy routes are loaded |
502
|
|
|
_matchRoutes, |
503
|
|
|
); |
504
|
|
|
} |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
return result; |
508
|
|
|
}, |
509
|
|
|
}; |
510
|
|
|
} |
511
|
|
|
|
512
|
|
|
function handleNavigation(opts |
513
|
|
|
|
514
|
|
|
) { |
515
|
|
|
const { location, routes, navigationType, version, matches, basename, allRoutes } = opts; |
516
|
|
|
const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename); |
517
|
|
|
|
518
|
|
|
const client = core.getClient(); |
519
|
|
|
if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) { |
520
|
|
|
return; |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) { |
524
|
|
|
const [name, source] = utils.resolveRouteNameAndSource( |
525
|
|
|
location, |
526
|
|
|
routes, |
527
|
|
|
allRoutes || routes, |
528
|
|
|
branches , |
529
|
|
|
basename, |
530
|
|
|
); |
531
|
|
|
|
532
|
|
|
// Check if this might be a lazy route context |
533
|
|
|
const isLazyRouteContext = utils.isLikelyLazyRouteContext(allRoutes || routes, location); |
534
|
|
|
|
535
|
|
|
const activeSpan = core.getActiveSpan(); |
536
|
|
|
const spanJson = activeSpan && core.spanToJSON(activeSpan); |
537
|
|
|
const isAlreadyInNavigationSpan = spanJson?.op === 'navigation'; |
538
|
|
|
|
539
|
|
|
// Cross usage can result in multiple navigation spans being created without this check |
540
|
|
|
if (isAlreadyInNavigationSpan && activeSpan && spanJson) { |
541
|
|
|
handleExistingNavigationSpan(activeSpan, spanJson, name, source, isLazyRouteContext); |
542
|
|
|
} else { |
543
|
|
|
createNewNavigationSpan(client, name, source, version, isLazyRouteContext); |
544
|
|
|
} |
545
|
|
|
} |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
function addRoutesToAllRoutes(routes) { |
549
|
|
|
routes.forEach(route => { |
550
|
|
|
const extractedChildRoutes = getChildRoutesRecursively(route); |
551
|
|
|
|
552
|
|
|
extractedChildRoutes.forEach(r => { |
553
|
|
|
allRoutes.add(r); |
554
|
|
|
}); |
555
|
|
|
}); |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
function getChildRoutesRecursively(route, allRoutes = new Set()) { |
559
|
|
|
if (!allRoutes.has(route)) { |
560
|
|
|
allRoutes.add(route); |
561
|
|
|
|
562
|
|
|
if (route.children && !route.index) { |
563
|
|
|
route.children.forEach(child => { |
564
|
|
|
const childRoutes = getChildRoutesRecursively(child, allRoutes); |
565
|
|
|
|
566
|
|
|
childRoutes.forEach(r => { |
567
|
|
|
allRoutes.add(r); |
568
|
|
|
}); |
569
|
|
|
}); |
570
|
|
|
} |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
return allRoutes; |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
function updatePageloadTransaction({ |
577
|
|
|
activeRootSpan, |
578
|
|
|
location, |
579
|
|
|
routes, |
580
|
|
|
matches, |
581
|
|
|
basename, |
582
|
|
|
allRoutes, |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
) { |
586
|
|
|
const branches = Array.isArray(matches) |
587
|
|
|
? matches |
588
|
|
|
: (_matchRoutes(allRoutes || routes, location, basename) ); |
589
|
|
|
|
590
|
|
|
if (branches) { |
591
|
|
|
let name, |
592
|
|
|
source = 'url'; |
593
|
|
|
|
594
|
|
|
const isInDescendantRoute = utils.locationIsInsideDescendantRoute(location, allRoutes || routes); |
595
|
|
|
|
596
|
|
|
if (isInDescendantRoute) { |
597
|
|
|
name = utils.prefixWithSlash(utils.rebuildRoutePathFromAllRoutes(allRoutes || routes, location)); |
598
|
|
|
source = 'route'; |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
if (!isInDescendantRoute || !name) { |
602
|
|
|
[name, source] = utils.getNormalizedName(routes, location, branches, basename); |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
core.getCurrentScope().setTransactionName(name || '/'); |
606
|
|
|
|
607
|
|
|
if (activeRootSpan) { |
608
|
|
|
activeRootSpan.updateName(name); |
609
|
|
|
activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); |
610
|
|
|
} |
611
|
|
|
} |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any |
615
|
|
|
function createV6CompatibleWithSentryReactRouterRouting( |
616
|
|
|
Routes, |
617
|
|
|
version, |
618
|
|
|
) { |
619
|
|
|
if (!_useEffect || !_useLocation || !_useNavigationType || !_createRoutesFromChildren || !_matchRoutes) { |
620
|
|
|
debugBuild.DEBUG_BUILD && |
621
|
|
|
core.debug.warn(`reactRouterV6Instrumentation was unable to wrap Routes because of one or more missing parameters. |
622
|
|
|
useEffect: ${_useEffect}. useLocation: ${_useLocation}. useNavigationType: ${_useNavigationType}. |
623
|
|
|
createRoutesFromChildren: ${_createRoutesFromChildren}. matchRoutes: ${_matchRoutes}.`); |
624
|
|
|
|
625
|
|
|
return Routes; |
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
const SentryRoutes = (props) => { |
629
|
|
|
const isMountRenderPass = React.useRef(true); |
630
|
|
|
|
631
|
|
|
const location = _useLocation(); |
632
|
|
|
const navigationType = _useNavigationType(); |
633
|
|
|
|
634
|
|
|
_useEffect( |
635
|
|
|
() => { |
636
|
|
|
const routes = _createRoutesFromChildren(props.children) ; |
637
|
|
|
|
638
|
|
|
if (isMountRenderPass.current) { |
639
|
|
|
addRoutesToAllRoutes(routes); |
640
|
|
|
|
641
|
|
|
updatePageloadTransaction({ |
642
|
|
|
activeRootSpan: getActiveRootSpan(), |
643
|
|
|
location, |
644
|
|
|
routes, |
645
|
|
|
allRoutes: Array.from(allRoutes), |
646
|
|
|
}); |
647
|
|
|
isMountRenderPass.current = false; |
648
|
|
|
} else { |
649
|
|
|
handleNavigation({ |
650
|
|
|
location, |
651
|
|
|
routes, |
652
|
|
|
navigationType, |
653
|
|
|
version, |
654
|
|
|
allRoutes: Array.from(allRoutes), |
655
|
|
|
}); |
656
|
|
|
} |
657
|
|
|
}, |
658
|
|
|
// `props.children` is purposely not included in the dependency array, because we do not want to re-run this effect |
659
|
|
|
// when the children change. We only want to start transactions when the location or navigation type change. |
660
|
|
|
[location, navigationType], |
661
|
|
|
); |
662
|
|
|
|
663
|
|
|
// @ts-expect-error Setting more specific React Component typing for `R` generic above |
664
|
|
|
// will break advanced type inference done by react router params |
665
|
|
|
return React.createElement(Routes, { ...props,} ); |
666
|
|
|
}; |
667
|
|
|
|
668
|
|
|
hoistNonReactStatics.hoistNonReactStatics(SentryRoutes, Routes); |
669
|
|
|
|
670
|
|
|
// @ts-expect-error Setting more specific React Component typing for `R` generic above |
671
|
|
|
// will break advanced type inference done by react router params |
672
|
|
|
return SentryRoutes; |
673
|
|
|
} |
674
|
|
|
|
675
|
|
|
function getActiveRootSpan() { |
676
|
|
|
const span = core.getActiveSpan(); |
677
|
|
|
const rootSpan = span ? core.getRootSpan(span) : undefined; |
678
|
|
|
|
679
|
|
|
if (!rootSpan) { |
680
|
|
|
return undefined; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
const op = core.spanToJSON(rootSpan).op; |
684
|
|
|
|
685
|
|
|
// Only use this root span if it is a pageload or navigation span |
686
|
|
|
return op === 'navigation' || op === 'pageload' ? rootSpan : undefined; |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
/** |
690
|
|
|
* Handles updating an existing navigation span |
691
|
|
|
*/ |
692
|
|
|
function handleExistingNavigationSpan( |
693
|
|
|
activeSpan, |
694
|
|
|
spanJson, |
695
|
|
|
name, |
696
|
|
|
source, |
697
|
|
|
isLikelyLazyRoute, |
698
|
|
|
) { |
699
|
|
|
// Check if we've already set the name for this span using a custom property |
700
|
|
|
const hasBeenNamed = ( |
701
|
|
|
activeSpan |
702
|
|
|
|
703
|
|
|
)?.__sentry_navigation_name_set__; |
704
|
|
|
|
705
|
|
|
if (!hasBeenNamed) { |
706
|
|
|
// This is the first time we're setting the name for this span |
707
|
|
|
if (!spanJson.timestamp) { |
708
|
|
|
activeSpan?.updateName(name); |
709
|
|
|
} |
710
|
|
|
|
711
|
|
|
// For lazy routes, don't mark as named yet so it can be updated later |
712
|
|
|
if (!isLikelyLazyRoute) { |
713
|
|
|
core.addNonEnumerableProperty( |
714
|
|
|
activeSpan , |
715
|
|
|
'__sentry_navigation_name_set__', |
716
|
|
|
true, |
717
|
|
|
); |
718
|
|
|
} |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
// Always set the source attribute to keep it consistent with the current route |
722
|
|
|
activeSpan?.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
/** |
726
|
|
|
* Creates a new navigation span |
727
|
|
|
*/ |
728
|
|
|
function createNewNavigationSpan( |
729
|
|
|
client, |
730
|
|
|
name, |
731
|
|
|
source, |
732
|
|
|
version, |
733
|
|
|
isLikelyLazyRoute, |
734
|
|
|
) { |
735
|
|
|
const newSpan = browser.startBrowserTracingNavigationSpan(client, { |
736
|
|
|
name, |
737
|
|
|
attributes: { |
738
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source, |
739
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation', |
740
|
|
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`, |
741
|
|
|
}, |
742
|
|
|
}); |
743
|
|
|
|
744
|
|
|
// For lazy routes, don't mark as named yet so it can be updated later when the route loads |
745
|
|
|
if (!isLikelyLazyRoute && newSpan) { |
746
|
|
|
core.addNonEnumerableProperty( |
747
|
|
|
newSpan , |
748
|
|
|
'__sentry_navigation_name_set__', |
749
|
|
|
true, |
750
|
|
|
); |
751
|
|
|
} |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
exports.addResolvedRoutesToParent = addResolvedRoutesToParent; |
755
|
|
|
exports.createNewNavigationSpan = createNewNavigationSpan; |
756
|
|
|
exports.createReactRouterV6CompatibleTracingIntegration = createReactRouterV6CompatibleTracingIntegration; |
757
|
|
|
exports.createV6CompatibleWithSentryReactRouterRouting = createV6CompatibleWithSentryReactRouterRouting; |
758
|
|
|
exports.createV6CompatibleWrapCreateBrowserRouter = createV6CompatibleWrapCreateBrowserRouter; |
759
|
|
|
exports.createV6CompatibleWrapCreateMemoryRouter = createV6CompatibleWrapCreateMemoryRouter; |
760
|
|
|
exports.createV6CompatibleWrapUseRoutes = createV6CompatibleWrapUseRoutes; |
761
|
|
|
exports.handleExistingNavigationSpan = handleExistingNavigationSpan; |
762
|
|
|
exports.handleNavigation = handleNavigation; |
763
|
|
|
exports.processResolvedRoutes = processResolvedRoutes; |
764
|
|
|
exports.updateNavigationSpan = updateNavigationSpan; |
765
|
|
|
//# sourceMappingURL=instrumentation.js.map |
766
|
|
|
|