@depup/sentry__react 10.44.0-depup.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/build/cjs/constants.js +12 -0
  4. package/build/cjs/constants.js.map +1 -0
  5. package/build/cjs/debug-build.js +11 -0
  6. package/build/cjs/debug-build.js.map +1 -0
  7. package/build/cjs/error.js +113 -0
  8. package/build/cjs/error.js.map +1 -0
  9. package/build/cjs/errorboundary.js +170 -0
  10. package/build/cjs/errorboundary.js.map +1 -0
  11. package/build/cjs/hoist-non-react-statics.js +159 -0
  12. package/build/cjs/hoist-non-react-statics.js.map +1 -0
  13. package/build/cjs/index.js +51 -0
  14. package/build/cjs/index.js.map +1 -0
  15. package/build/cjs/profiler.js +226 -0
  16. package/build/cjs/profiler.js.map +1 -0
  17. package/build/cjs/reactrouter-compat-utils/instrumentation.js +1295 -0
  18. package/build/cjs/reactrouter-compat-utils/instrumentation.js.map +1 -0
  19. package/build/cjs/reactrouter-compat-utils/lazy-routes.js +160 -0
  20. package/build/cjs/reactrouter-compat-utils/lazy-routes.js.map +1 -0
  21. package/build/cjs/reactrouter-compat-utils/route-manifest.js +194 -0
  22. package/build/cjs/reactrouter-compat-utils/route-manifest.js.map +1 -0
  23. package/build/cjs/reactrouter-compat-utils/utils.js +336 -0
  24. package/build/cjs/reactrouter-compat-utils/utils.js.map +1 -0
  25. package/build/cjs/reactrouter.js +236 -0
  26. package/build/cjs/reactrouter.js.map +1 -0
  27. package/build/cjs/reactrouterv3.js +130 -0
  28. package/build/cjs/reactrouterv3.js.map +1 -0
  29. package/build/cjs/reactrouterv6.js +61 -0
  30. package/build/cjs/reactrouterv6.js.map +1 -0
  31. package/build/cjs/reactrouterv7.js +61 -0
  32. package/build/cjs/reactrouterv7.js.map +1 -0
  33. package/build/cjs/redux.js +106 -0
  34. package/build/cjs/redux.js.map +1 -0
  35. package/build/cjs/sdk.js +21 -0
  36. package/build/cjs/sdk.js.map +1 -0
  37. package/build/cjs/tanstackrouter.js +134 -0
  38. package/build/cjs/tanstackrouter.js.map +1 -0
  39. package/build/esm/constants.js +8 -0
  40. package/build/esm/constants.js.map +1 -0
  41. package/build/esm/debug-build.js +9 -0
  42. package/build/esm/debug-build.js.map +1 -0
  43. package/build/esm/error.js +108 -0
  44. package/build/esm/error.js.map +1 -0
  45. package/build/esm/errorboundary.js +166 -0
  46. package/build/esm/errorboundary.js.map +1 -0
  47. package/build/esm/hoist-non-react-statics.js +157 -0
  48. package/build/esm/hoist-non-react-statics.js.map +1 -0
  49. package/build/esm/index.js +12 -0
  50. package/build/esm/index.js.map +1 -0
  51. package/build/esm/package.json +1 -0
  52. package/build/esm/profiler.js +221 -0
  53. package/build/esm/profiler.js.map +1 -0
  54. package/build/esm/reactrouter-compat-utils/instrumentation.js +1281 -0
  55. package/build/esm/reactrouter-compat-utils/instrumentation.js.map +1 -0
  56. package/build/esm/reactrouter-compat-utils/lazy-routes.js +156 -0
  57. package/build/esm/reactrouter-compat-utils/lazy-routes.js.map +1 -0
  58. package/build/esm/reactrouter-compat-utils/route-manifest.js +191 -0
  59. package/build/esm/reactrouter-compat-utils/route-manifest.js.map +1 -0
  60. package/build/esm/reactrouter-compat-utils/utils.js +320 -0
  61. package/build/esm/reactrouter-compat-utils/utils.js.map +1 -0
  62. package/build/esm/reactrouter.js +232 -0
  63. package/build/esm/reactrouter.js.map +1 -0
  64. package/build/esm/reactrouterv3.js +128 -0
  65. package/build/esm/reactrouterv3.js.map +1 -0
  66. package/build/esm/reactrouterv6.js +55 -0
  67. package/build/esm/reactrouterv6.js.map +1 -0
  68. package/build/esm/reactrouterv7.js +55 -0
  69. package/build/esm/reactrouterv7.js.map +1 -0
  70. package/build/esm/redux.js +104 -0
  71. package/build/esm/redux.js.map +1 -0
  72. package/build/esm/sdk.js +19 -0
  73. package/build/esm/sdk.js.map +1 -0
  74. package/build/esm/tanstackrouter.js +132 -0
  75. package/build/esm/tanstackrouter.js.map +1 -0
  76. package/build/types/constants.d.ts +4 -0
  77. package/build/types/constants.d.ts.map +1 -0
  78. package/build/types/debug-build.d.ts +7 -0
  79. package/build/types/debug-build.d.ts.map +1 -0
  80. package/build/types/error.d.ts +41 -0
  81. package/build/types/error.d.ts.map +1 -0
  82. package/build/types/errorboundary.d.ts +87 -0
  83. package/build/types/errorboundary.d.ts.map +1 -0
  84. package/build/types/hoist-non-react-statics.d.ts +21 -0
  85. package/build/types/hoist-non-react-statics.d.ts.map +1 -0
  86. package/build/types/index.d.ts +13 -0
  87. package/build/types/index.d.ts.map +1 -0
  88. package/build/types/profiler.d.ts +56 -0
  89. package/build/types/profiler.d.ts.map +1 -0
  90. package/build/types/reactrouter-compat-utils/index.d.ts +5 -0
  91. package/build/types/reactrouter-compat-utils/index.d.ts.map +1 -0
  92. package/build/types/reactrouter-compat-utils/instrumentation.d.ts +117 -0
  93. package/build/types/reactrouter-compat-utils/instrumentation.d.ts.map +1 -0
  94. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts +18 -0
  95. package/build/types/reactrouter-compat-utils/lazy-routes.d.ts.map +1 -0
  96. package/build/types/reactrouter-compat-utils/route-manifest.d.ts +13 -0
  97. package/build/types/reactrouter-compat-utils/route-manifest.d.ts.map +1 -0
  98. package/build/types/reactrouter-compat-utils/utils.d.ts +71 -0
  99. package/build/types/reactrouter-compat-utils/utils.d.ts.map +1 -0
  100. package/build/types/reactrouter.d.ts +41 -0
  101. package/build/types/reactrouter.d.ts.map +1 -0
  102. package/build/types/reactrouterv3.d.ts +29 -0
  103. package/build/types/reactrouterv3.d.ts.map +1 -0
  104. package/build/types/reactrouterv6.d.ts +32 -0
  105. package/build/types/reactrouterv6.d.ts.map +1 -0
  106. package/build/types/reactrouterv7.d.ts +32 -0
  107. package/build/types/reactrouterv7.d.ts.map +1 -0
  108. package/build/types/redux.d.ts +38 -0
  109. package/build/types/redux.d.ts.map +1 -0
  110. package/build/types/sdk.d.ts +7 -0
  111. package/build/types/sdk.d.ts.map +1 -0
  112. package/build/types/tanstackrouter.d.ts +13 -0
  113. package/build/types/tanstackrouter.d.ts.map +1 -0
  114. package/build/types/types.d.ts +163 -0
  115. package/build/types/types.d.ts.map +1 -0
  116. package/build/types/vendor/tanstackrouter-types.d.ts +36 -0
  117. package/build/types/vendor/tanstackrouter-types.d.ts.map +1 -0
  118. package/build/types-ts3.8/constants.d.ts +4 -0
  119. package/build/types-ts3.8/debug-build.d.ts +7 -0
  120. package/build/types-ts3.8/error.d.ts +41 -0
  121. package/build/types-ts3.8/errorboundary.d.ts +87 -0
  122. package/build/types-ts3.8/hoist-non-react-statics.d.ts +21 -0
  123. package/build/types-ts3.8/index.d.ts +13 -0
  124. package/build/types-ts3.8/profiler.d.ts +56 -0
  125. package/build/types-ts3.8/reactrouter-compat-utils/index.d.ts +5 -0
  126. package/build/types-ts3.8/reactrouter-compat-utils/instrumentation.d.ts +117 -0
  127. package/build/types-ts3.8/reactrouter-compat-utils/lazy-routes.d.ts +18 -0
  128. package/build/types-ts3.8/reactrouter-compat-utils/route-manifest.d.ts +13 -0
  129. package/build/types-ts3.8/reactrouter-compat-utils/utils.d.ts +77 -0
  130. package/build/types-ts3.8/reactrouter.d.ts +41 -0
  131. package/build/types-ts3.8/reactrouterv3.d.ts +29 -0
  132. package/build/types-ts3.8/reactrouterv6.d.ts +32 -0
  133. package/build/types-ts3.8/reactrouterv7.d.ts +32 -0
  134. package/build/types-ts3.8/redux.d.ts +38 -0
  135. package/build/types-ts3.8/sdk.d.ts +7 -0
  136. package/build/types-ts3.8/tanstackrouter.d.ts +13 -0
  137. package/build/types-ts3.8/types.d.ts +163 -0
  138. package/build/types-ts3.8/vendor/tanstackrouter-types.d.ts +36 -0
  139. package/changes.json +5 -0
  140. package/package.json +108 -0
@@ -0,0 +1,1295 @@
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
+
22
+ let _enableAsyncRouteHandlers = false;
23
+ let _lazyRouteTimeout = 3000;
24
+ let _lazyRouteManifest;
25
+ let _basename = '';
26
+
27
+ const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
28
+
29
+ // Prevents duplicate spans when router.subscribe fires multiple times
30
+ const activeNavigationSpans = new WeakMap
31
+
32
+ ();
33
+
34
+ // Exported for testing only
35
+ const allRoutes = new Set();
36
+
37
+ // Tracks lazy route loads to wait before finalizing span names
38
+ const pendingLazyRouteLoads = new WeakMap();
39
+
40
+ // Tracks deferred lazy route promises that can be resolved when patchRoutesOnNavigation is called
41
+ const deferredLazyRouteResolvers = new WeakMap();
42
+
43
+ /**
44
+ * Schedules a callback using requestAnimationFrame when available (browser),
45
+ * or falls back to setTimeout for SSR environments (Node.js, createMemoryRouter tests).
46
+ */
47
+ function scheduleCallback(callback) {
48
+ if (browser.WINDOW?.requestAnimationFrame) {
49
+ return browser.WINDOW.requestAnimationFrame(callback);
50
+ }
51
+ return setTimeout(callback, 0) ;
52
+ }
53
+
54
+ /**
55
+ * Cancels a scheduled callback, handling both RAF (browser) and timeout (SSR) IDs.
56
+ */
57
+ function cancelScheduledCallback(id) {
58
+ if (browser.WINDOW?.cancelAnimationFrame) {
59
+ browser.WINDOW.cancelAnimationFrame(id);
60
+ } else {
61
+ clearTimeout(id);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Computes location key for duplicate detection. Normalizes undefined/null to empty strings.
67
+ * Exported for testing.
68
+ */
69
+ function computeLocationKey(location) {
70
+ return `${location.pathname}${location.search || ''}${location.hash || ''}`;
71
+ }
72
+
73
+ /**
74
+ * Checks if a route name is parameterized (contains route parameters like :id or wildcards like *)
75
+ * vs a raw URL path.
76
+ */
77
+ function isParameterizedRoute(routeName) {
78
+ return routeName.includes(':') || routeName.includes('*');
79
+ }
80
+
81
+ /**
82
+ * Determines if a navigation should be skipped as a duplicate, and if an existing span should be updated.
83
+ * Exported for testing.
84
+ *
85
+ * @returns An object with:
86
+ * - skip: boolean - Whether to skip creating a new span
87
+ * - shouldUpdate: boolean - Whether to update the existing span name (wildcard upgrade)
88
+ */
89
+ function shouldSkipNavigation(
90
+ trackedNav
91
+
92
+ ,
93
+ locationKey,
94
+ proposedName,
95
+ spanHasEnded,
96
+ ) {
97
+ if (!trackedNav) {
98
+ return { skip: false, shouldUpdate: false };
99
+ }
100
+
101
+ // Check if this is a duplicate navigation (same location)
102
+ // 1. If it's a placeholder, it's always a duplicate (we're waiting for the real one)
103
+ // 2. If it's a real span, it's a duplicate only if it hasn't ended yet
104
+ const isDuplicate = trackedNav.locationKey === locationKey && (trackedNav.isPlaceholder || !spanHasEnded);
105
+
106
+ if (isDuplicate) {
107
+ // Check if we should update the span name with a better route
108
+ // Allow updates if:
109
+ // 1. Current has wildcard and new doesn't (wildcard → parameterized upgrade)
110
+ // 2. Current is raw path and new is parameterized (raw → parameterized upgrade)
111
+ // 3. New name is different and more specific (longer, indicating nested routes resolved)
112
+ const currentHasWildcard = !!trackedNav.routeName && utils.transactionNameHasWildcard(trackedNav.routeName);
113
+ const proposedHasWildcard = utils.transactionNameHasWildcard(proposedName);
114
+ const currentIsParameterized = !!trackedNav.routeName && isParameterizedRoute(trackedNav.routeName);
115
+ const proposedIsParameterized = isParameterizedRoute(proposedName);
116
+
117
+ const isWildcardUpgrade = currentHasWildcard && !proposedHasWildcard;
118
+ const isRawToParameterized = !currentIsParameterized && proposedIsParameterized;
119
+ const isMoreSpecific =
120
+ proposedName !== trackedNav.routeName &&
121
+ proposedName.length > (trackedNav.routeName?.length || 0) &&
122
+ !proposedHasWildcard;
123
+
124
+ const shouldUpdate = !!(trackedNav.routeName && (isWildcardUpgrade || isRawToParameterized || isMoreSpecific));
125
+
126
+ return { skip: true, shouldUpdate };
127
+ }
128
+
129
+ return { skip: false, shouldUpdate: false };
130
+ }
131
+
132
+ function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
133
+ const existingChildren = parentRoute.children || [];
134
+
135
+ const newRoutes = resolvedRoutes.filter(
136
+ newRoute =>
137
+ !existingChildren.some(
138
+ existing =>
139
+ existing === newRoute ||
140
+ (newRoute.path && existing.path === newRoute.path) ||
141
+ (newRoute.id && existing.id === newRoute.id),
142
+ ),
143
+ );
144
+
145
+ if (newRoutes.length > 0) {
146
+ parentRoute.children = [...existingChildren, ...newRoutes];
147
+ }
148
+ }
149
+
150
+ /** Registers a pending lazy route load promise for a span. */
151
+ function trackLazyRouteLoad(span, promise) {
152
+ let promises = pendingLazyRouteLoads.get(span);
153
+ if (!promises) {
154
+ promises = new Set();
155
+ pendingLazyRouteLoads.set(span, promises);
156
+ }
157
+ promises.add(promise);
158
+
159
+ // Clean up when promise resolves/rejects
160
+ // oxlint-disable-next-line typescript/no-floating-promises
161
+ promise.finally(() => {
162
+ const currentPromises = pendingLazyRouteLoads.get(span);
163
+ if (currentPromises) {
164
+ currentPromises.delete(promise);
165
+ }
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Creates a deferred promise for a span that will be resolved when patchRoutesOnNavigation is called.
171
+ * This ensures that patchedEnd waits for patchRoutesOnNavigation to be called before ending the span.
172
+ */
173
+ function createDeferredLazyRoutePromise(span) {
174
+ const deferredPromise = new Promise(resolve => {
175
+ deferredLazyRouteResolvers.set(span, resolve);
176
+ });
177
+
178
+ trackLazyRouteLoad(span, deferredPromise);
179
+ }
180
+
181
+ /**
182
+ * Resolves the deferred lazy route promise for a span.
183
+ * Called when patchRoutesOnNavigation is invoked.
184
+ */
185
+ function resolveDeferredLazyRoutePromise(span) {
186
+ const resolver = deferredLazyRouteResolvers.get(span);
187
+ if (resolver) {
188
+ resolver();
189
+ deferredLazyRouteResolvers.delete(span);
190
+ // Clear the flag so patchSpanEnd doesn't wait unnecessarily for routes that have already loaded
191
+ if ((span ).__sentry_may_have_lazy_routes__) {
192
+ (span ).__sentry_may_have_lazy_routes__ = false;
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Processes resolved routes by adding them to allRoutes and checking for nested async handlers.
199
+ * When capturedSpan is provided, updates that specific span instead of the current active span.
200
+ * This prevents race conditions where a lazy handler resolves after the user has navigated away.
201
+ */
202
+ function processResolvedRoutes(
203
+ resolvedRoutes,
204
+ parentRoute,
205
+ currentLocation = null,
206
+ capturedSpan,
207
+ ) {
208
+ resolvedRoutes.forEach(child => {
209
+ allRoutes.add(child);
210
+ // Only check for async handlers if the feature is enabled
211
+ if (_enableAsyncRouteHandlers) {
212
+ lazyRoutes.checkRouteForAsyncHandler(child, processResolvedRoutes);
213
+ }
214
+ });
215
+
216
+ if (parentRoute) {
217
+ // If a parent route is provided, add the resolved routes as children to the parent route
218
+ addResolvedRoutesToParent(resolvedRoutes, parentRoute);
219
+ }
220
+
221
+ // Use captured span if provided, otherwise fall back to current active span
222
+ const targetSpan = capturedSpan ?? utils.getActiveRootSpan();
223
+ if (targetSpan) {
224
+ const spanJson = core.spanToJSON(targetSpan);
225
+
226
+ // Skip update if span has already ended (timestamp is set when span.end() is called)
227
+ if (spanJson.timestamp) {
228
+ debugBuild.DEBUG_BUILD && core.debug.warn('[React Router] Lazy handler resolved after span ended - skipping update');
229
+ return;
230
+ }
231
+
232
+ const spanOp = spanJson.op;
233
+
234
+ // Use captured location for route matching (ensures we match against the correct route)
235
+ // Fall back to window.location only if no captured location and no captured span
236
+ // (i.e., this is not from an async handler)
237
+ let location = currentLocation;
238
+ if (!location && !capturedSpan) {
239
+ if (typeof browser.WINDOW !== 'undefined') {
240
+ const globalLocation = browser.WINDOW.location;
241
+ if (globalLocation?.pathname) {
242
+ location = { pathname: globalLocation.pathname };
243
+ }
244
+ }
245
+ }
246
+
247
+ if (location) {
248
+ if (spanOp === 'pageload') {
249
+ // Re-run the pageload transaction update with the newly loaded routes
250
+ updatePageloadTransaction({
251
+ activeRootSpan: targetSpan,
252
+ location: { pathname: location.pathname },
253
+ routes: Array.from(allRoutes),
254
+ allRoutes: Array.from(allRoutes),
255
+ });
256
+ } else if (spanOp === 'navigation') {
257
+ // For navigation spans, update the name with the newly loaded routes
258
+ updateNavigationSpan(targetSpan, location, Array.from(allRoutes), false, _matchRoutes);
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Updates a navigation span with the correct route name after lazy routes have been loaded.
266
+ */
267
+ function updateNavigationSpan(
268
+ activeRootSpan,
269
+ location,
270
+ allRoutes,
271
+ forceUpdate = false,
272
+ matchRoutes,
273
+ ) {
274
+ const spanJson = core.spanToJSON(activeRootSpan);
275
+ const currentName = spanJson.description;
276
+
277
+ const hasBeenNamed = (activeRootSpan )?.__sentry_navigation_name_set__;
278
+ const currentNameHasWildcard = currentName && utils.transactionNameHasWildcard(currentName);
279
+ const shouldUpdate = !hasBeenNamed || forceUpdate || currentNameHasWildcard;
280
+
281
+ if (shouldUpdate && !spanJson.timestamp) {
282
+ const currentBranches = matchRoutes(allRoutes, location);
283
+ const [name, source] = utils.resolveRouteNameAndSource(
284
+ location,
285
+ allRoutes,
286
+ allRoutes,
287
+ (currentBranches ) || [],
288
+ _basename,
289
+ _lazyRouteManifest,
290
+ _enableAsyncRouteHandlers,
291
+ );
292
+
293
+ const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
294
+ const isImprovement =
295
+ name &&
296
+ (!currentName || // No current name - always set
297
+ (!hasBeenNamed && (currentSource !== 'route' || source === 'route')) || // Not finalized - allow unless downgrading route→url
298
+ (currentSource !== 'route' && source === 'route') || // URL → route upgrade
299
+ (currentSource === 'route' && source === 'route' && currentNameHasWildcard)); // Route → better route (only if current has wildcard)
300
+ if (isImprovement) {
301
+ activeRootSpan.updateName(name);
302
+ activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
303
+
304
+ // Only mark as finalized for non-wildcard route names (allows URL→route upgrades).
305
+ if (!utils.transactionNameHasWildcard(name) && source === 'route') {
306
+ core.addNonEnumerableProperty(
307
+ activeRootSpan ,
308
+ '__sentry_navigation_name_set__',
309
+ true,
310
+ );
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ function setupRouterSubscription(
317
+ router,
318
+ routes,
319
+ version,
320
+ basename,
321
+ activeRootSpan,
322
+ ) {
323
+ let isInitialPageloadComplete = false;
324
+ let hasSeenPageloadSpan = !!activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload';
325
+ let hasSeenPopAfterPageload = false;
326
+ let scheduledNavigationHandler = null;
327
+ let lastHandledPathname = null;
328
+
329
+ router.subscribe((state) => {
330
+ if (!isInitialPageloadComplete) {
331
+ const currentRootSpan = utils.getActiveRootSpan();
332
+ const isCurrentlyInPageload = currentRootSpan && core.spanToJSON(currentRootSpan).op === 'pageload';
333
+
334
+ if (isCurrentlyInPageload) {
335
+ hasSeenPageloadSpan = true;
336
+ } else if (hasSeenPageloadSpan) {
337
+ if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) {
338
+ hasSeenPopAfterPageload = true;
339
+ } else {
340
+ isInitialPageloadComplete = true;
341
+ }
342
+ }
343
+ }
344
+
345
+ const shouldHandleNavigation =
346
+ state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete);
347
+
348
+ if (shouldHandleNavigation) {
349
+ // Include search and hash to allow query/hash-only navigations
350
+ // Use computeLocationKey() to ensure undefined/null values are normalized to empty strings
351
+ const currentLocationKey = computeLocationKey(state.location);
352
+ const navigationHandler = () => {
353
+ // Prevent multiple calls for the same location within the same navigation cycle
354
+ if (lastHandledPathname === currentLocationKey) {
355
+ return;
356
+ }
357
+ lastHandledPathname = currentLocationKey;
358
+ scheduledNavigationHandler = null;
359
+ handleNavigation({
360
+ location: state.location,
361
+ routes,
362
+ navigationType: state.historyAction,
363
+ version,
364
+ basename,
365
+ allRoutes: Array.from(allRoutes),
366
+ });
367
+ };
368
+
369
+ if (state.navigation.state !== 'idle') {
370
+ // Navigation in progress - reset if location changed
371
+ if (lastHandledPathname !== currentLocationKey) {
372
+ lastHandledPathname = null;
373
+ }
374
+ // Cancel any previously scheduled handler to avoid duplicates
375
+ if (scheduledNavigationHandler !== null) {
376
+ cancelScheduledCallback(scheduledNavigationHandler);
377
+ }
378
+ scheduledNavigationHandler = scheduleCallback(navigationHandler);
379
+ } else {
380
+ // Navigation completed - cancel scheduled handler if any, then call immediately
381
+ if (scheduledNavigationHandler !== null) {
382
+ cancelScheduledCallback(scheduledNavigationHandler);
383
+ scheduledNavigationHandler = null;
384
+ }
385
+ navigationHandler();
386
+ // Don't reset - next navigation cycle resets to prevent duplicates within same cycle.
387
+ }
388
+ }
389
+ });
390
+ }
391
+
392
+ /**
393
+ * Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions.
394
+ */
395
+ function createV6CompatibleWrapCreateBrowserRouter
396
+
397
+ (
398
+ createRouterFunction,
399
+ version,
400
+ ) {
401
+ if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
402
+ debugBuild.DEBUG_BUILD &&
403
+ core.debug.warn(
404
+ `reactRouterV${version}Instrumentation was unable to wrap the \`createRouter\` function because of one or more missing parameters.`,
405
+ );
406
+
407
+ return createRouterFunction;
408
+ }
409
+
410
+ return function (routes, opts) {
411
+ addRoutesToAllRoutes(routes);
412
+
413
+ if (_enableAsyncRouteHandlers) {
414
+ for (const route of routes) {
415
+ lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
416
+ }
417
+ }
418
+
419
+ // Capture the active span BEFORE creating the router.
420
+ // This is important because the span might end (due to idle timeout) before
421
+ // patchRoutesOnNavigation is called by React Router.
422
+ const activeRootSpan = utils.getActiveRootSpan();
423
+
424
+ // If patchRoutesOnNavigation is provided and we have an active span,
425
+ // mark the span as having potential lazy routes and create a deferred promise.
426
+ const hasPatchRoutesOnNavigation =
427
+ opts && 'patchRoutesOnNavigation' in opts && typeof opts.patchRoutesOnNavigation === 'function';
428
+ if (hasPatchRoutesOnNavigation && activeRootSpan) {
429
+ // Mark the span as potentially having lazy routes
430
+ core.addNonEnumerableProperty(
431
+ activeRootSpan ,
432
+ '__sentry_may_have_lazy_routes__',
433
+ true,
434
+ );
435
+ createDeferredLazyRoutePromise(activeRootSpan);
436
+ }
437
+
438
+ // Pass the captured span to wrapPatchRoutesOnNavigation so it uses the same span
439
+ // even if the span has ended by the time patchRoutesOnNavigation is called.
440
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts, false, activeRootSpan);
441
+ const router = createRouterFunction(routes, wrappedOpts);
442
+ const basename = opts?.basename;
443
+
444
+ if (router.state.historyAction === 'POP' && activeRootSpan) {
445
+ updatePageloadTransaction({
446
+ activeRootSpan,
447
+ location: router.state.location,
448
+ routes,
449
+ basename,
450
+ allRoutes: Array.from(allRoutes),
451
+ });
452
+ }
453
+
454
+ // Store basename for use in updateNavigationSpan
455
+ _basename = basename || '';
456
+
457
+ setupRouterSubscription(router, routes, version, basename, activeRootSpan);
458
+
459
+ return router;
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Creates a wrapCreateMemoryRouter function that can be used with all React Router v6 compatible versions.
465
+ */
466
+ function createV6CompatibleWrapCreateMemoryRouter
467
+
468
+ (
469
+ createRouterFunction,
470
+ version,
471
+ ) {
472
+ if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
473
+ debugBuild.DEBUG_BUILD &&
474
+ core.debug.warn(
475
+ `reactRouterV${version}Instrumentation was unable to wrap the \`createMemoryRouter\` function because of one or more missing parameters.`,
476
+ );
477
+
478
+ return createRouterFunction;
479
+ }
480
+
481
+ return function (
482
+ routes,
483
+ opts
484
+
485
+ ,
486
+ ) {
487
+ addRoutesToAllRoutes(routes);
488
+
489
+ if (_enableAsyncRouteHandlers) {
490
+ for (const route of routes) {
491
+ lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
492
+ }
493
+ }
494
+
495
+ // Capture the active span BEFORE creating the router (same as browser router)
496
+ const memoryActiveRootSpanEarly = utils.getActiveRootSpan();
497
+
498
+ // If patchRoutesOnNavigation is provided and we have an active span,
499
+ // mark the span as having potential lazy routes and create a deferred promise.
500
+ const hasPatchRoutesOnNavigation =
501
+ opts && 'patchRoutesOnNavigation' in opts && typeof opts.patchRoutesOnNavigation === 'function';
502
+ if (hasPatchRoutesOnNavigation && memoryActiveRootSpanEarly) {
503
+ core.addNonEnumerableProperty(
504
+ memoryActiveRootSpanEarly ,
505
+ '__sentry_may_have_lazy_routes__',
506
+ true,
507
+ );
508
+ createDeferredLazyRoutePromise(memoryActiveRootSpanEarly);
509
+ }
510
+
511
+ const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true, memoryActiveRootSpanEarly);
512
+
513
+ const router = createRouterFunction(routes, wrappedOpts);
514
+ const basename = opts?.basename;
515
+
516
+ let initialEntry = undefined;
517
+
518
+ const initialEntries = opts?.initialEntries;
519
+ const initialIndex = opts?.initialIndex;
520
+
521
+ const hasOnlyOneInitialEntry = initialEntries?.length === 1;
522
+ const hasIndexedEntry = initialIndex !== undefined && initialEntries?.[initialIndex];
523
+
524
+ initialEntry = hasOnlyOneInitialEntry
525
+ ? initialEntries[0]
526
+ : hasIndexedEntry
527
+ ? initialEntries[initialIndex]
528
+ : undefined;
529
+
530
+ const location = initialEntry
531
+ ? typeof initialEntry === 'string'
532
+ ? { pathname: initialEntry }
533
+ : initialEntry
534
+ : router.state.location;
535
+
536
+ const memoryActiveRootSpan = utils.getActiveRootSpan();
537
+
538
+ if (router.state.historyAction === 'POP' && memoryActiveRootSpan) {
539
+ updatePageloadTransaction({
540
+ activeRootSpan: memoryActiveRootSpan,
541
+ location,
542
+ routes,
543
+ basename,
544
+ allRoutes: Array.from(allRoutes),
545
+ });
546
+ }
547
+
548
+ // Store basename for use in updateNavigationSpan
549
+ _basename = basename || '';
550
+
551
+ setupRouterSubscription(router, routes, version, basename, memoryActiveRootSpan);
552
+
553
+ return router;
554
+ };
555
+ }
556
+
557
+ /**
558
+ * Creates a browser tracing integration that can be used with all React Router v6 compatible versions.
559
+ */
560
+ function createReactRouterV6CompatibleTracingIntegration(
561
+ options,
562
+ version,
563
+ ) {
564
+ const integration = browser.browserTracingIntegration({ ...options, instrumentPageLoad: false, instrumentNavigation: false });
565
+
566
+ const {
567
+ useEffect,
568
+ useLocation,
569
+ useNavigationType,
570
+ createRoutesFromChildren,
571
+ matchRoutes,
572
+ stripBasename,
573
+ enableAsyncRouteHandlers = false,
574
+ instrumentPageLoad = true,
575
+ instrumentNavigation = true,
576
+ lazyRouteTimeout,
577
+ lazyRouteManifest,
578
+ } = options;
579
+
580
+ return {
581
+ ...integration,
582
+ setup(client) {
583
+ integration.setup(client);
584
+
585
+ const finalTimeout = options.finalTimeout ?? 30000;
586
+ const defaultMaxWait = (options.idleTimeout ?? 1000) * 3;
587
+ const configuredMaxWait = lazyRouteTimeout ?? defaultMaxWait;
588
+
589
+ // Cap Infinity at finalTimeout to prevent indefinite hangs
590
+ if (configuredMaxWait === Infinity) {
591
+ _lazyRouteTimeout = finalTimeout;
592
+ debugBuild.DEBUG_BUILD &&
593
+ core.debug.log(
594
+ '[React Router] lazyRouteTimeout set to Infinity, capping at finalTimeout:',
595
+ finalTimeout,
596
+ 'ms to prevent indefinite hangs',
597
+ );
598
+ } else if (Number.isNaN(configuredMaxWait)) {
599
+ debugBuild.DEBUG_BUILD &&
600
+ core.debug.warn('[React Router] lazyRouteTimeout must be a number, falling back to default:', defaultMaxWait);
601
+ _lazyRouteTimeout = defaultMaxWait;
602
+ } else if (configuredMaxWait < 0) {
603
+ debugBuild.DEBUG_BUILD &&
604
+ core.debug.warn(
605
+ '[React Router] lazyRouteTimeout must be non-negative or Infinity, got:',
606
+ configuredMaxWait,
607
+ 'falling back to:',
608
+ defaultMaxWait,
609
+ );
610
+ _lazyRouteTimeout = defaultMaxWait;
611
+ } else {
612
+ _lazyRouteTimeout = configuredMaxWait;
613
+ }
614
+
615
+ _useEffect = useEffect;
616
+ _useLocation = useLocation;
617
+ _useNavigationType = useNavigationType;
618
+ _matchRoutes = matchRoutes;
619
+ _createRoutesFromChildren = createRoutesFromChildren;
620
+ _enableAsyncRouteHandlers = enableAsyncRouteHandlers;
621
+ _lazyRouteManifest = lazyRouteManifest;
622
+
623
+ // Initialize the router utils with the required dependencies
624
+ utils.initializeRouterUtils(matchRoutes, stripBasename || false);
625
+ },
626
+ afterAllSetup(client) {
627
+ integration.afterAllSetup(client);
628
+
629
+ const initPathName = browser.WINDOW.location?.pathname;
630
+ if (instrumentPageLoad && initPathName) {
631
+ browser.startBrowserTracingPageLoadSpan(client, {
632
+ name: initPathName,
633
+ attributes: {
634
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
635
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
636
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.pageload.react.reactrouter_v${version}`,
637
+ },
638
+ });
639
+ }
640
+
641
+ if (instrumentNavigation) {
642
+ CLIENTS_WITH_INSTRUMENT_NAVIGATION.add(client);
643
+ }
644
+ },
645
+ };
646
+ }
647
+
648
+ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
649
+ if (!_useEffect || !_useLocation || !_useNavigationType || !_matchRoutes) {
650
+ debugBuild.DEBUG_BUILD &&
651
+ core.debug.warn(
652
+ 'reactRouterV6Instrumentation was unable to wrap `useRoutes` because of one or more missing parameters.',
653
+ );
654
+
655
+ return origUseRoutes;
656
+ }
657
+
658
+ const SentryRoutes
659
+
660
+ = (props) => {
661
+ const isMountRenderPass = React.useRef(true);
662
+ const { routes, locationArg } = props;
663
+
664
+ const Routes = origUseRoutes(routes, locationArg);
665
+
666
+ const location = _useLocation();
667
+ const navigationType = _useNavigationType();
668
+
669
+ // A value with stable identity to either pick `locationArg` if available or `location` if not
670
+ const stableLocationParam =
671
+ typeof locationArg === 'string' || locationArg?.pathname ? (locationArg ) : location;
672
+
673
+ _useEffect(() => {
674
+ const normalizedLocation =
675
+ typeof stableLocationParam === 'string' ? { pathname: stableLocationParam } : stableLocationParam;
676
+
677
+ if (isMountRenderPass.current) {
678
+ addRoutesToAllRoutes(routes);
679
+
680
+ updatePageloadTransaction({
681
+ activeRootSpan: utils.getActiveRootSpan(),
682
+ location: normalizedLocation,
683
+ routes,
684
+ allRoutes: Array.from(allRoutes),
685
+ });
686
+ isMountRenderPass.current = false;
687
+ } else {
688
+ // Note: Component-based routes don't support lazy route tracking via lazyRouteTimeout
689
+ // because React.lazy() loads happen at the component level, not the router level.
690
+ // Use createBrowserRouter with patchRoutesOnNavigation for lazy route tracking.
691
+ handleNavigation({
692
+ location: normalizedLocation,
693
+ routes,
694
+ navigationType,
695
+ version,
696
+ allRoutes: Array.from(allRoutes),
697
+ });
698
+ }
699
+ }, [navigationType, stableLocationParam]);
700
+
701
+ return Routes;
702
+ };
703
+
704
+ // eslint-disable-next-line react/display-name
705
+ return (routes, locationArg) => {
706
+ return React.createElement(SentryRoutes, { routes: routes, locationArg: locationArg,} );
707
+ };
708
+ }
709
+ function wrapPatchRoutesOnNavigation(
710
+ opts,
711
+ isMemoryRouter = false,
712
+ capturedSpan,
713
+ ) {
714
+ if (!opts || !('patchRoutesOnNavigation' in opts) || typeof opts.patchRoutesOnNavigation !== 'function') {
715
+ return opts || {};
716
+ }
717
+
718
+ const originalPatchRoutes = opts.patchRoutesOnNavigation;
719
+ return {
720
+ ...opts,
721
+ patchRoutesOnNavigation: async (args) => {
722
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
723
+ const targetPath = (args )?.path;
724
+
725
+ // Use current active span if available, otherwise fall back to captured span (from router creation time).
726
+ // This ensures navigation spans use their own span (not the stale pageload span), while still
727
+ // supporting pageload spans that may have ended before patchRoutesOnNavigation is called.
728
+ const activeRootSpan = utils.getActiveRootSpan() ?? capturedSpan;
729
+
730
+ if (!isMemoryRouter) {
731
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
732
+ const originalPatch = (args )?.patch;
733
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
734
+ const matches = (args )?.matches ;
735
+ if (originalPatch) {
736
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
737
+ (args ).patch = (routeId, children) => {
738
+ addRoutesToAllRoutes(children);
739
+
740
+ // Find the parent route from matches and attach children to it in allRoutes.
741
+ // React Router's patch attaches children to its internal route copies, but we need
742
+ // to update the route objects in our allRoutes Set for proper route matching.
743
+ if (matches && matches.length > 0) {
744
+ const leafMatch = matches[matches.length - 1];
745
+ const leafRoute = leafMatch?.route;
746
+ if (leafRoute) {
747
+ // Find the matching route in allRoutes by id, reference, or path
748
+ const matchingRoute = Array.from(allRoutes).find(route => {
749
+ const idMatches = route.id !== undefined && route.id === routeId;
750
+ const referenceMatches = route === leafRoute;
751
+ const pathMatches =
752
+ route.path !== undefined && leafRoute.path !== undefined && route.path === leafRoute.path;
753
+
754
+ return idMatches || referenceMatches || pathMatches;
755
+ });
756
+
757
+ if (matchingRoute) {
758
+ addResolvedRoutesToParent(children, matchingRoute);
759
+ }
760
+ }
761
+ }
762
+
763
+ // Use the captured activeRootSpan instead of getActiveRootSpan() to avoid race conditions
764
+ // where user navigates away during lazy route loading and we'd update the wrong span
765
+ const spanJson = activeRootSpan ? core.spanToJSON(activeRootSpan) : undefined;
766
+ // Only update if we have a valid targetPath (patchRoutesOnNavigation can be called without path),
767
+ // the captured span exists, hasn't ended, and is a navigation span
768
+ if (
769
+ targetPath &&
770
+ activeRootSpan &&
771
+ spanJson &&
772
+ !spanJson.timestamp && // Span hasn't ended yet
773
+ spanJson.op === 'navigation'
774
+ ) {
775
+ updateNavigationSpan(
776
+ activeRootSpan,
777
+ { pathname: targetPath, search: '', hash: '', state: null, key: 'default' },
778
+ Array.from(allRoutes),
779
+ true,
780
+ _matchRoutes,
781
+ );
782
+ }
783
+ return originalPatch(routeId, children);
784
+ };
785
+ }
786
+ }
787
+
788
+ const lazyLoadPromise = (async () => {
789
+ // Set context so async handlers can access correct targetPath and span
790
+ const contextToken = utils.setNavigationContext(targetPath, activeRootSpan);
791
+ let result;
792
+ try {
793
+ result = await originalPatchRoutes(args);
794
+ } finally {
795
+ utils.clearNavigationContext(contextToken);
796
+ // Resolve the deferred promise now that patchRoutesOnNavigation has completed.
797
+ // This ensures patchedEnd has waited long enough for the lazy routes to load.
798
+ if (activeRootSpan) {
799
+ resolveDeferredLazyRoutePromise(activeRootSpan);
800
+ }
801
+ }
802
+
803
+ // Use the captured activeRootSpan instead of getActiveRootSpan() to avoid race conditions
804
+ // where user navigates away during lazy route loading and we'd update the wrong span
805
+ const spanJson = activeRootSpan ? core.spanToJSON(activeRootSpan) : undefined;
806
+ if (
807
+ activeRootSpan &&
808
+ spanJson &&
809
+ !spanJson.timestamp && // Span hasn't ended yet
810
+ spanJson.op === 'navigation'
811
+ ) {
812
+ // Use targetPath consistently - don't fall back to WINDOW.location which may have changed
813
+ // if the user navigated away during async loading
814
+ const pathname = targetPath;
815
+
816
+ if (pathname) {
817
+ updateNavigationSpan(
818
+ activeRootSpan,
819
+ { pathname, search: '', hash: '', state: null, key: 'default' },
820
+ Array.from(allRoutes),
821
+ false,
822
+ _matchRoutes,
823
+ );
824
+ }
825
+ }
826
+
827
+ return result;
828
+ })();
829
+
830
+ if (activeRootSpan) {
831
+ trackLazyRouteLoad(activeRootSpan, lazyLoadPromise);
832
+ }
833
+
834
+ return lazyLoadPromise;
835
+ },
836
+ };
837
+ }
838
+
839
+ // eslint-disable-next-line complexity
840
+ function handleNavigation(opts
841
+
842
+ ) {
843
+ const { location, routes, navigationType, version, matches, basename, allRoutes } = opts;
844
+ const branches = Array.isArray(matches) ? matches : _matchRoutes(allRoutes || routes, location, basename);
845
+
846
+ const client = core.getClient();
847
+ if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) {
848
+ return;
849
+ }
850
+
851
+ const activeRootSpan = utils.getActiveRootSpan();
852
+ if (activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload' && navigationType === 'POP') {
853
+ return;
854
+ }
855
+
856
+ if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
857
+ const [name, source] = utils.resolveRouteNameAndSource(
858
+ location,
859
+ allRoutes || routes,
860
+ allRoutes || routes,
861
+ branches ,
862
+ basename,
863
+ _lazyRouteManifest,
864
+ _enableAsyncRouteHandlers,
865
+ );
866
+
867
+ const locationKey = computeLocationKey(location);
868
+ const trackedNav = activeNavigationSpans.get(client);
869
+
870
+ // Determine if this navigation should be skipped as a duplicate
871
+ const trackedSpanHasEnded =
872
+ trackedNav && !trackedNav.isPlaceholder ? !!core.spanToJSON(trackedNav.span).timestamp : false;
873
+ const { skip, shouldUpdate } = shouldSkipNavigation(trackedNav, locationKey, name, trackedSpanHasEnded);
874
+
875
+ if (skip) {
876
+ if (shouldUpdate && trackedNav) {
877
+ const oldName = trackedNav.routeName;
878
+
879
+ if (trackedNav.isPlaceholder) {
880
+ // Update placeholder's route name - the real span will be created with this name
881
+ trackedNav.routeName = name;
882
+ debugBuild.DEBUG_BUILD &&
883
+ core.debug.log(
884
+ `[Tracing] Updated placeholder navigation name from "${oldName}" to "${name}" (will apply to real span)`,
885
+ );
886
+ } else {
887
+ // Update existing real span from wildcard to parameterized route name
888
+ trackedNav.span.updateName(name);
889
+ trackedNav.span.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source );
890
+ core.addNonEnumerableProperty(
891
+ trackedNav.span ,
892
+ '__sentry_navigation_name_set__',
893
+ true,
894
+ );
895
+ trackedNav.routeName = name;
896
+ debugBuild.DEBUG_BUILD && core.debug.log(`[Tracing] Updated navigation span name from "${oldName}" to "${name}"`);
897
+ }
898
+ } else {
899
+ debugBuild.DEBUG_BUILD && core.debug.log(`[Tracing] Skipping duplicate navigation for location: ${locationKey}`);
900
+ }
901
+ return;
902
+ }
903
+
904
+ // Create new navigation span (first navigation or legitimate new navigation)
905
+ // Reserve the spot in the map first to prevent race conditions
906
+ // Mark as placeholder to prevent concurrent handleNavigation calls from creating duplicates
907
+ const placeholderSpan = { end: () => {} } ;
908
+ const placeholderEntry = {
909
+ span: placeholderSpan,
910
+ routeName: name,
911
+ pathname: location.pathname,
912
+ locationKey,
913
+ isPlaceholder: true ,
914
+ };
915
+ activeNavigationSpans.set(client, placeholderEntry);
916
+
917
+ let navigationSpan;
918
+ try {
919
+ navigationSpan = browser.startBrowserTracingNavigationSpan(client, {
920
+ name: placeholderEntry.routeName, // Use placeholder's routeName in case it was updated
921
+ attributes: {
922
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
923
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
924
+ [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
925
+ },
926
+ });
927
+ } catch (e) {
928
+ // If span creation fails, remove the placeholder so we don't block future navigations
929
+ activeNavigationSpans.delete(client);
930
+ throw e;
931
+ }
932
+
933
+ if (navigationSpan) {
934
+ // Update the map with the real span (isPlaceholder omitted, defaults to false)
935
+ activeNavigationSpans.set(client, {
936
+ span: navigationSpan,
937
+ routeName: placeholderEntry.routeName, // Use the (potentially updated) placeholder routeName
938
+ pathname: location.pathname,
939
+ locationKey,
940
+ });
941
+ patchSpanEnd(navigationSpan, location, routes, basename, 'navigation');
942
+ } else {
943
+ // If no span was created, remove the placeholder
944
+ activeNavigationSpans.delete(client);
945
+ }
946
+ }
947
+ }
948
+
949
+ /* Only exported for testing purposes */
950
+ function addRoutesToAllRoutes(routes) {
951
+ routes.forEach(route => {
952
+ const extractedChildRoutes = getChildRoutesRecursively(route);
953
+
954
+ extractedChildRoutes.forEach(r => {
955
+ allRoutes.add(r);
956
+ });
957
+ });
958
+ }
959
+
960
+ function getChildRoutesRecursively(route, allRoutes = new Set()) {
961
+ if (!allRoutes.has(route)) {
962
+ allRoutes.add(route);
963
+
964
+ if (route.children && !route.index) {
965
+ route.children.forEach(child => {
966
+ const childRoutes = getChildRoutesRecursively(child, allRoutes);
967
+
968
+ childRoutes.forEach(r => {
969
+ allRoutes.add(r);
970
+ });
971
+ });
972
+ }
973
+ }
974
+
975
+ return allRoutes;
976
+ }
977
+
978
+ function updatePageloadTransaction({
979
+ activeRootSpan,
980
+ location,
981
+ routes,
982
+ matches,
983
+ basename,
984
+ allRoutes,
985
+ }
986
+
987
+ ) {
988
+ const branches = Array.isArray(matches)
989
+ ? matches
990
+ : (_matchRoutes(allRoutes || routes, location, basename) );
991
+
992
+ if (branches) {
993
+ const [name, source] = utils.resolveRouteNameAndSource(
994
+ location,
995
+ allRoutes || routes,
996
+ allRoutes || routes,
997
+ branches,
998
+ basename,
999
+ _lazyRouteManifest,
1000
+ _enableAsyncRouteHandlers,
1001
+ );
1002
+
1003
+ core.getCurrentScope().setTransactionName(name || '/');
1004
+
1005
+ if (activeRootSpan) {
1006
+ activeRootSpan.updateName(name);
1007
+ activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
1008
+
1009
+ // Patch span.end() to ensure we update the name one last time before the span is sent
1010
+ patchSpanEnd(activeRootSpan, location, routes, basename, 'pageload');
1011
+ }
1012
+ } else if (activeRootSpan) {
1013
+ // Even if branches is null (can happen when lazy routes haven't loaded yet),
1014
+ // we still need to patch span.end() so that when lazy routes load and the span ends,
1015
+ // we can update the transaction name correctly.
1016
+ patchSpanEnd(activeRootSpan, location, routes, basename, 'pageload');
1017
+ }
1018
+ }
1019
+
1020
+ /**
1021
+ * Determines if a span name should be updated during wildcard route resolution.
1022
+ *
1023
+ * Update conditions (in priority order):
1024
+ * 1. No current name + allowNoCurrentName: true → always update (pageload spans)
1025
+ * 2. Current name has wildcard + new is route without wildcard → upgrade (e.g., "/users/*" → "/users/:id")
1026
+ * 3. Current source is not 'route' + new source is 'route' → upgrade (e.g., URL → parameterized route)
1027
+ *
1028
+ * @param currentName - The current span name (may be undefined)
1029
+ * @param currentSource - The current span source ('route', 'url', or undefined)
1030
+ * @param newName - The proposed new span name
1031
+ * @param newSource - The proposed new span source
1032
+ * @param allowNoCurrentName - If true, allow updates when there's no current name (for pageload spans)
1033
+ * @returns true if the span name should be updated
1034
+ */
1035
+ function shouldUpdateWildcardSpanName(
1036
+ currentName,
1037
+ currentSource,
1038
+ newName,
1039
+ newSource,
1040
+ allowNoCurrentName = false,
1041
+ ) {
1042
+ if (!newName) {
1043
+ return false;
1044
+ }
1045
+
1046
+ if (!currentName && allowNoCurrentName) {
1047
+ return true;
1048
+ }
1049
+
1050
+ const hasWildcard = currentName && utils.transactionNameHasWildcard(currentName);
1051
+
1052
+ if (hasWildcard && newSource === 'route' && !utils.transactionNameHasWildcard(newName)) {
1053
+ return true;
1054
+ }
1055
+
1056
+ if (currentSource !== 'route' && newSource === 'route') {
1057
+ return true;
1058
+ }
1059
+
1060
+ return false;
1061
+ }
1062
+
1063
+ function tryUpdateSpanNameBeforeEnd(
1064
+ span,
1065
+ spanJson,
1066
+ currentName,
1067
+ location,
1068
+ routes,
1069
+ basename,
1070
+ spanType,
1071
+ allRoutes,
1072
+ ) {
1073
+ try {
1074
+ const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
1075
+
1076
+ if (currentSource === 'route' && currentName && !utils.transactionNameHasWildcard(currentName)) {
1077
+ return;
1078
+ }
1079
+
1080
+ const currentAllRoutes = Array.from(allRoutes);
1081
+ const routesToUse = currentAllRoutes.length > 0 ? currentAllRoutes : routes;
1082
+ const branches = _matchRoutes(routesToUse, location, basename) ;
1083
+
1084
+ if (!branches) {
1085
+ return;
1086
+ }
1087
+
1088
+ const [name, source] = utils.resolveRouteNameAndSource(
1089
+ location,
1090
+ routesToUse,
1091
+ routesToUse,
1092
+ branches,
1093
+ basename,
1094
+ _lazyRouteManifest,
1095
+ _enableAsyncRouteHandlers,
1096
+ );
1097
+
1098
+ const isImprovement = shouldUpdateWildcardSpanName(currentName, currentSource, name, source, true);
1099
+ const spanNotEnded = spanType === 'pageload' || !spanJson.timestamp;
1100
+
1101
+ if (isImprovement && spanNotEnded) {
1102
+ span.updateName(name);
1103
+ span.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
1104
+ }
1105
+ } catch (error) {
1106
+ debugBuild.DEBUG_BUILD && core.debug.warn(`Error updating span details before ending: ${error}`);
1107
+ }
1108
+ }
1109
+
1110
+ /**
1111
+ * Patches the span.end() method to update the transaction name one last time before the span is sent.
1112
+ * This handles cases where the span is cancelled early (e.g., document.hidden) before lazy routes have finished loading.
1113
+ */
1114
+ function patchSpanEnd(
1115
+ span,
1116
+ location,
1117
+ routes,
1118
+ basename,
1119
+ spanType,
1120
+ ) {
1121
+ const patchedPropertyName = `__sentry_${spanType}_end_patched__` ;
1122
+ const hasEndBeenPatched = (span )?.[patchedPropertyName];
1123
+
1124
+ if (hasEndBeenPatched || !span.end) {
1125
+ return;
1126
+ }
1127
+
1128
+ // Uses global allRoutes to access lazy-loaded routes added after this function was called.
1129
+
1130
+ const originalEnd = span.end.bind(span);
1131
+ let endCalled = false;
1132
+
1133
+ span.end = function patchedEnd(...args) {
1134
+ if (endCalled) {
1135
+ return;
1136
+ }
1137
+ endCalled = true;
1138
+
1139
+ // Capture timestamp immediately to avoid delay from async operations
1140
+ // If no timestamp was provided, capture the current time now
1141
+ const endTimestamp = args.length > 0 ? args[0] : Date.now() / 1000;
1142
+
1143
+ const spanJson = core.spanToJSON(span);
1144
+ const currentName = spanJson.description;
1145
+ const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
1146
+
1147
+ // Helper to clean up activeNavigationSpans after span ends
1148
+ const cleanupNavigationSpan = () => {
1149
+ const client = core.getClient();
1150
+ if (client && spanType === 'navigation') {
1151
+ const trackedNav = activeNavigationSpans.get(client);
1152
+ if (trackedNav && trackedNav.span === span) {
1153
+ activeNavigationSpans.delete(client);
1154
+ }
1155
+ }
1156
+ };
1157
+
1158
+ const pendingPromises = pendingLazyRouteLoads.get(span);
1159
+ const mayHaveLazyRoutes = (span ).__sentry_may_have_lazy_routes__;
1160
+
1161
+ // Wait for lazy routes if:
1162
+ // 1. (There are pending promises OR the span was marked as potentially having lazy routes) AND
1163
+ // 2. Current name exists AND
1164
+ // 3. Either the name has a wildcard OR the source is not 'route' (URL-based names)
1165
+ const hasPendingOrMayHaveLazyRoutes = (pendingPromises && pendingPromises.size > 0) || mayHaveLazyRoutes;
1166
+ const shouldWaitForLazyRoutes =
1167
+ hasPendingOrMayHaveLazyRoutes &&
1168
+ currentName &&
1169
+ (utils.transactionNameHasWildcard(currentName) || currentSource !== 'route');
1170
+
1171
+ if (shouldWaitForLazyRoutes) {
1172
+ if (_lazyRouteTimeout === 0) {
1173
+ tryUpdateSpanNameBeforeEnd(span, spanJson, currentName, location, routes, basename, spanType, allRoutes);
1174
+ cleanupNavigationSpan();
1175
+ originalEnd(endTimestamp);
1176
+ return;
1177
+ }
1178
+
1179
+ // If we have pending promises, wait for them. Otherwise, just wait for the timeout.
1180
+ // This handles the case where we know lazy routes might load but patchRoutesOnNavigation
1181
+ // hasn't been called yet.
1182
+ const timeoutPromise = new Promise(r => setTimeout(r, _lazyRouteTimeout));
1183
+ let waitPromise;
1184
+
1185
+ if (pendingPromises && pendingPromises.size > 0) {
1186
+ const allSettled = Promise.allSettled(pendingPromises).then(() => {});
1187
+ waitPromise = _lazyRouteTimeout === Infinity ? allSettled : Promise.race([allSettled, timeoutPromise]);
1188
+ } else {
1189
+ // No pending promises yet, but we know lazy routes might load
1190
+ // Wait for the timeout to give React Router time to call patchRoutesOnNavigation
1191
+ waitPromise = timeoutPromise;
1192
+ }
1193
+
1194
+ waitPromise
1195
+ .then(() => {
1196
+ const updatedSpanJson = core.spanToJSON(span);
1197
+ tryUpdateSpanNameBeforeEnd(
1198
+ span,
1199
+ updatedSpanJson,
1200
+ updatedSpanJson.description,
1201
+ location,
1202
+ routes,
1203
+ basename,
1204
+ spanType,
1205
+ allRoutes,
1206
+ );
1207
+ cleanupNavigationSpan();
1208
+ originalEnd(endTimestamp);
1209
+ })
1210
+ .catch(() => {
1211
+ cleanupNavigationSpan();
1212
+ originalEnd(endTimestamp);
1213
+ });
1214
+ return;
1215
+ }
1216
+
1217
+ tryUpdateSpanNameBeforeEnd(span, spanJson, currentName, location, routes, basename, spanType, allRoutes);
1218
+ cleanupNavigationSpan();
1219
+ originalEnd(endTimestamp);
1220
+ };
1221
+
1222
+ core.addNonEnumerableProperty(span , patchedPropertyName, true);
1223
+ }
1224
+
1225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1226
+ function createV6CompatibleWithSentryReactRouterRouting(
1227
+ Routes,
1228
+ version,
1229
+ ) {
1230
+ if (!_useEffect || !_useLocation || !_useNavigationType || !_createRoutesFromChildren || !_matchRoutes) {
1231
+ debugBuild.DEBUG_BUILD &&
1232
+ core.debug.warn(`reactRouterV6Instrumentation was unable to wrap Routes because of one or more missing parameters.
1233
+ useEffect: ${_useEffect}. useLocation: ${_useLocation}. useNavigationType: ${_useNavigationType}.
1234
+ createRoutesFromChildren: ${_createRoutesFromChildren}. matchRoutes: ${_matchRoutes}.`);
1235
+
1236
+ return Routes;
1237
+ }
1238
+
1239
+ const SentryRoutes = (props) => {
1240
+ const isMountRenderPass = React.useRef(true);
1241
+
1242
+ const location = _useLocation();
1243
+ const navigationType = _useNavigationType();
1244
+
1245
+ _useEffect(
1246
+ () => {
1247
+ const routes = _createRoutesFromChildren(props.children) ;
1248
+
1249
+ if (isMountRenderPass.current) {
1250
+ addRoutesToAllRoutes(routes);
1251
+
1252
+ updatePageloadTransaction({
1253
+ activeRootSpan: utils.getActiveRootSpan(),
1254
+ location,
1255
+ routes,
1256
+ allRoutes: Array.from(allRoutes),
1257
+ });
1258
+ isMountRenderPass.current = false;
1259
+ } else {
1260
+ // Note: Component-based routes don't support lazy route tracking via lazyRouteTimeout
1261
+ // because React.lazy() loads happen at the component level, not the router level.
1262
+ // Use createBrowserRouter with patchRoutesOnNavigation for lazy route tracking.
1263
+ handleNavigation({ location, routes, navigationType, version, allRoutes: Array.from(allRoutes) });
1264
+ }
1265
+ },
1266
+ // Re-run only on location/navigation changes, not children changes
1267
+ [location, navigationType],
1268
+ );
1269
+
1270
+ // @ts-expect-error Setting more specific React Component typing for `R` generic above
1271
+ // will break advanced type inference done by react router params
1272
+ return React.createElement(Routes, { ...props,} );
1273
+ };
1274
+
1275
+ hoistNonReactStatics.hoistNonReactStatics(SentryRoutes, Routes);
1276
+
1277
+ // @ts-expect-error Setting more specific React Component typing for `R` generic above
1278
+ // will break advanced type inference done by react router params
1279
+ return SentryRoutes;
1280
+ }
1281
+
1282
+ exports.addResolvedRoutesToParent = addResolvedRoutesToParent;
1283
+ exports.addRoutesToAllRoutes = addRoutesToAllRoutes;
1284
+ exports.allRoutes = allRoutes;
1285
+ exports.computeLocationKey = computeLocationKey;
1286
+ exports.createReactRouterV6CompatibleTracingIntegration = createReactRouterV6CompatibleTracingIntegration;
1287
+ exports.createV6CompatibleWithSentryReactRouterRouting = createV6CompatibleWithSentryReactRouterRouting;
1288
+ exports.createV6CompatibleWrapCreateBrowserRouter = createV6CompatibleWrapCreateBrowserRouter;
1289
+ exports.createV6CompatibleWrapCreateMemoryRouter = createV6CompatibleWrapCreateMemoryRouter;
1290
+ exports.createV6CompatibleWrapUseRoutes = createV6CompatibleWrapUseRoutes;
1291
+ exports.handleNavigation = handleNavigation;
1292
+ exports.processResolvedRoutes = processResolvedRoutes;
1293
+ exports.shouldSkipNavigation = shouldSkipNavigation;
1294
+ exports.updateNavigationSpan = updateNavigationSpan;
1295
+ //# sourceMappingURL=instrumentation.js.map