{"version":3,"file":"google-tag-gateway.cjs","names":["getGeolocation"],"sources":["../../src/utils/google-tag-gateway.ts"],"sourcesContent":["import { getGeolocation } from './geolocation';\n\n/**\n * In a browser, verify the setup by navigating to: https://example.com/metrics/healthy. The page\n * should read ok. Verify that geographical information is being included by navigating to:\n * https://example.com/metrics/?validate_geo=healthy. The page should read ok.\n */\nexport async function forwardToGoogleTagGateway(request: Request, gaId: string) {\n  const GATEWAY_HOST = `${gaId}.fps.goog`;\n  const { pathname, search } = new URL(request.url);\n\n  const target = `https://${GATEWAY_HOST}${pathname}${search}`;\n\n  const headers = new Headers();\n\n  // Forward the browser-identifying headers a load balancer would pass through:\n  // cookies for first-party measurement, user-agent + client hints so GA doesn't\n  // classify hits as bot traffic and the gateway can serve UA-appropriate scripts.\n  for (const [name, value] of request.headers) {\n    if (\n      name === 'cookie' ||\n      name === 'user-agent' ||\n      name === 'accept-language' ||\n      name === 'referer' ||\n      name === 'x-forwarded-for' ||\n      name.startsWith('sec-ch-')\n    ) {\n      headers.set(name, value);\n    }\n  }\n\n  // Convert geo headers to Google Tag Gateway format\n  // https://developers.google.com/tag-platform/tag-manager/gateway/setup-guide\n  const { country, region, city, latitude, longitude, ip_address } = getGeolocation(request);\n\n  // A CDN in front (Vercel/Cloudflare/CloudFront) normally sets x-forwarded-for already;\n  // fall back to the platform-specific client-IP header if it was stripped.\n  if (ip_address && !headers.has('x-forwarded-for')) {\n    headers.set('x-forwarded-for', ip_address);\n  }\n\n  if (country && region) {\n    headers.set('x-forwarded-countryregion', `${country}-${region}`);\n  } else if (country) {\n    headers.set('x-forwarded-country', country);\n  }\n\n  // City-level geolocation, format per the official Fastly/Google Cloud configs:\n  // latlong=<lat>,<lng>;city=<city>\n  const geolocation = [\n    latitude !== undefined && longitude !== undefined && `latlong=${latitude},${longitude}`,\n    city && `city=${city}`,\n  ].filter(Boolean);\n\n  if (geolocation.length > 0) {\n    headers.set('x-forwarded-geolocation', geolocation.join(';'));\n  }\n\n  const hasBody = request.method !== 'GET' && request.method !== 'HEAD';\n\n  // The conversion endpoint (g/measurement/conversion) answers POSTs with a 302 to\n  // www.google.com. Pass that redirect through to the browser instead of following it\n  // here: the hop only carries signal when the browser makes it with its own google.com\n  // cookies (Google Signals / cross-domain conversion linking). Not following redirects\n  // also means the one-shot body stream never needs to be replayed, so it can be\n  // forwarded as-is.\n  const response = await fetch(target, {\n    method: request.method,\n    headers,\n    body: hasBody ? request.body : undefined,\n    redirect: 'manual',\n    // Opt out of Next.js fetch caching (the official CloudFront config likewise\n    // requires CachingDisabled): measurement hits must never be served from cache.\n    cache: 'no-store',\n    ...(hasBody && { duplex: 'half' as const }),\n  });\n\n  // Strip content-encoding/content-length because fetch() auto-decompresses\n  // but keeps the original headers, causing ERR_CONTENT_DECODING_FAILED\n  const responseHeaders = new Headers(response.headers);\n  responseHeaders.delete('content-encoding');\n  responseHeaders.delete('content-length');\n\n  return new Response(response.body, { status: response.status, headers: responseHeaders });\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,0BAA0B,SAAkB,MAAc;CAC9E,MAAM,eAAe,GAAG,KAAK;CAC7B,MAAM,EAAE,UAAU,WAAW,IAAI,IAAI,QAAQ,GAAG;CAEhD,MAAM,SAAS,WAAW,eAAe,WAAW;CAEpD,MAAM,UAAU,IAAI,QAAQ;CAK5B,KAAK,MAAM,CAAC,MAAM,UAAU,QAAQ,SAClC,IACE,SAAS,YACT,SAAS,gBACT,SAAS,qBACT,SAAS,aACT,SAAS,qBACT,KAAK,WAAW,SAAS,GAEzB,QAAQ,IAAI,MAAM,KAAK;CAM3B,MAAM,EAAE,SAAS,QAAQ,MAAM,UAAU,WAAW,eAAeA,0BAAAA,eAAe,OAAO;CAIzF,IAAI,cAAc,CAAC,QAAQ,IAAI,iBAAiB,GAC9C,QAAQ,IAAI,mBAAmB,UAAU;CAG3C,IAAI,WAAW,QACb,QAAQ,IAAI,6BAA6B,GAAG,QAAQ,GAAG,QAAQ;MAC1D,IAAI,SACT,QAAQ,IAAI,uBAAuB,OAAO;CAK5C,MAAM,cAAc,CAClB,aAAa,KAAA,KAAa,cAAc,KAAA,KAAa,WAAW,SAAS,GAAG,aAC5E,QAAQ,QAAQ,MAClB,CAAC,CAAC,OAAO,OAAO;CAEhB,IAAI,YAAY,SAAS,GACvB,QAAQ,IAAI,2BAA2B,YAAY,KAAK,GAAG,CAAC;CAG9D,MAAM,UAAU,QAAQ,WAAW,SAAS,QAAQ,WAAW;CAQ/D,MAAM,WAAW,MAAM,MAAM,QAAQ;EACnC,QAAQ,QAAQ;EAChB;EACA,MAAM,UAAU,QAAQ,OAAO,KAAA;EAC/B,UAAU;EAGV,OAAO;EACP,GAAI,WAAW,EAAE,QAAQ,OAAgB;CAC3C,CAAC;CAID,MAAM,kBAAkB,IAAI,QAAQ,SAAS,OAAO;CACpD,gBAAgB,OAAO,kBAAkB;CACzC,gBAAgB,OAAO,gBAAgB;CAEvC,OAAO,IAAI,SAAS,SAAS,MAAM;EAAE,QAAQ,SAAS;EAAQ,SAAS;CAAgB,CAAC;AAC1F"}