UNPKG

@logtail/next

Version:

Better Stack Telemetry Next.js client

191 lines (176 loc) 4.62 kB
import { type NextRequest } from 'next/server'; export const isNoPrettyPrint = process.env.BETTER_STACK_NO_PRETTY_PRINT == 'true' ? true : false; export enum EndpointType { webVitals = 'web-vitals', logs = 'logs', } export interface RequestJSON { method: string; url: string; headers: Record<string, string>; cookies: Record<string, string>; nextUrl?: { basePath: string; buildId?: string; defaultLocale?: string; domainLocale?: { defaultLocale: string; domain: string; locales?: string[]; }; hash: string; host: string; hostname: string; href: string; locale?: string; origin: string; password: string; pathname: string; port: string; protocol: string; search: string; searchParams: Record<string, string>; username: string; }; ip?: string; geo?: { city?: string; country?: string; region?: string; latitude?: string; longitude?: string; }; body?: any; cache: { mode: RequestCache; credentials: RequestCredentials; redirect: RequestRedirect; referrerPolicy: ReferrerPolicy; integrity: string; }; mode: RequestMode; destination: RequestDestination; referrer: string; keepalive: boolean; signal: { aborted: boolean; reason: any; }; } /** * Transforms a NextRequest or Request object into a JSON-serializable object */ export async function requestToJSON(request: Request | NextRequest): Promise<RequestJSON> { // Get all headers const headers: Record<string, string> = {}; request.headers.forEach((value, key) => { headers[key] = value; }); let cookiesData: Record<string, string> = {}; if ('cookies' in request) { request.cookies.getAll().forEach((cookie) => { cookiesData[cookie.name] = cookie.value; }); } else { const cookieHeader = headers['cookie']; if (cookieHeader) { cookiesData = Object.fromEntries( cookieHeader.split(';').map((cookie) => { const [key, value] = cookie.trim().split('='); return [key, value]; }) ); } } let nextUrlData: RequestJSON['nextUrl'] | undefined; if ('nextUrl' in request) { const nextUrl = request.nextUrl; nextUrlData = { basePath: nextUrl.basePath, buildId: nextUrl.buildId, hash: nextUrl.hash, host: nextUrl.host, hostname: nextUrl.hostname, href: nextUrl.href, origin: nextUrl.origin, password: nextUrl.password, pathname: nextUrl.pathname, port: nextUrl.port, protocol: nextUrl.protocol, search: nextUrl.search, searchParams: Object.fromEntries(nextUrl.searchParams.entries()), username: nextUrl.username, }; } let body: RequestJSON['body'] | undefined; if (request.body) { try { const clonedRequest = request.clone(); try { body = await clonedRequest.json(); clonedRequest.body?.getReader; } catch { body = await clonedRequest.text(); } } catch (error) { console.warn('Could not parse request body:', error); } } const cache: RequestJSON['cache'] = { mode: request.cache, credentials: request.credentials, redirect: request.redirect, referrerPolicy: request.referrerPolicy, integrity: request.integrity, }; let ip: string | undefined; if ('ip' in request) { // @ts-ignore NextRequest.ip was removed in Next 15, works with undefined ip = request.ip; } // @ts-ignore NextRequest.ip was removed in Next 15, works with undefined let geo: NextRequest['geo'] | undefined; if ('geo' in request) { geo = request.geo; } return { method: request.method, url: request.url, headers, cookies: cookiesData, nextUrl: nextUrlData, ip, geo, body, cache, mode: request.mode, destination: request.destination, referrer: request.referrer, keepalive: request.keepalive, signal: { aborted: request.signal.aborted, reason: request.signal.reason, }, }; } export const throttle = (fn: Function, wait: number) => { let lastFn: ReturnType<typeof setTimeout>, lastTime: number; return function (this: any) { const context = this, args = arguments; // First call, set lastTime if (lastTime == null) { lastTime = Date.now(); } clearTimeout(lastFn); lastFn = setTimeout( () => { if (Date.now() - lastTime >= wait) { fn.apply(context, args); lastTime = Date.now(); } }, Math.max(wait - (Date.now() - lastTime), 0) ); }; };