@sentry/react 10.7.0 → 10.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/build/cjs/{reactrouterv6-compat-utils.js → reactrouter-compat-utils/instrumentation.js} +271 -310
  2. package/build/cjs/reactrouter-compat-utils/instrumentation.js.map +1 -0
  3. package/build/cjs/reactrouter-compat-utils/lazy-routes.js +80 -0
  4. package/build/cjs/reactrouter-compat-utils/lazy-routes.js.map +1 -0
  5. package/build/cjs/reactrouter-compat-utils/utils.js +297 -0
  6. package/build/cjs/reactrouter-compat-utils/utils.js.map +1 -0
  7. package/build/cjs/reactrouterv6.js +7 -6
  8. package/build/cjs/reactrouterv6.js.map +1 -1
  9. package/build/cjs/reactrouterv7.js +7 -6
  10. package/build/cjs/reactrouterv7.js.map +1 -1
  11. package/build/cjs/redux.js.map +1 -1
  12. package/build/esm/package.json +1 -1
  13. package/build/esm/{reactrouterv6-compat-utils.js → reactrouter-compat-utils/instrumentation.js} +265 -307
  14. package/build/esm/reactrouter-compat-utils/instrumentation.js.map +1 -0
  15. package/build/esm/reactrouter-compat-utils/lazy-routes.js +76 -0
  16. package/build/esm/reactrouter-compat-utils/lazy-routes.js.map +1 -0
  17. package/build/esm/reactrouter-compat-utils/utils.js +286 -0
  18. package/build/esm/reactrouter-compat-utils/utils.js.map +1 -0
  19. package/build/esm/reactrouterv6.js +2 -1
  20. package/build/esm/reactrouterv6.js.map +1 -1
  21. package/build/esm/reactrouterv7.js +2 -1
  22. package/build/esm/reactrouterv7.js.map +1 -1
  23. package/build/esm/redux.js.map +1 -1
  24. package/build/types/reactrouter-compat-utils/index.d.ts +5 -0
  25. package/build/types/reactrouter-compat-utils/index.d.ts.map +1 -0
  26. package/build/types/{reactrouterv6-compat-utils.d.ts → reactrouter-compat-utils/instrumentation.d.ts} +21 -7
  27. package/build/types/reactrouter-compat-utils/instrumentation.d.ts.map +1 -0
  28. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts +14 -0
  29. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts.map +1 -0
  30. package/build/types/reactrouter-compat-utils/utils.d.ts +50 -0
  31. package/build/types/reactrouter-compat-utils/utils.d.ts.map +1 -0
  32. package/build/types/reactrouterv6.d.ts +1 -1
  33. package/build/types/reactrouterv6.d.ts.map +1 -1
  34. package/build/types/reactrouterv7.d.ts +1 -1
  35. package/build/types/reactrouterv7.d.ts.map +1 -1
  36. package/build/types/redux.d.ts.map +1 -1
  37. package/build/types-ts3.8/reactrouter-compat-utils/index.d.ts +5 -0
  38. package/build/types-ts3.8/{reactrouterv6-compat-utils.d.ts → reactrouter-compat-utils/instrumentation.d.ts} +21 -7
  39. package/build/types-ts3.8/reactrouter-compat-utils/lazy-routes.d.ts +14 -0
  40. package/build/types-ts3.8/reactrouter-compat-utils/utils.d.ts +56 -0
  41. package/build/types-ts3.8/reactrouterv6.d.ts +1 -1
  42. package/build/types-ts3.8/reactrouterv7.d.ts +1 -1
  43. package/package.json +3 -3
  44. package/build/cjs/reactrouterv6-compat-utils.js.map +0 -1
  45. package/build/esm/reactrouterv6-compat-utils.js.map +0 -1
  46. package/build/types/reactrouterv6-compat-utils.d.ts.map +0 -1
@@ -1,8 +1,10 @@
1
1
  import { browserTracingIntegration, WINDOW, startBrowserTracingPageLoadSpan, startBrowserTracingNavigationSpan } from '@sentry/browser';
2
- import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, debug, getActiveSpan, getRootSpan, spanToJSON, getCurrentScope, getClient, addNonEnumerableProperty } from '@sentry/core';
2
+ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, debug, spanToJSON, getActiveSpan, getRootSpan, getCurrentScope, getClient, addNonEnumerableProperty } from '@sentry/core';
3
3
  import * as React from 'react';
4
- import { DEBUG_BUILD } from './debug-build.js';
5
- import { hoistNonReactStatics } from './hoist-non-react-statics.js';
4
+ import { DEBUG_BUILD } from '../debug-build.js';
5
+ import { hoistNonReactStatics } from '../hoist-non-react-statics.js';
6
+ import { checkRouteForAsyncHandler } from './lazy-routes.js';
7
+ import { locationIsInsideDescendantRoute, prefixWithSlash, rebuildRoutePathFromAllRoutes, getNormalizedName, resolveRouteNameAndSource, isLikelyLazyRouteContext, initializeRouterUtils } from './utils.js';
6
8
 
7
9
  /* eslint-disable max-lines */
8
10
  // Inspired from Donnie McNeal's solution:
@@ -14,14 +16,10 @@ let _useLocation;
14
16
  let _useNavigationType;
15
17
  let _createRoutesFromChildren;
16
18
  let _matchRoutes;
17
- let _stripBasename = false;
18
19
  let _enableAsyncRouteHandlers = false;
19
20
 
20
21
  const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
21
22
 
22
- // Keeping as a global variable for cross-usage in multiple functions
23
- const allRoutes = new Set();
24
-
25
23
  /**
26
24
  * Adds resolved routes as children to the parent route.
27
25
  * Prevents duplicate routes by checking if they already exist.
@@ -44,39 +42,22 @@ function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
44
42
  }
45
43
  }
46
44
 
47
- /**
48
- * Handles the result of an async handler function call.
49
- */
50
- function handleAsyncHandlerResult(result, route, handlerKey) {
51
- if (
52
- result &&
53
- typeof result === 'object' &&
54
- 'then' in result &&
55
- typeof (result ).then === 'function'
56
- ) {
57
- (result )
58
- .then((resolvedRoutes) => {
59
- if (Array.isArray(resolvedRoutes)) {
60
- processResolvedRoutes(resolvedRoutes, route);
61
- }
62
- })
63
- .catch((e) => {
64
- DEBUG_BUILD && debug.warn(`Error resolving async handler '${handlerKey}' for route`, route, e);
65
- });
66
- } else if (Array.isArray(result)) {
67
- processResolvedRoutes(result, route);
68
- }
69
- }
45
+ // Keeping as a global variable for cross-usage in multiple functions
46
+ const allRoutes = new Set();
70
47
 
71
48
  /**
72
49
  * Processes resolved routes by adding them to allRoutes and checking for nested async handlers.
73
50
  */
74
- function processResolvedRoutes(resolvedRoutes, parentRoute) {
51
+ function processResolvedRoutes(
52
+ resolvedRoutes,
53
+ parentRoute,
54
+ currentLocation = null,
55
+ ) {
75
56
  resolvedRoutes.forEach(child => {
76
57
  allRoutes.add(child);
77
58
  // Only check for async handlers if the feature is enabled
78
59
  if (_enableAsyncRouteHandlers) {
79
- checkRouteForAsyncHandler(child);
60
+ checkRouteForAsyncHandler(child, processResolvedRoutes);
80
61
  }
81
62
  });
82
63
 
@@ -85,63 +66,81 @@ function processResolvedRoutes(resolvedRoutes, parentRoute) {
85
66
  addResolvedRoutesToParent(resolvedRoutes, parentRoute);
86
67
  }
87
68
 
88
- // After processing lazy routes, check if we need to update an active pageload transaction
69
+ // After processing lazy routes, check if we need to update an active transaction
89
70
  const activeRootSpan = getActiveRootSpan();
90
- if (activeRootSpan && spanToJSON(activeRootSpan).op === 'pageload') {
91
- const location = WINDOW.location;
71
+ if (activeRootSpan) {
72
+ const spanOp = spanToJSON(activeRootSpan).op;
73
+
74
+ // Try to use the provided location first, then fall back to global window location if needed
75
+ let location = currentLocation;
76
+ if (!location) {
77
+ if (typeof WINDOW !== 'undefined') {
78
+ const globalLocation = WINDOW.location;
79
+ if (globalLocation) {
80
+ location = { pathname: globalLocation.pathname };
81
+ }
82
+ }
83
+ }
84
+
92
85
  if (location) {
93
- // Re-run the pageload transaction update with the newly loaded routes
94
- updatePageloadTransaction(
95
- activeRootSpan,
96
- { pathname: location.pathname },
97
- Array.from(allRoutes),
98
- undefined,
99
- undefined,
100
- Array.from(allRoutes),
101
- );
86
+ if (spanOp === 'pageload') {
87
+ // Re-run the pageload transaction update with the newly loaded routes
88
+ updatePageloadTransaction({
89
+ activeRootSpan,
90
+ location: { pathname: location.pathname },
91
+ routes: Array.from(allRoutes),
92
+ allRoutes: Array.from(allRoutes),
93
+ });
94
+ } else if (spanOp === 'navigation') {
95
+ // For navigation spans, update the name with the newly loaded routes
96
+ updateNavigationSpan(activeRootSpan, location, Array.from(allRoutes), false, _matchRoutes);
97
+ }
102
98
  }
103
99
  }
104
100
  }
105
101
 
106
102
  /**
107
- * Creates a proxy wrapper for an async handler function.
103
+ * Updates a navigation span with the correct route name after lazy routes have been loaded.
108
104
  */
109
- function createAsyncHandlerProxy(
110
- originalFunction,
111
- route,
112
- handlerKey,
105
+ function updateNavigationSpan(
106
+ activeRootSpan,
107
+ location,
108
+ allRoutes,
109
+ forceUpdate = false,
110
+ matchRoutes,
113
111
  ) {
114
- const proxy = new Proxy(originalFunction, {
115
- apply(target, thisArg, argArray) {
116
- const result = target.apply(thisArg, argArray);
117
- handleAsyncHandlerResult(result, route, handlerKey);
118
- return result;
119
- },
120
- });
121
-
122
- addNonEnumerableProperty(proxy, '__sentry_proxied__', true);
123
-
124
- return proxy;
125
- }
112
+ // Check if this span has already been named to avoid multiple updates
113
+ // But allow updates if this is a forced update (e.g., when lazy routes are loaded)
114
+ const hasBeenNamed =
115
+ !forceUpdate &&
116
+ (
117
+ activeRootSpan
118
+
119
+ )?.__sentry_navigation_name_set__;
120
+
121
+ if (!hasBeenNamed) {
122
+ // Get fresh branches for the current location with all loaded routes
123
+ const currentBranches = matchRoutes(allRoutes, location);
124
+ const [name, source] = resolveRouteNameAndSource(
125
+ location,
126
+ allRoutes,
127
+ allRoutes,
128
+ (currentBranches ) || [],
129
+ '',
130
+ );
126
131
 
127
- /**
128
- * Recursively checks a route for async handlers and sets up Proxies to add discovered child routes to allRoutes when called.
129
- */
130
- function checkRouteForAsyncHandler(route) {
131
- // Set up proxies for any functions in the route's handle
132
- if (route.handle && typeof route.handle === 'object') {
133
- for (const key of Object.keys(route.handle)) {
134
- const maybeFn = route.handle[key];
135
- if (typeof maybeFn === 'function' && !(maybeFn ).__sentry_proxied__) {
136
- route.handle[key] = createAsyncHandlerProxy(maybeFn, route, key);
137
- }
138
- }
139
- }
132
+ // Only update if we have a valid name and the span hasn't finished
133
+ const spanJson = spanToJSON(activeRootSpan);
134
+ if (name && !spanJson.timestamp) {
135
+ activeRootSpan.updateName(name);
136
+ activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
140
137
 
141
- // Recursively check child routes
142
- if (Array.isArray(route.children)) {
143
- for (const child of route.children) {
144
- checkRouteForAsyncHandler(child);
138
+ // Mark this span as having its name set to prevent future updates
139
+ addNonEnumerableProperty(
140
+ activeRootSpan ,
141
+ '__sentry_navigation_name_set__',
142
+ true,
143
+ );
145
144
  }
146
145
  }
147
146
  }
@@ -170,11 +169,14 @@ function createV6CompatibleWrapCreateBrowserRouter
170
169
  // Check for async handlers that might contain sub-route declarations (only if enabled)
171
170
  if (_enableAsyncRouteHandlers) {
172
171
  for (const route of routes) {
173
- checkRouteForAsyncHandler(route);
172
+ checkRouteForAsyncHandler(route, processResolvedRoutes);
174
173
  }
175
174
  }
176
175
 
177
- const router = createRouterFunction(routes, opts);
176
+ // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
177
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts);
178
+
179
+ const router = createRouterFunction(routes, wrappedOpts);
178
180
  const basename = opts?.basename;
179
181
 
180
182
  const activeRootSpan = getActiveRootSpan();
@@ -183,14 +185,13 @@ function createV6CompatibleWrapCreateBrowserRouter
183
185
  // This is the earliest convenient time to update the transaction name.
184
186
  // Callbacks to `router.subscribe` are not called for the initial load.
185
187
  if (router.state.historyAction === 'POP' && activeRootSpan) {
186
- updatePageloadTransaction(
188
+ updatePageloadTransaction({
187
189
  activeRootSpan,
188
- router.state.location,
190
+ location: router.state.location,
189
191
  routes,
190
- undefined,
191
192
  basename,
192
- Array.from(allRoutes),
193
- );
193
+ allRoutes: Array.from(allRoutes),
194
+ });
194
195
  }
195
196
 
196
197
  router.subscribe((state) => {
@@ -253,11 +254,14 @@ function createV6CompatibleWrapCreateMemoryRouter
253
254
  // Check for async handlers that might contain sub-route declarations (only if enabled)
254
255
  if (_enableAsyncRouteHandlers) {
255
256
  for (const route of routes) {
256
- checkRouteForAsyncHandler(route);
257
+ checkRouteForAsyncHandler(route, processResolvedRoutes);
257
258
  }
258
259
  }
259
260
 
260
- const router = createRouterFunction(routes, opts);
261
+ // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
262
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true);
263
+
264
+ const router = createRouterFunction(routes, wrappedOpts);
261
265
  const basename = opts?.basename;
262
266
 
263
267
  const activeRootSpan = getActiveRootSpan();
@@ -282,7 +286,13 @@ function createV6CompatibleWrapCreateMemoryRouter
282
286
  : router.state.location;
283
287
 
284
288
  if (router.state.historyAction === 'POP' && activeRootSpan) {
285
- updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes));
289
+ updatePageloadTransaction({
290
+ activeRootSpan,
291
+ location,
292
+ routes,
293
+ basename,
294
+ allRoutes: Array.from(allRoutes),
295
+ });
286
296
  }
287
297
 
288
298
  router.subscribe((state) => {
@@ -338,8 +348,10 @@ function createReactRouterV6CompatibleTracingIntegration(
338
348
  _useNavigationType = useNavigationType;
339
349
  _matchRoutes = matchRoutes;
340
350
  _createRoutesFromChildren = createRoutesFromChildren;
341
- _stripBasename = stripBasename || false;
342
351
  _enableAsyncRouteHandlers = enableAsyncRouteHandlers;
352
+
353
+ // Initialize the router utils with the required dependencies
354
+ initializeRouterUtils(matchRoutes, stripBasename || false);
343
355
  },
344
356
  afterAllSetup(client) {
345
357
  integration.afterAllSetup(client);
@@ -395,14 +407,12 @@ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
395
407
  if (isMountRenderPass.current) {
396
408
  addRoutesToAllRoutes(routes);
397
409
 
398
- updatePageloadTransaction(
399
- getActiveRootSpan(),
400
- normalizedLocation,
410
+ updatePageloadTransaction({
411
+ activeRootSpan: getActiveRootSpan(),
412
+ location: normalizedLocation,
401
413
  routes,
402
- undefined,
403
- undefined,
404
- Array.from(allRoutes),
405
- );
414
+ allRoutes: Array.from(allRoutes),
415
+ });
406
416
  isMountRenderPass.current = false;
407
417
  } else {
408
418
  handleNavigation({
@@ -424,6 +434,79 @@ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
424
434
  };
425
435
  }
426
436
 
437
+ function wrapPatchRoutesOnNavigation(
438
+ opts,
439
+ isMemoryRouter = false,
440
+ ) {
441
+ if (!opts || !('patchRoutesOnNavigation' in opts) || typeof opts.patchRoutesOnNavigation !== 'function') {
442
+ return opts || {};
443
+ }
444
+
445
+ const originalPatchRoutes = opts.patchRoutesOnNavigation;
446
+ return {
447
+ ...opts,
448
+ patchRoutesOnNavigation: async (args) => {
449
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
450
+ const targetPath = (args )?.path;
451
+
452
+ // For browser router, wrap the patch function to update span during patching
453
+ if (!isMemoryRouter) {
454
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
455
+ const originalPatch = (args )?.patch;
456
+ if (originalPatch) {
457
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
458
+ (args ).patch = (routeId, children) => {
459
+ addRoutesToAllRoutes(children);
460
+ const activeRootSpan = getActiveRootSpan();
461
+ if (activeRootSpan && (spanToJSON(activeRootSpan) ).op === 'navigation') {
462
+ updateNavigationSpan(
463
+ activeRootSpan,
464
+ {
465
+ pathname: targetPath,
466
+ search: '',
467
+ hash: '',
468
+ state: null,
469
+ key: 'default',
470
+ },
471
+ Array.from(allRoutes),
472
+ true, // forceUpdate = true since we're loading lazy routes
473
+ _matchRoutes,
474
+ );
475
+ }
476
+ return originalPatch(routeId, children);
477
+ };
478
+ }
479
+ }
480
+
481
+ const result = await originalPatchRoutes(args);
482
+
483
+ // Update navigation span after routes are patched
484
+ const activeRootSpan = getActiveRootSpan();
485
+ if (activeRootSpan && (spanToJSON(activeRootSpan) ).op === 'navigation') {
486
+ // For memory routers, we should not access window.location; use targetPath only
487
+ const pathname = isMemoryRouter ? targetPath : targetPath || WINDOW.location?.pathname;
488
+ if (pathname) {
489
+ updateNavigationSpan(
490
+ activeRootSpan,
491
+ {
492
+ pathname,
493
+ search: '',
494
+ hash: '',
495
+ state: null,
496
+ key: 'default',
497
+ },
498
+ Array.from(allRoutes),
499
+ false, // forceUpdate = false since this is after lazy routes are loaded
500
+ _matchRoutes,
501
+ );
502
+ }
503
+ }
504
+
505
+ return result;
506
+ },
507
+ };
508
+ }
509
+
427
510
  function handleNavigation(opts
428
511
 
429
512
  ) {
@@ -436,105 +519,28 @@ function handleNavigation(opts
436
519
  }
437
520
 
438
521
  if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
439
- let name,
440
- source = 'url';
441
- const isInDescendantRoute = locationIsInsideDescendantRoute(location, allRoutes || routes);
442
-
443
- if (isInDescendantRoute) {
444
- name = prefixWithSlash(rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
445
- source = 'route';
446
- }
522
+ const [name, source] = resolveRouteNameAndSource(
523
+ location,
524
+ routes,
525
+ allRoutes || routes,
526
+ branches ,
527
+ basename,
528
+ );
447
529
 
448
- if (!isInDescendantRoute || !name) {
449
- [name, source] = getNormalizedName(routes, location, branches, basename);
450
- }
530
+ // Check if this might be a lazy route context
531
+ const isLazyRouteContext = isLikelyLazyRouteContext(allRoutes || routes, location);
451
532
 
452
533
  const activeSpan = getActiveSpan();
453
- const isAlreadyInNavigationSpan = activeSpan && spanToJSON(activeSpan).op === 'navigation';
534
+ const spanJson = activeSpan && spanToJSON(activeSpan);
535
+ const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
454
536
 
455
537
  // Cross usage can result in multiple navigation spans being created without this check
456
- if (isAlreadyInNavigationSpan) {
457
- activeSpan?.updateName(name);
458
- activeSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
538
+ if (isAlreadyInNavigationSpan && activeSpan && spanJson) {
539
+ handleExistingNavigationSpan(activeSpan, spanJson, name, source, isLazyRouteContext);
459
540
  } else {
460
- startBrowserTracingNavigationSpan(client, {
461
- name,
462
- attributes: {
463
- [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
464
- [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
465
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
466
- },
467
- });
468
- }
469
- }
470
- }
471
-
472
- /**
473
- * Strip the basename from a pathname if exists.
474
- *
475
- * Vendored and modified from `react-router`
476
- * https://github.com/remix-run/react-router/blob/462bb712156a3f739d6139a0f14810b76b002df6/packages/router/utils.ts#L1038
477
- */
478
- function stripBasenameFromPathname(pathname, basename) {
479
- if (!basename || basename === '/') {
480
- return pathname;
481
- }
482
-
483
- if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
484
- return pathname;
485
- }
486
-
487
- // We want to leave trailing slash behavior in the user's control, so if they
488
- // specify a basename with a trailing slash, we should support it
489
- const startIndex = basename.endsWith('/') ? basename.length - 1 : basename.length;
490
- const nextChar = pathname.charAt(startIndex);
491
- if (nextChar && nextChar !== '/') {
492
- // pathname does not start with basename/
493
- return pathname;
494
- }
495
-
496
- return pathname.slice(startIndex) || '/';
497
- }
498
-
499
- function sendIndexPath(pathBuilder, pathname, basename) {
500
- const reconstructedPath = pathBuilder || _stripBasename ? stripBasenameFromPathname(pathname, basename) : pathname;
501
-
502
- const formattedPath =
503
- // If the path ends with a slash, remove it
504
- reconstructedPath[reconstructedPath.length - 1] === '/'
505
- ? reconstructedPath.slice(0, -1)
506
- : // If the path ends with a wildcard, remove it
507
- reconstructedPath.slice(-2) === '/*'
508
- ? reconstructedPath.slice(0, -1)
509
- : reconstructedPath;
510
-
511
- return [formattedPath, 'route'];
512
- }
513
-
514
- function pathEndsWithWildcard(path) {
515
- return path.endsWith('*');
516
- }
517
-
518
- function pathIsWildcardAndHasChildren(path, branch) {
519
- return (pathEndsWithWildcard(path) && !!branch.route.children?.length) || false;
520
- }
521
-
522
- function routeIsDescendant(route) {
523
- return !!(!route.children && route.element && route.path?.endsWith('/*'));
524
- }
525
-
526
- function locationIsInsideDescendantRoute(location, routes) {
527
- const matchedRoutes = _matchRoutes(routes, location) ;
528
-
529
- if (matchedRoutes) {
530
- for (const match of matchedRoutes) {
531
- if (routeIsDescendant(match.route) && pickSplat(match)) {
532
- return true;
533
- }
541
+ createNewNavigationSpan(client, name, source, version, isLazyRouteContext);
534
542
  }
535
543
  }
536
-
537
- return false;
538
544
  }
539
545
 
540
546
  function addRoutesToAllRoutes(routes) {
@@ -565,125 +571,15 @@ function getChildRoutesRecursively(route, allRoutes = new Set()) {
565
571
  return allRoutes;
566
572
  }
567
573
 
568
- function pickPath(match) {
569
- return trimWildcard(match.route.path || '');
570
- }
571
-
572
- function pickSplat(match) {
573
- return match.params['*'] || '';
574
- }
575
-
576
- function trimWildcard(path) {
577
- return path[path.length - 1] === '*' ? path.slice(0, -1) : path;
578
- }
579
-
580
- function trimSlash(path) {
581
- return path[path.length - 1] === '/' ? path.slice(0, -1) : path;
582
- }
583
-
584
- function prefixWithSlash(path) {
585
- return path[0] === '/' ? path : `/${path}`;
586
- }
587
-
588
- function rebuildRoutePathFromAllRoutes(allRoutes, location) {
589
- const matchedRoutes = _matchRoutes(allRoutes, location) ;
590
-
591
- if (!matchedRoutes || matchedRoutes.length === 0) {
592
- return '';
593
- }
594
-
595
- for (const match of matchedRoutes) {
596
- if (match.route.path && match.route.path !== '*') {
597
- const path = pickPath(match);
598
- const strippedPath = stripBasenameFromPathname(location.pathname, prefixWithSlash(match.pathnameBase));
599
-
600
- if (location.pathname === strippedPath) {
601
- return trimSlash(strippedPath);
602
- }
603
-
604
- return trimSlash(
605
- trimSlash(path || '') +
606
- prefixWithSlash(
607
- rebuildRoutePathFromAllRoutes(
608
- allRoutes.filter(route => route !== match.route),
609
- {
610
- pathname: strippedPath,
611
- },
612
- ),
613
- ),
614
- );
615
- }
616
- }
617
-
618
- return '';
619
- }
620
-
621
- function getNormalizedName(
622
- routes,
623
- location,
624
- branches,
625
- basename = '',
626
- ) {
627
- if (!routes || routes.length === 0) {
628
- return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
629
- }
630
-
631
- let pathBuilder = '';
632
-
633
- if (branches) {
634
- for (const branch of branches) {
635
- const route = branch.route;
636
- if (route) {
637
- // Early return if index route
638
- if (route.index) {
639
- return sendIndexPath(pathBuilder, branch.pathname, basename);
640
- }
641
- const path = route.path;
642
-
643
- // If path is not a wildcard and has no child routes, append the path
644
- if (path && !pathIsWildcardAndHasChildren(path, branch)) {
645
- const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
646
- pathBuilder = trimSlash(pathBuilder) + prefixWithSlash(newPath);
647
-
648
- // If the path matches the current location, return the path
649
- if (trimSlash(location.pathname) === trimSlash(basename + branch.pathname)) {
650
- if (
651
- // If the route defined on the element is something like
652
- // <Route path="/stores/:storeId/products/:productId" element={<div>Product</div>} />
653
- // We should check against the branch.pathname for the number of / separators
654
- getNumberOfUrlSegments(pathBuilder) !== getNumberOfUrlSegments(branch.pathname) &&
655
- // We should not count wildcard operators in the url segments calculation
656
- !pathEndsWithWildcard(pathBuilder)
657
- ) {
658
- return [(_stripBasename ? '' : basename) + newPath, 'route'];
659
- }
660
-
661
- // if the last character of the pathbuilder is a wildcard and there are children, remove the wildcard
662
- if (pathIsWildcardAndHasChildren(pathBuilder, branch)) {
663
- pathBuilder = pathBuilder.slice(0, -1);
664
- }
665
-
666
- return [(_stripBasename ? '' : basename) + pathBuilder, 'route'];
667
- }
668
- }
669
- }
670
- }
671
- }
672
-
673
- const fallbackTransactionName = _stripBasename
674
- ? stripBasenameFromPathname(location.pathname, basename)
675
- : location.pathname || '/';
676
-
677
- return [fallbackTransactionName, 'url'];
678
- }
679
-
680
- function updatePageloadTransaction(
574
+ function updatePageloadTransaction({
681
575
  activeRootSpan,
682
576
  location,
683
577
  routes,
684
578
  matches,
685
579
  basename,
686
580
  allRoutes,
581
+ }
582
+
687
583
  ) {
688
584
  const branches = Array.isArray(matches)
689
585
  ? matches
@@ -740,7 +636,12 @@ function createV6CompatibleWithSentryReactRouterRouting(
740
636
  if (isMountRenderPass.current) {
741
637
  addRoutesToAllRoutes(routes);
742
638
 
743
- updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, Array.from(allRoutes));
639
+ updatePageloadTransaction({
640
+ activeRootSpan: getActiveRootSpan(),
641
+ location,
642
+ routes,
643
+ allRoutes: Array.from(allRoutes),
644
+ });
744
645
  isMountRenderPass.current = false;
745
646
  } else {
746
647
  handleNavigation({
@@ -784,12 +685,69 @@ function getActiveRootSpan() {
784
685
  }
785
686
 
786
687
  /**
787
- * Returns number of URL segments of a passed string URL.
688
+ * Handles updating an existing navigation span
689
+ */
690
+ function handleExistingNavigationSpan(
691
+ activeSpan,
692
+ spanJson,
693
+ name,
694
+ source,
695
+ isLikelyLazyRoute,
696
+ ) {
697
+ // Check if we've already set the name for this span using a custom property
698
+ const hasBeenNamed = (
699
+ activeSpan
700
+
701
+ )?.__sentry_navigation_name_set__;
702
+
703
+ if (!hasBeenNamed) {
704
+ // This is the first time we're setting the name for this span
705
+ if (!spanJson.timestamp) {
706
+ activeSpan?.updateName(name);
707
+ }
708
+
709
+ // For lazy routes, don't mark as named yet so it can be updated later
710
+ if (!isLikelyLazyRoute) {
711
+ addNonEnumerableProperty(
712
+ activeSpan ,
713
+ '__sentry_navigation_name_set__',
714
+ true,
715
+ );
716
+ }
717
+ }
718
+
719
+ // Always set the source attribute to keep it consistent with the current route
720
+ activeSpan?.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
721
+ }
722
+
723
+ /**
724
+ * Creates a new navigation span
788
725
  */
789
- function getNumberOfUrlSegments(url) {
790
- // split at '/' or at '\/' to split regex urls correctly
791
- return url.split(/\\?\//).filter(s => s.length > 0 && s !== ',').length;
726
+ function createNewNavigationSpan(
727
+ client,
728
+ name,
729
+ source,
730
+ version,
731
+ isLikelyLazyRoute,
732
+ ) {
733
+ const newSpan = startBrowserTracingNavigationSpan(client, {
734
+ name,
735
+ attributes: {
736
+ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
737
+ [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
738
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
739
+ },
740
+ });
741
+
742
+ // For lazy routes, don't mark as named yet so it can be updated later when the route loads
743
+ if (!isLikelyLazyRoute && newSpan) {
744
+ addNonEnumerableProperty(
745
+ newSpan ,
746
+ '__sentry_navigation_name_set__',
747
+ true,
748
+ );
749
+ }
792
750
  }
793
751
 
794
- export { checkRouteForAsyncHandler, createReactRouterV6CompatibleTracingIntegration, createV6CompatibleWithSentryReactRouterRouting, createV6CompatibleWrapCreateBrowserRouter, createV6CompatibleWrapCreateMemoryRouter, createV6CompatibleWrapUseRoutes, getNumberOfUrlSegments, handleNavigation };
795
- //# sourceMappingURL=reactrouterv6-compat-utils.js.map
752
+ export { addResolvedRoutesToParent, createNewNavigationSpan, createReactRouterV6CompatibleTracingIntegration, createV6CompatibleWithSentryReactRouterRouting, createV6CompatibleWrapCreateBrowserRouter, createV6CompatibleWrapCreateMemoryRouter, createV6CompatibleWrapUseRoutes, handleExistingNavigationSpan, handleNavigation, processResolvedRoutes, updateNavigationSpan };
753
+ //# sourceMappingURL=instrumentation.js.map