node_modules/@sentry/react/build/esm/reactrouter-compat-utils/instrumentation.js   F
last analyzed

Complexity

Total Complexity 92
Complexity/F 5.11

Size

Lines of Code 753
Function Count 18

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 92
eloc 538
mnd 74
bc 74
fnc 18
dl 0
loc 753
bpm 4.1111
cpm 5.1111
noi 0
c 0
b 0
f 0
rs 2

16 Functions

Rating   Name   Duplication   Size   Complexity  
A instrumentation.js ➔ getActiveRootSpan 0 13 4
C instrumentation.js ➔ createV6CompatibleWrapCreateBrowserRouter 0 78 8
C instrumentation.js ➔ wrapPatchRoutesOnNavigation 0 70 11
A instrumentation.js ➔ addResolvedRoutesToParent 0 20 2
B instrumentation.js ➔ handleExistingNavigationSpan 0 35 7
B instrumentation.js ➔ createV6CompatibleWithSentryReactRouterRouting 0 60 3
A instrumentation.js ➔ createNewNavigationSpan 0 26 2
A instrumentation.js ➔ updateNavigationSpan 0 42 4
C instrumentation.js ➔ processResolvedRoutes 0 50 10
B instrumentation.js ➔ updatePageloadTransaction 0 35 6
A instrumentation.js ➔ addRoutesToAllRoutes 0 7 1
B instrumentation.js ➔ createReactRouterV6CompatibleTracingIntegration 0 58 6
D instrumentation.js ➔ createV6CompatibleWrapCreateMemoryRouter 0 86 13
B instrumentation.js ➔ handleNavigation 0 33 6
A instrumentation.js ➔ getChildRoutesRecursively 0 17 3
B instrumentation.js ➔ createV6CompatibleWrapUseRoutes 0 57 6

How to fix   Complexity   

Complexity

Complex classes like node_modules/@sentry/react/build/esm/reactrouter-compat-utils/instrumentation.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import { browserTracingIntegration, WINDOW, startBrowserTracingPageLoadSpan, startBrowserTracingNavigationSpan } from '@sentry/browser';
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
import * as React from 'react';
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';
8
9
/* eslint-disable max-lines */
10
// Inspired from Donnie McNeal's solution:
11
// https://gist.github.com/wontondon/e8c4bdf2888875e4c755712e99279536
12
13
14
let _useEffect;
15
let _useLocation;
16
let _useNavigationType;
17
let _createRoutesFromChildren;
18
let _matchRoutes;
19
let _enableAsyncRouteHandlers = false;
20
21
const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
22
23
/**
24
 * Adds resolved routes as children to the parent route.
25
 * Prevents duplicate routes by checking if they already exist.
26
 */
27
function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
28
  const existingChildren = parentRoute.children || [];
29
30
  const newRoutes = resolvedRoutes.filter(
31
    newRoute =>
32
      !existingChildren.some(
33
        existing =>
34
          existing === newRoute ||
35
          (newRoute.path && existing.path === newRoute.path) ||
36
          (newRoute.id && existing.id === newRoute.id),
37
      ),
38
  );
39
40
  if (newRoutes.length > 0) {
41
    parentRoute.children = [...existingChildren, ...newRoutes];
42
  }
43
}
44
45
// Keeping as a global variable for cross-usage in multiple functions
46
const allRoutes = new Set();
47
48
/**
49
 * Processes resolved routes by adding them to allRoutes and checking for nested async handlers.
50
 */
51
function processResolvedRoutes(
52
  resolvedRoutes,
53
  parentRoute,
54
  currentLocation = null,
55
) {
56
  resolvedRoutes.forEach(child => {
57
    allRoutes.add(child);
58
    // Only check for async handlers if the feature is enabled
59
    if (_enableAsyncRouteHandlers) {
60
      checkRouteForAsyncHandler(child, processResolvedRoutes);
61
    }
62
  });
63
64
  if (parentRoute) {
65
    // If a parent route is provided, add the resolved routes as children to the parent route
66
    addResolvedRoutesToParent(resolvedRoutes, parentRoute);
67
  }
68
69
  // After processing lazy routes, check if we need to update an active transaction
70
  const activeRootSpan = getActiveRootSpan();
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
85
    if (location) {
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
      }
98
    }
99
  }
100
}
101
102
/**
103
 * Updates a navigation span with the correct route name after lazy routes have been loaded.
104
 */
105
function updateNavigationSpan(
106
  activeRootSpan,
107
  location,
108
  allRoutes,
109
  forceUpdate = false,
110
  matchRoutes,
111
) {
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
    );
131
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);
137
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
      );
144
    }
145
  }
146
}
147
148
/**
149
 * Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions.
150
 */
151
function createV6CompatibleWrapCreateBrowserRouter
152
153
(
154
  createRouterFunction,
155
  version,
156
) {
157
  if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
158
    DEBUG_BUILD &&
159
      debug.warn(
160
        `reactRouterV${version}Instrumentation was unable to wrap the \`createRouter\` function because of one or more missing parameters.`,
161
      );
162
163
    return createRouterFunction;
164
  }
165
166
  return function (routes, opts) {
167
    addRoutesToAllRoutes(routes);
168
169
    // Check for async handlers that might contain sub-route declarations (only if enabled)
170
    if (_enableAsyncRouteHandlers) {
171
      for (const route of routes) {
172
        checkRouteForAsyncHandler(route, processResolvedRoutes);
173
      }
174
    }
175
176
    // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
177
    const wrappedOpts = wrapPatchRoutesOnNavigation(opts);
178
179
    const router = createRouterFunction(routes, wrappedOpts);
180
    const basename = opts?.basename;
181
182
    const activeRootSpan = getActiveRootSpan();
183
184
    // The initial load ends when `createBrowserRouter` is called.
185
    // This is the earliest convenient time to update the transaction name.
186
    // Callbacks to `router.subscribe` are not called for the initial load.
187
    if (router.state.historyAction === 'POP' && activeRootSpan) {
188
      updatePageloadTransaction({
189
        activeRootSpan,
190
        location: router.state.location,
191
        routes,
192
        basename,
193
        allRoutes: Array.from(allRoutes),
194
      });
195
    }
196
197
    router.subscribe((state) => {
198
      if (state.historyAction === 'PUSH' || state.historyAction === 'POP') {
199
        // Wait for the next render if loading an unsettled route
200
        if (state.navigation.state !== 'idle') {
201
          requestAnimationFrame(() => {
202
            handleNavigation({
203
              location: state.location,
204
              routes,
205
              navigationType: state.historyAction,
206
              version,
207
              basename,
208
              allRoutes: Array.from(allRoutes),
209
            });
210
          });
211
        } else {
212
          handleNavigation({
213
            location: state.location,
214
            routes,
215
            navigationType: state.historyAction,
216
            version,
217
            basename,
218
            allRoutes: Array.from(allRoutes),
219
          });
220
        }
221
      }
222
    });
223
224
    return router;
225
  };
226
}
227
228
/**
229
 * Creates a wrapCreateMemoryRouter function that can be used with all React Router v6 compatible versions.
230
 */
231
function createV6CompatibleWrapCreateMemoryRouter
232
233
(
234
  createRouterFunction,
235
  version,
236
) {
237
  if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
238
    DEBUG_BUILD &&
239
      debug.warn(
240
        `reactRouterV${version}Instrumentation was unable to wrap the \`createMemoryRouter\` function because of one or more missing parameters.`,
241
      );
242
243
    return createRouterFunction;
244
  }
245
246
  return function (
247
    routes,
248
    opts
249
250
,
251
  ) {
252
    addRoutesToAllRoutes(routes);
253
254
    // Check for async handlers that might contain sub-route declarations (only if enabled)
255
    if (_enableAsyncRouteHandlers) {
256
      for (const route of routes) {
257
        checkRouteForAsyncHandler(route, processResolvedRoutes);
258
      }
259
    }
260
261
    // Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
262
    const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true);
263
264
    const router = createRouterFunction(routes, wrappedOpts);
265
    const basename = opts?.basename;
266
267
    const activeRootSpan = getActiveRootSpan();
268
    let initialEntry = undefined;
269
270
    const initialEntries = opts?.initialEntries;
271
    const initialIndex = opts?.initialIndex;
272
273
    const hasOnlyOneInitialEntry = initialEntries && initialEntries.length === 1;
274
    const hasIndexedEntry = initialIndex !== undefined && initialEntries && initialEntries[initialIndex];
275
276
    initialEntry = hasOnlyOneInitialEntry
277
      ? initialEntries[0]
278
      : hasIndexedEntry
279
        ? initialEntries[initialIndex]
280
        : undefined;
281
282
    const location = initialEntry
283
      ? typeof initialEntry === 'string'
284
        ? { pathname: initialEntry }
285
        : initialEntry
286
      : router.state.location;
287
288
    if (router.state.historyAction === 'POP' && activeRootSpan) {
289
      updatePageloadTransaction({
290
        activeRootSpan,
291
        location,
292
        routes,
293
        basename,
294
        allRoutes: Array.from(allRoutes),
295
      });
296
    }
297
298
    router.subscribe((state) => {
299
      const location = state.location;
300
      if (state.historyAction === 'PUSH' || state.historyAction === 'POP') {
301
        handleNavigation({
302
          location,
303
          routes,
304
          navigationType: state.historyAction,
305
          version,
306
          basename,
307
          allRoutes: Array.from(allRoutes),
308
        });
309
      }
310
    });
311
312
    return router;
313
  };
314
}
315
316
/**
317
 * Creates a browser tracing integration that can be used with all React Router v6 compatible versions.
318
 */
319
function createReactRouterV6CompatibleTracingIntegration(
320
  options,
321
  version,
322
) {
323
  const integration = browserTracingIntegration({
324
    ...options,
325
    instrumentPageLoad: false,
326
    instrumentNavigation: false,
327
  });
328
329
  const {
330
    useEffect,
331
    useLocation,
332
    useNavigationType,
333
    createRoutesFromChildren,
334
    matchRoutes,
335
    stripBasename,
336
    enableAsyncRouteHandlers = false,
337
    instrumentPageLoad = true,
338
    instrumentNavigation = true,
339
  } = options;
340
341
  return {
342
    ...integration,
343
    setup(client) {
344
      integration.setup(client);
345
346
      _useEffect = useEffect;
347
      _useLocation = useLocation;
348
      _useNavigationType = useNavigationType;
349
      _matchRoutes = matchRoutes;
350
      _createRoutesFromChildren = createRoutesFromChildren;
351
      _enableAsyncRouteHandlers = enableAsyncRouteHandlers;
352
353
      // Initialize the router utils with the required dependencies
354
      initializeRouterUtils(matchRoutes, stripBasename || false);
355
    },
356
    afterAllSetup(client) {
357
      integration.afterAllSetup(client);
358
359
      const initPathName = WINDOW.location?.pathname;
360
      if (instrumentPageLoad && initPathName) {
361
        startBrowserTracingPageLoadSpan(client, {
362
          name: initPathName,
363
          attributes: {
364
            [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
365
            [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
366
            [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.reactrouter_v${version}`,
367
          },
368
        });
369
      }
370
371
      if (instrumentNavigation) {
372
        CLIENTS_WITH_INSTRUMENT_NAVIGATION.add(client);
373
      }
374
    },
375
  };
376
}
377
378
function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
379
  if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
380
    DEBUG_BUILD &&
381
      debug.warn(
382
        'reactRouterV6Instrumentation was unable to wrap `useRoutes` because of one or more missing parameters.',
383
      );
384
385
    return origUseRoutes;
386
  }
387
388
  const SentryRoutes
389
390
 = (props) => {
391
    const isMountRenderPass = React.useRef(true);
392
    const { routes, locationArg } = props;
393
394
    const Routes = origUseRoutes(routes, locationArg);
395
396
    const location = _useLocation();
397
    const navigationType = _useNavigationType();
398
399
    // A value with stable identity to either pick `locationArg` if available or `location` if not
400
    const stableLocationParam =
401
      typeof locationArg === 'string' || locationArg?.pathname ? (locationArg ) : location;
402
403
    _useEffect(() => {
404
      const normalizedLocation =
405
        typeof stableLocationParam === 'string' ? { pathname: stableLocationParam } : stableLocationParam;
406
407
      if (isMountRenderPass.current) {
408
        addRoutesToAllRoutes(routes);
409
410
        updatePageloadTransaction({
411
          activeRootSpan: getActiveRootSpan(),
412
          location: normalizedLocation,
413
          routes,
414
          allRoutes: Array.from(allRoutes),
415
        });
416
        isMountRenderPass.current = false;
417
      } else {
418
        handleNavigation({
419
          location: normalizedLocation,
420
          routes,
421
          navigationType,
422
          version,
423
          allRoutes: Array.from(allRoutes),
424
        });
425
      }
426
    }, [navigationType, stableLocationParam]);
427
428
    return Routes;
429
  };
430
431
  // eslint-disable-next-line react/display-name
432
  return (routes, locationArg) => {
433
    return React.createElement(SentryRoutes, { routes: routes, locationArg: locationArg,} );
434
  };
435
}
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
510
function handleNavigation(opts
511
512
) {
513
  const { location, routes, navigationType, version, matches, basename, allRoutes } = opts;
514
  const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename);
515
516
  const client = getClient();
517
  if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) {
518
    return;
519
  }
520
521
  if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
522
    const [name, source] = resolveRouteNameAndSource(
523
      location,
524
      routes,
525
      allRoutes || routes,
526
      branches ,
527
      basename,
528
    );
529
530
    // Check if this might be a lazy route context
531
    const isLazyRouteContext = isLikelyLazyRouteContext(allRoutes || routes, location);
532
533
    const activeSpan = getActiveSpan();
534
    const spanJson = activeSpan && spanToJSON(activeSpan);
535
    const isAlreadyInNavigationSpan = spanJson?.op === 'navigation';
536
537
    // Cross usage can result in multiple navigation spans being created without this check
538
    if (isAlreadyInNavigationSpan && activeSpan && spanJson) {
539
      handleExistingNavigationSpan(activeSpan, spanJson, name, source, isLazyRouteContext);
540
    } else {
541
      createNewNavigationSpan(client, name, source, version, isLazyRouteContext);
542
    }
543
  }
544
}
545
546
function addRoutesToAllRoutes(routes) {
547
  routes.forEach(route => {
548
    const extractedChildRoutes = getChildRoutesRecursively(route);
549
550
    extractedChildRoutes.forEach(r => {
551
      allRoutes.add(r);
552
    });
553
  });
554
}
555
556
function getChildRoutesRecursively(route, allRoutes = new Set()) {
557
  if (!allRoutes.has(route)) {
558
    allRoutes.add(route);
559
560
    if (route.children && !route.index) {
561
      route.children.forEach(child => {
562
        const childRoutes = getChildRoutesRecursively(child, allRoutes);
563
564
        childRoutes.forEach(r => {
565
          allRoutes.add(r);
566
        });
567
      });
568
    }
569
  }
570
571
  return allRoutes;
572
}
573
574
function updatePageloadTransaction({
575
  activeRootSpan,
576
  location,
577
  routes,
578
  matches,
579
  basename,
580
  allRoutes,
581
}
582
583
) {
584
  const branches = Array.isArray(matches)
585
    ? matches
586
    : (_matchRoutes(allRoutes || routes, location, basename) );
587
588
  if (branches) {
589
    let name,
590
      source = 'url';
591
592
    const isInDescendantRoute = locationIsInsideDescendantRoute(location, allRoutes || routes);
593
594
    if (isInDescendantRoute) {
595
      name = prefixWithSlash(rebuildRoutePathFromAllRoutes(allRoutes || routes, location));
596
      source = 'route';
597
    }
598
599
    if (!isInDescendantRoute || !name) {
600
      [name, source] = getNormalizedName(routes, location, branches, basename);
601
    }
602
603
    getCurrentScope().setTransactionName(name || '/');
604
605
    if (activeRootSpan) {
606
      activeRootSpan.updateName(name);
607
      activeRootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
608
    }
609
  }
610
}
611
612
// eslint-disable-next-line @typescript-eslint/no-explicit-any
613
function createV6CompatibleWithSentryReactRouterRouting(
614
  Routes,
615
  version,
616
) {
617
  if (!_useEffect || !_useLocation || !_useNavigationType || !_createRoutesFromChildren || !_matchRoutes) {
618
    DEBUG_BUILD &&
619
      debug.warn(`reactRouterV6Instrumentation was unable to wrap Routes because of one or more missing parameters.
620
      useEffect: ${_useEffect}. useLocation: ${_useLocation}. useNavigationType: ${_useNavigationType}.
621
      createRoutesFromChildren: ${_createRoutesFromChildren}. matchRoutes: ${_matchRoutes}.`);
622
623
    return Routes;
624
  }
625
626
  const SentryRoutes = (props) => {
627
    const isMountRenderPass = React.useRef(true);
628
629
    const location = _useLocation();
630
    const navigationType = _useNavigationType();
631
632
    _useEffect(
633
      () => {
634
        const routes = _createRoutesFromChildren(props.children) ;
635
636
        if (isMountRenderPass.current) {
637
          addRoutesToAllRoutes(routes);
638
639
          updatePageloadTransaction({
640
            activeRootSpan: getActiveRootSpan(),
641
            location,
642
            routes,
643
            allRoutes: Array.from(allRoutes),
644
          });
645
          isMountRenderPass.current = false;
646
        } else {
647
          handleNavigation({
648
            location,
649
            routes,
650
            navigationType,
651
            version,
652
            allRoutes: Array.from(allRoutes),
653
          });
654
        }
655
      },
656
      // `props.children` is purposely not included in the dependency array, because we do not want to re-run this effect
657
      // when the children change. We only want to start transactions when the location or navigation type change.
658
      [location, navigationType],
659
    );
660
661
    // @ts-expect-error Setting more specific React Component typing for `R` generic above
662
    // will break advanced type inference done by react router params
663
    return React.createElement(Routes, { ...props,} );
664
  };
665
666
  hoistNonReactStatics(SentryRoutes, Routes);
667
668
  // @ts-expect-error Setting more specific React Component typing for `R` generic above
669
  // will break advanced type inference done by react router params
670
  return SentryRoutes;
671
}
672
673
function getActiveRootSpan() {
674
  const span = getActiveSpan();
675
  const rootSpan = span ? getRootSpan(span) : undefined;
676
677
  if (!rootSpan) {
678
    return undefined;
679
  }
680
681
  const op = spanToJSON(rootSpan).op;
682
683
  // Only use this root span if it is a pageload or navigation span
684
  return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;
685
}
686
687
/**
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
725
 */
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
  }
750
}
751
752
export { addResolvedRoutesToParent, createNewNavigationSpan, createReactRouterV6CompatibleTracingIntegration, createV6CompatibleWithSentryReactRouterRouting, createV6CompatibleWrapCreateBrowserRouter, createV6CompatibleWrapCreateMemoryRouter, createV6CompatibleWrapUseRoutes, handleExistingNavigationSpan, handleNavigation, processResolvedRoutes, updateNavigationSpan };
753
//# sourceMappingURL=instrumentation.js.map
754