fix utm unicode
This commit is contained in:
parent
654e8899a8
commit
d6ea5054c2
@ -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"
|
||||||
|
|||||||
@ -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(/=+$/, "");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user