// Server-side proxy helper for Next.js API routes. // Forwards requests to the NestJS backend, including the Bearer token. import { NextRequest, NextResponse } from "next/server"; import { ACCESS_COOKIE, NONCE_COOKIE } from "./auth-cookies"; const BASE_URL = process.env.LEDGERONE_API_URL ?? "https://api.aarthalabs.com"; export function getBackendUrl(path: string): string { return `${BASE_URL}/api/${path.replace(/^\//, "")}`; } interface ProxyOptions { method?: string; body?: BodyInit | null; extraHeaders?: Record; search?: string; } export async function proxyRequest( req: NextRequest, backendPath: string, options: ProxyOptions = {} ): Promise { const url = new URL(req.url); const search = options.search !== undefined ? options.search : url.search; const targetUrl = `${getBackendUrl(backendPath)}${search}`; const method = options.method ?? req.method; const auth = req.headers.get("authorization") ?? ""; const cookieAccessToken = req.cookies.get(ACCESS_COOKIE)?.value ?? ""; const sessionNonce = req.cookies.get(NONCE_COOKIE)?.value ?? ""; const contentType = req.headers.get("content-type") ?? ""; const userAgent = req.headers.get("user-agent") ?? ""; const forwardedFor = req.headers.get("x-forwarded-for") ?? ""; const headers: Record = { ...options.extraHeaders }; if (auth) headers["Authorization"] = auth; else if (cookieAccessToken) headers["Authorization"] = `Bearer ${cookieAccessToken}`; if (sessionNonce) headers["X-LedgerOne-Session-Nonce"] = sessionNonce; if (userAgent) headers["User-Agent"] = userAgent; if (forwardedFor) headers["X-Forwarded-For"] = forwardedFor; let body: BodyInit | null | undefined = undefined; if (method !== "GET" && method !== "HEAD") { if (options.body !== undefined) { body = options.body; if (contentType) headers["Content-Type"] = contentType; } else if (contentType.includes("multipart/form-data")) { body = await req.formData(); // Do not set Content-Type — fetch sets it with boundary automatically } else { body = await req.text(); if (contentType) headers["Content-Type"] = contentType; } } try { const res = await fetch(targetUrl, { method, headers, body: body ?? undefined, }); const payload = await res.text(); const response = new NextResponse(payload, { status: res.status, headers: { "Content-Type": res.headers.get("content-type") ?? "application/json", }, }); const nextNonce = res.headers.get("x-ledgerone-next-nonce"); if (nextNonce) { response.cookies.set(NONCE_COOKIE, nextNonce, { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", path: "/", maxAge: 30 * 24 * 60 * 60, }); } return response; } catch { return NextResponse.json( { data: null, meta: { timestamp: new Date().toISOString(), version: "v1" }, error: { message: "Backend unavailable." }, }, { status: 503 } ); } }