UNPKG

personalization-middleware

Version:

Next.js middleware for request-based personalization

1 lines 8.66 kB
{"version":3,"sources":["../src/middleware.ts","../src/contextCollector.ts","../src/segmentEvaluator.ts"],"sourcesContent":["import { NextRequest, NextResponse } from \"next/server\";\nimport { collectRequestContext } from \"./contextCollector\";\nimport { createSegmentEvaluator } from \"./segmentEvaluator\";\n\nexport interface MiddlewareConfig {\n /**\n * The API endpoint that evaluates segments based on context\n */\n apiEndpoint: string;\n\n /**\n * API key for authentication\n */\n apiKey: string;\n\n /**\n * Custom header name for segments (default: 'x-user-segments')\n */\n headerName?: string;\n}\n\nconst DEFAULT_CONFIG = {\n headerName: \"x-user-segments\",\n};\n\nexport function createMiddleware(config: MiddlewareConfig) {\n const finalConfig = { ...DEFAULT_CONFIG, ...config };\n const evaluator = createSegmentEvaluator({\n apiEndpoint: config.apiEndpoint,\n apiKey: config.apiKey,\n });\n\n return async function middleware(request: NextRequest) {\n console.log(\"🔍 Middleware triggered for:\", request.url);\n\n try {\n // 1. Collect context from the request\n const context = collectRequestContext(request);\n\n // 2. Evaluate segments using the context\n const result = await evaluator.evaluateSegments(context);\n\n console.log(\"🔍 Result:\", result);\n\n // 3. Create a single response\n const response = NextResponse.next();\n\n // 4. Set the header by joining the segment strings directly\n const segments = result.segments?.join(\",\") || \"\";\n response.headers.set(finalConfig.headerName, segments);\n\n // 5. Return the response\n return response;\n } catch (error) {\n // In case of any error from the above steps, log it and return\n // a clean response with an empty segment header.\n console.error(\"Personalization middleware error:\", error);\n const response = NextResponse.next();\n response.headers.set(finalConfig.headerName, \"\");\n return response;\n }\n };\n}\n\n// Export a default middleware creator for convenience\nexport const createPersonalizationMiddleware = createMiddleware;\n\nexport const config = {\n matcher: [\"/((?!_next/static|_next/image|favicon.ico|public/).*)\"],\n};\n","import { NextRequest } from \"next/server\";\nimport { UAParser } from \"ua-parser-js\";\nimport { RequestContext, UtmParams } from \"./types\";\n\nconst SESSION_COOKIE_NAME = \"personalization_session\";\nconst USER_ID_COOKIE_NAME = \"user_id\";\n\nfunction extractDeviceInfo(userAgent: string) {\n const parser = new UAParser(userAgent);\n const browser = parser.getBrowser();\n const os = parser.getOS();\n const device = parser.getDevice();\n\n return {\n deviceType: device.type || undefined,\n browser: browser.name || undefined,\n os: os.name || undefined,\n };\n}\n\nfunction extractUtmParams(request: NextRequest): UtmParams {\n const searchParams = request.nextUrl.searchParams;\n const utm: UtmParams = {};\n\n for (const [key, value] of searchParams.entries()) {\n if (key.startsWith(\"utm_\")) {\n utm[key as keyof UtmParams] = value;\n }\n }\n\n return utm;\n}\n\n// This would typically be replaced with a real geolocation service\n// For now, we'll extract it from a header if available\nfunction extractGeolocation(request: NextRequest) {\n const geoHeader = request.headers.get(\"x-geo-location\");\n if (!geoHeader) return undefined;\n\n try {\n return JSON.parse(geoHeader);\n } catch {\n return undefined;\n }\n}\n\nexport function collectRequestContext(request: NextRequest): RequestContext {\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n\n return {\n utm: extractUtmParams(request),\n device: extractDeviceInfo(userAgent),\n geolocation: extractGeolocation(request),\n userId: request.cookies.get(USER_ID_COOKIE_NAME)?.value,\n sessionId: request.cookies.get(SESSION_COOKIE_NAME)?.value,\n referrer: request.headers.get(\"referer\") || undefined,\n // Add any custom attributes here if needed\n customAttributes: {},\n };\n}\n","import {\n RequestContext,\n Segment,\n SegmentEvaluationResult,\n SegmentEvaluatorConfig,\n} from \"./types\";\n\nexport class SegmentEvaluator {\n private config: Required<SegmentEvaluatorConfig>;\n\n constructor(config: SegmentEvaluatorConfig) {\n this.config = {\n timeout: 5000,\n maxRetries: 2,\n ...config,\n };\n }\n\n async evaluateSegments(\n context: RequestContext\n ): Promise<SegmentEvaluationResult> {\n try {\n console.log(\n `[SegmentEvaluator] Fetching segments from ${this.config.apiEndpoint}`\n );\n const response = await fetch(this.config.apiEndpoint, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.config.apiKey}`,\n },\n body: JSON.stringify({ context }),\n });\n\n if (!response.ok) {\n console.error(\n `[SegmentEvaluator] API request failed with status: ${response.status}`\n );\n const errorBody = await response.text();\n console.error(`[SegmentEvaluator] Error body: ${errorBody}`);\n // Do not throw, return a safe value\n return { segments: [], context, timestamp: Date.now(), requestId: \"\" };\n }\n\n const result = await response.json();\n console.log(`[SegmentEvaluator] Successfully received segments.`);\n return result;\n } catch (error) {\n console.error(\n \"[SegmentEvaluator] Critical error during segment evaluation:\",\n error\n );\n // Do not throw, return a safe value\n return { segments: [], context, timestamp: Date.now(), requestId: \"\" };\n }\n }\n}\n\n// Helper function to create a segment evaluator with default config\nexport function createSegmentEvaluator(\n config: SegmentEvaluatorConfig\n): SegmentEvaluator {\n return new SegmentEvaluator(config);\n}\n"],"mappings":"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","names":["config","config"]}