4 Commits

Author SHA1 Message Date
Elijah Duffy
99c1f003c6 0.0.5 2025-12-18 10:33:25 -08:00
Elijah Duffy
97497db8e4 pixel: don't send events in dev mode without a test code 2025-12-18 10:33:17 -08:00
Elijah Duffy
674663b027 0.0.4 2025-12-18 10:23:17 -08:00
Elijah Duffy
e7b12f50b1 pixel: improve fetching existing PixelControls 2025-12-18 10:23:12 -08:00
2 changed files with 121 additions and 60 deletions

View File

@@ -4,7 +4,7 @@
"type": "git", "type": "git",
"url": "https://gitea.auvem.com/svelte-toolkit/spectator.git" "url": "https://gitea.auvem.com/svelte-toolkit/spectator.git"
}, },
"version": "0.0.3", "version": "0.0.5",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

View File

@@ -1,11 +1,36 @@
<!-- @component
MetaPixel integrates the Meta (Facebook) Pixel into your Svelte application,
allowing you to track page views and custom events while respecting user consent
for tracking. The component manages the lifecycle of the Meta Pixel script and
PixelControl interface.
The PixelControl class also allows you to directly manage multiple Pixel
instances and handle event tracking with optional test event codes without
using the MetaPixel component.
-->
<script lang="ts" module> <script lang="ts" module>
export type PixelControlOptions = {
/**
* if provided, events fired will always have this code attached
* to prevent them from polluting real analytics data.
*/
testEventCode?: string;
/** Advanced matching data */
advancedMatching?: AdvancedMatching;
/** Initialization options */
initOptions?: InitOptions;
};
export class PixelControl { export class PixelControl {
private _pixelID: string; private _pixelID: string;
private _testEventCode?: string = undefined; private _testEventCode?: string = undefined;
private _trackingManager: MaybeGetter<TrackingManager | undefined>; private _trackingManager: MaybeGetter<TrackingManager | undefined>;
private static _baseLoaded: boolean = false; private static _baseLoaded: boolean = false;
private static _registeredPixels: Record<string, PixelControl> = {};
/** Indicates whether the Meta Pixel base script has been loaded. */
static get baseLoaded(): boolean { static get baseLoaded(): boolean {
return this._baseLoaded; return this._baseLoaded;
} }
@@ -24,11 +49,11 @@
private constructor( private constructor(
trackingManager: MaybeGetter<TrackingManager | undefined>, trackingManager: MaybeGetter<TrackingManager | undefined>,
pixelID: string, pixelID: string,
testEventCode?: string options?: PixelControlOptions
) { ) {
this._trackingManager = trackingManager; this._trackingManager = trackingManager;
this._pixelID = pixelID; this._pixelID = pixelID;
this._testEventCode = testEventCode; this._testEventCode = options?.testEventCode;
} }
/** Loads the Meta Pixel base script. */ /** Loads the Meta Pixel base script. */
@@ -60,43 +85,55 @@
} }
/** /**
* Returns a PixelControl instance for the given Meta Pixel ID. If * Registers a PixelControl instance for the given Meta Pixel ID. If
* the base Meta Pixel script has not been loaded yet, it will be * the base Meta Pixel script has not been loaded yet, it will be
* loaded automatically. Optionally sets a test event code for the Pixel. * loaded automatically. Optionally sets a test event code for the Pixel.
* Does NOT initialize the Pixel; call `fireInit()` on the returned instance * Should only be called once for each Pixel ID, use PixelControl.get()
* before tracking events. * to retrieve existing instances.
* @param trackingManager Tracking manager to handle user consent for tracking * @param trackingManager Tracking manager to handle user consent for tracking
* @param pixelID Meta Pixel ID * @param pixelID Meta Pixel ID
* @param options Optional settings * @param options Optional settings
* @returns PixelControl instance * @returns PixelControl instance
*/ */
static for( static initialize(
trackingManager: MaybeGetter<TrackingManager | undefined>, trackingManager: MaybeGetter<TrackingManager | undefined>,
pixelID: string, pixelID: string,
options?: { options?: PixelControlOptions
/**
* if provided, events fired will always have this code attached
* to prevent them from polluting real analytics data.
*/
testEventCode?: string;
}
): PixelControl { ): PixelControl {
// Load the base script if not already loaded
PixelControl.load(); PixelControl.load();
return new PixelControl(trackingManager, pixelID, options?.testEventCode);
// 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;
} }
/** /**
* Initializes this pixel with the Meta Pixel API including any advanced * Returns an existing PixelControl instance for the given Meta Pixel ID.
* matching data and options. * @param pixelID Meta Pixel ID
* @param advancedMatching Advanced matching data * @returns PixelControl instance
* @param initOptions Initialization options * @throws Error if no PixelControl instance is found for the given ID.
* @returns this PixelControl instance
*/ */
fireInit(advancedMatching?: AdvancedMatching, initOptions?: InitOptions): PixelControl { static get(pixelID: string): PixelControl {
PixelControl.loadGuard(); const pixel = this._registeredPixels[pixelID];
window.fbq('init', this._pixelID, advancedMatching, initOptions); if (!pixel) {
log.debug(`Meta Pixel [${this._pixelID}] initialized.`); throw new Error(`No PixelControl instance found for Meta Pixel ID: ${pixelID}`);
return this; }
return pixel;
} }
/** /**
@@ -117,10 +154,17 @@
*/ */
pageView() { pageView() {
if (!this.consentGuard()) return; if (!this.consentGuard()) return;
// Send the PageView event
if (!dev || this._testEventCode) {
window.fbq('track', 'PageView', undefined, { test_event_code: this._testEventCode }); window.fbq('track', 'PageView', undefined, { test_event_code: this._testEventCode });
log.debug( log.debug(
`Meta Pixel [${this._pixelID}] PageView event sent (test code: ${this._testEventCode}).` `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.`
);
}
} }
/** /**
@@ -129,13 +173,19 @@
*/ */
track<K extends StandardEventName>(event: K, params?: EventParamsByName[K], eventID?: string) { track<K extends StandardEventName>(event: K, params?: EventParamsByName[K], eventID?: string) {
if (!this.consentGuard()) return; if (!this.consentGuard()) return;
if (!dev || this._testEventCode) {
window.fbq('trackSingle', this._pixelID, event, params, { window.fbq('trackSingle', this._pixelID, event, params, {
eventID, eventID,
test_event_code: this._testEventCode test_event_code: this._testEventCode
}); });
log.debug( log.debug(
`Meta Pixel [${this._pixelID}] ${event} event sent (test code: ${this._testEventCode}).` `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.`
);
}
} }
/** /**
@@ -144,6 +194,7 @@
*/ */
trackCustom(event: string, params?: CommonParams & CustomParams, eventID?: string) { trackCustom(event: string, params?: CommonParams & CustomParams, eventID?: string) {
if (!this.consentGuard()) return; if (!this.consentGuard()) return;
if (!dev || this._testEventCode) {
window.fbq('trackSingleCustom', this._pixelID, event, params, { window.fbq('trackSingleCustom', this._pixelID, event, params, {
eventID, eventID,
test_event_code: this._testEventCode test_event_code: this._testEventCode
@@ -151,6 +202,11 @@
log.debug( log.debug(
`Meta Pixel [${this._pixelID}] ${event} custom event sent (test code: ${this._testEventCode}).` `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.`
);
}
} }
} }
</script> </script>
@@ -171,32 +227,30 @@
import { onNavigate } from '$app/navigation'; import { onNavigate } from '$app/navigation';
import { resolveGetter, type MaybeGetter } from './util/getter.ts'; import { resolveGetter, type MaybeGetter } from './util/getter.ts';
import log from 'loglevel'; import log from 'loglevel';
import { dev } from '$app/environment';
interface Props { interface Props {
/** Meta Pixel ID */
pixelID: string;
/**
* If a test event code is available, events fired will always have this
* code attached to prevent them from polluting real analytics data.
*/
testEventCode?: string;
/**
* Controls whether page views are automatically tracked by this
* component (default: true).
*/
autoPageView?: boolean;
/** /**
* Tracking manager to handle user consent for tracking. If omitted * Tracking manager to handle user consent for tracking. If omitted
* tracking is disabled by default until consent is granted via * tracking is disabled by default until consent is granted via
* PixelControl.grantConsent(). * PixelControl.grantConsent().
*/ */
trackingManager?: TrackingManager; trackingManager?: TrackingManager;
/** Meta Pixel ID */
pixelID: string;
/** Meta Pixel Options */
pixelOptions?: PixelControlOptions;
/**
* Controls whether page views are automatically tracked by this
* component (default: true).
*/
autoPageView?: boolean;
} }
let { pixelID, testEventCode, autoPageView = true, trackingManager }: Props = $props(); let { pixelID, pixelOptions, autoPageView = true, trackingManager }: Props = $props();
let pixel = $state<PixelControl | null>(null); let pixel = $state<PixelControl | null>(null);
@@ -205,7 +259,7 @@
throw new Error('MetaPixel component requires a TrackingManager to manage consent.'); throw new Error('MetaPixel component requires a TrackingManager to manage consent.');
} }
PixelControl.load(); PixelControl.load();
pixel = PixelControl.for(trackingManager, pixelID, { testEventCode }).fireInit(); pixel = PixelControl.initialize(trackingManager, pixelID, pixelOptions);
trackingManager.runWithConsent(() => { trackingManager.runWithConsent(() => {
if (autoPageView && pixel) { if (autoPageView && pixel) {
@@ -221,4 +275,11 @@
} }
}); });
}); });
export const getPixelControl = (): PixelControl => {
if (!pixel) {
throw new Error('MetaPixel component has not been initialized yet, wait for onMount.');
}
return pixel;
};
</script> </script>