@sentry/react-router 10.35.0 → 10.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/client/createClientInstrumentation.js +309 -0
- package/build/cjs/client/createClientInstrumentation.js.map +1 -0
- package/build/cjs/client/hydratedRouter.js +15 -18
- package/build/cjs/client/hydratedRouter.js.map +1 -1
- package/build/cjs/client/tracingIntegration.js +25 -2
- package/build/cjs/client/tracingIntegration.js.map +1 -1
- package/build/cjs/cloudflare/index.js +4 -0
- package/build/cjs/cloudflare/index.js.map +1 -1
- package/build/cjs/common/utils.js +68 -0
- package/build/cjs/common/utils.js.map +1 -0
- package/build/cjs/index.client.js +4 -0
- package/build/cjs/index.client.js.map +1 -1
- package/build/cjs/index.server.js +4 -0
- package/build/cjs/index.server.js.map +1 -1
- package/build/cjs/server/createServerInstrumentation.js +231 -0
- package/build/cjs/server/createServerInstrumentation.js.map +1 -0
- package/build/cjs/server/instrumentation/reactRouter.js +8 -0
- package/build/cjs/server/instrumentation/reactRouter.js.map +1 -1
- package/build/cjs/server/integration/reactRouterServer.js +16 -6
- package/build/cjs/server/integration/reactRouterServer.js.map +1 -1
- package/build/cjs/server/serverGlobals.js +25 -0
- package/build/cjs/server/serverGlobals.js.map +1 -0
- package/build/cjs/server/wrapSentryHandleRequest.js +22 -7
- package/build/cjs/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/cjs/server/wrapServerAction.js +21 -1
- package/build/cjs/server/wrapServerAction.js.map +1 -1
- package/build/cjs/server/wrapServerLoader.js +21 -1
- package/build/cjs/server/wrapServerLoader.js.map +1 -1
- package/build/cjs/vite/makeCustomSentryVitePlugins.js +1 -1
- package/build/cjs/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/esm/client/createClientInstrumentation.js +305 -0
- package/build/esm/client/createClientInstrumentation.js.map +1 -0
- package/build/esm/client/hydratedRouter.js +16 -19
- package/build/esm/client/hydratedRouter.js.map +1 -1
- package/build/esm/client/tracingIntegration.js +25 -2
- package/build/esm/client/tracingIntegration.js.map +1 -1
- package/build/esm/cloudflare/index.js +1 -0
- package/build/esm/cloudflare/index.js.map +1 -1
- package/build/esm/common/utils.js +63 -0
- package/build/esm/common/utils.js.map +1 -0
- package/build/esm/index.client.js +1 -0
- package/build/esm/index.client.js.map +1 -1
- package/build/esm/index.server.js +2 -0
- package/build/esm/index.server.js.map +1 -1
- package/build/esm/package.json +1 -1
- package/build/esm/server/createServerInstrumentation.js +229 -0
- package/build/esm/server/createServerInstrumentation.js.map +1 -0
- package/build/esm/server/instrumentation/reactRouter.js +9 -1
- package/build/esm/server/instrumentation/reactRouter.js.map +1 -1
- package/build/esm/server/integration/reactRouterServer.js +16 -6
- package/build/esm/server/integration/reactRouterServer.js.map +1 -1
- package/build/esm/server/serverGlobals.js +22 -0
- package/build/esm/server/serverGlobals.js.map +1 -0
- package/build/esm/server/wrapSentryHandleRequest.js +23 -8
- package/build/esm/server/wrapSentryHandleRequest.js.map +1 -1
- package/build/esm/server/wrapServerAction.js +22 -2
- package/build/esm/server/wrapServerAction.js.map +1 -1
- package/build/esm/server/wrapServerLoader.js +22 -2
- package/build/esm/server/wrapServerLoader.js.map +1 -1
- package/build/esm/vite/makeCustomSentryVitePlugins.js +1 -1
- package/build/esm/vite/makeCustomSentryVitePlugins.js.map +1 -1
- package/build/types/client/createClientInstrumentation.d.ts +28 -0
- package/build/types/client/createClientInstrumentation.d.ts.map +1 -0
- package/build/types/client/hydratedRouter.d.ts.map +1 -1
- package/build/types/client/index.d.ts +2 -1
- package/build/types/client/index.d.ts.map +1 -1
- package/build/types/client/tracingIntegration.d.ts +32 -2
- package/build/types/client/tracingIntegration.d.ts.map +1 -1
- package/build/types/common/types.d.ts +88 -0
- package/build/types/common/types.d.ts.map +1 -0
- package/build/types/common/utils.d.ts +27 -0
- package/build/types/common/utils.d.ts.map +1 -0
- package/build/types/server/createServerInstrumentation.d.ts +18 -0
- package/build/types/server/createServerInstrumentation.d.ts.map +1 -0
- package/build/types/server/index.d.ts +1 -0
- package/build/types/server/index.d.ts.map +1 -1
- package/build/types/server/instrumentation/reactRouter.d.ts.map +1 -1
- package/build/types/server/integration/reactRouterServer.d.ts.map +1 -1
- package/build/types/server/serverGlobals.d.ts +11 -0
- package/build/types/server/serverGlobals.d.ts.map +1 -0
- package/build/types/server/wrapSentryHandleRequest.d.ts.map +1 -1
- package/build/types/server/wrapServerAction.d.ts.map +1 -1
- package/build/types/server/wrapServerLoader.d.ts.map +1 -1
- package/package.json +9 -9
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const browser = require('@sentry/browser');
|
|
4
|
+
const core = require('@sentry/core');
|
|
5
|
+
const debugBuild = require('../common/debug-build.js');
|
|
6
|
+
const utils = require('../common/utils.js');
|
|
7
|
+
|
|
8
|
+
const WINDOW = core.GLOBAL_OBJ ;
|
|
9
|
+
|
|
10
|
+
// Tracks active numeric navigation span to prevent duplicate spans when popstate fires
|
|
11
|
+
let currentNumericNavigationSpan;
|
|
12
|
+
|
|
13
|
+
const SENTRY_CLIENT_INSTRUMENTATION_FLAG = '__sentryReactRouterClientInstrumentationUsed';
|
|
14
|
+
// Intentionally never reset - once set, instrumentation API handles all navigations for the session.
|
|
15
|
+
const SENTRY_NAVIGATE_HOOK_INVOKED_FLAG = '__sentryReactRouterNavigateHookInvoked';
|
|
16
|
+
const SENTRY_POPSTATE_LISTENER_ADDED_FLAG = '__sentryReactRouterPopstateListenerAdded';
|
|
17
|
+
|
|
18
|
+
const GLOBAL_WITH_FLAGS = core.GLOBAL_OBJ ;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for creating Sentry client instrumentation.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a Sentry client instrumentation for React Router's instrumentation API.
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
28
|
+
function createSentryClientInstrumentation(
|
|
29
|
+
options = {},
|
|
30
|
+
) {
|
|
31
|
+
const { captureErrors = true } = options;
|
|
32
|
+
|
|
33
|
+
debugBuild.DEBUG_BUILD && core.debug.log('React Router client instrumentation API created.');
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
router(router) {
|
|
37
|
+
// Set the flag when React Router actually invokes our instrumentation.
|
|
38
|
+
// This ensures the flag is only set in Library Mode (where hooks run),
|
|
39
|
+
// not in Framework Mode (where hooks are never called).
|
|
40
|
+
// See: https://github.com/remix-run/react-router/discussions/13749
|
|
41
|
+
GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG] = true;
|
|
42
|
+
debugBuild.DEBUG_BUILD && core.debug.log('React Router client instrumentation API router hook registered.');
|
|
43
|
+
|
|
44
|
+
// Add popstate listener for browser back/forward navigation (persists for session, one listener only)
|
|
45
|
+
if (!GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] && WINDOW.addEventListener) {
|
|
46
|
+
GLOBAL_WITH_FLAGS[SENTRY_POPSTATE_LISTENER_ADDED_FLAG] = true;
|
|
47
|
+
|
|
48
|
+
WINDOW.addEventListener('popstate', () => {
|
|
49
|
+
const client = core.getClient();
|
|
50
|
+
if (!client) {
|
|
51
|
+
currentNumericNavigationSpan = undefined;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pathname = WINDOW.location?.pathname || '/';
|
|
56
|
+
|
|
57
|
+
// If there's an active numeric navigation span, update it instead of creating a duplicate
|
|
58
|
+
if (currentNumericNavigationSpan) {
|
|
59
|
+
if (currentNumericNavigationSpan.isRecording()) {
|
|
60
|
+
currentNumericNavigationSpan.updateName(pathname);
|
|
61
|
+
}
|
|
62
|
+
currentNumericNavigationSpan = undefined;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Only create a new span for actual browser back/forward button clicks
|
|
67
|
+
browser.startBrowserTracingNavigationSpan(client, {
|
|
68
|
+
name: pathname,
|
|
69
|
+
attributes: {
|
|
70
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
|
|
71
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
72
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',
|
|
73
|
+
'navigation.type': 'browser.popstate',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
debugBuild.DEBUG_BUILD && core.debug.log('React Router popstate listener registered for browser back/forward navigation.');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
router.instrument({
|
|
82
|
+
async navigate(callNavigate, info) {
|
|
83
|
+
// navigate(0) triggers a page reload - skip span creation, but still capture errors
|
|
84
|
+
// (navigation can be rejected before reload, e.g., by a navigation guard)
|
|
85
|
+
if (info.to === 0) {
|
|
86
|
+
const result = await callNavigate();
|
|
87
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
88
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.navigate', {
|
|
89
|
+
'http.url': info.currentUrl,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG] = true;
|
|
96
|
+
|
|
97
|
+
// Handle numeric navigations (navigate(-1), navigate(1), etc.)
|
|
98
|
+
if (typeof info.to === 'number') {
|
|
99
|
+
const client = core.getClient();
|
|
100
|
+
let navigationSpan;
|
|
101
|
+
|
|
102
|
+
if (client) {
|
|
103
|
+
const navigationType = info.to < 0 ? 'router.back' : 'router.forward';
|
|
104
|
+
const currentPathname = WINDOW.location?.pathname || info.currentUrl;
|
|
105
|
+
|
|
106
|
+
navigationSpan = browser.startBrowserTracingNavigationSpan(client, {
|
|
107
|
+
name: currentPathname,
|
|
108
|
+
attributes: {
|
|
109
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
|
|
110
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
111
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',
|
|
112
|
+
'navigation.type': navigationType,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Store ref so popstate listener can update it instead of creating a duplicate
|
|
117
|
+
currentNumericNavigationSpan = navigationSpan;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await callNavigate();
|
|
122
|
+
|
|
123
|
+
if (navigationSpan && WINDOW.location) {
|
|
124
|
+
navigationSpan.updateName(WINDOW.location.pathname);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
128
|
+
if (navigationSpan) {
|
|
129
|
+
navigationSpan.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
130
|
+
}
|
|
131
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.navigate', {
|
|
132
|
+
'http.url': WINDOW.location?.pathname || info.currentUrl,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
currentNumericNavigationSpan = undefined;
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle string navigations (e.g., navigate('/about'))
|
|
142
|
+
const client = core.getClient();
|
|
143
|
+
const toPath = String(info.to);
|
|
144
|
+
let navigationSpan;
|
|
145
|
+
|
|
146
|
+
if (client) {
|
|
147
|
+
navigationSpan = browser.startBrowserTracingNavigationSpan(client, {
|
|
148
|
+
name: toPath,
|
|
149
|
+
attributes: {
|
|
150
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
|
|
151
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'navigation',
|
|
152
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router.instrumentation_api',
|
|
153
|
+
'navigation.type': 'router.navigate',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const result = await callNavigate();
|
|
159
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
160
|
+
if (navigationSpan) {
|
|
161
|
+
navigationSpan.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
162
|
+
}
|
|
163
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.navigate', {
|
|
164
|
+
'http.url': toPath,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
async fetch(callFetch, info) {
|
|
171
|
+
await core.startSpan(
|
|
172
|
+
{
|
|
173
|
+
name: `Fetcher ${info.fetcherKey}`,
|
|
174
|
+
attributes: {
|
|
175
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.fetcher',
|
|
176
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
async span => {
|
|
180
|
+
const result = await callFetch();
|
|
181
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
182
|
+
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
183
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.fetcher', {
|
|
184
|
+
'http.url': info.href,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
route(route) {
|
|
194
|
+
route.instrument({
|
|
195
|
+
async loader(callLoader, info) {
|
|
196
|
+
const urlPath = utils.getPathFromRequest(info.request);
|
|
197
|
+
const routePattern = utils.normalizeRoutePath(utils.getPattern(info)) || urlPath;
|
|
198
|
+
|
|
199
|
+
await core.startSpan(
|
|
200
|
+
{
|
|
201
|
+
name: routePattern,
|
|
202
|
+
attributes: {
|
|
203
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_loader',
|
|
204
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
async span => {
|
|
208
|
+
const result = await callLoader();
|
|
209
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
210
|
+
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
211
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.client_loader', {
|
|
212
|
+
'http.url': urlPath,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
async action(callAction, info) {
|
|
220
|
+
const urlPath = utils.getPathFromRequest(info.request);
|
|
221
|
+
const routePattern = utils.normalizeRoutePath(utils.getPattern(info)) || urlPath;
|
|
222
|
+
|
|
223
|
+
await core.startSpan(
|
|
224
|
+
{
|
|
225
|
+
name: routePattern,
|
|
226
|
+
attributes: {
|
|
227
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_action',
|
|
228
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
async span => {
|
|
232
|
+
const result = await callAction();
|
|
233
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
234
|
+
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
235
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.client_action', {
|
|
236
|
+
'http.url': urlPath,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async middleware(callMiddleware, info) {
|
|
244
|
+
const urlPath = utils.getPathFromRequest(info.request);
|
|
245
|
+
const routePattern = utils.normalizeRoutePath(utils.getPattern(info)) || urlPath;
|
|
246
|
+
|
|
247
|
+
await core.startSpan(
|
|
248
|
+
{
|
|
249
|
+
name: routePattern,
|
|
250
|
+
attributes: {
|
|
251
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_middleware',
|
|
252
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
async span => {
|
|
256
|
+
const result = await callMiddleware();
|
|
257
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
258
|
+
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
259
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.client_middleware', {
|
|
260
|
+
'http.url': urlPath,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
async lazy(callLazy) {
|
|
268
|
+
await core.startSpan(
|
|
269
|
+
{
|
|
270
|
+
name: 'Lazy Route Load',
|
|
271
|
+
attributes: {
|
|
272
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_lazy',
|
|
273
|
+
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
async span => {
|
|
277
|
+
const result = await callLazy();
|
|
278
|
+
if (result.status === 'error' && result.error instanceof Error) {
|
|
279
|
+
span.setStatus({ code: core.SPAN_STATUS_ERROR, message: 'internal_error' });
|
|
280
|
+
utils.captureInstrumentationError(result, captureErrors, 'react_router.client_lazy', {});
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Check if React Router's instrumentation API is being used on the client.
|
|
292
|
+
* @experimental
|
|
293
|
+
*/
|
|
294
|
+
function isClientInstrumentationApiUsed() {
|
|
295
|
+
return !!GLOBAL_WITH_FLAGS[SENTRY_CLIENT_INSTRUMENTATION_FLAG];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if React Router's instrumentation API's navigate hook was invoked.
|
|
300
|
+
* @experimental
|
|
301
|
+
*/
|
|
302
|
+
function isNavigateHookInvoked() {
|
|
303
|
+
return !!GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
exports.createSentryClientInstrumentation = createSentryClientInstrumentation;
|
|
307
|
+
exports.isClientInstrumentationApiUsed = isClientInstrumentationApiUsed;
|
|
308
|
+
exports.isNavigateHookInvoked = isNavigateHookInvoked;
|
|
309
|
+
//# sourceMappingURL=createClientInstrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createClientInstrumentation.js","sources":["../../../src/client/createClientInstrumentation.ts"],"sourcesContent":["import { startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n debug,\n getClient,\n GLOBAL_OBJ,\n SEMANTIC_ATTRIBUTE_SENTRY_OP,\n SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,\n SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,\n SPAN_STATUS_ERROR,\n startSpan,\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';\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\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 * @experimental\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 currentNumericNavigationSpan.updateName(pathname);\n }\n currentNumericNavigationSpan = undefined;\n return;\n }\n\n // Only create a new span for actual browser back/forward button clicks\n startBrowserTracingNavigationSpan(client, {\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 });\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 'http.url': 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(client, {\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\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 navigationSpan.updateName(WINDOW.location.pathname);\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 'http.url': WINDOW.location?.pathname || info.currentUrl,\n });\n }\n } finally {\n currentNumericNavigationSpan = undefined;\n }\n return;\n }\n\n // Handle string navigations (e.g., navigate('/about'))\n const client = getClient();\n const toPath = String(info.to);\n let navigationSpan;\n\n if (client) {\n navigationSpan = startBrowserTracingNavigationSpan(client, {\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 }\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 'http.url': toPath,\n });\n }\n return;\n },\n\n async fetch(callFetch, info) {\n await startSpan(\n {\n name: `Fetcher ${info.fetcherKey}`,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.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 'http.url': info.href,\n });\n }\n },\n );\n },\n });\n },\n\n route(route: InstrumentableRoute) {\n route.instrument({\n async loader(callLoader, info) {\n const urlPath = getPathFromRequest(info.request);\n const routePattern = normalizeRoutePath(getPattern(info)) || urlPath;\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_loader',\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 'http.url': urlPath,\n });\n }\n },\n );\n },\n\n async action(callAction, info) {\n const urlPath = getPathFromRequest(info.request);\n const routePattern = normalizeRoutePath(getPattern(info)) || urlPath;\n\n await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_action',\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 'http.url': 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 await startSpan(\n {\n name: routePattern,\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_middleware',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.react_router.instrumentation_api',\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 'http.url': urlPath,\n });\n }\n },\n );\n },\n\n async lazy(callLazy) {\n await startSpan(\n {\n name: 'Lazy Route Load',\n attributes: {\n [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'function.react_router.client_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 * Check if React Router's instrumentation API is being used on the client.\n * @experimental\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 * @experimental\n */\nexport function isNavigateHookInvoked(): boolean {\n return !!GLOBAL_WITH_FLAGS[SENTRY_NAVIGATE_HOOK_INVOKED_FLAG];\n}\n"],"names":["GLOBAL_OBJ","DEBUG_BUILD","debug","getClient","startBrowserTracingNavigationSpan","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","SEMANTIC_ATTRIBUTE_SENTRY_OP","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","captureInstrumentationError","SPAN_STATUS_ERROR","startSpan","getPathFromRequest","normalizeRoutePath","getPattern"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
3
3
|
const browser = require('@sentry/browser');
|
|
4
4
|
const core = require('@sentry/core');
|
|
5
5
|
const debugBuild = require('../common/debug-build.js');
|
|
6
|
+
const createClientInstrumentation = require('./createClientInstrumentation.js');
|
|
6
7
|
|
|
7
8
|
const GLOBAL_OBJ_WITH_DATA_ROUTER = core.GLOBAL_OBJ
|
|
8
9
|
|
|
@@ -24,7 +25,6 @@ function instrumentHydratedRouter() {
|
|
|
24
25
|
|
|
25
26
|
if (router) {
|
|
26
27
|
// The first time we hit the router, we try to update the pageload transaction
|
|
27
|
-
// todo: update pageload tx here
|
|
28
28
|
const pageloadSpan = getActiveRootSpan();
|
|
29
29
|
|
|
30
30
|
if (pageloadSpan) {
|
|
@@ -41,18 +41,18 @@ function instrumentHydratedRouter() {
|
|
|
41
41
|
[core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.react_router',
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
46
|
+
// Patching navigate for creating accurate navigation transactions
|
|
47
|
+
if (typeof router.navigate === 'function') {
|
|
48
|
+
const originalNav = router.navigate.bind(router);
|
|
49
|
+
router.navigate = function sentryPatchedNavigate(...args) {
|
|
50
|
+
// Skip if instrumentation API is enabled (it handles navigation spans itself)
|
|
51
|
+
if (!createClientInstrumentation.isClientInstrumentationApiUsed()) {
|
|
52
|
+
maybeCreateNavigationTransaction(String(args[0]) || '<unknown route>', 'url');
|
|
53
|
+
}
|
|
54
|
+
return originalNav(...args);
|
|
55
|
+
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Subscribe to router state changes to update navigation transactions with parameterized routes
|
|
@@ -69,7 +69,8 @@ function instrumentHydratedRouter() {
|
|
|
69
69
|
if (
|
|
70
70
|
navigationSpanName &&
|
|
71
71
|
newState.navigation.state === 'idle' && // navigation has completed
|
|
72
|
-
|
|
72
|
+
// this event is for the currently active navigation
|
|
73
|
+
normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName)
|
|
73
74
|
) {
|
|
74
75
|
navigationSpan.updateName(parameterizedNavRoute);
|
|
75
76
|
navigationSpan.setAttributes({
|
|
@@ -90,11 +91,7 @@ function instrumentHydratedRouter() {
|
|
|
90
91
|
const interval = setInterval(() => {
|
|
91
92
|
if (trySubscribe() || retryCount >= MAX_RETRIES) {
|
|
92
93
|
if (retryCount >= MAX_RETRIES) {
|
|
93
|
-
debugBuild.DEBUG_BUILD &&
|
|
94
|
-
core.consoleSandbox(() => {
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
|
-
console.warn('Unable to instrument React Router: router not found after hydration.');
|
|
97
|
-
});
|
|
94
|
+
debugBuild.DEBUG_BUILD && core.debug.warn('Unable to instrument React Router: router not found after hydration.');
|
|
98
95
|
}
|
|
99
96
|
clearInterval(interval);
|
|
100
97
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { startBrowserTracingNavigationSpan } from '@sentry/browser';\nimport type { Span } from '@sentry/core';\nimport {\n
|
|
1
|
+
{"version":3,"file":"hydratedRouter.js","sources":["../../../src/client/hydratedRouter.ts"],"sourcesContent":["import { 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} from '@sentry/core';\nimport type { DataRouter, RouterState } from 'react-router';\nimport { DEBUG_BUILD } from '../common/debug-build';\nimport { isClientInstrumentationApiUsed } from './createClientInstrumentation';\n\nconst GLOBAL_OBJ_WITH_DATA_ROUTER = GLOBAL_OBJ as typeof GLOBAL_OBJ & {\n __reactRouterDataRouter?: DataRouter;\n};\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).description;\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 });\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 maybeCreateNavigationTransaction(String(args[0]) || '<unknown route>', 'url');\n }\n return originalNav(...args);\n };\n }\n\n // Subscribe to router state changes to update navigation transactions with parameterized routes\n router.subscribe(newState => {\n const navigationSpan = getActiveRootSpan();\n\n if (!navigationSpan) {\n return;\n }\n\n const navigationSpanName = spanToJSON(navigationSpan).description;\n const parameterizedNavRoute = getParameterizedRoute(newState);\n\n if (\n navigationSpanName &&\n newState.navigation.state === 'idle' && // navigation has completed\n // this event is for the currently active navigation\n normalizePathname(newState.location.pathname) === normalizePathname(navigationSpanName)\n ) {\n navigationSpan.updateName(parameterizedNavRoute);\n navigationSpan.setAttributes({\n [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',\n [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.react_router',\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, source: 'url' | 'route'): Span | undefined {\n const client = getClient();\n\n if (!client) {\n return undefined;\n }\n\n return startBrowserTracingNavigationSpan(client, {\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 },\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).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\nfunction getParameterizedRoute(routerState: RouterState): string {\n const lastMatch = routerState.matches[routerState.matches.length - 1];\n return normalizePathname(lastMatch?.route.path ?? routerState.location.pathname);\n}\n\nfunction normalizePathname(pathname: string): string {\n // Ensure it starts with a single slash\n let normalized = pathname.startsWith('/') ? pathname : `/${pathname}`;\n // Remove trailing slash unless it's the root\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n}\n"],"names":["GLOBAL_OBJ","spanToJSON","SEMANTIC_ATTRIBUTE_SENTRY_SOURCE","SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN","isClientInstrumentationApiUsed","DEBUG_BUILD","debug","getClient","startBrowserTracingNavigationSpan","SEMANTIC_ATTRIBUTE_SENTRY_OP","getActiveSpan","getRootSpan"],"mappings":"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"}
|
|
@@ -1,18 +1,38 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
|
|
3
3
|
const browser = require('@sentry/browser');
|
|
4
|
+
const createClientInstrumentation = require('./createClientInstrumentation.js');
|
|
4
5
|
const hydratedRouter = require('./hydratedRouter.js');
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Options for the React Router tracing integration.
|
|
9
|
+
*/
|
|
10
|
+
|
|
6
11
|
/**
|
|
7
12
|
* Browser tracing integration for React Router (Framework) applications.
|
|
8
|
-
* This integration will create navigation spans and enhance
|
|
13
|
+
* This integration will create navigation spans and enhance transaction names with parameterized routes.
|
|
9
14
|
*/
|
|
10
|
-
function reactRouterTracingIntegration(
|
|
15
|
+
function reactRouterTracingIntegration(
|
|
16
|
+
options = {},
|
|
17
|
+
) {
|
|
11
18
|
const browserTracingIntegrationInstance = browser.browserTracingIntegration({
|
|
12
19
|
// Navigation transactions are started within the hydrated router instrumentation
|
|
13
20
|
instrumentNavigation: false,
|
|
14
21
|
});
|
|
15
22
|
|
|
23
|
+
let clientInstrumentationInstance;
|
|
24
|
+
|
|
25
|
+
if (options.useInstrumentationAPI || options.instrumentationOptions) {
|
|
26
|
+
clientInstrumentationInstance = createClientInstrumentation.createSentryClientInstrumentation(options.instrumentationOptions);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const getClientInstrumentation = () => {
|
|
30
|
+
if (!clientInstrumentationInstance) {
|
|
31
|
+
clientInstrumentationInstance = createClientInstrumentation.createSentryClientInstrumentation(options.instrumentationOptions);
|
|
32
|
+
}
|
|
33
|
+
return clientInstrumentationInstance;
|
|
34
|
+
};
|
|
35
|
+
|
|
16
36
|
return {
|
|
17
37
|
...browserTracingIntegrationInstance,
|
|
18
38
|
name: 'ReactRouterTracingIntegration',
|
|
@@ -20,6 +40,9 @@ function reactRouterTracingIntegration() {
|
|
|
20
40
|
browserTracingIntegrationInstance.afterAllSetup(client);
|
|
21
41
|
hydratedRouter.instrumentHydratedRouter();
|
|
22
42
|
},
|
|
43
|
+
get clientInstrumentation() {
|
|
44
|
+
return getClientInstrumentation();
|
|
45
|
+
},
|
|
23
46
|
};
|
|
24
47
|
}
|
|
25
48
|
|
|
@@ -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 { instrumentHydratedRouter } from './hydratedRouter';\n\n/**\n * Browser tracing integration for React Router (Framework) applications.\n * This integration will create navigation spans and enhance
|
|
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 '../common/types';\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 * @experimental\n */\n instrumentationOptions?: CreateSentryClientInstrumentationOptions;\n\n /**\n * Enable React Router's instrumentation API.\n * When true, prepares for use with HydratedRouter's `unstable_instrumentations` prop.\n * @experimental\n * @default false\n */\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 for React Router's instrumentation API.\n * Lazily initialized on first access.\n * @experimental HydratedRouter doesn't invoke these hooks in Framework Mode yet.\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 let clientInstrumentationInstance: ClientInstrumentation | undefined;\n\n if (options.useInstrumentationAPI || options.instrumentationOptions) {\n clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);\n }\n\n const getClientInstrumentation = (): ClientInstrumentation => {\n if (!clientInstrumentationInstance) {\n clientInstrumentationInstance = createSentryClientInstrumentation(options.instrumentationOptions);\n }\n return clientInstrumentationInstance;\n };\n\n return {\n ...browserTracingIntegrationInstance,\n name: 'ReactRouterTracingIntegration',\n afterAllSetup(client) {\n browserTracingIntegrationInstance.afterAllSetup(client);\n instrumentHydratedRouter();\n },\n get clientInstrumentation(): ClientInstrumentation {\n return getClientInstrumentation();\n },\n };\n}\n"],"names":["originalBrowserTracingIntegration","createSentryClientInstrumentation","instrumentHydratedRouter"],"mappings":";;;;;;AASA;AACA;AACA;;AA6BA;AACA;AACA;AACA;AACO,SAAS,6BAA6B;AAC7C,EAAE,OAAO,GAAyC,EAAE;AACpD,EAAiC;AACjC,EAAE,MAAM,iCAAA,GAAoCA,iCAAiC,CAAC;AAC9E;AACA,IAAI,oBAAoB,EAAE,KAAK;AAC/B,GAAG,CAAC;;AAEJ,EAAE,IAAI,6BAA6B;;AAEnC,EAAE,IAAI,OAAO,CAAC,yBAAyB,OAAO,CAAC,sBAAsB,EAAE;AACvE,IAAI,gCAAgCC,6DAAiC,CAAC,OAAO,CAAC,sBAAsB,CAAC;AACrG,EAAE;;AAEF,EAAE,MAAM,wBAAA,GAA2B,MAA6B;AAChE,IAAI,IAAI,CAAC,6BAA6B,EAAE;AACxC,MAAM,gCAAgCA,6DAAiC,CAAC,OAAO,CAAC,sBAAsB,CAAC;AACvG,IAAI;AACJ,IAAI,OAAO,6BAA6B;AACxC,EAAE,CAAC;;AAEH,EAAE,OAAO;AACT,IAAI,GAAG,iCAAiC;AACxC,IAAI,IAAI,EAAE,+BAA+B;AACzC,IAAI,aAAa,CAAC,MAAM,EAAE;AAC1B,MAAM,iCAAiC,CAAC,aAAa,CAAC,MAAM,CAAC;AAC7D,MAAMC,uCAAwB,EAAE;AAChC,IAAI,CAAC;AACL,IAAI,IAAI,qBAAqB,GAA0B;AACvD,MAAM,OAAO,wBAAwB,EAAE;AACvC,IAAI,CAAC;AACL,GAAG;AACH;;;;"}
|
|
@@ -5,6 +5,7 @@ const browser = require('@sentry/browser');
|
|
|
5
5
|
const sdk = require('../client/sdk.js');
|
|
6
6
|
const tracingIntegration = require('../client/tracingIntegration.js');
|
|
7
7
|
const react = require('@sentry/react');
|
|
8
|
+
const createClientInstrumentation = require('../client/createClientInstrumentation.js');
|
|
8
9
|
const wrapSentryHandleRequest = require('../server/wrapSentryHandleRequest.js');
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -52,6 +53,9 @@ exports.reactErrorHandler = react.reactErrorHandler;
|
|
|
52
53
|
exports.useProfiler = react.useProfiler;
|
|
53
54
|
exports.withErrorBoundary = react.withErrorBoundary;
|
|
54
55
|
exports.withProfiler = react.withProfiler;
|
|
56
|
+
exports.createSentryClientInstrumentation = createClientInstrumentation.createSentryClientInstrumentation;
|
|
57
|
+
exports.isClientInstrumentationApiUsed = createClientInstrumentation.isClientInstrumentationApiUsed;
|
|
58
|
+
exports.isNavigateHookInvoked = createClientInstrumentation.isNavigateHookInvoked;
|
|
55
59
|
exports.wrapSentryHandleRequest = wrapSentryHandleRequest.wrapSentryHandleRequest;
|
|
56
60
|
exports.injectTraceMetaTags = injectTraceMetaTags;
|
|
57
61
|
Object.prototype.hasOwnProperty.call(browser, '__proto__') &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/cloudflare/index.ts"],"sourcesContent":["import { getTraceMetaTags } from '@sentry/core';\n\nexport * from '../client';\n\nexport { wrapSentryHandleRequest } from '../server/wrapSentryHandleRequest';\n\n/**\n * Injects Sentry trace meta tags into the HTML response by transforming the ReadableStream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n * @param body - ReadableStream containing the HTML response body to modify\n * @returns A new ReadableStream with Sentry trace meta tags injected into the head section\n */\nexport function injectTraceMetaTags(body: ReadableStream): ReadableStream {\n const headClosingTag = '</head>';\n\n const reader = body.getReader();\n const stream = new ReadableStream({\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) {\n controller.close();\n return;\n }\n\n const encoder = new TextEncoder();\n const html = value instanceof Uint8Array ? new TextDecoder().decode(value) : String(value);\n\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n\n controller.enqueue(encoder.encode(modifiedHtml));\n return;\n }\n\n controller.enqueue(encoder.encode(html));\n },\n });\n\n return stream;\n}\n"],"names":["getTraceMetaTags"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/cloudflare/index.ts"],"sourcesContent":["import { getTraceMetaTags } from '@sentry/core';\n\nexport * from '../client';\n\nexport { wrapSentryHandleRequest } from '../server/wrapSentryHandleRequest';\n\n/**\n * Injects Sentry trace meta tags into the HTML response by transforming the ReadableStream.\n * This enables distributed tracing by adding trace context to the HTML document head.\n * @param body - ReadableStream containing the HTML response body to modify\n * @returns A new ReadableStream with Sentry trace meta tags injected into the head section\n */\nexport function injectTraceMetaTags(body: ReadableStream): ReadableStream {\n const headClosingTag = '</head>';\n\n const reader = body.getReader();\n const stream = new ReadableStream({\n async pull(controller) {\n const { done, value } = await reader.read();\n\n if (done) {\n controller.close();\n return;\n }\n\n const encoder = new TextEncoder();\n const html = value instanceof Uint8Array ? new TextDecoder().decode(value) : String(value);\n\n if (html.includes(headClosingTag)) {\n const modifiedHtml = html.replace(headClosingTag, `${getTraceMetaTags()}${headClosingTag}`);\n\n controller.enqueue(encoder.encode(modifiedHtml));\n return;\n }\n\n controller.enqueue(encoder.encode(html));\n },\n });\n\n return stream;\n}\n"],"names":["getTraceMetaTags"],"mappings":";;;;;;;;;;AAMA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,mBAAmB,CAAC,IAAI,EAAkC;AAC1E,EAAE,MAAM,cAAA,GAAiB,SAAS;;AAElC,EAAE,MAAM,MAAA,GAAS,IAAI,CAAC,SAAS,EAAE;AACjC,EAAE,MAAM,MAAA,GAAS,IAAI,cAAc,CAAC;AACpC,IAAI,MAAM,IAAI,CAAC,UAAU,EAAE;AAC3B,MAAM,MAAM,EAAE,IAAI,EAAE,KAAA,EAAM,GAAI,MAAM,MAAM,CAAC,IAAI,EAAE;;AAEjD,MAAM,IAAI,IAAI,EAAE;AAChB,QAAQ,UAAU,CAAC,KAAK,EAAE;AAC1B,QAAQ;AACR,MAAM;;AAEN,MAAM,MAAM,OAAA,GAAU,IAAI,WAAW,EAAE;AACvC,MAAM,MAAM,OAAO,KAAA,YAAiB,UAAA,GAAa,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAA,GAAI,MAAM,CAAC,KAAK,CAAC;;AAEhG,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;AACzC,QAAQ,MAAM,YAAA,GAAe,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,EAAAA,qBAAA,EAAA,CAAA,EAAA,cAAA,CAAA,CAAA,CAAA;;AAEA,QAAA,UAAA,CAAA,OAAA,CAAA,OAAA,CAAA,MAAA,CAAA,YAAA,CAAA,CAAA;AACA,QAAA;AACA,MAAA;;AAEA,MAAA,UAAA,CAAA,OAAA,CAAA,OAAA,CAAA,MAAA,CAAA,IAAA,CAAA,CAAA;AACA,IAAA,CAAA;AACA,GAAA,CAAA;;AAEA,EAAA,OAAA,MAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
const core = require('@sentry/core');
|
|
4
|
+
const debugBuild = require('./debug-build.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extracts pathname from request URL.
|
|
8
|
+
* Falls back to '<unknown>' with DEBUG warning if URL cannot be parsed.
|
|
9
|
+
*/
|
|
10
|
+
function getPathFromRequest(request) {
|
|
11
|
+
try {
|
|
12
|
+
return new URL(request.url).pathname;
|
|
13
|
+
} catch {
|
|
14
|
+
try {
|
|
15
|
+
// Fallback: use a dummy base URL since we only care about the pathname
|
|
16
|
+
return new URL(request.url, 'http://example.com').pathname;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
debugBuild.DEBUG_BUILD && core.debug.warn('Failed to parse URL from request:', request.url, error);
|
|
19
|
+
return '<unknown>';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extracts route pattern from instrumentation info.
|
|
26
|
+
* Prefers `pattern` (planned for v8) over `unstable_pattern` (v7.x).
|
|
27
|
+
*/
|
|
28
|
+
function getPattern(info) {
|
|
29
|
+
return info.pattern ?? info.unstable_pattern;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes route path by ensuring it starts with a slash.
|
|
34
|
+
* Returns undefined if the input is falsy.
|
|
35
|
+
*/
|
|
36
|
+
function normalizeRoutePath(pattern) {
|
|
37
|
+
if (!pattern) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return pattern.startsWith('/') ? pattern : `/${pattern}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Captures an error from instrumentation result.
|
|
45
|
+
* Caller must verify result contains an Error before calling.
|
|
46
|
+
*/
|
|
47
|
+
function captureInstrumentationError(
|
|
48
|
+
result,
|
|
49
|
+
captureErrors,
|
|
50
|
+
mechanismType,
|
|
51
|
+
data,
|
|
52
|
+
) {
|
|
53
|
+
if (captureErrors) {
|
|
54
|
+
core.captureException(result.error, {
|
|
55
|
+
mechanism: {
|
|
56
|
+
type: mechanismType,
|
|
57
|
+
handled: false,
|
|
58
|
+
data,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
exports.captureInstrumentationError = captureInstrumentationError;
|
|
65
|
+
exports.getPathFromRequest = getPathFromRequest;
|
|
66
|
+
exports.getPattern = getPattern;
|
|
67
|
+
exports.normalizeRoutePath = normalizeRoutePath;
|
|
68
|
+
//# sourceMappingURL=utils.js.map
|