@sentry/react 10.25.0 → 10.27.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.
- package/build/cjs/reactrouter-compat-utils/instrumentation.js +521 -228
- package/build/cjs/reactrouter-compat-utils/instrumentation.js.map +1 -1
- package/build/cjs/reactrouter-compat-utils/utils.js +57 -42
- package/build/cjs/reactrouter-compat-utils/utils.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/reactrouter-compat-utils/instrumentation.js +521 -230
- package/build/esm/reactrouter-compat-utils/instrumentation.js.map +1 -1
- package/build/esm/reactrouter-compat-utils/utils.js +57 -43
- package/build/esm/reactrouter-compat-utils/utils.js.map +1 -1
- package/build/types/reactrouter-compat-utils/index.d.ts +1 -1
- package/build/types/reactrouter-compat-utils/index.d.ts.map +1 -1
- package/build/types/reactrouter-compat-utils/instrumentation.d.ts +35 -4
- package/build/types/reactrouter-compat-utils/instrumentation.d.ts.map +1 -1
- package/build/types/reactrouter-compat-utils/utils.d.ts +2 -0
- package/build/types/reactrouter-compat-utils/utils.d.ts.map +1 -1
- package/build/types-ts3.8/reactrouter-compat-utils/index.d.ts +1 -1
- package/build/types-ts3.8/reactrouter-compat-utils/instrumentation.d.ts +35 -4
- package/build/types-ts3.8/reactrouter-compat-utils/utils.d.ts +2 -0
- package/package.json +3 -5
|
@@ -18,14 +18,112 @@ let _useLocation;
|
|
|
18
18
|
let _useNavigationType;
|
|
19
19
|
let _createRoutesFromChildren;
|
|
20
20
|
let _matchRoutes;
|
|
21
|
+
|
|
21
22
|
let _enableAsyncRouteHandlers = false;
|
|
23
|
+
let _lazyRouteTimeout = 3000;
|
|
22
24
|
|
|
23
25
|
const CLIENTS_WITH_INSTRUMENT_NAVIGATION = new WeakSet();
|
|
24
26
|
|
|
27
|
+
// Prevents duplicate spans when router.subscribe fires multiple times
|
|
28
|
+
const activeNavigationSpans = new WeakMap
|
|
29
|
+
|
|
30
|
+
();
|
|
31
|
+
|
|
32
|
+
// Exported for testing only
|
|
33
|
+
const allRoutes = new Set();
|
|
34
|
+
|
|
35
|
+
// Tracks lazy route loads to wait before finalizing span names
|
|
36
|
+
const pendingLazyRouteLoads = new WeakMap();
|
|
37
|
+
|
|
25
38
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
39
|
+
* Schedules a callback using requestAnimationFrame when available (browser),
|
|
40
|
+
* or falls back to setTimeout for SSR environments (Node.js, createMemoryRouter tests).
|
|
28
41
|
*/
|
|
42
|
+
function scheduleCallback(callback) {
|
|
43
|
+
if (browser.WINDOW?.requestAnimationFrame) {
|
|
44
|
+
return browser.WINDOW.requestAnimationFrame(callback);
|
|
45
|
+
}
|
|
46
|
+
return setTimeout(callback, 0) ;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Cancels a scheduled callback, handling both RAF (browser) and timeout (SSR) IDs.
|
|
51
|
+
*/
|
|
52
|
+
function cancelScheduledCallback(id) {
|
|
53
|
+
if (browser.WINDOW?.cancelAnimationFrame) {
|
|
54
|
+
browser.WINDOW.cancelAnimationFrame(id);
|
|
55
|
+
} else {
|
|
56
|
+
clearTimeout(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Computes location key for duplicate detection. Normalizes undefined/null to empty strings.
|
|
62
|
+
* Exported for testing.
|
|
63
|
+
*/
|
|
64
|
+
function computeLocationKey(location) {
|
|
65
|
+
return `${location.pathname}${location.search || ''}${location.hash || ''}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Checks if a route name is parameterized (contains route parameters like :id or wildcards like *)
|
|
70
|
+
* vs a raw URL path.
|
|
71
|
+
*/
|
|
72
|
+
function isParameterizedRoute(routeName) {
|
|
73
|
+
return routeName.includes(':') || routeName.includes('*');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Determines if a navigation should be skipped as a duplicate, and if an existing span should be updated.
|
|
78
|
+
* Exported for testing.
|
|
79
|
+
*
|
|
80
|
+
* @returns An object with:
|
|
81
|
+
* - skip: boolean - Whether to skip creating a new span
|
|
82
|
+
* - shouldUpdate: boolean - Whether to update the existing span name (wildcard upgrade)
|
|
83
|
+
*/
|
|
84
|
+
function shouldSkipNavigation(
|
|
85
|
+
trackedNav
|
|
86
|
+
|
|
87
|
+
,
|
|
88
|
+
locationKey,
|
|
89
|
+
proposedName,
|
|
90
|
+
spanHasEnded,
|
|
91
|
+
) {
|
|
92
|
+
if (!trackedNav) {
|
|
93
|
+
return { skip: false, shouldUpdate: false };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if this is a duplicate navigation (same location)
|
|
97
|
+
// 1. If it's a placeholder, it's always a duplicate (we're waiting for the real one)
|
|
98
|
+
// 2. If it's a real span, it's a duplicate only if it hasn't ended yet
|
|
99
|
+
const isDuplicate = trackedNav.locationKey === locationKey && (trackedNav.isPlaceholder || !spanHasEnded);
|
|
100
|
+
|
|
101
|
+
if (isDuplicate) {
|
|
102
|
+
// Check if we should update the span name with a better route
|
|
103
|
+
// Allow updates if:
|
|
104
|
+
// 1. Current has wildcard and new doesn't (wildcard → parameterized upgrade)
|
|
105
|
+
// 2. Current is raw path and new is parameterized (raw → parameterized upgrade)
|
|
106
|
+
// 3. New name is different and more specific (longer, indicating nested routes resolved)
|
|
107
|
+
const currentHasWildcard = !!trackedNav.routeName && utils.transactionNameHasWildcard(trackedNav.routeName);
|
|
108
|
+
const proposedHasWildcard = utils.transactionNameHasWildcard(proposedName);
|
|
109
|
+
const currentIsParameterized = !!trackedNav.routeName && isParameterizedRoute(trackedNav.routeName);
|
|
110
|
+
const proposedIsParameterized = isParameterizedRoute(proposedName);
|
|
111
|
+
|
|
112
|
+
const isWildcardUpgrade = currentHasWildcard && !proposedHasWildcard;
|
|
113
|
+
const isRawToParameterized = !currentIsParameterized && proposedIsParameterized;
|
|
114
|
+
const isMoreSpecific =
|
|
115
|
+
proposedName !== trackedNav.routeName &&
|
|
116
|
+
proposedName.length > (trackedNav.routeName?.length || 0) &&
|
|
117
|
+
!proposedHasWildcard;
|
|
118
|
+
|
|
119
|
+
const shouldUpdate = !!(trackedNav.routeName && (isWildcardUpgrade || isRawToParameterized || isMoreSpecific));
|
|
120
|
+
|
|
121
|
+
return { skip: true, shouldUpdate };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { skip: false, shouldUpdate: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
29
127
|
function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
|
|
30
128
|
const existingChildren = parentRoute.children || [];
|
|
31
129
|
|
|
@@ -44,9 +142,23 @@ function addResolvedRoutesToParent(resolvedRoutes, parentRoute) {
|
|
|
44
142
|
}
|
|
45
143
|
}
|
|
46
144
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
145
|
+
/** Registers a pending lazy route load promise for a span. */
|
|
146
|
+
function trackLazyRouteLoad(span, promise) {
|
|
147
|
+
let promises = pendingLazyRouteLoads.get(span);
|
|
148
|
+
if (!promises) {
|
|
149
|
+
promises = new Set();
|
|
150
|
+
pendingLazyRouteLoads.set(span, promises);
|
|
151
|
+
}
|
|
152
|
+
promises.add(promise);
|
|
153
|
+
|
|
154
|
+
// Clean up when promise resolves/rejects
|
|
155
|
+
promise.finally(() => {
|
|
156
|
+
const currentPromises = pendingLazyRouteLoads.get(span);
|
|
157
|
+
if (currentPromises) {
|
|
158
|
+
currentPromises.delete(promise);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
50
162
|
|
|
51
163
|
/**
|
|
52
164
|
* Processes resolved routes by adding them to allRoutes and checking for nested async handlers.
|
|
@@ -112,13 +224,14 @@ function updateNavigationSpan(
|
|
|
112
224
|
forceUpdate = false,
|
|
113
225
|
matchRoutes,
|
|
114
226
|
) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
227
|
+
const spanJson = core.spanToJSON(activeRootSpan);
|
|
228
|
+
const currentName = spanJson.description;
|
|
229
|
+
|
|
230
|
+
const hasBeenNamed = (activeRootSpan )?.__sentry_navigation_name_set__;
|
|
231
|
+
const currentNameHasWildcard = currentName && utils.transactionNameHasWildcard(currentName);
|
|
232
|
+
const shouldUpdate = !hasBeenNamed || forceUpdate || currentNameHasWildcard;
|
|
119
233
|
|
|
120
|
-
if (!
|
|
121
|
-
// Get fresh branches for the current location with all loaded routes
|
|
234
|
+
if (shouldUpdate && !spanJson.timestamp) {
|
|
122
235
|
const currentBranches = matchRoutes(allRoutes, location);
|
|
123
236
|
const [name, source] = utils.resolveRouteNameAndSource(
|
|
124
237
|
location,
|
|
@@ -128,22 +241,105 @@ function updateNavigationSpan(
|
|
|
128
241
|
'',
|
|
129
242
|
);
|
|
130
243
|
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
244
|
+
const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
|
|
245
|
+
const isImprovement =
|
|
246
|
+
name &&
|
|
247
|
+
(!currentName || // No current name - always set
|
|
248
|
+
(!hasBeenNamed && (currentSource !== 'route' || source === 'route')) || // Not finalized - allow unless downgrading route→url
|
|
249
|
+
(currentSource !== 'route' && source === 'route') || // URL → route upgrade
|
|
250
|
+
(currentSource === 'route' && source === 'route' && currentNameHasWildcard)); // Route → better route (only if current has wildcard)
|
|
251
|
+
if (isImprovement) {
|
|
134
252
|
activeRootSpan.updateName(name);
|
|
135
253
|
activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
|
|
136
254
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
255
|
+
// Only mark as finalized for non-wildcard route names (allows URL→route upgrades).
|
|
256
|
+
if (!utils.transactionNameHasWildcard(name) && source === 'route') {
|
|
257
|
+
core.addNonEnumerableProperty(
|
|
258
|
+
activeRootSpan ,
|
|
259
|
+
'__sentry_navigation_name_set__',
|
|
260
|
+
true,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
143
263
|
}
|
|
144
264
|
}
|
|
145
265
|
}
|
|
146
266
|
|
|
267
|
+
function setupRouterSubscription(
|
|
268
|
+
router,
|
|
269
|
+
routes,
|
|
270
|
+
version,
|
|
271
|
+
basename,
|
|
272
|
+
activeRootSpan,
|
|
273
|
+
) {
|
|
274
|
+
let isInitialPageloadComplete = false;
|
|
275
|
+
let hasSeenPageloadSpan = !!activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload';
|
|
276
|
+
let hasSeenPopAfterPageload = false;
|
|
277
|
+
let scheduledNavigationHandler = null;
|
|
278
|
+
let lastHandledPathname = null;
|
|
279
|
+
|
|
280
|
+
router.subscribe((state) => {
|
|
281
|
+
if (!isInitialPageloadComplete) {
|
|
282
|
+
const currentRootSpan = getActiveRootSpan();
|
|
283
|
+
const isCurrentlyInPageload = currentRootSpan && core.spanToJSON(currentRootSpan).op === 'pageload';
|
|
284
|
+
|
|
285
|
+
if (isCurrentlyInPageload) {
|
|
286
|
+
hasSeenPageloadSpan = true;
|
|
287
|
+
} else if (hasSeenPageloadSpan) {
|
|
288
|
+
if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) {
|
|
289
|
+
hasSeenPopAfterPageload = true;
|
|
290
|
+
} else {
|
|
291
|
+
isInitialPageloadComplete = true;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const shouldHandleNavigation =
|
|
297
|
+
state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete);
|
|
298
|
+
|
|
299
|
+
if (shouldHandleNavigation) {
|
|
300
|
+
// Include search and hash to allow query/hash-only navigations
|
|
301
|
+
// Use computeLocationKey() to ensure undefined/null values are normalized to empty strings
|
|
302
|
+
const currentLocationKey = computeLocationKey(state.location);
|
|
303
|
+
const navigationHandler = () => {
|
|
304
|
+
// Prevent multiple calls for the same location within the same navigation cycle
|
|
305
|
+
if (lastHandledPathname === currentLocationKey) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
lastHandledPathname = currentLocationKey;
|
|
309
|
+
scheduledNavigationHandler = null;
|
|
310
|
+
handleNavigation({
|
|
311
|
+
location: state.location,
|
|
312
|
+
routes,
|
|
313
|
+
navigationType: state.historyAction,
|
|
314
|
+
version,
|
|
315
|
+
basename,
|
|
316
|
+
allRoutes: Array.from(allRoutes),
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
if (state.navigation.state !== 'idle') {
|
|
321
|
+
// Navigation in progress - reset if location changed
|
|
322
|
+
if (lastHandledPathname !== currentLocationKey) {
|
|
323
|
+
lastHandledPathname = null;
|
|
324
|
+
}
|
|
325
|
+
// Cancel any previously scheduled handler to avoid duplicates
|
|
326
|
+
if (scheduledNavigationHandler !== null) {
|
|
327
|
+
cancelScheduledCallback(scheduledNavigationHandler);
|
|
328
|
+
}
|
|
329
|
+
scheduledNavigationHandler = scheduleCallback(navigationHandler);
|
|
330
|
+
} else {
|
|
331
|
+
// Navigation completed - cancel scheduled handler if any, then call immediately
|
|
332
|
+
if (scheduledNavigationHandler !== null) {
|
|
333
|
+
cancelScheduledCallback(scheduledNavigationHandler);
|
|
334
|
+
scheduledNavigationHandler = null;
|
|
335
|
+
}
|
|
336
|
+
navigationHandler();
|
|
337
|
+
// Don't reset - next navigation cycle resets to prevent duplicates within same cycle.
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
|
|
147
343
|
/**
|
|
148
344
|
* Creates a wrapCreateBrowserRouter function that can be used with all React Router v6 compatible versions.
|
|
149
345
|
*/
|
|
@@ -165,30 +361,17 @@ function createV6CompatibleWrapCreateBrowserRouter
|
|
|
165
361
|
return function (routes, opts) {
|
|
166
362
|
addRoutesToAllRoutes(routes);
|
|
167
363
|
|
|
168
|
-
// Check for async handlers that might contain sub-route declarations (only if enabled)
|
|
169
364
|
if (_enableAsyncRouteHandlers) {
|
|
170
365
|
for (const route of routes) {
|
|
171
366
|
lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
|
|
172
367
|
}
|
|
173
368
|
}
|
|
174
369
|
|
|
175
|
-
// Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
|
|
176
370
|
const wrappedOpts = wrapPatchRoutesOnNavigation(opts);
|
|
177
|
-
|
|
178
371
|
const router = createRouterFunction(routes, wrappedOpts);
|
|
179
372
|
const basename = opts?.basename;
|
|
180
|
-
|
|
181
373
|
const activeRootSpan = getActiveRootSpan();
|
|
182
374
|
|
|
183
|
-
// Track whether we've completed the initial pageload to properly distinguish
|
|
184
|
-
// between POPs that occur during pageload vs. legitimate back/forward navigation.
|
|
185
|
-
let isInitialPageloadComplete = false;
|
|
186
|
-
let hasSeenPageloadSpan = !!activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload';
|
|
187
|
-
let hasSeenPopAfterPageload = false;
|
|
188
|
-
|
|
189
|
-
// The initial load ends when `createBrowserRouter` is called.
|
|
190
|
-
// This is the earliest convenient time to update the transaction name.
|
|
191
|
-
// Callbacks to `router.subscribe` are not called for the initial load.
|
|
192
375
|
if (router.state.historyAction === 'POP' && activeRootSpan) {
|
|
193
376
|
updatePageloadTransaction({
|
|
194
377
|
activeRootSpan,
|
|
@@ -199,50 +382,7 @@ function createV6CompatibleWrapCreateBrowserRouter
|
|
|
199
382
|
});
|
|
200
383
|
}
|
|
201
384
|
|
|
202
|
-
router
|
|
203
|
-
// Track pageload completion to distinguish POPs during pageload from legitimate back/forward navigation
|
|
204
|
-
if (!isInitialPageloadComplete) {
|
|
205
|
-
const currentRootSpan = getActiveRootSpan();
|
|
206
|
-
const isCurrentlyInPageload = currentRootSpan && core.spanToJSON(currentRootSpan).op === 'pageload';
|
|
207
|
-
|
|
208
|
-
if (isCurrentlyInPageload) {
|
|
209
|
-
hasSeenPageloadSpan = true;
|
|
210
|
-
} else if (hasSeenPageloadSpan) {
|
|
211
|
-
// Pageload span was active but is now gone - pageload has completed
|
|
212
|
-
if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) {
|
|
213
|
-
// Pageload ended: ignore the first POP after pageload
|
|
214
|
-
hasSeenPopAfterPageload = true;
|
|
215
|
-
} else {
|
|
216
|
-
// Pageload ended: either non-POP action or subsequent POP
|
|
217
|
-
isInitialPageloadComplete = true;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// If we haven't seen a pageload span yet, keep waiting (don't mark as complete)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const shouldHandleNavigation =
|
|
224
|
-
state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete);
|
|
225
|
-
|
|
226
|
-
if (shouldHandleNavigation) {
|
|
227
|
-
const navigationHandler = () => {
|
|
228
|
-
handleNavigation({
|
|
229
|
-
location: state.location,
|
|
230
|
-
routes,
|
|
231
|
-
navigationType: state.historyAction,
|
|
232
|
-
version,
|
|
233
|
-
basename,
|
|
234
|
-
allRoutes: Array.from(allRoutes),
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
// Wait for the next render if loading an unsettled route
|
|
239
|
-
if (state.navigation.state !== 'idle') {
|
|
240
|
-
requestAnimationFrame(navigationHandler);
|
|
241
|
-
} else {
|
|
242
|
-
navigationHandler();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
385
|
+
setupRouterSubscription(router, routes, version, basename, activeRootSpan);
|
|
246
386
|
|
|
247
387
|
return router;
|
|
248
388
|
};
|
|
@@ -274,14 +414,12 @@ function createV6CompatibleWrapCreateMemoryRouter
|
|
|
274
414
|
) {
|
|
275
415
|
addRoutesToAllRoutes(routes);
|
|
276
416
|
|
|
277
|
-
// Check for async handlers that might contain sub-route declarations (only if enabled)
|
|
278
417
|
if (_enableAsyncRouteHandlers) {
|
|
279
418
|
for (const route of routes) {
|
|
280
419
|
lazyRoutes.checkRouteForAsyncHandler(route, processResolvedRoutes);
|
|
281
420
|
}
|
|
282
421
|
}
|
|
283
422
|
|
|
284
|
-
// Wrap patchRoutesOnNavigation to detect when lazy routes are loaded
|
|
285
423
|
const wrappedOpts = wrapPatchRoutesOnNavigation(opts, true);
|
|
286
424
|
|
|
287
425
|
const router = createRouterFunction(routes, wrappedOpts);
|
|
@@ -319,58 +457,7 @@ function createV6CompatibleWrapCreateMemoryRouter
|
|
|
319
457
|
});
|
|
320
458
|
}
|
|
321
459
|
|
|
322
|
-
|
|
323
|
-
// between POPs that occur during pageload vs. legitimate back/forward navigation.
|
|
324
|
-
let isInitialPageloadComplete = false;
|
|
325
|
-
let hasSeenPageloadSpan = !!memoryActiveRootSpan && core.spanToJSON(memoryActiveRootSpan).op === 'pageload';
|
|
326
|
-
let hasSeenPopAfterPageload = false;
|
|
327
|
-
|
|
328
|
-
router.subscribe((state) => {
|
|
329
|
-
// Track pageload completion to distinguish POPs during pageload from legitimate back/forward navigation
|
|
330
|
-
if (!isInitialPageloadComplete) {
|
|
331
|
-
const currentRootSpan = getActiveRootSpan();
|
|
332
|
-
const isCurrentlyInPageload = currentRootSpan && core.spanToJSON(currentRootSpan).op === 'pageload';
|
|
333
|
-
|
|
334
|
-
if (isCurrentlyInPageload) {
|
|
335
|
-
hasSeenPageloadSpan = true;
|
|
336
|
-
} else if (hasSeenPageloadSpan) {
|
|
337
|
-
// Pageload span was active but is now gone - pageload has completed
|
|
338
|
-
if (state.historyAction === 'POP' && !hasSeenPopAfterPageload) {
|
|
339
|
-
// Pageload ended: ignore the first POP after pageload
|
|
340
|
-
hasSeenPopAfterPageload = true;
|
|
341
|
-
} else {
|
|
342
|
-
// Pageload ended: either non-POP action or subsequent POP
|
|
343
|
-
isInitialPageloadComplete = true;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
// If we haven't seen a pageload span yet, keep waiting (don't mark as complete)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const location = state.location;
|
|
350
|
-
|
|
351
|
-
const shouldHandleNavigation =
|
|
352
|
-
state.historyAction === 'PUSH' || (state.historyAction === 'POP' && isInitialPageloadComplete);
|
|
353
|
-
|
|
354
|
-
if (shouldHandleNavigation) {
|
|
355
|
-
const navigationHandler = () => {
|
|
356
|
-
handleNavigation({
|
|
357
|
-
location,
|
|
358
|
-
routes,
|
|
359
|
-
navigationType: state.historyAction,
|
|
360
|
-
version,
|
|
361
|
-
basename,
|
|
362
|
-
allRoutes: Array.from(allRoutes),
|
|
363
|
-
});
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
// Wait for the next render if loading an unsettled route
|
|
367
|
-
if (state.navigation.state !== 'idle') {
|
|
368
|
-
requestAnimationFrame(navigationHandler);
|
|
369
|
-
} else {
|
|
370
|
-
navigationHandler();
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
});
|
|
460
|
+
setupRouterSubscription(router, routes, version, basename, memoryActiveRootSpan);
|
|
374
461
|
|
|
375
462
|
return router;
|
|
376
463
|
};
|
|
@@ -395,6 +482,7 @@ function createReactRouterV6CompatibleTracingIntegration(
|
|
|
395
482
|
enableAsyncRouteHandlers = false,
|
|
396
483
|
instrumentPageLoad = true,
|
|
397
484
|
instrumentNavigation = true,
|
|
485
|
+
lazyRouteTimeout,
|
|
398
486
|
} = options;
|
|
399
487
|
|
|
400
488
|
return {
|
|
@@ -402,6 +490,36 @@ function createReactRouterV6CompatibleTracingIntegration(
|
|
|
402
490
|
setup(client) {
|
|
403
491
|
integration.setup(client);
|
|
404
492
|
|
|
493
|
+
const finalTimeout = options.finalTimeout ?? 30000;
|
|
494
|
+
const defaultMaxWait = (options.idleTimeout ?? 1000) * 3;
|
|
495
|
+
const configuredMaxWait = lazyRouteTimeout ?? defaultMaxWait;
|
|
496
|
+
|
|
497
|
+
// Cap Infinity at finalTimeout to prevent indefinite hangs
|
|
498
|
+
if (configuredMaxWait === Infinity) {
|
|
499
|
+
_lazyRouteTimeout = finalTimeout;
|
|
500
|
+
debugBuild.DEBUG_BUILD &&
|
|
501
|
+
core.debug.log(
|
|
502
|
+
'[React Router] lazyRouteTimeout set to Infinity, capping at finalTimeout:',
|
|
503
|
+
finalTimeout,
|
|
504
|
+
'ms to prevent indefinite hangs',
|
|
505
|
+
);
|
|
506
|
+
} else if (Number.isNaN(configuredMaxWait)) {
|
|
507
|
+
debugBuild.DEBUG_BUILD &&
|
|
508
|
+
core.debug.warn('[React Router] lazyRouteTimeout must be a number, falling back to default:', defaultMaxWait);
|
|
509
|
+
_lazyRouteTimeout = defaultMaxWait;
|
|
510
|
+
} else if (configuredMaxWait < 0) {
|
|
511
|
+
debugBuild.DEBUG_BUILD &&
|
|
512
|
+
core.debug.warn(
|
|
513
|
+
'[React Router] lazyRouteTimeout must be non-negative or Infinity, got:',
|
|
514
|
+
configuredMaxWait,
|
|
515
|
+
'falling back to:',
|
|
516
|
+
defaultMaxWait,
|
|
517
|
+
);
|
|
518
|
+
_lazyRouteTimeout = defaultMaxWait;
|
|
519
|
+
} else {
|
|
520
|
+
_lazyRouteTimeout = configuredMaxWait;
|
|
521
|
+
}
|
|
522
|
+
|
|
405
523
|
_useEffect = useEffect;
|
|
406
524
|
_useLocation = useLocation;
|
|
407
525
|
_useNavigationType = useNavigationType;
|
|
@@ -474,6 +592,9 @@ function createV6CompatibleWrapUseRoutes(origUseRoutes, version) {
|
|
|
474
592
|
});
|
|
475
593
|
isMountRenderPass.current = false;
|
|
476
594
|
} else {
|
|
595
|
+
// Note: Component-based routes don't support lazy route tracking via lazyRouteTimeout
|
|
596
|
+
// because React.lazy() loads happen at the component level, not the router level.
|
|
597
|
+
// Use createBrowserRouter with patchRoutesOnNavigation for lazy route tracking.
|
|
477
598
|
handleNavigation({
|
|
478
599
|
location: normalizedLocation,
|
|
479
600
|
routes,
|
|
@@ -508,7 +629,8 @@ function wrapPatchRoutesOnNavigation(
|
|
|
508
629
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
509
630
|
const targetPath = (args )?.path;
|
|
510
631
|
|
|
511
|
-
|
|
632
|
+
const activeRootSpan = getActiveRootSpan();
|
|
633
|
+
|
|
512
634
|
if (!isMemoryRouter) {
|
|
513
635
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
514
636
|
const originalPatch = (args )?.patch;
|
|
@@ -516,13 +638,13 @@ function wrapPatchRoutesOnNavigation(
|
|
|
516
638
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
|
|
517
639
|
(args ).patch = (routeId, children) => {
|
|
518
640
|
addRoutesToAllRoutes(children);
|
|
519
|
-
const
|
|
520
|
-
if (
|
|
641
|
+
const currentActiveRootSpan = getActiveRootSpan();
|
|
642
|
+
if (currentActiveRootSpan && (core.spanToJSON(currentActiveRootSpan) ).op === 'navigation') {
|
|
521
643
|
updateNavigationSpan(
|
|
522
|
-
|
|
644
|
+
currentActiveRootSpan,
|
|
523
645
|
{ pathname: targetPath, search: '', hash: '', state: null, key: 'default' },
|
|
524
646
|
Array.from(allRoutes),
|
|
525
|
-
true,
|
|
647
|
+
true,
|
|
526
648
|
_matchRoutes,
|
|
527
649
|
);
|
|
528
650
|
}
|
|
@@ -531,50 +653,48 @@ function wrapPatchRoutesOnNavigation(
|
|
|
531
653
|
}
|
|
532
654
|
}
|
|
533
655
|
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
656
|
+
const lazyLoadPromise = (async () => {
|
|
657
|
+
const result = await originalPatchRoutes(args);
|
|
658
|
+
|
|
659
|
+
const currentActiveRootSpan = getActiveRootSpan();
|
|
660
|
+
if (currentActiveRootSpan && (core.spanToJSON(currentActiveRootSpan) ).op === 'navigation') {
|
|
661
|
+
const pathname = isMemoryRouter ? targetPath : targetPath || browser.WINDOW.location?.pathname;
|
|
662
|
+
|
|
663
|
+
if (pathname) {
|
|
664
|
+
updateNavigationSpan(
|
|
665
|
+
currentActiveRootSpan,
|
|
666
|
+
{ pathname, search: '', hash: '', state: null, key: 'default' },
|
|
667
|
+
Array.from(allRoutes),
|
|
668
|
+
false,
|
|
669
|
+
_matchRoutes,
|
|
670
|
+
);
|
|
671
|
+
}
|
|
547
672
|
}
|
|
548
673
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
false, // forceUpdate = false since this is after lazy routes are loaded
|
|
555
|
-
_matchRoutes,
|
|
556
|
-
);
|
|
557
|
-
}
|
|
674
|
+
return result;
|
|
675
|
+
})();
|
|
676
|
+
|
|
677
|
+
if (activeRootSpan) {
|
|
678
|
+
trackLazyRouteLoad(activeRootSpan, lazyLoadPromise);
|
|
558
679
|
}
|
|
559
680
|
|
|
560
|
-
return
|
|
681
|
+
return lazyLoadPromise;
|
|
561
682
|
},
|
|
562
683
|
};
|
|
563
684
|
}
|
|
564
685
|
|
|
686
|
+
// eslint-disable-next-line complexity
|
|
565
687
|
function handleNavigation(opts
|
|
566
688
|
|
|
567
689
|
) {
|
|
568
690
|
const { location, routes, navigationType, version, matches, basename, allRoutes } = opts;
|
|
569
|
-
const branches = Array.isArray(matches) ? matches : _matchRoutes(routes, location, basename);
|
|
691
|
+
const branches = Array.isArray(matches) ? matches : _matchRoutes(allRoutes || routes, location, basename);
|
|
570
692
|
|
|
571
693
|
const client = core.getClient();
|
|
572
694
|
if (!client || !CLIENTS_WITH_INSTRUMENT_NAVIGATION.has(client)) {
|
|
573
695
|
return;
|
|
574
696
|
}
|
|
575
697
|
|
|
576
|
-
// Avoid starting a navigation span on initial load when a pageload root span is active.
|
|
577
|
-
// This commonly happens when lazy routes resolve during the first render and React Router emits a POP.
|
|
578
698
|
const activeRootSpan = getActiveRootSpan();
|
|
579
699
|
if (activeRootSpan && core.spanToJSON(activeRootSpan).op === 'pageload' && navigationType === 'POP') {
|
|
580
700
|
return;
|
|
@@ -583,31 +703,90 @@ function handleNavigation(opts
|
|
|
583
703
|
if ((navigationType === 'PUSH' || navigationType === 'POP') && branches) {
|
|
584
704
|
const [name, source] = utils.resolveRouteNameAndSource(
|
|
585
705
|
location,
|
|
586
|
-
routes,
|
|
706
|
+
allRoutes || routes,
|
|
587
707
|
allRoutes || routes,
|
|
588
708
|
branches ,
|
|
589
709
|
basename,
|
|
590
710
|
);
|
|
591
711
|
|
|
592
|
-
const
|
|
593
|
-
const
|
|
594
|
-
|
|
712
|
+
const locationKey = computeLocationKey(location);
|
|
713
|
+
const trackedNav = activeNavigationSpans.get(client);
|
|
714
|
+
|
|
715
|
+
// Determine if this navigation should be skipped as a duplicate
|
|
716
|
+
const trackedSpanHasEnded =
|
|
717
|
+
trackedNav && !trackedNav.isPlaceholder ? !!core.spanToJSON(trackedNav.span).timestamp : false;
|
|
718
|
+
const { skip, shouldUpdate } = shouldSkipNavigation(trackedNav, locationKey, name, trackedSpanHasEnded);
|
|
719
|
+
|
|
720
|
+
if (skip) {
|
|
721
|
+
if (shouldUpdate && trackedNav) {
|
|
722
|
+
const oldName = trackedNav.routeName;
|
|
723
|
+
|
|
724
|
+
if (trackedNav.isPlaceholder) {
|
|
725
|
+
// Update placeholder's route name - the real span will be created with this name
|
|
726
|
+
trackedNav.routeName = name;
|
|
727
|
+
debugBuild.DEBUG_BUILD &&
|
|
728
|
+
core.debug.log(
|
|
729
|
+
`[Tracing] Updated placeholder navigation name from "${oldName}" to "${name}" (will apply to real span)`,
|
|
730
|
+
);
|
|
731
|
+
} else {
|
|
732
|
+
// Update existing real span from wildcard to parameterized route name
|
|
733
|
+
trackedNav.span.updateName(name);
|
|
734
|
+
trackedNav.span.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source );
|
|
735
|
+
core.addNonEnumerableProperty(
|
|
736
|
+
trackedNav.span ,
|
|
737
|
+
'__sentry_navigation_name_set__',
|
|
738
|
+
true,
|
|
739
|
+
);
|
|
740
|
+
trackedNav.routeName = name;
|
|
741
|
+
debugBuild.DEBUG_BUILD && core.debug.log(`[Tracing] Updated navigation span name from "${oldName}" to "${name}"`);
|
|
742
|
+
}
|
|
743
|
+
} else {
|
|
744
|
+
debugBuild.DEBUG_BUILD && core.debug.log(`[Tracing] Skipping duplicate navigation for location: ${locationKey}`);
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
595
748
|
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
749
|
+
// Create new navigation span (first navigation or legitimate new navigation)
|
|
750
|
+
// Reserve the spot in the map first to prevent race conditions
|
|
751
|
+
// Mark as placeholder to prevent concurrent handleNavigation calls from creating duplicates
|
|
752
|
+
const placeholderSpan = { end: () => {} } ;
|
|
753
|
+
const placeholderEntry = {
|
|
754
|
+
span: placeholderSpan,
|
|
755
|
+
routeName: name,
|
|
756
|
+
pathname: location.pathname,
|
|
757
|
+
locationKey,
|
|
758
|
+
isPlaceholder: true ,
|
|
759
|
+
};
|
|
760
|
+
activeNavigationSpans.set(client, placeholderEntry);
|
|
761
|
+
|
|
762
|
+
let navigationSpan;
|
|
763
|
+
try {
|
|
764
|
+
navigationSpan = browser.startBrowserTracingNavigationSpan(client, {
|
|
765
|
+
name: placeholderEntry.routeName, // Use placeholder's routeName in case it was updated
|
|
600
766
|
attributes: {
|
|
601
767
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
|
|
602
768
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
603
769
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: `auto.navigation.react.reactrouter_v${version}`,
|
|
604
770
|
},
|
|
605
771
|
});
|
|
772
|
+
} catch (e) {
|
|
773
|
+
// If span creation fails, remove the placeholder so we don't block future navigations
|
|
774
|
+
activeNavigationSpans.delete(client);
|
|
775
|
+
throw e;
|
|
776
|
+
}
|
|
606
777
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
778
|
+
if (navigationSpan) {
|
|
779
|
+
// Update the map with the real span (isPlaceholder omitted, defaults to false)
|
|
780
|
+
activeNavigationSpans.set(client, {
|
|
781
|
+
span: navigationSpan,
|
|
782
|
+
routeName: placeholderEntry.routeName, // Use the (potentially updated) placeholder routeName
|
|
783
|
+
pathname: location.pathname,
|
|
784
|
+
locationKey,
|
|
785
|
+
});
|
|
786
|
+
patchSpanEnd(navigationSpan, location, routes, basename, allRoutes, 'navigation');
|
|
787
|
+
} else {
|
|
788
|
+
// If no span was created, remove the placeholder
|
|
789
|
+
activeNavigationSpans.delete(client);
|
|
611
790
|
}
|
|
612
791
|
}
|
|
613
792
|
}
|
|
@@ -656,7 +835,13 @@ function updatePageloadTransaction({
|
|
|
656
835
|
: (_matchRoutes(allRoutes || routes, location, basename) );
|
|
657
836
|
|
|
658
837
|
if (branches) {
|
|
659
|
-
const [name, source] = utils.resolveRouteNameAndSource(
|
|
838
|
+
const [name, source] = utils.resolveRouteNameAndSource(
|
|
839
|
+
location,
|
|
840
|
+
allRoutes || routes,
|
|
841
|
+
allRoutes || routes,
|
|
842
|
+
branches,
|
|
843
|
+
basename,
|
|
844
|
+
);
|
|
660
845
|
|
|
661
846
|
core.getCurrentScope().setTransactionName(name || '/');
|
|
662
847
|
|
|
@@ -665,11 +850,93 @@ function updatePageloadTransaction({
|
|
|
665
850
|
activeRootSpan.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
|
|
666
851
|
|
|
667
852
|
// Patch span.end() to ensure we update the name one last time before the span is sent
|
|
668
|
-
|
|
853
|
+
patchSpanEnd(activeRootSpan, location, routes, basename, allRoutes, 'pageload');
|
|
669
854
|
}
|
|
670
855
|
}
|
|
671
856
|
}
|
|
672
857
|
|
|
858
|
+
/**
|
|
859
|
+
* Determines if a span name should be updated during wildcard route resolution.
|
|
860
|
+
*
|
|
861
|
+
* Update conditions (in priority order):
|
|
862
|
+
* 1. No current name + allowNoCurrentName: true → always update (pageload spans)
|
|
863
|
+
* 2. Current name has wildcard + new is route without wildcard → upgrade (e.g., "/users/*" → "/users/:id")
|
|
864
|
+
* 3. Current source is not 'route' + new source is 'route' → upgrade (e.g., URL → parameterized route)
|
|
865
|
+
*
|
|
866
|
+
* @param currentName - The current span name (may be undefined)
|
|
867
|
+
* @param currentSource - The current span source ('route', 'url', or undefined)
|
|
868
|
+
* @param newName - The proposed new span name
|
|
869
|
+
* @param newSource - The proposed new span source
|
|
870
|
+
* @param allowNoCurrentName - If true, allow updates when there's no current name (for pageload spans)
|
|
871
|
+
* @returns true if the span name should be updated
|
|
872
|
+
*/
|
|
873
|
+
function shouldUpdateWildcardSpanName(
|
|
874
|
+
currentName,
|
|
875
|
+
currentSource,
|
|
876
|
+
newName,
|
|
877
|
+
newSource,
|
|
878
|
+
allowNoCurrentName = false,
|
|
879
|
+
) {
|
|
880
|
+
if (!newName) {
|
|
881
|
+
return false;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (!currentName && allowNoCurrentName) {
|
|
885
|
+
return true;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const hasWildcard = currentName && utils.transactionNameHasWildcard(currentName);
|
|
889
|
+
|
|
890
|
+
if (hasWildcard && newSource === 'route' && !utils.transactionNameHasWildcard(newName)) {
|
|
891
|
+
return true;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (currentSource !== 'route' && newSource === 'route') {
|
|
895
|
+
return true;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function tryUpdateSpanNameBeforeEnd(
|
|
902
|
+
span,
|
|
903
|
+
spanJson,
|
|
904
|
+
currentName,
|
|
905
|
+
location,
|
|
906
|
+
routes,
|
|
907
|
+
basename,
|
|
908
|
+
spanType,
|
|
909
|
+
allRoutes,
|
|
910
|
+
) {
|
|
911
|
+
try {
|
|
912
|
+
const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
|
|
913
|
+
|
|
914
|
+
if (currentSource === 'route' && currentName && !utils.transactionNameHasWildcard(currentName)) {
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const currentAllRoutes = Array.from(allRoutes);
|
|
919
|
+
const routesToUse = currentAllRoutes.length > 0 ? currentAllRoutes : routes;
|
|
920
|
+
const branches = _matchRoutes(routesToUse, location, basename) ;
|
|
921
|
+
|
|
922
|
+
if (!branches) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const [name, source] = utils.resolveRouteNameAndSource(location, routesToUse, routesToUse, branches, basename);
|
|
927
|
+
|
|
928
|
+
const isImprovement = shouldUpdateWildcardSpanName(currentName, currentSource, name, source, true);
|
|
929
|
+
const spanNotEnded = spanType === 'pageload' || !spanJson.timestamp;
|
|
930
|
+
|
|
931
|
+
if (isImprovement && spanNotEnded) {
|
|
932
|
+
span.updateName(name);
|
|
933
|
+
span.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source);
|
|
934
|
+
}
|
|
935
|
+
} catch (error) {
|
|
936
|
+
debugBuild.DEBUG_BUILD && core.debug.warn(`Error updating span details before ending: ${error}`);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
673
940
|
/**
|
|
674
941
|
* Patches the span.end() method to update the transaction name one last time before the span is sent.
|
|
675
942
|
* This handles cases where the span is cancelled early (e.g., document.hidden) before lazy routes have finished loading.
|
|
@@ -689,71 +956,93 @@ function patchSpanEnd(
|
|
|
689
956
|
return;
|
|
690
957
|
}
|
|
691
958
|
|
|
959
|
+
// Use the passed route context, or fall back to global Set
|
|
960
|
+
const allRoutesSet = _allRoutes ? new Set(_allRoutes) : allRoutes;
|
|
961
|
+
|
|
692
962
|
const originalEnd = span.end.bind(span);
|
|
963
|
+
let endCalled = false;
|
|
693
964
|
|
|
694
965
|
span.end = function patchedEnd(...args) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
966
|
+
if (endCalled) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
endCalled = true;
|
|
970
|
+
|
|
971
|
+
// Capture timestamp immediately to avoid delay from async operations
|
|
972
|
+
// If no timestamp was provided, capture the current time now
|
|
973
|
+
const endTimestamp = args.length > 0 ? args[0] : Date.now() / 1000;
|
|
974
|
+
|
|
975
|
+
const spanJson = core.spanToJSON(span);
|
|
976
|
+
const currentName = spanJson.description;
|
|
977
|
+
const currentSource = spanJson.data?.[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE];
|
|
978
|
+
|
|
979
|
+
// Helper to clean up activeNavigationSpans after span ends
|
|
980
|
+
const cleanupNavigationSpan = () => {
|
|
981
|
+
const client = core.getClient();
|
|
982
|
+
if (client && spanType === 'navigation') {
|
|
983
|
+
const trackedNav = activeNavigationSpans.get(client);
|
|
984
|
+
if (trackedNav && trackedNav.span === span) {
|
|
985
|
+
activeNavigationSpans.delete(client);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
const pendingPromises = pendingLazyRouteLoads.get(span);
|
|
991
|
+
// Wait for lazy routes if:
|
|
992
|
+
// 1. There are pending promises AND
|
|
993
|
+
// 2. Current name exists AND
|
|
994
|
+
// 3. Either the name has a wildcard OR the source is not 'route' (URL-based names)
|
|
995
|
+
const shouldWaitForLazyRoutes =
|
|
996
|
+
pendingPromises &&
|
|
997
|
+
pendingPromises.size > 0 &&
|
|
998
|
+
currentName &&
|
|
999
|
+
(utils.transactionNameHasWildcard(currentName) || currentSource !== 'route');
|
|
1000
|
+
|
|
1001
|
+
if (shouldWaitForLazyRoutes) {
|
|
1002
|
+
if (_lazyRouteTimeout === 0) {
|
|
1003
|
+
tryUpdateSpanNameBeforeEnd(span, spanJson, currentName, location, routes, basename, spanType, allRoutesSet);
|
|
1004
|
+
cleanupNavigationSpan();
|
|
1005
|
+
originalEnd(endTimestamp);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
708
1008
|
|
|
709
|
-
|
|
710
|
-
|
|
1009
|
+
const allSettled = Promise.allSettled(pendingPromises).then(() => {});
|
|
1010
|
+
const waitPromise =
|
|
1011
|
+
_lazyRouteTimeout === Infinity
|
|
1012
|
+
? allSettled
|
|
1013
|
+
: Promise.race([allSettled, new Promise(r => setTimeout(r, _lazyRouteTimeout))]);
|
|
1014
|
+
|
|
1015
|
+
waitPromise
|
|
1016
|
+
.then(() => {
|
|
1017
|
+
const updatedSpanJson = core.spanToJSON(span);
|
|
1018
|
+
tryUpdateSpanNameBeforeEnd(
|
|
1019
|
+
span,
|
|
1020
|
+
updatedSpanJson,
|
|
1021
|
+
updatedSpanJson.description,
|
|
711
1022
|
location,
|
|
712
1023
|
routes,
|
|
713
|
-
currentAllRoutes.length > 0 ? currentAllRoutes : routes,
|
|
714
|
-
branches,
|
|
715
1024
|
basename,
|
|
1025
|
+
spanType,
|
|
1026
|
+
allRoutesSet,
|
|
716
1027
|
);
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
} catch (error) {
|
|
726
|
-
// Silently catch errors to ensure span.end() is always called
|
|
727
|
-
debugBuild.DEBUG_BUILD && core.debug.warn(`Error updating span details before ending: ${error}`);
|
|
1028
|
+
cleanupNavigationSpan();
|
|
1029
|
+
originalEnd(endTimestamp);
|
|
1030
|
+
})
|
|
1031
|
+
.catch(() => {
|
|
1032
|
+
cleanupNavigationSpan();
|
|
1033
|
+
originalEnd(endTimestamp);
|
|
1034
|
+
});
|
|
1035
|
+
return;
|
|
728
1036
|
}
|
|
729
1037
|
|
|
730
|
-
|
|
1038
|
+
tryUpdateSpanNameBeforeEnd(span, spanJson, currentName, location, routes, basename, spanType, allRoutesSet);
|
|
1039
|
+
cleanupNavigationSpan();
|
|
1040
|
+
originalEnd(endTimestamp);
|
|
731
1041
|
};
|
|
732
1042
|
|
|
733
|
-
// Mark this span as having its end() method patched to prevent duplicate patching
|
|
734
1043
|
core.addNonEnumerableProperty(span , patchedPropertyName, true);
|
|
735
1044
|
}
|
|
736
1045
|
|
|
737
|
-
function patchPageloadSpanEnd(
|
|
738
|
-
span,
|
|
739
|
-
location,
|
|
740
|
-
routes,
|
|
741
|
-
basename,
|
|
742
|
-
_allRoutes,
|
|
743
|
-
) {
|
|
744
|
-
patchSpanEnd(span, location, routes, basename, _allRoutes, 'pageload');
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function patchNavigationSpanEnd(
|
|
748
|
-
span,
|
|
749
|
-
location,
|
|
750
|
-
routes,
|
|
751
|
-
basename,
|
|
752
|
-
_allRoutes,
|
|
753
|
-
) {
|
|
754
|
-
patchSpanEnd(span, location, routes, basename, _allRoutes, 'navigation');
|
|
755
|
-
}
|
|
756
|
-
|
|
757
1046
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
758
1047
|
function createV6CompatibleWithSentryReactRouterRouting(
|
|
759
1048
|
Routes,
|
|
@@ -789,11 +1078,13 @@ function createV6CompatibleWithSentryReactRouterRouting(
|
|
|
789
1078
|
});
|
|
790
1079
|
isMountRenderPass.current = false;
|
|
791
1080
|
} else {
|
|
1081
|
+
// Note: Component-based routes don't support lazy route tracking via lazyRouteTimeout
|
|
1082
|
+
// because React.lazy() loads happen at the component level, not the router level.
|
|
1083
|
+
// Use createBrowserRouter with patchRoutesOnNavigation for lazy route tracking.
|
|
792
1084
|
handleNavigation({ location, routes, navigationType, version, allRoutes: Array.from(allRoutes) });
|
|
793
1085
|
}
|
|
794
1086
|
},
|
|
795
|
-
//
|
|
796
|
-
// when the children change. We only want to start transactions when the location or navigation type change.
|
|
1087
|
+
// Re-run only on location/navigation changes, not children changes
|
|
797
1088
|
[location, navigationType],
|
|
798
1089
|
);
|
|
799
1090
|
|
|
@@ -826,6 +1117,7 @@ function getActiveRootSpan() {
|
|
|
826
1117
|
exports.addResolvedRoutesToParent = addResolvedRoutesToParent;
|
|
827
1118
|
exports.addRoutesToAllRoutes = addRoutesToAllRoutes;
|
|
828
1119
|
exports.allRoutes = allRoutes;
|
|
1120
|
+
exports.computeLocationKey = computeLocationKey;
|
|
829
1121
|
exports.createReactRouterV6CompatibleTracingIntegration = createReactRouterV6CompatibleTracingIntegration;
|
|
830
1122
|
exports.createV6CompatibleWithSentryReactRouterRouting = createV6CompatibleWithSentryReactRouterRouting;
|
|
831
1123
|
exports.createV6CompatibleWrapCreateBrowserRouter = createV6CompatibleWrapCreateBrowserRouter;
|
|
@@ -833,5 +1125,6 @@ exports.createV6CompatibleWrapCreateMemoryRouter = createV6CompatibleWrapCreateM
|
|
|
833
1125
|
exports.createV6CompatibleWrapUseRoutes = createV6CompatibleWrapUseRoutes;
|
|
834
1126
|
exports.handleNavigation = handleNavigation;
|
|
835
1127
|
exports.processResolvedRoutes = processResolvedRoutes;
|
|
1128
|
+
exports.shouldSkipNavigation = shouldSkipNavigation;
|
|
836
1129
|
exports.updateNavigationSpan = updateNavigationSpan;
|
|
837
1130
|
//# sourceMappingURL=instrumentation.js.map
|