@sentry/remix
Version:
Official Sentry SDK for Remix
131 lines (127 loc) • 4.43 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const browser = require('@sentry/browser');
const attributes = require('@sentry/conventions/attributes');
const core = require('@sentry/core');
const routeTiming = require('../routeTiming.js');
const FRAME_REQUEST_HEADER = "x-remix-frame";
function browserTracingIntegration(options = {}) {
const integration = browser.browserTracingIntegration({ ...options, instrumentNavigation: false });
return {
...integration,
afterAllSetup(client) {
integration.afterAllSetup(client);
if (options.instrumentPageLoad !== false) {
nameActivePageLoadSpan(client);
}
if (options.instrumentNavigation !== false) {
instrumentNavigationApi(client);
}
}
};
}
function nameActivePageLoadSpan(client) {
const activeSpan = browser.getActiveSpan();
if (!activeSpan) {
return;
}
const rootSpan = browser.getRootSpan(activeSpan);
if (core.spanToJSON(rootSpan).attributes[attributes.SENTRY_OP] !== "pageload") {
return;
}
recordRoute(client, browser.WINDOW.location?.pathname, getDocumentRoute());
const route = browser.resolveCurrentRoute(client);
if (route) {
applyRoute(rootSpan, route);
}
}
function instrumentNavigationApi(client) {
const navigation = browser.WINDOW.navigation;
if (!navigation) {
return;
}
let pending;
client.on("spanEnd", (span) => {
if (pending?.span === span) {
pending = void 0;
}
});
navigation.addEventListener("navigate", (event) => {
const url = event.destination?.url;
if (!url || !isRuntimeNavigation(event, url)) {
return;
}
const pathname = pathnameOf(url) || "/";
const route = browser.resolveRoute(url, client);
const span = browser.startBrowserTracingNavigationSpan(
client,
{
name: route ?? (core.hasSpanStreamingEnabled(client) ? core.NAVIGATION_SPAN_NAME_FALLBACK : pathname),
attributes: {
[attributes.SENTRY_SEGMENT_NAME_SOURCE]: route ? "route" : "url",
...route && { [attributes.URL_TEMPLATE]: route },
[attributes.SENTRY_ORIGIN]: "auto.navigation.remix_v3"
}
},
// The span needs the destination: `location` still points at the previous page until the
// navigation finishes.
{ url }
);
pending = span && !route ? { span, url, pathname } : void 0;
});
core.addFetchInstrumentationHandler(({ fetchData, headers, response }) => {
if (!pending || !response) {
return;
}
if (headers?.get(FRAME_REQUEST_HEADER) !== "true") {
return;
}
if (pathnameOf(fetchData.url, browser.WINDOW.location?.href) !== pending.pathname) {
return;
}
const { span, url, pathname } = pending;
pending = void 0;
const servedPathname = pathnameOf(response.url, browser.WINDOW.location?.href) || pathname;
recordRoute(client, servedPathname, routeTiming.parseRouteTiming(response.headers.get("server-timing")));
const route = browser.resolveRoute(response.url || url, client);
if (route) {
applyRoute(span, route);
}
});
}
function recordRoute(client, pathname, route) {
const provider = browser.getRouteProvider(client);
if (provider && "record" in provider && typeof provider.record === "function") {
provider.record(pathname, route);
}
}
function applyRoute(span, route) {
span.updateName(route);
span.setAttributes({ [attributes.SENTRY_SEGMENT_NAME_SOURCE]: "route", [attributes.URL_TEMPLATE]: route });
const activeSpan = browser.getActiveSpan();
if (activeSpan && browser.getRootSpan(activeSpan) === span) {
browser.getCurrentScope().setTransactionName(route);
}
}
function getDocumentRoute() {
const entry = browser.WINDOW.performance?.getEntriesByType?.("navigation")[0];
return entry?.serverTiming?.find((timing) => timing.name === routeTiming.ROUTE_TIMING_NAME)?.description;
}
function isRuntimeNavigation(event, url) {
return event.canIntercept && event.info !== "remix-document-reload" && isSameOrigin(url);
}
function isSameOrigin(url) {
try {
return new URL(url).origin === browser.WINDOW.location?.origin;
} catch {
return false;
}
}
function pathnameOf(url, base) {
try {
return new URL(url, base).pathname;
} catch {
return void 0;
}
}
exports.browserTracingIntegration = browserTracingIntegration;
//# sourceMappingURL=browserTracingIntegration.js.map