import { yellow } from "kleur/colors"; import { defineMiddleware } from "../../core/middleware/index.js"; import { getOriginPathname } from "../../core/routing/rewrite.js"; import { ACTION_QUERY_PARAMS } from "../consts.js"; import { ACTION_API_CONTEXT_SYMBOL, formContentTypes, hasContentType } from "./utils.js"; import { getAction } from "./virtual/get-action.js"; import { serializeActionResult } from "./virtual/shared.js"; const onRequest = defineMiddleware(async (context, next) => { if (context._isPrerendered) { if (context.request.method === "POST") { console.warn( yellow("[astro:actions]"), "POST requests should not be sent to prerendered pages. If you're using Actions, disable prerendering with `export const prerender = false`." ); } return next(); } const locals = context.locals; if (locals._actionPayload) return next(); const actionPayload = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.json(); if (actionPayload) { if (!isActionPayload(actionPayload)) { throw new Error("Internal: Invalid action payload in cookie."); } return renderResult({ context, next, ...actionPayload }); } const actionName = context.url.searchParams.get(ACTION_QUERY_PARAMS.actionName); if (context.request.method === "POST" && actionName) { return handlePost({ context, next, actionName }); } return next(); }); async function renderResult({ context, next, actionResult, actionName }) { const locals = context.locals; locals._actionPayload = { actionResult, actionName }; const response = await next(); context.cookies.delete(ACTION_QUERY_PARAMS.actionPayload); if (actionResult.type === "error") { return new Response(response.body, { status: actionResult.status, statusText: actionResult.type, headers: response.headers }); } return response; } async function handlePost({ context, next, actionName }) { const { request } = context; const baseAction = await getAction(actionName); const contentType = request.headers.get("content-type"); let formData; if (contentType && hasContentType(contentType, formContentTypes)) { formData = await request.clone().formData(); } const { getActionResult, callAction, props, redirect, ...actionAPIContext } = context; Reflect.set(actionAPIContext, ACTION_API_CONTEXT_SYMBOL, true); const action = baseAction.bind(actionAPIContext); const actionResult = await action(formData); if (context.url.searchParams.get(ACTION_QUERY_PARAMS.actionRedirect) === "false") { return renderResult({ context, next, actionName, actionResult: serializeActionResult(actionResult) }); } return redirectWithResult({ context, actionName, actionResult }); } async function redirectWithResult({ context, actionName, actionResult }) { context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, { actionName, actionResult: serializeActionResult(actionResult) }); if (actionResult.error) { const referer2 = context.request.headers.get("Referer"); if (!referer2) { throw new Error("Internal: Referer unexpectedly missing from Action POST request."); } return context.redirect(referer2); } const referer = getOriginPathname(context.request); if (referer) { return context.redirect(referer); } return context.redirect(context.url.pathname); } function isActionPayload(json) { if (typeof json !== "object" || json == null) return false; if (!("actionResult" in json) || typeof json.actionResult !== "object") return false; if (!("actionName" in json) || typeof json.actionName !== "string") return false; return true; } export { onRequest };