102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { getFbpFbc } from '../metapixel/fbc.ts';
|
|
import type { TrackingManager } from '$lib/tracking.svelte';
|
|
import {
|
|
capiErrorBodySchema,
|
|
capiResponseBodySchema,
|
|
type CAPIErrorBody,
|
|
type CAPIRequestBody,
|
|
type CAPIResponseBody
|
|
} from './handle.ts';
|
|
import type { CAPICustomerInfoParams, CAPIEvent } from './event.ts';
|
|
import * as v from 'valibot';
|
|
import { dev } from '$app/environment';
|
|
|
|
/**
|
|
* Client abstracts HTTP communication with a spectator server endpoint for
|
|
* sending conversion events to Meta Conversions API.
|
|
*/
|
|
export class CAPIClient {
|
|
private _href: string;
|
|
private _trackingManager: TrackingManager;
|
|
|
|
/**
|
|
* Creates a new CAPIClient.
|
|
*
|
|
* @param serverHref - The spectator server endpoint URL.
|
|
* @param trackingManager - The tracking manager instance, used for consent status.
|
|
*/
|
|
constructor(serverHref: string, trackingManager: TrackingManager) {
|
|
this._href = serverHref;
|
|
this._trackingManager = trackingManager;
|
|
}
|
|
|
|
/**
|
|
* Sends CAPIEvents to the server endpoint. If no consent is given, no events
|
|
* are sent and a dummy response is returned.
|
|
* @param events - The array of CAPIEvents to send.
|
|
* @returns A promise that resolves to the server response.
|
|
* @throws Will throw an error if input or response shape validation fails.
|
|
*/
|
|
async sendEvents(events: CAPIEvent[]): Promise<CAPIResponseBody | CAPIErrorBody> {
|
|
// Respond with an empty response if consent is not given
|
|
if (!this._trackingManager.haveUserConsent()) {
|
|
if (dev) {
|
|
console.warn(`[CAPIClient] Consent not given. Skipping sending ${events.length} event(s).`);
|
|
}
|
|
return {
|
|
fbtrace_id: '',
|
|
events_received: 0,
|
|
messages: []
|
|
};
|
|
}
|
|
|
|
// Attempt to build enriched user data
|
|
const { fbp, fbc } = getFbpFbc();
|
|
const enrichedUserData: Partial<CAPICustomerInfoParams> = { fbp, fbc };
|
|
|
|
// Build request body
|
|
const body: CAPIRequestBody = {
|
|
events: events.map((e) => {
|
|
e.enrichUserData(enrichedUserData);
|
|
return e.toObject();
|
|
})
|
|
};
|
|
try {
|
|
v.parse(v.object({ events: v.array(v.any()) }), body); // Validate body shape
|
|
} catch (err) {
|
|
throw new Error(`[CAPIClient] Invalid request body shape: ${(err as Error).message}`);
|
|
}
|
|
|
|
const response = await fetch(this._href, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(body)
|
|
});
|
|
const json = await response.json();
|
|
|
|
try {
|
|
if (response.ok) {
|
|
const parsed = v.parse(capiResponseBodySchema, json);
|
|
return parsed as CAPIResponseBody;
|
|
} else {
|
|
const parsed = v.parse(capiErrorBodySchema, json);
|
|
return parsed as CAPIErrorBody;
|
|
}
|
|
} catch (err) {
|
|
throw new Error(`[CAPIClient] Invalid response shape: ${(err as Error).message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shorthand for sending a single CAPIEvent to the server endpoint.
|
|
* @param event - The CAPIEvent to send.
|
|
* @returns A promise that resolves to the server response.
|
|
* @throws Will throw an error if input or response shape validation fails.
|
|
*/
|
|
async trackEvent(event: CAPIEvent): Promise<CAPIResponseBody | CAPIErrorBody> {
|
|
return this.sendEvents([event]);
|
|
}
|
|
}
|