add meta pixel integration
This commit is contained in:
190
src/lib/MetaPixel.svelte
Normal file
190
src/lib/MetaPixel.svelte
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<script lang="ts" module>
|
||||||
|
export class PixelControl {
|
||||||
|
private _pixelID: string;
|
||||||
|
private _testEventCode?: string = undefined;
|
||||||
|
private _trackingManager: MaybeGetter<TrackingManager | undefined>;
|
||||||
|
|
||||||
|
private static _baseLoaded: boolean = false;
|
||||||
|
|
||||||
|
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,
|
||||||
|
testEventCode?: string
|
||||||
|
) {
|
||||||
|
this._trackingManager = trackingManager;
|
||||||
|
this._pixelID = pixelID;
|
||||||
|
this._testEventCode = testEventCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads the Meta Pixel base script. */
|
||||||
|
static load() {
|
||||||
|
if (this._baseLoaded && window._fbq) return;
|
||||||
|
if (!window._fbq) {
|
||||||
|
PixelControl.revokeConsent(); // Initialize without consent
|
||||||
|
loadMetaPixel(); // Load the Meta Pixel script
|
||||||
|
}
|
||||||
|
this._baseLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells the Meta pixel that the user has given consent for tracking. */
|
||||||
|
static grantConsent() {
|
||||||
|
this.loadGuard();
|
||||||
|
window.fbq?.('consent', 'grant');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tells the Meta pixel that the user has revoked consent for tracking. */
|
||||||
|
static revokeConsent() {
|
||||||
|
this.loadGuard();
|
||||||
|
window.fbq?.('consent', 'revoke');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns 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.
|
||||||
|
* @param pixelID Meta Pixel ID
|
||||||
|
* @param testEventCode Optional test event code
|
||||||
|
* @returns PixelControl instance
|
||||||
|
*/
|
||||||
|
static for(
|
||||||
|
trackingManager: MaybeGetter<TrackingManager | undefined>,
|
||||||
|
pixelID: string,
|
||||||
|
options?: {
|
||||||
|
testEventCode?: string;
|
||||||
|
advancedMatching?: AdvancedMatching;
|
||||||
|
initOptions?: InitOptions;
|
||||||
|
}
|
||||||
|
): PixelControl {
|
||||||
|
PixelControl.load();
|
||||||
|
window.fbq('init', pixelID);
|
||||||
|
return new PixelControl(trackingManager, pixelID, options?.testEventCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
window.fbq('track', 'PageView', undefined, { test_event_code: this._testEventCode });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
window.fbq('trackSingle', this._pixelID, event, params, {
|
||||||
|
eventID,
|
||||||
|
test_event_code: this._testEventCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
window.fbq('trackSingleCustom', this._pixelID, event, params, {
|
||||||
|
eventID,
|
||||||
|
test_event_code: this._testEventCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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';
|
||||||
|
|
||||||
|
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 is disabled by default until consent is granted via
|
||||||
|
* PixelControl.grantConsent().
|
||||||
|
*/
|
||||||
|
trackingManager?: TrackingManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { pixelID, testEventCode, 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.for(trackingManager, pixelID, { testEventCode });
|
||||||
|
|
||||||
|
trackingManager.runWithConsent(() => {
|
||||||
|
if (autoPageView && pixel) {
|
||||||
|
pixel.pageView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onNavigate(() => {
|
||||||
|
trackingManager?.runWithConsent(() => {
|
||||||
|
if (autoPageView && pixel) {
|
||||||
|
pixel.pageView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
351
src/lib/types/fbq.d.ts
vendored
Normal file
351
src/lib/types/fbq.d.ts
vendored
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
/**
|
||||||
|
* Meta Pixel (fbq) Type Definitions
|
||||||
|
*
|
||||||
|
* This file contains strongly-typed definitions for the Meta Pixel global
|
||||||
|
* (`fbq`) and its standard event parameter shapes. Exported types cover
|
||||||
|
* per-event params (e.g., `PurchaseParams`), the `FBQ` callable interface,
|
||||||
|
* `AdvancedMatching` for `fbq('init', ...)`, and related helper types.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```ts
|
||||||
|
* import type { FBQ, PurchaseParams } from '$lib/types/fbq';
|
||||||
|
* // call the global pixel (browser only)
|
||||||
|
* window.fbq('track', 'Purchase', { currency: 'USD', value: 9.99 } as PurchaseParams);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** A Meta Pixel identifier string. */
|
||||||
|
export type PixelId = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional fourth argument for deduplication when sending events to Conversions
|
||||||
|
* API and test events.
|
||||||
|
*/
|
||||||
|
export type EventOptions = {
|
||||||
|
eventID?: string;
|
||||||
|
test_event_code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common event parameter values shared across many standard events.
|
||||||
|
* Use `EventParamsByName` to map specific standard events to their params.
|
||||||
|
*/
|
||||||
|
export type CommonParams = Partial<{
|
||||||
|
/**
|
||||||
|
* Category of the page/product.
|
||||||
|
*/
|
||||||
|
content_category: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product IDs associated with the event, such as SKUs (e.g. ["ABC123", "XYZ456"]).
|
||||||
|
*/
|
||||||
|
content_ids: (string | number)[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the page/product.
|
||||||
|
*/
|
||||||
|
content_name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of content referenced in `content_ids`. If IDs refer to individual
|
||||||
|
* products, use 'product'. If they refer to groups of products, use 'product_group'.
|
||||||
|
*
|
||||||
|
* If empty, Meta will match the event to every item that has the same ID,
|
||||||
|
* independent of its type.
|
||||||
|
*/
|
||||||
|
content_type: 'product' | 'product_group';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of JSON objects that contains the quantity and the International
|
||||||
|
* Article Number (EAN) when applicable, or other product or content identifiers.
|
||||||
|
*
|
||||||
|
* Custom parameters can also be included in each object.
|
||||||
|
*/
|
||||||
|
contents: { id: string | number; quantity: number }[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currency for the `value` specified.
|
||||||
|
*/
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of items when checkout was initiated (used with `InitiateCheckout` event).
|
||||||
|
*/
|
||||||
|
num_items: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predicted lifetime value of a subscriber as defined by the advertiser
|
||||||
|
* and expressed as an exact value.
|
||||||
|
*/
|
||||||
|
predicted_ltv: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search string used in a search event (used with `Search` event).
|
||||||
|
*/
|
||||||
|
search_string: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows status of a registration (used with `CompleteRegistration` event).
|
||||||
|
*/
|
||||||
|
status: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value of a user performing this event to the business.
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When payment information is added in the checkout flow.
|
||||||
|
*/
|
||||||
|
export type AddPaymentInfoParams = Partial<
|
||||||
|
Pick<CommonParams, 'content_ids' | 'contents' | 'currency' | 'value'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a product is added to the shopping cart.
|
||||||
|
*/
|
||||||
|
export type AddToCartParams = Partial<
|
||||||
|
Pick<CommonParams, 'content_ids' | 'content_type' | 'contents' | 'currency' | 'value'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a product is added to a wishlist.
|
||||||
|
*/
|
||||||
|
export type AddToWishlistParams = Partial<
|
||||||
|
Pick<CommonParams, 'content_ids' | 'contents' | 'currency' | 'value'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a registration form is completed.
|
||||||
|
*/
|
||||||
|
export type CompleteRegistrationParams = Partial<Pick<CommonParams, 'currency' | 'value'>> & {
|
||||||
|
status?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person initiates contact with your business.
|
||||||
|
*/
|
||||||
|
export type ContactParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person customizes a product.
|
||||||
|
*/
|
||||||
|
export type CustomizeProductParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person donates funds to your organization or cause.
|
||||||
|
*/
|
||||||
|
export type DonateParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person searched for a location of your store via a website or app,
|
||||||
|
* with an intention to visit the physical location.
|
||||||
|
*/
|
||||||
|
export type FindLocationParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person enters the checkout flow prior to completing a purchase.
|
||||||
|
*/
|
||||||
|
export type InitiateCheckoutParams = Partial<
|
||||||
|
Pick<CommonParams, 'content_ids' | 'contents' | 'currency' | 'num_items' | 'value'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a sign up is completed.
|
||||||
|
*/
|
||||||
|
export type LeadParams = Partial<Pick<CommonParams, 'currency' | 'value'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a purchase is made or checkout flow is completed.
|
||||||
|
*/
|
||||||
|
export type PurchaseParams = {
|
||||||
|
currency: string;
|
||||||
|
value: number;
|
||||||
|
} & Partial<Pick<CommonParams, 'content_ids' | 'content_type' | 'contents' | 'num_items'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person books an appointment to visit one of your locations.
|
||||||
|
*/
|
||||||
|
export type ScheduleParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a search is made.
|
||||||
|
*/
|
||||||
|
export type SearchParams = Partial<
|
||||||
|
Pick<
|
||||||
|
CommonParams,
|
||||||
|
'content_ids' | 'content_type' | 'contents' | 'currency' | 'value' | 'search_string'
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person starts a free trial of a product or service you offer.
|
||||||
|
*/
|
||||||
|
export type StartTrialParams = Partial<Pick<CommonParams, 'currency' | 'predicted_ltv' | 'value'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person applies for a product, service, or program you offer.
|
||||||
|
*/
|
||||||
|
export type SubmitApplicationParams = Partial<CommonParams>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a person subscribes to a paid product or service you offer.
|
||||||
|
*/
|
||||||
|
export type SubscribeParams = Partial<Pick<CommonParams, 'currency' | 'predicted_ltv' | 'value'>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A visit to a web page you care about (for example, a product page or landing page).
|
||||||
|
*/
|
||||||
|
export type ViewContentParams = Partial<
|
||||||
|
Pick<CommonParams, 'content_ids' | 'content_type' | 'contents' | 'currency' | 'value'>
|
||||||
|
>;
|
||||||
|
|
||||||
|
/** Standard event names supported by Meta Pixel. */
|
||||||
|
export type StandardEventName =
|
||||||
|
| 'AddPaymentInfo'
|
||||||
|
| 'AddToCart'
|
||||||
|
| 'AddToWishlist'
|
||||||
|
| 'CompleteRegistration'
|
||||||
|
| 'Contact'
|
||||||
|
| 'CustomizeProduct'
|
||||||
|
| 'Donate'
|
||||||
|
| 'FindLocation'
|
||||||
|
| 'InitiateCheckout'
|
||||||
|
| 'Lead'
|
||||||
|
| 'Purchase'
|
||||||
|
| 'Schedule'
|
||||||
|
| 'Search'
|
||||||
|
| 'StartTrial'
|
||||||
|
| 'SubmitApplication'
|
||||||
|
| 'Subscribe'
|
||||||
|
| 'ViewContent'
|
||||||
|
| 'PageView';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advanced matching fields accepted by the Pixel init call. These fields are
|
||||||
|
* used to help match site visitors to people on Meta for better attribution.
|
||||||
|
* See Meta's Advanced Matching docs for details; values should generally be
|
||||||
|
* raw strings (or hashed values if you pre-hash on the client/server).
|
||||||
|
*/
|
||||||
|
export type AdvancedMatching = {
|
||||||
|
/** Primary contact email (or hashed email) */
|
||||||
|
email?: string;
|
||||||
|
/** Phone number (E.164 or local) */
|
||||||
|
phone?: string;
|
||||||
|
/** First name */
|
||||||
|
first_name?: string;
|
||||||
|
/** Last name */
|
||||||
|
last_name?: string;
|
||||||
|
/** City */
|
||||||
|
city?: string;
|
||||||
|
/** State/region */
|
||||||
|
state?: string;
|
||||||
|
/** Postal / ZIP code */
|
||||||
|
zip?: string;
|
||||||
|
/** Country code */
|
||||||
|
country?: string;
|
||||||
|
/** External id to match users (optional) */
|
||||||
|
external_id?: string;
|
||||||
|
/** Gender */
|
||||||
|
gender?: string;
|
||||||
|
/** Date of birth (ISO-like or YYYY-MM-DD) */
|
||||||
|
date_of_birth?: string;
|
||||||
|
// allow additional provider-specific keys
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Arbitrary init/config options from the global pixel initialization API. */
|
||||||
|
export type InitOptions = Record<string, unknown>;
|
||||||
|
|
||||||
|
/** Additional custom params for custom events. */
|
||||||
|
export type CustomParams = Record<string, unknown>;
|
||||||
|
|
||||||
|
/** Map each standard event name to its allowed parameter shape. */
|
||||||
|
export type EventParamsByName = {
|
||||||
|
AddPaymentInfo: AddPaymentInfoParams;
|
||||||
|
AddToCart: AddToCartParams;
|
||||||
|
AddToWishlist: AddToWishlistParams;
|
||||||
|
CompleteRegistration: CompleteRegistrationParams;
|
||||||
|
Contact: ContactParams;
|
||||||
|
CustomizeProduct: CustomizeProductParams;
|
||||||
|
Donate: DonateParams;
|
||||||
|
FindLocation: FindLocationParams;
|
||||||
|
InitiateCheckout: InitiateCheckoutParams;
|
||||||
|
Lead: LeadParams;
|
||||||
|
Purchase: PurchaseParams;
|
||||||
|
Schedule: ScheduleParams;
|
||||||
|
Search: SearchParams;
|
||||||
|
StartTrial: StartTrialParams;
|
||||||
|
SubmitApplication: SubmitApplicationParams;
|
||||||
|
Subscribe: SubscribeParams;
|
||||||
|
ViewContent: ViewContentParams;
|
||||||
|
PageView: Record<string, never>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Strongly-typed interface for the `fbq` global provided by Meta Pixel. */
|
||||||
|
export interface FBQ {
|
||||||
|
/**
|
||||||
|
* Initialize a pixel for this page.
|
||||||
|
* @param pixelId The pixel identifier to initialize.
|
||||||
|
* @param advancedMatching Optional advanced matching data (email/phone/etc.).
|
||||||
|
* @param options Additional init options.
|
||||||
|
*/
|
||||||
|
(cmd: 'init', pixelId: PixelId, advancedMatching?: AdvancedMatching, options?: InitOptions): void;
|
||||||
|
|
||||||
|
// Strongly-typed track overloads.
|
||||||
|
(cmd: 'track', event: 'Purchase', params: PurchaseParams, options?: EventOptions): void;
|
||||||
|
/** Generic overload for other standard events where params are optional */
|
||||||
|
<K extends Exclude<StandardEventName, 'Purchase'>>(
|
||||||
|
cmd: 'track',
|
||||||
|
event: K,
|
||||||
|
params?: EventParamsByName[K],
|
||||||
|
options?: EventOptions
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/** Custom event variant — accepts arbitrary custom keys in addition to `CommonParams`. */
|
||||||
|
(
|
||||||
|
cmd: 'trackCustom',
|
||||||
|
event: string,
|
||||||
|
params?: CommonParams & CustomParams,
|
||||||
|
options?: EventOptions
|
||||||
|
): void;
|
||||||
|
|
||||||
|
/** Single-pixel variants that mirror the typed `track` API without duplicating event shapes. */
|
||||||
|
<K extends StandardEventName>(
|
||||||
|
cmd: 'trackSingle',
|
||||||
|
pixelId: PixelId,
|
||||||
|
event: K,
|
||||||
|
params?: EventParamsByName[K],
|
||||||
|
options?: EventOptions
|
||||||
|
): void;
|
||||||
|
(
|
||||||
|
cmd: 'trackSingleCustom',
|
||||||
|
pixelId: PixelId,
|
||||||
|
event: string,
|
||||||
|
params?: CommonParams & CustomParams,
|
||||||
|
options?: EventOptions
|
||||||
|
): void;
|
||||||
|
|
||||||
|
// Configuration helpers with typed keys and values.
|
||||||
|
(cmd: 'set', key: 'autoConfig', value: boolean, pixelId?: PixelId): void;
|
||||||
|
(
|
||||||
|
cmd: 'set',
|
||||||
|
key: 'test_event_code' | 'agent' | 'eventSourceUrl',
|
||||||
|
value: string,
|
||||||
|
pixelId?: PixelId
|
||||||
|
): void;
|
||||||
|
(cmd: 'set', config: Record<string, unknown>): void;
|
||||||
|
|
||||||
|
// consent and LDU
|
||||||
|
(cmd: 'consent', state: 'grant' | 'revoke'): void;
|
||||||
|
(cmd: 'dataProcessingOptions', options: string[], countryCode?: number, stateCode?: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
fbq: FBQ;
|
||||||
|
_fbq?: FBQ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export {};
|
||||||
17
src/lib/util/getter.ts
Normal file
17
src/lib/util/getter.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* MaybeGetter is a type that can either be a value of type T or a function that returns a value of type T.
|
||||||
|
* This is useful for cases where you might want to pass a value directly or a function that computes the
|
||||||
|
* value later, potentially taking advantage of reactivity.
|
||||||
|
*/
|
||||||
|
export type MaybeGetter<T> = T | (() => T);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResolveGetter returns the underlying value stored by a MaybeGetter type.
|
||||||
|
* @returns Raw value T or function return T.
|
||||||
|
*/
|
||||||
|
export const resolveGetter = <T>(getter: MaybeGetter<T>): T => {
|
||||||
|
if (typeof getter === 'function') {
|
||||||
|
return (getter as () => T)();
|
||||||
|
}
|
||||||
|
return getter;
|
||||||
|
};
|
||||||
66
src/lib/util/meta-pixel-loader.ts
Normal file
66
src/lib/util/meta-pixel-loader.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
const SCRIPT_SRC = 'https://connect.facebook.net/en_US/fbevents.js';
|
||||||
|
|
||||||
|
type QueuedFBQ = ((...args: unknown[]) => void) & {
|
||||||
|
queue?: unknown[][];
|
||||||
|
callMethod?: (...args: unknown[]) => void;
|
||||||
|
loaded?: boolean;
|
||||||
|
version?: string;
|
||||||
|
push?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the Meta Pixel script and configures the `fbq` function to queue
|
||||||
|
* commands until the script is fully loaded. You may optionally await the
|
||||||
|
* returned Promise to ensure the script has loaded before proceeding.
|
||||||
|
*/
|
||||||
|
export const loadMetaPixel = (): Promise<void> => {
|
||||||
|
// Make sure we're using the browser
|
||||||
|
if (!browser || !window) {
|
||||||
|
return Promise.reject(new Error('Window is undefined'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If fbq is already defined, resolve immediately
|
||||||
|
const existing = window.fbq as QueuedFBQ | undefined;
|
||||||
|
if (existing && existing.loaded) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure fbq to queue commands until Meta takes over
|
||||||
|
const q = function (...args: unknown[]) {
|
||||||
|
if (q.callMethod) {
|
||||||
|
q.callMethod(...args);
|
||||||
|
} else {
|
||||||
|
if (!q.queue) q.queue = [];
|
||||||
|
q.queue.push(args);
|
||||||
|
}
|
||||||
|
} as QueuedFBQ;
|
||||||
|
q.queue = [];
|
||||||
|
q.push = q;
|
||||||
|
q.loaded = true;
|
||||||
|
q.version = '2.0';
|
||||||
|
window.fbq = q;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Avoid adding the same script twice
|
||||||
|
const existingScript = document.querySelector(
|
||||||
|
`script[src="${SCRIPT_SRC}"]`
|
||||||
|
) as HTMLScriptElement | null;
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.addEventListener('load', () => resolve());
|
||||||
|
existingScript.addEventListener('error', () =>
|
||||||
|
reject(new Error('Failed to load Meta Pixel script'))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create the script element
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = SCRIPT_SRC;
|
||||||
|
script.async = true;
|
||||||
|
script.addEventListener('load', () => resolve());
|
||||||
|
script.addEventListener('error', () => reject(new Error('Failed to load Meta Pixel script')));
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user