import { browser } from '$app/environment'; import log from 'loglevel'; 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; disablePushState?: boolean; allowDuplicatePageViews?: boolean; }; /** * 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. * * Options: * - `disablePushState` (default: true) — when true, sets * `window.fbq.disablePushState = true` before the pixel script loads so the * pixel does not auto-listen to `history.pushState`/`popstate` (recommended * for SPA frameworks like Svelte). * - `allowDuplicatePageViews` (default: false) — when true, sets * `window.fbq.allowDuplicatePageViews = true` on the stub. */ export const loadMetaPixel = (opts?: { disablePushState?: boolean; allowDuplicatePageViews?: boolean; }): Promise => { // Make sure we're using the browser if (!browser || !window) { return Promise.reject(new Error(`Not in browser, can't access window`)); } // Default behavior: disable pushState handling since Svelte apps manage // navigation themselves and Meta's auto-patching of history APIs can // cause duplicate/incorrect pageview events. Consumers can pass // `opts.disablePushState = false` to opt out. const disablePushState = opts?.disablePushState ?? true; const allowDuplicatePageViews = opts?.allowDuplicatePageViews ?? false; // If fbq is already defined, resolve immediately const existing = window.fbq as QueuedFBQ | undefined; if (existing) { // If the existing stub is present but hasn't set these flags yet, set // them now so the loaded library (if it inspects them) sees intended // behavior. Setting these is a no-op if initialization already // completed. if (disablePushState) existing.disablePushState = true; if (allowDuplicatePageViews) existing.allowDuplicatePageViews = true; const existingScript = getExistingScript(); if (existingScript) { return new Promise((resolve, reject) => { attachToScript(existingScript, resolve, reject); }); } log.debug( 'Meta Pixel fbq already present, skipping injection', existing.version, existing.queue ); 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'; // set control flags on the stub before the meta script runs if (disablePushState) q.disablePushState = true; if (allowDuplicatePageViews) q.allowDuplicatePageViews = true; window.fbq = q; window._fbq = q; return new Promise((resolve, reject) => { // Avoid adding the same script twice const existingScript = getExistingScript(); if (existingScript) { attachToScript(existingScript, resolve, reject); log.debug('Meta Pixel script already present, waiting for load'); return; } // Otherwise, create the script element const script = document.createElement('script'); script.src = SCRIPT_SRC; script.async = true; attachToScript(script, resolve, reject); document.head.appendChild(script); log.debug('Meta Pixel script added to document'); }); }; const getExistingScript = (): HTMLScriptElement | null => { return document.querySelector( `script[src*="connect.facebook.net"][src*="fbevents.js"]` ) as HTMLScriptElement | null; }; const attachToScript = ( el: HTMLScriptElement, resolve: () => void, reject: (err: Error) => void ) => { el.addEventListener('load', () => resolve()); el.addEventListener('error', () => reject(new Error('Failed to load Meta Pixel script'))); };