import { NextRequest, NextResponse } from 'next/server'; import { getServerAppServices } from '@/lib/services/create-services.server'; import { config } from '@/lib/config'; /** * Smart Proxy / Relay for Sentry/GlitchTip events. * * This Route Handler receives Sentry envelopes from the client, * injects the correct DSN if needed, and forwards them to the * internal GlitchTip/Sentry instance. * * This hides the real DSN from the client and bypasses ad-blockers * that target Sentry's default ingest endpoints. */ export async function POST(request: NextRequest) { const services = getServerAppServices(); const logger = services.logger.child({ component: 'sentry-relay' }); try { const envelope = await request.text(); // Sentry envelopes can contain multiple parts separated by newlines const lines = envelope.split('\n'); if (lines.length < 1) { return NextResponse.json({ error: 'Empty envelope' }, { status: 400 }); } JSON.parse(lines[0]); const realDsn = config.errors.glitchtip.dsn; if (!realDsn) { logger.warn('Sentry relay received but no SENTRY_DSN configured on server'); return NextResponse.json({ status: 'ignored' }, { status: 200 }); } const dsnUrl = new URL(realDsn); const projectId = dsnUrl.pathname.replace('/', ''); const relayUrl = `${dsnUrl.protocol}//${dsnUrl.host}/api/${projectId}/envelope/`; logger.debug('Relaying Sentry envelope', { projectId, host: dsnUrl.host, }); const response = await fetch(relayUrl, { method: 'POST', body: envelope, headers: { 'Content-Type': 'application/x-sentry-envelope', }, }); if (!response.ok) { const errorText = await response.text(); logger.error('Sentry/GlitchTip 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) { logger.error('Failed to relay Sentry request', { error: (error as Error).message, }); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } }