89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import log from 'loglevel';
|
||
|
||
export type EnsureFbcOptions = {
|
||
days?: number; // cookie lifetime, default 180
|
||
domain?: string; // optional cookie domain
|
||
sameSite?: 'Lax' | 'Strict' | 'None'; // default Lax
|
||
secure?: boolean; // default inferred from location.protocol === 'https:'
|
||
pixelLoaded?: boolean; // if true, skip manual set and let Pixel handle it
|
||
};
|
||
|
||
function getParam(name: string): string | undefined {
|
||
const params = new URLSearchParams(location.search);
|
||
const v = params.get(name);
|
||
return v || undefined;
|
||
}
|
||
|
||
function getCookie(name: string): string | undefined {
|
||
return document.cookie
|
||
.split('; ')
|
||
.find((c) => c.startsWith(name + '='))
|
||
?.split('=')[1];
|
||
}
|
||
|
||
function setCookie(
|
||
name: string,
|
||
value: string,
|
||
{
|
||
days = 180,
|
||
domain,
|
||
sameSite = 'Lax',
|
||
secure
|
||
}: Pick<EnsureFbcOptions, 'days' | 'domain' | 'sameSite' | 'secure'> = {}
|
||
) {
|
||
const d = new Date();
|
||
d.setTime(d.getTime() + days * 864e5);
|
||
const parts = [
|
||
`${name}=${encodeURIComponent(value)}`,
|
||
`expires=${d.toUTCString()}`,
|
||
'path=/',
|
||
`SameSite=${sameSite}`
|
||
];
|
||
if (domain) parts.push(`domain=${domain}`);
|
||
const isSecure = secure ?? location.protocol === 'https:';
|
||
if (isSecure) parts.push('Secure');
|
||
document.cookie = parts.join('; ');
|
||
}
|
||
|
||
function isValidFbc(value: string | undefined): boolean {
|
||
if (!value) return false;
|
||
// Expect "fb.1.<unix>.<fbclid>"
|
||
if (!value.startsWith('fb.1.')) return false;
|
||
const parts = value.split('.');
|
||
return parts.length >= 4 && /^\d+$/.test(parts[2]) && parts[3].length > 0;
|
||
}
|
||
|
||
/**
|
||
* Ensure _fbc cookie exists when landing URL contains fbclid.
|
||
* Call after consent. If pixelLoaded is true, it skips manual setting.
|
||
*/
|
||
export function ensureFbc(options: EnsureFbcOptions = {}) {
|
||
try {
|
||
const { pixelLoaded = false, ...cookieOpts } = options;
|
||
|
||
if (pixelLoaded) throw new Error('Pixel loaded, skipping manual _fbc set'); // Let the Pixel set _fbc if it’s active
|
||
|
||
const fbclid = getParam('fbclid');
|
||
if (!fbclid) throw new Error('No fbclid param present');
|
||
|
||
const existing = getCookie('_fbc');
|
||
if (isValidFbc(existing)) throw new Error('_fbc cookie already present and valid');
|
||
|
||
const ts = Math.floor(Date.now() / 1000);
|
||
const fbc = `fb.1.${ts}.${fbclid}`;
|
||
setCookie('_fbc', fbc, cookieOpts);
|
||
log.debug('[ensureFbc] Set _fbc cookie:', fbc);
|
||
} catch (e) {
|
||
log.debug('[ensureFbc]', (e as Error).message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Helper to read both _fbp and _fbc for your CAPI payload.
|
||
*/
|
||
export function getFbpFbc(): { fbp?: string; fbc?: string } {
|
||
const fbp = getCookie('_fbp');
|
||
const fbc = getCookie('_fbc');
|
||
return { fbp, fbc };
|
||
}
|