@sentry/nextjs
Version:
Official Sentry SDK for Next.js
201 lines (198 loc) • 9.7 kB
JavaScript
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