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

Complexity

Total Complexity 92
Complexity/F 5.11

Size

Lines of Code 765
Function Count 18

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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

16 Functions

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

How to fix   Complexity   

Complexity

Complex classes like node_modules/@sentry/react/build/cjs/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
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