diff --git a/src/lib/MetaPixel.svelte b/src/lib/MetaPixel.svelte
deleted file mode 100644
index e2d6106..0000000
--- a/src/lib/MetaPixel.svelte
+++ /dev/null
@@ -1,352 +0,0 @@
-
-
-
-
-
diff --git a/src/lib/index.ts b/src/lib/index.ts
index be123e9..5460a6b 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -3,8 +3,9 @@
import { dev } from '$app/environment';
import log from 'loglevel';
+export type * from './types/conversion.d.ts';
export type * as fbq from './types/fbq.d.ts';
-export { default as MetaPixel, PixelControl } from './MetaPixel.svelte';
+export * from './metapixel/index.ts';
export * from './tracking.svelte.ts';
export { default as Umami } from './Umami.svelte';
export * from './conversion/index.ts';
diff --git a/src/lib/metapixel/MetaPixel.svelte b/src/lib/metapixel/MetaPixel.svelte
new file mode 100644
index 0000000..b27bb9a
--- /dev/null
+++ b/src/lib/metapixel/MetaPixel.svelte
@@ -0,0 +1,68 @@
+
+
+
diff --git a/src/lib/metapixel/index.ts b/src/lib/metapixel/index.ts
new file mode 100644
index 0000000..654cbaa
--- /dev/null
+++ b/src/lib/metapixel/index.ts
@@ -0,0 +1,2 @@
+export { default as MetaPixel } from './MetaPixel.svelte';
+export { PixelControl, type PixelControlOptions } from './pixel-control.ts';
diff --git a/src/lib/metapixel/pixel-control.ts b/src/lib/metapixel/pixel-control.ts
new file mode 100644
index 0000000..59c2794
--- /dev/null
+++ b/src/lib/metapixel/pixel-control.ts
@@ -0,0 +1,284 @@
+import type { TrackingManager } from '../tracking.svelte.ts';
+import type {
+ EventParamsByName,
+ StandardEventName,
+ CommonParams,
+ CustomParams,
+ AdvancedMatching,
+ InitOptions
+} from '../types/fbq.js';
+import { loadMetaPixel } from '../util/meta-pixel-loader.ts';
+import { resolveGetter, type MaybeGetter } from '../util/getter.ts';
+import log from 'loglevel';
+import { dev } from '$app/environment';
+import { ConversionClient } from '../conversion/client.ts';
+
+export type PixelControlOptions = {
+ /**
+ * if provided, events fired will always have this code attached
+ * to prevent them from polluting real analytics data.
+ */
+ testEventCode?: string;
+ /**
+ * if provided, all events fired will be passed to the server endpoint
+ * at this URL to be sent via the Conversion API. Any events sent
+ * without a event ID will be assigned a random one.
+ */
+ conversionHref?: string;
+ /** Advanced matching data */
+ advancedMatching?: AdvancedMatching;
+ /** Initialization options */
+ initOptions?: InitOptions;
+};
+
+/**
+ * Manages multiple Meta Pixel instances and provides methods to
+ * interact with them, including consent management and event tracking.
+ */
+export class PixelControl {
+ private _pixelID: string;
+ private _testEventCode?: string = undefined;
+ private _trackingManager: MaybeGetter;
+ private _conversionClient?: ConversionClient = undefined;
+
+ private static _baseLoaded: boolean = false;
+ private static _registeredPixels: Record = {};
+
+ /** Indicates whether the Meta Pixel base script has been loaded. */
+ static get baseLoaded(): boolean {
+ return this._baseLoaded;
+ }
+
+ /**
+ * Ensures that the Meta Pixel base has been loaded before
+ * allowing further operations.
+ * @throws Error if the Meta Pixel API is not loaded.
+ */
+ static loadGuard(): void {
+ if (!this._baseLoaded || !window.fbq) {
+ throw new Error('Meta Pixel API has not been loaded. Call PixelControl.load() first.');
+ }
+ }
+
+ private constructor(
+ trackingManager: MaybeGetter,
+ pixelID: string,
+ options?: PixelControlOptions
+ ) {
+ this._trackingManager = trackingManager;
+ this._pixelID = pixelID;
+ this._testEventCode = options?.testEventCode;
+
+ const resolvedTrackingManager = resolveGetter(trackingManager);
+ if (options?.conversionHref && resolvedTrackingManager) {
+ this._conversionClient = new ConversionClient(
+ options.conversionHref,
+ resolvedTrackingManager
+ );
+ } else if (options?.conversionHref) {
+ log.warn(
+ `Conversion Client ${options.conversionHref} for Meta Pixel [${this._pixelID}] not initialized - TrackingManager is required for user consent.`
+ );
+ }
+ }
+
+ /** Loads the Meta Pixel base script. */
+ static async load() {
+ if (this._baseLoaded && !!window.fbq) return;
+ if (!window.fbq) {
+ try {
+ await loadMetaPixel(); // Load the Meta Pixel script
+ } catch (e) {
+ log.warn('Failed to load Meta Pixel script, all events will be queued.', e);
+ }
+ }
+ this._baseLoaded = true;
+ log.debug('Meta Pixel base script loaded.');
+ }
+
+ /** Tells the Meta pixel that the user has given consent for tracking. */
+ static grantConsent() {
+ this.loadGuard();
+ window.fbq?.('consent', 'grant');
+ log.debug('Meta Pixel consent granted.');
+ }
+
+ /** Tells the Meta pixel that the user has revoked consent for tracking. */
+ static revokeConsent() {
+ this.loadGuard();
+ window.fbq?.('consent', 'revoke');
+ log.debug('Meta Pixel consent revoked.');
+ }
+
+ /**
+ * Registers a PixelControl instance for the given Meta Pixel ID. If
+ * the base Meta Pixel script has not been loaded yet, it will be
+ * loaded automatically. Optionally sets a test event code for the Pixel.
+ * Should only be called once for each Pixel ID, use PixelControl.get()
+ * to retrieve existing instances.
+ * @param trackingManager Tracking manager to handle user consent for tracking
+ * @param pixelID Meta Pixel ID
+ * @param options Optional settings
+ * @returns PixelControl instance
+ */
+ static initialize(
+ trackingManager: MaybeGetter,
+ pixelID: string,
+ options?: PixelControlOptions
+ ): PixelControl {
+ // Load the base script if not already loaded
+ PixelControl.load();
+
+ // Check for existing PixelControl instance
+ if (this._registeredPixels[pixelID]) {
+ log.warn(
+ `PixelControl instance for Meta Pixel ID: ${pixelID} already exists. Returning existing instance.`
+ );
+ return this._registeredPixels[pixelID];
+ }
+
+ // Create and register the PixelControl instance
+ const pixel = new PixelControl(trackingManager, pixelID, options);
+ this._registeredPixels[pixelID] = pixel;
+
+ // Fire initialization
+ window.fbq('init', pixel._pixelID, options?.advancedMatching, options?.initOptions);
+ log.debug(`Meta Pixel [${pixel._pixelID}] initialized.`);
+
+ return pixel;
+ }
+
+ /**
+ * Returns an existing PixelControl instance for the given Meta Pixel ID.
+ * @param pixelID Meta Pixel ID
+ * @returns PixelControl instance
+ * @throws Error if no PixelControl instance is found for the given ID.
+ */
+ static get(pixelID: string): PixelControl {
+ const pixel = this._registeredPixels[pixelID];
+ if (!pixel) {
+ throw new Error(`No PixelControl instance found for Meta Pixel ID: ${pixelID}`);
+ }
+ return pixel;
+ }
+
+ /**
+ * Checks if the Meta Pixel has consent to track user data
+ * and if the Pixel has been loaded.
+ * @returns true if tracking is allowed, false otherwise.
+ * @throws Error if the Meta Pixel is not loaded.
+ */
+ consentGuard(): boolean {
+ PixelControl.loadGuard();
+ const trackingManager = resolveGetter(this._trackingManager);
+ return trackingManager?.haveUserConsent() ?? false;
+ }
+
+ /**
+ * Sends a PageView event
+ * @throws Error if the Meta Pixel is not initialized.
+ */
+ pageView() {
+ if (!this.consentGuard()) return;
+
+ let eventID: string | undefined = undefined;
+ // Optionally, send to conversion API endpoint if configured
+ if (this._conversionClient) {
+ eventID = crypto.randomUUID();
+ this._conversionClient
+ .trackEvent('PageView', { eventID })
+ .then((response) => {
+ log.debug(
+ `Meta Pixel [${this._pixelID}] PageView event sent to Conversion API with Event ID: ${eventID}, Response: ${JSON.stringify(
+ response
+ )}`
+ );
+ })
+ .catch((error) => {
+ log.error(
+ `Meta Pixel [${this._pixelID}] Failed to send PageView event to Conversion API with Event ID: ${eventID}`,
+ error
+ );
+ });
+ }
+
+ // Send the PageView event to Meta
+ if (!dev || this._testEventCode) {
+ window.fbq('track', 'PageView', undefined, {
+ test_event_code: this._testEventCode,
+ eventID
+ });
+ log.debug(
+ `Meta Pixel [${this._pixelID}] PageView event sent${dev && ` (test code: ${this._testEventCode})`}.`
+ );
+ } else {
+ log.info(
+ `Meta Pixel [${this._pixelID}] PageView event not sent in development mode without a test event code.`
+ );
+ }
+ }
+
+ /**
+ * Tracks a standard event for this pixel (uses `trackSingle` under the hood)
+ * @throws Error if the Meta Pixel is not initialized.
+ */
+ track(event: K, params?: EventParamsByName[K], eventID?: string) {
+ if (!this.consentGuard()) return;
+
+ // Optionally, send to conversion API endpoint if configured
+ if (this._conversionClient) {
+ eventID = eventID ?? crypto.randomUUID();
+ this._conversionClient
+ .trackEvent(event, { eventID: eventID, customData: params as any })
+ .then((response) => {
+ log.debug(
+ `Meta Pixel [${this._pixelID}] ${event} event sent to Conversion API with Event ID: ${eventID}, Response: ${JSON.stringify(
+ response
+ )}`
+ );
+ })
+ .catch((error) => {
+ log.error(
+ `Meta Pixel [${this._pixelID}] Failed to send ${event} event to Conversion API with Event ID: ${eventID}`,
+ error
+ );
+ });
+ }
+
+ // Send the PageView event to Meta
+ if (!dev || this._testEventCode) {
+ window.fbq('trackSingle', this._pixelID, event, params, {
+ eventID,
+ test_event_code: this._testEventCode
+ });
+ log.debug(
+ `Meta Pixel [${this._pixelID}] ${event} event sent${dev && ` (test code: ${this._testEventCode})`}.`
+ );
+ } else {
+ log.info(
+ `Meta Pixel [${this._pixelID}] ${event} event not sent in development mode without a test event code.`
+ );
+ }
+ }
+
+ /**
+ * Tracks a custom event for this pixel (uses `trackSingleCustom` under the hood)
+ * @throws Error if the Meta Pixel is not initialized.
+ */
+ trackCustom(event: string, params?: CommonParams & CustomParams, eventID?: string) {
+ if (!this.consentGuard()) return;
+ if (!dev || this._testEventCode) {
+ window.fbq('trackSingleCustom', this._pixelID, event, params, {
+ eventID,
+ test_event_code: this._testEventCode
+ });
+ log.debug(
+ `Meta Pixel [${this._pixelID}] ${event} custom event sent (test code: ${this._testEventCode}).`
+ );
+ } else {
+ log.info(
+ `Meta Pixel [${this._pixelID}] ${event} custom event not sent in development mode without a test event code.`
+ );
+ }
+ }
+}