import { dev } from '$app/environment'; import log from 'loglevel'; import type { CAPIEvent } from './event.ts'; import * as v from 'valibot'; import { capiResponseBodySchema, type CAPIResponseBody } from './handle.ts'; const GRAPH_VERSION = 'v24.0'; /** * Connector class for Meta Conversion API (CAPI). Abstraction over direct HTTP * requests to Meta's CAPI endpoint. * * See https://developers.facebook.com/docs/marketing-api/conversions-api/get-started * for more information. */ export class CAPIConnector { private _accessToken: string; private _pixelID: string; private _testEventCode?: string; /** * Creates a new MCAPIControl instance. * * @param accessToken - Your Meta Pixel Conversion API access token. * @param pixelID - Your Meta Pixel ID. * @param testEventCode - Optional test event code used for all events if provided. */ constructor(accessToken: string, pixelID: string, testEventCode?: string) { this._accessToken = accessToken; this._pixelID = pixelID; this._testEventCode = testEventCode; } /** * Sends conversion events to the Meta Conversion API. * * @param events - Array of CAPIEvent instances to send. * @returns The response from the Meta CAPI. * @throws Will throw an error if the request fails or the API returns an error. */ async sendEvents(events: CAPIEvent[]): Promise { if (dev && !this._testEventCode) { log.warn( `[CAPIConnector] Sending ${events.length} event(s) in dev mode without a test event code. ` + 'Consider providing a test event code to avoid affecting real data.' ); } const url = `https://graph.facebook.com/${GRAPH_VERSION}/${this._pixelID}/events`; const body = { data: events.map((e) => e.toObject()), test_event_code: this._testEventCode }; log.debug( `[CAPIConnector] [${this._pixelID}] Sending ${events.length} event(s) to Meta CAPI at ${url} with body: ${JSON.stringify(body, null, 2)}` ); const resp = await fetch(`${url}?access_token=${this._accessToken}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); const json = await resp.json(); if (!resp.ok) { throw new Error(`Meta CAPI error ${resp.status}: ${JSON.stringify(json, null, 2)}`); } try { const parsed = v.parse(capiResponseBodySchema, json); log.info( `[CAPIConnector] [${this._pixelID}] Successfully sent ${events.length} event(s) to Meta CAPI.` ); return parsed as CAPIResponseBody; } catch (err) { throw new Error(`[CAPIConnector] Invalid response shape: ${(err as Error).message}`); } } /** * Shorthand for sending a single event to the Meta Conversion API. * * @param event - The CAPIEvent instance to send. * @returns The response from the Meta CAPI. * @throws Will throw an error if the request fails or the API returns an error. */ async trackEvent(event: CAPIEvent): Promise { return this.sendEvents([event]); } }