Files
spectator/src/lib/capi/connector.ts
2025-12-22 16:57:06 -08:00

94 lines
2.9 KiB
TypeScript

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<CAPIResponseBody> {
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<CAPIResponseBody> {
return this.sendEvents([event]);
}
}