Files
sase.tr/apps/web/src/lib/google-ads.ts
Semih Yesilyurt 6c936d546b
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
fix(analytics): gtag shim kanonik arguments push — tüm conversion takibi kırıktı
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>
2026-08-01 10:09:28 +03:00

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);
}