UNPKG

8.04 kBJavaScriptView Raw
1/**
2 * react-router v8.0.0
3 *
4 * Copyright (c) Remix Software Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE.md file in the root directory of this source tree.
8 *
9 * @license MIT
10 */
11import { ErrorResponseImpl, isRouteErrorResponse, stripBasename } from "../router/utils.js";
12import { isRedirectStatusCode, isResponse } from "../router/router.js";
13import { encode } from "../../vendor/turbo-stream-v2/turbo-stream.js";
14import { NO_BODY_STATUS_CODES, SingleFetchRedirectSymbol } from "../dom/ssr/single-fetch.js";
15import "./mode.js";
16import { sanitizeError, sanitizeErrors } from "./errors.js";
17import { getDocumentHeaders } from "./headers.js";
18import { throwIfPotentialCSRFAttack } from "../actions.js";
19import { getNormalizedPath } from "./urls.js";
20//#region lib/server-runtime/single-fetch.ts
21const SERVER_NO_BODY_STATUS_CODES = new Set([...NO_BODY_STATUS_CODES, 304]);
22async function singleFetchAction(build, serverMode, staticHandler, request, loadContext, handleError) {
23 try {
24 try {
25 throwIfPotentialCSRFAttack(request, Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []);
26 } catch (e) {
27 return handleQueryError(/* @__PURE__ */ new Error("Bad Request"), 400);
28 }
29 return handleQueryResult(await staticHandler.query(request, {
30 requestContext: loadContext,
31 skipLoaderErrorBubbling: true,
32 skipRevalidation: true,
33 generateMiddlewareResponse: async (query) => {
34 try {
35 return handleQueryResult(await query(request));
36 } catch (error) {
37 return handleQueryError(error);
38 }
39 },
40 normalizePath: (r) => getNormalizedPath(r)
41 }));
42 } catch (error) {
43 return handleQueryError(error);
44 }
45 function handleQueryResult(result) {
46 return isResponse(result) ? result : staticContextToResponse(result);
47 }
48 function handleQueryError(error, status = 500) {
49 handleError(error);
50 return generateSingleFetchResponse(request, build, serverMode, {
51 result: { error },
52 headers: new Headers(),
53 status
54 });
55 }
56 function staticContextToResponse(context) {
57 let headers = getDocumentHeaders(context, build);
58 if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, {
59 status: context.statusCode,
60 headers
61 });
62 if (context.errors) {
63 Object.values(context.errors).forEach((err) => {
64 if (!isRouteErrorResponse(err) || err.error) handleError(err);
65 });
66 context.errors = sanitizeErrors(context.errors, serverMode);
67 }
68 let singleFetchResult;
69 if (context.errors) singleFetchResult = { error: Object.values(context.errors)[0] };
70 else singleFetchResult = { data: Object.values(context.actionData || {})[0] };
71 return generateSingleFetchResponse(request, build, serverMode, {
72 result: singleFetchResult,
73 headers,
74 status: context.statusCode
75 });
76 }
77}
78async function singleFetchLoaders(build, serverMode, staticHandler, request, loadContext, handleError) {
79 let routesParam = new URL(request.url).searchParams.get("_routes");
80 let loadRouteIds = routesParam ? new Set(routesParam.split(",")) : null;
81 try {
82 return handleQueryResult(await staticHandler.query(request, {
83 requestContext: loadContext,
84 filterMatchesToLoad: (m) => !loadRouteIds || loadRouteIds.has(m.route.id),
85 skipLoaderErrorBubbling: true,
86 generateMiddlewareResponse: async (query) => {
87 try {
88 return handleQueryResult(await query(request));
89 } catch (error) {
90 return handleQueryError(error);
91 }
92 },
93 normalizePath: (r) => getNormalizedPath(r)
94 }));
95 } catch (error) {
96 return handleQueryError(error);
97 }
98 function handleQueryResult(result) {
99 return isResponse(result) ? result : staticContextToResponse(result);
100 }
101 function handleQueryError(error) {
102 handleError(error);
103 return generateSingleFetchResponse(request, build, serverMode, {
104 result: { error },
105 headers: new Headers(),
106 status: 500
107 });
108 }
109 function staticContextToResponse(context) {
110 let headers = getDocumentHeaders(context, build);
111 if (isRedirectStatusCode(context.statusCode) && headers.has("Location")) return new Response(null, {
112 status: context.statusCode,
113 headers
114 });
115 if (context.errors) {
116 Object.values(context.errors).forEach((err) => {
117 if (!isRouteErrorResponse(err) || err.error) handleError(err);
118 });
119 context.errors = sanitizeErrors(context.errors, serverMode);
120 }
121 let results = {};
122 let loadedMatches = new Set(context.matches.filter((m) => loadRouteIds ? loadRouteIds.has(m.route.id) : m.route.loader != null).map((m) => m.route.id));
123 if (context.errors) for (let [id, error] of Object.entries(context.errors)) results[id] = { error };
124 for (let [id, data] of Object.entries(context.loaderData)) if (!(id in results) && loadedMatches.has(id)) results[id] = { data };
125 return generateSingleFetchResponse(request, build, serverMode, {
126 result: results,
127 headers,
128 status: context.statusCode
129 });
130 }
131}
132function generateSingleFetchResponse(request, build, serverMode, { result, headers, status }) {
133 let resultHeaders = new Headers(headers);
134 resultHeaders.set("X-Remix-Response", "yes");
135 if (SERVER_NO_BODY_STATUS_CODES.has(status)) return new Response(null, {
136 status,
137 headers: resultHeaders
138 });
139 resultHeaders.set("Content-Type", "text/x-script");
140 resultHeaders.delete("Content-Length");
141 return new Response(encodeViaTurboStream(result, request.signal, build.entry.module.streamTimeout, serverMode), {
142 status: status || 200,
143 headers: resultHeaders
144 });
145}
146function generateSingleFetchRedirectResponse(redirectResponse, request, build, serverMode) {
147 let redirect = getSingleFetchRedirect(redirectResponse.status, redirectResponse.headers, build.basename);
148 let headers = new Headers(redirectResponse.headers);
149 headers.delete("Location");
150 headers.set("Content-Type", "text/x-script");
151 return generateSingleFetchResponse(request, build, serverMode, {
152 result: request.method === "GET" ? { [SingleFetchRedirectSymbol]: redirect } : redirect,
153 headers,
154 status: 202
155 });
156}
157function getSingleFetchRedirect(status, headers, basename) {
158 let redirect = headers.get("Location");
159 if (basename) redirect = stripBasename(redirect, basename) || redirect;
160 return {
161 redirect,
162 status,
163 revalidate: headers.has("X-Remix-Revalidate") || headers.has("Set-Cookie"),
164 reload: headers.has("X-Remix-Reload-Document"),
165 replace: headers.has("X-Remix-Replace")
166 };
167}
168function encodeViaTurboStream(data, requestSignal, streamTimeout, serverMode) {
169 let controller = new AbortController();
170 let timeoutId = setTimeout(() => {
171 controller.abort(/* @__PURE__ */ new Error("Server Timeout"));
172 cleanupCallbacks();
173 }, typeof streamTimeout === "number" ? streamTimeout : 4950);
174 let abortControllerOnRequestAbort = () => {
175 controller.abort(requestSignal.reason);
176 cleanupCallbacks();
177 };
178 requestSignal.addEventListener("abort", abortControllerOnRequestAbort);
179 let cleanupCallbacks = () => {
180 clearTimeout(timeoutId);
181 requestSignal.removeEventListener("abort", abortControllerOnRequestAbort);
182 };
183 return encode(data, {
184 signal: controller.signal,
185 onComplete: cleanupCallbacks,
186 plugins: [(value) => {
187 if (value instanceof Error) {
188 let { name, message, stack } = serverMode === "production" ? sanitizeError(value, serverMode) : value;
189 return [
190 "SanitizedError",
191 name,
192 message,
193 stack
194 ];
195 }
196 if (value instanceof ErrorResponseImpl) {
197 let { data, status, statusText } = value;
198 return [
199 "ErrorResponse",
200 data,
201 status,
202 statusText
203 ];
204 }
205 if (value && typeof value === "object" && SingleFetchRedirectSymbol in value) return ["SingleFetchRedirect", value[SingleFetchRedirectSymbol]];
206 }],
207 postPlugins: [(value) => {
208 if (!value) return;
209 if (typeof value !== "object") return;
210 return ["SingleFetchClassInstance", Object.fromEntries(Object.entries(value))];
211 }, () => ["SingleFetchFallback"]]
212 });
213}
214//#endregion
215export { SERVER_NO_BODY_STATUS_CODES, encodeViaTurboStream, generateSingleFetchRedirectResponse, singleFetchAction, singleFetchLoaders };