diff --git a/src/lib/MetaPixel.svelte b/src/lib/MetaPixel.svelte index e351021..e79a6b3 100644 --- a/src/lib/MetaPixel.svelte +++ b/src/lib/MetaPixel.svelte @@ -16,7 +16,7 @@ * @throws Error if the Meta Pixel API is not loaded. */ static loadGuard(): void { - if (!this._baseLoaded || !window._fbq) { + if (!this._baseLoaded || !window.fbq) { throw new Error('Meta Pixel API has not been loaded. Call PixelControl.load() first.'); } } @@ -32,11 +32,14 @@ } /** 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 + 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.'); diff --git a/src/lib/util/meta-pixel-loader.ts b/src/lib/util/meta-pixel-loader.ts index c5485de..f9c3384 100644 --- a/src/lib/util/meta-pixel-loader.ts +++ b/src/lib/util/meta-pixel-loader.ts @@ -1,4 +1,5 @@ import { browser } from '$app/environment'; +import log from 'loglevel'; const SCRIPT_SRC = 'https://connect.facebook.net/en_US/fbevents.js'; @@ -18,12 +19,24 @@ type QueuedFBQ = ((...args: unknown[]) => void) & { export const loadMetaPixel = (): Promise => { // Make sure we're using the browser if (!browser || !window) { - return Promise.reject(new Error('Window is undefined')); + return Promise.reject(new Error(`Not in browser, can't access window`)); } // If fbq is already defined, resolve immediately const existing = window.fbq as QueuedFBQ | undefined; - if (existing && existing.loaded) { + if (existing) { + 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(); } @@ -41,17 +54,14 @@ export const loadMetaPixel = (): Promise => { q.loaded = true; q.version = '2.0'; window.fbq = q; + 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; + const existingScript = getExistingScript(); if (existingScript) { - existingScript.addEventListener('load', () => resolve()); - existingScript.addEventListener('error', () => - reject(new Error('Failed to load Meta Pixel script')) - ); + attachToScript(existingScript, resolve, reject); + log.debug('Meta Pixel script already present, waiting for load'); return; } @@ -59,8 +69,23 @@ export const loadMetaPixel = (): Promise => { 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'))); + 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'))); +};