@sentry/remix
Version:
Official Sentry SDK for Remix
155 lines (130 loc) • 4.67 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const core = require('@sentry/core');
const debugBuild = require('./debug-build.js');
const response = require('./vendor/response.js');
/**
* Store configured FormData keys as span attributes for Remix actions.
*/
async function storeFormDataKeys(
args,
span,
formDataKeys,
) {
try {
// We clone the request for Remix be able to read the FormData later.
const clonedRequest = args.request.clone();
// This only will return the last name of multiple file uploads in a single FormData entry.
// We can switch to `unstable_parseMultipartFormData` when it's stable.
// https://remix.run/docs/en/main/utils/parse-multipart-form-data#unstable_parsemultipartformdata
const formData = await clonedRequest.formData();
formData.forEach((value, key) => {
let attrKey = key;
if (formDataKeys?.[key]) {
if (typeof formDataKeys[key] === 'string') {
attrKey = formDataKeys[key];
}
span.setAttribute(
`remix.action_form_data.${attrKey}`,
typeof value === 'string' ? value : '[non-string value]',
);
}
});
} catch (e) {
debugBuild.DEBUG_BUILD && core.debug.warn('Failed to read FormData from request', e);
}
}
/**
* Converts Remix route IDs to parameterized paths at runtime.
* (e.g., "routes/users.$id" -> "/users/:id")
*
* @param routeId - The Remix route ID
* @returns The parameterized path
* @internal
*/
function convertRemixRouteIdToPath(routeId) {
// Remove the "routes/" prefix if present
const path = routeId.replace(/^routes\//, '');
// Handle root index route
if (path === 'index' || path === '_index') {
return '/';
}
// Split by dots to get segments
const segments = path.split('.');
const pathSegments = [];
for (let i = 0; i < segments.length; i++) {
const segment = segments[i];
if (!segment) {
continue;
}
// Skip layout route segments (prefixed with _)
if (segment.startsWith('_') && segment !== '_index') {
continue;
}
// Handle '_index' segments at the end (always skip - indicates an index route)
if (segment === '_index' && i === segments.length - 1) {
continue;
}
// Handle 'index' segments at the end (skip only if there are path segments,
// otherwise root index is handled by the early return above)
if (segment === 'index' && i === segments.length - 1 && pathSegments.length > 0) {
continue;
}
// Handle splat routes (catch-all)
// Remix accesses splat params via params["*"] at runtime
if (segment === '$') {
pathSegments.push(':*');
continue;
}
// Handle dynamic segments (prefixed with $)
if (segment.startsWith('$')) {
const paramName = segment.substring(1);
pathSegments.push(`:${paramName}`);
} else if (segment !== 'index') {
// Static segment (skip remaining 'index' segments)
pathSegments.push(segment);
}
}
// Return with leading slash for consistency with client-side URL paths
const routePath = pathSegments.length > 0 ? `/${pathSegments.join('/')}` : '/';
return routePath;
}
/** Check if running in Cloudflare Workers environment. */
function isCloudflareEnv() {
// eslint-disable-next-line no-restricted-globals
return typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Cloudflare');
}
/**
* Get transaction name from routes and url
*/
function getTransactionName(routes, url) {
const matches = response.matchServerRoutes(routes, url.pathname);
const match = matches && response.getRequestMatch(url, matches);
if (match === null) {
return [url.pathname, 'url'];
}
const routeId = match.route.id || 'no-route-id';
// Convert route ID to parameterized path (e.g., "routes/users.$id" -> "/users/:id")
// This is a pure string transformation that works without the Vite plugin manifest
const parameterizedPath = convertRemixRouteIdToPath(routeId);
return [parameterizedPath, 'route'];
}
/**
* Creates routes from the server route manifest
*
* @param manifest
* @param parentId
*/
function createRoutes(manifest, parentId) {
return Object.entries(manifest)
.filter(([, route]) => route.parentId === parentId)
.map(([id, route]) => ({
...route,
children: createRoutes(manifest, id),
})) ;
}
exports.convertRemixRouteIdToPath = convertRemixRouteIdToPath;
exports.createRoutes = createRoutes;
exports.getTransactionName = getTransactionName;
exports.isCloudflareEnv = isCloudflareEnv;
exports.storeFormDataKeys = storeFormDataKeys;
//# sourceMappingURL=utils.js.map