import { NextRequest, NextResponse } from 'next/server'; import { getApiBase, resetApiBase } from '@/lib/api-base'; export async function GET(req: NextRequest, props: { params: Promise<{ route: string[] }> }) { return handleProxy(req, props); } export async function POST(req: NextRequest, props: { params: Promise<{ route: string[] }> }) { return handleProxy(req, props); } export async function PUT(req: NextRequest, props: { params: Promise<{ route: string[] }> }) { return handleProxy(req, props); } export async function DELETE(req: NextRequest, props: { params: Promise<{ route: string[] }> }) { return handleProxy(req, props); } export async function PATCH(req: NextRequest, props: { params: Promise<{ route: string[] }> }) { return handleProxy(req, props); } async function handleProxy(req: NextRequest, { params }: { params: Promise<{ route: string[] }> }) { try { const routeArray = (await params).route; const route = routeArray.join('/'); const url = new URL(req.url); // INTERNAL_API_URL is read at RUNTIME (not baked at build time like NEXT_PUBLIC_*). // getApiBase() probes configured URLs and transparently falls back to the stable // Swarm service alias if the configured one (e.g. task hostname ...-0) is unreachable. const localBase = await getApiBase(); const destinationUrl = `${localBase}/api/${route}${url.search}`; // Prepare headers const headers = new Headers(); const contentType = req.headers.get('content-type'); if (contentType) { headers.set('Content-Type', contentType); } else { headers.set('Content-Type', 'application/json'); } const contentLength = req.headers.get('content-length'); if (contentLength) { headers.set('Content-Length', contentLength); } // Forward cookie for authentication const cookie = req.headers.get('cookie'); if (cookie) { headers.set('cookie', cookie); } // Forward all custom headers starting with x- (e.g. x-backone-site-uuid, x-view-as-agent) req.headers.forEach((value, name) => { if (name.toLowerCase().startsWith('x-')) { headers.set(name, value); } }); const hasBody = !['GET', 'HEAD'].includes(req.method); const body = hasBody ? req.body : undefined; const response = await fetch(destinationUrl, { method: req.method, headers, body: body, cache: 'no-store', // Prevent Next.js from aggressively caching the backend response // @ts-expect-error - `duplex` is required for streaming bodies in older fetch typings duplex: hasBody ? 'half' : undefined, }); const responseBuffer = await response.arrayBuffer(); const nextResponse = new NextResponse(responseBuffer, { status: response.status, headers: { 'Content-Type': response.headers.get('content-type') || 'application/json', } }); const rawCookies = response.headers.getSetCookie ? response.headers.getSetCookie() : []; if (rawCookies.length > 0) { rawCookies.forEach(cookie => { nextResponse.headers.append('Set-Cookie', cookie); }); } else { const setCookieHeader = response.headers.get('set-cookie'); if (setCookieHeader) { nextResponse.headers.set('Set-Cookie', setCookieHeader); } } return nextResponse; } catch (error) { // Backend may have been redeployed (task hostname changed) — re-resolve next request. resetApiBase(); console.error('[Proxy Error]', error); const message = error instanceof Error ? error.message : 'Unknown proxy error'; return NextResponse.json({ ok: false, message }, { status: 500 }); } }