@aranova/tracking-next 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../../tracking-core/src/consent.ts","../../tracking-core/src/payloads.ts","../../tracking-core/src/gtag.ts","../../tracking-core/src/tracking.ts","../src/ConsentBanner.tsx","../src/hooks.ts","../src/GoogleAdsTracking.tsx","../src/middleware.ts","../src/server.ts"],"sourcesContent":["export { getConsentState, setConsentState } from '../../tracking-core/src/index';\nexport {\n captureTrackingParamsFromLocation,\n createTrackingClientContext,\n createTrackingEventCreatePayload,\n createTrackingSessionUpsertPayload,\n TRACKING_PARAM_KEYS,\n} from '../../tracking-core/src/index';\nexport type {\n ConsentState,\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInitConfig,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from '../../tracking-core/src/index';\nexport { ConsentBanner } from './ConsentBanner';\nexport { useConsentState, useGclid, useTrackingParams } from './hooks';\nexport { GoogleAdsTracking, type GoogleAdsTrackingProps } from './GoogleAdsTracking';\nexport { createTrackingMiddleware } from './middleware';\nexport { getTrackingParamsServer } from './server';\n","import type { ConsentState } from './types';\n\nexport const CONSENT_STATE_KEY = 'consent_state';\nexport const CONSENT_TIMESTAMP_KEY = 'consent_timestamp';\n\nexport type GtagConsentValue = 'granted' | 'denied';\n\nexport interface ConsentUpdatePayload {\n ad_storage: GtagConsentValue;\n ad_user_data: GtagConsentValue;\n ad_personalization: GtagConsentValue;\n analytics_storage: GtagConsentValue;\n}\n\nexport function buildConsentPayload(state: GtagConsentValue): ConsentUpdatePayload {\n return {\n ad_storage: state,\n ad_user_data: state,\n ad_personalization: state,\n analytics_storage: state,\n };\n}\n\nexport function getConsentState(): ConsentState {\n if (typeof window === 'undefined')\n return 'pending';\n\n const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);\n\n if (storedState === 'granted' || storedState === 'denied')\n return storedState;\n\n return 'pending';\n}\n\nexport function setConsentState(state: GtagConsentValue): void {\n if (typeof window === 'undefined')\n return;\n\n window.localStorage.setItem(CONSENT_STATE_KEY, state);\n window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, new Date().toISOString());\n\n if (typeof window.gtag === 'function')\n window.gtag('consent', 'update', buildConsentPayload(state));\n}\n\nexport function restoreStoredConsent(): ConsentState {\n const consentState = getConsentState();\n\n if (consentState === 'granted' || consentState === 'denied')\n setConsentState(consentState);\n\n return consentState;\n}\n","import type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\ninterface TrackingContextInput {\n packageName?: string | null;\n pageTitle?: string | null;\n referrer?: string | null;\n sdkVersion?: string | null;\n siteOrigin?: string | null;\n}\n\ninterface TrackingEventInput {\n eventType: string;\n metadata?: Record<string, unknown> | null;\n pageUrl?: string | null;\n sessionId: string;\n}\n\ninterface TrackingSessionInput {\n consentState?: Record<string, unknown> | null;\n firstPage?: string | null;\n sessionId: string;\n}\n\nexport function createTrackingClientContext(\n surface: TrackingInstallSurface,\n input: TrackingContextInput = {},\n): TrackingClientContext {\n return {\n surface,\n sdk_version: input.sdkVersion ?? null,\n package_name: input.packageName ?? null,\n site_origin: input.siteOrigin ?? (typeof window === 'undefined' ? null : window.location.origin),\n page_title: input.pageTitle ?? (typeof document === 'undefined' ? null : document.title || null),\n referrer: input.referrer ?? (typeof document === 'undefined' ? null : document.referrer || null),\n };\n}\n\nexport function createTrackingSessionUpsertPayload(\n trackingParams: TrackingParams,\n input: TrackingSessionInput,\n context: TrackingClientContext,\n): TrackingSessionUpsertPayload {\n return {\n session_id: input.sessionId,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n utm_source: trackingParams.utm_source,\n utm_medium: trackingParams.utm_medium,\n utm_campaign: trackingParams.utm_campaign,\n utm_term: trackingParams.utm_term,\n utm_content: trackingParams.utm_content,\n first_page: input.firstPage ?? null,\n consent_state: input.consentState ?? null,\n context,\n };\n}\n\nexport function createTrackingEventCreatePayload(\n trackingParams: TrackingParams,\n input: TrackingEventInput,\n context: TrackingClientContext,\n): TrackingEventCreatePayload {\n return {\n session_id: input.sessionId,\n event_type: input.eventType,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n page_url: input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n context,\n };\n}\n","import { buildConsentPayload, getConsentState, restoreStoredConsent } from './consent';\n\nexport const GTAG_SCRIPT_HOST = 'https://www.googletagmanager.com/gtag/js';\nexport const TRACKING_SCRIPT_ATTRIBUTE = 'data-aranova-tracking';\n\nfunction getScriptMarker(id: string): string {\n return `aranova-${id}`;\n}\n\nexport function createConsentDefaultScript(): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n});\n`.trim();\n}\n\nexport function createGtagInitScript(gtagId: string): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('js', new Date());\ngtag('config', '${gtagId}');\n`.trim();\n}\n\nexport function createConsentRestoreScript(): string {\n return `\nconst consentState = window.localStorage.getItem('consent_state');\nif (window.gtag && (consentState === 'granted' || consentState === 'denied')) {\n window.gtag('consent', 'update', {\n ad_storage: consentState,\n ad_user_data: consentState,\n ad_personalization: consentState,\n analytics_storage: consentState,\n });\n}\n`.trim();\n}\n\nexport function ensureGtagFunction(): NonNullable<Window['gtag']> {\n window.dataLayer = window.dataLayer || [];\n\n if (typeof window.gtag === 'function')\n return window.gtag;\n\n window.gtag = (...args: unknown[]) => {\n window.dataLayer?.push(args);\n };\n\n return window.gtag;\n}\n\nexport function applyDefaultConsentState(): void {\n const gtag = ensureGtagFunction();\n gtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n });\n}\n\nexport function loadGtagScript(gtagId: string): void {\n if (typeof document === 'undefined')\n return;\n\n const marker = getScriptMarker('gtag-loader');\n const existingScript = document.querySelector<HTMLScriptElement>(`script[${TRACKING_SCRIPT_ATTRIBUTE}=\"${marker}\"]`);\n if (existingScript)\n return;\n\n const script = document.createElement('script');\n script.async = true;\n script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;\n script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);\n document.head.appendChild(script);\n}\n\nexport function initializeGtag(gtagId: string): void {\n const gtag = ensureGtagFunction();\n gtag('js', new Date());\n gtag('config', gtagId);\n}\n\nexport function restoreConsentState(): void {\n if (typeof window === 'undefined')\n return;\n\n const consentState = getConsentState();\n if (consentState === 'granted' || consentState === 'denied')\n window.gtag?.('consent', 'update', buildConsentPayload(consentState));\n}\n\nexport function bootstrapGoogleAdsTracking(gtagId: string): void {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return;\n\n applyDefaultConsentState();\n loadGtagScript(gtagId);\n initializeGtag(gtagId);\n restoreStoredConsent();\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState, setConsentState, type ConsentState } from '../../tracking-core/src/index';\n\nexport function ConsentBanner() {\n const [consentState, setBannerConsentState] = useState<ConsentState>('pending');\n const [isReady, setIsReady] = useState(false);\n\n useEffect(() => {\n setBannerConsentState(getConsentState());\n setIsReady(true);\n }, []);\n\n if (!isReady || consentState !== 'pending')\n return null;\n\n const handleAccept = (): void => {\n setConsentState('granted');\n setBannerConsentState('granted');\n };\n\n const handleDecline = (): void => {\n setConsentState('denied');\n setBannerConsentState('denied');\n };\n\n return (\n <div className=\"fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg\">\n <div className=\"mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <p className=\"text-sm text-gray-700\">\n We use cookies to understand ad performance and improve how our marketing works across visits.\n You can accept or decline this tracking.\n </p>\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleDecline}\n className=\"rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100\"\n >\n Decline\n </button>\n <button\n type=\"button\"\n onClick={handleAccept}\n className=\"rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800\"\n >\n Accept\n </button>\n </div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState } from '../../tracking-core/src/index';\nimport {\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n type ConsentState,\n type TrackingParams,\n} from '../../tracking-core/src/index';\n\nexport function useGclid(): string | null {\n const [gclid, setGclid] = useState<string | null>(null);\n\n useEffect(() => {\n setGclid(getCookieValueFromDocument('gclid'));\n }, []);\n\n return gclid;\n}\n\nexport function useTrackingParams(): TrackingParams {\n const [trackingParams, setTrackingParams] = useState<TrackingParams>(createEmptyTrackingParams());\n\n useEffect(() => {\n setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));\n }, []);\n\n return trackingParams;\n}\n\nexport function useConsentState(): ConsentState {\n const [consentState, setConsentStateValue] = useState<ConsentState>('pending');\n\n useEffect(() => {\n setConsentStateValue(getConsentState());\n\n const handleStorage = (event: StorageEvent): void => {\n if (event.key === 'consent_state')\n setConsentStateValue(getConsentState());\n };\n\n window.addEventListener('storage', handleStorage);\n\n return () => {\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return consentState;\n}\n","'use client';\n\nimport Script from 'next/script';\n\nimport {\n createConsentDefaultScript,\n createConsentRestoreScript,\n createGtagInitScript,\n} from '../../tracking-core/src/index';\n\nexport interface GoogleAdsTrackingProps {\n gtagId: string;\n}\n\nexport function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps) {\n return (\n <>\n <Script id=\"consent-default\" strategy=\"beforeInteractive\">\n {createConsentDefaultScript()}\n </Script>\n <Script\n src={`https://www.googletagmanager.com/gtag/js?id=${gtagId}`}\n strategy=\"afterInteractive\"\n />\n <Script id=\"gtag-init\" strategy=\"afterInteractive\">\n {createGtagInitScript(gtagId)}\n </Script>\n <Script id=\"consent-restore\" strategy=\"afterInteractive\">\n {createConsentRestoreScript()}\n </Script>\n </>\n );\n}\n","import 'server-only';\n\nimport { NextResponse } from 'next/server';\nimport type { NextMiddleware, NextRequest } from 'next/server';\n\nimport { getTrackingQueryValues } from '../../tracking-core/src/index';\n\nexport function createTrackingMiddleware(): NextMiddleware {\n return (request: NextRequest) => {\n const response = NextResponse.next();\n const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);\n\n Object.entries(trackingValues).forEach(([param, value]) => {\n response.cookies.set(param, value, {\n maxAge: 7776000,\n path: '/',\n httpOnly: false,\n });\n });\n\n return response;\n };\n}\n","import 'server-only';\n\nimport { cookies } from 'next/headers';\n\nimport { getTrackingParamsFromCookieReader, type TrackingParams } from '../../tracking-core/src/index';\n\ninterface CookieValue {\n value: string;\n}\n\ninterface CookieStore {\n get(name: string): CookieValue | undefined;\n}\n\nfunction getCookieStore(): CookieStore {\n return cookies() as unknown as CookieStore;\n}\n\nexport function getTrackingParamsServer(): TrackingParams {\n const cookieStore = getCookieStore();\n return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);\n}\n"],"mappings":"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","names":["cookies","import_react","import_jsx_runtime","Script","import_server_only"]}
1
+ {"version":3,"sources":["../src/index.ts","../../tracking-core/src/consent.ts","../../tracking-core/src/payloads.ts","../../tracking-core/src/gtag.ts","../../tracking-core/src/tracking.ts","../../tracking-core/src/session.ts","../../tracking-core/src/ingest.ts","../../tracking-core/src/page-view.ts","../src/ConsentBanner.tsx","../src/hooks.ts","../src/GoogleAdsTracking.tsx","../src/TrackingProvider.tsx"],"sourcesContent":["// Public entry for @aranova/tracking-next — see docs/tracking-package/install-modes.md\nexport { getConsentState, setConsentState } from '../../tracking-core/src/index';\nexport {\n captureTrackingParamsFromLocation,\n createTrackingClientContext,\n createTrackingEventCreatePayload,\n createTrackingSessionUpsertPayload,\n TRACKING_PARAM_KEYS,\n} from '../../tracking-core/src/index';\nexport type {\n ConsentState,\n TrackingClient,\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInitConfig,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from '../../tracking-core/src/index';\nexport { ConsentBanner } from './ConsentBanner';\nexport { useConsentState, useGclid, useTrackingParams } from './hooks';\nexport { GoogleAdsTracking, type GoogleAdsTrackingProps } from './GoogleAdsTracking';\n// Server-only helpers (`createTrackingMiddleware`, `getTrackingParamsServer`)\n// are exposed via the dedicated `@aranova/tracking-next/middleware` and\n// `@aranova/tracking-next/server` subpaths so client bundles never pull in\n// `next/headers` or other server-only modules.\nexport { TrackingProvider, useTracking, type TrackingProviderProps } from './TrackingProvider';\n","import type { ConsentState } from './types';\n\nexport const CONSENT_STATE_KEY = 'consent_state';\nexport const CONSENT_TIMESTAMP_KEY = 'consent_timestamp';\n\nexport type GtagConsentValue = 'granted' | 'denied';\n\nexport interface ConsentUpdatePayload {\n ad_storage: GtagConsentValue;\n ad_user_data: GtagConsentValue;\n ad_personalization: GtagConsentValue;\n analytics_storage: GtagConsentValue;\n}\n\nexport function buildConsentPayload(state: GtagConsentValue): ConsentUpdatePayload {\n return {\n ad_storage: state,\n ad_user_data: state,\n ad_personalization: state,\n analytics_storage: state,\n };\n}\n\nexport function getConsentState(): ConsentState {\n if (typeof window === 'undefined')\n return 'pending';\n\n const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);\n\n if (storedState === 'granted' || storedState === 'denied')\n return storedState;\n\n return 'pending';\n}\n\nexport function setConsentState(state: GtagConsentValue): void {\n if (typeof window === 'undefined')\n return;\n\n window.localStorage.setItem(CONSENT_STATE_KEY, state);\n window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, new Date().toISOString());\n\n if (typeof window.gtag === 'function')\n window.gtag('consent', 'update', buildConsentPayload(state));\n}\n\nexport function restoreStoredConsent(): ConsentState {\n const consentState = getConsentState();\n\n if (consentState === 'granted' || consentState === 'denied')\n setConsentState(consentState);\n\n return consentState;\n}\n","import type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\ninterface TrackingContextInput {\n packageName?: string | null;\n pageTitle?: string | null;\n referrer?: string | null;\n sdkVersion?: string | null;\n siteOrigin?: string | null;\n}\n\ninterface TrackingEventInput {\n eventType: string;\n metadata?: Record<string, unknown> | null;\n pageUrl?: string | null;\n sessionId: string;\n}\n\ninterface TrackingSessionInput {\n consentState?: Record<string, unknown> | null;\n firstPage?: string | null;\n sessionId: string;\n visitorId?: string | null;\n}\n\nexport function createTrackingClientContext(\n surface: TrackingInstallSurface,\n input: TrackingContextInput = {},\n): TrackingClientContext {\n return {\n surface,\n sdk_version: input.sdkVersion ?? null,\n package_name: input.packageName ?? null,\n site_origin: input.siteOrigin ?? (typeof window === 'undefined' ? null : window.location.origin),\n page_title: input.pageTitle ?? (typeof document === 'undefined' ? null : document.title || null),\n referrer: input.referrer ?? (typeof document === 'undefined' ? null : document.referrer || null),\n };\n}\n\nexport function createTrackingSessionUpsertPayload(\n trackingParams: TrackingParams,\n input: TrackingSessionInput,\n context: TrackingClientContext,\n): TrackingSessionUpsertPayload {\n return {\n session_id: input.sessionId,\n visitor_id: input.visitorId ?? null,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n utm_source: trackingParams.utm_source,\n utm_medium: trackingParams.utm_medium,\n utm_campaign: trackingParams.utm_campaign,\n utm_term: trackingParams.utm_term,\n utm_content: trackingParams.utm_content,\n first_page: input.firstPage ?? null,\n consent_state: input.consentState ?? null,\n context,\n };\n}\n\nexport function createTrackingEventCreatePayload(\n trackingParams: TrackingParams,\n input: TrackingEventInput,\n context: TrackingClientContext,\n): TrackingEventCreatePayload {\n return {\n session_id: input.sessionId,\n event_type: input.eventType,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n page_url: input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n context,\n };\n}\n","import { buildConsentPayload, getConsentState, restoreStoredConsent } from './consent';\n\nexport const GTAG_SCRIPT_HOST = 'https://www.googletagmanager.com/gtag/js';\nexport const TRACKING_SCRIPT_ATTRIBUTE = 'data-aranova-tracking';\n\nfunction getScriptMarker(id: string): string {\n return `aranova-${id}`;\n}\n\nexport function createConsentDefaultScript(): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n});\n`.trim();\n}\n\nexport function createGtagInitScript(gtagId: string): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('js', new Date());\ngtag('config', '${gtagId}');\n`.trim();\n}\n\nexport function createConsentRestoreScript(): string {\n return `\nconst consentState = window.localStorage.getItem('consent_state');\nif (window.gtag && (consentState === 'granted' || consentState === 'denied')) {\n window.gtag('consent', 'update', {\n ad_storage: consentState,\n ad_user_data: consentState,\n ad_personalization: consentState,\n analytics_storage: consentState,\n });\n}\n`.trim();\n}\n\nexport function ensureGtagFunction(): NonNullable<Window['gtag']> {\n window.dataLayer = window.dataLayer || [];\n\n if (typeof window.gtag === 'function')\n return window.gtag;\n\n window.gtag = (...args: unknown[]) => {\n window.dataLayer?.push(args);\n };\n\n return window.gtag;\n}\n\nexport function applyDefaultConsentState(): void {\n const gtag = ensureGtagFunction();\n gtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n });\n}\n\nexport function loadGtagScript(gtagId: string): void {\n if (typeof document === 'undefined')\n return;\n\n const marker = getScriptMarker('gtag-loader');\n const existingScript = document.querySelector<HTMLScriptElement>(`script[${TRACKING_SCRIPT_ATTRIBUTE}=\"${marker}\"]`);\n if (existingScript)\n return;\n\n const script = document.createElement('script');\n script.async = true;\n script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;\n script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);\n document.head.appendChild(script);\n}\n\nexport function initializeGtag(gtagId: string): void {\n const gtag = ensureGtagFunction();\n gtag('js', new Date());\n gtag('config', gtagId);\n}\n\nexport function restoreConsentState(): void {\n if (typeof window === 'undefined')\n return;\n\n const consentState = getConsentState();\n if (consentState === 'granted' || consentState === 'denied')\n window.gtag?.('consent', 'update', buildConsentPayload(consentState));\n}\n\nexport function bootstrapGoogleAdsTracking(gtagId: string): void {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return;\n\n applyDefaultConsentState();\n loadGtagScript(gtagId);\n initializeGtag(gtagId);\n restoreStoredConsent();\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","// Tracking ingest client. Queues events, debounce-flushes them to the\n// /tracking/events endpoint, and degrades silently on errors so a broken\n// network never breaks the host site.\n\nimport { getConsentState } from './consent';\nimport {\n captureTrackingParamsFromLocation,\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n} from './tracking';\nimport { getOrRotateSessionId, getVisitorId } from './session';\nimport type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\nexport const DEFAULT_FLUSH_INTERVAL_MS = 2000;\nexport const DEFAULT_MAX_QUEUE_SIZE = 10;\nexport const HARD_MAX_BATCH = 50;\nexport const API_KEY_HEADER = 'X-Aranova-Api-Key';\n\nexport interface TrackingClientConfig {\n apiKey: string;\n endpoint: string;\n surface: TrackingInstallSurface;\n sdkVersion?: string;\n packageName?: string;\n /** Override the default 2s debounce window. */\n flushIntervalMs?: number;\n /** Override the default 10-event batch trigger. */\n maxQueueSize?: number;\n /** When true, swallow nothing — useful for tests. */\n debug?: boolean;\n}\n\nexport interface TrackEventInput {\n eventType: string;\n pageUrl?: string | null;\n metadata?: Record<string, unknown> | null;\n occurredAt?: Date | string | null;\n}\n\ninterface QueuedEvent {\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n}\n\nexport interface TrackingClient {\n trackEvent: (input: TrackEventInput) => void;\n flush: () => Promise<void>;\n getSessionId: () => string;\n getVisitorId: () => string;\n destroy: () => void;\n}\n\ninterface IngestRequestBody {\n session: TrackingSessionUpsertPayload;\n events: Array<{\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n }>;\n}\n\nfunction buildContext(\n surface: TrackingInstallSurface,\n sdkVersion: string | null,\n packageName: string | null,\n): TrackingClientContext {\n return {\n surface,\n sdk_version: sdkVersion,\n package_name: packageName,\n site_origin: typeof window === 'undefined' ? null : window.location.origin,\n page_title: typeof document === 'undefined' ? null : document.title || null,\n referrer: typeof document === 'undefined' ? null : document.referrer || null,\n };\n}\n\nfunction readTrackingParams(): TrackingParams {\n if (typeof window === 'undefined')\n return createEmptyTrackingParams();\n // Capture from URL on every read so the first event in a session reflects the\n // landing-page params even if the cookie helper hasn't run yet.\n try {\n captureTrackingParamsFromLocation();\n } catch {\n // ignore\n }\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n\nfunction consentSnapshot(): Record<string, unknown> | null {\n try {\n return { state: getConsentState() };\n } catch {\n return null;\n }\n}\n\nasync function postWithFetch(url: string, body: string, apiKey: string, keepalive: boolean): Promise<void> {\n if (typeof fetch !== 'function')\n return;\n try {\n await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [API_KEY_HEADER]: apiKey,\n },\n body,\n keepalive,\n // CORS is open on the tracking endpoint; never send cookies.\n credentials: 'omit',\n mode: 'cors',\n });\n } catch {\n // fire-and-forget — never throw to the host site\n }\n}\n\nexport function createTrackingClient(config: TrackingClientConfig): TrackingClient {\n const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);\n const sdkVersion = config.sdkVersion ?? null;\n const packageName = config.packageName ?? null;\n const endpointBase = config.endpoint.replace(/\\/$/, '');\n const eventsUrl = `${endpointBase}/events`;\n\n let queue: QueuedEvent[] = [];\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n let firstPage: string | null = null;\n let destroyed = false;\n\n // Initialize identity early so the first POST has stable values.\n const visitorId = getVisitorId();\n let sessionId = getOrRotateSessionId();\n\n if (typeof window !== 'undefined')\n firstPage = window.location.href;\n\n function buildSessionPayload(): TrackingSessionUpsertPayload {\n sessionId = getOrRotateSessionId();\n const params = readTrackingParams();\n const context = buildContext(config.surface, sdkVersion, packageName);\n return {\n session_id: sessionId,\n visitor_id: visitorId,\n gclid: params.gclid,\n fbclid: params.fbclid,\n utm_source: params.utm_source,\n utm_medium: params.utm_medium,\n utm_campaign: params.utm_campaign,\n utm_term: params.utm_term,\n utm_content: params.utm_content,\n first_page: firstPage,\n consent_state: consentSnapshot(),\n context,\n };\n }\n\n function scheduleFlush(): void {\n if (flushTimer !== null || destroyed)\n return;\n flushTimer = setTimeout(() => {\n flushTimer = null;\n void flush();\n }, flushIntervalMs);\n }\n\n function clearScheduledFlush(): void {\n if (flushTimer !== null) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n }\n\n async function flush(): Promise<void> {\n if (queue.length === 0)\n return;\n\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n\n const serialized = JSON.stringify(body);\n\n // Prefer fetch + keepalive for better error visibility. sendBeacon is the\n // pagehide fallback path because it survives navigation away.\n await postWithFetch(eventsUrl, serialized, config.apiKey, false);\n }\n\n function trackEvent(input: TrackEventInput): void {\n if (destroyed)\n return;\n if (!input || typeof input.eventType !== 'string' || input.eventType.length === 0)\n return;\n\n const occurredAt =\n input.occurredAt instanceof Date\n ? input.occurredAt.toISOString()\n : typeof input.occurredAt === 'string'\n ? input.occurredAt\n : new Date().toISOString();\n\n queue.push({\n event_type: input.eventType,\n page_url:\n input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n occurred_at: occurredAt,\n });\n\n if (queue.length >= maxQueueSize) {\n void flush();\n } else {\n scheduleFlush();\n }\n }\n\n function flushOnUnload(): void {\n if (queue.length === 0)\n return;\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n const serialized = JSON.stringify(body);\n void postWithFetch(eventsUrl, serialized, config.apiKey, true);\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', flushOnUnload);\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushOnUnload();\n });\n }\n\n return {\n trackEvent,\n flush,\n getSessionId: () => sessionId,\n getVisitorId: () => visitorId,\n destroy: () => {\n destroyed = true;\n clearScheduledFlush();\n queue = [];\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', flushOnUnload);\n }\n },\n };\n}\n","// Auto page_view emission. Fires once on attach, then again on every SPA\n// navigation. Detects navigation via three signals:\n// - history.pushState (patched)\n// - history.replaceState (patched)\n// - popstate event (back/forward buttons)\n//\n// The Next.js provider can opt out of this and use `usePathname` instead so\n// it picks up App Router transitions reliably without monkey-patching globals.\n\nimport type { TrackingClient } from './ingest';\n\nexport interface PageViewMetadata {\n page: {\n title: string | null;\n path: string;\n search: string;\n hash: string;\n };\n referrer: string | null;\n viewport?: { w: number; h: number };\n}\n\nexport function buildPageViewMetadata(): PageViewMetadata | null {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return null;\n return {\n page: {\n title: document.title || null,\n path: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n },\n referrer: document.referrer || null,\n viewport: { w: window.innerWidth, h: window.innerHeight },\n };\n}\n\nexport function fireManualPageView(client: TrackingClient): void {\n const metadata = buildPageViewMetadata();\n client.trackEvent({\n eventType: 'page_view',\n pageUrl: typeof window === 'undefined' ? null : window.location.href,\n metadata: metadata as unknown as Record<string, unknown> | null,\n });\n}\n\nexport interface AttachAutoPageViewOptions {\n /** Skip the initial fire on attach (useful when the host already fired one). */\n skipInitial?: boolean;\n}\n\nexport function attachAutoPageView(\n client: TrackingClient,\n options: AttachAutoPageViewOptions = {},\n): () => void {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => {};\n }\n\n let lastPath = window.location.pathname + window.location.search;\n\n function maybeFire(): void {\n const current = window.location.pathname + window.location.search;\n if (current === lastPath)\n return;\n lastPath = current;\n fireManualPageView(client);\n }\n\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n function patchedPushState(this: History, ...args: Parameters<History['pushState']>): void {\n originalPushState(...args);\n // Defer one tick so the URL is settled before we read it.\n setTimeout(maybeFire, 0);\n }\n function patchedReplaceState(this: History, ...args: Parameters<History['replaceState']>): void {\n originalReplaceState(...args);\n setTimeout(maybeFire, 0);\n }\n\n history.pushState = patchedPushState as History['pushState'];\n history.replaceState = patchedReplaceState as History['replaceState'];\n window.addEventListener('popstate', maybeFire);\n\n if (!options.skipInitial)\n fireManualPageView(client);\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n window.removeEventListener('popstate', maybeFire);\n };\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState, setConsentState, type ConsentState } from '../../tracking-core/src/index';\n\nexport function ConsentBanner() {\n const [consentState, setBannerConsentState] = useState<ConsentState>('pending');\n const [isReady, setIsReady] = useState(false);\n\n useEffect(() => {\n setBannerConsentState(getConsentState());\n setIsReady(true);\n }, []);\n\n if (!isReady || consentState !== 'pending')\n return null;\n\n const handleAccept = (): void => {\n setConsentState('granted');\n setBannerConsentState('granted');\n };\n\n const handleDecline = (): void => {\n setConsentState('denied');\n setBannerConsentState('denied');\n };\n\n return (\n <div className=\"fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg\">\n <div className=\"mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <p className=\"text-sm text-gray-700\">\n We use cookies to understand ad performance and improve how our marketing works across visits.\n You can accept or decline this tracking.\n </p>\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleDecline}\n className=\"rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100\"\n >\n Decline\n </button>\n <button\n type=\"button\"\n onClick={handleAccept}\n className=\"rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800\"\n >\n Accept\n </button>\n </div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState } from '../../tracking-core/src/index';\nimport {\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n type ConsentState,\n type TrackingParams,\n} from '../../tracking-core/src/index';\n\nexport function useGclid(): string | null {\n const [gclid, setGclid] = useState<string | null>(null);\n\n useEffect(() => {\n setGclid(getCookieValueFromDocument('gclid'));\n }, []);\n\n return gclid;\n}\n\nexport function useTrackingParams(): TrackingParams {\n const [trackingParams, setTrackingParams] = useState<TrackingParams>(createEmptyTrackingParams());\n\n useEffect(() => {\n setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));\n }, []);\n\n return trackingParams;\n}\n\nexport function useConsentState(): ConsentState {\n const [consentState, setConsentStateValue] = useState<ConsentState>('pending');\n\n useEffect(() => {\n setConsentStateValue(getConsentState());\n\n const handleStorage = (event: StorageEvent): void => {\n if (event.key === 'consent_state')\n setConsentStateValue(getConsentState());\n };\n\n window.addEventListener('storage', handleStorage);\n\n return () => {\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return consentState;\n}\n","'use client';\n\nimport Script from 'next/script';\n\nimport {\n createConsentDefaultScript,\n createConsentRestoreScript,\n createGtagInitScript,\n} from '../../tracking-core/src/index';\n\nexport interface GoogleAdsTrackingProps {\n gtagId: string;\n}\n\nexport function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps) {\n return (\n <>\n <Script id=\"consent-default\" strategy=\"beforeInteractive\">\n {createConsentDefaultScript()}\n </Script>\n <Script\n src={`https://www.googletagmanager.com/gtag/js?id=${gtagId}`}\n strategy=\"afterInteractive\"\n />\n <Script id=\"gtag-init\" strategy=\"afterInteractive\">\n {createGtagInitScript(gtagId)}\n </Script>\n <Script id=\"consent-restore\" strategy=\"afterInteractive\">\n {createConsentRestoreScript()}\n </Script>\n </>\n );\n}\n","'use client';\n\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ReactNode,\n} from 'react';\n\nimport {\n createTrackingClient,\n fireManualPageView,\n type TrackingClient,\n} from '../../tracking-core/src/index';\n\nexport interface TrackingProviderProps {\n /** Public tracking API key issued for this business. Required. */\n apiKey: string;\n /** Full ingest endpoint base URL, e.g. https://api.aranova.io/tracking. Required. */\n endpoint: string;\n /** Disable automatic page_view emission (manual tracking only). */\n disableAutoPageView?: boolean;\n /** Optional debug flag — currently a no-op, reserved for verbose logging. */\n debug?: boolean;\n children: ReactNode;\n}\n\nconst TrackingContext = createContext<TrackingClient | null>(null);\n\n/**\n * Provider for App Router projects. Uses `usePathname` for SPA route detection\n * instead of patching `history.pushState`, because Next's router does not\n * always go through the History API for transitions.\n *\n * `gtagId` lives on the existing <GoogleAdsTracking> component — keep them\n * separate so a client without paid Google Ads can install just this provider.\n */\nexport function TrackingProvider({\n apiKey,\n endpoint,\n disableAutoPageView,\n debug,\n children,\n}: TrackingProviderProps) {\n if (!apiKey)\n throw new Error('TrackingProvider: apiKey is required');\n if (!endpoint)\n throw new Error('TrackingProvider: endpoint is required');\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const clientRef = useRef<TrackingClient | null>(null);\n if (clientRef.current === null) {\n clientRef.current = createTrackingClient({\n apiKey,\n endpoint,\n surface: 'next',\n packageName: '@aranova/tracking-next',\n debug,\n });\n }\n\n // Fire page_view on every route change. Includes initial mount because both\n // pathname and searchParams are stable values that change on navigation.\n useEffect(() => {\n if (disableAutoPageView)\n return;\n const client = clientRef.current;\n if (client === null)\n return;\n fireManualPageView(client);\n }, [pathname, searchParams, disableAutoPageView]);\n\n useEffect(() => {\n return () => {\n clientRef.current?.destroy();\n clientRef.current = null;\n };\n }, []);\n\n const contextValue = useMemo(() => clientRef.current, []);\n\n return (\n <TrackingContext.Provider value={contextValue}>\n {children}\n </TrackingContext.Provider>\n );\n}\n\nexport function useTracking(): TrackingClient {\n const client = useContext(TrackingContext);\n if (client === null)\n throw new Error('useTracking must be called inside a <TrackingProvider>');\n return client;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","Script","import_react","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -40,6 +40,7 @@ function createTrackingClientContext(surface, input = {}) {
40
40
  function createTrackingSessionUpsertPayload(trackingParams, input, context) {
41
41
  return {
42
42
  session_id: input.sessionId,
43
+ visitor_id: input.visitorId ?? null,
43
44
  gclid: trackingParams.gclid,
44
45
  fbclid: trackingParams.fbclid,
45
46
  utm_source: trackingParams.utm_source,
@@ -145,8 +146,8 @@ function getTrackingQueryValues(searchParams) {
145
146
  function getCookieValueFromDocument(key) {
146
147
  if (typeof document === "undefined")
147
148
  return null;
148
- const cookies2 = document.cookie ? document.cookie.split("; ") : [];
149
- const match = cookies2.find((cookie) => cookie.startsWith(`${key}=`));
149
+ const cookies = document.cookie ? document.cookie.split("; ") : [];
150
+ const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
150
151
  if (!match)
151
152
  return null;
152
153
  const [, rawValue = ""] = match.split("=");
@@ -171,6 +172,248 @@ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ?
171
172
  return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
172
173
  }
173
174
 
175
+ // ../tracking-core/src/session.ts
176
+ var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
177
+ var SESSION_STORAGE_KEY = "aranova_tracking_session";
178
+ var SESSION_IDLE_MS = 30 * 60 * 1e3;
179
+ function safeUuid() {
180
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
181
+ return crypto.randomUUID();
182
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
183
+ }
184
+ function readLocalStorage(key) {
185
+ try {
186
+ return window.localStorage.getItem(key);
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ function writeLocalStorage(key, value) {
192
+ try {
193
+ window.localStorage.setItem(key, value);
194
+ } catch {
195
+ }
196
+ }
197
+ function getVisitorId() {
198
+ if (typeof window === "undefined")
199
+ return safeUuid();
200
+ const existing = readLocalStorage(VISITOR_STORAGE_KEY);
201
+ if (existing && existing.length > 0)
202
+ return existing;
203
+ const fresh = safeUuid();
204
+ writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
205
+ return fresh;
206
+ }
207
+ function getOrRotateSessionId(now = Date.now()) {
208
+ if (typeof window === "undefined")
209
+ return safeUuid();
210
+ const raw = readLocalStorage(SESSION_STORAGE_KEY);
211
+ if (raw) {
212
+ try {
213
+ const parsed = JSON.parse(raw);
214
+ if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
215
+ if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
216
+ const refreshed = { id: parsed.id, last_event_at: now };
217
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
218
+ return parsed.id;
219
+ }
220
+ }
221
+ } catch {
222
+ }
223
+ }
224
+ const fresh = { id: safeUuid(), last_event_at: now };
225
+ writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
226
+ return fresh.id;
227
+ }
228
+
229
+ // ../tracking-core/src/ingest.ts
230
+ var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
231
+ var DEFAULT_MAX_QUEUE_SIZE = 10;
232
+ var HARD_MAX_BATCH = 50;
233
+ var API_KEY_HEADER = "X-Aranova-Api-Key";
234
+ function buildContext(surface, sdkVersion, packageName) {
235
+ return {
236
+ surface,
237
+ sdk_version: sdkVersion,
238
+ package_name: packageName,
239
+ site_origin: typeof window === "undefined" ? null : window.location.origin,
240
+ page_title: typeof document === "undefined" ? null : document.title || null,
241
+ referrer: typeof document === "undefined" ? null : document.referrer || null
242
+ };
243
+ }
244
+ function readTrackingParams() {
245
+ if (typeof window === "undefined")
246
+ return createEmptyTrackingParams();
247
+ try {
248
+ captureTrackingParamsFromLocation();
249
+ } catch {
250
+ }
251
+ return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
252
+ }
253
+ function consentSnapshot() {
254
+ try {
255
+ return { state: getConsentState() };
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+ async function postWithFetch(url, body, apiKey, keepalive) {
261
+ if (typeof fetch !== "function")
262
+ return;
263
+ try {
264
+ await fetch(url, {
265
+ method: "POST",
266
+ headers: {
267
+ "Content-Type": "application/json",
268
+ [API_KEY_HEADER]: apiKey
269
+ },
270
+ body,
271
+ keepalive,
272
+ // CORS is open on the tracking endpoint; never send cookies.
273
+ credentials: "omit",
274
+ mode: "cors"
275
+ });
276
+ } catch {
277
+ }
278
+ }
279
+ function createTrackingClient(config) {
280
+ const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
281
+ const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
282
+ const sdkVersion = config.sdkVersion ?? null;
283
+ const packageName = config.packageName ?? null;
284
+ const endpointBase = config.endpoint.replace(/\/$/, "");
285
+ const eventsUrl = `${endpointBase}/events`;
286
+ let queue = [];
287
+ let flushTimer = null;
288
+ let firstPage = null;
289
+ let destroyed = false;
290
+ const visitorId = getVisitorId();
291
+ let sessionId = getOrRotateSessionId();
292
+ if (typeof window !== "undefined")
293
+ firstPage = window.location.href;
294
+ function buildSessionPayload() {
295
+ sessionId = getOrRotateSessionId();
296
+ const params = readTrackingParams();
297
+ const context = buildContext(config.surface, sdkVersion, packageName);
298
+ return {
299
+ session_id: sessionId,
300
+ visitor_id: visitorId,
301
+ gclid: params.gclid,
302
+ fbclid: params.fbclid,
303
+ utm_source: params.utm_source,
304
+ utm_medium: params.utm_medium,
305
+ utm_campaign: params.utm_campaign,
306
+ utm_term: params.utm_term,
307
+ utm_content: params.utm_content,
308
+ first_page: firstPage,
309
+ consent_state: consentSnapshot(),
310
+ context
311
+ };
312
+ }
313
+ function scheduleFlush() {
314
+ if (flushTimer !== null || destroyed)
315
+ return;
316
+ flushTimer = setTimeout(() => {
317
+ flushTimer = null;
318
+ void flush();
319
+ }, flushIntervalMs);
320
+ }
321
+ function clearScheduledFlush() {
322
+ if (flushTimer !== null) {
323
+ clearTimeout(flushTimer);
324
+ flushTimer = null;
325
+ }
326
+ }
327
+ async function flush() {
328
+ if (queue.length === 0)
329
+ return;
330
+ const events = queue.slice(0, HARD_MAX_BATCH);
331
+ queue = queue.slice(events.length);
332
+ clearScheduledFlush();
333
+ const body = {
334
+ session: buildSessionPayload(),
335
+ events
336
+ };
337
+ const serialized = JSON.stringify(body);
338
+ await postWithFetch(eventsUrl, serialized, config.apiKey, false);
339
+ }
340
+ function trackEvent(input) {
341
+ if (destroyed)
342
+ return;
343
+ if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
344
+ return;
345
+ const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
346
+ queue.push({
347
+ event_type: input.eventType,
348
+ page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
349
+ metadata: input.metadata ?? null,
350
+ occurred_at: occurredAt
351
+ });
352
+ if (queue.length >= maxQueueSize) {
353
+ void flush();
354
+ } else {
355
+ scheduleFlush();
356
+ }
357
+ }
358
+ function flushOnUnload() {
359
+ if (queue.length === 0)
360
+ return;
361
+ const events = queue.slice(0, HARD_MAX_BATCH);
362
+ queue = queue.slice(events.length);
363
+ clearScheduledFlush();
364
+ const body = {
365
+ session: buildSessionPayload(),
366
+ events
367
+ };
368
+ const serialized = JSON.stringify(body);
369
+ void postWithFetch(eventsUrl, serialized, config.apiKey, true);
370
+ }
371
+ if (typeof window !== "undefined") {
372
+ window.addEventListener("pagehide", flushOnUnload);
373
+ window.addEventListener("visibilitychange", () => {
374
+ if (document.visibilityState === "hidden") flushOnUnload();
375
+ });
376
+ }
377
+ return {
378
+ trackEvent,
379
+ flush,
380
+ getSessionId: () => sessionId,
381
+ getVisitorId: () => visitorId,
382
+ destroy: () => {
383
+ destroyed = true;
384
+ clearScheduledFlush();
385
+ queue = [];
386
+ if (typeof window !== "undefined") {
387
+ window.removeEventListener("pagehide", flushOnUnload);
388
+ }
389
+ }
390
+ };
391
+ }
392
+
393
+ // ../tracking-core/src/page-view.ts
394
+ function buildPageViewMetadata() {
395
+ if (typeof window === "undefined" || typeof document === "undefined")
396
+ return null;
397
+ return {
398
+ page: {
399
+ title: document.title || null,
400
+ path: window.location.pathname,
401
+ search: window.location.search,
402
+ hash: window.location.hash
403
+ },
404
+ referrer: document.referrer || null,
405
+ viewport: { w: window.innerWidth, h: window.innerHeight }
406
+ };
407
+ }
408
+ function fireManualPageView(client) {
409
+ const metadata = buildPageViewMetadata();
410
+ client.trackEvent({
411
+ eventType: "page_view",
412
+ pageUrl: typeof window === "undefined" ? null : window.location.href,
413
+ metadata
414
+ });
415
+ }
416
+
174
417
  // src/ConsentBanner.tsx
175
418
  import { useEffect, useState } from "react";
176
419
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -266,48 +509,77 @@ function GoogleAdsTracking({ gtagId }) {
266
509
  ] });
267
510
  }
268
511
 
269
- // src/middleware.ts
270
- import "server-only";
271
- import { NextResponse } from "next/server";
272
- function createTrackingMiddleware() {
273
- return (request) => {
274
- const response = NextResponse.next();
275
- const trackingValues = getTrackingQueryValues(request.nextUrl.searchParams);
276
- Object.entries(trackingValues).forEach(([param, value]) => {
277
- response.cookies.set(param, value, {
278
- maxAge: 7776e3,
279
- path: "/",
280
- httpOnly: false
281
- });
512
+ // src/TrackingProvider.tsx
513
+ import { usePathname, useSearchParams } from "next/navigation";
514
+ import {
515
+ createContext,
516
+ useContext,
517
+ useEffect as useEffect3,
518
+ useMemo,
519
+ useRef
520
+ } from "react";
521
+ import { jsx as jsx3 } from "react/jsx-runtime";
522
+ var TrackingContext = createContext(null);
523
+ function TrackingProvider({
524
+ apiKey,
525
+ endpoint,
526
+ disableAutoPageView,
527
+ debug,
528
+ children
529
+ }) {
530
+ if (!apiKey)
531
+ throw new Error("TrackingProvider: apiKey is required");
532
+ if (!endpoint)
533
+ throw new Error("TrackingProvider: endpoint is required");
534
+ const pathname = usePathname();
535
+ const searchParams = useSearchParams();
536
+ const clientRef = useRef(null);
537
+ if (clientRef.current === null) {
538
+ clientRef.current = createTrackingClient({
539
+ apiKey,
540
+ endpoint,
541
+ surface: "next",
542
+ packageName: "@aranova/tracking-next",
543
+ debug
282
544
  });
283
- return response;
284
- };
285
- }
286
-
287
- // src/server.ts
288
- import "server-only";
289
- import { cookies } from "next/headers";
290
- function getCookieStore() {
291
- return cookies();
545
+ }
546
+ useEffect3(() => {
547
+ if (disableAutoPageView)
548
+ return;
549
+ const client = clientRef.current;
550
+ if (client === null)
551
+ return;
552
+ fireManualPageView(client);
553
+ }, [pathname, searchParams, disableAutoPageView]);
554
+ useEffect3(() => {
555
+ return () => {
556
+ clientRef.current?.destroy();
557
+ clientRef.current = null;
558
+ };
559
+ }, []);
560
+ const contextValue = useMemo(() => clientRef.current, []);
561
+ return /* @__PURE__ */ jsx3(TrackingContext.Provider, { value: contextValue, children });
292
562
  }
293
- function getTrackingParamsServer() {
294
- const cookieStore = getCookieStore();
295
- return getTrackingParamsFromCookieReader((key) => cookieStore.get(key)?.value);
563
+ function useTracking() {
564
+ const client = useContext(TrackingContext);
565
+ if (client === null)
566
+ throw new Error("useTracking must be called inside a <TrackingProvider>");
567
+ return client;
296
568
  }
297
569
  export {
298
570
  ConsentBanner,
299
571
  GoogleAdsTracking,
300
572
  TRACKING_PARAM_KEYS,
573
+ TrackingProvider,
301
574
  captureTrackingParamsFromLocation,
302
575
  createTrackingClientContext,
303
576
  createTrackingEventCreatePayload,
304
- createTrackingMiddleware,
305
577
  createTrackingSessionUpsertPayload,
306
578
  getConsentState,
307
- getTrackingParamsServer,
308
579
  setConsentState,
309
580
  useConsentState,
310
581
  useGclid,
582
+ useTracking,
311
583
  useTrackingParams
312
584
  };
313
585
  //# sourceMappingURL=index.mjs.map