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