UNPKG

@sentry/nextjs

Version:
201 lines (198 loc) 9.7 kB
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, GLOBAL_OBJ, browserPerformanceTimeOrigin, SEMANTIC_ATTRIBUTE_SENTRY_OP } from '@sentry/core'; import { WINDOW, startBrowserTracingNavigationSpan, getAbsoluteUrl, startBrowserTracingPageLoadSpan } from '@sentry/react'; import { maybeParameterizeRoute } from './parameterization.js'; import { URL_TEMPLATE, URL_FULL, URL_PATH } from '@sentry/conventions/attributes'; function stripTrailingSlash(pathname) { return pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname; } function setNavigationSpanUrlAttributes(span, urlPath, urlOrPath) { span.setAttributes({ [URL_PATH]: urlPath, [URL_FULL]: getAbsoluteUrl(urlOrPath) }); } const INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME = "incomplete-app-router-transaction"; let navigationRoutingMode = "router-patch"; const currentRouterPatchingNavigationSpanRef = { current: void 0 }; function appRouterInstrumentPageLoad(client) { const pathname = stripTrailingSlash(WINDOW.location.pathname); const parameterizedPathname = maybeParameterizeRoute(pathname); const origin = browserPerformanceTimeOrigin(); startBrowserTracingPageLoadSpan(client, { name: parameterizedPathname ?? pathname, // pageload should always start at timeOrigin (and needs to be in s, not ms) startTime: origin ? origin / 1e3 : void 0, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "pageload", [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.pageload.nextjs.app_router_instrumentation", [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? "route" : "url", ...parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname } } }); } const GLOBAL_OBJ_WITH_NEXT_ROUTER = GLOBAL_OBJ; const globalWithInjectedBasePath = GLOBAL_OBJ; function appRouterInstrumentNavigation(client) { routerTransitionHandler = (href, navigationType) => { const basePath = process.env._sentryBasePath ?? globalWithInjectedBasePath._sentryBasePath; const normalizedHref = basePath && !href.startsWith(basePath) ? `${basePath}${href}` : href; const unparameterizedPathname = stripTrailingSlash(new URL(normalizedHref, WINDOW.location.href).pathname); const parameterizedPathname = maybeParameterizeRoute(unparameterizedPathname); const pathname = parameterizedPathname ?? unparameterizedPathname; if (navigationRoutingMode === "router-patch") { navigationRoutingMode = "transition-start-hook"; } const currentNavigationSpan = currentRouterPatchingNavigationSpanRef.current; if (currentNavigationSpan) { currentNavigationSpan.updateName(pathname); currentNavigationSpan.setAttributes({ "navigation.type": `router.${navigationType}`, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? "route" : "url", ...parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname } }); setNavigationSpanUrlAttributes(currentNavigationSpan, unparameterizedPathname, normalizedHref); currentRouterPatchingNavigationSpanRef.current = void 0; } else { startBrowserTracingNavigationSpan( client, { name: pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation", [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.nextjs.app_router_instrumentation", [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? "route" : "url", "navigation.type": `router.${navigationType}`, ...parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname } } }, { url: getAbsoluteUrl(normalizedHref) } ); } }; WINDOW.addEventListener("popstate", () => { const pathname = stripTrailingSlash(WINDOW.location.pathname); const parameterizedPathname = maybeParameterizeRoute(pathname); if (currentRouterPatchingNavigationSpanRef.current?.isRecording()) { currentRouterPatchingNavigationSpanRef.current.updateName(parameterizedPathname ?? pathname); currentRouterPatchingNavigationSpanRef.current.setAttribute( SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, parameterizedPathname ? "route" : "url" ); if (parameterizedPathname) { currentRouterPatchingNavigationSpanRef.current.setAttribute(URL_TEMPLATE, parameterizedPathname); } setNavigationSpanUrlAttributes(currentRouterPatchingNavigationSpanRef.current, pathname, WINDOW.location.href); } else { currentRouterPatchingNavigationSpanRef.current = startBrowserTracingNavigationSpan( client, { name: parameterizedPathname ?? pathname, attributes: { [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.nextjs.app_router_instrumentation", [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? "route" : "url", "navigation.type": "browser.popstate", ...parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname } } }, { url: getAbsoluteUrl(pathname) } ); } }); let routerPatched = false; let triesToFindRouter = 0; const MAX_TRIES_TO_FIND_ROUTER = 500; const ROUTER_AVAILABILITY_CHECK_INTERVAL_MS = 20; const checkForRouterAvailabilityInterval = setInterval(() => { triesToFindRouter++; const router = GLOBAL_OBJ_WITH_NEXT_ROUTER?.next?.router ?? GLOBAL_OBJ_WITH_NEXT_ROUTER?.nd?.router; if (routerPatched || triesToFindRouter > MAX_TRIES_TO_FIND_ROUTER) { clearInterval(checkForRouterAvailabilityInterval); } else if (router) { clearInterval(checkForRouterAvailabilityInterval); routerPatched = true; patchRouter(client, router, currentRouterPatchingNavigationSpanRef); ["nd", "next"].forEach((globalValueName) => { const globalValue = GLOBAL_OBJ_WITH_NEXT_ROUTER[globalValueName]; if (globalValue) { GLOBAL_OBJ_WITH_NEXT_ROUTER[globalValueName] = new Proxy(globalValue, { set(target, p, newValue) { if (p === "router" && typeof newValue === "object" && newValue !== null) { patchRouter(client, newValue, currentRouterPatchingNavigationSpanRef); } target[p] = newValue; return true; } }); } }); } }, ROUTER_AVAILABILITY_CHECK_INTERVAL_MS); } function transactionNameifyRouterArgument(target) { try { return new URL(target, "http://example.com/").pathname; } catch { return "/"; } } const patchedRouters = /* @__PURE__ */ new WeakSet(); function patchRouter(client, router, currentNavigationSpanRef) { if (patchedRouters.has(router)) { return; } patchedRouters.add(router); ["back", "forward", "push", "replace"].forEach((routerFunctionName) => { if (router?.[routerFunctionName]) { router[routerFunctionName] = new Proxy(router[routerFunctionName], { apply(target, thisArg, argArray) { if (navigationRoutingMode !== "router-patch") { return target.apply(thisArg, argArray); } let transactionName = INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME; const transactionAttributes = { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: "navigation", [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: "auto.navigation.nextjs.app_router_instrumentation", [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "url" }; const href = argArray[0]; const basePath = process.env._sentryBasePath ?? globalWithInjectedBasePath._sentryBasePath; const normalizedHref = basePath && typeof href === "string" && !href.startsWith(basePath) ? `${basePath}${href}` : href; if (routerFunctionName === "push") { transactionName = stripTrailingSlash(transactionNameifyRouterArgument(normalizedHref)); transactionAttributes["navigation.type"] = "router.push"; } else if (routerFunctionName === "replace") { transactionName = stripTrailingSlash(transactionNameifyRouterArgument(normalizedHref)); transactionAttributes["navigation.type"] = "router.replace"; } else if (routerFunctionName === "back") { transactionAttributes["navigation.type"] = "router.back"; } else if (routerFunctionName === "forward") { transactionAttributes["navigation.type"] = "router.forward"; } const parameterizedPathname = maybeParameterizeRoute(transactionName); const navigationUrl = routerFunctionName === "back" || routerFunctionName === "forward" ? void 0 : getAbsoluteUrl(normalizedHref); currentNavigationSpanRef.current = startBrowserTracingNavigationSpan( client, { name: parameterizedPathname ?? transactionName, attributes: { ...transactionAttributes, [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: parameterizedPathname ? "route" : "url", ...parameterizedPathname && { [URL_TEMPLATE]: parameterizedPathname } } }, navigationUrl ? { url: navigationUrl } : void 0 ); return target.apply(thisArg, argArray); } }); } }); } let routerTransitionHandler = void 0; function captureRouterTransitionStart(href, navigationType) { if (routerTransitionHandler) { routerTransitionHandler(href, navigationType); } } export { INCOMPLETE_APP_ROUTER_INSTRUMENTATION_TRANSACTION_NAME, appRouterInstrumentNavigation, appRouterInstrumentPageLoad, captureRouterTransitionStart }; //# sourceMappingURL=appRouterRoutingInstrumentation.js.map