Compare commits
7 Commits
0cd3f10da6
...
v0.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9a9f5ed30 | ||
|
|
0e1a449cb6 | ||
|
|
764da5db2e | ||
|
|
6692338b83 | ||
|
|
3561012fb9 | ||
|
|
b26f6160f8 | ||
|
|
1bb202ffa5 |
@@ -4,7 +4,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitea.auvem.com/svelte-toolkit/spectator.git"
|
"url": "https://gitea.auvem.com/svelte-toolkit/spectator.git"
|
||||||
},
|
},
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
@@ -113,11 +113,11 @@ export class ConversionControl {
|
|||||||
const event = new ServerEvent()
|
const event = new ServerEvent()
|
||||||
.setEventName(eventName)
|
.setEventName(eventName)
|
||||||
.setEventTime(Math.floor(Date.now() / 1000))
|
.setEventTime(Math.floor(Date.now() / 1000))
|
||||||
.setEventId(details.eventID)
|
|
||||||
.setUserData(buildUserData(details.userData))
|
.setUserData(buildUserData(details.userData))
|
||||||
.setActionSource(details.actionSource);
|
.setActionSource(details.actionSource);
|
||||||
|
|
||||||
if (details?.eventSourceURL) event.setEventSourceUrl(details.eventSourceURL);
|
if (details.eventID) event.setEventId(details.eventID);
|
||||||
|
if (details.eventSourceURL) event.setEventSourceUrl(details.eventSourceURL);
|
||||||
if (params) {
|
if (params) {
|
||||||
const customData = buildCustomData(params);
|
const customData = buildCustomData(params);
|
||||||
event.setCustomData(customData);
|
event.setCustomData(customData);
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import type {
|
|||||||
} from '$lib/types/conversion.js';
|
} from '$lib/types/conversion.js';
|
||||||
|
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
|
import { getFbpFbcFromCookies } from '../metapixel/fbc.ts';
|
||||||
|
|
||||||
const getEventIP = (request: Request, getClientAddress: () => string) => {
|
export const getRequestIP = (request: Request, getClientAddress: () => string) => {
|
||||||
return (
|
return (
|
||||||
request.headers.get('x-forwarded-for') ||
|
request.headers.get('x-forwarded-for') ||
|
||||||
request.headers.get('cf-connecting-ip') ||
|
request.headers.get('cf-connecting-ip') ||
|
||||||
@@ -26,18 +27,21 @@ const getEventIP = (request: Request, getClientAddress: () => string) => {
|
|||||||
export const createConversionRequestHandler: (control: ConversionControl) => RequestHandler = (
|
export const createConversionRequestHandler: (control: ConversionControl) => RequestHandler = (
|
||||||
control
|
control
|
||||||
) => {
|
) => {
|
||||||
const handle: RequestHandler = async ({ request, getClientAddress }) => {
|
const handle: RequestHandler = async ({ request, getClientAddress, cookies }) => {
|
||||||
try {
|
try {
|
||||||
const body = (await request.json()) as ConversionRequestBody;
|
const body = (await request.json()) as ConversionRequestBody;
|
||||||
|
|
||||||
// Build user data with IP and user agent
|
// Build user data with IP and user agent
|
||||||
const ip = getEventIP(request, getClientAddress);
|
const ip = getRequestIP(request, getClientAddress);
|
||||||
const ua = request.headers.get('user-agent');
|
const ua = request.headers.get('user-agent');
|
||||||
|
const { fbp, fbc } = getFbpFbcFromCookies(cookies);
|
||||||
|
|
||||||
const userData: ConversionUserData = {
|
const userData: ConversionUserData = {
|
||||||
...body.user,
|
...body.user,
|
||||||
ip,
|
ip,
|
||||||
ua: ua ?? body.user?.ua ?? undefined
|
ua: ua ?? body.user?.ua ?? undefined,
|
||||||
|
fbc: body.user?.fbc ?? fbc,
|
||||||
|
fbp: body.user?.fbp ?? fbp
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build custom data with UTM params if applicable
|
// Build custom data with UTM params if applicable
|
||||||
|
|||||||
@@ -13,4 +13,6 @@ export * from './conversion/index.ts';
|
|||||||
// set log level to debug if we're in dev mode
|
// set log level to debug if we're in dev mode
|
||||||
if (dev) {
|
if (dev) {
|
||||||
log.setLevel('debug');
|
log.setLevel('debug');
|
||||||
|
} else {
|
||||||
|
log.setLevel('warn');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,22 +38,20 @@ PixelControl interface.
|
|||||||
|
|
||||||
let pixel = $state<PixelControl | null>(null);
|
let pixel = $state<PixelControl | null>(null);
|
||||||
|
|
||||||
const ensureFbcOptions: EnsureFbcOptions = $derived({
|
const fbcOptions: EnsureFbcOptions = {
|
||||||
sameSite: 'Lax',
|
sameSite: 'Lax'
|
||||||
pixelLoaded: PixelControl.baseLoaded && !PixelControl.baseFailed
|
};
|
||||||
});
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!trackingManager) {
|
if (!trackingManager) {
|
||||||
throw new Error('MetaPixel component requires a TrackingManager to manage consent.');
|
throw new Error('MetaPixel component requires a TrackingManager to manage consent.');
|
||||||
}
|
}
|
||||||
PixelControl.load();
|
|
||||||
pixel = PixelControl.initialize(trackingManager, pixelID, pixelOptions);
|
pixel = PixelControl.initialize(trackingManager, pixelID, pixelOptions);
|
||||||
|
|
||||||
trackingManager.runWithConsent(() => {
|
trackingManager.runWithConsent(() => {
|
||||||
if (autoPageView && pixel) {
|
if (autoPageView && pixel) {
|
||||||
pixel.pageView();
|
pixel.pageView();
|
||||||
ensureFbc(ensureFbcOptions);
|
ensureFbc(fbcOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -62,7 +60,7 @@ PixelControl interface.
|
|||||||
trackingManager?.runWithConsent(() => {
|
trackingManager?.runWithConsent(() => {
|
||||||
if (autoPageView && pixel) {
|
if (autoPageView && pixel) {
|
||||||
pixel.pageView();
|
pixel.pageView();
|
||||||
ensureFbc(ensureFbcOptions);
|
ensureFbc(fbcOptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { Cookies } from '@sveltejs/kit';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
|
||||||
export type EnsureFbcOptions = {
|
export type EnsureFbcOptions = {
|
||||||
@@ -79,10 +80,19 @@ export function ensureFbc(options: EnsureFbcOptions = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to read both _fbp and _fbc for your CAPI payload.
|
* Helper to read both _fbp and _fbc for your CAPI payload from browser cookies.
|
||||||
*/
|
*/
|
||||||
export function getFbpFbc(): { fbp?: string; fbc?: string } {
|
export function getFbpFbc(): { fbp?: string; fbc?: string } {
|
||||||
const fbp = getCookie('_fbp');
|
const fbp = getCookie('_fbp');
|
||||||
const fbc = getCookie('_fbc');
|
const fbc = getCookie('_fbc');
|
||||||
return { fbp, fbc };
|
return { fbp, fbc };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to read both _fbp and _fbc for your CAPI payload from cookies object.
|
||||||
|
*/
|
||||||
|
export function getFbpFbcFromCookies(cookies: Cookies): { fbp?: string; fbc?: string } {
|
||||||
|
const fbp = cookies.get('_fbp') || undefined;
|
||||||
|
const fbc = cookies.get('_fbc') || undefined;
|
||||||
|
return { fbp, fbc };
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ export class PixelControl {
|
|||||||
private _trackingManager: MaybeGetter<TrackingManager | undefined>;
|
private _trackingManager: MaybeGetter<TrackingManager | undefined>;
|
||||||
private _conversionClient?: ConversionClient = undefined;
|
private _conversionClient?: ConversionClient = undefined;
|
||||||
|
|
||||||
private static _baseLoaded: boolean = $state(false);
|
private static _baseLoaded: boolean = false;
|
||||||
private static _baseFailed: boolean = $state(false);
|
|
||||||
private static _registeredPixels: Record<string, PixelControl> = {};
|
private static _registeredPixels: Record<string, PixelControl> = {};
|
||||||
|
|
||||||
/** Indicates whether the Meta Pixel base script has been loaded. */
|
/** Indicates whether the Meta Pixel base script has been loaded. */
|
||||||
@@ -77,11 +76,6 @@ export class PixelControl {
|
|||||||
return this._baseLoaded;
|
return this._baseLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Indicates whether the Meta Pixel base script has failed to load. */
|
|
||||||
static get baseFailed(): boolean {
|
|
||||||
return this._baseFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures that the Meta Pixel base has been loaded before
|
* Ensures that the Meta Pixel base has been loaded before
|
||||||
* allowing further operations.
|
* allowing further operations.
|
||||||
@@ -116,20 +110,11 @@ export class PixelControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Loads the Meta Pixel base script. */
|
/** Loads the Meta Pixel base script. */
|
||||||
static async load() {
|
static load() {
|
||||||
if (this._baseLoaded && !this.baseFailed && !!window.fbq) return;
|
if (this._baseLoaded && !!window.fbq) return;
|
||||||
if (!window.fbq) {
|
loadMetaPixel(); // Load the Meta Pixel script
|
||||||
try {
|
this._baseLoaded = true;
|
||||||
await loadMetaPixel(); // Load the Meta Pixel script
|
log.debug('[PixelControl] Meta Pixel base script loaded.', this._baseLoaded);
|
||||||
} catch (e) {
|
|
||||||
log.warn('[PixelControl] Failed to load Meta Pixel script, all events will be queued.', e);
|
|
||||||
this._baseFailed = true;
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this._baseLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug('[PixelControl] Meta Pixel base script loaded.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tells the Meta pixel that the user has given consent for tracking. */
|
/** Tells the Meta pixel that the user has given consent for tracking. */
|
||||||
@@ -212,14 +197,15 @@ export class PixelControl {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a PageView event
|
* Sends a PageView event
|
||||||
|
* @param disableCAPI If true, disables sending this event to the Conversion API
|
||||||
* @throws Error if the Meta Pixel is not initialized.
|
* @throws Error if the Meta Pixel is not initialized.
|
||||||
*/
|
*/
|
||||||
pageView() {
|
pageView(disableCAPI: boolean = false) {
|
||||||
if (!this.consentGuard()) return;
|
if (!this.consentGuard()) return;
|
||||||
|
|
||||||
let eventID: string | undefined = undefined;
|
let eventID: string | undefined = undefined;
|
||||||
// Optionally, send to conversion API endpoint if configured
|
// Optionally, send to conversion API endpoint if configured
|
||||||
if (this._conversionClient) {
|
if (this._conversionClient && !disableCAPI) {
|
||||||
eventID = crypto.randomUUID();
|
eventID = crypto.randomUUID();
|
||||||
this._conversionClient
|
this._conversionClient
|
||||||
.trackEvent('PageView', { eventID })
|
.trackEvent('PageView', { eventID })
|
||||||
@@ -256,13 +242,22 @@ export class PixelControl {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks a standard event for this pixel (uses `trackSingle` under the hood)
|
* Tracks a standard event for this pixel (uses `trackSingle` under the hood)
|
||||||
|
* @param event Standard event name
|
||||||
|
* @param params Event parameters
|
||||||
|
* @param eventID Optional event ID for deduplication with Conversion API
|
||||||
|
* @param disableCAPI If true, disables sending this event to the Conversion API
|
||||||
* @throws Error if the Meta Pixel is not initialized.
|
* @throws Error if the Meta Pixel is not initialized.
|
||||||
*/
|
*/
|
||||||
track<K extends StandardEventName>(event: K, params?: EventParamsByName[K], eventID?: string) {
|
track<K extends StandardEventName>(
|
||||||
|
event: K,
|
||||||
|
params?: EventParamsByName[K],
|
||||||
|
eventID?: string,
|
||||||
|
disableCAPI: boolean = false
|
||||||
|
) {
|
||||||
if (!this.consentGuard()) return;
|
if (!this.consentGuard()) return;
|
||||||
|
|
||||||
// Optionally, send to conversion API endpoint if configured
|
// Optionally, send to conversion API endpoint if configured
|
||||||
if (this._conversionClient) {
|
if (this._conversionClient && !disableCAPI) {
|
||||||
eventID = eventID ?? crypto.randomUUID();
|
eventID = eventID ?? crypto.randomUUID();
|
||||||
this._conversionClient
|
this._conversionClient
|
||||||
.trackEvent(event, { eventID: eventID, customData: pixelParamsToCustomData(params ?? {}) })
|
.trackEvent(event, { eventID: eventID, customData: pixelParamsToCustomData(params ?? {}) })
|
||||||
|
|||||||
2
src/lib/types/conversion.d.ts
vendored
2
src/lib/types/conversion.d.ts
vendored
@@ -51,7 +51,7 @@ export type ConversionEventParams = {
|
|||||||
* Parameters for sending a conversion event to Meta Pixel.
|
* Parameters for sending a conversion event to Meta Pixel.
|
||||||
*/
|
*/
|
||||||
export type ConversionEventDetails = {
|
export type ConversionEventDetails = {
|
||||||
eventID: string;
|
eventID?: string;
|
||||||
actionSource: 'website' | 'app' | 'offline' | 'other';
|
actionSource: 'website' | 'app' | 'offline' | 'other';
|
||||||
userData: ConversionUserData;
|
userData: ConversionUserData;
|
||||||
eventSourceURL?: string;
|
eventSourceURL?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user