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