@jillen/analytics
Version:
Advanced analytics package for Next.js applications with bot detection and comprehensive tracking
1 lines • 12.3 kB
Source Map (JSON)
{"version":3,"sources":["../../analytics-constants.ts","../../analytics-bot-utils.ts","../../server/middleware-utils.ts","../../server/header-parser.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Analytics configuration using environment variables\n * Both server and client components use NEXT_PUBLIC_* variables for consistency\n */\nexport const ANALYTICS_CONFIG = {\n SERVER_URL: process.env.NEXT_PUBLIC_ANALYTICS_SERVER_URL || \"\",\n SITE_ID: process.env.NEXT_PUBLIC_ANALYTICS_SITE_ID || \"\",\n} as const;\n\n/**\n * Validate that required environment variables are set\n */\nexport function validateAnalyticsConfig() {\n const { SERVER_URL, SITE_ID } = ANALYTICS_CONFIG;\n \n if (!SERVER_URL || !SITE_ID) {\n console.warn(\"[Analytics] Missing environment variables:\", {\n SERVER_URL: SERVER_URL ? \"✓\" : \"✗ NEXT_PUBLIC_ANALYTICS_SERVER_URL\",\n SITE_ID: SITE_ID ? \"✓\" : \"✗ NEXT_PUBLIC_ANALYTICS_SITE_ID\"\n });\n return false;\n }\n \n return true;\n} ","import type { NextRequest } from 'next/server';\nimport { ANALYTICS_CONFIG, validateAnalyticsConfig } from './analytics-constants';\n\n/**\n * Simplified Bot Tracking Utility\n * \n * Fire-and-forget bot tracking with minimal data collection.\n * Designed for performance and reliability - no complex calculations.\n */\n\n// Simple hash generation for bot IDs (no complex session windows)\nfunction generateSimpleBotVisitorId(ip: string, userAgent: string): string {\n const normalizedUA = userAgent.toLowerCase().replace(/\\s+/g, \"\");\n const combined = `bot:${ip}:${normalizedUA}`;\n return Buffer.from(combined)\n .toString(\"base64\")\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .substring(0, 16);\n}\n\nfunction generateSimpleBotSessionId(ip: string): string {\n const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD\n const combined = `bot-session:${ip}:${today}`;\n return Buffer.from(combined)\n .toString(\"base64\")\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .substring(0, 16);\n}\n\n/**\n * Track bot visit with minimal data collection\n * Fire-and-forget: does not throw errors or block execution\n */\nexport function trackBotVisit(request: NextRequest, pathname: string): void {\n // Fire-and-forget: run async without awaiting\n void (async () => {\n try {\n // Validate configuration\n if (!validateAnalyticsConfig()) {\n return;\n }\n\n const serverUrl = ANALYTICS_CONFIG.SERVER_URL;\n const siteId = ANALYTICS_CONFIG.SITE_ID;\n\n // Extract essential bot data\n const userAgent = request.headers.get('user-agent') || '';\n const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || \n request.headers.get('x-real-ip') || \n 'unknown';\n\n // Generate minimal required IDs\n const visitorId = generateSimpleBotVisitorId(ip, userAgent);\n const sessionId = generateSimpleBotSessionId(ip);\n\n // Prepare minimal bot payload\n const botPayload = {\n // Required fields (minimal values)\n siteId,\n path: pathname,\n visitorId,\n sessionId,\n eventType: \"pageview\" as const,\n \n // Essential bot data\n userAgent,\n ipAddress: ip,\n \n // Client-side fields (bots don't have these)\n isNewVisitor: true,\n screenResolution: null,\n viewportSize: null,\n connectionType: null,\n clientTimeZone: null,\n sessionStartTime: new Date().toISOString(),\n \n // Optional server fields (skip expensive lookups)\n referrer: request.headers.get('referer') || null,\n country: null,\n city: null,\n region: null,\n continent: null,\n latitude: null,\n longitude: null,\n timezone: null,\n postalCode: null,\n host: request.headers.get('host') || null,\n protocol: request.headers.get('x-forwarded-proto') as \"http\" | \"https\" || null,\n deploymentUrl: null,\n edgeRegion: null,\n language: null,\n doNotTrack: false,\n isMobile: /Mobi|Android/i.test(userAgent),\n cacheStatus: \"UNKNOWN\" as const,\n };\n\n // Send to analytics server (fire-and-forget)\n const endpoint = `${serverUrl}/api/log/ingest`;\n \n await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n mode: 'cors',\n body: JSON.stringify(botPayload),\n });\n\n } catch {\n // Silent fail - do not log errors or throw exceptions\n // Bot tracking should never break the application\n return;\n }\n })();\n} ","import type { NextRequest } from 'next/server';\nimport { isbot } from 'isbot';\nimport { trackBotVisit } from '../analytics-bot-utils';\n\n/**\n * Combined middleware utility for analytics\n * Handles both pathname header injection and bot tracking\n */\nexport function setupAnalyticsMiddleware(request: NextRequest) {\n // Extract pathname from URL\n const pathname = request.nextUrl.pathname;\n \n // Clone headers and add pathname\n const headers = new Headers(request.headers);\n headers.set('x-pathname', pathname);\n \n // Track bot visits if this is a bot\n const userAgent = request.headers.get('user-agent') || '';\n if (isbot(userAgent)) {\n trackBotVisit(request, pathname);\n }\n \n return {\n headers,\n pathname\n };\n} ","/**\n * Server-side utility to parse analytics data from Next.js headers\n */\n\nexport interface ParsedAnalyticsHeaders {\n ip: string;\n country: string | null;\n city: string | null;\n region: string | null;\n continent: string | null;\n latitude: string | null;\n longitude: string | null;\n timezone: string | null;\n postalCode: string | null;\n host: string | null;\n protocol: \"http\" | \"https\" | null;\n deploymentUrl: string | null;\n userAgent: string;\n edgeRegion: string | null;\n cacheStatus: \"HIT\" | \"MISS\" | \"BYPASS\" | \"STALE\" | null;\n}\n\nexport function parseAnalyticsHeaders(headers: Headers): ParsedAnalyticsHeaders {\n const ip = headers.get(\"x-forwarded-for\") || headers.get(\"x-real-ip\") || \"unknown\";\n const country = headers.get(\"x-vercel-ip-country\") || null;\n const userAgent = headers.get(\"user-agent\") || \"\";\n const city = headers.get(\"x-vercel-ip-city\") || null;\n const region = headers.get(\"x-vercel-ip-country-region\") || null;\n const continent = headers.get(\"x-vercel-ip-continent\") || null;\n const latitude = headers.get(\"x-vercel-ip-latitude\") || null;\n const longitude = headers.get(\"x-vercel-ip-longitude\") || null;\n const timezone = headers.get(\"x-vercel-ip-timezone\") || null;\n const postalCode = headers.get(\"x-vercel-ip-postal-code\") || null;\n const host = headers.get(\"host\") || null;\n const protocol = headers.get(\"x-forwarded-proto\") as \"http\" | \"https\" | null;\n const deploymentUrl = headers.get(\"x-vercel-deployment-url\") || null;\n const vercelId = headers.get(\"x-vercel-id\") || null;\n\n // Extract edge region from x-vercel-id\n const edgeRegion = vercelId ? vercelId.split(\"::\")[0] : null;\n const cacheStatus = null; // This would need to be determined differently\n\n return {\n ip,\n country,\n city,\n region,\n continent,\n latitude,\n longitude,\n timezone,\n postalCode,\n host,\n protocol,\n deploymentUrl,\n userAgent,\n edgeRegion,\n cacheStatus,\n };\n} "]}