You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			511 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			511 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | "use strict"; | ||
|  | Object.defineProperty(exports, "__esModule", { | ||
|  |     value: true | ||
|  | }); | ||
|  | Object.defineProperty(exports, "handleAction", { | ||
|  |     enumerable: true, | ||
|  |     get: function() { | ||
|  |         return handleAction; | ||
|  |     } | ||
|  | }); | ||
|  | const _approuterheaders = require("../../client/components/app-router-headers"); | ||
|  | const _notfound = require("../../client/components/not-found"); | ||
|  | const _redirect = require("../../client/components/redirect"); | ||
|  | const _renderresult = /*#__PURE__*/ _interop_require_default(require("../render-result")); | ||
|  | const _flightrenderresult = require("./flight-render-result"); | ||
|  | const _utils = require("../lib/server-ipc/utils"); | ||
|  | const _requestcookies = require("../web/spec-extension/adapters/request-cookies"); | ||
|  | const _constants = require("../../lib/constants"); | ||
|  | const _serveractionrequestmeta = require("../lib/server-action-request-meta"); | ||
|  | const _csrfprotection = require("./csrf-protection"); | ||
|  | const _log = require("../../build/output/log"); | ||
|  | function _interop_require_default(obj) { | ||
|  |     return obj && obj.__esModule ? obj : { | ||
|  |         default: obj | ||
|  |     }; | ||
|  | } | ||
|  | function formDataFromSearchQueryString(query) { | ||
|  |     const searchParams = new URLSearchParams(query); | ||
|  |     const formData = new FormData(); | ||
|  |     for (const [key, value] of searchParams){ | ||
|  |         formData.append(key, value); | ||
|  |     } | ||
|  |     return formData; | ||
|  | } | ||
|  | function nodeHeadersToRecord(headers) { | ||
|  |     const record = {}; | ||
|  |     for (const [key, value] of Object.entries(headers)){ | ||
|  |         if (value !== undefined) { | ||
|  |             record[key] = Array.isArray(value) ? value.join(", ") : `${value}`; | ||
|  |         } | ||
|  |     } | ||
|  |     return record; | ||
|  | } | ||
|  | function getForwardedHeaders(req, res) { | ||
|  |     // Get request headers and cookies
 | ||
|  |     const requestHeaders = req.headers; | ||
|  |     const requestCookies = requestHeaders["cookie"] ?? ""; | ||
|  |     // Get response headers and Set-Cookie header
 | ||
|  |     const responseHeaders = res.getHeaders(); | ||
|  |     const rawSetCookies = responseHeaders["set-cookie"]; | ||
|  |     const setCookies = (Array.isArray(rawSetCookies) ? rawSetCookies : [ | ||
|  |         rawSetCookies | ||
|  |     ]).map((setCookie)=>{ | ||
|  |         // remove the suffixes like 'HttpOnly' and 'SameSite'
 | ||
|  |         const [cookie] = `${setCookie}`.split(";", 1); | ||
|  |         return cookie; | ||
|  |     }); | ||
|  |     // Merge request and response headers
 | ||
|  |     const mergedHeaders = (0, _utils.filterReqHeaders)({ | ||
|  |         ...nodeHeadersToRecord(requestHeaders), | ||
|  |         ...nodeHeadersToRecord(responseHeaders) | ||
|  |     }, _utils.actionsForbiddenHeaders); | ||
|  |     // Merge cookies
 | ||
|  |     const mergedCookies = requestCookies.split("; ").concat(setCookies).join("; "); | ||
|  |     // Update the 'cookie' header with the merged cookies
 | ||
|  |     mergedHeaders["cookie"] = mergedCookies; | ||
|  |     // Remove headers that should not be forwarded
 | ||
|  |     delete mergedHeaders["transfer-encoding"]; | ||
|  |     return new Headers(mergedHeaders); | ||
|  | } | ||
|  | async function addRevalidationHeader(res, { staticGenerationStore, requestStore }) { | ||
|  |     var _staticGenerationStore_revalidatedTags; | ||
|  |     await Promise.all(Object.values(staticGenerationStore.pendingRevalidates || [])); | ||
|  |     // If a tag was revalidated, the client router needs to invalidate all the
 | ||
|  |     // client router cache as they may be stale. And if a path was revalidated, the
 | ||
|  |     // client needs to invalidate all subtrees below that path.
 | ||
|  |     // To keep the header size small, we use a tuple of
 | ||
|  |     // [[revalidatedPaths], isTagRevalidated ? 1 : 0, isCookieRevalidated ? 1 : 0]
 | ||
|  |     // instead of a JSON object.
 | ||
|  |     // TODO-APP: Currently the prefetch cache doesn't have subtree information,
 | ||
|  |     // so we need to invalidate the entire cache if a path was revalidated.
 | ||
|  |     // TODO-APP: Currently paths are treated as tags, so the second element of the tuple
 | ||
|  |     // is always empty.
 | ||
|  |     const isTagRevalidated = ((_staticGenerationStore_revalidatedTags = staticGenerationStore.revalidatedTags) == null ? void 0 : _staticGenerationStore_revalidatedTags.length) ? 1 : 0; | ||
|  |     const isCookieRevalidated = (0, _requestcookies.getModifiedCookieValues)(requestStore.mutableCookies).length ? 1 : 0; | ||
|  |     res.setHeader("x-action-revalidated", JSON.stringify([ | ||
|  |         [], | ||
|  |         isTagRevalidated, | ||
|  |         isCookieRevalidated | ||
|  |     ])); | ||
|  | } | ||
|  | async function createRedirectRenderResult(req, res, redirectUrl, basePath, staticGenerationStore) { | ||
|  |     res.setHeader("x-action-redirect", redirectUrl); | ||
|  |     // if we're redirecting to a relative path, we'll try to stream the response
 | ||
|  |     if (redirectUrl.startsWith("/")) { | ||
|  |         var _staticGenerationStore_incrementalCache; | ||
|  |         const forwardedHeaders = getForwardedHeaders(req, res); | ||
|  |         forwardedHeaders.set(_approuterheaders.RSC_HEADER, "1"); | ||
|  |         // For standalone or the serverful mode, use the internal hostname directly
 | ||
|  |         // other than the headers from the request.
 | ||
|  |         const host = process.env.__NEXT_PRIVATE_HOST || req.headers["host"]; | ||
|  |         const proto = ((_staticGenerationStore_incrementalCache = staticGenerationStore.incrementalCache) == null ? void 0 : _staticGenerationStore_incrementalCache.requestProtocol) || "https"; | ||
|  |         const fetchUrl = new URL(`${proto}://${host}${basePath}${redirectUrl}`); | ||
|  |         if (staticGenerationStore.revalidatedTags) { | ||
|  |             var _staticGenerationStore_incrementalCache_prerenderManifest_preview, _staticGenerationStore_incrementalCache_prerenderManifest, _staticGenerationStore_incrementalCache1; | ||
|  |             forwardedHeaders.set(_constants.NEXT_CACHE_REVALIDATED_TAGS_HEADER, staticGenerationStore.revalidatedTags.join(",")); | ||
|  |             forwardedHeaders.set(_constants.NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER, ((_staticGenerationStore_incrementalCache1 = staticGenerationStore.incrementalCache) == null ? void 0 : (_staticGenerationStore_incrementalCache_prerenderManifest = _staticGenerationStore_incrementalCache1.prerenderManifest) == null ? void 0 : (_staticGenerationStore_incrementalCache_prerenderManifest_preview = _staticGenerationStore_incrementalCache_prerenderManifest.preview) == null ? void 0 : _staticGenerationStore_incrementalCache_prerenderManifest_preview.previewModeId) || ""); | ||
|  |         } | ||
|  |         // Ensures that when the path was revalidated we don't return a partial response on redirects
 | ||
|  |         // if (staticGenerationStore.pathWasRevalidated) {
 | ||
|  |         forwardedHeaders.delete("next-router-state-tree"); | ||
|  |         // }
 | ||
|  |         try { | ||
|  |             const headResponse = await fetch(fetchUrl, { | ||
|  |                 method: "HEAD", | ||
|  |                 headers: forwardedHeaders, | ||
|  |                 next: { | ||
|  |                     // @ts-ignore
 | ||
|  |                     internal: 1 | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (headResponse.headers.get("content-type") === _approuterheaders.RSC_CONTENT_TYPE_HEADER) { | ||
|  |                 const response = await fetch(fetchUrl, { | ||
|  |                     method: "GET", | ||
|  |                     headers: forwardedHeaders, | ||
|  |                     next: { | ||
|  |                         // @ts-ignore
 | ||
|  |                         internal: 1 | ||
|  |                     } | ||
|  |                 }); | ||
|  |                 // copy the headers from the redirect response to the response we're sending
 | ||
|  |                 for (const [key, value] of response.headers){ | ||
|  |                     if (!_utils.actionsForbiddenHeaders.includes(key)) { | ||
|  |                         res.setHeader(key, value); | ||
|  |                     } | ||
|  |                 } | ||
|  |                 return new _flightrenderresult.FlightRenderResult(response.body); | ||
|  |             } | ||
|  |         } catch (err) { | ||
|  |             // we couldn't stream the redirect response, so we'll just do a normal redirect
 | ||
|  |             console.error(`failed to get redirect response`, err); | ||
|  |         } | ||
|  |     } | ||
|  |     return _renderresult.default.fromStatic("{}"); | ||
|  | } | ||
|  | var // Used to compare Host header and Origin header.
 | ||
|  | HostType; | ||
|  | (function(HostType) { | ||
|  |     HostType["XForwardedHost"] = "x-forwarded-host"; | ||
|  |     HostType["Host"] = "host"; | ||
|  | })(HostType || (HostType = {})); | ||
|  | /** | ||
|  |  * Ensures the value of the header can't create long logs. | ||
|  |  */ function limitUntrustedHeaderValueForLogs(value) { | ||
|  |     return value.length > 100 ? value.slice(0, 100) + "..." : value; | ||
|  | } | ||
|  | async function handleAction({ req, res, ComponentMod, serverModuleMap, generateFlight, staticGenerationStore, requestStore, serverActions, ctx }) { | ||
|  |     const contentType = req.headers["content-type"]; | ||
|  |     const { actionId, isURLEncodedAction, isMultipartAction, isFetchAction } = (0, _serveractionrequestmeta.getServerActionRequestMetadata)(req); | ||
|  |     // If it's not a Server Action, skip handling.
 | ||
|  |     if (!(0, _serveractionrequestmeta.getIsServerAction)(req)) { | ||
|  |         return; | ||
|  |     } | ||
|  |     if (staticGenerationStore.isStaticGeneration) { | ||
|  |         throw new Error("Invariant: server actions can't be handled during static rendering"); | ||
|  |     } | ||
|  |     // When running actions the default is no-store, you can still `cache: 'force-cache'`
 | ||
|  |     staticGenerationStore.fetchCache = "default-no-store"; | ||
|  |     const originDomain = typeof req.headers["origin"] === "string" ? new URL(req.headers["origin"]).host : undefined; | ||
|  |     const forwardedHostHeader = req.headers["x-forwarded-host"]; | ||
|  |     const hostHeader = req.headers["host"]; | ||
|  |     const host = forwardedHostHeader ? { | ||
|  |         type: "x-forwarded-host", | ||
|  |         value: forwardedHostHeader | ||
|  |     } : hostHeader ? { | ||
|  |         type: "host", | ||
|  |         value: hostHeader | ||
|  |     } : undefined; | ||
|  |     let warning = undefined; | ||
|  |     function warnBadServerActionRequest() { | ||
|  |         if (warning) { | ||
|  |             (0, _log.warn)(warning); | ||
|  |         } | ||
|  |     } | ||
|  |     // This is to prevent CSRF attacks. If `x-forwarded-host` is set, we need to
 | ||
|  |     // ensure that the request is coming from the same host.
 | ||
|  |     if (!originDomain) { | ||
|  |         // This might be an old browser that doesn't send `host` header. We ignore
 | ||
|  |         // this case.
 | ||
|  |         warning = "Missing `origin` header from a forwarded Server Actions request."; | ||
|  |     } else if (!host || originDomain !== host.value) { | ||
|  |         // If the customer sets a list of allowed origins, we'll allow the request.
 | ||
|  |         // These are considered safe but might be different from forwarded host set
 | ||
|  |         // by the infra (i.e. reverse proxies).
 | ||
|  |         if ((0, _csrfprotection.isCsrfOriginAllowed)(originDomain, serverActions == null ? void 0 : serverActions.allowedOrigins)) { | ||
|  |         // Ignore it
 | ||
|  |         } else { | ||
|  |             if (host) { | ||
|  |                 // This seems to be an CSRF attack. We should not proceed the action.
 | ||
|  |                 console.error(`\`${host.type}\` header with value \`${limitUntrustedHeaderValueForLogs(host.value)}\` does not match \`origin\` header with value \`${limitUntrustedHeaderValueForLogs(originDomain)}\` from a forwarded Server Actions request. Aborting the action.`); | ||
|  |             } else { | ||
|  |                 // This is an attack. We should not proceed the action.
 | ||
|  |                 console.error(`\`x-forwarded-host\` or \`host\` headers are not provided. One of these is needed to compare the \`origin\` header from a forwarded Server Actions request. Aborting the action.`); | ||
|  |             } | ||
|  |             const error = new Error("Invalid Server Actions request."); | ||
|  |             if (isFetchAction) { | ||
|  |                 res.statusCode = 500; | ||
|  |                 await Promise.all(Object.values(staticGenerationStore.pendingRevalidates || [])); | ||
|  |                 const promise = Promise.reject(error); | ||
|  |                 try { | ||
|  |                     // we need to await the promise to trigger the rejection early
 | ||
|  |                     // so that it's already handled by the time we call
 | ||
|  |                     // the RSC runtime. Otherwise, it will throw an unhandled
 | ||
|  |                     // promise rejection error in the renderer.
 | ||
|  |                     await promise; | ||
|  |                 } catch  { | ||
|  |                 // swallow error, it's gonna be handled on the client
 | ||
|  |                 } | ||
|  |                 return { | ||
|  |                     type: "done", | ||
|  |                     result: await generateFlight(ctx, { | ||
|  |                         actionResult: promise, | ||
|  |                         // if the page was not revalidated, we can skip the rendering the flight tree
 | ||
|  |                         skipFlight: !staticGenerationStore.pathWasRevalidated | ||
|  |                     }) | ||
|  |                 }; | ||
|  |             } | ||
|  |             throw error; | ||
|  |         } | ||
|  |     } | ||
|  |     // ensure we avoid caching server actions unexpectedly
 | ||
|  |     res.setHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); | ||
|  |     let bound = []; | ||
|  |     const { actionAsyncStorage } = ComponentMod; | ||
|  |     let actionResult; | ||
|  |     let formState; | ||
|  |     let actionModId; | ||
|  |     try { | ||
|  |         await actionAsyncStorage.run({ | ||
|  |             isAction: true | ||
|  |         }, async ()=>{ | ||
|  |             if (process.env.NEXT_RUNTIME === "edge") { | ||
|  |                 // Use react-server-dom-webpack/server.edge
 | ||
|  |                 const { decodeReply, decodeAction, decodeFormState } = ComponentMod; | ||
|  |                 const webRequest = req; | ||
|  |                 if (!webRequest.body) { | ||
|  |                     throw new Error("invariant: Missing request body."); | ||
|  |                 } | ||
|  |                 if (isMultipartAction) { | ||
|  |                     // TODO-APP: Add streaming support
 | ||
|  |                     const formData = await webRequest.request.formData(); | ||
|  |                     if (isFetchAction) { | ||
|  |                         bound = await decodeReply(formData, serverModuleMap); | ||
|  |                     } else { | ||
|  |                         const action = await decodeAction(formData, serverModuleMap); | ||
|  |                         if (typeof action === "function") { | ||
|  |                             // Only warn if it's a server action, otherwise skip for other post requests
 | ||
|  |                             warnBadServerActionRequest(); | ||
|  |                             const actionReturnedState = await action(); | ||
|  |                             formState = decodeFormState(actionReturnedState, formData); | ||
|  |                         } | ||
|  |                         // Skip the fetch path
 | ||
|  |                         return; | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     try { | ||
|  |                         actionModId = getActionModIdOrError(actionId, serverModuleMap); | ||
|  |                     } catch (err) { | ||
|  |                         console.error(err); | ||
|  |                         return { | ||
|  |                             type: "not-found" | ||
|  |                         }; | ||
|  |                     } | ||
|  |                     let actionData = ""; | ||
|  |                     const reader = webRequest.body.getReader(); | ||
|  |                     while(true){ | ||
|  |                         const { done, value } = await reader.read(); | ||
|  |                         if (done) { | ||
|  |                             break; | ||
|  |                         } | ||
|  |                         actionData += new TextDecoder().decode(value); | ||
|  |                     } | ||
|  |                     if (isURLEncodedAction) { | ||
|  |                         const formData = formDataFromSearchQueryString(actionData); | ||
|  |                         bound = await decodeReply(formData, serverModuleMap); | ||
|  |                     } else { | ||
|  |                         bound = await decodeReply(actionData, serverModuleMap); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } else { | ||
|  |                 // Use react-server-dom-webpack/server.node which supports streaming
 | ||
|  |                 const { decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState } = require(`./react-server.node`); | ||
|  |                 if (isMultipartAction) { | ||
|  |                     if (isFetchAction) { | ||
|  |                         const busboy = require("busboy"); | ||
|  |                         const bb = busboy({ | ||
|  |                             headers: req.headers | ||
|  |                         }); | ||
|  |                         req.pipe(bb); | ||
|  |                         bound = await decodeReplyFromBusboy(bb, serverModuleMap); | ||
|  |                     } else { | ||
|  |                         // Convert the Node.js readable stream to a Web Stream.
 | ||
|  |                         const readableStream = new ReadableStream({ | ||
|  |                             start (controller) { | ||
|  |                                 req.on("data", (chunk)=>{ | ||
|  |                                     controller.enqueue(new Uint8Array(chunk)); | ||
|  |                                 }); | ||
|  |                                 req.on("end", ()=>{ | ||
|  |                                     controller.close(); | ||
|  |                                 }); | ||
|  |                                 req.on("error", (err)=>{ | ||
|  |                                     controller.error(err); | ||
|  |                                 }); | ||
|  |                             } | ||
|  |                         }); | ||
|  |                         // React doesn't yet publish a busboy version of decodeAction
 | ||
|  |                         // so we polyfill the parsing of FormData.
 | ||
|  |                         const fakeRequest = new Request("http://localhost", { | ||
|  |                             method: "POST", | ||
|  |                             // @ts-expect-error
 | ||
|  |                             headers: { | ||
|  |                                 "Content-Type": contentType | ||
|  |                             }, | ||
|  |                             body: readableStream, | ||
|  |                             duplex: "half" | ||
|  |                         }); | ||
|  |                         const formData = await fakeRequest.formData(); | ||
|  |                         const action = await decodeAction(formData, serverModuleMap); | ||
|  |                         if (typeof action === "function") { | ||
|  |                             // Only warn if it's a server action, otherwise skip for other post requests
 | ||
|  |                             warnBadServerActionRequest(); | ||
|  |                             const actionReturnedState = await action(); | ||
|  |                             formState = await decodeFormState(actionReturnedState, formData); | ||
|  |                         } | ||
|  |                         // Skip the fetch path
 | ||
|  |                         return; | ||
|  |                     } | ||
|  |                 } else { | ||
|  |                     try { | ||
|  |                         actionModId = getActionModIdOrError(actionId, serverModuleMap); | ||
|  |                     } catch (err) { | ||
|  |                         console.error(err); | ||
|  |                         return { | ||
|  |                             type: "not-found" | ||
|  |                         }; | ||
|  |                     } | ||
|  |                     const chunks = []; | ||
|  |                     for await (const chunk of req){ | ||
|  |                         chunks.push(Buffer.from(chunk)); | ||
|  |                     } | ||
|  |                     const actionData = Buffer.concat(chunks).toString("utf-8"); | ||
|  |                     const readableLimit = (serverActions == null ? void 0 : serverActions.bodySizeLimit) ?? "1 MB"; | ||
|  |                     const limit = require("next/dist/compiled/bytes").parse(readableLimit); | ||
|  |                     if (actionData.length > limit) { | ||
|  |                         const { ApiError } = require("../api-utils"); | ||
|  |                         throw new ApiError(413, `Body exceeded ${readableLimit} limit.
 | ||
|  | To configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/api-reference/functions/server-actions#size-limitation`);
 | ||
|  |                     } | ||
|  |                     if (isURLEncodedAction) { | ||
|  |                         const formData = formDataFromSearchQueryString(actionData); | ||
|  |                         bound = await decodeReply(formData, serverModuleMap); | ||
|  |                     } else { | ||
|  |                         bound = await decodeReply(actionData, serverModuleMap); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |             // actions.js
 | ||
|  |             // app/page.js
 | ||
|  |             //   action worker1
 | ||
|  |             //     appRender1
 | ||
|  |             // app/foo/page.js
 | ||
|  |             //   action worker2
 | ||
|  |             //     appRender
 | ||
|  |             // / -> fire action -> POST / -> appRender1 -> modId for the action file
 | ||
|  |             // /foo -> fire action -> POST /foo -> appRender2 -> modId for the action file
 | ||
|  |             try { | ||
|  |                 actionModId = actionModId ?? getActionModIdOrError(actionId, serverModuleMap); | ||
|  |             } catch (err) { | ||
|  |                 console.error(err); | ||
|  |                 return { | ||
|  |                     type: "not-found" | ||
|  |                 }; | ||
|  |             } | ||
|  |             const actionHandler = (await ComponentMod.__next_app__.require(actionModId))[// `actionId` must exist if we got here, as otherwise we would have thrown an error above
 | ||
|  |             actionId]; | ||
|  |             const returnVal = await actionHandler.apply(null, bound); | ||
|  |             // For form actions, we need to continue rendering the page.
 | ||
|  |             if (isFetchAction) { | ||
|  |                 await addRevalidationHeader(res, { | ||
|  |                     staticGenerationStore, | ||
|  |                     requestStore | ||
|  |                 }); | ||
|  |                 actionResult = await generateFlight(ctx, { | ||
|  |                     actionResult: Promise.resolve(returnVal), | ||
|  |                     // if the page was not revalidated, we can skip the rendering the flight tree
 | ||
|  |                     skipFlight: !staticGenerationStore.pathWasRevalidated | ||
|  |                 }); | ||
|  |             } | ||
|  |         }); | ||
|  |         return { | ||
|  |             type: "done", | ||
|  |             result: actionResult, | ||
|  |             formState | ||
|  |         }; | ||
|  |     } catch (err) { | ||
|  |         if ((0, _redirect.isRedirectError)(err)) { | ||
|  |             const redirectUrl = (0, _redirect.getURLFromRedirectError)(err); | ||
|  |             const statusCode = (0, _redirect.getRedirectStatusCodeFromError)(err); | ||
|  |             await addRevalidationHeader(res, { | ||
|  |                 staticGenerationStore, | ||
|  |                 requestStore | ||
|  |             }); | ||
|  |             // if it's a fetch action, we'll set the status code for logging/debugging purposes
 | ||
|  |             // but we won't set a Location header, as the redirect will be handled by the client router
 | ||
|  |             res.statusCode = statusCode; | ||
|  |             if (isFetchAction) { | ||
|  |                 return { | ||
|  |                     type: "done", | ||
|  |                     result: await createRedirectRenderResult(req, res, redirectUrl, ctx.renderOpts.basePath, staticGenerationStore) | ||
|  |                 }; | ||
|  |             } | ||
|  |             if (err.mutableCookies) { | ||
|  |                 const headers = new Headers(); | ||
|  |                 // If there were mutable cookies set, we need to set them on the
 | ||
|  |                 // response.
 | ||
|  |                 if ((0, _requestcookies.appendMutableCookies)(headers, err.mutableCookies)) { | ||
|  |                     res.setHeader("set-cookie", Array.from(headers.values())); | ||
|  |                 } | ||
|  |             } | ||
|  |             res.setHeader("Location", redirectUrl); | ||
|  |             return { | ||
|  |                 type: "done", | ||
|  |                 result: _renderresult.default.fromStatic("") | ||
|  |             }; | ||
|  |         } else if ((0, _notfound.isNotFoundError)(err)) { | ||
|  |             res.statusCode = 404; | ||
|  |             await addRevalidationHeader(res, { | ||
|  |                 staticGenerationStore, | ||
|  |                 requestStore | ||
|  |             }); | ||
|  |             if (isFetchAction) { | ||
|  |                 const promise = Promise.reject(err); | ||
|  |                 try { | ||
|  |                     // we need to await the promise to trigger the rejection early
 | ||
|  |                     // so that it's already handled by the time we call
 | ||
|  |                     // the RSC runtime. Otherwise, it will throw an unhandled
 | ||
|  |                     // promise rejection error in the renderer.
 | ||
|  |                     await promise; | ||
|  |                 } catch  { | ||
|  |                 // swallow error, it's gonna be handled on the client
 | ||
|  |                 } | ||
|  |                 return { | ||
|  |                     type: "done", | ||
|  |                     result: await generateFlight(ctx, { | ||
|  |                         skipFlight: false, | ||
|  |                         actionResult: promise, | ||
|  |                         asNotFound: true | ||
|  |                     }) | ||
|  |                 }; | ||
|  |             } | ||
|  |             return { | ||
|  |                 type: "not-found" | ||
|  |             }; | ||
|  |         } | ||
|  |         if (isFetchAction) { | ||
|  |             res.statusCode = 500; | ||
|  |             await Promise.all(Object.values(staticGenerationStore.pendingRevalidates || [])); | ||
|  |             const promise = Promise.reject(err); | ||
|  |             try { | ||
|  |                 // we need to await the promise to trigger the rejection early
 | ||
|  |                 // so that it's already handled by the time we call
 | ||
|  |                 // the RSC runtime. Otherwise, it will throw an unhandled
 | ||
|  |                 // promise rejection error in the renderer.
 | ||
|  |                 await promise; | ||
|  |             } catch  { | ||
|  |             // swallow error, it's gonna be handled on the client
 | ||
|  |             } | ||
|  |             return { | ||
|  |                 type: "done", | ||
|  |                 result: await generateFlight(ctx, { | ||
|  |                     actionResult: promise, | ||
|  |                     // if the page was not revalidated, we can skip the rendering the flight tree
 | ||
|  |                     skipFlight: !staticGenerationStore.pathWasRevalidated | ||
|  |                 }) | ||
|  |             }; | ||
|  |         } | ||
|  |         throw err; | ||
|  |     } | ||
|  | } | ||
|  | /** | ||
|  |  * Attempts to find the module ID for the action from the module map. When this fails, it could be a deployment skew where | ||
|  |  * the action came from a different deployment. It could also simply be an invalid POST request that is not a server action. | ||
|  |  * In either case, we'll throw an error to be handled by the caller. | ||
|  |  */ function getActionModIdOrError(actionId, serverModuleMap) { | ||
|  |     try { | ||
|  |         var _serverModuleMap_actionId; | ||
|  |         // if we're missing the action ID header, we can't do any further processing
 | ||
|  |         if (!actionId) { | ||
|  |             throw new Error("Invariant: Missing 'next-action' header."); | ||
|  |         } | ||
|  |         const actionModId = serverModuleMap == null ? void 0 : (_serverModuleMap_actionId = serverModuleMap[actionId]) == null ? void 0 : _serverModuleMap_actionId.id; | ||
|  |         if (!actionModId) { | ||
|  |             throw new Error("Invariant: Couldn't find action module ID from module map."); | ||
|  |         } | ||
|  |         return actionModId; | ||
|  |     } catch (err) { | ||
|  |         throw new Error(`Failed to find Server Action "${actionId}". This request might be from an older or newer deployment. ${err instanceof Error ? `Original error: ${err.message}` : ""}`); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | //# sourceMappingURL=action-handler.js.map
 |