pixel: refactor to separate files
This commit is contained in:
@@ -1,352 +0,0 @@
|
|||||||
<!-- @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>
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PixelControl {
|
|
||||||
private _pixelID: string;
|
|
||||||
private _testEventCode?: string = undefined;
|
|
||||||
private _trackingManager: MaybeGetter<TrackingManager | undefined>;
|
|
||||||
private _conversionClient?: ConversionClient = undefined;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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<TrackingManager | undefined>,
|
|
||||||
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<TrackingManager | undefined>,
|
|
||||||
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<K extends StandardEventName>(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.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
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 { onNavigate } from '$app/navigation';
|
|
||||||
import { resolveGetter, type MaybeGetter } from './util/getter.ts';
|
|
||||||
import log from 'loglevel';
|
|
||||||
import { dev } from '$app/environment';
|
|
||||||
import { ConversionClient } from './conversion/client.ts';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
/**
|
|
||||||
* Tracking manager to handle user consent for tracking. If omitted
|
|
||||||
* tracking is disabled by default until consent is granted via
|
|
||||||
* PixelControl.grantConsent().
|
|
||||||
*/
|
|
||||||
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, pixelOptions, autoPageView = true, trackingManager }: Props = $props();
|
|
||||||
|
|
||||||
let pixel = $state<PixelControl | null>(null);
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (!trackingManager) {
|
|
||||||
throw new Error('MetaPixel component requires a TrackingManager to manage consent.');
|
|
||||||
}
|
|
||||||
PixelControl.load();
|
|
||||||
pixel = PixelControl.initialize(trackingManager, pixelID, pixelOptions);
|
|
||||||
|
|
||||||
trackingManager.runWithConsent(() => {
|
|
||||||
if (autoPageView && pixel) {
|
|
||||||
pixel.pageView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
onNavigate(() => {
|
|
||||||
trackingManager?.runWithConsent(() => {
|
|
||||||
if (autoPageView && pixel) {
|
|
||||||
pixel.pageView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getPixelControl = (): PixelControl => {
|
|
||||||
if (!pixel) {
|
|
||||||
throw new Error('MetaPixel component has not been initialized yet, wait for onMount.');
|
|
||||||
}
|
|
||||||
return pixel;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -3,8 +3,9 @@
|
|||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
|
||||||
|
export type * from './types/conversion.d.ts';
|
||||||
export type * as fbq from './types/fbq.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 * from './tracking.svelte.ts';
|
||||||
export { default as Umami } from './Umami.svelte';
|
export { default as Umami } from './Umami.svelte';
|
||||||
export * from './conversion/index.ts';
|
export * from './conversion/index.ts';
|
||||||
|
|||||||
68
src/lib/metapixel/MetaPixel.svelte
Normal file
68
src/lib/metapixel/MetaPixel.svelte
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<!-- @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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import type { TrackingManager } from '../tracking.svelte.ts';
|
||||||
|
import { onNavigate } from '$app/navigation';
|
||||||
|
import { PixelControl, type PixelControlOptions } from './pixel-control.ts';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
/**
|
||||||
|
* Tracking manager to handle user consent for tracking. If omitted
|
||||||
|
* tracking is disabled by default until consent is granted via
|
||||||
|
* PixelControl.grantConsent().
|
||||||
|
*/
|
||||||
|
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, pixelOptions, autoPageView = true, trackingManager }: Props = $props();
|
||||||
|
|
||||||
|
let pixel = $state<PixelControl | null>(null);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!trackingManager) {
|
||||||
|
throw new Error('MetaPixel component requires a TrackingManager to manage consent.');
|
||||||
|
}
|
||||||
|
PixelControl.load();
|
||||||
|
pixel = PixelControl.initialize(trackingManager, pixelID, pixelOptions);
|
||||||
|
|
||||||
|
trackingManager.runWithConsent(() => {
|
||||||
|
if (autoPageView && pixel) {
|
||||||
|
pixel.pageView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onNavigate(() => {
|
||||||
|
trackingManager?.runWithConsent(() => {
|
||||||
|
if (autoPageView && pixel) {
|
||||||
|
pixel.pageView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getPixelControl = (): PixelControl => {
|
||||||
|
if (!pixel) {
|
||||||
|
throw new Error('MetaPixel component has not been initialized yet, wait for onMount.');
|
||||||
|
}
|
||||||
|
return pixel;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
2
src/lib/metapixel/index.ts
Normal file
2
src/lib/metapixel/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as MetaPixel } from './MetaPixel.svelte';
|
||||||
|
export { PixelControl, type PixelControlOptions } from './pixel-control.ts';
|
||||||
284
src/lib/metapixel/pixel-control.ts
Normal file
284
src/lib/metapixel/pixel-control.ts
Normal file
@@ -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<TrackingManager | undefined>;
|
||||||
|
private _conversionClient?: ConversionClient = undefined;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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<TrackingManager | undefined>,
|
||||||
|
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<TrackingManager | undefined>,
|
||||||
|
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<K extends StandardEventName>(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.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user