UNPKG

react-router

Version:
216 lines (215 loc) • 8.04 kB
/** * react-router v8.0.0 * * Copyright (c) Remix Software Inc. * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ import { ErrorResponseImpl, isRouteErrorResponse, stripBasename } from "../router/utils.js"; import { isRedirectStatusCode, isResponse } from "../router/router.js"; import { encode } from "../../vendor/turbo-stream-v2/turbo-stream.js"; import { NO_BODY_STATUS_CODES, SingleFetchRedirectSymbol } from "../dom/ssr/single-fetch.js"; import "./mode.js"; import { sanitizeError, sanitizeErrors } from "./errors.js"; import { getDocumentHeaders } from "./headers.js"; import { throwIfPotentialCSRFAttack } from "../actions.js"; import { getNormalizedPath } from "./urls.js"; //#region lib/server-runtime/single-fetch.ts const SERVER_NO_BODY_STATUS_CODES = new Set([...NO_BODY_STATUS_CODES, 304]); async function singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) { try { try { throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []); } catch (e) { return handleQueryError(/* @__PURE__ */ new Error("Bad Request"), 400); } return handleQueryResult(await staticHandler.query(request, { requestContext: loadContext, skipLoaderErrorBubbling: true, skipRevalidation: true, generateMiddlewareResponse: async (query) => { try { return handleQueryResult(await query(request)); } catch (error) { return handleQueryError(error); } }, normalizePath: (r) => getNormalizedPath(r) })); } catch (error) { return handleQueryError(error); } function handleQueryResult(result) { return isResponse(result) ? result : staticContextToResponse(result); } function handleQueryError(error, status = 500) { handleError(error); return generateSingleFetchResponse(request, build, serverMode, { result: { error }, headers: new Headers(), status }); } function staticContextToResponse(context) { let headers = getDocumentHeaders(context, build); if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, { status: context.statusCode, headers }); if (context.errors) { Object.values(context.errors).forEach((err) => { if (!isRouteErrorResponse(err) || err.error) handleError(err); }); context.errors = sanitizeErrors(context.errors, serverMode); } let singleFetchResult; if (context.errors) singleFetchResult = { error: Object.values(context.errors)[0] }; else singleFetchResult = { data: Object.values(context.actionData || {})[0] }; return generateSingleFetchResponse(request, build, serverMode, { result: singleFetchResult, headers, status: context.statusCode }); } } async function singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError) { let routesParam = new URL(request.url).searchParams.get("_routes"); let loadRouteIds = routesParam ? new Set(routesParam.split(",")) : null; try { return handleQueryResult(await staticHandler.query(request, { requestContext: loadContext, filterMatchesToLoad: (m) => !loadRouteIds || loadRouteIds.has(m.route.id), skipLoaderErrorBubbling: true, generateMiddlewareResponse: async (query) => { try { return handleQueryResult(await query(request)); } catch (error) { return handleQueryError(error); } }, normalizePath: (r) => getNormalizedPath(r) })); } catch (error) { return handleQueryError(error); } function handleQueryResult(result) { return isResponse(result) ? result : staticContextToResponse(result); } function handleQueryError(error) { handleError(error); return generateSingleFetchResponse(request, build, serverMode, { result: { error }, headers: new Headers(), status: 500 }); } function staticContextToResponse(context) { let headers = getDocumentHeaders(context, build); if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, { status: context.statusCode, headers }); if (context.errors) { Object.values(context.errors).forEach((err) => { if (!isRouteErrorResponse(err) || err.error) handleError(err); }); context.errors = sanitizeErrors(context.errors, serverMode); } let results = {}; let loadedMatches = new Set(context.matches.filter((m) => loadRouteIds ? loadRouteIds.has(m.route.id) : m.route.loader != null).map((m) => m.route.id)); if (context.errors) for (let [id, error] of Object.entries(context.errors)) results[id] = { error }; for (let [id, data] of Object.entries(context.loaderData)) if (!(id in results) && loadedMatches.has(id)) results[id] = { data }; return generateSingleFetchResponse(request, build, serverMode, { result: results, headers, status: context.statusCode }); } } function generateSingleFetchResponse(request, build, serverMode, { result, headers, status }) { let resultHeaders = new Headers(headers); resultHeaders.set("X-Remix-Response", "yes"); if (SERVER_NO_BODY_STATUS_CODES.has(status)) return new Response(null, { status, headers: resultHeaders }); resultHeaders.set("Content-Type", "text/x-script"); resultHeaders.delete("Content-Length"); return new Response(encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), { status: status || 200, headers: resultHeaders }); } function generateSingleFetchRedirectResponse(redirectResponse, request, build, serverMode) { let redirect = getSingleFetchRedirect(redirectResponse.status, redirectResponse.headers, build.basename); let headers = new Headers(redirectResponse.headers); headers.delete("Location"); headers.set("Content-Type", "text/x-script"); return generateSingleFetchResponse(request, build, serverMode, { result: request.method === "GET" ? { [SingleFetchRedirectSymbol]: redirect } : redirect, headers, status: 202 }); } function getSingleFetchRedirect(status, headers, basename) { let redirect = headers.get("Location"); if (basename) redirect = stripBasename(redirect, basename) || redirect; return { redirect, status, revalidate: headers.has("X-Remix-Revalidate") || headers.has("Set-Cookie"), reload: headers.has("X-Remix-Reload-Document"), replace: headers.has("X-Remix-Replace") }; } function encodeViaTurboStream(data, requestSignal, streamTimeout, serverMode) { let controller = new AbortController(); let timeoutId = setTimeout(() => { controller.abort(/* @__PURE__ */ new Error("Server Timeout")); cleanupCallbacks(); }, typeof streamTimeout === "number" ? streamTimeout : 4950); let abortControllerOnRequestAbort = () => { controller.abort(requestSignal.reason); cleanupCallbacks(); }; requestSignal.addEventListener("abort", abortControllerOnRequestAbort); let cleanupCallbacks = () => { clearTimeout(timeoutId); requestSignal.removeEventListener("abort", abortControllerOnRequestAbort); }; return encode(data, { signal: controller.signal, onComplete: cleanupCallbacks, plugins: [(value) => { if (value instanceof Error) { let { name, message, stack } = serverMode === "production" ? sanitizeError(value, serverMode) : value; return [ "SanitizedError", name, message, stack ]; } if (value instanceof ErrorResponseImpl) { let { data, status, statusText } = value; return [ "ErrorResponse", data, status, statusText ]; } if (value && typeof value === "object" && SingleFetchRedirectSymbol in value) return ["SingleFetchRedirect", value[SingleFetchRedirectSymbol]]; }], postPlugins: [(value) => { if (!value) return; if (typeof value !== "object") return; return ["SingleFetchClassInstance", Object.fromEntries(Object.entries(value))]; }, () => ["SingleFetchFallback"]] }); } //#endregion export { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders };