UNPKG

@adopture/next

Version:

Next.js SDK for Adopture feature adoption tracking with SSR support

1 lines 15.4 kB
{"version":3,"sources":["../src/middleware.ts"],"names":["createAdoptureMiddleware","config","request","startTime","now","pathname","shouldExcludeRoute","NextResponse","userId","extractUserIdFromHeaders","metadata","getClientIP","response","success","error","e","routeEvent","sanitizeMetadata","sendTrackingEvent","err","performanceEvent","generateRequestId","type","event","payload","tRPCPayload","errorText","headers","authHeader","match","tokenParts","userIdHeader","cookieHeader","cookies","parseCookies","forwardedFor","realIP","cfConnectingIP","cookie","name","rest","simpleAdoptureMiddleware","apiKey","options","withAdoptureTracking","middleware","middlewarePresets"],"mappings":"gFAaO,SAASA,CAAAA,CAAyBC,EAA0B,CACjE,OAAO,eAAkCC,CAAAA,CAA6C,CACpF,IAAMC,CAAAA,CAAYC,GAAI,CAChBC,CAAAA,CAAWH,EAAQ,OAAA,CAAQ,QAAA,CAGjC,GAAII,CAAAA,CAAmBD,EAAUJ,CAAAA,CAAO,aAAA,CAAeA,CAAAA,CAAO,aAAa,EACzE,OAAOM,YAAAA,CAAa,IAAA,EAAK,CAI3B,IAAMC,CAAAA,CAASP,CAAAA,CAAO,kBAAkBC,CAAO,CAAA,EAAKO,EAAyBP,CAAAA,CAAQ,OAAO,CAAA,CAGtFQ,CAAAA,CAAW,CACf,GAAIT,CAAAA,CAAO,iBAAA,GAAoBC,CAAO,GAAK,EAAC,CAC5C,QAAA,CAAAG,CAAAA,CACA,OAAQH,CAAAA,CAAQ,MAAA,CAChB,UAAWA,CAAAA,CAAQ,OAAA,CAAQ,IAAI,YAAY,CAAA,EAAK,EAAA,CAChD,OAAA,CAASA,EAAQ,OAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,EAAK,GAC3C,EAAA,CAAIS,CAAAA,CAAYT,CAAO,CAAA,CACvB,QAAUA,CAAAA,CAAgB,GAAA,EAAK,SAAW,EAAA,CAC1C,IAAA,CAAOA,EAAgB,GAAA,EAAK,IAAA,EAAQ,EAAA,CACpC,MAAA,CAASA,EAAgB,GAAA,EAAK,MAAA,EAAU,EAC1C,CAAA,CAEIU,EACAC,CAAAA,CAAU,IAAA,CACVC,CAAAA,CAAuB,IAAA,CAE3B,GAAI,CACFF,CAAAA,CAAWL,aAAa,IAAA,GAC1B,OAASQ,CAAAA,CAAG,CACVF,CAAAA,CAAU,KAAA,CACVC,EAAQC,CAAAA,YAAa,KAAA,CAAQA,CAAAA,CAAE,OAAA,CAAU,gBACzCH,CAAAA,CAAWL,YAAAA,CAAa,IAAA,GAC1B,CAGA,GAAIN,CAAAA,CAAO,qBAAuBO,CAAAA,CAAQ,CACxC,IAAMQ,GAAAA,CAAa,CACjB,SAAA,CAAW,CAAA,MAAA,EAASX,EAAS,OAAA,CAAQ,kBAAA,CAAoB,GAAG,CAAC,GAC7D,MAAA,CAAAG,CAAAA,CACA,QAAA,CAAUS,CAAAA,CAAiB,CACzB,GAAGP,CAAAA,CACH,QAAAG,CAAAA,CACA,KAAA,CAAAC,EACA,QAAA,CAAUV,CAAAA,EAAI,CAAID,CACpB,CAAC,CAAA,CACD,SAAA,CAAWA,CACb,CAAA,CAGAe,EAAkBjB,CAAAA,CAAQ,OAAA,CAASe,GAAU,CAAA,CAAE,MAAMG,CAAAA,EAAO,CAC1D,QAAQ,KAAA,CAAM,mDAAA,CAAqDA,CAAG,EACxE,CAAC,EACH,CAGA,GAAIlB,CAAAA,CAAO,yBAAA,EAA6BO,CAAAA,CAAQ,CAC9C,IAAMY,GAAAA,CAAmB,CACvB,SAAA,CAAW,wBAAA,CACX,OAAAZ,CAAAA,CACA,QAAA,CAAUS,EAAiB,CACzB,GAAGP,EACH,YAAA,CAAcN,CAAAA,EAAI,CAAID,CAAAA,CACtB,QAAAU,CAAAA,CACA,KAAA,CAAAC,CACF,CAAC,EACD,SAAA,CAAWX,CACb,CAAA,CAGAe,CAAAA,CAAkBjB,EAAQ,aAAA,CAAemB,GAAgB,EAAE,KAAA,CAAMD,CAAAA,EAAO,CACtE,OAAA,CAAQ,KAAA,CAAM,yDAAA,CAA2DA,CAAG,EAC9E,CAAC,EACH,CAGA,OAAAP,EAAS,OAAA,CAAQ,GAAA,CAAI,uBAAA,CAAyBS,CAAAA,EAAmB,CAAA,CACjET,CAAAA,CAAS,QAAQ,GAAA,CAAI,sBAAA,CAAwBT,EAAU,QAAA,EAAU,CAAA,CAE7DK,CAAAA,EACFI,EAAS,OAAA,CAAQ,GAAA,CAAI,oBAAA,CAAsBJ,CAAM,EAG5CI,CACT,CACF,CAKA,eAAeM,EACbjB,CAAAA,CACAqB,GAAAA,CACAC,EACe,CACf,IAAMC,EAAU,CACd,IAAA,CAAAF,GAAAA,CACA,KAAA,CAAAC,EACA,SAAA,CAAWnB,CAAAA,EAAI,CACf,MAAA,CAAQ,YACV,CAAA,CAEA,GAAI,CAEF,IAAMqB,EAAc,CAClB,SAAA,CAAW,uCACX,SAAA,CAAWF,CAAAA,CAAM,WAAa,CAAA,MAAA,EAASC,CAAAA,CAAQ,IAAI,CAAA,CAAA,CACnD,OAAQD,CAAAA,CAAM,MAAA,EAAU,WAAA,CACxB,SAAA,CAAW,kBACX,SAAA,CAAWA,CAAAA,CAAM,SAAA,EAAa,CAAA,WAAA,EAAc,KAAK,GAAA,EAAK,GACtD,QAAA,CAAUA,CAAAA,CAAM,UAAY,EAC9B,CAAA,CAEMX,CAAAA,CAAW,MAAM,KAAA,CAAM,CAAA,EAAGX,CAAAA,CAAO,MAAA,EAAU,uBAAuB,CAAA,gCAAA,CAAA,CAAoC,CAC1G,MAAA,CAAQ,MAAA,CACR,QAAS,CACP,cAAA,CAAgB,mBAChB,aAAA,CAAiB,CAAA,OAAA,EAAUA,EAAO,MAAM,CAAA,CAAA,CACxC,YAAA,CAAc,gCAChB,EACA,IAAA,CAAM,IAAA,CAAK,SAAA,CAAUwB,CAAW,CAClC,CAAC,CAAA,CAED,GAAI,CAACb,EAAS,EAAA,CAAI,CAChB,IAAMc,CAAAA,CAAY,MAAMd,EAAS,IAAA,EAAK,CACtC,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQA,CAAAA,CAAS,MAAM,CAAA,EAAA,EAAKc,CAAS,CAAA,CAAE,CACzD,CACF,CAAA,MAASZ,EAAO,CAEd,MAAMA,CACR,CACF,CAKA,SAASL,CAAAA,CAAyBkB,CAAAA,CAAiC,CAEjE,IAAMC,EAAaD,CAAAA,CAAQ,GAAA,CAAI,eAAe,CAAA,CAC9C,GAAIC,CAAAA,CAAY,CAEd,IAAMC,CAAAA,CAAQD,EAAW,KAAA,CAAM,gBAAgB,EAC/C,GAAIC,CAAAA,CAEF,GAAI,CACF,IAAMC,CAAAA,CAAaD,CAAAA,CAAM,CAAC,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CACrC,GAAIC,CAAAA,CAAW,MAAA,GAAW,CAAA,CAAG,CAE3B,IAAMN,CAAAA,CAAU,IAAA,CAAK,MAAM,IAAA,CAAKM,CAAAA,CAAW,CAAC,CAAC,CAAC,CAAA,CAC9C,OAAON,EAAQ,GAAA,EAAOA,CAAAA,CAAQ,MAAA,EAAUA,CAAAA,CAAQ,SAAW,IAC7D,CACF,CAAA,KAAQ,CAER,CAEJ,CAGA,IAAMO,EAAeJ,CAAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,EACxBA,CAAAA,CAAQ,GAAA,CAAI,QAAQ,GACpBA,CAAAA,CAAQ,GAAA,CAAI,SAAS,CAAA,CACzC,GAAII,CAAAA,CACF,OAAOA,CAAAA,CAIT,IAAMC,EAAeL,CAAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA,CACzC,GAAIK,EAAc,CAChB,IAAMC,CAAAA,CAAUC,CAAAA,CAAaF,CAAY,CAAA,CACzC,OAAOC,CAAAA,CAAQ,MAAA,EAAUA,EAAQ,OAAA,EAAWA,CAAAA,CAAQ,SAAS,CAAA,EAAK,IACpE,CAEA,OAAO,IACT,CAKA,SAAStB,EAAYT,CAAAA,CAA8B,CAEjD,IAAMiC,CAAAA,CAAejC,EAAQ,OAAA,CAAQ,GAAA,CAAI,iBAAiB,CAAA,CAC1D,GAAIiC,CAAAA,CACF,OAAOA,CAAAA,CAAa,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,EAAE,IAAA,EAAK,CAGzC,IAAMC,CAAAA,CAASlC,CAAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,CAC9C,GAAIkC,CAAAA,CACF,OAAOA,EAGT,IAAMC,CAAAA,CAAiBnC,CAAAA,CAAQ,OAAA,CAAQ,IAAI,kBAAkB,CAAA,CAC7D,OAAImC,CAAAA,EAIInC,CAAAA,CAAgB,IAAM,SAChC,CAKA,SAASgC,CAAAA,CAAaF,EAAiD,CACrE,IAAMC,CAAAA,CAAqC,GAE3C,OAAAD,CAAAA,CAAa,KAAA,CAAM,GAAG,EAAE,OAAA,CAAQM,CAAAA,EAAU,CACxC,GAAM,CAACC,EAAM,GAAGC,CAAI,CAAA,CAAIF,CAAAA,CAAO,MAAM,GAAG,CAAA,CACpCC,CAAAA,EAAQC,CAAAA,CAAK,OAAS,CAAA,GACxBP,CAAAA,CAAQM,CAAAA,CAAK,IAAA,EAAM,CAAA,CAAIC,CAAAA,CAAK,KAAK,GAAG,CAAA,CAAE,MAAK,EAE/C,CAAC,CAAA,CAEMP,CACT,CAKA,SAASZ,CAAAA,EAA4B,CACnC,OAAO,KAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,EAAE,SAAA,CAAU,CAAC,EAAI,IAAA,CAAK,GAAA,GAAM,QAAA,CAAS,EAAE,CACzE,CAKO,SAASoB,CAAAA,CAAyBC,CAAAA,CAAgBC,CAAAA,CAItD,CACD,OAAO3C,CAAAA,CAAyB,CAC9B,MAAA,CAAA0C,CAAAA,CACA,oBAAqBC,CAAAA,EAAS,mBAAA,EAAuB,KACrD,yBAAA,CAA2BA,CAAAA,EAAS,2BAA6B,KAAA,CACjE,aAAA,CAAeA,CAAAA,EAAS,aAC1B,CAAC,CACH,CAKO,SAASC,CAAAA,CACdC,EACA5C,CAAAA,CACA,CACA,OAAO,eAAiCC,EAAmC,CAIzE,OAAA,MAH2BF,EAAyBC,CAAM,CAAA,CAGjCC,CAAO,CAAA,CAGzB2C,CAAAA,CAAW3C,CAAO,CAC3B,CACF,CAKO,IAAM4C,CAAAA,CAAoB,CAI/B,MAAQJ,CAAAA,GAAsC,CAC5C,MAAA,CAAAA,CAAAA,CACA,oBAAqB,IAAA,CACrB,yBAAA,CAA2B,KAC7B,CAAA,CAAA,CAKA,GAAA,CAAMA,IAAsC,CAC1C,MAAA,CAAAA,CAAAA,CACA,mBAAA,CAAqB,KACrB,yBAAA,CAA2B,IAAA,CAC3B,aAAA,CAAe,CAAC,QAAQ,CAC1B,CAAA,CAAA,CAKA,KAAA,CAAQA,CAAAA,GAAsC,CAC5C,MAAA,CAAAA,CAAAA,CACA,oBAAqB,IAAA,CACrB,yBAAA,CAA2B,MAC3B,aAAA,CAAe,CAAC,QAAA,CAAU,UAAA,CAAY,cAAc,CACtD,CAAA,CAAA,CAKA,IAAA,CAAOA,CAAAA,GAAsC,CAC3C,MAAA,CAAAA,CAAAA,CACA,mBAAA,CAAqB,IAAA,CACrB,0BAA2B,IAAA,CAC3B,aAAA,CAAe,CAAC,UAAA,CAAY,cAAc,CAC5C,CAAA,CACF","file":"middleware.mjs","sourcesContent":["/**\n * Next.js Middleware integration for Adopture\n * Provides edge runtime compatible tracking for route changes and performance metrics\n */\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport type { MiddlewareConfig } from './types';\nimport { shouldExcludeRoute, extractUserId, sanitizeMetadata, now } from './shared/utils';\n\n/**\n * Adopture middleware for Next.js\n * Tracks route changes and performance metrics at the edge\n */\nexport function createAdoptureMiddleware(config: MiddlewareConfig) {\n return async function adoptureMiddleware(request: NextRequest): Promise<NextResponse> {\n const startTime = now();\n const pathname = request.nextUrl.pathname;\n \n // Check if route should be tracked\n if (shouldExcludeRoute(pathname, config.excludeRoutes, config.includeRoutes)) {\n return NextResponse.next();\n }\n\n // Extract user ID from request\n const userId = config.userIdExtractor?.(request) || extractUserIdFromHeaders(request.headers);\n \n // Extract metadata from request\n const metadata = {\n ...(config.metadataExtractor?.(request) || {}),\n pathname,\n method: request.method,\n userAgent: request.headers.get('user-agent') || '',\n referer: request.headers.get('referer') || '',\n ip: getClientIP(request),\n country: (request as any).geo?.country || '',\n city: (request as any).geo?.city || '',\n region: (request as any).geo?.region || '',\n };\n\n let response: NextResponse;\n let success = true;\n let error: string | null = null;\n\n try {\n response = NextResponse.next();\n } catch (e) {\n success = false;\n error = e instanceof Error ? e.message : 'Unknown error';\n response = NextResponse.next();\n }\n\n // Track route access if enabled\n if (config.enableRouteTracking && userId) {\n const routeEvent = {\n featureId: `route-${pathname.replace(/[^a-zA-Z0-9_.-]/g, '-')}`,\n userId,\n metadata: sanitizeMetadata({\n ...metadata,\n success,\n error,\n duration: now() - startTime,\n }),\n timestamp: startTime,\n };\n\n // Send event asynchronously to avoid blocking the response\n sendTrackingEvent(config, 'route', routeEvent).catch(err => {\n console.error('[ADOPTURE-MIDDLEWARE] Failed to send route event:', err);\n });\n }\n\n // Track performance metrics if enabled\n if (config.enablePerformanceTracking && userId) {\n const performanceEvent = {\n featureId: 'middleware:performance',\n userId,\n metadata: sanitizeMetadata({\n ...metadata,\n responseTime: now() - startTime,\n success,\n error,\n }),\n timestamp: startTime,\n };\n\n // Send event asynchronously\n sendTrackingEvent(config, 'performance', performanceEvent).catch(err => {\n console.error('[ADOPTURE-MIDDLEWARE] Failed to send performance event:', err);\n });\n }\n\n // Add tracking headers to response (for client-side correlation)\n response.headers.set('x-adopture-request-id', generateRequestId());\n response.headers.set('x-adopture-timestamp', startTime.toString());\n \n if (userId) {\n response.headers.set('x-adopture-user-id', userId);\n }\n\n return response;\n };\n}\n\n/**\n * Send tracking event to Adopture API\n */\nasync function sendTrackingEvent(\n config: MiddlewareConfig,\n type: string,\n event: any\n): Promise<void> {\n const payload = {\n type,\n event,\n timestamp: now(),\n source: 'middleware',\n };\n\n try {\n // Convert to tRPC format for middleware events\n const tRPCPayload = {\n projectId: '35207190-2644-4230-b088-9ada94638e5d',\n featureId: event.featureId || `route-${payload.type}`,\n userId: event.userId || 'anonymous',\n eventType: 'feature_exposed' as const,\n sessionId: event.sessionId || `middleware-${Date.now()}`,\n metadata: event.metadata || {}\n };\n \n const response = await fetch(`${config.apiUrl || 'http://localhost:3001'}/trpc/featureAdoption.trackEvent`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n 'User-Agent': 'adopture-next-middleware/0.1.0',\n },\n body: JSON.stringify(tRPCPayload),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n } catch (error) {\n // Re-throw for caller to handle\n throw error;\n }\n}\n\n/**\n * Extract user ID from request headers\n */\nfunction extractUserIdFromHeaders(headers: Headers): string | null {\n // Try authorization header\n const authHeader = headers.get('authorization');\n if (authHeader) {\n // Extract from Bearer token\n const match = authHeader.match(/Bearer\\s+(.+)/i);\n if (match) {\n // Try to decode user ID from token (simplified)\n try {\n const tokenParts = match[1].split('.');\n if (tokenParts.length === 3) {\n // JWT-like token\n const payload = JSON.parse(atob(tokenParts[1]));\n return payload.sub || payload.userId || payload.user_id || null;\n }\n } catch {\n // Not a JWT, continue\n }\n }\n }\n\n // Try custom headers\n const userIdHeader = headers.get('x-user-id') || \n headers.get('x-user') || \n headers.get('user-id');\n if (userIdHeader) {\n return userIdHeader;\n }\n\n // Try cookie header\n const cookieHeader = headers.get('cookie');\n if (cookieHeader) {\n const cookies = parseCookies(cookieHeader);\n return cookies.userId || cookies.user_id || cookies['user-id'] || null;\n }\n\n return null;\n}\n\n/**\n * Get client IP address from request\n */\nfunction getClientIP(request: NextRequest): string {\n // Try various headers for client IP\n const forwardedFor = request.headers.get('x-forwarded-for');\n if (forwardedFor) {\n return forwardedFor.split(',')[0].trim();\n }\n\n const realIP = request.headers.get('x-real-ip');\n if (realIP) {\n return realIP;\n }\n\n const cfConnectingIP = request.headers.get('cf-connecting-ip');\n if (cfConnectingIP) {\n return cfConnectingIP;\n }\n\n return (request as any).ip || 'unknown';\n}\n\n/**\n * Parse cookie header string into object\n */\nfunction parseCookies(cookieHeader: string): { [key: string]: string } {\n const cookies: { [key: string]: string } = {};\n \n cookieHeader.split(';').forEach(cookie => {\n const [name, ...rest] = cookie.split('=');\n if (name && rest.length > 0) {\n cookies[name.trim()] = rest.join('=').trim();\n }\n });\n\n return cookies;\n}\n\n/**\n * Generate unique request ID\n */\nfunction generateRequestId(): string {\n return Math.random().toString(36).substring(2) + Date.now().toString(36);\n}\n\n/**\n * Utility function to create a basic middleware with minimal configuration\n */\nexport function simpleAdoptureMiddleware(apiKey: string, options?: {\n enableRouteTracking?: boolean;\n enablePerformanceTracking?: boolean;\n excludeRoutes?: string[];\n}) {\n return createAdoptureMiddleware({\n apiKey,\n enableRouteTracking: options?.enableRouteTracking ?? true,\n enablePerformanceTracking: options?.enablePerformanceTracking ?? false,\n excludeRoutes: options?.excludeRoutes,\n });\n}\n\n/**\n * Higher-order middleware that wraps existing middleware with Adopture tracking\n */\nexport function withAdoptureTracking<T extends NextRequest>(\n middleware: (request: T) => NextResponse | Promise<NextResponse>,\n config: MiddlewareConfig\n) {\n return async function wrappedMiddleware(request: T): Promise<NextResponse> {\n const adoptureMiddleware = createAdoptureMiddleware(config);\n \n // Run Adopture tracking first\n await adoptureMiddleware(request);\n \n // Then run the original middleware\n return middleware(request);\n };\n}\n\n/**\n * Configuration helper for common middleware patterns\n */\nexport const middlewarePresets = {\n /**\n * Basic tracking for all routes\n */\n basic: (apiKey: string): MiddlewareConfig => ({\n apiKey,\n enableRouteTracking: true,\n enablePerformanceTracking: false,\n }),\n\n /**\n * API-focused tracking\n */\n api: (apiKey: string): MiddlewareConfig => ({\n apiKey,\n enableRouteTracking: true,\n enablePerformanceTracking: true,\n includeRoutes: ['/api/*'],\n }),\n\n /**\n * Page-focused tracking (exclude API routes)\n */\n pages: (apiKey: string): MiddlewareConfig => ({\n apiKey,\n enableRouteTracking: true,\n enablePerformanceTracking: false,\n excludeRoutes: ['/api/*', '/_next/*', '/favicon.ico'],\n }),\n\n /**\n * Full tracking with performance metrics\n */\n full: (apiKey: string): MiddlewareConfig => ({\n apiKey,\n enableRouteTracking: true,\n enablePerformanceTracking: true,\n excludeRoutes: ['/_next/*', '/favicon.ico'],\n }),\n};"]}