import { NextRequest } from 'next/server'; export interface GetClientIpOptions { /** * Which index to take from a comma-separated forwarded-for list. * 0 = leftmost (original client; default). * -1 = rightmost (closest hop). */ forwardedIndex?: number; /** * Custom header priority override. Default uses common reverse-proxy headers. */ headerPriority?: readonly string[]; /** * Value to return when nothing found. * Default: 'unknown' */ fallback?: string; } const DEFAULT_HEADER_PRIORITY = [ 'x-forwarded-for', 'x-real-ip', 'cf-connecting-ip', 'vercel-forwarded-for', ] as const; export function getClientIp( req: NextRequest, opts: GetClientIpOptions = {} ): string { const { forwardedIndex = 0, headerPriority = DEFAULT_HEADER_PRIORITY, fallback = 'unknown', } = opts; for (const h of headerPriority) { const raw = req.headers.get(h); if (!raw) continue; // headers like x-forwarded-for can be CSV const parts = raw.split(',').map(p => p.trim()).filter(Boolean); if (parts.length === 0) continue; const idx = forwardedIndex >= 0 ? forwardedIndex : parts.length + forwardedIndex; // support -1 end indexing const sel = parts[idx] ?? parts[0]; // safe fallback to 0 const norm = normalizeIp(sel); if (norm) return norm; } return fallback; } /** * Normalize IPv4/IPv6/port-suffixed forms to a canonical-ish string. */ export function normalizeIp(raw: string | null | undefined): string { if (!raw) return ''; let ip = raw.trim(); // Strip brackets: [2001:db8::1] if (ip.startsWith('[') && ip.endsWith(']')) { ip = ip.slice(1, -1); } // IPv4:port (because some proxies send ip:port) // Heuristic: if '.' present (IPv4-ish) and ':' present, drop port after last colon. if (ip.includes('.') && ip.includes(':')) { ip = ip.slice(0, ip.lastIndexOf(':')); } // IPv4-mapped IPv6 ::ffff:203.0.113.5 if (ip.startsWith('::ffff:')) { ip = ip.substring(7); } return ip; } export interface RequestInfo { ip: string; userAgent: string; referer?: string; requestId: string; method: string; url: string; } export function getRequestInfo(req: NextRequest, ipOpts?: GetClientIpOptions): RequestInfo { const ip = getClientIp(req, ipOpts); const userAgent = req.headers.get('user-agent')?.trim() || 'unknown'; const refererRaw = req.headers.get('referer')?.trim(); const referer = refererRaw && refererRaw.length > 0 ? refererRaw : undefined; const { href, pathname } = req.nextUrl; return { ip, userAgent, referer, requestId: (globalThis.crypto?.randomUUID?.() ?? crypto.randomUUID()), method: req.method, url: href || pathname, }; }