Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
initGoogleAds gtag shim'i array push ediyordu ((...args) => dataLayer.push(args));
gtag.js komut olarak sadece `arguments` nesnesini işler, array'i inert dataLayer
verisi sayar → config/event HİÇ çalışmıyordu → _gcl_aw linker cookie'si set
olmuyor, conversion ping'i Google'a gitmiyor, panelde 14 günde 0 dönüşüm (email
kayıtları dahil — sadece OAuth değil). Google'ın kanonik snippet formuna
(function gtag(){dataLayer.push(arguments)}) döndürüldü. OAuth post-auth fix'iyle
(685c6af) birlikte email+OAuth tüm kayıtlar artık ateşler + doğru atfeder.
Doğrulama: headless test gtag'i teyit edemez (Google bot-baskılaması); kesin
kanıt gerçek tarayıcıda kaydın panele düşmesi.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
88 lines
3.6 KiB
TypeScript
88 lines
3.6 KiB
TypeScript
// Google Ads (gtag) — lazily injected so it stays out of the initial bundle and
|
|
// off entirely when VITE_GOOGLE_ADS_ID is unset (dev/preview). All exported
|
|
// functions are fire-and-forget; tracking loss on failure is acceptable.
|
|
//
|
|
// Reports conversions client-side to Google Ads. The Google tag auto-captures
|
|
// gclid into its own _gcl_* cookies for click attribution; we additionally stash
|
|
// the raw gclid in a first-party cookie so a future server-side conversion import
|
|
// (Google Ads API, gated on Basic access) can attribute the same click.
|
|
|
|
type GtagArgs = unknown[];
|
|
|
|
declare global {
|
|
interface Window {
|
|
gtag?: (...args: GtagArgs) => void;
|
|
dataLayer?: GtagArgs[];
|
|
}
|
|
}
|
|
|
|
let _initialized = false;
|
|
let _adsId: string | undefined;
|
|
|
|
function captureGclid(): void {
|
|
try {
|
|
const p = new URLSearchParams(window.location.search);
|
|
const gclid = p.get("gclid") || p.get("gbraid") || p.get("wbraid");
|
|
if (gclid) {
|
|
// 90-day first-party cookie for later server-side conversion import.
|
|
document.cookie = `sase_gclid=${encodeURIComponent(gclid)}; path=/; max-age=${90 * 24 * 60 * 60}; SameSite=Lax`;
|
|
}
|
|
} catch {
|
|
// URL/cookie access can throw in locked-down embeds — ignore.
|
|
}
|
|
}
|
|
|
|
export function initGoogleAds(): void {
|
|
const id = import.meta.env.VITE_GOOGLE_ADS_ID; // e.g. "AW-1234567890"
|
|
if (!id || _initialized || typeof window === "undefined") return;
|
|
_initialized = true;
|
|
_adsId = id;
|
|
|
|
window.dataLayer = window.dataLayer || [];
|
|
// gtag.js executes only queue entries that are the `arguments` object; pushing a
|
|
// plain array (rest params → `args`) is stored as inert dataLayer data and the
|
|
// command (config/event) NEVER runs. That silently disabled ALL conversion
|
|
// tracking — no _gcl_aw linker cookie, no conversion pings, 0 conversions in the
|
|
// panel. Use Google's canonical snippet form that pushes `arguments`.
|
|
function gtag() {
|
|
// biome-ignore lint/style/noArguments: gtag.js only processes the raw arguments object
|
|
window.dataLayer?.push(arguments as unknown as GtagArgs);
|
|
}
|
|
if (!window.gtag) window.gtag = gtag;
|
|
|
|
const script = document.createElement("script");
|
|
script.async = true;
|
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}`;
|
|
const first = document.getElementsByTagName("script")[0];
|
|
first?.parentNode?.insertBefore(script, first);
|
|
|
|
window.gtag("js", new Date());
|
|
// allow_enhanced_conversions lets hashed first-party data (email set below)
|
|
// improve match rate without exposing PII to the page.
|
|
window.gtag("config", id, { allow_enhanced_conversions: true });
|
|
|
|
captureGclid();
|
|
}
|
|
|
|
// Enhanced Conversions: provide first-party data; the tag hashes it in-browser.
|
|
export function setGoogleAdsUserData(email?: string): void {
|
|
if (!_initialized || !email) return;
|
|
window.gtag?.("set", "user_data", { email });
|
|
}
|
|
|
|
// Fire a conversion. `label` is the conversion action's label (from Google Ads
|
|
// → Conversions); the event is sent to "<AW-id>/<label>". When unset (no label
|
|
// configured) this is a no-op, so the call sites stay safe before setup.
|
|
export function trackGoogleAdsConversion(
|
|
label: string | undefined,
|
|
opts?: { value?: number; currency?: string; transactionId?: string; email?: string },
|
|
): void {
|
|
if (!_initialized || !_adsId || !label) return;
|
|
if (opts?.email) setGoogleAdsUserData(opts.email);
|
|
const params: Record<string, unknown> = { send_to: `${_adsId}/${label}` };
|
|
if (opts?.value != null) params.value = opts.value;
|
|
if (opts?.currency) params.currency = opts.currency;
|
|
if (opts?.transactionId) params.transaction_id = opts.transactionId;
|
|
window.gtag?.("event", "conversion", params);
|
|
}
|