import { json, type RequestHandler } from '@sveltejs/kit'; import { StatusCodes } from 'http-status-codes'; import { getFbpFbcFromCookies } from '../metapixel/fbc.ts'; import type { CAPIConnector } from './connector.ts'; import { getRequestIP } from '../util/ip.ts'; import * as v from 'valibot'; import { CAPIEvent, MetaServerEventParamsSchema, type CAPICustomerInfoParams } from './event.ts'; export const capiRequestBodySchema = v.object({ events: v.array(MetaServerEventParamsSchema) }); /** Request body for conversion events */ export type CAPIRequestBody = v.InferOutput; export const capiErrorBodySchema = v.object({ error: v.string() }); /** Returned by the conversion request handler in case of an error */ export type CAPIErrorBody = v.InferOutput; export const capiResponseBodySchema = v.object({ pixelID: v.string(), fbTraceID: v.string(), receivedEvents: v.number(), processedEvents: v.number(), messages: v.array(v.string()) }); /** Returned by the conversion request handler in case of a successful response */ export type CAPIResponseBody = v.InferOutput; /** * Creates a SvelteKit request handler for processing conversion events. * * @param connector - The CAPIConnector instance to send events through. * @returns A SvelteKit RequestHandler function. */ export const createCAPIHandler: (connector: CAPIConnector) => RequestHandler = (connector) => { const handle: RequestHandler = async ({ request, getClientAddress, cookies }) => { try { const json = await request.json(); const parsed = v.parse(capiRequestBodySchema, json); // Build enriched user data with IP, user agent, and fbp/fbc from cookies const ip = getRequestIP(request, getClientAddress); const ua = request.headers.get('user-agent') ?? undefined; const { fbp, fbc } = getFbpFbcFromCookies(cookies); const enrichedUserData: Partial = { clientIP: ip, clientUserAgent: ua, fbp, fbc }; // Enrich each event's user data const events: CAPIEvent[] = parsed.events.map((eventParams) => { const event = CAPIEvent.fromObject(eventParams); event.enrichUserData(enrichedUserData); return event; }); // Send the event via the control const response = await connector.sendEvents(events); return json(response, { status: StatusCodes.OK }); } catch (e) { const response: CAPIErrorBody = { error: e instanceof Error ? e.message : String(e) }; return json(response, { status: StatusCodes.INTERNAL_SERVER_ERROR }); } }; return handle; };