fix utm unicode

This commit is contained in:
dev.daminik00 2025-12-24 22:03:17 +03:00
parent 654e8899a8
commit d6ea5054c2
2 changed files with 48 additions and 9 deletions

View File

@ -30,14 +30,16 @@ export function AnalyticsScripts({ data, externalId }: AnalyticsScriptsProps) {
const googleAnalyticsIds = data?.google_analytics || []; const googleAnalyticsIds = data?.google_analytics || [];
const yandexMetrikaIds = data?.yandex_metrica || []; const yandexMetrikaIds = data?.yandex_metrica || [];
// Track which pixels have been initialized with external_id // Track which pixels have been initialized (basic init)
const initializedPixelsRef = useRef<Set<string>>(new Set()); const initializedPixelsRef = useRef<Set<string>>(new Set());
// Track which pixels have been enhanced with external_id
const enhancedPixelsRef = useRef<Set<string>>(new Set());
// Initialize FB Pixel when externalId becomes available // Initialize FB Pixel immediately when SDK is ready
// external_id is OPTIONAL - we must not block tracking for users without fingerprint
useEffect(() => { useEffect(() => {
if (!externalId || facebookPixels.length === 0) return; if (facebookPixels.length === 0) return;
// Wait for fbq to be available
const initPixels = () => { const initPixels = () => {
if (!window.fbq) { if (!window.fbq) {
// SDK not loaded yet, retry in 100ms // SDK not loaded yet, retry in 100ms
@ -48,20 +50,44 @@ export function AnalyticsScripts({ data, externalId }: AnalyticsScriptsProps) {
facebookPixels.forEach((pixelId) => { facebookPixels.forEach((pixelId) => {
if (initializedPixelsRef.current.has(pixelId)) return; if (initializedPixelsRef.current.has(pixelId)) return;
window.fbq!("init", pixelId, { external_id: externalId }); // Initialize with external_id if available, otherwise without
// This ensures ALL users get tracked, not just those with fingerprint
if (externalId) {
window.fbq!("init", pixelId, { external_id: externalId });
enhancedPixelsRef.current.add(pixelId);
} else {
window.fbq!("init", pixelId);
}
window.fbq!("track", "PageView"); window.fbq!("track", "PageView");
initializedPixelsRef.current.add(pixelId); initializedPixelsRef.current.add(pixelId);
}); });
}; };
initPixels(); initPixels();
}, [facebookPixels, externalId]);
// Enhance already-initialized pixels with external_id when it becomes available later
// This improves matching for Conversions API deduplication
useEffect(() => {
if (!externalId || facebookPixels.length === 0) return;
if (!window.fbq) return;
facebookPixels.forEach((pixelId) => {
// Only enhance if pixel was initialized but not yet enhanced with external_id
if (initializedPixelsRef.current.has(pixelId) && !enhancedPixelsRef.current.has(pixelId)) {
// Re-init with external_id for better matching (doesn't fire PageView again)
window.fbq!("init", pixelId, { external_id: externalId });
enhancedPixelsRef.current.add(pixelId);
}
});
}, [externalId, facebookPixels]); }, [externalId, facebookPixels]);
if (!data) return null; if (!data) return null;
return ( return (
<> <>
{/* Facebook Pixel SDK - loads immediately, init happens in useEffect when externalId ready */} {/* Facebook Pixel SDK - loads immediately, init happens in useEffect (with or without externalId) */}
{facebookPixels.length > 0 && ( {facebookPixels.length > 0 && (
<Script <Script
id="fb-pixel-sdk" id="fb-pixel-sdk"

View File

@ -65,17 +65,30 @@ export const getCurrentQueryParams = (): Record<string, string> => {
return utmParams; return utmParams;
}; };
/**
* Convert bytes to base64 string (handles UTF-8 properly)
* MDN recommended approach: https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa#unicode_strings
*/
const bytesToBase64 = (bytes: Uint8Array): string => {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
};
/** /**
* Encode params as base64 JSON for state parameter * Encode params as base64 JSON for state parameter
* Uses URL-safe base64 encoding * Uses URL-safe base64 encoding with UTF-8 support
* Handles Unicode characters (e.g., utm_campaign=)
*/ */
export const encodeStateParam = (params: Record<string, string>): string => { export const encodeStateParam = (params: Record<string, string>): string => {
if (Object.keys(params).length === 0) return ""; if (Object.keys(params).length === 0) return "";
try { try {
const json = JSON.stringify(params); const json = JSON.stringify(params);
// Use btoa for base64, replace unsafe chars for URL // Encode string as UTF-8 bytes, then convert to base64
const base64 = btoa(json) const bytes = new TextEncoder().encode(json);
const base64 = bytesToBase64(bytes)
.replace(/\+/g, "-") .replace(/\+/g, "-")
.replace(/\//g, "_") .replace(/\//g, "_")
.replace(/=+$/, ""); .replace(/=+$/, "");