Breaks SvelteKit SPA which doesn't allow use of history API, requiring its own wrapper to be used instead.
121 lines
3.9 KiB
TypeScript
121 lines
3.9 KiB
TypeScript
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<void> => {
|
|
// 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')));
|
|
};
|