@sentry/react-router 11.0.0-alpha.1 → 11.0.0-alpha.2
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 +2 -2
- package/build/cjs/client/createClientInstrumentation.js +30 -23
- package/build/cjs/client/createClientInstrumentation.js.map +1 -1
- package/build/cjs/client/hydratedRouter.js +11 -19
- package/build/cjs/client/hydratedRouter.js.map +1 -1
- package/build/cjs/client/tracingIntegration.js.map +1 -1
- package/build/cjs/client/utils.js +32 -12
- package/build/cjs/client/utils.js.map +1 -1
- package/build/cjs/cloudflare/index.js +3 -2
- package/build/cjs/cloudflare/index.js.map +1 -1
- package/build/cjs/common/utils.js.map +1 -1
- package/build/cjs/index.server.js +0 -6
- package/build/cjs/index.server.js.map +1 -1
- package/build/cjs/server/createServerInstrumentation.js +22 -15
- package/build/cjs/server/createServerInstrumentation.js.map +1 -1
- package/build/cjs/server/getMetaTagTransformer.js +3 -2
- package/build/cjs/server/getMetaTagTransformer.js.map +1 -1
- package/build/cjs/server/integration/lowQualityTransactionsFilterIntegration.js +3 -2
- package/build/cjs/server/integration/lowQualityTransactionsFilterIntegration.js.map +1 -1
- package/build/cjs/server/wrapSentryHandleRequest.js +2 -2
- package/build/cjs/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/cjs/vite/buildEnd/handleOnBuildEnd.js +17 -29
- package/build/cjs/vite/buildEnd/handleOnBuildEnd.js.map +1 -1
- package/build/cjs/vite/index.js +12 -0
- package/build/cjs/vite/index.js.map +1 -0
- package/build/cjs/vite/makeCustomSentryVitePlugins.js +21 -32
- package/build/cjs/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/esm/client/createClientInstrumentation.js +22 -15
- package/build/esm/client/createClientInstrumentation.js.map +1 -1
- package/build/esm/client/hydratedRouter.js +14 -22
- package/build/esm/client/hydratedRouter.js.map +1 -1
- package/build/esm/client/tracingIntegration.js.map +1 -1
- package/build/esm/client/utils.js +34 -14
- package/build/esm/client/utils.js.map +1 -1
- package/build/esm/cloudflare/index.js +3 -2
- package/build/esm/cloudflare/index.js.map +1 -1
- package/build/esm/common/utils.js.map +1 -1
- package/build/esm/index.server.js +0 -3
- package/build/esm/index.server.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/server/createServerInstrumentation.js +25 -18
- package/build/esm/server/createServerInstrumentation.js.map +1 -1
- package/build/esm/server/getMetaTagTransformer.js +3 -2
- package/build/esm/server/getMetaTagTransformer.js.map +1 -1
- package/build/esm/server/integration/lowQualityTransactionsFilterIntegration.js +3 -2
- package/build/esm/server/integration/lowQualityTransactionsFilterIntegration.js.map +1 -1
- package/build/esm/server/wrapSentryHandleRequest.js +4 -4
- package/build/esm/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/esm/vite/buildEnd/handleOnBuildEnd.js +17 -29
- package/build/esm/vite/buildEnd/handleOnBuildEnd.js.map +1 -1
- package/build/esm/vite/index.js +4 -0
- package/build/esm/vite/index.js.map +1 -0
- package/build/esm/vite/makeCustomSentryVitePlugins.js +21 -32
- package/build/esm/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/types/client/createClientInstrumentation.d.ts +1 -1
- package/build/types/client/createClientInstrumentation.d.ts.map +1 -1
- package/build/types/client/hydratedRouter.d.ts.map +1 -1
- package/build/types/client/tracingIntegration.d.ts +1 -1
- package/build/types/client/tracingIntegration.d.ts.map +1 -1
- package/build/types/client/utils.d.ts +7 -1
- package/build/types/client/utils.d.ts.map +1 -1
- package/build/types/cloudflare/index.d.ts.map +1 -1
- package/build/types/common/utils.d.ts +3 -2
- package/build/types/common/utils.d.ts.map +1 -1
- package/build/types/index.server.d.ts +0 -1
- package/build/types/index.server.d.ts.map +1 -1
- package/build/types/index.types.d.ts +3 -1
- package/build/types/index.types.d.ts.map +1 -1
- package/build/types/server/createServerInstrumentation.d.ts +1 -1
- package/build/types/server/createServerInstrumentation.d.ts.map +1 -1
- package/build/types/server/getMetaTagTransformer.d.ts.map +1 -1
- package/build/types/server/integration/lowQualityTransactionsFilterIntegration.d.ts.map +1 -1
- package/build/types/server/wrapSentryHandleRequest.d.ts.map +1 -1
- package/build/types/vite/buildEnd/handleOnBuildEnd.d.ts.map +1 -1
- package/build/types/vite/makeCustomSentryVitePlugins.d.ts.map +1 -1
- package/build/types/vite/types.d.ts +15 -13
- package/build/types/vite/types.d.ts.map +1 -1
- package/package.json +16 -10
- package/build/types/common/types.d.ts +0 -86
- package/build/types/common/types.d.ts.map +0 -1
|
@@ -1,66 +1,55 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const vite = require('@sentry/bundler-plugins/vite');
|
|
4
|
+
const core = require('@sentry/core');
|
|
4
5
|
|
|
5
6
|
async function makeCustomSentryVitePlugins(options) {
|
|
7
|
+
core.warnOnRemovedBuildOptions(options, ["unstable_sentryVitePluginOptions"]);
|
|
6
8
|
const {
|
|
7
9
|
debug,
|
|
8
|
-
unstable_sentryVitePluginOptions,
|
|
9
10
|
bundleSizeOptimizations,
|
|
10
11
|
applicationKey,
|
|
11
12
|
authToken,
|
|
13
|
+
errorHandler,
|
|
14
|
+
headers,
|
|
15
|
+
moduleMetadata,
|
|
12
16
|
org,
|
|
13
17
|
project,
|
|
18
|
+
sentryUrl,
|
|
19
|
+
silent,
|
|
14
20
|
telemetry,
|
|
15
21
|
reactComponentAnnotation,
|
|
16
22
|
release
|
|
17
23
|
} = options;
|
|
18
|
-
const unstableSourcemapsDisable = unstable_sentryVitePluginOptions?.sourcemaps?.disable;
|
|
19
|
-
if (unstableSourcemapsDisable !== void 0 && unstableSourcemapsDisable !== true) {
|
|
20
|
-
console.warn(
|
|
21
|
-
`[Sentry] \`unstable_sentryVitePluginOptions.sourcemaps.disable: ${JSON.stringify(
|
|
22
|
-
unstableSourcemapsDisable
|
|
23
|
-
)}\` does not apply to the Vite plugin. Debug ID injection and source map upload are handled by the \`sentryOnBuildEnd\` hook for React Router, so letting the Vite plugin do it as well would inject a second debug ID per chunk. The option still applies to \`sentryOnBuildEnd\`.`
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
24
|
const sentryVitePlugins = vite.sentryVitePlugin({
|
|
27
25
|
applicationKey,
|
|
28
26
|
authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN,
|
|
29
27
|
bundleSizeOptimizations,
|
|
30
28
|
debug: debug ?? false,
|
|
29
|
+
errorHandler,
|
|
30
|
+
headers,
|
|
31
|
+
moduleMetadata,
|
|
31
32
|
org: org ?? process.env.SENTRY_ORG,
|
|
32
33
|
project: project ?? process.env.SENTRY_PROJECT,
|
|
34
|
+
reactComponentAnnotation,
|
|
35
|
+
release,
|
|
36
|
+
silent,
|
|
33
37
|
telemetry: telemetry ?? true,
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
38
|
+
// Release creation runs in the plugin's `writeBundle` even with `sourcemaps.disable` set below,
|
|
39
|
+
// so the plugin needs its own URL rather than relying on the `sentryOnBuildEnd` CLI instance.
|
|
40
|
+
url: sentryUrl,
|
|
37
41
|
_metaOptions: {
|
|
38
|
-
...unstable_sentryVitePluginOptions?._metaOptions,
|
|
39
42
|
telemetry: {
|
|
40
|
-
...unstable_sentryVitePluginOptions?._metaOptions?.telemetry,
|
|
41
43
|
metaFramework: "react-router"
|
|
42
44
|
}
|
|
43
45
|
},
|
|
44
|
-
reactComponentAnnotation: {
|
|
45
|
-
// Only assign when set, as an explicit `undefined` would erase the unstable value
|
|
46
|
-
...reactComponentAnnotation?.enabled !== void 0 && { enabled: reactComponentAnnotation.enabled },
|
|
47
|
-
...reactComponentAnnotation?.ignoredComponents !== void 0 && {
|
|
48
|
-
ignoredComponents: reactComponentAnnotation.ignoredComponents
|
|
49
|
-
},
|
|
50
|
-
...unstable_sentryVitePluginOptions?.reactComponentAnnotation
|
|
51
|
-
},
|
|
52
|
-
release: {
|
|
53
|
-
...unstable_sentryVitePluginOptions?.release,
|
|
54
|
-
...release
|
|
55
|
-
},
|
|
56
46
|
sourcemaps: {
|
|
57
|
-
|
|
58
|
-
//
|
|
59
|
-
//
|
|
47
|
+
// Debug ID injection and upload are handled by the `sentryOnBuildEnd` hook, so the Vite plugin
|
|
48
|
+
// must never do it as well - that would inject a second debug ID per chunk and break source
|
|
49
|
+
// map resolution.
|
|
60
50
|
disable: true,
|
|
61
|
-
// The plugin deletes these in a `finally` block that runs regardless of `disable`,
|
|
62
|
-
//
|
|
63
|
-
// Deletion is handled there instead, from the same option.
|
|
51
|
+
// The plugin deletes these in a `finally` block that runs regardless of `disable`, which would
|
|
52
|
+
// remove the maps before `sentryOnBuildEnd` gets to upload them. Deletion happens there instead.
|
|
64
53
|
filesToDeleteAfterUpload: void 0
|
|
65
54
|
}
|
|
66
55
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"makeCustomSentryVitePlugins.js","sources":["../../../src/vite/makeCustomSentryVitePlugins.ts"],"sourcesContent":["import { sentryVitePlugin } from '@sentry/bundler-plugins/vite';\nimport { type Plugin } from 'vite';\nimport type { SentryReactRouterBuildOptions } from './types';\n\n/**\n * Create a custom subset of sentry's vite plugins\n */\nexport async function makeCustomSentryVitePlugins(options: SentryReactRouterBuildOptions): Promise<Plugin[]> {\n const {\n debug,\n
|
|
1
|
+
{"version":3,"file":"makeCustomSentryVitePlugins.js","sources":["../../../src/vite/makeCustomSentryVitePlugins.ts"],"sourcesContent":["import { sentryVitePlugin } from '@sentry/bundler-plugins/vite';\nimport { warnOnRemovedBuildOptions } from '@sentry/core';\nimport { type Plugin } from 'vite';\nimport type { SentryReactRouterBuildOptions } from './types';\n\n/**\n * Create a custom subset of sentry's vite plugins\n */\nexport async function makeCustomSentryVitePlugins(options: SentryReactRouterBuildOptions): Promise<Plugin[]> {\n warnOnRemovedBuildOptions(options, ['unstable_sentryVitePluginOptions']);\n\n const {\n debug,\n bundleSizeOptimizations,\n applicationKey,\n authToken,\n errorHandler,\n headers,\n moduleMetadata,\n org,\n project,\n sentryUrl,\n silent,\n telemetry,\n reactComponentAnnotation,\n release,\n } = options;\n\n const sentryVitePlugins = sentryVitePlugin({\n applicationKey,\n authToken: authToken ?? process.env.SENTRY_AUTH_TOKEN,\n bundleSizeOptimizations,\n debug: debug ?? false,\n errorHandler,\n headers,\n moduleMetadata,\n org: org ?? process.env.SENTRY_ORG,\n project: project ?? process.env.SENTRY_PROJECT,\n reactComponentAnnotation,\n release,\n silent,\n telemetry: telemetry ?? true,\n // Release creation runs in the plugin's `writeBundle` even with `sourcemaps.disable` set below,\n // so the plugin needs its own URL rather than relying on the `sentryOnBuildEnd` CLI instance.\n url: sentryUrl,\n _metaOptions: {\n telemetry: {\n metaFramework: 'react-router',\n },\n },\n sourcemaps: {\n // Debug ID injection and upload are handled by the `sentryOnBuildEnd` hook, so the Vite plugin\n // must never do it as well - that would inject a second debug ID per chunk and break source\n // map resolution.\n disable: true,\n // The plugin deletes these in a `finally` block that runs regardless of `disable`, which would\n // remove the maps before `sentryOnBuildEnd` gets to upload them. Deletion happens there instead.\n filesToDeleteAfterUpload: undefined,\n },\n }) as Plugin[];\n\n return sentryVitePlugins;\n}\n"],"names":["warnOnRemovedBuildOptions","sentryVitePlugin"],"mappings":";;;;;AAQA,eAAsB,4BAA4B,OAAA,EAA2D;AAC3G,EAAAA,8BAAA,CAA0B,OAAA,EAAS,CAAC,kCAAkC,CAAC,CAAA;AAEvE,EAAA,MAAM;AAAA,IACJ,KAAA;AAAA,IACA,uBAAA;AAAA,IACA,cAAA;AAAA,IACA,SAAA;AAAA,IACA,YAAA;AAAA,IACA,OAAA;AAAA,IACA,cAAA;AAAA,IACA,GAAA;AAAA,IACA,OAAA;AAAA,IACA,SAAA;AAAA,IACA,MAAA;AAAA,IACA,SAAA;AAAA,IACA,wBAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,oBAAoBC,qBAAA,CAAiB;AAAA,IACzC,cAAA;AAAA,IACA,SAAA,EAAW,SAAA,IAAa,OAAA,CAAQ,GAAA,CAAI,iBAAA;AAAA,IACpC,uBAAA;AAAA,IACA,OAAO,KAAA,IAAS,KAAA;AAAA,IAChB,YAAA;AAAA,IACA,OAAA;AAAA,IACA,cAAA;AAAA,IACA,GAAA,EAAK,GAAA,IAAO,OAAA,CAAQ,GAAA,CAAI,UAAA;AAAA,IACxB,OAAA,EAAS,OAAA,IAAW,OAAA,CAAQ,GAAA,CAAI,cAAA;AAAA,IAChC,wBAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAW,SAAA,IAAa,IAAA;AAAA;AAAA;AAAA,IAGxB,GAAA,EAAK,SAAA;AAAA,IACL,YAAA,EAAc;AAAA,MACZ,SAAA,EAAW;AAAA,QACT,aAAA,EAAe;AAAA;AACjB,KACF;AAAA,IACA,UAAA,EAAY;AAAA;AAAA;AAAA;AAAA,MAIV,OAAA,EAAS,IAAA;AAAA;AAAA;AAAA,MAGT,wBAAA,EAA0B;AAAA;AAC5B,GACD,CAAA;AAED,EAAA,OAAO,iBAAA;AACT;;;;"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { startBrowserTracingNavigationSpan, getAbsoluteUrl } from '@sentry/browser';
|
|
2
|
-
import { debug,
|
|
2
|
+
import { debug, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, GLOBAL_OBJ, getClient, NAVIGATION_SPAN_NAME_FALLBACK, SEMANTIC_ATTRIBUTE_SENTRY_OP, hasSpanStreamingEnabled, filterCollectedUrl, getActiveSpan, getRootSpan, spanToJSON, updateSpanName } from '@sentry/core';
|
|
3
|
+
import { startSpan } from '@sentry/core/browser';
|
|
3
4
|
import { DEBUG_BUILD } from '../common/debug-build.js';
|
|
4
5
|
import { captureInstrumentationError, getPathFromRequest, normalizeRoutePath, getPattern } from '../common/utils.js';
|
|
5
6
|
import { updateNavigationSpanUrlFromLocation, finalizeNavigationSpanFromHydratedRouter, resolveNavigateArg, resolveNavigateAbsoluteUrl } from './utils.js';
|
|
6
|
-
import { CODE_FUNCTION_NAME, SENTRY_OP, URL_FULL, URL_TEMPLATE } from '@sentry/conventions/attributes';
|
|
7
|
-
import {
|
|
7
|
+
import { CODE_FUNCTION_NAME, SENTRY_OP, SENTRY_SEGMENT_NAME_SOURCE, URL_FULL, URL_TEMPLATE } from '@sentry/conventions/attributes';
|
|
8
|
+
import { FUNCTION, MIDDLEWARE } from '@sentry/conventions/op';
|
|
8
9
|
|
|
9
10
|
const WINDOW = GLOBAL_OBJ;
|
|
10
11
|
let currentNumericNavigationSpan;
|
|
@@ -39,9 +40,11 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
39
40
|
startBrowserTracingNavigationSpan(
|
|
40
41
|
client,
|
|
41
42
|
{
|
|
42
|
-
|
|
43
|
+
// With span streaming, span names have to be low cardinality, so we can't fall back to
|
|
44
|
+
// the URL. The route hooks parameterize the span once they resolve.
|
|
45
|
+
name: hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : pathname,
|
|
43
46
|
attributes: {
|
|
44
|
-
[
|
|
47
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: "url",
|
|
45
48
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation",
|
|
46
49
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.react_router.instrumentation_api",
|
|
47
50
|
"navigation.type": "browser.popstate"
|
|
@@ -73,9 +76,11 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
73
76
|
navigationSpan2 = startBrowserTracingNavigationSpan(
|
|
74
77
|
client2,
|
|
75
78
|
{
|
|
76
|
-
|
|
79
|
+
// With span streaming, span names have to be low cardinality, so we can't fall back
|
|
80
|
+
// to the URL. The route is resolved once the navigation settles.
|
|
81
|
+
name: hasSpanStreamingEnabled(client2) ? NAVIGATION_SPAN_NAME_FALLBACK : currentPathname,
|
|
77
82
|
attributes: {
|
|
78
|
-
[
|
|
83
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: "url",
|
|
79
84
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation",
|
|
80
85
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.react_router.instrumentation_api",
|
|
81
86
|
"navigation.type": navigationType
|
|
@@ -110,9 +115,11 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
110
115
|
navigationSpan = startBrowserTracingNavigationSpan(
|
|
111
116
|
client,
|
|
112
117
|
{
|
|
113
|
-
|
|
118
|
+
// With span streaming, span names have to be low cardinality, so we can't fall back to
|
|
119
|
+
// the URL. The route hooks parameterize the span once they resolve.
|
|
120
|
+
name: hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : toPath,
|
|
114
121
|
attributes: {
|
|
115
|
-
[
|
|
122
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: "url",
|
|
116
123
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation",
|
|
117
124
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.react_router.instrumentation_api",
|
|
118
125
|
"navigation.type": "router.navigate"
|
|
@@ -137,7 +144,7 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
137
144
|
{
|
|
138
145
|
name: `Fetcher ${info.fetcherKey}`,
|
|
139
146
|
attributes: {
|
|
140
|
-
[SENTRY_OP]:
|
|
147
|
+
[SENTRY_OP]: FUNCTION,
|
|
141
148
|
[CODE_FUNCTION_NAME]: "fetcher",
|
|
142
149
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.react_router.instrumentation_api"
|
|
143
150
|
}
|
|
@@ -167,7 +174,7 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
167
174
|
{
|
|
168
175
|
name: routePattern,
|
|
169
176
|
attributes: {
|
|
170
|
-
[SENTRY_OP]:
|
|
177
|
+
[SENTRY_OP]: FUNCTION,
|
|
171
178
|
[CODE_FUNCTION_NAME]: "clientLoader",
|
|
172
179
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.react_router.instrumentation_api"
|
|
173
180
|
}
|
|
@@ -192,7 +199,7 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
192
199
|
{
|
|
193
200
|
name: routePattern,
|
|
194
201
|
attributes: {
|
|
195
|
-
[SENTRY_OP]:
|
|
202
|
+
[SENTRY_OP]: FUNCTION,
|
|
196
203
|
[CODE_FUNCTION_NAME]: "clientAction",
|
|
197
204
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.react_router.instrumentation_api"
|
|
198
205
|
}
|
|
@@ -222,7 +229,7 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
222
229
|
{
|
|
223
230
|
name: `middleware ${routeId}`,
|
|
224
231
|
attributes: {
|
|
225
|
-
[SENTRY_OP]:
|
|
232
|
+
[SENTRY_OP]: MIDDLEWARE,
|
|
226
233
|
[CODE_FUNCTION_NAME]: "clientMiddleware",
|
|
227
234
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.react_router.instrumentation_api",
|
|
228
235
|
"react_router.route.id": routeId,
|
|
@@ -246,7 +253,7 @@ function createSentryClientInstrumentation(options = {}) {
|
|
|
246
253
|
{
|
|
247
254
|
name: "Lazy Route Load",
|
|
248
255
|
attributes: {
|
|
249
|
-
[SENTRY_OP]:
|
|
256
|
+
[SENTRY_OP]: FUNCTION,
|
|
250
257
|
[CODE_FUNCTION_NAME]: "lazy",
|
|
251
258
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.function.react_router.instrumentation_api"
|
|
252
259
|
}
|
|
@@ -279,7 +286,7 @@ function updateRootSpanRoute(routeName, hasPattern) {
|
|
|
279
286
|
return;
|
|
280
287
|
}
|
|
281
288
|
updateSpanName(rootSpan, routeName);
|
|
282
|
-
rootSpan.setAttributes({ [
|
|
289
|
+
rootSpan.setAttributes({ [SENTRY_SEGMENT_NAME_SOURCE]: "route", [URL_TEMPLATE]: routeName });
|
|
283
290
|
}
|
|
284
291
|
function isClientInstrumentationApiUsed() {
|
|
285
292
|
return !!GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createClientInstrumentation.js","sources":["../../../src/client/createClientInstrumentation.ts"],"sourcesContent":["// oxlint-disable max-lines\nimport { getAbsoluteUrl, startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n SPAN_STATUS_ERROR,\n startSpan,\n updateSpanName,\n filterCollectedUrl,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport type { ClientInstrumentation, InstrumentableRoute, InstrumentableRouter } from '../common/types';\nimport { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils';\nimport {\n resolveNavigateAbsoluteUrl,\n resolveNavigateArg,\n finalizeNavigationSpanFromHydratedRouter,\n updateNavigationSpanUrlFromLocation,\n} from './utils';\nimport { CODE_FUNCTION_NAME, SENTRY_OP, URL_FULL, URL_TEMPLATE } from '@sentry/conventions/attributes';\nimport { GENERAL_FUNCTION_SPAN_OP, WEB_SERVER_MIDDLEWARE_SPAN_OP } from '@sentry/conventions/op';\n\nconst WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;\n\n// Tracks active numeric navigation span to prevent duplicate spans when popstate fires\nlet currentNumericNavigationSpan: Span | undefined;\n\n// Per-request middleware counters, keyed by Request\nconst middlewareCountersMap = new WeakMap<object, Record<string, number>>();\n\nconst SENTRY_CLIENT_INSTRUMENTATION_FLAG = '__sentryReactRouterClientInstrumentationUsed';\n// Intentionally never reset - once set, instrumentation API handles all navigations for the session.\nconst SENTRY_NAVIGATE_HOOK_INVOKED_FLAG = '__sentryReactRouterNavigateHookInvoked';\nconst SENTRY_POPSTATE_LISTENER_ADDED_FLAG = '__sentryReactRouterPopstateListenerAdded';\n\ntype GlobalObjWithFlags = typeof GLOBAL_OBJ & {\n [SENTRY_CLIENT_INSTRUMENTATION_FLAG]?: boolean;\n [SENTRY_NAVIGATE_HOOK_INVOKED_FLAG]?: boolean;\n [SENTRY_POPSTATE_LISTENER_ADDED_FLAG]?: boolean;\n};\n\nconst GLOBAL_WITH_FLAGS = GLOBAL_OBJ as GlobalObjWithFlags;\n\n/**\n * Options for creating Sentry client instrumentation.\n */\nexport interface CreateSentryClientInstrumentationOptions {\n /**\n * Whether to capture errors from loaders/actions automatically.\n * Set to `false` to avoid duplicates if using custom error handlers.\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Creates a Sentry client instrumentation for React Router's instrumentation API.\n */\nexport function createSentryClientInstrumentation(\n options: CreateSentryClientInstrumentationOptions = {},\n): ClientInstrumentation {\n const { captureErrors = true } = options;\n\n DEBUG_BUILD && debug.log('React Router client instrumentation API created.');\n\n return {\n router(router: InstrumentableRouter) {\n // Set the flag when React Router actually invokes our instrumentation.\n // This ensures the flag is only set in Library Mode (where hooks run),\n // not in Framework Mode (where hooks are never called).\n // See: https://github.com/remix-run/react-router/discussions/13749\n GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG] = true;\n DEBUG_BUILD && debug.log('React Router client instrumentation API router hook registered.');\n\n // Add popstate listener for browser back/forward navigation (persists for session, one listener only)\n if (!GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] && WINDOW.addEventListener) {\n GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] = true;\n\n WINDOW.addEventListener('popstate', () => {\n const client = getClient();\n if (!client) {\n currentNumericNavigationSpan = undefined;\n return;\n }\n\n const pathname = WINDOW.location?.pathname || '/';\n\n // If there's an active numeric navigation span, update it instead of creating a duplicate\n if (currentNumericNavigationSpan) {\n if (currentNumericNavigationSpan.isRecording()) {\n updateNavigationSpanUrlFromLocation(currentNumericNavigationSpan);\n }\n currentNumericNavigationSpan = undefined;\n return;\n }\n\n // Only create a new span for actual browser back/forward button clicks\n startBrowserTracingNavigationSpan(\n client,\n {\n name: pathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': 'browser.popstate',\n },\n },\n { url: getAbsoluteUrl(pathname) },\n );\n });\n\n DEBUG_BUILD && debug.log('React Router popstate listener registered for browser back/forward navigation.');\n }\n\n router.instrument({\n async navigate(callNavigate, info) {\n // navigate(0) triggers a page reload - skip span creation, but still capture errors\n // (navigation can be rejected before reload, e.g., by a navigation guard)\n if (info.to === 0) {\n const result = await callNavigate();\n if (result.status === 'error' && result.error instanceof Error) {\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: filterCollectedUrl(info.currentUrl),\n });\n }\n return;\n }\n\n GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG] = true;\n\n // Handle numeric navigations (navigate(-1), navigate(1), etc.)\n if (typeof info.to === 'number') {\n const client = getClient();\n let navigationSpan;\n\n if (client) {\n const navigationType = info.to < 0 ? 'router.back' : 'router.forward';\n const currentPathname = WINDOW.location?.pathname || info.currentUrl;\n\n navigationSpan = startBrowserTracingNavigationSpan(\n client,\n {\n name: currentPathname,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': navigationType,\n },\n },\n { url: getAbsoluteUrl(currentPathname) },\n );\n\n // Store ref so popstate listener can update it instead of creating a duplicate\n currentNumericNavigationSpan = navigationSpan;\n }\n\n try {\n const result = await callNavigate();\n\n if (navigationSpan && WINDOW.location) {\n finalizeNavigationSpanFromHydratedRouter(navigationSpan);\n }\n\n if (result.status === 'error' && result.error instanceof Error) {\n if (navigationSpan) {\n navigationSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n }\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: WINDOW.location?.pathname || filterCollectedUrl(info.currentUrl),\n });\n }\n } finally {\n currentNumericNavigationSpan = undefined;\n }\n return;\n }\n\n // Handle string/object navigations (e.g., navigate('/about') or navigate({ pathname: '/about' }))\n const client = getClient();\n const toPath = resolveNavigateArg(info.to);\n let navigationSpan;\n\n if (client) {\n navigationSpan = startBrowserTracingNavigationSpan(\n client,\n {\n name: toPath,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': 'router.navigate',\n },\n },\n { url: resolveNavigateAbsoluteUrl(info.to, info.currentUrl) },\n );\n }\n\n const result = await callNavigate();\n if (result.status === 'error' && result.error instanceof Error) {\n if (navigationSpan) {\n navigationSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n }\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: filterCollectedUrl(toPath),\n });\n }\n return;\n },\n\n async fetch(callFetch, info) {\n await startSpan(\n {\n name: `Fetcher ${info.fetcherKey}`,\n attributes: {\n [SENTRY_OP]: GENERAL_FUNCTION_SPAN_OP,\n [CODE_FUNCTION_NAME]: 'fetcher',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callFetch();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.fetcher', {\n [URL_FULL]: filterCollectedUrl(info.href),\n });\n }\n },\n );\n },\n });\n },\n\n route(route: InstrumentableRoute) {\n const routeId = route.id;\n\n route.instrument({\n async loader(callLoader, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = normalizeRoutePath(getPattern(info));\n const routePattern = pattern || urlPath;\n // Parameterize the active navigation root span. (Route hooks don't fire on initial\n // pageload, so this only affects navigations.)\n updateRootSpanRoute(routePattern, !!pattern);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SENTRY_OP]: GENERAL_FUNCTION_SPAN_OP,\n [CODE_FUNCTION_NAME]: 'clientLoader',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLoader();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_loader', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async action(callAction, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = normalizeRoutePath(getPattern(info));\n const routePattern = pattern || urlPath;\n updateRootSpanRoute(routePattern, !!pattern);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SENTRY_OP]: GENERAL_FUNCTION_SPAN_OP,\n [CODE_FUNCTION_NAME]: 'clientAction',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callAction();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_action', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async middleware(callMiddleware, info) {\n const urlPath = getPathFromRequest(info.request);\n const routePattern = normalizeRoutePath(getPattern(info)) || urlPath;\n\n let counters = middlewareCountersMap.get(info.request);\n if (!counters) {\n counters = {};\n middlewareCountersMap.set(info.request, counters);\n }\n\n const middlewareIndex = counters[routeId] ?? 0;\n counters[routeId] = middlewareIndex + 1;\n\n await startSpan(\n {\n name: `middleware ${routeId}`,\n attributes: {\n [SENTRY_OP]: WEB_SERVER_MIDDLEWARE_SPAN_OP,\n [CODE_FUNCTION_NAME]: 'clientMiddleware',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n 'react_router.route.id': routeId,\n 'http.route': routePattern,\n 'react_router.middleware.index': middlewareIndex,\n },\n },\n async span => {\n const result = await callMiddleware();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_middleware', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async lazy(callLazy) {\n await startSpan(\n {\n name: 'Lazy Route Load',\n attributes: {\n [SENTRY_OP]: GENERAL_FUNCTION_SPAN_OP,\n [CODE_FUNCTION_NAME]: 'lazy',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLazy();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_lazy', {});\n }\n },\n );\n },\n });\n },\n };\n}\n\n/**\n * Updates the active navigation/pageload root span name with the parameterized route, so the\n * transaction reflects the parameterized route pattern (e.g. `/users/:id`).\n */\nfunction updateRootSpanRoute(routeName: string, hasPattern: boolean): void {\n if (!hasPattern) {\n return;\n }\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan && getRootSpan(activeSpan);\n if (!rootSpan) {\n return;\n }\n\n const { attributes } = spanToJSON(rootSpan);\n const op = attributes[SENTRY_OP];\n if (op !== 'navigation' && op !== 'pageload') {\n return;\n }\n\n updateSpanName(rootSpan, routeName);\n rootSpan.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', [URL_TEMPLATE]: routeName });\n}\n\n/**\n * Check if React Router's instrumentation API is being used on the client.\n */\nexport function isClientInstrumentationApiUsed(): boolean {\n return !!GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG];\n}\n\n/**\n * Check if React Router's instrumentation API's navigate hook was invoked.\n */\nexport function isNavigateHookInvoked(): boolean {\n return !!GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG];\n}\n"],"names":["result","client","navigationSpan"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createClientInstrumentation.js","sources":["../../../src/client/createClientInstrumentation.ts"],"sourcesContent":["// oxlint-disable max-lines\nimport { getAbsoluteUrl, startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n hasSpanStreamingEnabled,\n NAVIGATION_SPAN_NAME_FALLBACK,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n spanToJSON,\n SPAN_STATUS_ERROR,\n updateSpanName,\n filterCollectedUrl,\n} from '@sentry/core';\nimport { startSpan } from '@sentry/core/browser';\nimport type { ClientInstrumentation } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { captureInstrumentationError, getPathFromRequest, getPattern, normalizeRoutePath } from '../common/utils';\nimport {\n resolveNavigateAbsoluteUrl,\n resolveNavigateArg,\n finalizeNavigationSpanFromHydratedRouter,\n updateNavigationSpanUrlFromLocation,\n} from './utils';\nimport {\n SENTRY_SEGMENT_NAME_SOURCE,\n CODE_FUNCTION_NAME,\n SENTRY_OP,\n URL_FULL,\n URL_TEMPLATE,\n} from '@sentry/conventions/attributes';\nimport { FUNCTION, MIDDLEWARE } from '@sentry/conventions/op';\n\nconst WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;\n\n// Tracks active numeric navigation span to prevent duplicate spans when popstate fires\nlet currentNumericNavigationSpan: Span | undefined;\n\n// Per-request middleware counters, keyed by Request\nconst middlewareCountersMap = new WeakMap<object, Record<string, number>>();\n\nconst SENTRY_CLIENT_INSTRUMENTATION_FLAG = '__sentryReactRouterClientInstrumentationUsed';\n// Intentionally never reset - once set, instrumentation API handles all navigations for the session.\nconst SENTRY_NAVIGATE_HOOK_INVOKED_FLAG = '__sentryReactRouterNavigateHookInvoked';\nconst SENTRY_POPSTATE_LISTENER_ADDED_FLAG = '__sentryReactRouterPopstateListenerAdded';\n\ntype GlobalObjWithFlags = typeof GLOBAL_OBJ & {\n [SENTRY_CLIENT_INSTRUMENTATION_FLAG]?: boolean;\n [SENTRY_NAVIGATE_HOOK_INVOKED_FLAG]?: boolean;\n [SENTRY_POPSTATE_LISTENER_ADDED_FLAG]?: boolean;\n};\n\nconst GLOBAL_WITH_FLAGS = GLOBAL_OBJ as GlobalObjWithFlags;\n\n/**\n * Options for creating Sentry client instrumentation.\n */\nexport interface CreateSentryClientInstrumentationOptions {\n /**\n * Whether to capture errors from loaders/actions automatically.\n * Set to `false` to avoid duplicates if using custom error handlers.\n * @default true\n */\n captureErrors?: boolean;\n}\n\n/**\n * Creates a Sentry client instrumentation for React Router's instrumentation API.\n */\nexport function createSentryClientInstrumentation(\n options: CreateSentryClientInstrumentationOptions = {},\n): ClientInstrumentation {\n const { captureErrors = true } = options;\n\n DEBUG_BUILD && debug.log('React Router client instrumentation API created.');\n\n return {\n router(router) {\n // Set the flag when React Router actually invokes our instrumentation.\n // This ensures the flag is only set in Library Mode (where hooks run),\n // not in Framework Mode (where hooks are never called).\n // See: https://github.com/remix-run/react-router/discussions/13749\n GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG] = true;\n DEBUG_BUILD && debug.log('React Router client instrumentation API router hook registered.');\n\n // Add popstate listener for browser back/forward navigation (persists for session, one listener only)\n if (!GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] && WINDOW.addEventListener) {\n GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] = true;\n\n WINDOW.addEventListener('popstate', () => {\n const client = getClient();\n if (!client) {\n currentNumericNavigationSpan = undefined;\n return;\n }\n\n const pathname = WINDOW.location?.pathname || '/';\n\n // If there's an active numeric navigation span, update it instead of creating a duplicate\n if (currentNumericNavigationSpan) {\n if (currentNumericNavigationSpan.isRecording()) {\n updateNavigationSpanUrlFromLocation(currentNumericNavigationSpan);\n }\n currentNumericNavigationSpan = undefined;\n return;\n }\n\n // Only create a new span for actual browser back/forward button clicks\n startBrowserTracingNavigationSpan(\n client,\n {\n // With span streaming, span names have to be low cardinality, so we can't fall back to\n // the URL. The route hooks parameterize the span once they resolve.\n name: hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : pathname,\n attributes: {\n [SENTRY_SEGMENT_NAME_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': 'browser.popstate',\n },\n },\n { url: getAbsoluteUrl(pathname) },\n );\n });\n\n DEBUG_BUILD && debug.log('React Router popstate listener registered for browser back/forward navigation.');\n }\n\n router.instrument({\n async navigate(callNavigate, info) {\n // navigate(0) triggers a page reload - skip span creation, but still capture errors\n // (navigation can be rejected before reload, e.g., by a navigation guard)\n if (info.to === 0) {\n const result = await callNavigate();\n if (result.status === 'error' && result.error instanceof Error) {\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: filterCollectedUrl(info.currentUrl),\n });\n }\n return;\n }\n\n GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG] = true;\n\n // Handle numeric navigations (navigate(-1), navigate(1), etc.)\n if (typeof info.to === 'number') {\n const client = getClient();\n let navigationSpan;\n\n if (client) {\n const navigationType = info.to < 0 ? 'router.back' : 'router.forward';\n const currentPathname = WINDOW.location?.pathname || info.currentUrl;\n\n navigationSpan = startBrowserTracingNavigationSpan(\n client,\n {\n // With span streaming, span names have to be low cardinality, so we can't fall back\n // to the URL. The route is resolved once the navigation settles.\n name: hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : currentPathname,\n attributes: {\n [SENTRY_SEGMENT_NAME_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': navigationType,\n },\n },\n { url: getAbsoluteUrl(currentPathname) },\n );\n\n // Store ref so popstate listener can update it instead of creating a duplicate\n currentNumericNavigationSpan = navigationSpan;\n }\n\n try {\n const result = await callNavigate();\n\n if (navigationSpan && WINDOW.location) {\n finalizeNavigationSpanFromHydratedRouter(navigationSpan);\n }\n\n if (result.status === 'error' && result.error instanceof Error) {\n if (navigationSpan) {\n navigationSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n }\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: WINDOW.location?.pathname || filterCollectedUrl(info.currentUrl),\n });\n }\n } finally {\n currentNumericNavigationSpan = undefined;\n }\n return;\n }\n\n // Handle string/object navigations (e.g., navigate('/about') or navigate({ pathname: '/about' }))\n const client = getClient();\n const toPath = resolveNavigateArg(info.to);\n let navigationSpan;\n\n if (client) {\n navigationSpan = startBrowserTracingNavigationSpan(\n client,\n {\n // With span streaming, span names have to be low cardinality, so we can't fall back to\n // the URL. The route hooks parameterize the span once they resolve.\n name: hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : toPath,\n attributes: {\n [SENTRY_SEGMENT_NAME_SOURCE]: 'url',\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',\n 'navigation.type': 'router.navigate',\n },\n },\n { url: resolveNavigateAbsoluteUrl(info.to, info.currentUrl) },\n );\n }\n\n const result = await callNavigate();\n if (result.status === 'error' && result.error instanceof Error) {\n if (navigationSpan) {\n navigationSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n }\n captureInstrumentationError(result, captureErrors, 'react_router.navigate', {\n [URL_FULL]: filterCollectedUrl(toPath),\n });\n }\n return;\n },\n\n async fetch(callFetch, info) {\n await startSpan(\n {\n name: `Fetcher ${info.fetcherKey}`,\n attributes: {\n [SENTRY_OP]: FUNCTION,\n [CODE_FUNCTION_NAME]: 'fetcher',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callFetch();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.fetcher', {\n [URL_FULL]: filterCollectedUrl(info.href),\n });\n }\n },\n );\n },\n });\n },\n\n route(route) {\n const routeId = route.id;\n\n route.instrument({\n async loader(callLoader, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = normalizeRoutePath(getPattern(info));\n const routePattern = pattern || urlPath;\n // Parameterize the active navigation root span. (Route hooks don't fire on initial\n // pageload, so this only affects navigations.)\n updateRootSpanRoute(routePattern, !!pattern);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SENTRY_OP]: FUNCTION,\n [CODE_FUNCTION_NAME]: 'clientLoader',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLoader();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_loader', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async action(callAction, info) {\n const urlPath = getPathFromRequest(info.request);\n const pattern = normalizeRoutePath(getPattern(info));\n const routePattern = pattern || urlPath;\n updateRootSpanRoute(routePattern, !!pattern);\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SENTRY_OP]: FUNCTION,\n [CODE_FUNCTION_NAME]: 'clientAction',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callAction();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_action', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async middleware(callMiddleware, info) {\n const urlPath = getPathFromRequest(info.request);\n const routePattern = normalizeRoutePath(getPattern(info)) || urlPath;\n\n let counters = middlewareCountersMap.get(info.request);\n if (!counters) {\n counters = {};\n middlewareCountersMap.set(info.request, counters);\n }\n\n const middlewareIndex = counters[routeId] ?? 0;\n counters[routeId] = middlewareIndex + 1;\n\n await startSpan(\n {\n name: `middleware ${routeId}`,\n attributes: {\n [SENTRY_OP]: MIDDLEWARE,\n [CODE_FUNCTION_NAME]: 'clientMiddleware',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n 'react_router.route.id': routeId,\n 'http.route': routePattern,\n 'react_router.middleware.index': middlewareIndex,\n },\n },\n async span => {\n const result = await callMiddleware();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_middleware', {\n [URL_FULL]: urlPath,\n });\n }\n },\n );\n },\n\n async lazy(callLazy) {\n await startSpan(\n {\n name: 'Lazy Route Load',\n attributes: {\n [SENTRY_OP]: FUNCTION,\n [CODE_FUNCTION_NAME]: 'lazy',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\n },\n },\n async span => {\n const result = await callLazy();\n if (result.status === 'error' && result.error instanceof Error) {\n span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });\n captureInstrumentationError(result, captureErrors, 'react_router.client_lazy', {});\n }\n },\n );\n },\n });\n },\n };\n}\n\n/**\n * Updates the active navigation/pageload root span name with the parameterized route, so the\n * transaction reflects the parameterized route pattern (e.g. `/users/:id`).\n */\nfunction updateRootSpanRoute(routeName: string, hasPattern: boolean): void {\n if (!hasPattern) {\n return;\n }\n\n const activeSpan = getActiveSpan();\n const rootSpan = activeSpan && getRootSpan(activeSpan);\n if (!rootSpan) {\n return;\n }\n\n const { attributes } = spanToJSON(rootSpan);\n const op = attributes[SENTRY_OP];\n if (op !== 'navigation' && op !== 'pageload') {\n return;\n }\n\n updateSpanName(rootSpan, routeName);\n rootSpan.setAttributes({ [SENTRY_SEGMENT_NAME_SOURCE]: 'route', [URL_TEMPLATE]: routeName });\n}\n\n/**\n * Check if React Router's instrumentation API is being used on the client.\n */\nexport function isClientInstrumentationApiUsed(): boolean {\n return !!GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG];\n}\n\n/**\n * Check if React Router's instrumentation API's navigate hook was invoked.\n */\nexport function isNavigateHookInvoked(): boolean {\n return !!GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG];\n}\n"],"names":["result","client","navigationSpan"],"mappings":";;;;;;;;;AAqCA,MAAM,MAAA,GAAS,UAAA;AAGf,IAAI,4BAAA;AAGJ,MAAM,qBAAA,uBAA4B,OAAA,EAAwC;AAE1E,MAAM,kCAAA,GAAqC,8CAAA;AAE3C,MAAM,iCAAA,GAAoC,wCAAA;AAC1C,MAAM,mCAAA,GAAsC,0CAAA;AAQ5C,MAAM,iBAAA,GAAoB,UAAA;AAiBnB,SAAS,iCAAA,CACd,OAAA,GAAoD,EAAC,EAC9B;AACvB,EAAA,MAAM,EAAE,aAAA,GAAgB,IAAA,EAAK,GAAI,OAAA;AAEjC,EAAA,WAAA,IAAe,KAAA,CAAM,IAAI,kDAAkD,CAAA;AAE3E,EAAA,OAAO;AAAA,IACL,OAAO,MAAA,EAAQ;AAKb,MAAA,iBAAA,CAAkB,kCAAkC,CAAA,GAAI,IAAA;AACxD,MAAA,WAAA,IAAe,KAAA,CAAM,IAAI,iEAAiE,CAAA;AAG1F,MAAA,IAAI,CAAC,iBAAA,CAAkB,mCAAmC,CAAA,IAAK,OAAO,gBAAA,EAAkB;AACtF,QAAA,iBAAA,CAAkB,mCAAmC,CAAA,GAAI,IAAA;AAEzD,QAAA,MAAA,CAAO,gBAAA,CAAiB,YAAY,MAAM;AACxC,UAAA,MAAM,SAAS,SAAA,EAAU;AACzB,UAAA,IAAI,CAAC,MAAA,EAAQ;AACX,YAAA,4BAAA,GAA+B,MAAA;AAC/B,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,QAAA,GAAW,MAAA,CAAO,QAAA,EAAU,QAAA,IAAY,GAAA;AAG9C,UAAA,IAAI,4BAAA,EAA8B;AAChC,YAAA,IAAI,4BAAA,CAA6B,aAAY,EAAG;AAC9C,cAAA,mCAAA,CAAoC,4BAA4B,CAAA;AAAA,YAClE;AACA,YAAA,4BAAA,GAA+B,MAAA;AAC/B,YAAA;AAAA,UACF;AAGA,UAAA,iCAAA;AAAA,YACE,MAAA;AAAA,YACA;AAAA;AAAA;AAAA,cAGE,IAAA,EAAM,uBAAA,CAAwB,MAAM,CAAA,GAAI,6BAAA,GAAgC,QAAA;AAAA,cACxE,UAAA,EAAY;AAAA,gBACV,CAAC,0BAA0B,GAAG,KAAA;AAAA,gBAC9B,CAAC,4BAA4B,GAAG,YAAA;AAAA,gBAChC,CAAC,gCAAgC,GAAG,kDAAA;AAAA,gBACpC,iBAAA,EAAmB;AAAA;AACrB,aACF;AAAA,YACA,EAAE,GAAA,EAAK,cAAA,CAAe,QAAQ,CAAA;AAAE,WAClC;AAAA,QACF,CAAC,CAAA;AAED,QAAA,WAAA,IAAe,KAAA,CAAM,IAAI,gFAAgF,CAAA;AAAA,MAC3G;AAEA,MAAA,MAAA,CAAO,UAAA,CAAW;AAAA,QAChB,MAAM,QAAA,CAAS,YAAA,EAAc,IAAA,EAAM;AAGjC,UAAA,IAAI,IAAA,CAAK,OAAO,CAAA,EAAG;AACjB,YAAA,MAAMA,OAAAA,GAAS,MAAM,YAAA,EAAa;AAClC,YAAA,IAAIA,OAAAA,CAAO,MAAA,KAAW,OAAA,IAAWA,OAAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,cAAA,2BAAA,CAA4BA,OAAAA,EAAQ,eAAe,uBAAA,EAAyB;AAAA,gBAC1E,CAAC,QAAQ,GAAG,kBAAA,CAAmB,KAAK,UAAU;AAAA,eAC/C,CAAA;AAAA,YACH;AACA,YAAA;AAAA,UACF;AAEA,UAAA,iBAAA,CAAkB,iCAAiC,CAAA,GAAI,IAAA;AAGvD,UAAA,IAAI,OAAO,IAAA,CAAK,EAAA,KAAO,QAAA,EAAU;AAC/B,YAAA,MAAMC,UAAS,SAAA,EAAU;AACzB,YAAA,IAAIC,eAAAA;AAEJ,YAAA,IAAID,OAAAA,EAAQ;AACV,cAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,EAAA,GAAK,CAAA,GAAI,aAAA,GAAgB,gBAAA;AACrD,cAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,QAAA,EAAU,QAAA,IAAY,IAAA,CAAK,UAAA;AAE1D,cAAAC,eAAAA,GAAiB,iCAAA;AAAA,gBACfD,OAAAA;AAAA,gBACA;AAAA;AAAA;AAAA,kBAGE,IAAA,EAAM,uBAAA,CAAwBA,OAAM,CAAA,GAAI,6BAAA,GAAgC,eAAA;AAAA,kBACxE,UAAA,EAAY;AAAA,oBACV,CAAC,0BAA0B,GAAG,KAAA;AAAA,oBAC9B,CAAC,4BAA4B,GAAG,YAAA;AAAA,oBAChC,CAAC,gCAAgC,GAAG,kDAAA;AAAA,oBACpC,iBAAA,EAAmB;AAAA;AACrB,iBACF;AAAA,gBACA,EAAE,GAAA,EAAK,cAAA,CAAe,eAAe,CAAA;AAAE,eACzC;AAGA,cAAA,4BAAA,GAA+BC,eAAAA;AAAA,YACjC;AAEA,YAAA,IAAI;AACF,cAAA,MAAMF,OAAAA,GAAS,MAAM,YAAA,EAAa;AAElC,cAAA,IAAIE,eAAAA,IAAkB,OAAO,QAAA,EAAU;AACrC,gBAAA,wCAAA,CAAyCA,eAAc,CAAA;AAAA,cACzD;AAEA,cAAA,IAAIF,OAAAA,CAAO,MAAA,KAAW,OAAA,IAAWA,OAAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAIE,eAAAA,EAAgB;AAClB,kBAAAA,gBAAe,SAAA,CAAU,EAAE,MAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AAAA,gBACjF;AACA,gBAAA,2BAAA,CAA4BF,OAAAA,EAAQ,eAAe,uBAAA,EAAyB;AAAA,kBAC1E,CAAC,QAAQ,GAAG,MAAA,CAAO,UAAU,QAAA,IAAY,kBAAA,CAAmB,KAAK,UAAU;AAAA,iBAC5E,CAAA;AAAA,cACH;AAAA,YACF,CAAA,SAAE;AACA,cAAA,4BAAA,GAA+B,MAAA;AAAA,YACjC;AACA,YAAA;AAAA,UACF;AAGA,UAAA,MAAM,SAAS,SAAA,EAAU;AACzB,UAAA,MAAM,MAAA,GAAS,kBAAA,CAAmB,IAAA,CAAK,EAAE,CAAA;AACzC,UAAA,IAAI,cAAA;AAEJ,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,cAAA,GAAiB,iCAAA;AAAA,cACf,MAAA;AAAA,cACA;AAAA;AAAA;AAAA,gBAGE,IAAA,EAAM,uBAAA,CAAwB,MAAM,CAAA,GAAI,6BAAA,GAAgC,MAAA;AAAA,gBACxE,UAAA,EAAY;AAAA,kBACV,CAAC,0BAA0B,GAAG,KAAA;AAAA,kBAC9B,CAAC,4BAA4B,GAAG,YAAA;AAAA,kBAChC,CAAC,gCAAgC,GAAG,kDAAA;AAAA,kBACpC,iBAAA,EAAmB;AAAA;AACrB,eACF;AAAA,cACA,EAAE,GAAA,EAAK,0BAAA,CAA2B,KAAK,EAAA,EAAI,IAAA,CAAK,UAAU,CAAA;AAAE,aAC9D;AAAA,UACF;AAEA,UAAA,MAAM,MAAA,GAAS,MAAM,YAAA,EAAa;AAClC,UAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,YAAA,IAAI,cAAA,EAAgB;AAClB,cAAA,cAAA,CAAe,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AAAA,YACjF;AACA,YAAA,2BAAA,CAA4B,MAAA,EAAQ,eAAe,uBAAA,EAAyB;AAAA,cAC1E,CAAC,QAAQ,GAAG,kBAAA,CAAmB,MAAM;AAAA,aACtC,CAAA;AAAA,UACH;AACA,UAAA;AAAA,QACF,CAAA;AAAA,QAEA,MAAM,KAAA,CAAM,SAAA,EAAW,IAAA,EAAM;AAC3B,UAAA,MAAM,SAAA;AAAA,YACJ;AAAA,cACE,IAAA,EAAM,CAAA,QAAA,EAAW,IAAA,CAAK,UAAU,CAAA,CAAA;AAAA,cAChC,UAAA,EAAY;AAAA,gBACV,CAAC,SAAS,GAAG,QAAA;AAAA,gBACb,CAAC,kBAAkB,GAAG,SAAA;AAAA,gBACtB,CAAC,gCAAgC,GAAG;AAAA;AACtC,aACF;AAAA,YACA,OAAM,IAAA,KAAQ;AACZ,cAAA,MAAM,MAAA,GAAS,MAAM,SAAA,EAAU;AAC/B,cAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AACrE,gBAAA,2BAAA,CAA4B,MAAA,EAAQ,eAAe,sBAAA,EAAwB;AAAA,kBACzE,CAAC,QAAQ,GAAG,kBAAA,CAAmB,KAAK,IAAI;AAAA,iBACzC,CAAA;AAAA,cACH;AAAA,YACF;AAAA,WACF;AAAA,QACF;AAAA,OACD,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,MAAM,KAAA,EAAO;AACX,MAAA,MAAM,UAAU,KAAA,CAAM,EAAA;AAEtB,MAAA,KAAA,CAAM,UAAA,CAAW;AAAA,QACf,MAAM,MAAA,CAAO,UAAA,EAAY,IAAA,EAAM;AAC7B,UAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,OAAO,CAAA;AAC/C,UAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,UAAA,CAAW,IAAI,CAAC,CAAA;AACnD,UAAA,MAAM,eAAe,OAAA,IAAW,OAAA;AAGhC,UAAA,mBAAA,CAAoB,YAAA,EAAc,CAAC,CAAC,OAAO,CAAA;AAE3C,UAAA,MAAM,SAAA;AAAA,YACJ;AAAA,cACE,IAAA,EAAM,YAAA;AAAA,cACN,UAAA,EAAY;AAAA,gBACV,CAAC,SAAS,GAAG,QAAA;AAAA,gBACb,CAAC,kBAAkB,GAAG,cAAA;AAAA,gBACtB,CAAC,gCAAgC,GAAG;AAAA;AACtC,aACF;AAAA,YACA,OAAM,IAAA,KAAQ;AACZ,cAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,cAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AACrE,gBAAA,2BAAA,CAA4B,MAAA,EAAQ,eAAe,4BAAA,EAA8B;AAAA,kBAC/E,CAAC,QAAQ,GAAG;AAAA,iBACb,CAAA;AAAA,cACH;AAAA,YACF;AAAA,WACF;AAAA,QACF,CAAA;AAAA,QAEA,MAAM,MAAA,CAAO,UAAA,EAAY,IAAA,EAAM;AAC7B,UAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,OAAO,CAAA;AAC/C,UAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,UAAA,CAAW,IAAI,CAAC,CAAA;AACnD,UAAA,MAAM,eAAe,OAAA,IAAW,OAAA;AAChC,UAAA,mBAAA,CAAoB,YAAA,EAAc,CAAC,CAAC,OAAO,CAAA;AAE3C,UAAA,MAAM,SAAA;AAAA,YACJ;AAAA,cACE,IAAA,EAAM,YAAA;AAAA,cACN,UAAA,EAAY;AAAA,gBACV,CAAC,SAAS,GAAG,QAAA;AAAA,gBACb,CAAC,kBAAkB,GAAG,cAAA;AAAA,gBACtB,CAAC,gCAAgC,GAAG;AAAA;AACtC,aACF;AAAA,YACA,OAAM,IAAA,KAAQ;AACZ,cAAA,MAAM,MAAA,GAAS,MAAM,UAAA,EAAW;AAChC,cAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AACrE,gBAAA,2BAAA,CAA4B,MAAA,EAAQ,eAAe,4BAAA,EAA8B;AAAA,kBAC/E,CAAC,QAAQ,GAAG;AAAA,iBACb,CAAA;AAAA,cACH;AAAA,YACF;AAAA,WACF;AAAA,QACF,CAAA;AAAA,QAEA,MAAM,UAAA,CAAW,cAAA,EAAgB,IAAA,EAAM;AACrC,UAAA,MAAM,OAAA,GAAU,kBAAA,CAAmB,IAAA,CAAK,OAAO,CAAA;AAC/C,UAAA,MAAM,YAAA,GAAe,kBAAA,CAAmB,UAAA,CAAW,IAAI,CAAC,CAAA,IAAK,OAAA;AAE7D,UAAA,IAAI,QAAA,GAAW,qBAAA,CAAsB,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA;AACrD,UAAA,IAAI,CAAC,QAAA,EAAU;AACb,YAAA,QAAA,GAAW,EAAC;AACZ,YAAA,qBAAA,CAAsB,GAAA,CAAI,IAAA,CAAK,OAAA,EAAS,QAAQ,CAAA;AAAA,UAClD;AAEA,UAAA,MAAM,eAAA,GAAkB,QAAA,CAAS,OAAO,CAAA,IAAK,CAAA;AAC7C,UAAA,QAAA,CAAS,OAAO,IAAI,eAAA,GAAkB,CAAA;AAEtC,UAAA,MAAM,SAAA;AAAA,YACJ;AAAA,cACE,IAAA,EAAM,cAAc,OAAO,CAAA,CAAA;AAAA,cAC3B,UAAA,EAAY;AAAA,gBACV,CAAC,SAAS,GAAG,UAAA;AAAA,gBACb,CAAC,kBAAkB,GAAG,kBAAA;AAAA,gBACtB,CAAC,gCAAgC,GAAG,gDAAA;AAAA,gBACpC,uBAAA,EAAyB,OAAA;AAAA,gBACzB,YAAA,EAAc,YAAA;AAAA,gBACd,+BAAA,EAAiC;AAAA;AACnC,aACF;AAAA,YACA,OAAM,IAAA,KAAQ;AACZ,cAAA,MAAM,MAAA,GAAS,MAAM,cAAA,EAAe;AACpC,cAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AACrE,gBAAA,2BAAA,CAA4B,MAAA,EAAQ,eAAe,gCAAA,EAAkC;AAAA,kBACnF,CAAC,QAAQ,GAAG;AAAA,iBACb,CAAA;AAAA,cACH;AAAA,YACF;AAAA,WACF;AAAA,QACF,CAAA;AAAA,QAEA,MAAM,KAAK,QAAA,EAAU;AACnB,UAAA,MAAM,SAAA;AAAA,YACJ;AAAA,cACE,IAAA,EAAM,iBAAA;AAAA,cACN,UAAA,EAAY;AAAA,gBACV,CAAC,SAAS,GAAG,QAAA;AAAA,gBACb,CAAC,kBAAkB,GAAG,MAAA;AAAA,gBACtB,CAAC,gCAAgC,GAAG;AAAA;AACtC,aACF;AAAA,YACA,OAAM,IAAA,KAAQ;AACZ,cAAA,MAAM,MAAA,GAAS,MAAM,QAAA,EAAS;AAC9B,cAAA,IAAI,MAAA,CAAO,MAAA,KAAW,OAAA,IAAW,MAAA,CAAO,iBAAiB,KAAA,EAAO;AAC9D,gBAAA,IAAA,CAAK,UAAU,EAAE,IAAA,EAAM,iBAAA,EAAmB,OAAA,EAAS,kBAAkB,CAAA;AACrE,gBAAA,2BAAA,CAA4B,MAAA,EAAQ,aAAA,EAAe,0BAAA,EAA4B,EAAE,CAAA;AAAA,cACnF;AAAA,YACF;AAAA,WACF;AAAA,QACF;AAAA,OACD,CAAA;AAAA,IACH;AAAA,GACF;AACF;AAMA,SAAS,mBAAA,CAAoB,WAAmB,UAAA,EAA2B;AACzE,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,aAAa,aAAA,EAAc;AACjC,EAAA,MAAM,QAAA,GAAW,UAAA,IAAc,WAAA,CAAY,UAAU,CAAA;AACrD,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,UAAA,EAAW,GAAI,UAAA,CAAW,QAAQ,CAAA;AAC1C,EAAA,MAAM,EAAA,GAAK,WAAW,SAAS,CAAA;AAC/B,EAAA,IAAI,EAAA,KAAO,YAAA,IAAgB,EAAA,KAAO,UAAA,EAAY;AAC5C,IAAA;AAAA,EACF;AAEA,EAAA,cAAA,CAAe,UAAU,SAAS,CAAA;AAClC,EAAA,QAAA,CAAS,aAAA,CAAc,EAAE,CAAC,0BAA0B,GAAG,SAAS,CAAC,YAAY,GAAG,SAAA,EAAW,CAAA;AAC7F;AAKO,SAAS,8BAAA,GAA0C;AACxD,EAAA,OAAO,CAAC,CAAC,iBAAA,CAAkB,kCAAkC,CAAA;AAC/D;AAKO,SAAS,qBAAA,GAAiC;AAC/C,EAAA,OAAO,CAAC,CAAC,iBAAA,CAAkB,iCAAiC,CAAA;AAC9D;;;;"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getAbsoluteUrl, startBrowserTracingNavigationSpan } from '@sentry/browser';
|
|
2
|
-
import { GLOBAL_OBJ, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
|
|
2
|
+
import { GLOBAL_OBJ, spanToJSON, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, isThenable, debug, getActiveSpan, getRootSpan, getClient, NAVIGATION_SPAN_NAME_FALLBACK, SEMANTIC_ATTRIBUTE_SENTRY_OP, hasSpanStreamingEnabled } from '@sentry/core';
|
|
3
3
|
import { DEBUG_BUILD } from '../common/debug-build.js';
|
|
4
4
|
import { isClientInstrumentationApiUsed } from './createClientInstrumentation.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { normalizePathname, updateSpanWithParameterizedRoute, finalizeNavigationSpanFromRouterState, resolveNavigateAbsoluteUrl, resolveNavigateArg } from './utils.js';
|
|
6
|
+
import { URL_PATH, SENTRY_OP, SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes';
|
|
7
7
|
|
|
8
8
|
const GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ;
|
|
9
9
|
const WINDOW = GLOBAL_OBJ;
|
|
@@ -14,16 +14,11 @@ function instrumentHydratedRouter() {
|
|
|
14
14
|
if (router) {
|
|
15
15
|
const pageloadSpan = getActiveRootSpan();
|
|
16
16
|
if (pageloadSpan) {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
pageloadSpan.
|
|
22
|
-
pageloadSpan.setAttributes({
|
|
23
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
|
|
24
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.pageload.react_router",
|
|
25
|
-
[URL_TEMPLATE]: parameterizePageloadRoute
|
|
26
|
-
});
|
|
17
|
+
const pageloadPath = spanToJSON(pageloadSpan).attributes[URL_PATH];
|
|
18
|
+
if (typeof pageloadPath === "string" && // this event is for the currently active pageload
|
|
19
|
+
normalizePathname(router.state.location.pathname) === normalizePathname(pageloadPath)) {
|
|
20
|
+
pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, "auto.pageload.react_router");
|
|
21
|
+
updateSpanWithParameterizedRoute(pageloadSpan, router.state);
|
|
27
22
|
}
|
|
28
23
|
}
|
|
29
24
|
if (typeof router.navigate === "function") {
|
|
@@ -70,21 +65,16 @@ function instrumentHydratedRouter() {
|
|
|
70
65
|
}
|
|
71
66
|
const rootSpanJson = spanToJSON(rootSpan);
|
|
72
67
|
const rootSpanAttributes = rootSpanJson.attributes;
|
|
73
|
-
if (rootSpanAttributes[SENTRY_OP] === "navigation" && isClientInstrumentationApiUsed() && rootSpanAttributes[
|
|
68
|
+
if (rootSpanAttributes[SENTRY_OP] === "navigation" && isClientInstrumentationApiUsed() && rootSpanAttributes[SENTRY_SEGMENT_NAME_SOURCE] === "route") {
|
|
74
69
|
return;
|
|
75
70
|
}
|
|
76
71
|
const rootSpanName = rootSpanJson.name;
|
|
77
|
-
const parameterizedRoute = getParameterizedRoute(newState);
|
|
78
72
|
const spanPathname = rootSpanAttributes[URL_PATH];
|
|
79
73
|
const destinationPathname = normalizePathname(newState.location.pathname);
|
|
80
74
|
if (rootSpanName && newState.navigation.state === "idle" && // navigation has completed
|
|
81
75
|
// this event is for the currently active root span
|
|
82
76
|
(destinationPathname === normalizePathname(rootSpanName) || spanPathname && destinationPathname === normalizePathname(spanPathname))) {
|
|
83
|
-
rootSpan
|
|
84
|
-
rootSpan.setAttributes({
|
|
85
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
|
|
86
|
-
[URL_TEMPLATE]: parameterizedRoute
|
|
87
|
-
});
|
|
77
|
+
updateSpanWithParameterizedRoute(rootSpan, newState);
|
|
88
78
|
}
|
|
89
79
|
});
|
|
90
80
|
return true;
|
|
@@ -112,9 +102,11 @@ function maybeCreateNavigationTransaction(name, url, source) {
|
|
|
112
102
|
return startBrowserTracingNavigationSpan(
|
|
113
103
|
client,
|
|
114
104
|
{
|
|
115
|
-
|
|
105
|
+
// With span streaming, span names have to be low cardinality, so we can't fall back to the URL.
|
|
106
|
+
// The route is resolved once the router settles, which updates the span name then.
|
|
107
|
+
name: !hasSpanStreamingEnabled(client) ? name : NAVIGATION_SPAN_NAME_FALLBACK,
|
|
116
108
|
attributes: {
|
|
117
|
-
[
|
|
109
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: source,
|
|
118
110
|
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation",
|
|
119
111
|
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.react_router",
|
|
120
112
|
...{}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { getAbsoluteUrl, startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n isThenable,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n spanToJSON,\n} from '@sentry/core';\nimport type { DataRouter } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { isClientInstrumentationApiUsed } from './createClientInstrumentation';\nimport {\n finalizeNavigationSpanFromRouterState,\n getParameterizedRoute,\n normalizePathname,\n resolveNavigateAbsoluteUrl,\n resolveNavigateArg,\n} from './utils';\nimport { SENTRY_OP, URL_PATH, URL_TEMPLATE } from '@sentry/conventions/attributes';\n\nconst GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __reactRouterDataRouter?: DataRouter;\n};\n\nconst WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;\n\nconst MAX_RETRIES = 40; // 2 seconds at 50ms interval\n\n/**\n * Instruments the React Router Data Router for pageloads and navigation.\n *\n * This function waits for the router to be available after hydration, then:\n * 1. Updates the pageload transaction with parameterized route info\n * 2. Patches router.navigate() to create navigation transactions\n * 3. Subscribes to router state changes to update navigation transactions with parameterized routes\n */\nexport function instrumentHydratedRouter(): void {\n function trySubscribe(): boolean {\n const router = GLOBAL_OBJ_WITH_DATA_ROUTER.__reactRouterDataRouter;\n\n if (router) {\n // The first time we hit the router, we try to update the pageload transaction\n const pageloadSpan = getActiveRootSpan();\n\n if (pageloadSpan) {\n const pageloadName = spanToJSON(pageloadSpan).name;\n const parameterizePageloadRoute = getParameterizedRoute(router.state);\n if (\n pageloadName &&\n // this event is for the currently active pageload\n normalizePathname(router.state.location.pathname) === normalizePathname(pageloadName)\n ) {\n pageloadSpan.updateName(parameterizePageloadRoute);\n pageloadSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react_router',\n [URL_TEMPLATE]: parameterizePageloadRoute,\n });\n }\n }\n\n // Patching navigate for creating accurate navigation transactions\n if (typeof router.navigate === 'function') {\n const originalNav = router.navigate.bind(router);\n router.navigate = function sentryPatchedNavigate(...args) {\n // Skip if instrumentation API is enabled (it handles navigation spans itself)\n if (!isClientInstrumentationApiUsed()) {\n const target = args[0];\n\n if (typeof target === 'number') {\n // navigate(0) triggers a reload, not a route change — skip span creation\n if (target !== 0) {\n const currentPathname = WINDOW.location?.pathname || '/';\n const navigationSpan = maybeCreateNavigationTransaction(\n currentPathname,\n getAbsoluteUrl(currentPathname),\n 'url',\n );\n\n const result = originalNav(...args);\n\n // Numeric navigations (`navigate(-1)`/`navigate(1)`) don't carry a destination\n // path, so we can only resolve the real URL/route once the router has settled.\n if (navigationSpan) {\n // Finalize from the (updated) router state after navigation completes, in both\n // the resolve and reject paths, so a rejected navigation still ends up with the\n // correct URL attributes instead of the placeholder start pathname.\n if (isThenable(result)) {\n result.then(\n () => finalizeNavigationSpanFromRouterState(navigationSpan, router.state),\n () => finalizeNavigationSpanFromRouterState(navigationSpan, router.state),\n );\n } else {\n finalizeNavigationSpanFromRouterState(navigationSpan, router.state);\n }\n }\n\n return result;\n }\n } else {\n maybeCreateNavigationTransaction(\n resolveNavigateArg(target) || '<unknown route>',\n resolveNavigateAbsoluteUrl(target),\n 'url',\n );\n }\n }\n return originalNav(...args);\n };\n }\n\n // Subscribe to router state changes to update navigation transactions (and any pageload\n // whose route info only became available after `trySubscribe`, e.g. lazy routes) with the\n // parameterized route.\n router.subscribe(newState => {\n const rootSpan = getActiveRootSpan();\n\n if (!rootSpan) {\n return;\n }\n\n const rootSpanJson = spanToJSON(rootSpan);\n const rootSpanAttributes = rootSpanJson.attributes;\n\n // When the instrumentation API is active, navigation roots are parameterized\n // by the native route hooks\n if (\n rootSpanAttributes[SENTRY_OP] === 'navigation' &&\n isClientInstrumentationApiUsed() &&\n rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] === 'route'\n ) {\n return;\n }\n\n const rootSpanName = rootSpanJson.name;\n const parameterizedRoute = getParameterizedRoute(newState);\n const spanPathname = rootSpanAttributes[URL_PATH] as string | undefined;\n const destinationPathname = normalizePathname(newState.location.pathname);\n\n if (\n rootSpanName &&\n newState.navigation.state === 'idle' && // navigation has completed\n // this event is for the currently active root span\n (destinationPathname === normalizePathname(rootSpanName) ||\n (spanPathname && destinationPathname === normalizePathname(spanPathname)))\n ) {\n rootSpan.updateName(parameterizedRoute);\n rootSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [URL_TEMPLATE]: parameterizedRoute,\n });\n }\n });\n return true;\n }\n return false;\n }\n\n // Wait until the router is available (since the SDK loads before hydration)\n if (!trySubscribe()) {\n let retryCount = 0;\n // Retry until the router is available or max retries reached\n const interval = setInterval(() => {\n if (trySubscribe() || retryCount >= MAX_RETRIES) {\n if (retryCount >= MAX_RETRIES) {\n DEBUG_BUILD && debug.warn('Unable to instrument React Router: router not found after hydration.');\n }\n clearInterval(interval);\n }\n retryCount++;\n }, 50);\n }\n}\n\nfunction maybeCreateNavigationTransaction(name: string, url: string, source: 'url' | 'route'): Span | undefined {\n const client = getClient();\n\n if (!client) {\n return undefined;\n }\n\n return startBrowserTracingNavigationSpan(\n client,\n {\n name,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router',\n ...(source === 'route' ? { [URL_TEMPLATE]: name } : {}),\n },\n },\n { url },\n );\n}\n\nfunction getActiveRootSpan(): Span | undefined {\n const activeSpan = getActiveSpan();\n if (!activeSpan) {\n return undefined;\n }\n\n const rootSpan = getRootSpan(activeSpan);\n\n const op = spanToJSON(rootSpan).attributes[SENTRY_OP];\n\n // Only use this root span if it is a pageload or navigation span\n return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;\n}\n"],"names":[],"mappings":";;;;;;;AA0BA,MAAM,2BAAA,GAA8B,UAAA;AAIpC,MAAM,MAAA,GAAS,UAAA;AAEf,MAAM,WAAA,GAAc,EAAA;AAUb,SAAS,wBAAA,GAAiC;AAC/C,EAAA,SAAS,YAAA,GAAwB;AAC/B,IAAA,MAAM,SAAS,2BAAA,CAA4B,uBAAA;AAE3C,IAAA,IAAI,MAAA,EAAQ;AAEV,MAAA,MAAM,eAAe,iBAAA,EAAkB;AAEvC,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,MAAM,YAAA,GAAe,UAAA,CAAW,YAAY,CAAA,CAAE,IAAA;AAC9C,QAAA,MAAM,yBAAA,GAA4B,qBAAA,CAAsB,MAAA,CAAO,KAAK,CAAA;AACpE,QAAA,IACE,YAAA;AAAA,QAEA,iBAAA,CAAkB,OAAO,KAAA,CAAM,QAAA,CAAS,QAAQ,CAAA,KAAM,iBAAA,CAAkB,YAAY,CAAA,EACpF;AACA,UAAA,YAAA,CAAa,WAAW,yBAAyB,CAAA;AACjD,UAAA,YAAA,CAAa,aAAA,CAAc;AAAA,YACzB,CAAC,gCAAgC,GAAG,OAAA;AAAA,YACpC,CAAC,gCAAgC,GAAG,4BAAA;AAAA,YACpC,CAAC,YAAY,GAAG;AAAA,WACjB,CAAA;AAAA,QACH;AAAA,MACF;AAGA,MAAA,IAAI,OAAO,MAAA,CAAO,QAAA,KAAa,UAAA,EAAY;AACzC,QAAA,MAAM,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,MAAM,CAAA;AAC/C,QAAA,MAAA,CAAO,QAAA,GAAW,SAAS,qBAAA,CAAA,GAAyB,IAAA,EAAM;AAExD,UAAA,IAAI,CAAC,gCAA+B,EAAG;AACrC,YAAA,MAAM,MAAA,GAAS,KAAK,CAAC,CAAA;AAErB,YAAA,IAAI,OAAO,WAAW,QAAA,EAAU;AAE9B,cAAA,IAAI,WAAW,CAAA,EAAG;AAChB,gBAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,QAAA,EAAU,QAAA,IAAY,GAAA;AACrD,gBAAA,MAAM,cAAA,GAAiB,gCAAA;AAAA,kBACrB,eAAA;AAAA,kBACA,eAAe,eAAe,CAAA;AAAA,kBAC9B;AAAA,iBACF;AAEA,gBAAA,MAAM,MAAA,GAAS,WAAA,CAAY,GAAG,IAAI,CAAA;AAIlC,gBAAA,IAAI,cAAA,EAAgB;AAIlB,kBAAA,IAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AACtB,oBAAA,MAAA,CAAO,IAAA;AAAA,sBACL,MAAM,qCAAA,CAAsC,cAAA,EAAgB,MAAA,CAAO,KAAK,CAAA;AAAA,sBACxE,MAAM,qCAAA,CAAsC,cAAA,EAAgB,MAAA,CAAO,KAAK;AAAA,qBAC1E;AAAA,kBACF,CAAA,MAAO;AACL,oBAAA,qCAAA,CAAsC,cAAA,EAAgB,OAAO,KAAK,CAAA;AAAA,kBACpE;AAAA,gBACF;AAEA,gBAAA,OAAO,MAAA;AAAA,cACT;AAAA,YACF,CAAA,MAAO;AACL,cAAA,gCAAA;AAAA,gBACE,kBAAA,CAAmB,MAAM,CAAA,IAAK,iBAAA;AAAA,gBAC9B,2BAA2B,MAAM,CAAA;AAAA,gBACjC;AAAA,eACF;AAAA,YACF;AAAA,UACF;AACA,UAAA,OAAO,WAAA,CAAY,GAAG,IAAI,CAAA;AAAA,QAC5B,CAAA;AAAA,MACF;AAKA,MAAA,MAAA,CAAO,UAAU,CAAA,QAAA,KAAY;AAC3B,QAAA,MAAM,WAAW,iBAAA,EAAkB;AAEnC,QAAA,IAAI,CAAC,QAAA,EAAU;AACb,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,YAAA,GAAe,WAAW,QAAQ,CAAA;AACxC,QAAA,MAAM,qBAAqB,YAAA,CAAa,UAAA;AAIxC,QAAA,IACE,kBAAA,CAAmB,SAAS,CAAA,KAAM,YAAA,IAClC,gCAA+B,IAC/B,kBAAA,CAAmB,gCAAgC,CAAA,KAAM,OAAA,EACzD;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,eAAe,YAAA,CAAa,IAAA;AAClC,QAAA,MAAM,kBAAA,GAAqB,sBAAsB,QAAQ,CAAA;AACzD,QAAA,MAAM,YAAA,GAAe,mBAAmB,QAAQ,CAAA;AAChD,QAAA,MAAM,mBAAA,GAAsB,iBAAA,CAAkB,QAAA,CAAS,QAAA,CAAS,QAAQ,CAAA;AAExE,QAAA,IACE,YAAA,IACA,QAAA,CAAS,UAAA,CAAW,KAAA,KAAU,MAAA;AAAA;AAAA,SAE7B,mBAAA,KAAwB,kBAAkB,YAAY,CAAA,IACpD,gBAAgB,mBAAA,KAAwB,iBAAA,CAAkB,YAAY,CAAA,CAAA,EACzE;AACA,UAAA,QAAA,CAAS,WAAW,kBAAkB,CAAA;AACtC,UAAA,QAAA,CAAS,aAAA,CAAc;AAAA,YACrB,CAAC,gCAAgC,GAAG,OAAA;AAAA,YACpC,CAAC,YAAY,GAAG;AAAA,WACjB,CAAA;AAAA,QACH;AAAA,MACF,CAAC,CAAA;AACD,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,IAAI,CAAC,cAAa,EAAG;AACnB,IAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,IAAA,MAAM,QAAA,GAAW,YAAY,MAAM;AACjC,MAAA,IAAI,YAAA,EAAa,IAAK,UAAA,IAAc,WAAA,EAAa;AAC/C,QAAA,IAAI,cAAc,WAAA,EAAa;AAC7B,UAAA,WAAA,IAAe,KAAA,CAAM,KAAK,sEAAsE,CAAA;AAAA,QAClG;AACA,QAAA,aAAA,CAAc,QAAQ,CAAA;AAAA,MACxB;AACA,MAAA,UAAA,EAAA;AAAA,IACF,GAAG,EAAE,CAAA;AAAA,EACP;AACF;AAEA,SAAS,gCAAA,CAAiC,IAAA,EAAc,GAAA,EAAa,MAAA,EAA2C;AAC9G,EAAA,MAAM,SAAS,SAAA,EAAU;AAEzB,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,iCAAA;AAAA,IACL,MAAA;AAAA,IACA;AAAA,MACE,IAAA;AAAA,MACA,UAAA,EAAY;AAAA,QACV,CAAC,gCAAgC,GAAG,MAAA;AAAA,QACpC,CAAC,4BAA4B,GAAG,YAAA;AAAA,QAChC,CAAC,gCAAgC,GAAG,8BAAA;AAAA,QACpC,GAAoD;AAAC;AACvD,KACF;AAAA,IACA,EAAE,GAAA;AAAI,GACR;AACF;AAEA,SAAS,iBAAA,GAAsC;AAC7C,EAAA,MAAM,aAAa,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,UAAA,EAAY;AACf,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAW,YAAY,UAAU,CAAA;AAEvC,EAAA,MAAM,EAAA,GAAK,UAAA,CAAW,QAAQ,CAAA,CAAE,WAAW,SAAS,CAAA;AAGpD,EAAA,OAAO,EAAA,KAAO,YAAA,IAAgB,EAAA,KAAO,UAAA,GAAa,QAAA,GAAW,MAAA;AAC/D;;;;"}
|
|
1
|
+
{"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { getAbsoluteUrl, startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n getActiveSpan,\n getClient,\n getRootSpan,\n GLOBAL_OBJ,\n hasSpanStreamingEnabled,\n isThenable,\n NAVIGATION_SPAN_NAME_FALLBACK,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n spanToJSON,\n} from '@sentry/core';\nimport type { DataRouter } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { isClientInstrumentationApiUsed } from './createClientInstrumentation';\nimport {\n finalizeNavigationSpanFromRouterState,\n normalizePathname,\n resolveNavigateAbsoluteUrl,\n resolveNavigateArg,\n updateSpanWithParameterizedRoute,\n} from './utils';\nimport { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_OP, URL_PATH, URL_TEMPLATE } from '@sentry/conventions/attributes';\n\nconst GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __reactRouterDataRouter?: DataRouter;\n};\n\nconst WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;\n\nconst MAX_RETRIES = 40; // 2 seconds at 50ms interval\n\n/**\n * Instruments the React Router Data Router for pageloads and navigation.\n *\n * This function waits for the router to be available after hydration, then:\n * 1. Updates the pageload transaction with parameterized route info\n * 2. Patches router.navigate() to create navigation transactions\n * 3. Subscribes to router state changes to update navigation transactions with parameterized routes\n */\nexport function instrumentHydratedRouter(): void {\n function trySubscribe(): boolean {\n const router = GLOBAL_OBJ_WITH_DATA_ROUTER.__reactRouterDataRouter;\n\n if (router) {\n // The first time we hit the router, we try to update the pageload transaction\n const pageloadSpan = getActiveRootSpan();\n\n if (pageloadSpan) {\n // Matched against `url.path` rather than the span name: with span streaming, the pageload\n // span is named `Pageload` until a route is resolved, so the name may not hold the pathname.\n const pageloadPath = spanToJSON(pageloadSpan).attributes[URL_PATH];\n if (\n typeof pageloadPath === 'string' &&\n // this event is for the currently active pageload\n normalizePathname(router.state.location.pathname) === normalizePathname(pageloadPath)\n ) {\n pageloadSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.pageload.react_router');\n updateSpanWithParameterizedRoute(pageloadSpan, router.state);\n }\n }\n\n // Patching navigate for creating accurate navigation transactions\n if (typeof router.navigate === 'function') {\n const originalNav = router.navigate.bind(router);\n router.navigate = function sentryPatchedNavigate(...args) {\n // Skip if instrumentation API is enabled (it handles navigation spans itself)\n if (!isClientInstrumentationApiUsed()) {\n const target = args[0];\n\n if (typeof target === 'number') {\n // navigate(0) triggers a reload, not a route change — skip span creation\n if (target !== 0) {\n const currentPathname = WINDOW.location?.pathname || '/';\n const navigationSpan = maybeCreateNavigationTransaction(\n currentPathname,\n getAbsoluteUrl(currentPathname),\n 'url',\n );\n\n const result = originalNav(...args);\n\n // Numeric navigations (`navigate(-1)`/`navigate(1)`) don't carry a destination\n // path, so we can only resolve the real URL/route once the router has settled.\n if (navigationSpan) {\n // Finalize from the (updated) router state after navigation completes, in both\n // the resolve and reject paths, so a rejected navigation still ends up with the\n // correct URL attributes instead of the placeholder start pathname.\n if (isThenable(result)) {\n result.then(\n () => finalizeNavigationSpanFromRouterState(navigationSpan, router.state),\n () => finalizeNavigationSpanFromRouterState(navigationSpan, router.state),\n );\n } else {\n finalizeNavigationSpanFromRouterState(navigationSpan, router.state);\n }\n }\n\n return result;\n }\n } else {\n maybeCreateNavigationTransaction(\n resolveNavigateArg(target) || '<unknown route>',\n resolveNavigateAbsoluteUrl(target),\n 'url',\n );\n }\n }\n return originalNav(...args);\n };\n }\n\n // Subscribe to router state changes to update navigation transactions (and any pageload\n // whose route info only became available after `trySubscribe`, e.g. lazy routes) with the\n // parameterized route.\n router.subscribe(newState => {\n const rootSpan = getActiveRootSpan();\n\n if (!rootSpan) {\n return;\n }\n\n const rootSpanJson = spanToJSON(rootSpan);\n const rootSpanAttributes = rootSpanJson.attributes;\n\n // When the instrumentation API is active, navigation roots are parameterized\n // by the native route hooks\n if (\n rootSpanAttributes[SENTRY_OP] === 'navigation' &&\n isClientInstrumentationApiUsed() &&\n rootSpanAttributes[SENTRY_SEGMENT_NAME_SOURCE] === 'route'\n ) {\n return;\n }\n\n const rootSpanName = rootSpanJson.name;\n const spanPathname = rootSpanAttributes[URL_PATH] as string | undefined;\n const destinationPathname = normalizePathname(newState.location.pathname);\n\n if (\n rootSpanName &&\n newState.navigation.state === 'idle' && // navigation has completed\n // this event is for the currently active root span\n (destinationPathname === normalizePathname(rootSpanName) ||\n (spanPathname && destinationPathname === normalizePathname(spanPathname)))\n ) {\n updateSpanWithParameterizedRoute(rootSpan, newState);\n }\n });\n return true;\n }\n return false;\n }\n\n // Wait until the router is available (since the SDK loads before hydration)\n if (!trySubscribe()) {\n let retryCount = 0;\n // Retry until the router is available or max retries reached\n const interval = setInterval(() => {\n if (trySubscribe() || retryCount >= MAX_RETRIES) {\n if (retryCount >= MAX_RETRIES) {\n DEBUG_BUILD && debug.warn('Unable to instrument React Router: router not found after hydration.');\n }\n clearInterval(interval);\n }\n retryCount++;\n }, 50);\n }\n}\n\nfunction maybeCreateNavigationTransaction(name: string, url: string, source: 'url' | 'route'): Span | undefined {\n const client = getClient();\n\n if (!client) {\n return undefined;\n }\n\n return startBrowserTracingNavigationSpan(\n client,\n {\n // With span streaming, span names have to be low cardinality, so we can't fall back to the URL.\n // The route is resolved once the router settles, which updates the span name then.\n name: source === 'route' || !hasSpanStreamingEnabled(client) ? name : NAVIGATION_SPAN_NAME_FALLBACK,\n attributes: {\n [SENTRY_SEGMENT_NAME_SOURCE]: source,\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router',\n ...(source === 'route' ? { [URL_TEMPLATE]: name } : {}),\n },\n },\n { url },\n );\n}\n\nfunction getActiveRootSpan(): Span | undefined {\n const activeSpan = getActiveSpan();\n if (!activeSpan) {\n return undefined;\n }\n\n const rootSpan = getRootSpan(activeSpan);\n\n const op = spanToJSON(rootSpan).attributes[SENTRY_OP];\n\n // Only use this root span if it is a pageload or navigation span\n return op === 'navigation' || op === 'pageload' ? rootSpan : undefined;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracingIntegration.js","sources":["../../../src/client/tracingIntegration.ts"],"sourcesContent":["import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport type { ClientInstrumentation } from '
|
|
1
|
+
{"version":3,"file":"tracingIntegration.js","sources":["../../../src/client/tracingIntegration.ts"],"sourcesContent":["import { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/browser';\nimport type { Integration } from '@sentry/core';\nimport type { ClientInstrumentation } from 'react-router';\nimport {\n createSentryClientInstrumentation,\n type CreateSentryClientInstrumentationOptions,\n} from './createClientInstrumentation';\nimport { instrumentHydratedRouter } from './hydratedRouter';\n\n/**\n * Options for the React Router tracing integration.\n */\nexport interface ReactRouterTracingIntegrationOptions {\n /**\n * Options for React Router's instrumentation API.\n */\n instrumentationOptions?: CreateSentryClientInstrumentationOptions;\n\n /** @deprecated `clientInstrumentation` is always built, so this flag is a no-op. Will be removed in a future major. */\n useInstrumentationAPI?: boolean;\n}\n\n/**\n * React Router tracing integration with support for the instrumentation API.\n */\nexport interface ReactRouterTracingIntegration extends Integration {\n /**\n * Client instrumentation to pass to `HydratedRouter`'s `instrumentations` prop.\n *\n * @deprecated Use the standalone `createSentryClientInstrumentation()` export instead and pass its\n * result to `HydratedRouter`'s `instrumentations` prop. This mirrors the server-side\n * `createSentryServerInstrumentation()` API. Will be removed in a future major.\n */\n readonly clientInstrumentation: ClientInstrumentation;\n}\n\n/**\n * Browser tracing integration for React Router (Framework) applications.\n * This integration will create navigation spans and enhance transaction names with parameterized routes.\n */\nexport function reactRouterTracingIntegration(\n options: ReactRouterTracingIntegrationOptions = {},\n): ReactRouterTracingIntegration {\n const browserTracingIntegrationInstance = originalBrowserTracingIntegration({\n // Navigation transactions are started within the hydrated router instrumentation\n instrumentNavigation: false,\n });\n\n // Built eagerly so it can be passed to HydratedRouter's `instrumentations` prop. This has no\n // side effects - the API is only marked \"used\" once React Router invokes the `router()` hook.\n const clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);\n\n return {\n ...browserTracingIntegrationInstance,\n name: 'ReactRouterTracingIntegration',\n afterAllSetup(client) {\n browserTracingIntegrationInstance.afterAllSetup(client);\n instrumentHydratedRouter();\n },\n // oxlint-disable-next-line typescript/no-deprecated -- exposes the deprecated getter for back-compat until it is removed\n get clientInstrumentation(): ClientInstrumentation {\n return clientInstrumentationInstance;\n },\n };\n}\n"],"names":["originalBrowserTracingIntegration"],"mappings":";;;;AAwCO,SAAS,6BAAA,CACd,OAAA,GAAgD,EAAC,EAClB;AAC/B,EAAA,MAAM,oCAAoCA,yBAAA,CAAkC;AAAA;AAAA,IAE1E,oBAAA,EAAsB;AAAA,GACvB,CAAA;AAID,EAAA,MAAM,6BAAA,GAAgC,iCAAA,CAAkC,OAAA,CAAQ,sBAAsB,CAAA;AAEtG,EAAA,OAAO;AAAA,IACL,GAAG,iCAAA;AAAA,IACH,IAAA,EAAM,+BAAA;AAAA,IACN,cAAc,MAAA,EAAQ;AACpB,MAAA,iCAAA,CAAkC,cAAc,MAAM,CAAA;AACtD,MAAA,wBAAA,EAAyB;AAAA,IAC3B,CAAA;AAAA;AAAA,IAEA,IAAI,qBAAA,GAA+C;AACjD,MAAA,OAAO,6BAAA;AAAA,IACT;AAAA,GACF;AACF;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAbsoluteUrl } from '@sentry/browser';
|
|
2
|
-
import { GLOBAL_OBJ,
|
|
3
|
-
import { URL_FULL, URL_PATH, URL_TEMPLATE } from '@sentry/conventions/attributes';
|
|
2
|
+
import { GLOBAL_OBJ, getClient, NAVIGATION_SPAN_NAME_FALLBACK, hasSpanStreamingEnabled, filterCollectedUrl } from '@sentry/core';
|
|
3
|
+
import { URL_FULL, URL_PATH, SENTRY_SEGMENT_NAME_SOURCE, URL_TEMPLATE } from '@sentry/conventions/attributes';
|
|
4
4
|
|
|
5
5
|
const WINDOW = GLOBAL_OBJ;
|
|
6
6
|
const GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ;
|
|
@@ -51,9 +51,10 @@ function updateNavigationSpanUrlFromLocation(span) {
|
|
|
51
51
|
}
|
|
52
52
|
const { pathname, search = "", hash = "" } = WINDOW.location;
|
|
53
53
|
const destinationUrl = getAbsoluteUrl(`${pathname}${search}${hash}`);
|
|
54
|
-
|
|
54
|
+
const client = getClient();
|
|
55
|
+
span.updateName(client && hasSpanStreamingEnabled(client) ? NAVIGATION_SPAN_NAME_FALLBACK : pathname);
|
|
55
56
|
span.setAttributes({
|
|
56
|
-
[
|
|
57
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: "url",
|
|
57
58
|
[URL_PATH]: pathname,
|
|
58
59
|
[URL_FULL]: filterCollectedUrl(destinationUrl)
|
|
59
60
|
});
|
|
@@ -65,9 +66,33 @@ function normalizePathname(pathname) {
|
|
|
65
66
|
}
|
|
66
67
|
return normalized;
|
|
67
68
|
}
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
function getRouteTemplate(routerState) {
|
|
70
|
+
const { matches } = routerState;
|
|
71
|
+
if (!matches.length) {
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
74
|
+
let template = "";
|
|
75
|
+
for (const match of matches) {
|
|
76
|
+
const routePath = match.route.path;
|
|
77
|
+
if (!routePath) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
template = routePath.startsWith("/") ? routePath : `${template.replace(/\/$/, "")}/${routePath}`;
|
|
81
|
+
}
|
|
82
|
+
return normalizePathname(template);
|
|
83
|
+
}
|
|
84
|
+
function updateSpanWithParameterizedRoute(span, routerState) {
|
|
85
|
+
const routeTemplate = getRouteTemplate(routerState);
|
|
86
|
+
const client = getClient();
|
|
87
|
+
if (!routeTemplate && client && hasSpanStreamingEnabled(client)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const parameterizedRoute = routeTemplate || normalizePathname(routerState.location.pathname);
|
|
91
|
+
span.updateName(parameterizedRoute);
|
|
92
|
+
span.setAttributes({
|
|
93
|
+
[SENTRY_SEGMENT_NAME_SOURCE]: "route",
|
|
94
|
+
[URL_TEMPLATE]: parameterizedRoute
|
|
95
|
+
});
|
|
71
96
|
}
|
|
72
97
|
function finalizeNavigationSpanFromRouterState(span, routerState) {
|
|
73
98
|
updateNavigationSpanUrlFromLocation(span);
|
|
@@ -76,12 +101,7 @@ function finalizeNavigationSpanFromRouterState(span, routerState) {
|
|
|
76
101
|
}
|
|
77
102
|
const { pathname } = WINDOW.location;
|
|
78
103
|
if (routerState.navigation?.state === "idle" && normalizePathname(routerState.location.pathname) === normalizePathname(pathname)) {
|
|
79
|
-
|
|
80
|
-
span.updateName(parameterizedRoute);
|
|
81
|
-
span.setAttributes({
|
|
82
|
-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "route",
|
|
83
|
-
[URL_TEMPLATE]: parameterizedRoute
|
|
84
|
-
});
|
|
104
|
+
updateSpanWithParameterizedRoute(span, routerState);
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
function finalizeNavigationSpanFromHydratedRouter(span) {
|
|
@@ -93,5 +113,5 @@ function finalizeNavigationSpanFromHydratedRouter(span) {
|
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
export { finalizeNavigationSpanFromHydratedRouter, finalizeNavigationSpanFromRouterState,
|
|
116
|
+
export { finalizeNavigationSpanFromHydratedRouter, finalizeNavigationSpanFromRouterState, normalizePathname, resolveNavigateAbsoluteUrl, resolveNavigateArg, resolveNavigateUrl, updateNavigationSpanUrlFromLocation, updateSpanWithParameterizedRoute };
|
|
97
117
|
//# sourceMappingURL=utils.js.map
|