import { NextRequest, NextResponse } from 'next/server'; import { getServerAppServices } from '@/lib/services/create-services.server'; import { config } from '@/lib/config'; /** * Smart Proxy for Umami Analytics. * * This Route Handler receives tracking events from the browser, * injects the secret UMAMI_WEBSITE_ID, and forwards them to the * internal Umami API endpoint. * * This ensures: * 1. The Website ID is NOT leaked to the client bundle. * 2. The Umami API endpoint is hidden behind our domain. * 3. We have full control over the tracking data. */ export async function POST(request: NextRequest) { const services = getServerAppServices(); const logger = services.logger.child({ component: 'umami-smart-proxy' }); try { const body = await request.json(); const { type, payload } = body; // Inject the secret websiteId from server config const websiteId = config.analytics.umami.websiteId; if (!websiteId) { logger.warn('Umami tracking received but no Website ID configured on server'); return NextResponse.json({ status: 'ignored' }, { status: 200 }); } // Prepare the enhanced payload with the secret ID const enhancedPayload = { ...payload, website: websiteId, }; const umamiEndpoint = config.analytics.umami.apiEndpoint; // Log the event (internal only) logger.debug('Forwarding analytics event', { type, url: payload.url, website: websiteId.slice(0, 8) + '...', }); const response = await fetch(`${umamiEndpoint}/api/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'User-Agent': request.headers.get('user-agent') || 'KLZ-Smart-Proxy', 'X-Forwarded-For': request.headers.get('x-forwarded-for') || '', }, body: JSON.stringify({ type, payload: enhancedPayload }), }); if (!response.ok) { const errorText = await response.text(); if (!process.env.CI) { logger.error('Umami API responded with error', { status: response.status, error: errorText.slice(0, 100), }); } return new NextResponse(errorText, { status: response.status }); } return NextResponse.json({ status: 'ok' }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; // Console error to ensure it appears in logs even if logger fails if (!process.env.CI) { console.error('CRITICAL PROXY ERROR:', { message: errorMessage, stack: errorStack, endpoint: config.analytics.umami.apiEndpoint, }); logger.error('Failed to proxy analytics request', { error: errorMessage, stack: errorStack, }); } return NextResponse.json( { error: 'Internal Server Error', details: errorMessage, // Expose error for debugging endpoint: config.analytics.umami.apiEndpoint ? 'configured' : 'missing', }, { status: 500 }, ); } }