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