import { NextRequest, NextResponse } from "next/server"; const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8888"; // #region agent log function debugLog(hypothesisId: string, message: string, data: Record) { fetch("http://localhost:7687/ingest/ee56ea93-ad22-4673-ab77-595d83a9b3c5", { method: "POST", headers: { "Content-Type": "application/json", "X-Debug-Session-Id": "6b638a", }, body: JSON.stringify({ sessionId: "6b638a", runId: "login-400-debug", hypothesisId, location: "frontend/app/api/[...path]/route.ts:debugLog", message, data, timestamp: Date.now(), }), }).catch(() => {}); } // #endregion export async function GET(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxyRequest(req, ctx); } export async function POST(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxyRequest(req, ctx); } export async function PUT(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxyRequest(req, ctx); } export async function PATCH(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxyRequest(req, ctx); } export async function DELETE(req: NextRequest, ctx: { params: Promise<{ path: string[] }> }) { return proxyRequest(req, ctx); } async function proxyRequest( req: NextRequest, ctx: { params: Promise<{ path: string[] }> } ) { const { path } = await ctx.params; const search = req.nextUrl.search || ""; const proxiedPathRaw = req.nextUrl.pathname.replace(/^\/api\//, ""); const normalizedPathRaw = proxiedPathRaw.startsWith("api/") ? proxiedPathRaw : `api/${proxiedPathRaw}`; const proxiedPath = normalizedPathRaw.endsWith("/") ? normalizedPathRaw : `${normalizedPathRaw}/`; const target = `${API_BASE}/${proxiedPath}${search}`; // #region agent log debugLog("H1_H2_H3", "proxy_request_start", { method: req.method, path, proxiedPath, originalPathname: req.nextUrl.pathname, search, apiBase: API_BASE, target, }); // #endregion const headers = new Headers(req.headers); headers.delete("host"); headers.delete("content-length"); const hasBody = !["GET", "HEAD"].includes(req.method); const contentType = headers.get("content-type"); const isMultipart = (contentType || "").toLowerCase().includes("multipart/form-data"); let clonedBodyText = ""; let requestBodyLength = 0; let requestBodyJsonKeys: string[] = []; let requestBodyReadError: string | null = null; if (hasBody && !isMultipart) { try { clonedBodyText = await req.clone().text(); requestBodyLength = clonedBodyText.length; if (contentType?.includes("application/json")) { const parsed = JSON.parse(clonedBodyText || "{}"); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { requestBodyJsonKeys = Object.keys(parsed).sort(); } } } catch (error) { requestBodyReadError = error instanceof Error ? error.message : "unknown_error"; } } // #region agent log debugLog("H1_H3_H4", "proxy_request_body_probe", { method: req.method, target, hasBody, isMultipart, contentType, requestBodyLength, requestBodyJsonKeys, requestBodyReadError, hasUsernameKey: requestBodyJsonKeys.includes("username"), hasPasswordKey: requestBodyJsonKeys.includes("password"), }); // #endregion const init: RequestInit & { duplex?: "half" } = { method: req.method, headers, cache: "no-store", }; if (hasBody) { if (isMultipart) { // Multipart precisa seguir como stream para preservar boundary e arquivos. init.body = req.body; init.duplex = "half"; debugLog("H9", "proxy_request_forward_mode", { method: req.method, target, forwardMode: "stream_multipart", contentType, }); } else { // JSON/urlencoded segue buffer textual para evitar perda de stream no proxy. init.body = clonedBodyText; debugLog("H9", "proxy_request_forward_mode", { method: req.method, target, forwardMode: "buffered_text", forwardedBodyLength: clonedBodyText.length, contentType, }); } } let res: Response; try { res = await fetch(target, init); } catch (error) { // #region agent log debugLog("H2", "proxy_request_fetch_exception", { target, error: error instanceof Error ? error.message : "unknown_error", }); // #endregion return NextResponse.json({ message: "Falha de conexão no proxy." }, { status: 502 }); } // #region agent log debugLog("H5", "proxy_request_first_attempt_done", { target, status: res.status, needsTrailingSlashRetry: res.status === 404 && !target.endsWith("/"), }); // #endregion if (res.status === 404 && !target.endsWith("/")) { const targetWithSlash = `${target}/`; // #region agent log debugLog("H5", "proxy_request_retry_with_trailing_slash", { from: target, to: targetWithSlash, }); // #endregion try { const retried = await fetch(targetWithSlash, init); // #region agent log debugLog("H5", "proxy_request_retry_response", { targetWithSlash, status: retried.status, statusText: retried.statusText, }); // #endregion res = retried; } catch (error) { // #region agent log debugLog("H5", "proxy_request_retry_exception", { targetWithSlash, error: error instanceof Error ? error.message : "unknown_error", }); // #endregion } } // #region agent log debugLog("H1_H3", "proxy_request_response", { target, status: res.status, statusText: res.statusText, }); // #endregion if (res.status >= 400) { let responsePreview = ""; try { responsePreview = (await res.clone().text()).slice(0, 300); } catch { responsePreview = "unreadable_response_body"; } // #region agent log debugLog("H2_H4", "proxy_request_error_response_preview", { target, status: res.status, statusText: res.statusText, responsePreview, }); // #endregion } const responseHeaders = new Headers(); res.headers.forEach((value, key) => { responseHeaders.set(key, value); }); return new NextResponse(res.body, { status: res.status, statusText: res.statusText, headers: responseHeaders, }); }