@sentry/react 10.6.0 → 10.8.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 (43) 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/esm/package.json +1 -1
  12. package/build/esm/{reactrouterv6-compat-utils.js → reactrouter-compat-utils/instrumentation.js} +265 -307
  13. package/build/esm/reactrouter-compat-utils/instrumentation.js.map +1 -0
  14. package/build/esm/reactrouter-compat-utils/lazy-routes.js +76 -0
  15. package/build/esm/reactrouter-compat-utils/lazy-routes.js.map +1 -0
  16. package/build/esm/reactrouter-compat-utils/utils.js +286 -0
  17. package/build/esm/reactrouter-compat-utils/utils.js.map +1 -0
  18. package/build/esm/reactrouterv6.js +2 -1
  19. package/build/esm/reactrouterv6.js.map +1 -1
  20. package/build/esm/reactrouterv7.js +2 -1
  21. package/build/esm/reactrouterv7.js.map +1 -1
  22. package/build/types/reactrouter-compat-utils/index.d.ts +5 -0
  23. package/build/types/reactrouter-compat-utils/index.d.ts.map +1 -0
  24. package/build/types/{reactrouterv6-compat-utils.d.ts → reactrouter-compat-utils/instrumentation.d.ts} +21 -7
  25. package/build/types/reactrouter-compat-utils/instrumentation.d.ts.map +1 -0
  26. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts +14 -0
  27. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts.map +1 -0
  28. package/build/types/reactrouter-compat-utils/utils.d.ts +50 -0
  29. package/build/types/reactrouter-compat-utils/utils.d.ts.map +1 -0
  30. package/build/types/reactrouterv6.d.ts +1 -1
  31. package/build/types/reactrouterv6.d.ts.map +1 -1
  32. package/build/types/reactrouterv7.d.ts +1 -1
  33. package/build/types/reactrouterv7.d.ts.map +1 -1
  34. package/build/types-ts3.8/reactrouter-compat-utils/index.d.ts +5 -0
  35. package/build/types-ts3.8/{reactrouterv6-compat-utils.d.ts → reactrouter-compat-utils/instrumentation.d.ts} +21 -7
  36. package/build/types-ts3.8/reactrouter-compat-utils/lazy-routes.d.ts +14 -0
  37. package/build/types-ts3.8/reactrouter-compat-utils/utils.d.ts +56 -0
  38. package/build/types-ts3.8/reactrouterv6.d.ts +1 -1
  39. package/build/types-ts3.8/reactrouterv7.d.ts +1 -1
  40. package/package.json +3 -3
  41. package/build/cjs/reactrouterv6-compat-utils.js.map +0 -1
  42. package/build/esm/reactrouterv6-compat-utils.js.map +0 -1
  43. package/build/types/reactrouterv6-compat-utils.d.ts.map +0 -1
@@ -3,8 +3,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
3
  const browser = require('@sentry/browser');
4
4
  const core = require('@sentry/core');
5
5
  const React = require('react');
6
- const debugBuild = require('./debug-build.js');
7
- const hoistNonReactStatics = require('./hoist-non-react-statics.js');
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');
8
10
 
9
11
  /* eslint-disable max-lines */
10
12
  // Inspired from Donnie McNeal's solution:
@@ -16,14 +18,10 @@ let _useLocation;
16
18
  let _useNavigationType;
17
19
  let _createRoutesFromChildren;
18
20
  let _matchRoutes;
19
- let _stripBasename = false;
20
21
  let _enableAsyncRouteHandlers = false;
21
22
 
22
23
  const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
23
24
 
24
- // Keeping as a global variable for cross-usage in multiple functions
25
- const allRoutes = new Set();
26
-
27
25
  /**
28
26
  * Adds resolved routes as children to the parent route.
29
27
  * Prevents duplicate routes by checking if they already exist.
@@ -46,39 +44,22 @@ function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
46
44
  }
47
45
  }
48
46
 
49
- /**
50
- * Handles the result of an async handler function call.
51
- */
52
- function handleAsyncHandlerResult(result, route, handlerKey) {
53
- if (
54
- result &&
55
- typeof result === 'object' &&
56
- 'then' in result &&
57
- typeof (result ).then === 'function'
58
- ) {
59
- (result )
60
- .then((resolvedRoutes) => {
61
- if (Array.isArray(resolvedRoutes)) {
62
- processResolvedRoutes(resolvedRoutes, route);
63
- }
64
- })
65
- .catch((e) => {
66
- debugBuild.DEBUG_BUILD && core.debug.warn(`Error resolving async handler '${handlerKey}' for route`, route, e);
67
- });
68
- } else if (Array.isArray(result)) {
69
- processResolvedRoutes(result, route);
70
- }
71
- }
47
+ // Keeping as a global variable for cross-usage in multiple functions
48
+ const allRoutes = new Set();
72
49
 
73
50
  /**
74
51
  * Processes resolved routes by adding them to allRoutes and checking for nested async handlers.
75
52
  */
76
- function processResolvedRoutes(resolvedRoutes, parentRoute) {
53
+ function processResolvedRoutes(
54
+ resolvedRoutes,
55
+ parentRoute,
56
+ currentLocation = null,
57
+ ) {
77
58
  resolvedRoutes.forEach(child => {
78
59
  allRoutes.add(child);
79
60
  // Only check for async handlers if the feature is enabled
80
61
  if (_enableAsyncRouteHandlers) {
81
- checkRouteForAsyncHandler(child);
62
+ lazyRoutes.checkRouteForAsyncHandler(child, processResolvedRoutes);
82
63
  }
83
64
  });
84
65
 
@@ -87,63 +68,81 @@ function processResolvedRoutes(resolvedRoutes, parentRoute) {
87
68
  addResolvedRoutesToParent(resolvedRoutes, parentRoute);
88
69
  }
89
70
 
90
- // After processing lazy routes, check if we need to update an active pageload transaction
71
+ // After processing lazy routes, check if we need to update an active transaction
91
72
  const activeRootSpan = getActiveRootSpan();
92
- if (activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload') {
93
- const location = browser.WINDOW.location;
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
+
94
87
  if (location) {
95
- // Re-run the pageload transaction update with the newly loaded routes
96
- updatePageloadTransaction(
97
- activeRootSpan,
98
- { pathname: location.pathname },
99
- Array.from(allRoutes),
100
- undefined,
101
- undefined,
102
- Array.from(allRoutes),
103
- );
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
+ }
104
100
  }
105
101
  }
106
102
  }
107
103
 
108
104
  /**
109
- * Creates a proxy wrapper for an async handler function.
105
+ * Updates a navigation span with the correct route name after lazy routes have been loaded.
110
106
  */
111
- function createAsyncHandlerProxy(
112
- originalFunction,
113
- route,
114
- handlerKey,
107
+ function updateNavigationSpan(
108
+ activeRootSpan,
109
+ location,
110
+ allRoutes,
111
+ forceUpdate = false,
112
+ matchRoutes,
115
113
  ) {
116
- const proxy = new Proxy(originalFunction, {
117
- apply(target, thisArg, argArray) {
118
- const result = target.apply(thisArg, argArray);
119
- handleAsyncHandlerResult(result, route, handlerKey);
120
- return result;
121
- },
122
- });
123
-
124
- core.addNonEnumerableProperty(proxy, '__sentry_proxied__', true);
125
-
126
- return proxy;
127
- }
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
+ );
128
133
 
129
- /**
130
- * Recursively checks a route for async handlers and sets up Proxies to add discovered child routes to allRoutes when called.
131
- */
132
- function checkRouteForAsyncHandler(route) {
133
- // Set up proxies for any functions in the route's handle
134
- if (route.handle && typeof route.handle === 'object') {
135
- for (const key of Object.keys(route.handle)) {
136
- const maybeFn = route.handle[key];
137
- if (typeof maybeFn === 'function' && !(maybeFn ).__sentry_proxied__) {
138
- route.handle[key] = createAsyncHandlerProxy(maybeFn, route, key);
139
- }
140
- }
141
- }
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);
142
139
 
143
- // Recursively check child routes
144
- if (Array.isArray(route.children)) {
145
- for (const child of route.children) {
146
- checkRouteForAsyncHandler(child);
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
+ );
147
146
  }
148
147
  }
149
148
  }
@@ -172,11 +171,14 @@ function createV6CompatibleWrapCreateBrowserRouter
172
171
  // Check for async handlers that might contain sub-route declarations (only if enabled)
173
172
  if (_enableAsyncRouteHandlers) {
174
173
  for (const route of routes) {
175
- checkRouteForAsyncHandler(route);
174
+ lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
176
175
  }
177
176
  }
178
177
 
179
- const router = createRouterFunction(routes, opts);
178
+ // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
179
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts);
180
+
181
+ const router = createRouterFunction(routes, wrappedOpts);
180
182
  const basename = opts?.basename;
181
183
 
182
184
  const activeRootSpan = getActiveRootSpan();
@@ -185,14 +187,13 @@ function createV6CompatibleWrapCreateBrowserRouter
185
187
  // This is the earliest convenient time to update the transaction name.
186
188
  // Callbacks to `router.subscribe` are not called for the initial load.
187
189
  if (router.state.historyAction === 'POP' && activeRootSpan) {
188
- updatePageloadTransaction(
190
+ updatePageloadTransaction({
189
191
  activeRootSpan,
190
- router.state.location,
192
+ location: router.state.location,
191
193
  routes,
192
- undefined,
193
194
  basename,
194
- Array.from(allRoutes),
195
- );
195
+ allRoutes: Array.from(allRoutes),
196
+ });
196
197
  }
197
198
 
198
199
  router.subscribe((state) => {
@@ -255,11 +256,14 @@ function createV6CompatibleWrapCreateMemoryRouter
255
256
  // Check for async handlers that might contain sub-route declarations (only if enabled)
256
257
  if (_enableAsyncRouteHandlers) {
257
258
  for (const route of routes) {
258
- checkRouteForAsyncHandler(route);
259
+ lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
259
260
  }
260
261
  }
261
262
 
262
- const router = createRouterFunction(routes, opts);
263
+ // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
264
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true);
265
+
266
+ const router = createRouterFunction(routes, wrappedOpts);
263
267
  const basename = opts?.basename;
264
268
 
265
269
  const activeRootSpan = getActiveRootSpan();
@@ -284,7 +288,13 @@ function createV6CompatibleWrapCreateMemoryRouter
284
288
  : router.state.location;
285
289
 
286
290
  if (router.state.historyAction === 'POP' && activeRootSpan) {
287
- updatePageloadTransaction(activeRootSpan, location, routes, undefined, basename, Array.from(allRoutes));
291
+ updatePageloadTransaction({
292
+ activeRootSpan,
293
+ location,
294
+ routes,
295
+ basename,
296
+ allRoutes: Array.from(allRoutes),
297
+ });
288
298
  }
289
299
 
290
300
  router.subscribe((state) => {
@@ -340,8 +350,10 @@ function createReactRouterV6CompatibleTracingIntegration(
340
350
  _useNavigationType = useNavigationType;
341
351
  _matchRoutes = matchRoutes;
342
352
  _createRoutesFromChildren = createRoutesFromChildren;
343
- _stripBasename = stripBasename || false;
344
353
  _enableAsyncRouteHandlers = enableAsyncRouteHandlers;
354
+
355
+ // Initialize the router utils with the required dependencies
356
+ utils.initializeRouterUtils(matchRoutes, stripBasename || false);
345
357
  },
346
358
  afterAllSetup(client) {
347
359
  integration.afterAllSetup(client);
@@ -397,14 +409,12 @@ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
397
409
  if (isMountRenderPass.current) {
398
410
  addRoutesToAllRoutes(routes);
399
411
 
400
- updatePageloadTransaction(
401
- getActiveRootSpan(),
402
- normalizedLocation,
412
+ updatePageloadTransaction({
413
+ activeRootSpan: getActiveRootSpan(),
414
+ location: normalizedLocation,
403
415
  routes,
404
- undefined,
405
- undefined,
406
- Array.from(allRoutes),
407
- );
416
+ allRoutes: Array.from(allRoutes),
417
+ });
408
418
  isMountRenderPass.current = false;
409
419
  } else {
410
420
  handleNavigation({
@@ -426,6 +436,79 @@ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
426
436
  };
427
437
  }
428
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
+
429
512
  function handleNavigation(opts
430
513
 
431
514
  ) {
@@ -438,107 +521,30 @@ function handleNavigation(opts
438
521
  }
439
522
 
440
523
  if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
441
- let name,
442
- source = 'url';
443
- const isInDescendantRoute = locationIsInsideDescendantRoute(location, allRoutes || routes);
444
-
445
- if (isInDescendantRoute) {
446
- name = prefixWithSlash(rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
447
- source = 'route';
448
- }
524
+ const [name, source] = utils.resolveRouteNameAndSource(
525
+ location,
526
+ routes,
527
+ allRoutes || routes,
528
+ branches ,
529
+ basename,
530
+ );
449
531
 
450
- if (!isInDescendantRoute || !name) {
451
- [name, source] = getNormalizedName(routes, location, branches, basename);
452
- }
532
+ // Check if this might be a lazy route context
533
+ const isLazyRouteContext = utils.isLikelyLazyRouteContext(allRoutes || routes, location);
453
534
 
454
535
  const activeSpan = core.getActiveSpan();
455
- const isAlreadyInNavigationSpan = activeSpan && core.spanToJSON(activeSpan).op === 'navigation';
536
+ const spanJson = activeSpan && core.spanToJSON(activeSpan);
537
+ const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
456
538
 
457
539
  // Cross usage can result in multiple navigation spans being created without this check
458
- if (isAlreadyInNavigationSpan) {
459
- activeSpan?.updateName(name);
460
- activeSpan?.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
540
+ if (isAlreadyInNavigationSpan && activeSpan && spanJson) {
541
+ handleExistingNavigationSpan(activeSpan, spanJson, name, source, isLazyRouteContext);
461
542
  } else {
462
- browser.startBrowserTracingNavigationSpan(client, {
463
- name,
464
- attributes: {
465
- [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
466
- [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
467
- [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
468
- },
469
- });
543
+ createNewNavigationSpan(client, name, source, version, isLazyRouteContext);
470
544
  }
471
545
  }
472
546
  }
473
547
 
474
- /**
475
- * Strip the basename from a pathname if exists.
476
- *
477
- * Vendored and modified from `react-router`
478
- * https://github.com/remix-run/react-router/blob/462bb712156a3f739d6139a0f14810b76b002df6/packages/router/utils.ts#L1038
479
- */
480
- function stripBasenameFromPathname(pathname, basename) {
481
- if (!basename || basename === '/') {
482
- return pathname;
483
- }
484
-
485
- if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
486
- return pathname;
487
- }
488
-
489
- // We want to leave trailing slash behavior in the user's control, so if they
490
- // specify a basename with a trailing slash, we should support it
491
- const startIndex = basename.endsWith('/') ? basename.length - 1 : basename.length;
492
- const nextChar = pathname.charAt(startIndex);
493
- if (nextChar && nextChar !== '/') {
494
- // pathname does not start with basename/
495
- return pathname;
496
- }
497
-
498
- return pathname.slice(startIndex) || '/';
499
- }
500
-
501
- function sendIndexPath(pathBuilder, pathname, basename) {
502
- const reconstructedPath = pathBuilder || _stripBasename ? stripBasenameFromPathname(pathname, basename) : pathname;
503
-
504
- const formattedPath =
505
- // If the path ends with a slash, remove it
506
- reconstructedPath[reconstructedPath.length - 1] === '/'
507
- ? reconstructedPath.slice(0, -1)
508
- : // If the path ends with a wildcard, remove it
509
- reconstructedPath.slice(-2) === '/*'
510
- ? reconstructedPath.slice(0, -1)
511
- : reconstructedPath;
512
-
513
- return [formattedPath, 'route'];
514
- }
515
-
516
- function pathEndsWithWildcard(path) {
517
- return path.endsWith('*');
518
- }
519
-
520
- function pathIsWildcardAndHasChildren(path, branch) {
521
- return (pathEndsWithWildcard(path) && !!branch.route.children?.length) || false;
522
- }
523
-
524
- function routeIsDescendant(route) {
525
- return !!(!route.children && route.element && route.path?.endsWith('/*'));
526
- }
527
-
528
- function locationIsInsideDescendantRoute(location, routes) {
529
- const matchedRoutes = _matchRoutes(routes, location) ;
530
-
531
- if (matchedRoutes) {
532
- for (const match of matchedRoutes) {
533
- if (routeIsDescendant(match.route) && pickSplat(match)) {
534
- return true;
535
- }
536
- }
537
- }
538
-
539
- return false;
540
- }
541
-
542
548
  function addRoutesToAllRoutes(routes) {
543
549
  routes.forEach(route => {
544
550
  const extractedChildRoutes = getChildRoutesRecursively(route);
@@ -567,125 +573,15 @@ function getChildRoutesRecursively(route, allRoutes = new Set()) {
567
573
  return allRoutes;
568
574
  }
569
575
 
570
- function pickPath(match) {
571
- return trimWildcard(match.route.path || '');
572
- }
573
-
574
- function pickSplat(match) {
575
- return match.params['*'] || '';
576
- }
577
-
578
- function trimWildcard(path) {
579
- return path[path.length - 1] === '*' ? path.slice(0, -1) : path;
580
- }
581
-
582
- function trimSlash(path) {
583
- return path[path.length - 1] === '/' ? path.slice(0, -1) : path;
584
- }
585
-
586
- function prefixWithSlash(path) {
587
- return path[0] === '/' ? path : `/${path}`;
588
- }
589
-
590
- function rebuildRoutePathFromAllRoutes(allRoutes, location) {
591
- const matchedRoutes = _matchRoutes(allRoutes, location) ;
592
-
593
- if (!matchedRoutes || matchedRoutes.length === 0) {
594
- return '';
595
- }
596
-
597
- for (const match of matchedRoutes) {
598
- if (match.route.path && match.route.path !== '*') {
599
- const path = pickPath(match);
600
- const strippedPath = stripBasenameFromPathname(location.pathname, prefixWithSlash(match.pathnameBase));
601
-
602
- if (location.pathname === strippedPath) {
603
- return trimSlash(strippedPath);
604
- }
605
-
606
- return trimSlash(
607
- trimSlash(path || '') +
608
- prefixWithSlash(
609
- rebuildRoutePathFromAllRoutes(
610
- allRoutes.filter(route => route !== match.route),
611
- {
612
- pathname: strippedPath,
613
- },
614
- ),
615
- ),
616
- );
617
- }
618
- }
619
-
620
- return '';
621
- }
622
-
623
- function getNormalizedName(
624
- routes,
625
- location,
626
- branches,
627
- basename = '',
628
- ) {
629
- if (!routes || routes.length === 0) {
630
- return [_stripBasename ? stripBasenameFromPathname(location.pathname, basename) : location.pathname, 'url'];
631
- }
632
-
633
- let pathBuilder = '';
634
-
635
- if (branches) {
636
- for (const branch of branches) {
637
- const route = branch.route;
638
- if (route) {
639
- // Early return if index route
640
- if (route.index) {
641
- return sendIndexPath(pathBuilder, branch.pathname, basename);
642
- }
643
- const path = route.path;
644
-
645
- // If path is not a wildcard and has no child routes, append the path
646
- if (path && !pathIsWildcardAndHasChildren(path, branch)) {
647
- const newPath = path[0] === '/' || pathBuilder[pathBuilder.length - 1] === '/' ? path : `/${path}`;
648
- pathBuilder = trimSlash(pathBuilder) + prefixWithSlash(newPath);
649
-
650
- // If the path matches the current location, return the path
651
- if (trimSlash(location.pathname) === trimSlash(basename + branch.pathname)) {
652
- if (
653
- // If the route defined on the element is something like
654
- // <Route path="/stores/:storeId/products/:productId" element={<div>Product</div>} />
655
- // We should check against the branch.pathname for the number of / separators
656
- getNumberOfUrlSegments(pathBuilder) !== getNumberOfUrlSegments(branch.pathname) &&
657
- // We should not count wildcard operators in the url segments calculation
658
- !pathEndsWithWildcard(pathBuilder)
659
- ) {
660
- return [(_stripBasename ? '' : basename) + newPath, 'route'];
661
- }
662
-
663
- // if the last character of the pathbuilder is a wildcard and there are children, remove the wildcard
664
- if (pathIsWildcardAndHasChildren(pathBuilder, branch)) {
665
- pathBuilder = pathBuilder.slice(0, -1);
666
- }
667
-
668
- return [(_stripBasename ? '' : basename) + pathBuilder, 'route'];
669
- }
670
- }
671
- }
672
- }
673
- }
674
-
675
- const fallbackTransactionName = _stripBasename
676
- ? stripBasenameFromPathname(location.pathname, basename)
677
- : location.pathname || '/';
678
-
679
- return [fallbackTransactionName, 'url'];
680
- }
681
-
682
- function updatePageloadTransaction(
576
+ function updatePageloadTransaction({
683
577
  activeRootSpan,
684
578
  location,
685
579
  routes,
686
580
  matches,
687
581
  basename,
688
582
  allRoutes,
583
+ }
584
+
689
585
  ) {
690
586
  const branches = Array.isArray(matches)
691
587
  ? matches
@@ -695,15 +591,15 @@ function updatePageloadTransaction(
695
591
  let name,
696
592
  source = 'url';
697
593
 
698
- const isInDescendantRoute = locationIsInsideDescendantRoute(location, allRoutes || routes);
594
+ const isInDescendantRoute = utils.locationIsInsideDescendantRoute(location, allRoutes || routes);
699
595
 
700
596
  if (isInDescendantRoute) {
701
- name = prefixWithSlash(rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
597
+ name = utils.prefixWithSlash(utils.rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
702
598
  source = 'route';
703
599
  }
704
600
 
705
601
  if (!isInDescendantRoute || !name) {
706
- [name, source] = getNormalizedName(routes, location, branches, basename);
602
+ [name, source] = utils.getNormalizedName(routes, location, branches, basename);
707
603
  }
708
604
 
709
605
  core.getCurrentScope().setTransactionName(name || '/');
@@ -742,7 +638,12 @@ function createV6CompatibleWithSentryReactRouterRouting(
742
638
  if (isMountRenderPass.current) {
743
639
  addRoutesToAllRoutes(routes);
744
640
 
745
- updatePageloadTransaction(getActiveRootSpan(), location, routes, undefined, undefined, Array.from(allRoutes));
641
+ updatePageloadTransaction({
642
+ activeRootSpan: getActiveRootSpan(),
643
+ location,
644
+ routes,
645
+ allRoutes: Array.from(allRoutes),
646
+ });
746
647
  isMountRenderPass.current = false;
747
648
  } else {
748
649
  handleNavigation({
@@ -786,19 +687,79 @@ function getActiveRootSpan() {
786
687
  }
787
688
 
788
689
  /**
789
- * Returns number of URL segments of a passed string URL.
690
+ * Handles updating an existing navigation span
790
691
  */
791
- function getNumberOfUrlSegments(url) {
792
- // split at '/' or at '\/' to split regex urls correctly
793
- return url.split(/\\?\//).filter(s => s.length > 0 && s !== ',').length;
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
+ }
794
752
  }
795
753
 
796
- exports.checkRouteForAsyncHandler = checkRouteForAsyncHandler;
754
+ exports.addResolvedRoutesToParent = addResolvedRoutesToParent;
755
+ exports.createNewNavigationSpan = createNewNavigationSpan;
797
756
  exports.createReactRouterV6CompatibleTracingIntegration = createReactRouterV6CompatibleTracingIntegration;
798
757
  exports.createV6CompatibleWithSentryReactRouterRouting = createV6CompatibleWithSentryReactRouterRouting;
799
758
  exports.createV6CompatibleWrapCreateBrowserRouter = createV6CompatibleWrapCreateBrowserRouter;
800
759
  exports.createV6CompatibleWrapCreateMemoryRouter = createV6CompatibleWrapCreateMemoryRouter;
801
760
  exports.createV6CompatibleWrapUseRoutes = createV6CompatibleWrapUseRoutes;
802
- exports.getNumberOfUrlSegments = getNumberOfUrlSegments;
761
+ exports.handleExistingNavigationSpan = handleExistingNavigationSpan;
803
762
  exports.handleNavigation = handleNavigation;
804
- //# sourceMappingURL=reactrouterv6-compat-utils.js.map
763
+ exports.processResolvedRoutes = processResolvedRoutes;
764
+ exports.updateNavigationSpan = updateNavigationSpan;
765
+ //# sourceMappingURL=instrumentation.js.map