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