personalization-middleware
Version:
Next.js middleware for request-based personalization
137 lines (134 loc) • 4.18 kB
JavaScript
// src/middleware.ts
import { NextResponse } from "next/server";
// src/contextCollector.ts
import { UAParser } from "ua-parser-js";
var SESSION_COOKIE_NAME = "personalization_session";
var USER_ID_COOKIE_NAME = "user_id";
function extractDeviceInfo(userAgent) {
const parser = new UAParser(userAgent);
const browser = parser.getBrowser();
const os = parser.getOS();
const device = parser.getDevice();
return {
deviceType: device.type || void 0,
browser: browser.name || void 0,
os: os.name || void 0
};
}
function extractUtmParams(request) {
const searchParams = request.nextUrl.searchParams;
const utm = {};
for (const [key, value] of searchParams.entries()) {
if (key.startsWith("utm_")) {
utm[key] = value;
}
}
return utm;
}
function extractGeolocation(request) {
const geoHeader = request.headers.get("x-geo-location");
if (!geoHeader) return void 0;
try {
return JSON.parse(geoHeader);
} catch {
return void 0;
}
}
function collectRequestContext(request) {
const userAgent = request.headers.get("user-agent") || "";
return {
utm: extractUtmParams(request),
device: extractDeviceInfo(userAgent),
geolocation: extractGeolocation(request),
userId: request.cookies.get(USER_ID_COOKIE_NAME)?.value,
sessionId: request.cookies.get(SESSION_COOKIE_NAME)?.value,
referrer: request.headers.get("referer") || void 0,
// Add any custom attributes here if needed
customAttributes: {}
};
}
// src/segmentEvaluator.ts
var SegmentEvaluator = class {
constructor(config2) {
this.config = {
timeout: 5e3,
maxRetries: 2,
...config2
};
}
async evaluateSegments(context) {
try {
console.log(
`[SegmentEvaluator] Fetching segments from ${this.config.apiEndpoint}`
);
const response = await fetch(this.config.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.config.apiKey}`
},
body: JSON.stringify({ context })
});
if (!response.ok) {
console.error(
`[SegmentEvaluator] API request failed with status: ${response.status}`
);
const errorBody = await response.text();
console.error(`[SegmentEvaluator] Error body: ${errorBody}`);
return { segments: [], context, timestamp: Date.now(), requestId: "" };
}
const result = await response.json();
console.log(`[SegmentEvaluator] Successfully received segments.`);
return result;
} catch (error) {
console.error(
"[SegmentEvaluator] Critical error during segment evaluation:",
error
);
return { segments: [], context, timestamp: Date.now(), requestId: "" };
}
}
};
function createSegmentEvaluator(config2) {
return new SegmentEvaluator(config2);
}
// src/middleware.ts
var DEFAULT_CONFIG = {
headerName: "x-user-segments"
};
function createMiddleware(config2) {
const finalConfig = { ...DEFAULT_CONFIG, ...config2 };
const evaluator = createSegmentEvaluator({
apiEndpoint: config2.apiEndpoint,
apiKey: config2.apiKey
});
return async function middleware(request) {
console.log("\u{1F50D} Middleware triggered for:", request.url);
try {
const context = collectRequestContext(request);
const result = await evaluator.evaluateSegments(context);
console.log("\u{1F50D} Result:", result);
const response = NextResponse.next();
const segments = result.segments?.join(",") || "";
response.headers.set(finalConfig.headerName, segments);
return response;
} catch (error) {
console.error("Personalization middleware error:", error);
const response = NextResponse.next();
response.headers.set(finalConfig.headerName, "");
return response;
}
};
}
var createPersonalizationMiddleware = createMiddleware;
var config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|public/).*)"]
};
export {
collectRequestContext,
config,
createMiddleware,
createPersonalizationMiddleware,
createSegmentEvaluator
};
//# sourceMappingURL=index.mjs.map