@sentry/remix
Version:
Official Sentry SDK for Remix
171 lines (142 loc) • 4.7 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const core = require('@sentry/core');
const debugBuild = require('../utils/debug-build.js');
const globalWithInjectedManifest = core.GLOBAL_OBJ
;
// Performance caches
let cachedManifest = null;
let cachedManifestString = undefined;
const compiledRegexCache = new Map();
const routeResultCache = new Map();
/**
* Calculate specificity score for route matching. Lower scores = more specific routes.
*/
function getRouteSpecificity(routePath) {
const segments = routePath.split('/').filter(Boolean);
let score = 0;
for (const segment of segments) {
if (segment.startsWith(':')) {
const paramName = segment.substring(1);
if (paramName.endsWith('*')) {
// Splat/catchall routes are least specific
score += 100;
} else {
// Dynamic segments are more specific than splats
score += 10;
}
}
// Static segments add 0 (most specific)
}
return score;
}
/**
* Get compiled regex from cache or create and cache it.
*/
function getCompiledRegex(regexString) {
if (compiledRegexCache.has(regexString)) {
return compiledRegexCache.get(regexString) ?? null;
}
try {
// oxlint-disable-next-line sdk/no-regexp-constructor -- regex patterns are from build-time route manifest, not user input
const regex = new RegExp(regexString);
compiledRegexCache.set(regexString, regex);
return regex;
} catch (error) {
debugBuild.DEBUG_BUILD && core.debug.warn('Could not compile regex', { regexString, error });
return null;
}
}
/**
* Get and cache the route manifest from the global object.
* @returns The parsed route manifest or null if not available/invalid.
*/
function getManifest() {
if (
!globalWithInjectedManifest?._sentryRemixRouteManifest ||
typeof globalWithInjectedManifest._sentryRemixRouteManifest !== 'string'
) {
return null;
}
const currentManifestString = globalWithInjectedManifest._sentryRemixRouteManifest;
if (cachedManifest && cachedManifestString === currentManifestString) {
return cachedManifest;
}
compiledRegexCache.clear();
routeResultCache.clear();
let manifest = {
staticRoutes: [],
dynamicRoutes: [],
};
try {
// The manifest string is JSON-stringified in the Vite plugin for safe injection into JavaScript.
// We parse once to convert the JSON string back to an object.
manifest = JSON.parse(currentManifestString);
if (!Array.isArray(manifest.staticRoutes) || !Array.isArray(manifest.dynamicRoutes)) {
return null;
}
cachedManifest = manifest;
cachedManifestString = currentManifestString;
return manifest;
} catch {
debugBuild.DEBUG_BUILD && core.debug.warn('Could not extract route manifest');
return null;
}
}
/**
* Find matching routes from static and dynamic route collections.
* @param route - The route to match against.
* @param staticRoutes - Array of static route objects.
* @param dynamicRoutes - Array of dynamic route objects.
* @returns Array of matching parameterized route paths.
*/
function findMatchingRoutes(
route,
staticRoutes,
dynamicRoutes,
) {
const matches = [];
// Static routes don't need parameterization - return empty to keep source as 'url'
if (staticRoutes.some(r => r.path === route)) {
return matches;
}
// Check dynamic routes
for (const dynamicRoute of dynamicRoutes) {
if (dynamicRoute.regex) {
const regex = getCompiledRegex(dynamicRoute.regex);
if (regex?.test(route)) {
matches.push(dynamicRoute.path);
}
}
}
return matches;
}
/**
* Check if the route manifest is available (injected by the Vite plugin).
* @returns True if the manifest is available, false otherwise.
*/
function hasManifest() {
return getManifest() !== null;
}
/**
* Parameterize a route using the route manifest.
*
* @param route - The route to parameterize.
* @returns The parameterized route or undefined if no parameterization is needed.
*/
const maybeParameterizeRemixRoute = (route) => {
const manifest = getManifest();
if (!manifest) {
return undefined;
}
if (routeResultCache.has(route)) {
return routeResultCache.get(route);
}
const { staticRoutes, dynamicRoutes } = manifest;
const matches = findMatchingRoutes(route, staticRoutes, dynamicRoutes);
const result = matches.sort((a, b) => getRouteSpecificity(a) - getRouteSpecificity(b))[0];
routeResultCache.set(route, result);
return result;
};
exports.hasManifest = hasManifest;
exports.maybeParameterizeRemixRoute = maybeParameterizeRemixRoute;
//# sourceMappingURL=remixRouteParameterization.js.map