98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
import { useEffect } from "react";
|
|
import {
|
|
type ActionFunction,
|
|
type HeadersFunction,
|
|
type LoaderFunctionArgs,
|
|
type MiddlewareFunction,
|
|
useLoaderData,
|
|
useRouteError,
|
|
useRouteLoaderData,
|
|
} from "react-router";
|
|
|
|
import "./globals.css";
|
|
import "./themes.css";
|
|
import "./styles/sentry-masking.css";
|
|
|
|
import { RootSurfaceApp, RootSurfaceErrorBoundary, rootI18nNamespaces } from "./root-client-surface";
|
|
import { serverTimingHeaders } from "./modules/metrics/utils/defaultHeaders.server";
|
|
import type { PortalRootData } from "./services/customer-portal/portal-root";
|
|
import type { AppRootData } from "./utils/data/useRootData";
|
|
import { type v2MetaFunction } from "./utils/compat/v2MetaFunction";
|
|
import { CSP_NONCE_REQUEST_HEADER } from "./utils/security/contentSecurityPolicy";
|
|
|
|
// Production and development builds receive this value from vite.config.ts.
|
|
// The test-only runtime branch keeps the route module independently testable
|
|
// without weakening the explicit surface selection used for real builds.
|
|
const portalBuildSelected =
|
|
import.meta.env.MODE === "test" ? process.env.WEB_RUNTIME_SURFACE === "portal" : import.meta.env.VITE_HIVEGRID_BUILD_SURFACE === "portal";
|
|
|
|
export const middleware: MiddlewareFunction<Response>[] = [
|
|
async ({ request }, next) => {
|
|
// VA-P26a: the portal surface owns a separate identity class and must not
|
|
// parse, validate, or bind the core User/TenantUser session at the root.
|
|
if (portalBuildSelected) return next();
|
|
const { runCoreRootMiddleware } = await import("./root-core.server");
|
|
return runCoreRootMiddleware(request, next);
|
|
},
|
|
];
|
|
|
|
export const headers: HeadersFunction = (args) => {
|
|
const responseHeaders = new Headers(serverTimingHeaders(args));
|
|
if (!portalBuildSelected) {
|
|
for (const headerName of [CSP_NONCE_REQUEST_HEADER, "Content-Security-Policy"] as const) {
|
|
const value = args.loaderHeaders.get(headerName) ?? args.parentHeaders.get(headerName);
|
|
if (value) responseHeaders.set(headerName, value);
|
|
}
|
|
return responseHeaders;
|
|
}
|
|
|
|
for (const headerName of [CSP_NONCE_REQUEST_HEADER, "Content-Security-Policy", "Cache-Control", "Referrer-Policy"] as const) {
|
|
const value = args.errorHeaders?.get(headerName) ?? args.actionHeaders.get(headerName) ?? args.loaderHeaders.get(headerName) ?? args.parentHeaders.get(headerName);
|
|
if (value) responseHeaders.set(headerName, value);
|
|
}
|
|
return responseHeaders;
|
|
};
|
|
|
|
export const handle = { i18n: [...rootI18nNamespaces] };
|
|
|
|
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
|
if (portalBuildSelected) {
|
|
const { loadPortalRootData } = await import("./services/customer-portal/portal-root.server");
|
|
return loadPortalRootData(request);
|
|
}
|
|
|
|
const { loadCoreRootData } = await import("./root-core.server");
|
|
return loadCoreRootData({ request, params });
|
|
};
|
|
|
|
export const meta: v2MetaFunction<AppRootData | PortalRootData> = ({ data }) => data?.metatags ?? [];
|
|
|
|
export const action: ActionFunction = async ({ request }) => {
|
|
if (portalBuildSelected) {
|
|
const { handlePortalRootAction } = await import("./services/customer-portal/portal-root.server");
|
|
return handlePortalRootAction(request);
|
|
}
|
|
const { handleCoreRootAction } = await import("./root-core.server");
|
|
return handleCoreRootAction(request);
|
|
};
|
|
|
|
export default function App() {
|
|
const data = useLoaderData<AppRootData | PortalRootData>();
|
|
return <RootSurfaceApp data={data} />;
|
|
}
|
|
|
|
export function ErrorBoundary() {
|
|
const error = useRouteError();
|
|
const rootData = useRouteLoaderData<AppRootData | PortalRootData>("root");
|
|
// Report route-level errors that React handled before they reached the SSR
|
|
// integration. Portal and Core share this transport, not their UI shells.
|
|
useEffect(() => {
|
|
if (error) {
|
|
void import("@sentry/react").then((Sentry) => {
|
|
Sentry.captureException(error);
|
|
});
|
|
}
|
|
}, [error]);
|
|
return <RootSurfaceErrorBoundary rootData={rootData} />;
|
|
}
|