next
Version:
The React Framework
754 lines (753 loc) • 38.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
handleAction: null,
parseHostHeader: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
handleAction: function() {
return handleAction;
},
parseHostHeader: function() {
return parseHostHeader;
}
});
const _approuterheaders = require("../../client/components/app-router-headers");
const _httpaccessfallback = require("../../client/components/http-access-fallback/http-access-fallback");
const _redirect = require("../../client/components/redirect");
const _redirecterror = require("../../client/components/redirect-error");
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");
const _cookies = require("../web/spec-extension/cookies");
const _headers = require("../web/spec-extension/adapters/headers");
const _utils1 = require("../web/utils");
const _actionutils = require("./action-utils");
const _helpers = require("../base-http/helpers");
const _redirectstatuscode = require("../../client/components/redirect-status-code");
const _requeststore = require("../async-storage/request-store");
const _workunitasyncstorageexternal = require("../app-render/work-unit-async-storage.external");
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 = new _cookies.RequestCookies(_headers.HeadersAdapter.from(requestHeaders));
// Get response headers and cookies
const responseHeaders = res.getHeaders();
const responseCookies = new _cookies.ResponseCookies((0, _utils1.fromNodeOutgoingHttpHeaders)(responseHeaders));
// Merge request and response headers
const mergedHeaders = (0, _utils.filterReqHeaders)({
...nodeHeadersToRecord(requestHeaders),
...nodeHeadersToRecord(responseHeaders)
}, _utils.actionsForbiddenHeaders);
// Merge cookies into requestCookies, so responseCookies always take precedence
// and overwrite/delete those from requestCookies.
responseCookies.getAll().forEach((cookie)=>{
if (typeof cookie.value === 'undefined') {
requestCookies.delete(cookie.name);
} else {
requestCookies.set(cookie);
}
});
// Update the 'cookie' header with the merged cookies
mergedHeaders['cookie'] = requestCookies.toString();
// Remove headers that should not be forwarded
delete mergedHeaders['transfer-encoding'];
return new Headers(mergedHeaders);
}
async function addRevalidationHeader(res, { workStore, requestStore }) {
var _workStore_incrementalCache, _workStore_revalidatedTags;
await Promise.all([
(_workStore_incrementalCache = workStore.incrementalCache) == null ? void 0 : _workStore_incrementalCache.revalidateTag(workStore.revalidatedTags || []),
...Object.values(workStore.pendingRevalidates || {}),
...workStore.pendingRevalidateWrites || []
]);
// 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 = ((_workStore_revalidatedTags = workStore.revalidatedTags) == null ? void 0 : _workStore_revalidatedTags.length) ? 1 : 0;
const isCookieRevalidated = (0, _requestcookies.getModifiedCookieValues)(requestStore.mutableCookies).length ? 1 : 0;
res.setHeader('x-action-revalidated', JSON.stringify([
[],
isTagRevalidated,
isCookieRevalidated
]));
}
/**
* Forwards a server action request to a separate worker. Used when the requested action is not available in the current worker.
*/ async function createForwardedActionResponse(req, res, host, workerPathname, basePath, workStore) {
var _workStore_incrementalCache;
if (!host) {
throw Object.defineProperty(new Error('Invariant: Missing `host` header from a forwarded Server Actions request.'), "__NEXT_ERROR_CODE", {
value: "E226",
enumerable: false,
configurable: true
});
}
const forwardedHeaders = getForwardedHeaders(req, res);
// indicate that this action request was forwarded from another worker
// we use this to skip rendering the flight tree so that we don't update the UI
// with the response from the forwarded worker
forwardedHeaders.set('x-action-forwarded', '1');
const proto = ((_workStore_incrementalCache = workStore.incrementalCache) == null ? void 0 : _workStore_incrementalCache.requestProtocol) || 'https';
// For standalone or the serverful mode, use the internal origin directly
// other than the host headers from the request.
const origin = process.env.__NEXT_PRIVATE_ORIGIN || `${proto}://${host.value}`;
const fetchUrl = new URL(`${origin}${basePath}${workerPathname}`);
try {
var _response_headers_get;
let body;
if (// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME === 'edge' && (0, _helpers.isWebNextRequest)(req)) {
if (!req.body) {
throw Object.defineProperty(new Error('Invariant: missing request body.'), "__NEXT_ERROR_CODE", {
value: "E333",
enumerable: false,
configurable: true
});
}
body = req.body;
} else if (// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' && (0, _helpers.isNodeNextRequest)(req)) {
body = req.stream();
} else {
throw Object.defineProperty(new Error('Invariant: Unknown request type.'), "__NEXT_ERROR_CODE", {
value: "E114",
enumerable: false,
configurable: true
});
}
// Forward the request to the new worker
const response = await fetch(fetchUrl, {
method: 'POST',
body,
duplex: 'half',
headers: forwardedHeaders,
redirect: 'manual',
next: {
// @ts-ignore
internal: 1
}
});
if ((_response_headers_get = response.headers.get('content-type')) == null ? void 0 : _response_headers_get.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER)) {
// 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);
} else {
var // Since we aren't consuming the response body, we cancel it to avoid memory leaks
_response_body;
(_response_body = response.body) == null ? void 0 : _response_body.cancel();
}
} catch (err) {
// we couldn't stream the forwarded response, so we'll just return an empty response
console.error(`failed to forward action response`, err);
}
return _renderresult.default.fromStatic('{}');
}
/**
* Returns the parsed redirect URL if we deem that it is hosted by us.
*
* We handle both relative and absolute redirect URLs.
*
* In case the redirect URL is not relative to the application we return `null`.
*/ function getAppRelativeRedirectUrl(basePath, host, redirectUrl) {
if (redirectUrl.startsWith('/') || redirectUrl.startsWith('.')) {
// Make sure we are appending the basePath to relative URLS
return new URL(`${basePath}${redirectUrl}`, 'http://n');
}
const parsedRedirectUrl = new URL(redirectUrl);
if ((host == null ? void 0 : host.value) !== parsedRedirectUrl.host) {
return null;
}
// At this point the hosts are the same, just confirm we
// are routing to a path underneath the `basePath`
return parsedRedirectUrl.pathname.startsWith(basePath) ? parsedRedirectUrl : null;
}
async function createRedirectRenderResult(req, res, originalHost, redirectUrl, redirectType, basePath, workStore) {
res.setHeader('x-action-redirect', `${redirectUrl};${redirectType}`);
// If we're redirecting to another route of this Next.js application, we'll
// try to stream the response from the other worker path. When that works,
// we can save an extra roundtrip and avoid a full page reload.
// When the redirect URL starts with a `/` or is to the same host, under the
// `basePath` we treat it as an app-relative redirect;
const appRelativeRedirectUrl = getAppRelativeRedirectUrl(basePath, originalHost, redirectUrl);
if (appRelativeRedirectUrl) {
var _workStore_incrementalCache;
if (!originalHost) {
throw Object.defineProperty(new Error('Invariant: Missing `host` header from a forwarded Server Actions request.'), "__NEXT_ERROR_CODE", {
value: "E226",
enumerable: false,
configurable: true
});
}
const forwardedHeaders = getForwardedHeaders(req, res);
forwardedHeaders.set(_approuterheaders.RSC_HEADER, '1');
const proto = ((_workStore_incrementalCache = workStore.incrementalCache) == null ? void 0 : _workStore_incrementalCache.requestProtocol) || 'https';
// For standalone or the serverful mode, use the internal origin directly
// other than the host headers from the request.
const origin = process.env.__NEXT_PRIVATE_ORIGIN || `${proto}://${originalHost.value}`;
const fetchUrl = new URL(`${origin}${appRelativeRedirectUrl.pathname}${appRelativeRedirectUrl.search}`);
if (workStore.revalidatedTags) {
var _workStore_incrementalCache_prerenderManifest_preview, _workStore_incrementalCache_prerenderManifest, _workStore_incrementalCache1;
forwardedHeaders.set(_constants.NEXT_CACHE_REVALIDATED_TAGS_HEADER, workStore.revalidatedTags.join(','));
forwardedHeaders.set(_constants.NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER, ((_workStore_incrementalCache1 = workStore.incrementalCache) == null ? void 0 : (_workStore_incrementalCache_prerenderManifest = _workStore_incrementalCache1.prerenderManifest) == null ? void 0 : (_workStore_incrementalCache_prerenderManifest_preview = _workStore_incrementalCache_prerenderManifest.preview) == null ? void 0 : _workStore_incrementalCache_prerenderManifest_preview.previewModeId) || '');
}
// Ensures that when the path was revalidated we don't return a partial response on redirects
forwardedHeaders.delete(_approuterheaders.NEXT_ROUTER_STATE_TREE_HEADER);
// When an action follows a redirect, it's no longer handling an action: it's just a normal RSC request
// to the requested URL. We should remove the `next-action` header so that it's not treated as an action
forwardedHeaders.delete(_approuterheaders.ACTION_HEADER);
try {
var _response_headers_get;
const response = await fetch(fetchUrl, {
method: 'GET',
headers: forwardedHeaders,
next: {
// @ts-ignore
internal: 1
}
});
if ((_response_headers_get = response.headers.get('content-type')) == null ? void 0 : _response_headers_get.startsWith(_approuterheaders.RSC_CONTENT_TYPE_HEADER)) {
// 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);
} else {
var // Since we aren't consuming the response body, we cancel it to avoid memory leaks
_response_body;
(_response_body = response.body) == null ? void 0 : _response_body.cancel();
}
} 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('{}');
}
/**
* Ensures the value of the header can't create long logs.
*/ function limitUntrustedHeaderValueForLogs(value) {
return value.length > 100 ? value.slice(0, 100) + '...' : value;
}
function parseHostHeader(headers, originDomain) {
var _forwardedHostHeader_split_, _forwardedHostHeader_split;
const forwardedHostHeader = headers['x-forwarded-host'];
const forwardedHostHeaderValue = forwardedHostHeader && Array.isArray(forwardedHostHeader) ? forwardedHostHeader[0] : forwardedHostHeader == null ? void 0 : (_forwardedHostHeader_split = forwardedHostHeader.split(',')) == null ? void 0 : (_forwardedHostHeader_split_ = _forwardedHostHeader_split[0]) == null ? void 0 : _forwardedHostHeader_split_.trim();
const hostHeader = headers['host'];
if (originDomain) {
return forwardedHostHeaderValue === originDomain ? {
type: "x-forwarded-host",
value: forwardedHostHeaderValue
} : hostHeader === originDomain ? {
type: "host",
value: hostHeader
} : undefined;
}
return forwardedHostHeaderValue ? {
type: "x-forwarded-host",
value: forwardedHostHeaderValue
} : hostHeader ? {
type: "host",
value: hostHeader
} : undefined;
}
async function handleAction({ req, res, ComponentMod, serverModuleMap, generateFlight, workStore, requestStore, serverActions, ctx }) {
const contentType = req.headers['content-type'];
const { serverActionsManifest, page } = ctx.renderOpts;
const { actionId, isURLEncodedAction, isMultipartAction, isFetchAction, isServerAction } = (0, _serveractionrequestmeta.getServerActionRequestMetadata)(req);
// If it's not a Server Action, skip handling.
if (!isServerAction) {
return;
}
if (workStore.isStaticGeneration) {
throw Object.defineProperty(new Error("Invariant: server actions can't be handled during static rendering"), "__NEXT_ERROR_CODE", {
value: "E359",
enumerable: false,
configurable: true
});
}
let temporaryReferences;
const finalizeAndGenerateFlight = (...args)=>{
// When we switch to the render phase, cookies() will return
// `workUnitStore.cookies` instead of `workUnitStore.userspaceMutableCookies`.
// We want the render to see any cookie writes that we performed during the action,
// so we need to update the immutable cookies to reflect the changes.
(0, _requeststore.synchronizeMutableCookies)(requestStore);
requestStore.phase = 'render';
return generateFlight(...args);
};
requestStore.phase = 'action';
// When running actions the default is no-store, you can still `cache: 'force-cache'`
workStore.fetchCache = 'default-no-store';
const originDomain = typeof req.headers['origin'] === 'string' ? new URL(req.headers['origin']).host : undefined;
const host = parseHostHeader(req.headers);
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 = Object.defineProperty(new Error('Invalid Server Actions request.'), "__NEXT_ERROR_CODE", {
value: "E80",
enumerable: false,
configurable: true
});
if (isFetchAction) {
var _workStore_incrementalCache;
res.statusCode = 500;
await Promise.all([
(_workStore_incrementalCache = workStore.incrementalCache) == null ? void 0 : _workStore_incrementalCache.revalidateTag(workStore.revalidatedTags || []),
...Object.values(workStore.pendingRevalidates || {}),
...workStore.pendingRevalidateWrites || []
]);
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 finalizeAndGenerateFlight(req, ctx, requestStore, {
actionResult: promise,
// if the page was not revalidated, we can skip the rendering the flight tree
skipFlight: !workStore.pathWasRevalidated,
temporaryReferences
})
};
}
throw error;
}
}
// ensure we avoid caching server actions unexpectedly
res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate');
let boundActionArguments = [];
const { actionAsyncStorage } = ComponentMod;
let actionResult;
let formState;
let actionModId;
const actionWasForwarded = Boolean(req.headers['x-action-forwarded']);
if (actionId) {
const forwardedWorker = (0, _actionutils.selectWorkerForForwarding)(actionId, page, serverActionsManifest);
// If forwardedWorker is truthy, it means there isn't a worker for the action
// in the current handler, so we forward the request to a worker that has the action.
if (forwardedWorker) {
return {
type: 'done',
result: await createForwardedActionResponse(req, res, host, forwardedWorker, ctx.renderOpts.basePath, workStore)
};
}
}
try {
await actionAsyncStorage.run({
isAction: true
}, async ()=>{
if (// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME === 'edge' && (0, _helpers.isWebNextRequest)(req)) {
if (!req.body) {
throw Object.defineProperty(new Error('invariant: Missing request body.'), "__NEXT_ERROR_CODE", {
value: "E364",
enumerable: false,
configurable: true
});
}
// TODO: add body limit
// Use react-server-dom-webpack/server.edge
const { createTemporaryReferenceSet, decodeReply, decodeAction, decodeFormState } = ComponentMod;
temporaryReferences = createTemporaryReferenceSet();
if (isMultipartAction) {
// TODO-APP: Add streaming support
const formData = await req.request.formData();
if (isFetchAction) {
boundActionArguments = await decodeReply(formData, serverModuleMap, {
temporaryReferences
});
} 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 _workunitasyncstorageexternal.workUnitAsyncStorage.run(requestStore, action);
formState = await decodeFormState(actionReturnedState, formData, serverModuleMap);
requestStore.phase = 'render';
}
// Skip the fetch path
return;
}
} else {
try {
actionModId = getActionModIdOrError(actionId, serverModuleMap);
} catch (err) {
if (actionId !== null) {
console.error(err);
}
return {
type: 'not-found'
};
}
const chunks = [];
const reader = req.body.getReader();
while(true){
const { done, value } = await reader.read();
if (done) {
break;
}
chunks.push(value);
}
const actionData = Buffer.concat(chunks).toString('utf-8');
if (isURLEncodedAction) {
const formData = formDataFromSearchQueryString(actionData);
boundActionArguments = await decodeReply(formData, serverModuleMap, {
temporaryReferences
});
} else {
boundActionArguments = await decodeReply(actionData, serverModuleMap, {
temporaryReferences
});
}
}
} else if (// The type check here ensures that `req` is correctly typed, and the
// environment variable check provides dead code elimination.
process.env.NEXT_RUNTIME !== 'edge' && (0, _helpers.isNodeNextRequest)(req)) {
// Use react-server-dom-webpack/server.node which supports streaming
const { createTemporaryReferenceSet, decodeReply, decodeReplyFromBusboy, decodeAction, decodeFormState } = require(`./react-server.node`);
temporaryReferences = createTemporaryReferenceSet();
const { Transform } = require('node:stream');
const defaultBodySizeLimit = '1 MB';
const bodySizeLimit = (serverActions == null ? void 0 : serverActions.bodySizeLimit) ?? defaultBodySizeLimit;
const bodySizeLimitBytes = bodySizeLimit !== defaultBodySizeLimit ? require('next/dist/compiled/bytes').parse(bodySizeLimit) : 1024 * 1024 // 1 MB
;
let size = 0;
const body = req.body.pipe(new Transform({
transform (chunk, encoding, callback) {
size += Buffer.byteLength(chunk, encoding);
if (size > bodySizeLimitBytes) {
const { ApiError } = require('../api-utils');
callback(Object.defineProperty(new ApiError(413, `Body exceeded ${bodySizeLimit} limit.
To configure the body size limit for Server Actions, see: https://nextjs.org/docs/app/api-reference/next-config-js/serverActions#bodysizelimit`), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
}));
return;
}
callback(null, chunk);
}
}));
if (isMultipartAction) {
if (isFetchAction) {
const busboy = require('busboy')({
defParamCharset: 'utf8',
headers: req.headers,
limits: {
fieldSize: bodySizeLimitBytes
}
});
body.pipe(busboy);
boundActionArguments = await decodeReplyFromBusboy(busboy, serverModuleMap, {
temporaryReferences
});
} else {
// 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: new ReadableStream({
start: (controller)=>{
body.on('data', (chunk)=>{
controller.enqueue(new Uint8Array(chunk));
});
body.on('end', ()=>{
controller.close();
});
body.on('error', (err)=>{
controller.error(err);
});
}
}),
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 _workunitasyncstorageexternal.workUnitAsyncStorage.run(requestStore, action);
formState = await decodeFormState(actionReturnedState, formData, serverModuleMap);
requestStore.phase = 'render';
}
// Skip the fetch path
return;
}
} else {
try {
actionModId = getActionModIdOrError(actionId, serverModuleMap);
} catch (err) {
if (actionId !== null) {
console.error(err);
}
return {
type: 'not-found'
};
}
const chunks = [];
for await (const chunk of req.body){
chunks.push(Buffer.from(chunk));
}
const actionData = Buffer.concat(chunks).toString('utf-8');
if (isURLEncodedAction) {
const formData = formDataFromSearchQueryString(actionData);
boundActionArguments = await decodeReply(formData, serverModuleMap, {
temporaryReferences
});
} else {
boundActionArguments = await decodeReply(actionData, serverModuleMap, {
temporaryReferences
});
}
}
} else {
throw Object.defineProperty(new Error('Invariant: Unknown request type.'), "__NEXT_ERROR_CODE", {
value: "E114",
enumerable: false,
configurable: true
});
}
// 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) {
if (actionId !== null) {
console.error(err);
}
return {
type: 'not-found'
};
}
const actionMod = await ComponentMod.__next_app__.require(actionModId);
const actionHandler = actionMod[// `actionId` must exist if we got here, as otherwise we would have thrown an error above
actionId];
const returnVal = await _workunitasyncstorageexternal.workUnitAsyncStorage.run(requestStore, ()=>actionHandler.apply(null, boundActionArguments));
// For form actions, we need to continue rendering the page.
if (isFetchAction) {
await addRevalidationHeader(res, {
workStore,
requestStore
});
actionResult = await finalizeAndGenerateFlight(req, ctx, requestStore, {
actionResult: Promise.resolve(returnVal),
// if the page was not revalidated, or if the action was forwarded from another worker, we can skip the rendering the flight tree
skipFlight: !workStore.pathWasRevalidated || actionWasForwarded,
temporaryReferences
});
}
});
return {
type: 'done',
result: actionResult,
formState
};
} catch (err) {
if ((0, _redirecterror.isRedirectError)(err)) {
const redirectUrl = (0, _redirect.getURLFromRedirectError)(err);
const redirectType = (0, _redirect.getRedirectTypeFromError)(err);
await addRevalidationHeader(res, {
workStore,
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 = _redirectstatuscode.RedirectStatusCode.SeeOther;
if (isFetchAction) {
return {
type: 'done',
result: await createRedirectRenderResult(req, res, host, redirectUrl, redirectType, ctx.renderOpts.basePath, workStore)
};
}
res.setHeader('Location', redirectUrl);
return {
type: 'done',
result: _renderresult.default.fromStatic('')
};
} else if ((0, _httpaccessfallback.isHTTPAccessFallbackError)(err)) {
res.statusCode = (0, _httpaccessfallback.getAccessFallbackHTTPStatus)(err);
await addRevalidationHeader(res, {
workStore,
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 finalizeAndGenerateFlight(req, ctx, requestStore, {
skipFlight: false,
actionResult: promise,
temporaryReferences
})
};
}
return {
type: 'not-found'
};
}
if (isFetchAction) {
var _workStore_incrementalCache1;
res.statusCode = 500;
await Promise.all([
(_workStore_incrementalCache1 = workStore.incrementalCache) == null ? void 0 : _workStore_incrementalCache1.revalidateTag(workStore.revalidatedTags || []),
...Object.values(workStore.pendingRevalidates || {}),
...workStore.pendingRevalidateWrites || []
]);
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
}
requestStore.phase = 'render';
return {
type: 'done',
result: await generateFlight(req, ctx, requestStore, {
actionResult: promise,
// if the page was not revalidated, or if the action was forwarded from another worker, we can skip the rendering the flight tree
skipFlight: !workStore.pathWasRevalidated || actionWasForwarded,
temporaryReferences
})
};
}
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 Object.defineProperty(new Error("Invariant: Missing 'next-action' header."), "__NEXT_ERROR_CODE", {
value: "E416",
enumerable: false,
configurable: true
});
}
const actionModId = serverModuleMap == null ? void 0 : (_serverModuleMap_actionId = serverModuleMap[actionId]) == null ? void 0 : _serverModuleMap_actionId.id;
if (!actionModId) {
throw Object.defineProperty(new Error("Invariant: Couldn't find action module ID from module map."), "__NEXT_ERROR_CODE", {
value: "E32",
enumerable: false,
configurable: true
});
}
return actionModId;
} catch (err) {
throw Object.defineProperty(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}` : ''}\nRead more: https://nextjs.org/docs/messages/failed-to-find-server-action`), "__NEXT_ERROR_CODE", {
value: "E264",
enumerable: false,
configurable: true
});
}
}
//# sourceMappingURL=action-handler.js.map
;